mirror of
https://github.com/Aviortheking/games.git
synced 2025-04-23 19:32:09 +00:00
Updated Infrastructure
Signed-off-by: Avior <florian.bouillon@delta-wings.net>
This commit is contained in:
parent
04b3879946
commit
40f1e296af
2
.gitignore
vendored
2
.gitignore
vendored
@ -25,3 +25,5 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
*~
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Non experimental config
|
// Non experimental config
|
||||||
// target: 'serverless',
|
target: 'server',
|
||||||
poweredByHeader: false,
|
poweredByHeader: false,
|
||||||
trailingSlash: false,
|
trailingSlash: false,
|
||||||
optimizeFonts: true,
|
optimizeFonts: true,
|
||||||
|
15476
package-lock.json
generated
Normal file
15476
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -35,7 +35,6 @@
|
|||||||
"eslint": "^7.1.0",
|
"eslint": "^7.1.0",
|
||||||
"eslint-config-next": "^10.2.2",
|
"eslint-config-next": "^10.2.2",
|
||||||
"eslint-plugin-react": "^7.18.3",
|
"eslint-plugin-react": "^7.18.3",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^9.1.1"
|
||||||
"vercel": "^22.0.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
public/assets/tictactoe/explosion.png
Normal file
BIN
public/assets/tictactoe/explosion.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
@ -1,245 +0,0 @@
|
|||||||
/* eslint-disable max-classes-per-file */
|
|
||||||
export default class GameEngine {
|
|
||||||
public ctx: CanvasRenderingContext2D
|
|
||||||
public canvas: HTMLCanvasElement
|
|
||||||
public caseSize: [number, number] = [1, 1]
|
|
||||||
public cursor: {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
isDown: boolean
|
|
||||||
wasDown: boolean
|
|
||||||
} = {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
isDown: false,
|
|
||||||
wasDown: false
|
|
||||||
}
|
|
||||||
private currentScene?: Scene
|
|
||||||
private isRunning = false
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
private id: string,
|
|
||||||
private options?: {
|
|
||||||
caseCount?: number | [number, number]
|
|
||||||
background?: string
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const canvas = document.querySelector<HTMLCanvasElement>(id)
|
|
||||||
if (!canvas) {
|
|
||||||
throw new Error('Error, canvas not found!')
|
|
||||||
}
|
|
||||||
this.canvas = canvas
|
|
||||||
if (this.options?.caseCount) {
|
|
||||||
this.caseSize = [
|
|
||||||
// @ts-expect-error idc
|
|
||||||
this.canvas.width / ((typeof this.options.caseCount) !== 'number' ? this.options.caseCount[0] : this.options.caseCount ),
|
|
||||||
// @ts-expect-error idc2 lol
|
|
||||||
this.canvas.height / ((typeof this.options.caseCount) !== 'number' ? this.options.caseCount[1] : this.options.caseCount)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d')
|
|
||||||
if (!ctx) {
|
|
||||||
throw new Error('Error, Context could not get found!')
|
|
||||||
}
|
|
||||||
ctx.imageSmoothingEnabled = false
|
|
||||||
this.ctx = ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
public start() {
|
|
||||||
if (this.isRunning) {
|
|
||||||
console.warn('Game is already running')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.isRunning = true
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
this.update()
|
|
||||||
})
|
|
||||||
document.addEventListener('mousemove', (ev) => {
|
|
||||||
this.cursor.x = ev.clientX
|
|
||||||
this.cursor.y = ev.clientY
|
|
||||||
if (this.cursor.isDown) {
|
|
||||||
this.cursor.wasDown = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
document.addEventListener('mousedown', () => {
|
|
||||||
this.cursor.isDown = true
|
|
||||||
})
|
|
||||||
document.addEventListener('mouseup', () => {
|
|
||||||
this.cursor.isDown = false
|
|
||||||
this.cursor.wasDown = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public pause() {
|
|
||||||
this.isRunning = false
|
|
||||||
}
|
|
||||||
|
|
||||||
public setScene(scene: Scene | string) {
|
|
||||||
this.currentScene = typeof scene === 'string' ? Scene.scenes[scene] : scene
|
|
||||||
this.currentScene.setGameEngine(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private update() {
|
|
||||||
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()
|
|
||||||
setTimeout(() => {
|
|
||||||
this.update()
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export class SoundManager {
|
|
||||||
private audio: HTMLAudioElement
|
|
||||||
public constructor(path: string) {
|
|
||||||
this.audio = new Audio(path)
|
|
||||||
this.audio.load()
|
|
||||||
this.audio.volume = .8
|
|
||||||
}
|
|
||||||
|
|
||||||
public play() {
|
|
||||||
this.audio.play()
|
|
||||||
}
|
|
||||||
|
|
||||||
public end() {
|
|
||||||
this.audio.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AssetsManager {
|
|
||||||
|
|
||||||
public static assets: Record<string, AssetsManager> = {}
|
|
||||||
|
|
||||||
public isLoaded = false
|
|
||||||
|
|
||||||
private image: HTMLImageElement
|
|
||||||
|
|
||||||
private constructor(
|
|
||||||
private path: string
|
|
||||||
) {
|
|
||||||
this.image = new Image()
|
|
||||||
}
|
|
||||||
|
|
||||||
public static init(path: string) {
|
|
||||||
if (!this.assets[path]) {
|
|
||||||
this.assets[path] = new AssetsManager(path)
|
|
||||||
}
|
|
||||||
return this.assets[path]
|
|
||||||
}
|
|
||||||
|
|
||||||
public async load() {
|
|
||||||
return new Promise<void>((res, rej) => {
|
|
||||||
this.image.src = this.path
|
|
||||||
this.image.onload = () => {
|
|
||||||
this.isLoaded = true
|
|
||||||
res()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async get() {
|
|
||||||
if (!this.isLoaded) {
|
|
||||||
await this.load()
|
|
||||||
}
|
|
||||||
return this.image
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Scene {
|
|
||||||
public static scenes: Record<string, Scene> = {}
|
|
||||||
|
|
||||||
public background?: string
|
|
||||||
|
|
||||||
private components: Array<Component2D> = []
|
|
||||||
private ge!: GameEngine
|
|
||||||
|
|
||||||
public constructor(sceneId: string) {
|
|
||||||
Scene.scenes[sceneId] = this
|
|
||||||
}
|
|
||||||
|
|
||||||
public addComponent(...cp: Array<Component2D>) {
|
|
||||||
return this.components.push(...cp)
|
|
||||||
}
|
|
||||||
|
|
||||||
public setGameEngine(ge: GameEngine) {
|
|
||||||
this.ge = ge
|
|
||||||
}
|
|
||||||
|
|
||||||
public async init() {
|
|
||||||
this.components.forEach((v) => {
|
|
||||||
if (v.init) {
|
|
||||||
v.init()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public async update() {
|
|
||||||
this.components.forEach(async (v) => {
|
|
||||||
const state: Partial<ComponentState> = {}
|
|
||||||
const width = (v.size?.width ?? 1) * this.ge.caseSize[0]
|
|
||||||
const height = (v.size?.height ?? 1) * this.ge.caseSize[1]
|
|
||||||
if (v.pos) {
|
|
||||||
const ax = v.pos.x * this.ge.caseSize[0]
|
|
||||||
const ay = v.pos.y * this.ge.caseSize[1]
|
|
||||||
state.mouseHovering =
|
|
||||||
this.ge.cursor.x >= ax && this.ge.cursor.x < (ax + width) &&
|
|
||||||
this.ge.cursor.y >= ay && this.ge.cursor.y < (ay + height)
|
|
||||||
state.mouseClicking = state.mouseHovering && this.ge.cursor.isDown
|
|
||||||
state.mouseClicked = state.mouseClicking && !this.ge.cursor.wasDown
|
|
||||||
}
|
|
||||||
if (v.display && v.pos) {
|
|
||||||
const ax = v.pos.x * this.ge.caseSize[0]
|
|
||||||
const ay = v.pos.y * this.ge.caseSize[1]
|
|
||||||
if (v.display.type === 'color') {
|
|
||||||
this.ge.ctx.fillStyle = v.display.source
|
|
||||||
this.ge.ctx.fillRect(
|
|
||||||
ax,
|
|
||||||
ay,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
)
|
|
||||||
} else if (v.display.type === 'image') {
|
|
||||||
this.ge.ctx.drawImage(
|
|
||||||
await AssetsManager.init(v.display.source).get(),
|
|
||||||
ax,
|
|
||||||
ay,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (v.update) {
|
|
||||||
v.update(state as ComponentState)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComponentState {
|
|
||||||
mouseHovering: boolean
|
|
||||||
mouseClicking: boolean
|
|
||||||
mouseClicked: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class Component2D {
|
|
||||||
public id?: number
|
|
||||||
public display?: {
|
|
||||||
type: 'image' | 'color'
|
|
||||||
source: string
|
|
||||||
}
|
|
||||||
public pos?: {x: number, y: number, z?: number, rotation?: number}
|
|
||||||
public size?: {width: number, height: number}
|
|
||||||
|
|
||||||
public init?(): Promise<void> | void
|
|
||||||
|
|
||||||
public update?(state: ComponentState): Promise<void> | void
|
|
||||||
}
|
|
38
src/GameEngine/Asset.ts
Normal file
38
src/GameEngine/Asset.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export default class Asset {
|
||||||
|
|
||||||
|
public static assets: Record<string, Asset> = {}
|
||||||
|
|
||||||
|
public isLoaded = false
|
||||||
|
|
||||||
|
private image!: HTMLImageElement
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
private path: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public static init(path: string) {
|
||||||
|
if (!this.assets[path]) {
|
||||||
|
this.assets[path] = new Asset(path)
|
||||||
|
}
|
||||||
|
return this.assets[path]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async load() {
|
||||||
|
return new Promise<void>((res, rej) => {
|
||||||
|
this.image = new Image()
|
||||||
|
this.image.src = this.path
|
||||||
|
this.image.onload = () => {
|
||||||
|
this.isLoaded = true
|
||||||
|
res()
|
||||||
|
}
|
||||||
|
this.image.onerror = rej
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get() {
|
||||||
|
if (!this.isLoaded) {
|
||||||
|
await this.load()
|
||||||
|
}
|
||||||
|
return this.image
|
||||||
|
}
|
||||||
|
}
|
34
src/GameEngine/Component2D.ts
Normal file
34
src/GameEngine/Component2D.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import Renderer from './Renderer'
|
||||||
|
|
||||||
|
export interface ComponentState {
|
||||||
|
mouseHovering: boolean
|
||||||
|
mouseClicking: boolean
|
||||||
|
mouseClicked: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default abstract class Component2D {
|
||||||
|
public id?: number
|
||||||
|
public renderer?: Renderer
|
||||||
|
public pos?: {x: number, y: number, z?: number, rotation?: number}
|
||||||
|
|
||||||
|
|
||||||
|
protected size?: number | {width: number, height: number}
|
||||||
|
|
||||||
|
public init?(): Promise<void> | void
|
||||||
|
|
||||||
|
public update?(state: ComponentState): Promise<void> | void
|
||||||
|
|
||||||
|
public width() {
|
||||||
|
if (!this.size) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return typeof this.size === 'number' ? this.size : this.size?.width
|
||||||
|
}
|
||||||
|
|
||||||
|
public height() {
|
||||||
|
if (!this.size) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return typeof this.size === 'number' ? this.size : this.size?.height
|
||||||
|
}
|
||||||
|
}
|
24
src/GameEngine/Renderer/ColorRenderer.ts
Normal file
24
src/GameEngine/Renderer/ColorRenderer.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import GameEngine from 'GameEngine'
|
||||||
|
import Component2D from 'GameEngine/Component2D'
|
||||||
|
import Renderer from '.'
|
||||||
|
|
||||||
|
export default class ColorRenderer implements Renderer {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private component: Component2D,
|
||||||
|
private color: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||||
|
if (!this.component.pos) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.fillStyle = this.color
|
||||||
|
ctx.fillRect(
|
||||||
|
this.component.pos.x * (ge.caseSize[0]),
|
||||||
|
this.component.pos.y * (ge.caseSize[1]),
|
||||||
|
(this.component.width() ?? ge.caseSize[0]) * ge.caseSize[0],
|
||||||
|
(this.component.height() ?? ge.caseSize[1]) * ge.caseSize[1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
25
src/GameEngine/Renderer/ImageRenderer.ts
Normal file
25
src/GameEngine/Renderer/ImageRenderer.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import GameEngine from 'GameEngine'
|
||||||
|
import Asset from 'GameEngine/Asset'
|
||||||
|
import Component2D from 'GameEngine/Component2D'
|
||||||
|
import Renderer from '.'
|
||||||
|
|
||||||
|
export default class ImageRenderer implements Renderer {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private component: Component2D,
|
||||||
|
private image: Asset
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||||
|
if (!this.component.pos) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.drawImage(
|
||||||
|
await this.image.get(),
|
||||||
|
this.component.pos.x * (ge.caseSize[0]),
|
||||||
|
this.component.pos.y * (ge.caseSize[1]),
|
||||||
|
(this.component.width() ?? ge.caseSize[0]) * ge.caseSize[0],
|
||||||
|
(this.component.height() ?? ge.caseSize[1]) * ge.caseSize[1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
31
src/GameEngine/Renderer/TileRenderer.ts
Normal file
31
src/GameEngine/Renderer/TileRenderer.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import GameEngine from 'GameEngine'
|
||||||
|
import Component2D from 'GameEngine/Component2D'
|
||||||
|
import Tileset from 'GameEngine/Tileset'
|
||||||
|
import Renderer from '.'
|
||||||
|
|
||||||
|
export default class TileRenderer implements Renderer {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private component: Component2D,
|
||||||
|
private tileset: Tileset,
|
||||||
|
private id: number
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async render(ge: GameEngine, ctx: CanvasRenderingContext2D) {
|
||||||
|
if (!this.component.pos) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const {sx, sy} = this.tileset.getSourceData(this.id)
|
||||||
|
ctx.drawImage(
|
||||||
|
await this.tileset.asset.get(),
|
||||||
|
sx,
|
||||||
|
sy,
|
||||||
|
this.tileset.width(),
|
||||||
|
this.tileset.height(),
|
||||||
|
this.component.pos.x * (ge.caseSize[0]),
|
||||||
|
this.component.pos.y * (ge.caseSize[1]),
|
||||||
|
(this.component.width() ?? ge.caseSize[0]) * ge.caseSize[0],
|
||||||
|
(this.component.height() ?? ge.caseSize[1]) * ge.caseSize[1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
5
src/GameEngine/Renderer/index.ts
Normal file
5
src/GameEngine/Renderer/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import GameEngine from 'GameEngine'
|
||||||
|
|
||||||
|
export default interface Renderer {
|
||||||
|
render(ge: GameEngine, ctx: CanvasRenderingContext2D): Promise<void>
|
||||||
|
}
|
56
src/GameEngine/Scene.ts
Normal file
56
src/GameEngine/Scene.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import GameEngine from 'GameEngine'
|
||||||
|
import { ComponentState } from 'react'
|
||||||
|
import AssetsManager from './Asset'
|
||||||
|
import Component2D from './Component2D'
|
||||||
|
|
||||||
|
export default class Scene {
|
||||||
|
public static scenes: Record<string, Scene> = {}
|
||||||
|
|
||||||
|
public background?: string
|
||||||
|
|
||||||
|
private components: Array<Component2D> = []
|
||||||
|
private ge!: GameEngine
|
||||||
|
|
||||||
|
public constructor(sceneId: string) {
|
||||||
|
Scene.scenes[sceneId] = this
|
||||||
|
}
|
||||||
|
|
||||||
|
public addComponent(...cp: Array<Component2D>) {
|
||||||
|
return this.components.push(...cp)
|
||||||
|
}
|
||||||
|
|
||||||
|
public setGameEngine(ge: GameEngine) {
|
||||||
|
this.ge = ge
|
||||||
|
}
|
||||||
|
|
||||||
|
public async init() {
|
||||||
|
this.components.forEach((v) => {
|
||||||
|
if (v.init) {
|
||||||
|
v.init()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update() {
|
||||||
|
this.components.forEach(async (v) => {
|
||||||
|
const state: Partial<ComponentState> = {}
|
||||||
|
const width = (v.size?.width ?? 1) * this.ge.caseSize[0]
|
||||||
|
const height = (v.size?.height ?? 1) * this.ge.caseSize[1]
|
||||||
|
if (v.pos) {
|
||||||
|
const ax = v.pos.x * this.ge.caseSize[0]
|
||||||
|
const ay = v.pos.y * this.ge.caseSize[1]
|
||||||
|
state.mouseHovering =
|
||||||
|
this.ge.cursor.x >= ax && this.ge.cursor.x < (ax + width) &&
|
||||||
|
this.ge.cursor.y >= ay && this.ge.cursor.y < (ay + height)
|
||||||
|
state.mouseClicking = state.mouseHovering && this.ge.cursor.isDown
|
||||||
|
state.mouseClicked = state.mouseClicking && !this.ge.cursor.wasDown
|
||||||
|
}
|
||||||
|
if (v.renderer) {
|
||||||
|
await v.renderer.render(this.ge, this.ge.ctx)
|
||||||
|
}
|
||||||
|
if (v.update) {
|
||||||
|
v.update(state as ComponentState)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
16
src/GameEngine/SoundManager.ts
Normal file
16
src/GameEngine/SoundManager.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export default class SoundManager {
|
||||||
|
private audio: HTMLAudioElement
|
||||||
|
public constructor(path: string) {
|
||||||
|
this.audio = new Audio(path)
|
||||||
|
this.audio.load()
|
||||||
|
this.audio.volume = .8
|
||||||
|
}
|
||||||
|
|
||||||
|
public play() {
|
||||||
|
this.audio.play()
|
||||||
|
}
|
||||||
|
|
||||||
|
public end() {
|
||||||
|
this.audio.pause()
|
||||||
|
}
|
||||||
|
}
|
41
src/GameEngine/Tileset.ts
Normal file
41
src/GameEngine/Tileset.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import Asset from './Asset'
|
||||||
|
|
||||||
|
export interface TilesetDeclaration {
|
||||||
|
// id: string
|
||||||
|
// padding?: number
|
||||||
|
fileSize: {width: number, height: number}
|
||||||
|
tileSize: number | {width: number, height: number}
|
||||||
|
spacing?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Tileset {
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public asset: Asset,
|
||||||
|
private declaration: TilesetDeclaration
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public getPosFromId(id: number): {x: number, y: number} {
|
||||||
|
const cols = Math.trunc(this.declaration.fileSize.width / this.width())
|
||||||
|
const x = id % cols
|
||||||
|
const y = Math.trunc(id / cols)
|
||||||
|
return {x, y}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSourceData(id: number): {sx: number ,sy: number} {
|
||||||
|
const {x, y} = this.getPosFromId(id)
|
||||||
|
const sx = x * this.width() + x * (this.declaration.spacing ?? 0)
|
||||||
|
const sy = y * this.height() + y * (this.declaration.spacing ?? 0)
|
||||||
|
return {sx, sy}
|
||||||
|
}
|
||||||
|
|
||||||
|
public width() {
|
||||||
|
const item = this.declaration.tileSize
|
||||||
|
return typeof item === 'number' ? item : item.width
|
||||||
|
}
|
||||||
|
|
||||||
|
public height() {
|
||||||
|
const item = this.declaration.tileSize
|
||||||
|
return typeof item === 'number' ? item : item.height
|
||||||
|
}
|
||||||
|
}
|
105
src/GameEngine/index.ts
Normal file
105
src/GameEngine/index.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import Scene from './Scene'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO:
|
||||||
|
* Animation Engine
|
||||||
|
* Camera fined control
|
||||||
|
* Collision
|
||||||
|
*/
|
||||||
|
export default class GameEngine {
|
||||||
|
public ctx: CanvasRenderingContext2D
|
||||||
|
public canvas: HTMLCanvasElement
|
||||||
|
public caseSize: [number, number] = [1, 1]
|
||||||
|
public cursor: {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
isDown: boolean
|
||||||
|
wasDown: boolean
|
||||||
|
} = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
isDown: false,
|
||||||
|
wasDown: false
|
||||||
|
}
|
||||||
|
private currentScene?: Scene
|
||||||
|
private isRunning = false
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private id: string,
|
||||||
|
private options?: {
|
||||||
|
caseCount?: number | [number, number]
|
||||||
|
background?: string
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const canvas = document.querySelector<HTMLCanvasElement>(id)
|
||||||
|
if (!canvas) {
|
||||||
|
throw new Error('Error, canvas not found!')
|
||||||
|
}
|
||||||
|
this.canvas = canvas
|
||||||
|
if (this.options?.caseCount) {
|
||||||
|
this.caseSize = [
|
||||||
|
// @ts-expect-error idc
|
||||||
|
this.canvas.width / ((typeof this.options.caseCount) !== 'number' ? this.options.caseCount[0] : this.options.caseCount ),
|
||||||
|
// @ts-expect-error idc2 lol
|
||||||
|
this.canvas.height / ((typeof this.options.caseCount) !== 'number' ? this.options.caseCount[1] : this.options.caseCount)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error('Error, Context could not get found!')
|
||||||
|
}
|
||||||
|
ctx.imageSmoothingEnabled = false
|
||||||
|
this.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
public start() {
|
||||||
|
if (this.isRunning) {
|
||||||
|
console.warn('Game is already running')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.isRunning = true
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.update()
|
||||||
|
})
|
||||||
|
document.addEventListener('mousemove', (ev) => {
|
||||||
|
this.cursor.x = ev.clientX
|
||||||
|
this.cursor.y = ev.clientY
|
||||||
|
if (this.cursor.isDown) {
|
||||||
|
this.cursor.wasDown = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
document.addEventListener('mousedown', () => {
|
||||||
|
this.cursor.isDown = true
|
||||||
|
})
|
||||||
|
document.addEventListener('mouseup', () => {
|
||||||
|
this.cursor.isDown = false
|
||||||
|
this.cursor.wasDown = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause() {
|
||||||
|
this.isRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
public setScene(scene: Scene | string) {
|
||||||
|
this.currentScene = typeof scene === 'string' ? Scene.scenes[scene] : scene
|
||||||
|
this.currentScene.setGameEngine(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
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()
|
||||||
|
setTimeout(() => {
|
||||||
|
this.update()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
}
|
39
src/games/tictactoe/Explosion.ts
Normal file
39
src/games/tictactoe/Explosion.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import Asset from 'GameEngine/Asset'
|
||||||
|
import Component2D from 'GameEngine/Component2D'
|
||||||
|
import TileRenderer from 'GameEngine/Renderer/TileRenderer'
|
||||||
|
import SoundManager from 'GameEngine/SoundManager'
|
||||||
|
import Tileset from 'GameEngine/Tileset'
|
||||||
|
|
||||||
|
export class Explosion extends Component2D {
|
||||||
|
|
||||||
|
public size = {
|
||||||
|
width: .9,
|
||||||
|
height: .9
|
||||||
|
}
|
||||||
|
|
||||||
|
private explosionTileset = new Tileset(Asset.init('/assets/tictactoe/explosion.png'), {
|
||||||
|
fileSize: {width: 480, height: 362},
|
||||||
|
tileSize: 91,
|
||||||
|
spacing: 1,
|
||||||
|
})
|
||||||
|
private animationNumber = -1
|
||||||
|
private n = 0
|
||||||
|
|
||||||
|
|
||||||
|
public update() {
|
||||||
|
if (this.animationNumber !== -1 && this.n++ >= 10) {
|
||||||
|
this.renderer = new TileRenderer(this, this.explosionTileset, this.animationNumber++)
|
||||||
|
this.n = 0
|
||||||
|
if (this.animationNumber >= 10) {
|
||||||
|
this.animationNumber = -1
|
||||||
|
this.renderer = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public run(pos: Component2D['pos']) {
|
||||||
|
new SoundManager('/assets/tictactoe/victory.wav').play()
|
||||||
|
this.pos = pos
|
||||||
|
this.animationNumber = 0
|
||||||
|
}
|
||||||
|
}
|
119
src/games/tictactoe/Item.ts
Normal file
119
src/games/tictactoe/Item.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import Asset from 'GameEngine/Asset'
|
||||||
|
import Component2D, { ComponentState } from 'GameEngine/Component2D'
|
||||||
|
import TileRenderer from 'GameEngine/Renderer/TileRenderer'
|
||||||
|
import SoundManager from 'GameEngine/SoundManager'
|
||||||
|
import Tileset from 'GameEngine/Tileset'
|
||||||
|
import { globalState } from '.'
|
||||||
|
import { Explosion } from './Explosion'
|
||||||
|
|
||||||
|
export default class Item extends Component2D {
|
||||||
|
|
||||||
|
public static explosion = new Explosion()
|
||||||
|
|
||||||
|
public size = {
|
||||||
|
width: .9,
|
||||||
|
height: .9
|
||||||
|
}
|
||||||
|
|
||||||
|
private x: number
|
||||||
|
private y: number
|
||||||
|
|
||||||
|
private tileset = new Tileset(Asset.init('/assets/pokemon-shuffle/icons.png'), {
|
||||||
|
fileSize: {width: 3750, height: 3266},
|
||||||
|
tileSize: 121
|
||||||
|
})
|
||||||
|
|
||||||
|
public constructor(index: number) {
|
||||||
|
super()
|
||||||
|
this.x = Math.trunc(index % 3)
|
||||||
|
this.y = Math.trunc(index / 3)
|
||||||
|
this.pos = {
|
||||||
|
x: this.x + this.x * .05,
|
||||||
|
y: this.y + this.y * .05
|
||||||
|
}
|
||||||
|
console.log(this.pos, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
console.log('item initialized')
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(state: ComponentState) {
|
||||||
|
// console.log(state)
|
||||||
|
const value: '' | 'X' | 'O' = globalState.gameState[this.x][this.y] as '' | 'X' | 'O'
|
||||||
|
if (state.mouseClicked && value === '') {
|
||||||
|
// console.log('hovering')
|
||||||
|
this.onClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === 'X') {
|
||||||
|
this.renderer = new TileRenderer(this, this.tileset, 20)
|
||||||
|
// this.renderer = new ImageRenderer(this, Asset.init('/assets/tictactoe/X.png'))
|
||||||
|
} else if(value === 'O') {
|
||||||
|
this.renderer = new TileRenderer(this, this.tileset, 54)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClick() {
|
||||||
|
const clickSound = new SoundManager('/assets/tictactoe/bip.wav')
|
||||||
|
clickSound.play()
|
||||||
|
globalState.gameState[this.x][this.y] = globalState.playerTurn
|
||||||
|
console.log(this.checkVictory())
|
||||||
|
if (this.checkVictory()) {
|
||||||
|
Item.explosion.run(this.pos)
|
||||||
|
globalState.gameState = [
|
||||||
|
['', '', ''],
|
||||||
|
['', '', ''],
|
||||||
|
['', '', '']
|
||||||
|
]
|
||||||
|
console.log(globalState)
|
||||||
|
}
|
||||||
|
globalState.playerTurn = globalState.playerTurn === 'X' ? 'O' : 'X'
|
||||||
|
}
|
||||||
|
|
||||||
|
// uh MAYBE refactor lol
|
||||||
|
private checkVictory() {
|
||||||
|
if(globalState.gameState[0][0] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[1][0] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[2][0] === globalState.playerTurn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(globalState.gameState[0][1] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[0][2] === globalState.playerTurn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(globalState.gameState[1][1] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[2][2] === globalState.playerTurn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(globalState.gameState[2][2] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[2][1] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[2][0] === globalState.playerTurn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(globalState.gameState[1][2] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[0][2] === globalState.playerTurn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(globalState.gameState[1][1] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[0][1] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[2][1] === globalState.playerTurn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(globalState.gameState[1][0] === globalState.playerTurn) {
|
||||||
|
if(globalState.gameState[1][2] === globalState.playerTurn) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
20
src/games/tictactoe/Line.ts
Normal file
20
src/games/tictactoe/Line.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import Component2D from 'GameEngine/Component2D'
|
||||||
|
import ColorRenderer from 'GameEngine/Renderer/ColorRenderer'
|
||||||
|
|
||||||
|
export default class Line extends Component2D {
|
||||||
|
|
||||||
|
public constructor(direction: number, index: number) {
|
||||||
|
super()
|
||||||
|
this.renderer = new ColorRenderer(this, 'orange')
|
||||||
|
|
||||||
|
this.pos = {
|
||||||
|
x: direction ? index ? 1.95 : .9 : 0,
|
||||||
|
y: direction ? 0 : index ? .9 : 1.95
|
||||||
|
}
|
||||||
|
this.size = {
|
||||||
|
width: direction ? .15 : 3,
|
||||||
|
height: direction ? 3 : .15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,4 @@
|
|||||||
/* eslint-disable max-classes-per-file */
|
export const globalState: {
|
||||||
import { Component2D, ComponentState, SoundManager } from 'GameEngine'
|
|
||||||
|
|
||||||
const globalState: {
|
|
||||||
playerTurn: 'X' | 'O'
|
playerTurn: 'X' | 'O'
|
||||||
gameState: [
|
gameState: [
|
||||||
[string, string, string],
|
[string, string, string],
|
||||||
@ -16,132 +13,3 @@ const globalState: {
|
|||||||
['', '', '']
|
['', '', '']
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Item extends Component2D {
|
|
||||||
|
|
||||||
public size = {
|
|
||||||
width: .9,
|
|
||||||
height: .9
|
|
||||||
}
|
|
||||||
|
|
||||||
private x: number
|
|
||||||
private y: number
|
|
||||||
|
|
||||||
public constructor(index: number) {
|
|
||||||
super()
|
|
||||||
this.x = Math.trunc(index % 3)
|
|
||||||
this.y = Math.trunc(index / 3)
|
|
||||||
this.pos = {
|
|
||||||
x: this.x + this.x * .05,
|
|
||||||
y: this.y + this.y * .05
|
|
||||||
}
|
|
||||||
console.log(this.pos, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
public init() {
|
|
||||||
console.log('item initialized')
|
|
||||||
}
|
|
||||||
|
|
||||||
public update(state: ComponentState) {
|
|
||||||
// console.log(state)
|
|
||||||
const value: '' | 'X' | 'O' = globalState.gameState[this.x][this.y] as '' | 'X' | 'O'
|
|
||||||
if (state.mouseClicked && value === '') {
|
|
||||||
// console.log('hovering')
|
|
||||||
this.onClick()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === 'X') {
|
|
||||||
this.display = {
|
|
||||||
type: 'image',
|
|
||||||
source: '/assets/tictactoe/X.png'
|
|
||||||
}
|
|
||||||
} else if(value === 'O') {
|
|
||||||
this.display = {
|
|
||||||
type: 'image',
|
|
||||||
source: '/assets/tictactoe/O.png'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onClick() {
|
|
||||||
const clickSound = new SoundManager('/assets/tictactoe/bip.wav')
|
|
||||||
clickSound.play()
|
|
||||||
globalState.gameState[this.x][this.y] = globalState.playerTurn
|
|
||||||
console.log(this.checkVictory())
|
|
||||||
if (this.checkVictory()) {
|
|
||||||
new SoundManager('/assets/tictactoe/victory.wav').play()
|
|
||||||
// globalState.gameState = [
|
|
||||||
// ['', '', ''],
|
|
||||||
// ['', '', ''],
|
|
||||||
// ['', '', '']
|
|
||||||
// ]
|
|
||||||
}
|
|
||||||
globalState.playerTurn = globalState.playerTurn === 'X' ? 'O' : 'X'
|
|
||||||
}
|
|
||||||
|
|
||||||
// uh MAYBE refactor lol
|
|
||||||
private checkVictory() {
|
|
||||||
if(globalState.gameState[0][0] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[1][0] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[2][0] === globalState.playerTurn) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(globalState.gameState[0][1] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[0][2] === globalState.playerTurn) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(globalState.gameState[1][1] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[2][2] === globalState.playerTurn) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(globalState.gameState[2][2] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[2][1] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[2][0] === globalState.playerTurn) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(globalState.gameState[1][2] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[0][2] === globalState.playerTurn) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(globalState.gameState[1][1] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[0][1] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[2][1] === globalState.playerTurn) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(globalState.gameState[1][0] === globalState.playerTurn) {
|
|
||||||
if(globalState.gameState[1][2] === globalState.playerTurn) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Line extends Component2D {
|
|
||||||
|
|
||||||
public constructor(direction: number, index: number) {
|
|
||||||
super()
|
|
||||||
this.display = {
|
|
||||||
type: 'color',
|
|
||||||
source: 'orange'
|
|
||||||
}
|
|
||||||
this.pos = {
|
|
||||||
x: direction ? index ? 1.95 : .9 : 0,
|
|
||||||
y: direction ? 0 : index ? .9 : 1.95
|
|
||||||
}
|
|
||||||
this.size = {
|
|
||||||
width: direction ? .15 : 3,
|
|
||||||
height: direction ? 3 : .15
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
import { Text, Link } from '@dzeio/components'
|
/* eslint-disable max-classes-per-file */
|
||||||
import GameEngine, { Scene } from 'GameEngine'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Item, Line } from '../../games/tictactoe'
|
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'
|
||||||
|
|
||||||
export default class Snake extends React.PureComponent {
|
export default class Snake extends React.PureComponent {
|
||||||
|
|
||||||
public async componentDidMount() {
|
public async componentDidMount() {
|
||||||
const ge = new GameEngine('#test', {
|
const ge = new GameEngine('#test', {
|
||||||
caseCount: 3,
|
caseCount: 3,
|
||||||
@ -13,16 +18,20 @@ export default class Snake extends React.PureComponent {
|
|||||||
...Array.from(new Array(2)).map((_, index) => new Line(0, index)),
|
...Array.from(new Array(2)).map((_, index) => new Line(0, index)),
|
||||||
...Array.from(new Array(2)).map((_, index) => new Line(1, index)),
|
...Array.from(new Array(2)).map((_, index) => new Line(1, index)),
|
||||||
...Array.from(new Array(9)).map((_, index) => new Item(index)),
|
...Array.from(new Array(9)).map((_, index) => new Item(index)),
|
||||||
|
Item.explosion
|
||||||
)
|
)
|
||||||
|
|
||||||
ge.start()
|
ge.start()
|
||||||
ge.setScene(scene)
|
ge.setScene(scene)
|
||||||
}
|
}
|
||||||
|
|
||||||
public render = () => (
|
public render = () => (
|
||||||
<>
|
<>
|
||||||
<canvas id="test" width="300" height="300"></canvas>
|
<canvas id="test" width="300" height="300"></canvas>
|
||||||
|
<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>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>
|
<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