mirror of
https://github.com/Aviortheking/games.git
synced 2025-06-08 21:09:55 +00:00
uodate
This commit is contained in:
parent
2143f9887e
commit
4bb1f17467
4755
package-lock.json
generated
4755
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,6 @@
|
||||
"test": "jest --config jext.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dzeio/components": "^0.10.1",
|
||||
"@dzeio/object-util": "^1.2.0",
|
||||
"critters": "^0.0.10",
|
||||
"easy-sitemap": "^1.0.0",
|
||||
|
@ -45,4 +45,9 @@ export default class Vector2D {
|
||||
parseFloat(this.y.toFixed(nDecimal))
|
||||
)
|
||||
}
|
||||
|
||||
public set(x: number, y: number) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,21 @@ export interface ComponentState {
|
||||
isColliding?: string
|
||||
}
|
||||
|
||||
export type StaticComponent<
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
T extends {} | void = {} | void
|
||||
> =
|
||||
new (params: T | undefined) => Component2D<T>
|
||||
|
||||
/**
|
||||
* 2D Component
|
||||
*/
|
||||
export default abstract class Component2D {
|
||||
export default abstract class Component2D<
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
T extends {} | void = {} | void
|
||||
> {
|
||||
|
||||
public params: T = {} as T
|
||||
|
||||
/**
|
||||
* Indicate how the component is rendered
|
||||
@ -75,6 +86,12 @@ export default abstract class Component2D {
|
||||
*/
|
||||
public debug?: boolean
|
||||
|
||||
public constructor(it: T | void) {
|
||||
if (it) {
|
||||
this.params = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function run when the component is initialized
|
||||
*/
|
||||
|
@ -1,25 +1,57 @@
|
||||
import GameEngine from 'GameEngine'
|
||||
import ComponentDebug from 'GameEngine/2D/Debug/ComponentDebug'
|
||||
import Vector2D from 'GameEngine/2D/Vector2D'
|
||||
import Component2D from 'GameEngine/Component2D'
|
||||
import Renderer from 'GameEngine/Renderer'
|
||||
import TextRenderer from 'GameEngine/Renderer/TextRenderer'
|
||||
|
||||
export default class FPSCounter extends Component2D {
|
||||
export default class FPSCounter extends Component2D<{textColor?: string, size?: number}> {
|
||||
|
||||
|
||||
public position: Vector2D = new Vector2D(0,0)
|
||||
public position: Vector2D = new Vector2D(10,8)
|
||||
public scale: Vector2D = new Vector2D(1, 1)
|
||||
public origin: Vector2D = new Vector2D(0, 0)
|
||||
public childs: Array<Component2D> = [new ComponentDebug(this)]
|
||||
|
||||
public renderer: TextRenderer = new TextRenderer(this, {text: 'pouet'})
|
||||
public renderer: TextRenderer = new TextRenderer(this, {text: 'loading...'})
|
||||
|
||||
private lastUpdate: number = new Date().getTime()
|
||||
private length = 1
|
||||
private previousFrameTimes: Array<number> = []
|
||||
private lastUpdate = window.performance.now() * 1000
|
||||
|
||||
public init() {
|
||||
const fps = GameEngine.getGameEngine().options?.goalFramerate
|
||||
if (!fps || fps < 1) {
|
||||
this.length = 60
|
||||
} else {
|
||||
this.length = fps
|
||||
}
|
||||
|
||||
if (this.params.textColor) {
|
||||
this.renderer.color = this.params.textColor
|
||||
}
|
||||
|
||||
if (this.params.size) {
|
||||
this.renderer.size = this.params.size
|
||||
}
|
||||
}
|
||||
|
||||
public update() {
|
||||
const now = new Date().getTime()
|
||||
this.renderer.text = (1000 / (now - this.lastUpdate)).toFixed(2)
|
||||
this.lastUpdate = now
|
||||
const t = GameEngine.getGameEngine().lastFrame
|
||||
// if (!t) {return}
|
||||
// console.log(this.previousFrameTimes, t)
|
||||
const diff = t - this.lastUpdate
|
||||
this.lastUpdate = t
|
||||
this.previousFrameTimes.push(diff)
|
||||
if (this.previousFrameTimes.length > this.length) {
|
||||
this.previousFrameTimes.shift()
|
||||
}
|
||||
const time = (this.previousFrameTimes.reduce((p, c) => p + c, 0)) / this.previousFrameTimes.length
|
||||
if (time === 0) {
|
||||
this.renderer.text = 'a lot'
|
||||
} else {
|
||||
this.renderer.text = (1000 / time).toFixed(2)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,17 +29,14 @@ export default class Scene {
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.components.forEach((v) => {
|
||||
if (v.init) {
|
||||
v.init()
|
||||
}
|
||||
})
|
||||
for await (const component of this.components) {
|
||||
await component.init?.()
|
||||
}
|
||||
}
|
||||
|
||||
public async update() {
|
||||
for (let index = 0; index < this.components.length; index++) {
|
||||
const component = this.components[index];
|
||||
await this.updateComponent(component, index)
|
||||
await this.updateComponent(this.components[index], index)
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,8 +96,8 @@ export default class Scene {
|
||||
if (debug) {
|
||||
console.log('Processing childs', v)
|
||||
}
|
||||
for await (const child of v.childs) {
|
||||
await this.updateComponent(child)
|
||||
for (let cIndex = 0; cIndex < v.childs.length; cIndex++) {
|
||||
await this.updateComponent(v.childs[cIndex], cIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,23 @@ export default class GameEngine {
|
||||
wasDown: false
|
||||
}
|
||||
public currentScene?: Scene
|
||||
|
||||
// last frame timestamp
|
||||
public lastFrame = 0
|
||||
|
||||
/**
|
||||
* last frame execution time in milliseconds
|
||||
*
|
||||
* @memberof GameEngine
|
||||
*/
|
||||
public frameTime = 0
|
||||
|
||||
private isRunning = false
|
||||
private timer = 16.6
|
||||
|
||||
|
||||
// timer between frames
|
||||
private timer = 0
|
||||
|
||||
|
||||
public constructor(
|
||||
id: string,
|
||||
@ -61,12 +76,8 @@ export default class GameEngine {
|
||||
ctx.imageSmoothingEnabled = false
|
||||
this.ctx = ctx
|
||||
|
||||
if (options?.goalFramerate) {
|
||||
if (options.goalFramerate === -1) {
|
||||
this.timer = 0
|
||||
} else {
|
||||
this.timer = 1000 / options.goalFramerate
|
||||
}
|
||||
if (options?.goalFramerate && options.goalFramerate >= 0) {
|
||||
this.timer = 1000 / options.goalFramerate
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,13 +91,11 @@ export default class GameEngine {
|
||||
return
|
||||
}
|
||||
this.isRunning = true
|
||||
requestAnimationFrame(() => {
|
||||
this.update()
|
||||
})
|
||||
this.currentScene?.init().then(() => this.update())
|
||||
document.addEventListener('mousemove', (ev) => {
|
||||
this.cursor.position = new Vector2D(
|
||||
ev.clientX / this.caseSize.x - (this.currentScene?.camera?.topLeft?.x ?? 0),
|
||||
ev.clientY / this.caseSize.y - (this.currentScene?.camera?.topLeft?.y ?? 0)
|
||||
(ev.clientX + window.scrollX) / this.caseSize.x - (this.currentScene?.camera?.topLeft?.x ?? 0),
|
||||
(ev.clientY + window.scrollY) / this.caseSize.y - (this.currentScene?.camera?.topLeft?.y ?? 0)
|
||||
)
|
||||
if (this.cursor.isDown) {
|
||||
this.cursor.wasDown = true
|
||||
@ -109,35 +118,66 @@ export default class GameEngine {
|
||||
|
||||
public async setScene(scene: Scene | string) {
|
||||
console.log('Setting scene', typeof scene === 'string' ? scene : scene.id)
|
||||
const wasRunning = this.isRunning
|
||||
if (wasRunning) {
|
||||
this.isRunning = false
|
||||
}
|
||||
await this.currentScene?.destroy()
|
||||
await this.currentScene?.init()
|
||||
if (wasRunning) {
|
||||
this.isRunning = true
|
||||
}
|
||||
this.currentScene = typeof scene === 'string' ? Scene.scenes[scene] : scene
|
||||
this.currentScene.setGameEngine(this)
|
||||
}
|
||||
|
||||
private update() {
|
||||
const now = new Date().getTime()
|
||||
if (!this.isRunning) {
|
||||
return
|
||||
}
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
if (this.options?.background) {
|
||||
this.ctx.fillStyle = this.options.background
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
}
|
||||
this.currentScene?.update()
|
||||
const diff = new Date().getTime() - now
|
||||
if (diff > this.timer) {
|
||||
requestAnimationFrame(() => {
|
||||
this.update()
|
||||
})
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
// this.update()
|
||||
requestAnimationFrame(() => {
|
||||
this.update()
|
||||
})
|
||||
}, this.timer - diff)
|
||||
}
|
||||
private async update() {
|
||||
// console.log('update')
|
||||
let frameFinished = true
|
||||
setInterval((it) => {
|
||||
// get current time
|
||||
const now = window.performance.now()
|
||||
|
||||
// game is not runnig, wait a frame
|
||||
if (!this.isRunning || !frameFinished) {
|
||||
// console.log('skip frame')
|
||||
// setTimeout(() => {
|
||||
// this.update()
|
||||
// }, this.timer)
|
||||
return
|
||||
}
|
||||
|
||||
// game is running too fast, wait until necessary
|
||||
if (this.lastFrame + this.timer > now ) {
|
||||
// console.log('skip frame')
|
||||
// setTimeout(() => {
|
||||
// this.update()
|
||||
// }, (this.lastFrame + this.timer) - now)
|
||||
return
|
||||
}
|
||||
// console.log('new frame')
|
||||
frameFinished = false
|
||||
|
||||
// if a background need to be drawn
|
||||
if (this.options?.background) {
|
||||
this.ctx.fillStyle = this.options.background
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
} else {
|
||||
// clear the previous frame
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)
|
||||
}
|
||||
|
||||
// update scene
|
||||
this.currentScene?.update()
|
||||
|
||||
// calculate for next frame
|
||||
this.lastFrame = window.performance.now()
|
||||
this.frameTime = window.performance.now() - now
|
||||
frameFinished = true
|
||||
// this.update()
|
||||
// requestAnimationFrame(() => {
|
||||
// })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
48
src/games/city/Button.ts
Normal file
48
src/games/city/Button.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { stat } from 'fs'
|
||||
import GameEngine from 'GameEngine'
|
||||
import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D'
|
||||
import ColliderDebugger from 'GameEngine/2D/Debug/ColliderDebugger'
|
||||
import PointDebugger from 'GameEngine/2D/Debug/PointDebugger'
|
||||
import Vector2D from 'GameEngine/2D/Vector2D'
|
||||
import Component2D, { ComponentState } from 'GameEngine/Component2D'
|
||||
import Renderer from 'GameEngine/Renderer'
|
||||
import RectRenderer from 'GameEngine/Renderer/RectRenderer'
|
||||
import { globalState } from '.'
|
||||
import Cursor from './Cursor'
|
||||
import Space from './Space'
|
||||
import TextComponent from './TextComponent'
|
||||
|
||||
export default class Button extends Component2D {
|
||||
private static isOnCursor = false
|
||||
|
||||
public size = {
|
||||
width: 3, height: 1
|
||||
}
|
||||
|
||||
public renderer: RectRenderer = new RectRenderer(this)
|
||||
public collider: BoxCollider2D = new BoxCollider2D(this, 'click')
|
||||
|
||||
public position: Vector2D = new Vector2D(10, 5)
|
||||
|
||||
public scale: Vector2D = new Vector2D(20, 10)
|
||||
|
||||
public constructor() {
|
||||
super()
|
||||
|
||||
this.childs = [
|
||||
new TextComponent(this, 'remèttre à zéro', 'bold', 10, 'white')
|
||||
]
|
||||
this.renderer.material = 'black'
|
||||
}
|
||||
|
||||
public update(state: ComponentState): void | Promise<void> {
|
||||
if (state.isColliding === 'click' || state.isColliding === 'down') {
|
||||
Space.shouldReset = true
|
||||
globalState.x10Moved = 0
|
||||
globalState.x20Moved = 0
|
||||
} else {
|
||||
Space.shouldReset = false
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +1,19 @@
|
||||
import { stat } from 'fs'
|
||||
import GameEngine from 'GameEngine'
|
||||
import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D'
|
||||
import ColliderDebugger from 'GameEngine/2D/Debug/ColliderDebugger'
|
||||
import PointDebugger from 'GameEngine/2D/Debug/PointDebugger'
|
||||
import Vector2D from 'GameEngine/2D/Vector2D'
|
||||
import Component2D, { ComponentState } from 'GameEngine/Component2D'
|
||||
import Renderer from 'GameEngine/Renderer'
|
||||
import RectRenderer from 'GameEngine/Renderer/RectRenderer'
|
||||
import { globalState } from '.'
|
||||
import Cursor from './Cursor'
|
||||
import TextComponent from './TextComponent'
|
||||
|
||||
export default class Space extends Component2D {
|
||||
private static isOnCursor = false
|
||||
public static shouldReset = false
|
||||
|
||||
public size = {
|
||||
width: 3, height: 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
public renderer: RectRenderer = new RectRenderer(this)
|
||||
public collider: BoxCollider2D = new BoxCollider2D(this, 'click')
|
||||
|
||||
@ -27,19 +22,26 @@ export default class Space extends Component2D {
|
||||
public scale: Vector2D = new Vector2D(30, 10)
|
||||
|
||||
private posBeforeCursor: Vector2D | null = null
|
||||
private basePosition: Vector2D
|
||||
|
||||
private hasMoved = false
|
||||
|
||||
public constructor(
|
||||
position: Vector2D,
|
||||
size: Vector2D,
|
||||
private cursor: Cursor,
|
||||
private placeableRects: Array<[Vector2D, Vector2D]>,
|
||||
private log: boolean = false
|
||||
) {
|
||||
super()
|
||||
this.position = position
|
||||
this.basePosition = position
|
||||
// this.debug = true
|
||||
this.scale = size
|
||||
|
||||
const text = `${size.x}x${size.y}`
|
||||
this.childs = [
|
||||
new TextComponent(this, text, 'bold', 16, 'blue')
|
||||
new TextComponent(this, text, 'bold', 8, 'blue')
|
||||
]
|
||||
}
|
||||
|
||||
@ -50,25 +52,22 @@ export default class Space extends Component2D {
|
||||
this.renderer.material = 'white'
|
||||
this.renderer.stroke = {color: 'black', width: 3}
|
||||
const cursor = GameEngine.getGameEngine().cursor
|
||||
// if (this.log) console.log(point)
|
||||
if (this.log) console.log(Space.shouldReset)
|
||||
if (state.isColliding === 'click' || state.isColliding === 'down') {
|
||||
if (!this.hasMoved) {
|
||||
this.hasMoved = true
|
||||
if (this.scale.x === 10) {
|
||||
globalState.x10Moved ++
|
||||
} else {
|
||||
globalState.x20Moved ++
|
||||
}
|
||||
}
|
||||
this.renderer.stroke = {color: 'green', width: 3}
|
||||
if (!this.posBeforeCursor) {
|
||||
this.posBeforeCursor = this.position
|
||||
}
|
||||
Space.isOnCursor = true
|
||||
// console.log('follow cursor', cursor.position, this.position)
|
||||
this.position = cursor.position.decimalCount(0)
|
||||
let canPlace = false
|
||||
for (const placeableRect of this.placeableRects) {
|
||||
if (point.isIn(placeableRect[0], this.scale.sub(placeableRect[1]))) {
|
||||
canPlace = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!canPlace) this.renderer.stroke = {color: 'red', width: 3}
|
||||
else this.posBeforeCursor = this.position
|
||||
} else if (this.posBeforeCursor) {
|
||||
this.position = cursor.position
|
||||
let canPlace = false
|
||||
for (const placeableRect of this.placeableRects) {
|
||||
if (point.isIn(placeableRect[0], this.scale.sub(placeableRect[1]))) {
|
||||
@ -77,10 +76,31 @@ export default class Space extends Component2D {
|
||||
}
|
||||
}
|
||||
if (!canPlace) {
|
||||
this.position = this.posBeforeCursor
|
||||
this.renderer.stroke = {color: 'red', width: 3}
|
||||
}
|
||||
else {
|
||||
this.posBeforeCursor = this.position
|
||||
}
|
||||
} else if (this.posBeforeCursor) {
|
||||
const futurePosition = this.position.decimalCount(0)
|
||||
let canPlace = false
|
||||
for (const placeableRect of this.placeableRects) {
|
||||
if (point.isIn(placeableRect[0], this.scale.sub(placeableRect[1]))) {
|
||||
canPlace = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!canPlace) {
|
||||
this.position = this.posBeforeCursor.decimalCount(0)
|
||||
} else {
|
||||
this.position = futurePosition
|
||||
}
|
||||
Space.isOnCursor = false
|
||||
this.posBeforeCursor = null
|
||||
}
|
||||
|
||||
if (Space.shouldReset) {
|
||||
this.position = this.basePosition
|
||||
this.hasMoved = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
51
src/games/city/Text.ts
Normal file
51
src/games/city/Text.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { stat } from 'fs'
|
||||
import GameEngine from 'GameEngine'
|
||||
import BoxCollider2D from 'GameEngine/2D/Collision/BoxCollider2D'
|
||||
import ColliderDebugger from 'GameEngine/2D/Debug/ColliderDebugger'
|
||||
import PointDebugger from 'GameEngine/2D/Debug/PointDebugger'
|
||||
import Vector2D from 'GameEngine/2D/Vector2D'
|
||||
import Component2D, { ComponentState } from 'GameEngine/Component2D'
|
||||
import Renderer from 'GameEngine/Renderer'
|
||||
import RectRenderer from 'GameEngine/Renderer/RectRenderer'
|
||||
import TextRenderer from 'GameEngine/Renderer/TextRenderer'
|
||||
import { globalState } from '.'
|
||||
import Cursor from './Cursor'
|
||||
|
||||
export default class Text extends Component2D {
|
||||
|
||||
|
||||
public renderer: TextRenderer = new TextRenderer(this)
|
||||
|
||||
// public position: Vector2D = new Vector2D(0, 0)
|
||||
|
||||
public constructor(private type: keyof typeof globalState, weight?: 'bold', size?: number, color?: string) {
|
||||
super()
|
||||
switch (type) {
|
||||
case 'x10Moved':
|
||||
this.position = new Vector2D(10, 40)
|
||||
break
|
||||
case 'x20Moved':
|
||||
this.position = new Vector2D(10, 43)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
this.renderer.text = 'nique'
|
||||
this.renderer.weight = weight
|
||||
this.renderer.size = size
|
||||
this.renderer.color = color
|
||||
}
|
||||
|
||||
public update(state: ComponentState): void | Promise<void> {
|
||||
switch (this.type) {
|
||||
case 'x10Moved':
|
||||
this.renderer.text = '10x20: ' + (5 - globalState.x10Moved)
|
||||
break
|
||||
case 'x20Moved':
|
||||
this.renderer.text = '10x20: ' + (5 - globalState.x20Moved)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
7
src/games/city/index.ts
Normal file
7
src/games/city/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export const globalState: {
|
||||
x20Moved: number
|
||||
x10Moved: number
|
||||
} = {
|
||||
x10Moved: 0,
|
||||
x20Moved: 0
|
||||
}
|
@ -2,7 +2,6 @@ import React from 'react'
|
||||
import App from 'next/app'
|
||||
|
||||
import PlausibleProvider from 'next-plausible'
|
||||
import '@dzeio/components/style.css'
|
||||
|
||||
export default class CApp extends App {
|
||||
|
||||
|
@ -1,24 +1,20 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import React from 'react'
|
||||
import { Text, Link } from '@dzeio/components'
|
||||
import GameEngine from 'GameEngine'
|
||||
import Scene from 'GameEngine/Scene'
|
||||
import Item from 'games/tictactoe/Item'
|
||||
import Line from 'games/tictactoe/Line'
|
||||
import Start from 'games/tictactoe/Menu/Start'
|
||||
import FPSCounter from 'GameEngine/Components/FPSCounter'
|
||||
import ComponentDebug from 'GameEngine/2D/Debug/ComponentDebug'
|
||||
import Tile from 'games/city/Tile'
|
||||
import Space from 'games/city/Space'
|
||||
import Vector2D from 'GameEngine/2D/Vector2D'
|
||||
import Cursor from 'games/city/Cursor'
|
||||
import Button from 'games/city/Button'
|
||||
import Text from 'games/city/Text'
|
||||
import FPSCounter from 'GameEngine/Components/FPSCounter'
|
||||
|
||||
export default class Snake extends React.PureComponent {
|
||||
|
||||
public async componentDidMount() {
|
||||
const ge = new GameEngine('#test', {
|
||||
caseCount: [208,104],
|
||||
debugColliders: true,
|
||||
debugColliders: false,
|
||||
goalFramerate: 60
|
||||
})
|
||||
const mainScene = new Scene('Menu')
|
||||
@ -55,9 +51,7 @@ export default class Snake extends React.PureComponent {
|
||||
const baseX = width / 2 + 1
|
||||
const baseY = i % 2 === 0 ? 50 : 70
|
||||
|
||||
const it = new Space(new Vector2D(width, height), cursor, placeableRects)
|
||||
it.position.x = baseX
|
||||
it.position.y = baseY
|
||||
const it = new Space(new Vector2D(baseX, baseY), new Vector2D(width, height), cursor, placeableRects)
|
||||
// it.debug = i === 0
|
||||
mainScene.addComponent(
|
||||
it
|
||||
@ -69,6 +63,13 @@ export default class Snake extends React.PureComponent {
|
||||
// new Space(new Vector2D(20, 10), cursor, placeableRects, true)
|
||||
// )
|
||||
|
||||
mainScene.addComponent(
|
||||
new Button(),
|
||||
new Text('x10Moved', undefined, 16),
|
||||
new Text('x20Moved', undefined, 16),
|
||||
new FPSCounter({textColor: 'white', size: 32})
|
||||
)
|
||||
|
||||
await ge.setScene(mainScene)
|
||||
ge.start()
|
||||
}
|
||||
@ -77,9 +78,6 @@ export default class Snake extends React.PureComponent {
|
||||
public render = () => (
|
||||
<>
|
||||
<canvas id="test" width="2080" height="1040" style={{backgroundImage: 'url(\'/assets/city/background.png\')'}}></canvas>
|
||||
<Text>
|
||||
<span id="debug"></span>
|
||||
</Text>
|
||||
</>
|
||||
)
|
||||
|
||||
|
@ -1,17 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
import { Link, Text } from '@dzeio/components'
|
||||
|
||||
export default class Index extends React.Component {
|
||||
|
||||
public render = () => (
|
||||
<main>
|
||||
<Text>
|
||||
<Link href="/pokemon-shuffle">Pokémon Shuffle</Link>
|
||||
</Text>
|
||||
<Text>
|
||||
<Link href="/tictactoe">TicTacToe mais ou il y a eu beaucoup trop de temps de passé sur le jeux</Link>
|
||||
</Text>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
@ -1,459 +0,0 @@
|
||||
import { Button, Text, Util, NotificationManager, Col, Row, Input } from '@dzeio/components'
|
||||
import { GetServerSideProps } from 'next'
|
||||
import React, { MouseEvent as ReactMouseEvent } from 'react'
|
||||
import css from './pokemon-shuffle.module.styl'
|
||||
|
||||
interface Props {
|
||||
itemCount: number
|
||||
boardSize: number
|
||||
}
|
||||
|
||||
interface Cell {
|
||||
id: number
|
||||
id2: number
|
||||
horizontalCombo?: true
|
||||
verticalCombo?: true
|
||||
justSpawned?: true
|
||||
isFalling?: true
|
||||
}
|
||||
|
||||
interface States {
|
||||
items: Array<Array<Cell | undefined>>
|
||||
loading?: true
|
||||
movingItem?: {x: number, y: number, cell: Cell}
|
||||
damage: number
|
||||
turn: number
|
||||
combo: number
|
||||
comboMax: number
|
||||
cursorPos: {x: number, y: number}
|
||||
hitBoss: boolean
|
||||
boss: {
|
||||
hp: number
|
||||
id: number
|
||||
}
|
||||
}
|
||||
|
||||
export default class PokemonShuffle extends React.Component<Props, States> {
|
||||
|
||||
public state: States = {
|
||||
items: [[]],
|
||||
damage: 0,
|
||||
turn: 0,
|
||||
combo: 0,
|
||||
comboMax: 0,
|
||||
cursorPos: {x: 0, y: 0},
|
||||
hitBoss: false,
|
||||
boss: {
|
||||
hp: 10e4,
|
||||
id: 2
|
||||
}
|
||||
}
|
||||
|
||||
private n = this.props.boardSize
|
||||
|
||||
|
||||
public async componentDidMount() {
|
||||
await this.start()
|
||||
const boss = document.querySelector<HTMLElement>(`.${css.boss}`)
|
||||
console.log(boss)
|
||||
this.setState({
|
||||
boss: Object.assign(this.state.boss, {pos: [boss?.offsetTop ?? 0, boss?.offsetLeft ?? 0]}),
|
||||
comboMax: parseInt(window.localStorage.getItem('pokemon-shuffle/comboMax') ?? '0', 10)
|
||||
})
|
||||
}
|
||||
|
||||
public render = () => (
|
||||
<main>
|
||||
<form method="GET">
|
||||
<Input name="boardSize" type="number" placeholder="Nombre de lignes" defaultValue={this.props.boardSize} max={10} />
|
||||
<Input name="itemCount" type="number" placeholder="Nombre de pokémon différents" defaultValue={this.props.itemCount - 1} max={810} />
|
||||
<Button>Changer</Button>
|
||||
</form>
|
||||
<ul>
|
||||
<li><Text>Tour: {this.state.turn}</Text></li>
|
||||
<li><Text>Combo: {this.state.combo}, Max: {this.state.comboMax}</Text></li>
|
||||
<li><Text>Points: {this.state.damage}</Text></li>
|
||||
</ul>
|
||||
<Row align="center" >
|
||||
<Col>
|
||||
<Row direction="column" justify="center" align="center" >
|
||||
<Col nogrow>
|
||||
<Text className={Util.buildClassName(
|
||||
css[`icon-${this.state.boss.id}`],
|
||||
css.cell,
|
||||
css.noAnimation,
|
||||
css.boss,
|
||||
[css.loading, this.state.hitBoss]
|
||||
)}>
|
||||
</Text>
|
||||
</Col>
|
||||
<Col nogrow>
|
||||
<div className={css.bossBar}>
|
||||
<div>
|
||||
<div style={{width: `${Math.max(0, 100 - (100 * this.state.damage / this.state.boss.hp))}%`}}></div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col>
|
||||
<table style={{margin: 'auto'}} className={css.table}>
|
||||
<tbody className={Util.buildClassName(css.table, [css.loading, this.state.loading])}>
|
||||
{this.state.items.map((row, y) => (
|
||||
<tr key={y}>
|
||||
{row.map((cell, x) => (
|
||||
<td
|
||||
key={cell?.id2 ?? x}
|
||||
onClick={this.onCellClick(x, y)}
|
||||
className={css.cellParent}
|
||||
>
|
||||
{/* <Text>{JSON.stringify(cell)}</Text> */}
|
||||
{cell && (
|
||||
<Text className={Util.buildClassName(
|
||||
css[`icon-${cell.id}`],
|
||||
css.cell,
|
||||
[css.isFalling, cell.isFalling],
|
||||
[css.justSpawned, cell.justSpawned],
|
||||
[css.explode, cell.horizontalCombo || cell.verticalCombo]
|
||||
)}>
|
||||
</Text>
|
||||
)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</Col>
|
||||
</Row>
|
||||
<Button onClick={this.start}>Start!</Button>
|
||||
{this.state.movingItem && (
|
||||
<div className={css.hoverItem} style={{
|
||||
left: this.state.cursorPos.x,
|
||||
top: this.state.cursorPos.y,
|
||||
// transform: 'scale(2)'
|
||||
}}>
|
||||
<Text className={Util.buildClassName(css[`icon-${this.state.movingItem.cell?.id}`], css.cell)}>
|
||||
<div></div>
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
<Text>
|
||||
TODO list:
|
||||
</Text>
|
||||
<ul>
|
||||
<li><Text>Faire que les clear ce fasse de manière Async</Text></li>
|
||||
<li><Text>Utiliser le système de damages de Pokémon Shuffle https://bulbapedia.bulbagarden.net/wiki/Pok%C3%A9mon_Shuffle#Damage</Text></li>
|
||||
<li><Text>Mode VS (Voir si on fait en local et/ou en ligne avec le Websocket)</Text></li>
|
||||
<li><Text>Système de classement en ligne (maybe avec un compte pour eviter lees hackers lol)</Text></li>
|
||||
<li><Text>Combat de boss a la Pokémon Shuffle lol</Text></li>
|
||||
</ul>
|
||||
<NotificationManager />
|
||||
</main>
|
||||
)
|
||||
|
||||
private mouveMove = (ev: MouseEvent) => {
|
||||
this.setState({cursorPos: {
|
||||
x: ev.clientX,
|
||||
y: ev.clientY
|
||||
}})
|
||||
}
|
||||
|
||||
private start = async () => {
|
||||
if (this.state.loading) {return}
|
||||
await this.asyncSetState({
|
||||
loading: true,
|
||||
// generate datas
|
||||
items: Array
|
||||
.from(Array(this.props.boardSize))
|
||||
.map(
|
||||
() => Array.from(Array(this.props.boardSize))
|
||||
.map(() => ({id: random(0, this.props.itemCount), id2: this.n++}))
|
||||
)
|
||||
})
|
||||
// Quickly calculate everythings to make it look like it was perfecly generated
|
||||
await this.calculate(true)
|
||||
this.setState({turn: 1, damage: 0, comboMax: 0, combo: 0})
|
||||
}
|
||||
|
||||
private onCellClick = (x: number, y: number) => async (ev: ReactMouseEvent) => {
|
||||
if (this.state.loading) {
|
||||
return NotificationManager.addNotification('Cant play while Calculating')
|
||||
}
|
||||
if (!this.state.movingItem) {
|
||||
const cell = this.state.items[y][x]
|
||||
if (!cell) {
|
||||
return NotificationManager.addNotification('Cant move nothing')
|
||||
}
|
||||
document.addEventListener('mousemove', this.mouveMove)
|
||||
this.setState({movingItem: {x,y,cell}})
|
||||
this.state.items[y][x] = undefined
|
||||
this.mouveMove(ev.nativeEvent)
|
||||
return
|
||||
} else {
|
||||
document.removeEventListener('mousemove', this.mouveMove)
|
||||
const items = this.state.items
|
||||
const temp = items[y][x]
|
||||
console.log(temp, this.state.movingItem)
|
||||
items[y][x] = this.state.movingItem.cell
|
||||
const tmpX = this.state.movingItem.x
|
||||
const tmpY = this.state.movingItem.y
|
||||
if (temp) {
|
||||
items[tmpY][tmpX] = temp
|
||||
}
|
||||
this.setState({
|
||||
movingItem: undefined,
|
||||
loading: true,
|
||||
items
|
||||
}, async () => {
|
||||
const revert = !await this.calculate()
|
||||
if (revert) {
|
||||
const movingItem = items[y][x]
|
||||
items[y][x] = temp
|
||||
items[tmpY][tmpX] = movingItem
|
||||
this.setState({
|
||||
items,
|
||||
turn: this.state.turn - 1
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private asyncSetState = (states: Partial<States>) => new Promise<void>(
|
||||
(res) => this.setState(states as States, () => res())
|
||||
)
|
||||
|
||||
/**
|
||||
* Check if items has combos
|
||||
* @returns if items were changed
|
||||
*/
|
||||
private async checkup(initial: boolean): Promise<boolean> {
|
||||
const items = this.state.items
|
||||
let checkupCount = 0
|
||||
let newPoints = 0
|
||||
for (let y = 0; y < items.length; y++) {
|
||||
const row = items[y]
|
||||
for (let x = 0; x < row.length; x++) {
|
||||
const cell = row[x]
|
||||
if (!cell) {continue}
|
||||
const id = cell.id
|
||||
// Checkup horizontal
|
||||
if (!cell.horizontalCombo && !(cell.isFalling || cell.justSpawned)) {
|
||||
let sameCount = 0
|
||||
while((x + ++sameCount) < items.length) {
|
||||
// console.log(y + sameCount, x)
|
||||
const tmp = row[x + sameCount]
|
||||
if (!tmp || tmp.id !== id || tmp.isFalling || tmp.justSpawned) {break}
|
||||
}
|
||||
if (sameCount >= 3) {
|
||||
checkupCount += 1
|
||||
let len = 0
|
||||
for (let i = x; i < (x + sameCount); i++) {
|
||||
const tmp = items[y][i]
|
||||
if (!tmp) {continue}
|
||||
tmp.horizontalCombo = true
|
||||
len++
|
||||
}
|
||||
newPoints += calculateScore(len, this.state.combo)
|
||||
|
||||
}
|
||||
}
|
||||
// Checkup Vertical
|
||||
if (!cell.verticalCombo && !(cell.isFalling || cell.justSpawned)) {
|
||||
let sameCount = 0
|
||||
while((y + ++sameCount) < items.length) {
|
||||
// console.log(y + sameCount, x)
|
||||
const tmp = items[y + sameCount][x]
|
||||
if (!tmp || tmp.id !== id || tmp.isFalling || tmp.justSpawned) {break}
|
||||
}
|
||||
if (sameCount >= 3) {
|
||||
checkupCount += 1
|
||||
let len = 0
|
||||
for (let i = y; i < (y + sameCount); i++) {
|
||||
const tmp = items[i][x]
|
||||
if (!tmp) {continue}
|
||||
tmp.verticalCombo = true
|
||||
len++
|
||||
}
|
||||
newPoints += calculateScore(len, this.state.combo)
|
||||
// console.log(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If combos were found
|
||||
if (checkupCount) {
|
||||
const combo = this.state.combo + checkupCount
|
||||
const comboMax = Math.max(this.state.comboMax, combo)
|
||||
if (comboMax === combo && !initial) {
|
||||
window.localStorage.setItem('pokemon-shuffle/comboMax', comboMax.toString())
|
||||
}
|
||||
await this.asyncSetState({
|
||||
items,
|
||||
damage: this.state.damage + newPoints,
|
||||
combo,
|
||||
comboMax,
|
||||
hitBoss: true
|
||||
})
|
||||
}
|
||||
return !!checkupCount
|
||||
}
|
||||
|
||||
private async endTurn(state?: Partial<States>) {
|
||||
await this.asyncSetState({...state, loading: undefined, turn: this.state.turn + 1, combo: 0})
|
||||
}
|
||||
|
||||
private async calculate(initial = false) {
|
||||
// remove combos
|
||||
const items = this.state.items.map((r) => r.map((c) => {
|
||||
if (!c) {
|
||||
return c
|
||||
}
|
||||
delete c.horizontalCombo
|
||||
delete c.verticalCombo
|
||||
return c
|
||||
}))
|
||||
|
||||
let needContinue = false
|
||||
let hadTurn = false
|
||||
do {
|
||||
// Make items fall
|
||||
needContinue = false
|
||||
for (let y = (items.length - 1); y >= 0; y--) {
|
||||
const row = items[y]
|
||||
for (let x = 0; x < row.length; x++) {
|
||||
const cell = row[x]
|
||||
if (cell) {
|
||||
cell.justSpawned = undefined
|
||||
cell.isFalling = undefined
|
||||
}
|
||||
if (cell && y+1 < row.length && !items[y+1][x]) {
|
||||
cell.isFalling = true
|
||||
needContinue = true
|
||||
// Move cell down
|
||||
items[y+1][x] = cell
|
||||
items[y][x] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the top lane
|
||||
for (let x = 0; x < items[0].length; x++) {
|
||||
const cell = items[0][x]
|
||||
if (!cell) {
|
||||
needContinue = true
|
||||
items[0][x] = {id: random(0, this.props.itemCount), id2: this.n++, justSpawned: true}
|
||||
}
|
||||
}
|
||||
|
||||
// Need to wait for the falling animation
|
||||
if (needContinue) {
|
||||
await this.asyncSetState({items, hitBoss: false})
|
||||
if (!initial) {
|
||||
await wait(300)
|
||||
}
|
||||
}
|
||||
|
||||
// Checkup if there is combos
|
||||
const checkup = await this.checkup(initial)
|
||||
if (!checkup && !needContinue) {
|
||||
await this.endTurn({items})
|
||||
break
|
||||
}
|
||||
|
||||
// Clear items
|
||||
let hasCleared = false
|
||||
for (const row of items) {
|
||||
for (let x = 0; x < row.length; x++) {
|
||||
const cell = row[x]
|
||||
if (!cell || (!cell.horizontalCombo && !cell.verticalCombo)) {continue}
|
||||
row[x] = undefined
|
||||
hasCleared = true
|
||||
needContinue = true
|
||||
}
|
||||
}
|
||||
|
||||
if (hasCleared && !initial) {
|
||||
await wait(500)
|
||||
}
|
||||
hadTurn = true
|
||||
|
||||
} while (needContinue)
|
||||
return hadTurn
|
||||
}
|
||||
}
|
||||
|
||||
function calculateScore(len: number, combo: number) {
|
||||
let score = (len - 2) * 40 // currently the damage
|
||||
if (len > 3) {
|
||||
switch (len) {
|
||||
case 4:
|
||||
score *= 1.5
|
||||
break
|
||||
case 5:
|
||||
score *= 2
|
||||
break
|
||||
case 6:
|
||||
score *= 3
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if (combo > 1) {
|
||||
if (combo >= 2 && combo <= 4) {
|
||||
score *= 1.1
|
||||
}
|
||||
if (combo >= 5 && combo <= 9) {
|
||||
score *= 1.15
|
||||
}
|
||||
if (combo >= 10 && combo <= 24) {
|
||||
score *= 1.2
|
||||
}
|
||||
if (combo >= 25 && combo <= 49) {
|
||||
score *= 1.3
|
||||
}
|
||||
if (combo >= 50 && combo <= 74) {
|
||||
score *= 1.4
|
||||
}
|
||||
if (combo >= 75 && combo <= 99) {
|
||||
score *= 1.5
|
||||
}
|
||||
if (combo >= 100 && combo <= 199) {
|
||||
score *= 2
|
||||
}
|
||||
if (combo >= 200) {
|
||||
score *= 2.5
|
||||
}
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
function random(min = 0, max = 100): number {
|
||||
const r = Math.floor(Math.random() * (max - min) + min)
|
||||
// dont return 1 as it is the `?`
|
||||
if (r === 1) {
|
||||
return random(min, max)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
function wait(time: number): Promise<void> {
|
||||
return new Promise((res) => setTimeout(() => res(), time))
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
|
||||
const { boardSize, itemCount } = ctx.query as Record<string, string>
|
||||
|
||||
return {
|
||||
props: {
|
||||
// add 1 to suppress the `?`
|
||||
itemCount: itemCount ? parseInt(itemCount, 10) + 1 : 7,
|
||||
boardSize: boardSize ? parseInt(boardSize, 10) : 7
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import React from 'react'
|
||||
import { Text, Link } from '@dzeio/components'
|
||||
import GameEngine from 'GameEngine'
|
||||
import Scene from 'GameEngine/Scene'
|
||||
import Item from 'games/tictactoe/Item'
|
||||
@ -40,15 +39,6 @@ export default class Snake extends React.PureComponent {
|
||||
public render = () => (
|
||||
<>
|
||||
<canvas id="test" width="300" height="300"></canvas>
|
||||
<Text>
|
||||
<span id="debug"></span>
|
||||
</Text>
|
||||
<Text>Explositon animation from <Link href="https://opengameart.org/content/explosion-animations">https://opengameart.org/content/explosion-animations</Link></Text>
|
||||
<Text>
|
||||
Bienvenue sur le TicTacToe le plus Overengineered du monde xd<br />
|
||||
Avec un moteur de jeux complètement fait maison et bien plus encore en préparation ! (vieille version en 75 lignes, nouvelle en plus de 200 lol)
|
||||
</Text>
|
||||
<Text>Version faites il y a 4 ans encore disponible sur Github lol <Link href="https://github.com/Aviortheking/TicTacToe">https://github.com/Aviortheking/TicTacToe</Link></Text>
|
||||
</>
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user