From 3bb8e4f2286791606e482bfab9b0a72005b4705b Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 24 Dec 2023 02:08:24 +0700 Subject: [PATCH 01/44] Add color to grid --- doc.MD | 5 +++++ main.js | 19 ++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 doc.MD diff --git a/doc.MD b/doc.MD new file mode 100644 index 0000000..4f9eab8 --- /dev/null +++ b/doc.MD @@ -0,0 +1,5 @@ +TODO: +~~1. Create map grid with colors.~~ +2. Add click handler to switch color when clicked. +3. Put numbers on grid. If zero then don't show, else show. +4. Make number > 1 moveable within 1 distance. If target tile has number, add them together. \ No newline at end of file diff --git a/main.js b/main.js index d2f2a5a..b0a1a7c 100644 --- a/main.js +++ b/main.js @@ -1,8 +1,6 @@ import * as PIXI from 'pixi.js'; import { defineHex, Grid, rectangle } from 'honeycomb-grid' -// you may want the origin to be the top left corner of a hex's bounding box -// instead of its center (which is the default) const Hex = defineHex({ dimensions: 30, origin: 'topLeft' }) const grid = new Grid(Hex, rectangle({ width: 10, height: 10 })) @@ -16,8 +14,15 @@ grid.forEach(renderHex) app.stage.addChild(graphics) function renderHex(hex) { - // PIXI.Polygon happens to be compatible with hex.corners - graphics.drawShape( - new PIXI.Polygon(hex.corners) - ) -} \ No newline at end of file + graphics + .beginFill('#000') + .drawShape(new PIXI.Polygon(hex.corners)) + .endFill(); +} + +document.addEventListener('click', ({offsetX, offsetY}) => { + const hex = grid.pointToHex( + { x: offsetX, y: offsetY } + ); + +}); \ No newline at end of file From 082b62ac9ce57604883b6c1eb0271367a3f63237 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 24 Dec 2023 15:00:31 +0700 Subject: [PATCH 02/44] Add click handler and refactor renderer --- colors.json | 8 ++++++ game-config.json | 3 +++ main.js | 30 +++++++++++----------- src/render.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 colors.json create mode 100644 game-config.json create mode 100644 src/render.js diff --git a/colors.json b/colors.json new file mode 100644 index 0000000..cceef3c --- /dev/null +++ b/colors.json @@ -0,0 +1,8 @@ +{ + "grid": + { + "background": "#000", + "primary": "#0d6efd", + "highlight": "#44ffb7" + } +} \ No newline at end of file diff --git a/game-config.json b/game-config.json new file mode 100644 index 0000000..09f4979 --- /dev/null +++ b/game-config.json @@ -0,0 +1,3 @@ +{ + "maxFPS": 30 +} \ No newline at end of file diff --git a/main.js b/main.js index b0a1a7c..8f01033 100644 --- a/main.js +++ b/main.js @@ -1,28 +1,28 @@ import * as PIXI from 'pixi.js'; -import { defineHex, Grid, rectangle } from 'honeycomb-grid' - -const Hex = defineHex({ dimensions: 30, origin: 'topLeft' }) -const grid = new Grid(Hex, rectangle({ width: 10, height: 10 })) +import Renderer from './src/render' const app = new PIXI.Application({ backgroundAlpha: 0 }) -const graphics = new PIXI.Graphics() document.body.appendChild(app.view) -graphics.lineStyle(1, 0x999999) -grid.forEach(renderHex) -app.stage.addChild(graphics) +const renderer = new Renderer( + app, + { dimensions: 30, origin: 'topLeft' }, + { width: 10, height: 10 } +); +renderer.init(); -function renderHex(hex) { - graphics - .beginFill('#000') - .drawShape(new PIXI.Polygon(hex.corners)) - .endFill(); -} +const grid = renderer.getGrid(); document.addEventListener('click', ({offsetX, offsetY}) => { const hex = grid.pointToHex( - { x: offsetX, y: offsetY } + { x: offsetX, y: offsetY }, + { allowOutside: false } ); + if (hex !== undefined) { + renderer.selectCoordinate(hex.q, hex.r); + } else { + renderer.selectCoordinate(null, null); + } }); \ No newline at end of file diff --git a/src/render.js b/src/render.js new file mode 100644 index 0000000..2c33429 --- /dev/null +++ b/src/render.js @@ -0,0 +1,66 @@ +import * as PIXI from 'pixi.js'; +import { defineHex, Grid, rectangle } from 'honeycomb-grid' +import { grid as gridColor } from '../colors.json' +import { maxFPS } from '../game-config.json'; + +class Renderer +{ + /** + * Construct the renderer object + * @param {PIXI.Application} app + * @param {object} hexConfig + * @param {object} gridConfig + */ + constructor(app, hexConfig, gridConfig) + { + this.app = app; + this.hex = defineHex(hexConfig); + this.grid = new Grid(this.hex, rectangle(gridConfig)); + /** @member {Number} */ + this.elapsed = 0.0; + /** @member {Number} */ + this.fps = maxFPS; + this.selectedCoordinate = {q: null, r: null}; + } + + init() + { + const graphics = new PIXI.Graphics(); + graphics.lineStyle(1, 0x999999); + + const renderHex = (hex) => { + let cellColor = gridColor.background; + if (this.selectedCoordinate.q == hex.q && this.selectedCoordinate.r == hex.r) { + cellColor = gridColor.highlight; + } + + graphics + .beginFill(cellColor) + .drawShape(new PIXI.Polygon(hex.corners)) + .endFill(); + } + + this.app.ticker.add((delta) => { + const timeNow = (new Date()).getTime(); + const timeDiff = timeNow - this.elapsed; + const tickLimit = 1000 / this.fps; + if (timeDiff < tickLimit) + return; + + this.elapsed = timeNow; + this.grid.forEach(renderHex); + this.app.stage.addChild(graphics); + }); + } + + selectCoordinate(q, r){ + this.selectedCoordinate.q = q; + this.selectedCoordinate.r = r; + } + + getGrid(){ + return this.grid; + } +} + +export default Renderer \ No newline at end of file From dbca7142b49e9dce1456958c97c0731384ea72d3 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 6 Jan 2024 22:14:29 +0700 Subject: [PATCH 03/44] Add number rendering in grid --- doc.MD | 5 ++-- game-config.json | 7 +++++- main.js | 27 +++++++++++++++++---- src/model/MapTile.js | 57 ++++++++++++++++++++++++++++++++++++++++++++ src/render.js | 27 ++++++++------------- 5 files changed, 98 insertions(+), 25 deletions(-) create mode 100644 src/model/MapTile.js diff --git a/doc.MD b/doc.MD index 4f9eab8..39f3db4 100644 --- a/doc.MD +++ b/doc.MD @@ -1,5 +1,6 @@ TODO: ~~1. Create map grid with colors.~~ -2. Add click handler to switch color when clicked. -3. Put numbers on grid. If zero then don't show, else show. +~~2. Add click handler to switch color when clicked.~~ +~~3. Put numbers on grid. If zero then don't show, else show.~~ + 1. Optimize how grid is rendered 4. Make number > 1 moveable within 1 distance. If target tile has number, add them together. \ No newline at end of file diff --git a/game-config.json b/game-config.json index 09f4979..a962982 100644 --- a/game-config.json +++ b/game-config.json @@ -1,3 +1,8 @@ { - "maxFPS": 30 + "maxFPS": 30, + "hex": + { + "dimensions": 30, + "origin": "topLeft" + } } \ No newline at end of file diff --git a/main.js b/main.js index 8f01033..6ec13a8 100644 --- a/main.js +++ b/main.js @@ -1,14 +1,31 @@ import * as PIXI from 'pixi.js'; -import Renderer from './src/render' +import { Grid } from 'honeycomb-grid'; +import Renderer from './src/render'; +import MapTile from './src/model/MapTile'; -const app = new PIXI.Application({ backgroundAlpha: 0 }) +const app = new PIXI.Application({ backgroundAlpha: 0 }); -document.body.appendChild(app.view) +globalThis.__PIXI_APP__ = app; + +document.body.appendChild(app.view); + +// TEMP CREATE GRID + +let exampleGrid = []; + +for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + const randNum = Math.floor(Math.random() * 3); + const coordinates = { col: j, row: i }; + exampleGrid.push(MapTile.create(coordinates, (randNum < 1 ? 1 : 0))); + } +} + +// END TEMP CREATE GRID const renderer = new Renderer( app, - { dimensions: 30, origin: 'topLeft' }, - { width: 10, height: 10 } + Grid.fromIterable(exampleGrid) ); renderer.init(); diff --git a/src/model/MapTile.js b/src/model/MapTile.js new file mode 100644 index 0000000..e874942 --- /dev/null +++ b/src/model/MapTile.js @@ -0,0 +1,57 @@ +import { defineHex } from 'honeycomb-grid' +import gameConfig from '../../game-config.json' +import { grid as gridColor } from '../../colors.json' +import * as PIXI from 'pixi.js'; + +class MapTile extends defineHex(gameConfig.hex) +{ + /** + * @type {Number} + */ + cellNumber; + + /** + * @param {import('honeycomb-grid').HexCoordinates} coordinates + * @param {Number} cellNumber + */ + static create(coordinates, cellNumber) + { + const hex = new MapTile(coordinates); + hex.cellNumber = cellNumber; + return hex; + } + + /** + * @param {PIXI.Graphics} graphic + */ + render(graphic) + { + let cellColor = gridColor.background; + if (this.cellNumber > 0) { + cellColor = gridColor.primary; + } + + graphic.beginFill(cellColor).drawShape(new PIXI.Polygon(this.corners)); + + if (this.cellNumber > 0) { + const text = new PIXI.Text(this.cellNumber); + text.x = this.x - text.width / 2; + text.y = this.y - text.height / 2; + graphic.addChild(text); + } + + graphic.endFill(); + } + + /** + * @param {PIXI.Graphics} graphic + */ + renderSelected(graphic) + { + graphic.beginFill(gridColor.highlight) + .drawShape(new PIXI.Polygon(this.corners)) + .endFill(); + } +} + +export default MapTile; \ No newline at end of file diff --git a/src/render.js b/src/render.js index 2c33429..897dcea 100644 --- a/src/render.js +++ b/src/render.js @@ -1,6 +1,5 @@ import * as PIXI from 'pixi.js'; -import { defineHex, Grid, rectangle } from 'honeycomb-grid' -import { grid as gridColor } from '../colors.json' +import { Grid } from 'honeycomb-grid' import { maxFPS } from '../game-config.json'; class Renderer @@ -8,14 +7,12 @@ class Renderer /** * Construct the renderer object * @param {PIXI.Application} app - * @param {object} hexConfig - * @param {object} gridConfig + * @param {Grid} grid */ - constructor(app, hexConfig, gridConfig) + constructor(app, grid) { this.app = app; - this.hex = defineHex(hexConfig); - this.grid = new Grid(this.hex, rectangle(gridConfig)); + this.grid = grid; /** @member {Number} */ this.elapsed = 0.0; /** @member {Number} */ @@ -28,16 +25,12 @@ class Renderer const graphics = new PIXI.Graphics(); graphics.lineStyle(1, 0x999999); - const renderHex = (hex) => { - let cellColor = gridColor.background; - if (this.selectedCoordinate.q == hex.q && this.selectedCoordinate.r == hex.r) { - cellColor = gridColor.highlight; + const renderTile = (tile) => { + if (this.selectedCoordinate.q == tile.q && this.selectedCoordinate.r == tile.r) { + tile.renderSelected(graphics); + } else { + tile.render(graphics); } - - graphics - .beginFill(cellColor) - .drawShape(new PIXI.Polygon(hex.corners)) - .endFill(); } this.app.ticker.add((delta) => { @@ -48,7 +41,7 @@ class Renderer return; this.elapsed = timeNow; - this.grid.forEach(renderHex); + this.grid.forEach(renderTile); this.app.stage.addChild(graphics); }); } From 276bd0e7520e2614c63d622996a543c2e0d4905a Mon Sep 17 00:00:00 2001 From: extremebip Date: Fri, 12 Jan 2024 20:36:44 +0700 Subject: [PATCH 04/44] Add initial rendering & basic re-rendering with event --- main.js | 14 +++++++++---- src/render.js | 56 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/main.js b/main.js index 6ec13a8..9195711 100644 --- a/main.js +++ b/main.js @@ -31,15 +31,21 @@ renderer.init(); const grid = renderer.getGrid(); +console.log(grid); + document.addEventListener('click', ({offsetX, offsetY}) => { const hex = grid.pointToHex( { x: offsetX, y: offsetY }, { allowOutside: false } ); - + if (hex !== undefined) { - renderer.selectCoordinate(hex.q, hex.r); - } else { - renderer.selectCoordinate(null, null); + renderer.addEvent({q: hex.q, r: hex.r, event: 'click'}); } + + // if (hex !== undefined) { + // renderer.selectCoordinate(hex.q, hex.r); + // } else { + // renderer.selectCoordinate(null, null); + // } }); \ No newline at end of file diff --git a/src/render.js b/src/render.js index 897dcea..9c68ada 100644 --- a/src/render.js +++ b/src/render.js @@ -4,6 +4,11 @@ import { maxFPS } from '../game-config.json'; class Renderer { + /** + * @type {Array} + */ + eventList = []; + /** * Construct the renderer object * @param {PIXI.Application} app @@ -22,16 +27,11 @@ class Renderer init() { - const graphics = new PIXI.Graphics(); - graphics.lineStyle(1, 0x999999); - - const renderTile = (tile) => { - if (this.selectedCoordinate.q == tile.q && this.selectedCoordinate.r == tile.r) { - tile.renderSelected(graphics); - } else { - tile.render(graphics); - } - } + this.graphics = new PIXI.Graphics(); + this.graphics.lineStyle(1, 0x999999); + + this.grid.forEach((tile) => tile.render(this.graphics)); + this.app.stage.addChild(this.graphics); this.app.ticker.add((delta) => { const timeNow = (new Date()).getTime(); @@ -41,19 +41,45 @@ class Renderer return; this.elapsed = timeNow; - this.grid.forEach(renderTile); - this.app.stage.addChild(graphics); + this.processEvents(); }); } - selectCoordinate(q, r){ - this.selectedCoordinate.q = q; - this.selectedCoordinate.r = r; + processEvents(){ + if (this.eventList.length === 0) return; + + while (this.eventList.length > 0) { + const e = this.eventList.shift(); + const tile = this.grid.getHex({q: e.q, r: e.r}); + if (e.event == "click") { + tile.renderSelected(this.graphics); + const selectedHex = this.getSelectedHex(); + + if (selectedHex != null) { + selectedHex.render(this.graphics); + } + + this.selectedCoordinate.q = e.q; + this.selectedCoordinate.r = e.r; + } + } + } + + addEvent(event){ + this.eventList.push(event); } getGrid(){ return this.grid; } + + getSelectedHex(){ + if (this.selectedCoordinate.q == null && this.selectedCoordinate.r == null) { + return null; + } else { + return this.grid.getHex({q: this.selectedCoordinate.q, r: this.selectedCoordinate.r}); + } + } } export default Renderer \ No newline at end of file From dfc160cadedae57836fdcb89b266995d9b7f2100 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 9 Mar 2024 18:11:59 +0700 Subject: [PATCH 05/44] Patch multiple click on active tile cause it to be inactive --- src/render.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/render.js b/src/render.js index 9c68ada..a8d7fd7 100644 --- a/src/render.js +++ b/src/render.js @@ -52,12 +52,12 @@ class Renderer const e = this.eventList.shift(); const tile = this.grid.getHex({q: e.q, r: e.r}); if (e.event == "click") { - tile.renderSelected(this.graphics); const selectedHex = this.getSelectedHex(); - if (selectedHex != null) { - selectedHex.render(this.graphics); + selectedHex.render(); } + + tile.renderSelected(); this.selectedCoordinate.q = e.q; this.selectedCoordinate.r = e.r; From 3d6e94597f3338abed3111ada7988ecfcefd86f4 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 9 Mar 2024 18:12:17 +0700 Subject: [PATCH 06/44] Patch duplicate tile rendered on active tile --- src/model/MapTile.js | 28 ++++++++++++++++++++++------ src/render.js | 6 +----- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/model/MapTile.js b/src/model/MapTile.js index e874942..cee531e 100644 --- a/src/model/MapTile.js +++ b/src/model/MapTile.js @@ -10,6 +10,11 @@ class MapTile extends defineHex(gameConfig.hex) */ cellNumber; + /** + * @type {PIXI.Graphics} + */ + graphic; + /** * @param {import('honeycomb-grid').HexCoordinates} coordinates * @param {Number} cellNumber @@ -18,37 +23,48 @@ class MapTile extends defineHex(gameConfig.hex) { const hex = new MapTile(coordinates); hex.cellNumber = cellNumber; + hex.graphic = new PIXI.Graphics(); + // hex.graphic.lineStyle(1, 0x999999); return hex; } /** * @param {PIXI.Graphics} graphic */ - render(graphic) + render() { + this.graphic.clear(); + this.graphic.lineStyle(1, 0x999999); + // graphic.clear(); let cellColor = gridColor.background; if (this.cellNumber > 0) { cellColor = gridColor.primary; } - graphic.beginFill(cellColor).drawShape(new PIXI.Polygon(this.corners)); + this.graphic.beginFill(cellColor).drawShape(new PIXI.Polygon(this.corners)); + this.graphic.removeChildren(); if (this.cellNumber > 0) { const text = new PIXI.Text(this.cellNumber); text.x = this.x - text.width / 2; text.y = this.y - text.height / 2; - graphic.addChild(text); + this.graphic.addChild(text); } - graphic.endFill(); + this.graphic.endFill(); + + return this.graphic; } /** * @param {PIXI.Graphics} graphic */ - renderSelected(graphic) + renderSelected() { - graphic.beginFill(gridColor.highlight) + this.graphic.clear(); + this.graphic.lineStyle(1, 0x999999); + // graphic.clear(); + this.graphic.beginFill(gridColor.highlight) .drawShape(new PIXI.Polygon(this.corners)) .endFill(); } diff --git a/src/render.js b/src/render.js index a8d7fd7..0101ed5 100644 --- a/src/render.js +++ b/src/render.js @@ -27,11 +27,7 @@ class Renderer init() { - this.graphics = new PIXI.Graphics(); - this.graphics.lineStyle(1, 0x999999); - - this.grid.forEach((tile) => tile.render(this.graphics)); - this.app.stage.addChild(this.graphics); + this.grid.forEach((tile) => this.app.stage.addChild(tile.render())); this.app.ticker.add((delta) => { const timeNow = (new Date()).getTime(); From daae02a16fbc473d6751c2f24b3b0c41c17b03d5 Mon Sep 17 00:00:00 2001 From: extremebip Date: Mon, 11 Mar 2024 23:15:46 +0700 Subject: [PATCH 07/44] Refactor renderer to handle multiple selected tiles --- main.js | 17 +++++++++-------- src/model/MapTile.js | 5 ++--- src/render.js | 34 ++++++++++++++++++---------------- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/main.js b/main.js index 9195711..5f0517e 100644 --- a/main.js +++ b/main.js @@ -39,13 +39,14 @@ document.addEventListener('click', ({offsetX, offsetY}) => { { allowOutside: false } ); - if (hex !== undefined) { - renderer.addEvent({q: hex.q, r: hex.r, event: 'click'}); + if (hex === undefined) return; + + const highlightedTiles = renderer.getHighlightedTiles(); + const clickedTile = highlightedTiles.find((tile) => hex.q == tile.q && hex.r == tile.r); + if (clickedTile !== undefined) { + renderer.addEvent({q: hex.q, r: hex.r, event: 'unhighlight'}); + } else { + renderer.addEvent({q: hex.q, r: hex.r, event: 'highlight'}); } - - // if (hex !== undefined) { - // renderer.selectCoordinate(hex.q, hex.r); - // } else { - // renderer.selectCoordinate(null, null); - // } + }); \ No newline at end of file diff --git a/src/model/MapTile.js b/src/model/MapTile.js index cee531e..2ec7ffa 100644 --- a/src/model/MapTile.js +++ b/src/model/MapTile.js @@ -24,7 +24,6 @@ class MapTile extends defineHex(gameConfig.hex) const hex = new MapTile(coordinates); hex.cellNumber = cellNumber; hex.graphic = new PIXI.Graphics(); - // hex.graphic.lineStyle(1, 0x999999); return hex; } @@ -35,7 +34,7 @@ class MapTile extends defineHex(gameConfig.hex) { this.graphic.clear(); this.graphic.lineStyle(1, 0x999999); - // graphic.clear(); + let cellColor = gridColor.background; if (this.cellNumber > 0) { cellColor = gridColor.primary; @@ -63,7 +62,7 @@ class MapTile extends defineHex(gameConfig.hex) { this.graphic.clear(); this.graphic.lineStyle(1, 0x999999); - // graphic.clear(); + this.graphic.beginFill(gridColor.highlight) .drawShape(new PIXI.Polygon(this.corners)) .endFill(); diff --git a/src/render.js b/src/render.js index 0101ed5..e6b7846 100644 --- a/src/render.js +++ b/src/render.js @@ -22,7 +22,8 @@ class Renderer this.elapsed = 0.0; /** @member {Number} */ this.fps = maxFPS; - this.selectedCoordinate = {q: null, r: null}; + /** @member {Array.<{q: Number, r:Number}>} */ + this.highlightedCoordinates = []; } init() @@ -47,16 +48,18 @@ class Renderer while (this.eventList.length > 0) { const e = this.eventList.shift(); const tile = this.grid.getHex({q: e.q, r: e.r}); - if (e.event == "click") { - const selectedHex = this.getSelectedHex(); - if (selectedHex != null) { - selectedHex.render(); - } - + if (e.event == "highlight") { tile.renderSelected(); - - this.selectedCoordinate.q = e.q; - this.selectedCoordinate.r = e.r; + this.highlightedCoordinates.push({q: tile.q, r: tile.r}); + } else if (e.event == "unhighlight") { + tile.render(); + const highlightedIdx = this.highlightedCoordinates.findIndex( + (coordinate) => coordinate.q == tile.q && coordinate.r == tile.r + ); + + if (highlightedIdx !== -1) { + this.highlightedCoordinates.splice(highlightedIdx, 1); + } } } } @@ -69,12 +72,11 @@ class Renderer return this.grid; } - getSelectedHex(){ - if (this.selectedCoordinate.q == null && this.selectedCoordinate.r == null) { - return null; - } else { - return this.grid.getHex({q: this.selectedCoordinate.q, r: this.selectedCoordinate.r}); - } + getHighlightedTiles(){ + if (this.highlightedCoordinates.length === 0) return []; + return this.highlightedCoordinates.map((coordinate) => { + return this.grid.getHex(coordinate); + }); } } From c0e527441319845c479bfa7f7cf83e9dce25fb9e Mon Sep 17 00:00:00 2001 From: extremebip Date: Tue, 12 Mar 2024 00:09:06 +0700 Subject: [PATCH 08/44] Add tile movement system --- main.js | 16 +++++++--------- src/system.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 src/system.js diff --git a/main.js b/main.js index 5f0517e..7243707 100644 --- a/main.js +++ b/main.js @@ -2,6 +2,7 @@ import * as PIXI from 'pixi.js'; import { Grid } from 'honeycomb-grid'; import Renderer from './src/render'; import MapTile from './src/model/MapTile'; +import GameSystem from './src/system'; const app = new PIXI.Application({ backgroundAlpha: 0 }); @@ -33,20 +34,17 @@ const grid = renderer.getGrid(); console.log(grid); +const gameSystem = new GameSystem(renderer); + document.addEventListener('click', ({offsetX, offsetY}) => { - const hex = grid.pointToHex( + const tile = grid.pointToHex( { x: offsetX, y: offsetY }, { allowOutside: false } ); - if (hex === undefined) return; - - const highlightedTiles = renderer.getHighlightedTiles(); - const clickedTile = highlightedTiles.find((tile) => hex.q == tile.q && hex.r == tile.r); - if (clickedTile !== undefined) { - renderer.addEvent({q: hex.q, r: hex.r, event: 'unhighlight'}); + if (gameSystem.activeTile == null) { + gameSystem.selectTile(tile); } else { - renderer.addEvent({q: hex.q, r: hex.r, event: 'highlight'}); + gameSystem.attemptTileMovement(tile); } - }); \ No newline at end of file diff --git a/src/system.js b/src/system.js new file mode 100644 index 0000000..f83ec87 --- /dev/null +++ b/src/system.js @@ -0,0 +1,51 @@ +class GameSystem +{ + /** + * @type {import('./model/MapTile').default} + */ + activeTile = null; + + /** + * + * @param {import('./render').default} renderer + */ + constructor(renderer) + { + this.renderer = renderer; + } + + selectTile(tile) { + if (tile === undefined) { + this.renderer.addEvent({q: this.activeTile.q, r: this.activeTile.r, event: 'unhighlight'}); + this.activeTile = null; + } else { + this.activeTile = tile; + this.renderer.addEvent({q: this.activeTile.q, r: this.activeTile.r, event: 'highlight'}); + } + } + + attemptTileMovement(targetTile) { + if (targetTile === undefined) { + this.selectTile(undefined); + return; + } + + const grid = this.renderer.getGrid(); + const distance = grid.distance(this.activeTile, targetTile); + if (distance != 1) { + this.selectTile(undefined); + return; + } + + this.moveTile(targetTile); + } + + moveTile(targetTile) { + targetTile.cellNumber += this.activeTile.cellNumber; + this.activeTile.cellNumber = 0; + this.selectTile(undefined); + targetTile.render(); + } +} + +export default GameSystem; \ No newline at end of file From f6af0b9b6d03e3846bb5a0977d54b3de02cd0cfd Mon Sep 17 00:00:00 2001 From: extremebip Date: Thu, 13 Jun 2024 13:28:59 +0700 Subject: [PATCH 09/44] Reset projects --- colors.json | 8 - doc.MD | 6 - game-config.json | 8 - index.html | 13 - main.js | 50 -- package-lock.json | 1344 ------------------------------------------ package.json | 27 - src/model/MapTile.js | 72 --- src/render.js | 83 --- src/system.js | 51 -- 10 files changed, 1662 deletions(-) delete mode 100644 colors.json delete mode 100644 doc.MD delete mode 100644 game-config.json delete mode 100644 index.html delete mode 100644 main.js delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 src/model/MapTile.js delete mode 100644 src/render.js delete mode 100644 src/system.js diff --git a/colors.json b/colors.json deleted file mode 100644 index cceef3c..0000000 --- a/colors.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "grid": - { - "background": "#000", - "primary": "#0d6efd", - "highlight": "#44ffb7" - } -} \ No newline at end of file diff --git a/doc.MD b/doc.MD deleted file mode 100644 index 39f3db4..0000000 --- a/doc.MD +++ /dev/null @@ -1,6 +0,0 @@ -TODO: -~~1. Create map grid with colors.~~ -~~2. Add click handler to switch color when clicked.~~ -~~3. Put numbers on grid. If zero then don't show, else show.~~ - 1. Optimize how grid is rendered -4. Make number > 1 moveable within 1 distance. If target tile has number, add them together. \ No newline at end of file diff --git a/game-config.json b/game-config.json deleted file mode 100644 index a962982..0000000 --- a/game-config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "maxFPS": 30, - "hex": - { - "dimensions": 30, - "origin": "topLeft" - } -} \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index 3d08438..0000000 --- a/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite App - - -
- - - diff --git a/main.js b/main.js deleted file mode 100644 index 7243707..0000000 --- a/main.js +++ /dev/null @@ -1,50 +0,0 @@ -import * as PIXI from 'pixi.js'; -import { Grid } from 'honeycomb-grid'; -import Renderer from './src/render'; -import MapTile from './src/model/MapTile'; -import GameSystem from './src/system'; - -const app = new PIXI.Application({ backgroundAlpha: 0 }); - -globalThis.__PIXI_APP__ = app; - -document.body.appendChild(app.view); - -// TEMP CREATE GRID - -let exampleGrid = []; - -for (let i = 0; i < 10; i++) { - for (let j = 0; j < 10; j++) { - const randNum = Math.floor(Math.random() * 3); - const coordinates = { col: j, row: i }; - exampleGrid.push(MapTile.create(coordinates, (randNum < 1 ? 1 : 0))); - } -} - -// END TEMP CREATE GRID - -const renderer = new Renderer( - app, - Grid.fromIterable(exampleGrid) -); -renderer.init(); - -const grid = renderer.getGrid(); - -console.log(grid); - -const gameSystem = new GameSystem(renderer); - -document.addEventListener('click', ({offsetX, offsetY}) => { - const tile = grid.pointToHex( - { x: offsetX, y: offsetY }, - { allowOutside: false } - ); - - if (gameSystem.activeTile == null) { - gameSystem.selectTile(tile); - } else { - gameSystem.attemptTileMovement(tile); - } -}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 5aff014..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1344 +0,0 @@ -{ - "name": "battle-simulator", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "battle-simulator", - "version": "0.0.0", - "license": "ISC", - "dependencies": { - "honeycomb-grid": "^4.1.5", - "pixi.js": "^7.3.2" - }, - "devDependencies": { - "vite": "^5.0.8" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.10.tgz", - "integrity": "sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.10.tgz", - "integrity": "sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.10.tgz", - "integrity": "sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.10.tgz", - "integrity": "sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.10.tgz", - "integrity": "sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.10.tgz", - "integrity": "sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.10.tgz", - "integrity": "sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.10.tgz", - "integrity": "sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.10.tgz", - "integrity": "sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.10.tgz", - "integrity": "sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.10.tgz", - "integrity": "sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.10.tgz", - "integrity": "sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.10.tgz", - "integrity": "sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.10.tgz", - "integrity": "sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.10.tgz", - "integrity": "sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.10.tgz", - "integrity": "sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.10.tgz", - "integrity": "sha512-SpYNEqg/6pZYoc+1zLCjVOYvxfZVZj6w0KROZ3Fje/QrM3nfvT2llI+wmKSrWuX6wmZeTapbarvuNNK/qepSgA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.10.tgz", - "integrity": "sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.10.tgz", - "integrity": "sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.10.tgz", - "integrity": "sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.10.tgz", - "integrity": "sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.10.tgz", - "integrity": "sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.10.tgz", - "integrity": "sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@pixi/accessibility": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/accessibility/-/accessibility-7.3.2.tgz", - "integrity": "sha512-MdkU22HTauRvq9cMeWZIQGaDDa86sr+m12rKNdLV+FaDQgP/AhP+qCVpK7IKeJa9BrWGXaYMw/vueij7HkyDSA==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2", - "@pixi/events": "7.3.2" - } - }, - "node_modules/@pixi/app": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/app/-/app-7.3.2.tgz", - "integrity": "sha512-3YRFSMvAxDebAz3/JJv+2jzbPkT8cHC0IHmmLRN8krDL1pZV+YjMLgMwN/Oeyv5TSbwNqnrF5su5whNkRaxeZQ==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2" - } - }, - "node_modules/@pixi/assets": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/assets/-/assets-7.3.2.tgz", - "integrity": "sha512-yteq6ptAxA09EcwU9D9hl7qr5yWIqy+c2PsXkTDkc76vTAwIamLY3KxLq2aR5y1U4L4O6aHFJd26uNhHcuTPmw==", - "dependencies": { - "@types/css-font-loading-module": "^0.0.7" - }, - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/utils": "7.3.2" - } - }, - "node_modules/@pixi/color": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/color/-/color-7.3.2.tgz", - "integrity": "sha512-jur5PvdOtUBEUTjmPudW5qdQq6yYGlVGsi3HyhasJw14bN+GKJwiCKgIsyrsiNL5HBUXmje4ICwQohf6BqKqxA==", - "dependencies": { - "@pixi/colord": "^2.9.6" - } - }, - "node_modules/@pixi/colord": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", - "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==" - }, - "node_modules/@pixi/compressed-textures": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/compressed-textures/-/compressed-textures-7.3.2.tgz", - "integrity": "sha512-J3ENMHDPQO6CJRei55gqI0WmiZJIK6SgsW5AEkShT0aAe5miEBSomv70pXw/58ru+4/Hx8cXjamsGt4aQB2D0Q==", - "peerDependencies": { - "@pixi/assets": "7.3.2", - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/constants": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/constants/-/constants-7.3.2.tgz", - "integrity": "sha512-Q8W3ncsFxmfgC5EtokpG92qJZabd+Dl+pbQAdHwiPY3v+8UNq77u4VN2qtl1Z04864hCcg7AStIYEDrzqTLF6Q==" - }, - "node_modules/@pixi/core": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/core/-/core-7.3.2.tgz", - "integrity": "sha512-Pta3ee8MtJ3yKxGXzglBWgwbEOKMB6Eth+FpLTjL0rgxiqTB550YX6jsNEQQAzcGjCBlO3rC/IF57UZ2go/X6w==", - "dependencies": { - "@pixi/color": "7.3.2", - "@pixi/constants": "7.3.2", - "@pixi/extensions": "7.3.2", - "@pixi/math": "7.3.2", - "@pixi/runner": "7.3.2", - "@pixi/settings": "7.3.2", - "@pixi/ticker": "7.3.2", - "@pixi/utils": "7.3.2", - "@types/offscreencanvas": "^2019.6.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/pixijs" - } - }, - "node_modules/@pixi/display": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/display/-/display-7.3.2.tgz", - "integrity": "sha512-cY5AnZ3TWt5GYGx4e5AQ2/2U9kP+RorBg/O30amJ+8e9bFk9rS8cjh/DDq/hc4lql96BkXAInTl40eHnAML5lQ==", - "peerDependencies": { - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/events": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/events/-/events-7.3.2.tgz", - "integrity": "sha512-Moca9epu8jk1wIQCdVYjhz2pD9Ol21m50wvWUKvpgt9yM/AjkCLSDt8HO/PmTpavDrkhx5pVVWeDDA6FyUNaGA==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2" - } - }, - "node_modules/@pixi/extensions": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/extensions/-/extensions-7.3.2.tgz", - "integrity": "sha512-Qw84ADfvmVu4Mwj+zTik/IEEK9lWS5n4trbrpQCcEZ+Mb8oRAXWvKz199mi1s7+LaZXDqeCY1yr2PHQaFf1KBA==" - }, - "node_modules/@pixi/extract": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/extract/-/extract-7.3.2.tgz", - "integrity": "sha512-KsoflvQZV/XD8A8xbtRnmI4reYekbI4MOi7ilwQe5tMz6O1mO7IzrSukxkSMD02f6SpbAqbi7a1EayTjvY0ECQ==", - "peerDependencies": { - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/filter-alpha": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/filter-alpha/-/filter-alpha-7.3.2.tgz", - "integrity": "sha512-nZMdn310wH5ZK1slwv3X4qT8eLoAGO7SgYGCy5IsMtpCtNObzE9XA4tAfhXrjihyzPS9KvszgAbnv1Qpfh0/uw==", - "peerDependencies": { - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/filter-blur": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/filter-blur/-/filter-blur-7.3.2.tgz", - "integrity": "sha512-unu3zhwHMhN+iAe7Td2rK40i2UJ2GOhzWK+6jcU3ZkMOsFCT5kgBoMRTejeQVcvCs6GoYK8imbkE7mXt05Vj6A==", - "peerDependencies": { - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/filter-color-matrix": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/filter-color-matrix/-/filter-color-matrix-7.3.2.tgz", - "integrity": "sha512-rbyjes/9SMoV9jjPiK0sLMkmLfN8D17GoTJIfq/KLv1x9646W5fL2QSKkN04UkZ+020ndWvIOxK1S97tvRyCfg==", - "peerDependencies": { - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/filter-displacement": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/filter-displacement/-/filter-displacement-7.3.2.tgz", - "integrity": "sha512-ZHl7Sfb8JYd9Z6j96OHCC0NhMKhhXJRE5AbkSDohjEMVCK1BV5rDGAHV8WVt/2MJ/j83CXUpydzyMhdM4lMchg==", - "peerDependencies": { - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/filter-fxaa": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/filter-fxaa/-/filter-fxaa-7.3.2.tgz", - "integrity": "sha512-9brtlxDnQTZk2XiFBKdBK9e+8CX9LdxxcL7LRpjEyiHuAPvTlQgu9B85LrJ4GzWKqJJKaIIZBzhIoiCLUnfeXg==", - "peerDependencies": { - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/filter-noise": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/filter-noise/-/filter-noise-7.3.2.tgz", - "integrity": "sha512-F8GQQ20n7tCjThX6GCXckiXz2YffOCxicTJ0oat9aVDZh+sVsAxYX0aKSdHh0hhv18F0yuc6tPsSL5DYb63xFg==", - "peerDependencies": { - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/graphics": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/graphics/-/graphics-7.3.2.tgz", - "integrity": "sha512-PhU6j1yub4tH/s+/gqByzgZ3mLv1mfb6iGXbquycg3+WypcxHZn0opFtI/axsazaQ9SEaWxw1m3i40WG5ANH5g==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2", - "@pixi/sprite": "7.3.2" - } - }, - "node_modules/@pixi/math": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/math/-/math-7.3.2.tgz", - "integrity": "sha512-dutoZ0IVJ5ME7UtYNo2szu4D7qsgtJB7e3ylujBVu7BOP2e710BVtFwFSFV768N14h9H5roGnuzVoDiJac2u+w==" - }, - "node_modules/@pixi/mesh": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/mesh/-/mesh-7.3.2.tgz", - "integrity": "sha512-LFkt7ELYXQLgbgHpjl68j6JD5ejUwma8zoPn2gqSBbY+6pK/phjvV1Wkh76muF46VvNulgXF0+qLIDdCsfrDaA==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2" - } - }, - "node_modules/@pixi/mesh-extras": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/mesh-extras/-/mesh-extras-7.3.2.tgz", - "integrity": "sha512-s/tg9TsTZZxLEdCDKWnBChDGkc041HCTP7ykJv4fEROzb9B0lskULYyvv+/YNNKa2Ugb9WnkMknpOdOXCpjyyg==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/mesh": "7.3.2" - } - }, - "node_modules/@pixi/mixin-cache-as-bitmap": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/mixin-cache-as-bitmap/-/mixin-cache-as-bitmap-7.3.2.tgz", - "integrity": "sha512-bZRlyUN5+9kCUjn67V0IFtYIrbmx9Vs4sMOmXyrX3Q4B4gPLE46IzZz3v0IVaTjp32udlQztfJalIaWbuqgb3A==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2", - "@pixi/sprite": "7.3.2" - } - }, - "node_modules/@pixi/mixin-get-child-by-name": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/mixin-get-child-by-name/-/mixin-get-child-by-name-7.3.2.tgz", - "integrity": "sha512-mbUi3WxXrkViH7qOgjk4fu2BN36NwNb7u+Fy1J5dS8Bntj57ZVKmEV9PbUy0zYjXE8rVmeAvSu/2kbn5n9UutQ==", - "peerDependencies": { - "@pixi/display": "7.3.2" - } - }, - "node_modules/@pixi/mixin-get-global-position": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/mixin-get-global-position/-/mixin-get-global-position-7.3.2.tgz", - "integrity": "sha512-1nhWbBgmw6rK7yQJxzeI9yjKYYEkM5i3pee8qVu4YWo3b1xWVQA7osQG7aGM/4qywDkXaA1ZvciA5hfg6f4Q5Q==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2" - } - }, - "node_modules/@pixi/particle-container": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/particle-container/-/particle-container-7.3.2.tgz", - "integrity": "sha512-JYc4j4z97KmxyLp+1Lg0SNi8hy6RxcBBNQGk+CSLNXeDWxx3hykT5gj/ORX1eXyzHh1ZCG1XzeVS9Yr8QhlFHA==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2", - "@pixi/sprite": "7.3.2" - } - }, - "node_modules/@pixi/prepare": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/prepare/-/prepare-7.3.2.tgz", - "integrity": "sha512-aLPAXSYLUhMwxzJtn9m0TSZe+dQlZCt09QNBqYbSi8LZId54QMDyvfBb4zBOJZrD2xAZgYL5RIJuKHwZtFX6lQ==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2", - "@pixi/graphics": "7.3.2", - "@pixi/text": "7.3.2" - } - }, - "node_modules/@pixi/runner": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/runner/-/runner-7.3.2.tgz", - "integrity": "sha512-maKotoKJCQiQGBJwfM+iYdQKjrPN/Tn9+72F4WIf706zp/5vKoxW688Rsktg5BX4Mcn7ZkZvcJYTxj2Mv87lFA==" - }, - "node_modules/@pixi/settings": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/settings/-/settings-7.3.2.tgz", - "integrity": "sha512-vtxzuARDTbFe0fRYSqB53B+mPpX7v+QjjnCUmVMVvZiWr3QcngMWVml6c6dQDln7IakWoKZRrNG4FpggvDgLVg==", - "dependencies": { - "@pixi/constants": "7.3.2", - "@types/css-font-loading-module": "^0.0.7", - "ismobilejs": "^1.1.0" - } - }, - "node_modules/@pixi/sprite": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/sprite/-/sprite-7.3.2.tgz", - "integrity": "sha512-IpWTKXExJNXVcY7ITopJ+JW48DahdbCo/81D2IYzBImq3jyiJM2Km5EoJgvAM5ZQ3Ev3KPPIBzYLD+HoPWcxdw==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2" - } - }, - "node_modules/@pixi/sprite-animated": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/sprite-animated/-/sprite-animated-7.3.2.tgz", - "integrity": "sha512-j9pyUe4cefxE9wecNfbWQyL5fBQKvCGYaOA0DE1X46ukBHrIuhA8u3jg2X3N3r4IcbVvxpWFYDrDsWXWeiBmSw==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/sprite": "7.3.2" - } - }, - "node_modules/@pixi/sprite-tiling": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/sprite-tiling/-/sprite-tiling-7.3.2.tgz", - "integrity": "sha512-tWVVb/rMIx5AczfUrVxa0dZaIufP5C0IOL7IGfFUDQqDu5JSAUC0mwLe4F12jAXBVsqYhCGYx5bIHbPiI5vcSQ==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2", - "@pixi/sprite": "7.3.2" - } - }, - "node_modules/@pixi/spritesheet": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/spritesheet/-/spritesheet-7.3.2.tgz", - "integrity": "sha512-UkwqrPYDqrEdK5ub9qn/9VBvt5caA8ffV5iYR6ssCvrpaQovBKmS+b5pr/BYf8xNTExDpR3OmPIo8iDEYWWLuw==", - "peerDependencies": { - "@pixi/assets": "7.3.2", - "@pixi/core": "7.3.2" - } - }, - "node_modules/@pixi/text": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/text/-/text-7.3.2.tgz", - "integrity": "sha512-LdtNj+K5tPB/0UcDcO52M/C7xhwFTGFhtdF42fPhRuJawM23M3zm1Y8PapXv+mury+IxCHT1w30YlAi0qTVpKQ==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/sprite": "7.3.2" - } - }, - "node_modules/@pixi/text-bitmap": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/text-bitmap/-/text-bitmap-7.3.2.tgz", - "integrity": "sha512-p8KLgtZSPowWU/Zj+GVtfsUT8uGYo4TtKKYbLoWuxkRA5Pc1+4C9/rV/EOSFfoZIdW5C+iFg5VxRgBllUQf+aA==", - "peerDependencies": { - "@pixi/assets": "7.3.2", - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2", - "@pixi/mesh": "7.3.2", - "@pixi/text": "7.3.2" - } - }, - "node_modules/@pixi/text-html": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/text-html/-/text-html-7.3.2.tgz", - "integrity": "sha512-IYhBWEPOvqUtlHkS5/c1Hseuricj5jrrGd21ivcvHmcnK/x2m+CRGvvzeBp1mqoYBnDbQVrD2wSXSe4Dv9tEJA==", - "peerDependencies": { - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2", - "@pixi/sprite": "7.3.2", - "@pixi/text": "7.3.2" - } - }, - "node_modules/@pixi/ticker": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/ticker/-/ticker-7.3.2.tgz", - "integrity": "sha512-5kIPhBeXwDJohCzKzJJ6T7f1oAGbHAgeiwOjlTO+9lNXUX8ZPj0407V3syuF+64kFqJzIBCznBRpI+fmT4c9SA==", - "dependencies": { - "@pixi/extensions": "7.3.2", - "@pixi/settings": "7.3.2", - "@pixi/utils": "7.3.2" - } - }, - "node_modules/@pixi/utils": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-7.3.2.tgz", - "integrity": "sha512-KhNvj9YcY7Zi2dTKZgDpx8C6OxKKR541vwtG6JgdBZZYDeMBOIghN2Vi5zn4diW5BhDfHBmdSJ1wZXEtE2MDwg==", - "dependencies": { - "@pixi/color": "7.3.2", - "@pixi/constants": "7.3.2", - "@pixi/settings": "7.3.2", - "@types/earcut": "^2.1.0", - "earcut": "^2.2.4", - "eventemitter3": "^4.0.0", - "url": "^0.11.0" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.1.tgz", - "integrity": "sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.1.tgz", - "integrity": "sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.1.tgz", - "integrity": "sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.1.tgz", - "integrity": "sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.1.tgz", - "integrity": "sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.1.tgz", - "integrity": "sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.1.tgz", - "integrity": "sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.1.tgz", - "integrity": "sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.1.tgz", - "integrity": "sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.1.tgz", - "integrity": "sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.1.tgz", - "integrity": "sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.1.tgz", - "integrity": "sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.1.tgz", - "integrity": "sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/css-font-loading-module": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", - "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==" - }, - "node_modules/@types/earcut": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", - "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==" - }, - "node_modules/@types/offscreencanvas": { - "version": "2019.7.3", - "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", - "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==" - }, - "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dependencies": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/earcut": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", - "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" - }, - "node_modules/esbuild": { - "version": "0.19.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.10.tgz", - "integrity": "sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.10", - "@esbuild/android-arm": "0.19.10", - "@esbuild/android-arm64": "0.19.10", - "@esbuild/android-x64": "0.19.10", - "@esbuild/darwin-arm64": "0.19.10", - "@esbuild/darwin-x64": "0.19.10", - "@esbuild/freebsd-arm64": "0.19.10", - "@esbuild/freebsd-x64": "0.19.10", - "@esbuild/linux-arm": "0.19.10", - "@esbuild/linux-arm64": "0.19.10", - "@esbuild/linux-ia32": "0.19.10", - "@esbuild/linux-loong64": "0.19.10", - "@esbuild/linux-mips64el": "0.19.10", - "@esbuild/linux-ppc64": "0.19.10", - "@esbuild/linux-riscv64": "0.19.10", - "@esbuild/linux-s390x": "0.19.10", - "@esbuild/linux-x64": "0.19.10", - "@esbuild/netbsd-x64": "0.19.10", - "@esbuild/openbsd-x64": "0.19.10", - "@esbuild/sunos-x64": "0.19.10", - "@esbuild/win32-arm64": "0.19.10", - "@esbuild/win32-ia32": "0.19.10", - "@esbuild/win32-x64": "0.19.10" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dependencies": { - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dependencies": { - "get-intrinsic": "^1.2.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/honeycomb-grid": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/honeycomb-grid/-/honeycomb-grid-4.1.5.tgz", - "integrity": "sha512-VrnQwu5dHuzqK3wFhLD9EURmLSyEWb0teiHhDJq6WdK0MrFsEtKNYT1HLzXOLe2X1acU8Y9hhK7wWfIiGK1G5w==", - "engines": { - "node": ">=16" - } - }, - "node_modules/ismobilejs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", - "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==" - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/pixi.js": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-7.3.2.tgz", - "integrity": "sha512-GJickUrT3UcBInGT1CU6cv2oktCdocE5QM74CD3t+weiJPPWIzleNlp7zrBR5QIDdU6bEO8CUgUXH2Y9QvlCMw==", - "dependencies": { - "@pixi/accessibility": "7.3.2", - "@pixi/app": "7.3.2", - "@pixi/assets": "7.3.2", - "@pixi/compressed-textures": "7.3.2", - "@pixi/core": "7.3.2", - "@pixi/display": "7.3.2", - "@pixi/events": "7.3.2", - "@pixi/extensions": "7.3.2", - "@pixi/extract": "7.3.2", - "@pixi/filter-alpha": "7.3.2", - "@pixi/filter-blur": "7.3.2", - "@pixi/filter-color-matrix": "7.3.2", - "@pixi/filter-displacement": "7.3.2", - "@pixi/filter-fxaa": "7.3.2", - "@pixi/filter-noise": "7.3.2", - "@pixi/graphics": "7.3.2", - "@pixi/mesh": "7.3.2", - "@pixi/mesh-extras": "7.3.2", - "@pixi/mixin-cache-as-bitmap": "7.3.2", - "@pixi/mixin-get-child-by-name": "7.3.2", - "@pixi/mixin-get-global-position": "7.3.2", - "@pixi/particle-container": "7.3.2", - "@pixi/prepare": "7.3.2", - "@pixi/sprite": "7.3.2", - "@pixi/sprite-animated": "7.3.2", - "@pixi/sprite-tiling": "7.3.2", - "@pixi/spritesheet": "7.3.2", - "@pixi/text": "7.3.2", - "@pixi/text-bitmap": "7.3.2", - "@pixi/text-html": "7.3.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/pixijs" - } - }, - "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" - }, - "node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rollup": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.1.tgz", - "integrity": "sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.1", - "@rollup/rollup-android-arm64": "4.9.1", - "@rollup/rollup-darwin-arm64": "4.9.1", - "@rollup/rollup-darwin-x64": "4.9.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.1", - "@rollup/rollup-linux-arm64-gnu": "4.9.1", - "@rollup/rollup-linux-arm64-musl": "4.9.1", - "@rollup/rollup-linux-riscv64-gnu": "4.9.1", - "@rollup/rollup-linux-x64-gnu": "4.9.1", - "@rollup/rollup-linux-x64-musl": "4.9.1", - "@rollup/rollup-win32-arm64-msvc": "4.9.1", - "@rollup/rollup-win32-ia32-msvc": "4.9.1", - "@rollup/rollup-win32-x64-msvc": "4.9.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/url": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.3.tgz", - "integrity": "sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==", - "dependencies": { - "punycode": "^1.4.1", - "qs": "^6.11.2" - } - }, - "node_modules/vite": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", - "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", - "dev": true, - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index fc354a1..0000000 --- a/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "battle-simulator", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "preview": "vite preview" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/7GCraft/battle-simulator.git" - }, - "author": "", - "license": "ISC", - "bugs": { - "url": "https://github.com/7GCraft/battle-simulator/issues" - }, - "devDependencies": { - "vite": "^5.0.8" - }, - "dependencies": { - "honeycomb-grid": "^4.1.5", - "pixi.js": "^7.3.2" - } -} diff --git a/src/model/MapTile.js b/src/model/MapTile.js deleted file mode 100644 index 2ec7ffa..0000000 --- a/src/model/MapTile.js +++ /dev/null @@ -1,72 +0,0 @@ -import { defineHex } from 'honeycomb-grid' -import gameConfig from '../../game-config.json' -import { grid as gridColor } from '../../colors.json' -import * as PIXI from 'pixi.js'; - -class MapTile extends defineHex(gameConfig.hex) -{ - /** - * @type {Number} - */ - cellNumber; - - /** - * @type {PIXI.Graphics} - */ - graphic; - - /** - * @param {import('honeycomb-grid').HexCoordinates} coordinates - * @param {Number} cellNumber - */ - static create(coordinates, cellNumber) - { - const hex = new MapTile(coordinates); - hex.cellNumber = cellNumber; - hex.graphic = new PIXI.Graphics(); - return hex; - } - - /** - * @param {PIXI.Graphics} graphic - */ - render() - { - this.graphic.clear(); - this.graphic.lineStyle(1, 0x999999); - - let cellColor = gridColor.background; - if (this.cellNumber > 0) { - cellColor = gridColor.primary; - } - - this.graphic.beginFill(cellColor).drawShape(new PIXI.Polygon(this.corners)); - - this.graphic.removeChildren(); - if (this.cellNumber > 0) { - const text = new PIXI.Text(this.cellNumber); - text.x = this.x - text.width / 2; - text.y = this.y - text.height / 2; - this.graphic.addChild(text); - } - - this.graphic.endFill(); - - return this.graphic; - } - - /** - * @param {PIXI.Graphics} graphic - */ - renderSelected() - { - this.graphic.clear(); - this.graphic.lineStyle(1, 0x999999); - - this.graphic.beginFill(gridColor.highlight) - .drawShape(new PIXI.Polygon(this.corners)) - .endFill(); - } -} - -export default MapTile; \ No newline at end of file diff --git a/src/render.js b/src/render.js deleted file mode 100644 index e6b7846..0000000 --- a/src/render.js +++ /dev/null @@ -1,83 +0,0 @@ -import * as PIXI from 'pixi.js'; -import { Grid } from 'honeycomb-grid' -import { maxFPS } from '../game-config.json'; - -class Renderer -{ - /** - * @type {Array} - */ - eventList = []; - - /** - * Construct the renderer object - * @param {PIXI.Application} app - * @param {Grid} grid - */ - constructor(app, grid) - { - this.app = app; - this.grid = grid; - /** @member {Number} */ - this.elapsed = 0.0; - /** @member {Number} */ - this.fps = maxFPS; - /** @member {Array.<{q: Number, r:Number}>} */ - this.highlightedCoordinates = []; - } - - init() - { - this.grid.forEach((tile) => this.app.stage.addChild(tile.render())); - - this.app.ticker.add((delta) => { - const timeNow = (new Date()).getTime(); - const timeDiff = timeNow - this.elapsed; - const tickLimit = 1000 / this.fps; - if (timeDiff < tickLimit) - return; - - this.elapsed = timeNow; - this.processEvents(); - }); - } - - processEvents(){ - if (this.eventList.length === 0) return; - - while (this.eventList.length > 0) { - const e = this.eventList.shift(); - const tile = this.grid.getHex({q: e.q, r: e.r}); - if (e.event == "highlight") { - tile.renderSelected(); - this.highlightedCoordinates.push({q: tile.q, r: tile.r}); - } else if (e.event == "unhighlight") { - tile.render(); - const highlightedIdx = this.highlightedCoordinates.findIndex( - (coordinate) => coordinate.q == tile.q && coordinate.r == tile.r - ); - - if (highlightedIdx !== -1) { - this.highlightedCoordinates.splice(highlightedIdx, 1); - } - } - } - } - - addEvent(event){ - this.eventList.push(event); - } - - getGrid(){ - return this.grid; - } - - getHighlightedTiles(){ - if (this.highlightedCoordinates.length === 0) return []; - return this.highlightedCoordinates.map((coordinate) => { - return this.grid.getHex(coordinate); - }); - } -} - -export default Renderer \ No newline at end of file diff --git a/src/system.js b/src/system.js deleted file mode 100644 index f83ec87..0000000 --- a/src/system.js +++ /dev/null @@ -1,51 +0,0 @@ -class GameSystem -{ - /** - * @type {import('./model/MapTile').default} - */ - activeTile = null; - - /** - * - * @param {import('./render').default} renderer - */ - constructor(renderer) - { - this.renderer = renderer; - } - - selectTile(tile) { - if (tile === undefined) { - this.renderer.addEvent({q: this.activeTile.q, r: this.activeTile.r, event: 'unhighlight'}); - this.activeTile = null; - } else { - this.activeTile = tile; - this.renderer.addEvent({q: this.activeTile.q, r: this.activeTile.r, event: 'highlight'}); - } - } - - attemptTileMovement(targetTile) { - if (targetTile === undefined) { - this.selectTile(undefined); - return; - } - - const grid = this.renderer.getGrid(); - const distance = grid.distance(this.activeTile, targetTile); - if (distance != 1) { - this.selectTile(undefined); - return; - } - - this.moveTile(targetTile); - } - - moveTile(targetTile) { - targetTile.cellNumber += this.activeTile.cellNumber; - this.activeTile.cellNumber = 0; - this.selectTile(undefined); - targetTile.render(); - } -} - -export default GameSystem; \ No newline at end of file From bf8580f0e12d3885f06858d645748cfd08737755 Mon Sep 17 00:00:00 2001 From: extremebip Date: Thu, 13 Jun 2024 14:44:43 +0700 Subject: [PATCH 10/44] Init project --- index.html | 12 + package-lock.json | 2346 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 20 + public/vite.svg | 1 + src/App.ts | 0 src/Game.ts | 0 src/vite-env.d.ts | 1 + tsconfig.json | 23 + 8 files changed, 2403 insertions(+) create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 public/vite.svg create mode 100644 src/App.ts create mode 100644 src/Game.ts create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.json diff --git a/index.html b/index.html new file mode 100644 index 0000000..80538cf --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Battle Simulator + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..78c9a69 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2346 @@ +{ + "name": "battle-simulator", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "battle-simulator", + "version": "0.0.0", + "dependencies": { + "boardgame.io": "^0.50.2", + "honeycomb-grid": "^4.1.5", + "pixi.js": "^8.1.6" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.2.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@koa/cors": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.4.3.tgz", + "integrity": "sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==", + "dependencies": { + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@koa/router": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@koa/router/-/router-10.1.1.tgz", + "integrity": "sha512-ORNjq5z4EmQPriKbR0ER3k4Gh7YGNhWDL7JBW+8wXDrHLbWYKYSJaOJ9aN06npF5tbTxe2JBOsurpJDAvjiXKw==", + "deprecated": "**IMPORTANT 10x+ PERFORMANCE UPGRADE**: Please upgrade to v12.0.1+ as we have fixed an issue with debuglog causing 10x slower router benchmark performance, see https://github.com/koajs/router/pull/173", + "dependencies": { + "debug": "^4.1.1", + "http-errors": "^1.7.3", + "koa-compose": "^4.1.0", + "methods": "^1.1.2", + "path-to-regexp": "^6.1.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@pixi/colord": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@pixi/colord/-/colord-2.9.6.tgz", + "integrity": "sha512-nezytU2pw587fQstUu1AsJZDVEynjskwOL+kibwcdxsMBFqPsFFNA7xl0ii/gXuDi6M0xj3mfRJj8pBSc2jCfA==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@types/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/component-emitter": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.14.tgz", + "integrity": "sha512-lmPil1g82wwWg/qHSxMWkSKyJGQOK+ejXeMAAWyxNtVUD0/Ycj2maL63RAqpxVfdtvTfZkRnqzB0A9ft59y69g==" + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/content-disposition": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/@types/content-disposition/-/content-disposition-0.5.8.tgz", + "integrity": "sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==" + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cookies": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.9.0.tgz", + "integrity": "sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==", + "dependencies": { + "@types/connect": "*", + "@types/express": "*", + "@types/keygrip": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", + "integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==" + }, + "node_modules/@types/earcut": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/earcut/-/earcut-2.1.4.tgz", + "integrity": "sha512-qp3m9PPz4gULB9MhjGID7wpo3gJ4bTGXm7ltNDsmOvsPduTeHp8wSW9YckBj3mljeOh4F0m2z/0JKAALRKbmLQ==" + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.3.tgz", + "integrity": "sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/formidable": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-2.0.6.tgz", + "integrity": "sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-assert": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.5.tgz", + "integrity": "sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "node_modules/@types/keygrip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", + "integrity": "sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==" + }, + "node_modules/@types/koa": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.15.0.tgz", + "integrity": "sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==", + "dependencies": { + "@types/accepts": "*", + "@types/content-disposition": "*", + "@types/cookies": "*", + "@types/http-assert": "*", + "@types/http-errors": "*", + "@types/keygrip": "*", + "@types/koa-compose": "*", + "@types/node": "*" + } + }, + "node_modules/@types/koa__router": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/@types/koa__router/-/koa__router-8.0.11.tgz", + "integrity": "sha512-WXgKWpBsbS14kzmzD9LeFapOIa678h7zvUHxDwXwSx4ETKXhXLVUAToX6jZ/U7EihM7qwyD9W/BZvB0MRu7MTQ==", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/koa-compose": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.8.tgz", + "integrity": "sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==", + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/node": { + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.42", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.42.tgz", + "integrity": "sha512-uvJtt4OD1Vjdebrrz3kNLgpOicYbikwnM8WPG6YD2lkCOHDtPdEtCINJFIFtbOCtPfA8SreR/vKyUNbAt92IwQ==" + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/boardgame.io": { + "version": "0.50.2", + "resolved": "https://registry.npmjs.org/boardgame.io/-/boardgame.io-0.50.2.tgz", + "integrity": "sha512-oEcgtiDidTk2Mvweg3issGxOxMwOTTPlkHMoLexQ0LTQJXl60P3vA026SLYy313nrHM6s/vK7qE7TavMFxyC0g==", + "funding": [ + "https://github.com/boardgameio/boardgame.io?sponsor=1", + { + "type": "opencollective", + "url": "https://opencollective.com/boardgameio" + } + ], + "dependencies": { + "@koa/cors": "^3.1.0", + "@koa/router": "^10.1.1", + "@types/koa": "^2.13.4", + "@types/koa__router": "^8.0.8", + "flatted": "^3.2.1", + "immer": "^9.0.5", + "koa": "^2.13.3", + "koa-body": "^5.0.0", + "koa-socket-2": "^2.0.0", + "lodash.isplainobject": "^4.0.6", + "nanoid": "^3.1.30", + "p-queue": "^6.6.2", + "prop-types": "^15.5.10", + "react-cookies": "^0.1.0", + "redux": "^4.1.0", + "rfc6902": "^5.0.0", + "setimmediate": "^1.0.5", + "socket.io": "^4.5.0", + "socket.io-client": "^4.1.3", + "svelte": "^3.41.0", + "svelte-json-tree-auto": "^0.1.0", + "ts-toolbelt": "^6.3.6" + }, + "engines": { + "node": ">=10.0", + "npm": ">=6.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-5.2.0.tgz", + "integrity": "sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==", + "dependencies": { + "inflation": "^2.0.0", + "qs": "^6.4.0", + "raw-body": "^2.2.0", + "type-is": "^1.6.14" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookies": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", + "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookies/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + }, + "node_modules/formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "engines": { + "node": ">=8" + } + }, + "node_modules/honeycomb-grid": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/honeycomb-grid/-/honeycomb-grid-4.1.5.tgz", + "integrity": "sha512-VrnQwu5dHuzqK3wFhLD9EURmLSyEWb0teiHhDJq6WdK0MrFsEtKNYT1HLzXOLe2X1acU8Y9hhK7wWfIiGK1G5w==", + "engines": { + "node": ">=16" + } + }, + "node_modules/http-assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", + "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.8.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/inflation": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.1.0.tgz", + "integrity": "sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ismobilejs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ismobilejs/-/ismobilejs-1.1.1.tgz", + "integrity": "sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz", + "integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==", + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.9.0", + "debug": "^4.3.2", + "delegates": "^1.0.0", + "depd": "^2.0.0", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^2.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-body": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-body/-/koa-body-5.0.0.tgz", + "integrity": "sha512-nHwEODrQGiyKBILCWO8QSS40C87cKr2cp3y/Cw8u9Z8w5t0CdSkGm3+y9WK5BIAlPpo9tTw5RtSbxpVyG79vmw==", + "dependencies": { + "@types/formidable": "^2.0.4", + "co-body": "^5.1.1", + "formidable": "^2.0.1" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + }, + "node_modules/koa-convert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", + "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa-socket-2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/koa-socket-2/-/koa-socket-2-2.0.0.tgz", + "integrity": "sha512-YSy9nYtd4/CR9bChAeL9/3lb17ietphRFgNTAwOkhrCOHFZuOPv+dMORK3sxSJygiIqH5ugJyc2/4Ek1Hc5eVA==", + "dependencies": { + "koa-compose": "^4.1.0", + "socket.io": "^3.0.2" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/koa-socket-2/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa-socket-2/node_modules/engine.io": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.2.tgz", + "integrity": "sha512-t5z6zjXuVLhXDMiFJPYsPOWEER8B0tIsD3ETgw19S1yg9zryvUfY3Vhtk3Gf4sihw/bQGIqQ//gjvVlu+Ca0bQ==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.0", + "ws": "~7.4.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/koa-socket-2/node_modules/engine.io-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.3.tgz", + "integrity": "sha512-xEAAY0msNnESNPc00e19y5heTPX4y/TJ36gr8t1voOaNmTojP9b3oK3BbJLFufW2XFPQaaijpFewm2g2Um3uqA==", + "dependencies": { + "base64-arraybuffer": "0.1.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/koa-socket-2/node_modules/socket.io": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.2.tgz", + "integrity": "sha512-JubKZnTQ4Z8G4IZWtaAZSiRP3I/inpy8c/Bsx2jrwGrTbKeVU5xd6qkKMHpChYeM3dWZSO0QACiGK+obhBNwYw==", + "dependencies": { + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.1", + "engine.io": "~4.1.0", + "socket.io-adapter": "~2.1.0", + "socket.io-parser": "~4.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/koa-socket-2/node_modules/socket.io-adapter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", + "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==" + }, + "node_modules/koa-socket-2/node_modules/socket.io-parser": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", + "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", + "dependencies": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/koa-socket-2/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/koa/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/pixi.js": { + "version": "8.1.6", + "resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.1.6.tgz", + "integrity": "sha512-FZT/dLZ9Tdw8eN6odgunSQHORsgHcvLlfOWm7cpR4ZxVGPRqFgDQ4hXpwVxeY9UK4PZtlJ4U910H1QZ4XNLJxg==", + "dependencies": { + "@pixi/colord": "^2.9.6", + "@types/css-font-loading-module": "^0.0.12", + "@types/earcut": "^2.1.4", + "@webgpu/types": "^0.1.40", + "@xmldom/xmldom": "^0.8.10", + "earcut": "^2.2.4", + "eventemitter3": "^5.0.1", + "ismobilejs": "^1.1.1", + "parse-svg-path": "^0.1.2" + } + }, + "node_modules/pixi.js/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/qs": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", + "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-cookies": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/react-cookies/-/react-cookies-0.1.1.tgz", + "integrity": "sha512-PP75kJ4vtoHuuTdq0TAD3RmlAv7vuDQh9fkC4oDlhntgs9vX1DmREomO0Y1mcQKR9nMZ6/zxoflaMJ3MAmF5KQ==", + "dependencies": { + "cookie": "^0.3.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/rfc6902": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/rfc6902/-/rfc6902-5.1.1.tgz", + "integrity": "sha512-8rKImbe8GTXJ7cl9v+sF3U0WQ9aaSBn4fEcGP1VJakWN335ufj75ctvWXEhrzecGRxXpY6pEFbcdWzesjV7bFg==" + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/svelte": { + "version": "3.59.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz", + "integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/svelte-json-tree-auto": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/svelte-json-tree-auto/-/svelte-json-tree-auto-0.1.0.tgz", + "integrity": "sha512-XDUBTV/BNStd/9WJDndzxQ9KqhoNoZBxkOo1H1d29qKW+YxG6eRrt2Yp0Cv/6KLKDtFLhJ9huJpqpOSe4yMG0Q==" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/ts-toolbelt": { + "version": "6.15.5", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", + "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==" + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.2.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.13.tgz", + "integrity": "sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ylru": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", + "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", + "engines": { + "node": ">= 4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..68786fd --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "battle-simulator", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "^5.2.2", + "vite": "^5.2.0" + }, + "dependencies": { + "boardgame.io": "^0.50.2", + "honeycomb-grid": "^4.1.5", + "pixi.js": "^8.1.6" + } +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.ts b/src/App.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/Game.ts b/src/Game.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..75abdef --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} From aa6becfb8368d2d8722ec6d3f2149f9251e892d3 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 16 Jun 2024 00:49:45 +0700 Subject: [PATCH 11/44] Init App & Game code --- .editorconfig | 9 +++++++++ game-config.json | 6 ++++++ src/App.ts | 20 ++++++++++++++++++++ src/Game.ts | 14 ++++++++++++++ src/model/MapTile.ts | 17 +++++++++++++++++ src/temp/board-mocker.ts | 10 ++++++++++ src/types/model/BaseMapTile.d.ts | 6 ++++++ 7 files changed, 82 insertions(+) create mode 100644 .editorconfig create mode 100644 game-config.json create mode 100644 src/model/MapTile.ts create mode 100644 src/temp/board-mocker.ts create mode 100644 src/types/model/BaseMapTile.d.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6f87ae0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 3 +trim_trailing_whitespace = true \ No newline at end of file diff --git a/game-config.json b/game-config.json new file mode 100644 index 0000000..f883e30 --- /dev/null +++ b/game-config.json @@ -0,0 +1,6 @@ +{ + "maxFPS": 30, + "hex": { + "dimensions": 30 + } +} diff --git a/src/App.ts b/src/App.ts index e69de29..8eb577c 100644 --- a/src/App.ts +++ b/src/App.ts @@ -0,0 +1,20 @@ +import { Client } from "boardgame.io/client"; +import { BattleSimulator } from "./Game"; +import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; + +class BattleSimulatorClient { + client: _ClientImpl; + rootElement: HTMLElement; + + constructor(rootElement: HTMLElement) { + this.client = Client({ game: BattleSimulator }); + this.client.start(); + this.rootElement = rootElement; + this.createBoard(); + } + + createBoard() {} +} + +const appElement = document.getElementById("app")!; +const app = new BattleSimulatorClient(appElement); diff --git a/src/Game.ts b/src/Game.ts index e69de29..1694adb 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -0,0 +1,14 @@ +import { Game } from "boardgame.io"; +import { mockBoard } from "./temp/board-mocker"; +import { BaseMapTile } from "./types/model/BaseMapTile"; + +export interface GameState { + cells: BaseMapTile[][]; +} + +export const BattleSimulator: Game = { + setup: () => ({ + cells: mockBoard(), + }), + moves: {}, +}; diff --git a/src/model/MapTile.ts b/src/model/MapTile.ts new file mode 100644 index 0000000..36898bb --- /dev/null +++ b/src/model/MapTile.ts @@ -0,0 +1,17 @@ +import { HexCoordinates, defineHex } from "honeycomb-grid"; +import gameConfig from "../../game-config.json"; +import * as PIXI from "pixi.js"; + +class MapTile extends defineHex(gameConfig.hex) { + cellNumber!: Number; + graphic!: PIXI.Graphics; + + static create(coordinates: HexCoordinates, cellNumber: Number) { + const hex = new MapTile(coordinates); + hex.cellNumber = cellNumber; + hex.graphic = new PIXI.Graphics(); + return hex; + } +} + +export default MapTile; diff --git a/src/temp/board-mocker.ts b/src/temp/board-mocker.ts new file mode 100644 index 0000000..353cdf7 --- /dev/null +++ b/src/temp/board-mocker.ts @@ -0,0 +1,10 @@ +import { BaseMapTile } from "../types/model/BaseMapTile"; + +export const mockBoard = (): BaseMapTile[][] => { + return Array.from(Array(10), (_, i) => + Array.from(Array(10), (_, j) => ({ + coordinates: { row: i, col: j }, + cellNumber: Math.random() * 3 < 1 ? 1 : 0, + })) + ); +}; diff --git a/src/types/model/BaseMapTile.d.ts b/src/types/model/BaseMapTile.d.ts new file mode 100644 index 0000000..39c024c --- /dev/null +++ b/src/types/model/BaseMapTile.d.ts @@ -0,0 +1,6 @@ +import { HexCoordinates } from "honeycomb-grid"; + +export declare type BaseMapTile = { + cellNumber: Number; + coordinates: HexCoordinates; +}; From 4dc3157e618988eb2c582419b4d1906bdd77592b Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 16 Jun 2024 01:41:02 +0700 Subject: [PATCH 12/44] Add simple grid rendering --- colors.json | 8 +++++++ game-config.json | 6 ----- gameConfig.ts | 7 ++++++ index.html | 1 - src/App.ts | 38 +++++++++++++++++++++++++------- src/model/MapTile.ts | 32 ++++++++++++++++++++++++--- src/types/model/BaseMapTile.d.ts | 2 +- 7 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 colors.json delete mode 100644 game-config.json create mode 100644 gameConfig.ts diff --git a/colors.json b/colors.json new file mode 100644 index 0000000..cceef3c --- /dev/null +++ b/colors.json @@ -0,0 +1,8 @@ +{ + "grid": + { + "background": "#000", + "primary": "#0d6efd", + "highlight": "#44ffb7" + } +} \ No newline at end of file diff --git a/game-config.json b/game-config.json deleted file mode 100644 index f883e30..0000000 --- a/game-config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "maxFPS": 30, - "hex": { - "dimensions": 30 - } -} diff --git a/gameConfig.ts b/gameConfig.ts new file mode 100644 index 0000000..020cf02 --- /dev/null +++ b/gameConfig.ts @@ -0,0 +1,7 @@ +export default { + maxFPS: 30, + hex: { + dimensions: 30, + origin: "topLeft", + }, +} as const; diff --git a/index.html b/index.html index 80538cf..0030329 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,6 @@ Battle Simulator -
diff --git a/src/App.ts b/src/App.ts index 8eb577c..6999b3c 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,20 +1,42 @@ import { Client } from "boardgame.io/client"; -import { BattleSimulator } from "./Game"; +import { BattleSimulator, GameState } from "./Game"; import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; +import MapTile from "./model/MapTile"; +import { Grid } from "honeycomb-grid"; +import * as PIXI from "pixi.js"; class BattleSimulatorClient { - client: _ClientImpl; - rootElement: HTMLElement; + client: _ClientImpl; + pixiApp: PIXI.Application; - constructor(rootElement: HTMLElement) { + constructor(pixiApp: PIXI.Application) { this.client = Client({ game: BattleSimulator }); this.client.start(); - this.rootElement = rootElement; + this.pixiApp = pixiApp; this.createBoard(); } - createBoard() {} + createBoard() { + const initialState = this.client.getInitialState(); + const cells = initialState.G.cells; + + // Temporary code to draw grid here + const grid = Grid.fromIterable( + cells.flatMap((row) => + row.map((cell) => MapTile.create(cell.coordinates, cell.cellNumber)) + ) + ); + + grid.forEach((tile) => pixiApp.stage.addChild(tile.render())); + } } -const appElement = document.getElementById("app")!; -const app = new BattleSimulatorClient(appElement); +const pixiApp = new PIXI.Application(); +await pixiApp.init({ backgroundAlpha: 0 }); + +document.body.appendChild(pixiApp.canvas); + +// Debug Only +globalThis.__PIXI_APP__ = pixiApp; + +const app = new BattleSimulatorClient(pixiApp); diff --git a/src/model/MapTile.ts b/src/model/MapTile.ts index 36898bb..52dd95b 100644 --- a/src/model/MapTile.ts +++ b/src/model/MapTile.ts @@ -1,17 +1,43 @@ import { HexCoordinates, defineHex } from "honeycomb-grid"; -import gameConfig from "../../game-config.json"; +import gameConfig from "../../gameConfig"; +import { grid as gridColor } from "../../colors.json"; import * as PIXI from "pixi.js"; class MapTile extends defineHex(gameConfig.hex) { - cellNumber!: Number; + cellNumber!: number; graphic!: PIXI.Graphics; - static create(coordinates: HexCoordinates, cellNumber: Number) { + static create(coordinates: HexCoordinates, cellNumber: number) { const hex = new MapTile(coordinates); hex.cellNumber = cellNumber; hex.graphic = new PIXI.Graphics(); return hex; } + + render() { + this.graphic.clear(); + // this.graphic.setStrokeStyle(); + + let cellColor = gridColor.background; + if (this.cellNumber > 0) { + cellColor = gridColor.primary; + } + + this.graphic + .poly(this.corners) + .fill({ color: cellColor }) + .stroke({ width: 1, color: "#999999" }); + + this.graphic.removeChildren(); + if (this.cellNumber > 0) { + const text = new PIXI.Text({ text: this.cellNumber }); + text.x = this.x - text.width / 2; + text.y = this.y - text.height / 2; + this.graphic.addChild(text); + } + + return this.graphic; + } } export default MapTile; diff --git a/src/types/model/BaseMapTile.d.ts b/src/types/model/BaseMapTile.d.ts index 39c024c..6a82396 100644 --- a/src/types/model/BaseMapTile.d.ts +++ b/src/types/model/BaseMapTile.d.ts @@ -1,6 +1,6 @@ import { HexCoordinates } from "honeycomb-grid"; export declare type BaseMapTile = { - cellNumber: Number; + cellNumber: number; coordinates: HexCoordinates; }; From b796067dfd0a3f1c0701b9f6bb6dd6347ef07c88 Mon Sep 17 00:00:00 2001 From: extremebip Date: Tue, 18 Jun 2024 01:43:31 +0700 Subject: [PATCH 13/44] Change board orientation config --- gameConfig.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gameConfig.ts b/gameConfig.ts index 020cf02..751cd05 100644 --- a/gameConfig.ts +++ b/gameConfig.ts @@ -1,7 +1,9 @@ +import { Orientation } from "honeycomb-grid"; + export default { - maxFPS: 30, hex: { dimensions: 30, origin: "topLeft", + orientation: Orientation.FLAT, }, } as const; From 47d6d8c959d53692a1ad71a8337b85c16938e64c Mon Sep 17 00:00:00 2001 From: extremebip Date: Tue, 18 Jun 2024 01:45:21 +0700 Subject: [PATCH 14/44] Add movable character --- colors.json | 22 ++++++---- src/App.ts | 59 +++++++++++++++++++++++++- src/Game.ts | 24 ++++++++--- src/model/Base/PlayerHex.ts | 7 ++++ src/model/Base/TileHex.ts | 6 +++ src/model/MapTile.ts | 7 ++-- src/model/Player.ts | 81 ++++++++++++++++++++++++++++++++++++ src/temp/board-mocker.ts | 35 ++++++++++++++++ src/types/config/colors.d.ts | 4 ++ 9 files changed, 227 insertions(+), 18 deletions(-) create mode 100644 src/model/Base/PlayerHex.ts create mode 100644 src/model/Base/TileHex.ts create mode 100644 src/model/Player.ts create mode 100644 src/types/config/colors.d.ts diff --git a/colors.json b/colors.json index cceef3c..041b676 100644 --- a/colors.json +++ b/colors.json @@ -1,8 +1,16 @@ { - "grid": - { - "background": "#000", - "primary": "#0d6efd", - "highlight": "#44ffb7" - } -} \ No newline at end of file + "grid": { + "background": "#000", + "primary": "#0d6efd" + }, + "player": { + "red": { + "primary": "#c40000", + "active": "#ff6868" + }, + "blue": { + "primary": "#004aff", + "active": "#71afff" + } + } +} diff --git a/src/App.ts b/src/App.ts index 6999b3c..178521f 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,19 +1,29 @@ import { Client } from "boardgame.io/client"; import { BattleSimulator, GameState } from "./Game"; -import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; +import { + ClientState, + _ClientImpl, +} from "boardgame.io/dist/types/src/client/client"; import MapTile from "./model/MapTile"; import { Grid } from "honeycomb-grid"; import * as PIXI from "pixi.js"; +import Player from "./model/Player"; class BattleSimulatorClient { client: _ClientImpl; pixiApp: PIXI.Application; + grid: Grid; + player: Player; constructor(pixiApp: PIXI.Application) { this.client = Client({ game: BattleSimulator }); this.client.start(); this.pixiApp = pixiApp; - this.createBoard(); + this.grid = this.createBoard(); + this.player = this.createPlayer(); + + this.attachListeners(); + this.client.subscribe((state) => this.update(state)); } createBoard() { @@ -28,6 +38,51 @@ class BattleSimulatorClient { ); grid.forEach((tile) => pixiApp.stage.addChild(tile.render())); + return grid; + } + + createPlayer() { + const initialState = this.client.getInitialState(); + const playerPos = initialState.G.playerPos; + + // const player = new Player(playerPos, "blue"); + const player = Player.create(this.grid.getHex(playerPos)!, "blue"); + pixiApp.stage.addChild(player.render()); + return player; + } + + attachListeners() { + document.addEventListener("click", ({ offsetX, offsetY }) => { + const tile = this.grid.pointToHex( + { x: offsetX, y: offsetY }, + { allowOutside: false } + ); + + if (tile !== undefined) { + this.client.moves.movePlayer({ q: tile.q, r: tile.r }); + } + }); + } + + update(state: ClientState) { + if (state === null) return; + this.player.destroy(); + + const newPlayerPosition = state.G.playerPos; + const player = Player.create( + this.grid.getHex(newPlayerPosition)!, + "blue" + ); + pixiApp.stage.addChild(player.render()); + this.player = player; + // console.log("Before: ", { q: this.player.q, r: this.player.r }); + // console.log("After: ", newPlayerPosition); + // const cubePosition = toCube(TileHex.settings, newPlayerPosition); + // const tilePosition = this.grid.getHex(newPlayerPosition); + // this.player = this.player.translate({ + // q: cubePosition.q - this.player.q, + // r: cubePosition.r - this.player.r, + // }); } } diff --git a/src/Game.ts b/src/Game.ts index 1694adb..7773f89 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1,14 +1,28 @@ import { Game } from "boardgame.io"; -import { mockBoard } from "./temp/board-mocker"; +import { mockBoard, mockPosition } from "./temp/board-mocker"; import { BaseMapTile } from "./types/model/BaseMapTile"; +import { HexCoordinates, distance } from "honeycomb-grid"; +import { INVALID_MOVE } from "boardgame.io/core"; +import BaseHex from "./model/Base/TileHex"; export interface GameState { cells: BaseMapTile[][]; + playerPos: HexCoordinates; } export const BattleSimulator: Game = { - setup: () => ({ - cells: mockBoard(), - }), - moves: {}, + setup: () => { + const cells = mockBoard(); + return { + cells: cells, + playerPos: mockPosition(cells), + }; + }, + moves: { + movePlayer: ({ G }, target: HexCoordinates) => { + if (distance(BaseHex.settings, G.playerPos, target) > 1) + return INVALID_MOVE; + G.playerPos = target; + }, + }, }; diff --git a/src/model/Base/PlayerHex.ts b/src/model/Base/PlayerHex.ts new file mode 100644 index 0000000..5a698ca --- /dev/null +++ b/src/model/Base/PlayerHex.ts @@ -0,0 +1,7 @@ +// Temporary class because player is now rendered using hex +import { defineHex } from "honeycomb-grid"; +import gameConfig from "../../../gameConfig"; + +const PlayerHex = defineHex({ ...gameConfig.hex, dimensions: 30 }); + +export default PlayerHex; diff --git a/src/model/Base/TileHex.ts b/src/model/Base/TileHex.ts new file mode 100644 index 0000000..85e5189 --- /dev/null +++ b/src/model/Base/TileHex.ts @@ -0,0 +1,6 @@ +import { defineHex } from "honeycomb-grid"; +import gameConfig from "../../../gameConfig"; + +const TileHex = defineHex(gameConfig.hex); + +export default TileHex; diff --git a/src/model/MapTile.ts b/src/model/MapTile.ts index 52dd95b..feafbd1 100644 --- a/src/model/MapTile.ts +++ b/src/model/MapTile.ts @@ -1,9 +1,9 @@ -import { HexCoordinates, defineHex } from "honeycomb-grid"; -import gameConfig from "../../gameConfig"; +import { HexCoordinates } from "honeycomb-grid"; import { grid as gridColor } from "../../colors.json"; import * as PIXI from "pixi.js"; +import BaseHex from "./Base/TileHex"; -class MapTile extends defineHex(gameConfig.hex) { +class MapTile extends BaseHex { cellNumber!: number; graphic!: PIXI.Graphics; @@ -16,7 +16,6 @@ class MapTile extends defineHex(gameConfig.hex) { render() { this.graphic.clear(); - // this.graphic.setStrokeStyle(); let cellColor = gridColor.background; if (this.cellNumber > 0) { diff --git a/src/model/Player.ts b/src/model/Player.ts new file mode 100644 index 0000000..49e1ca9 --- /dev/null +++ b/src/model/Player.ts @@ -0,0 +1,81 @@ +import PlayerHex from "./Base/PlayerHex"; +import MapTile from "./MapTile"; +import * as PIXI from "pixi.js"; +import { player as playerColor } from "../../colors.json"; +import { PlayerTeamColor } from "../types/config/colors"; +import { multiplyMatrix } from "../temp/board-mocker"; + +// class Player { +class Player extends PlayerHex { + power!: number; + positionTile!: MapTile; + colorKey!: PlayerTeamColor; + graphic!: PIXI.Graphics; + + // constructor(initialTile: MapTile, colorKey: string) { + // this.power = 0; + // this.positionTile = initialTile; + // this.colorKey = colorKey; + // } + + // Temporary Create function for Hex Player + static create(initialTile: MapTile, colorKey: PlayerTeamColor) { + const hex = new Player({ q: initialTile.q, r: initialTile.r }); + hex.power = 0; + hex.positionTile = initialTile; + hex.colorKey = colorKey; + hex.graphic = new PIXI.Graphics(); + return hex; + } + + render() { + this.graphic.clear(); + + const color = playerColor[this.colorKey].active; + + // TODO: Turns original hex corner into shrinked corner + // => Linear Transformation - Scaling + // https://gamemath.com/book/matrixtransforms.html + + // Get Original corner points then + // Standardize each corner point by substracting it from the hex center + const originalCorners = this.corners.map((p) => [ + p.x - this.x, + p.y - this.y, + ]); + + // Scale matrix + const scale = 4 / 5; + const sm = [ + [scale, 0], + [0, scale], + ]; + + const newCorners = multiplyMatrix(originalCorners, sm).map((p) => ({ + x: p[0] + this.x, + y: p[1] + this.y, + })); + + this.graphic + .poly(newCorners) + .fill({ color }) + .stroke({ width: 1, color: "#999999" }); + + this.graphic.removeChildren(); + + const text = new PIXI.Text({ text: this.power }); + text.x = this.x - text.width / 2; + text.y = this.y - text.height / 2; + this.graphic.addChild(text); + + return this.graphic; + } + + destroy() { + this.graphic.removeChildren(); + this.graphic.clear(); + this.graphic.destroy(); + } +} + +export default Player; diff --git a/src/temp/board-mocker.ts b/src/temp/board-mocker.ts index 353cdf7..b43ac04 100644 --- a/src/temp/board-mocker.ts +++ b/src/temp/board-mocker.ts @@ -1,5 +1,12 @@ +import { HexCoordinates } from "honeycomb-grid"; import { BaseMapTile } from "../types/model/BaseMapTile"; +function getRandomInt(min: number, max: number) { + const minCeiled = Math.ceil(min); + const maxFloored = Math.floor(max); + return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); +} + export const mockBoard = (): BaseMapTile[][] => { return Array.from(Array(10), (_, i) => Array.from(Array(10), (_, j) => ({ @@ -8,3 +15,31 @@ export const mockBoard = (): BaseMapTile[][] => { })) ); }; + +export const mockPosition = (board: BaseMapTile[][]): HexCoordinates => { + const availablePositions = board.map((row) => + row.map((c, i) => (c.cellNumber === 0 ? i : -1)).filter((c) => c > -1) + ); + const rowIdx = getRandomInt(0, availablePositions.length); + const colIdx = getRandomInt(0, availablePositions[rowIdx].length); + return { row: rowIdx, col: availablePositions[rowIdx][colIdx] }; +}; + +export const multiplyMatrix = (mat1: number[][], mat2: number[][]) => { + const n1 = mat1.length, + m1 = mat1[0].length, + n2 = mat2.length, + m2 = mat2[0].length; + if (m1 !== n2) throw "Invalid matrix size"; + + const res = Array.from(Array(n1), () => Array(m2).fill(0)); + for (let i = 0; i < n1; i++) { + for (let j = 0; j < m2; j++) { + let temp = 0; + for (let k = 0; k < m1; k++) temp += mat1[i][k] * mat2[k][j]; + res[i][j] = temp; + } + } + + return res; +}; diff --git a/src/types/config/colors.d.ts b/src/types/config/colors.d.ts new file mode 100644 index 0000000..685a6e3 --- /dev/null +++ b/src/types/config/colors.d.ts @@ -0,0 +1,4 @@ +import * as Config from "../../../colors.json"; + +export type Player = typeof Config.player; +export type PlayerTeamColor = keyof typeof Config.player; From b84dc1f5e52959fe8e97c01a07cda9fef6a44e7f Mon Sep 17 00:00:00 2001 From: extremebip Date: Wed, 19 Jun 2024 10:02:12 +0700 Subject: [PATCH 15/44] Add roadmap into notes --- notes.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 notes.md diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..68b4a23 --- /dev/null +++ b/notes.md @@ -0,0 +1,8 @@ +Playground Roadmap + +1. [x] Init project with boardgame.io & PIXI.js. +2. [x] Create the board with numbers. +3. [ ] Add player and movement. +4. [ ] Add multiplayer and win condition. +5. [ ] Add multiple units for each player. +6. [ ] Push playground into main. From ba01e30d4527f55c668e9cdabd99a735841a8bb2 Mon Sep 17 00:00:00 2001 From: extremebip Date: Wed, 19 Jun 2024 16:21:57 +0700 Subject: [PATCH 16/44] Simplify player hex scaling calculation --- src/model/Player.ts | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/model/Player.ts b/src/model/Player.ts index 49e1ca9..7e944d9 100644 --- a/src/model/Player.ts +++ b/src/model/Player.ts @@ -3,7 +3,6 @@ import MapTile from "./MapTile"; import * as PIXI from "pixi.js"; import { player as playerColor } from "../../colors.json"; import { PlayerTeamColor } from "../types/config/colors"; -import { multiplyMatrix } from "../temp/board-mocker"; // class Player { class Player extends PlayerHex { @@ -33,27 +32,13 @@ class Player extends PlayerHex { const color = playerColor[this.colorKey].active; - // TODO: Turns original hex corner into shrinked corner + // Turns original hex corner into shrinked corner // => Linear Transformation - Scaling // https://gamemath.com/book/matrixtransforms.html - - // Get Original corner points then - // Standardize each corner point by substracting it from the hex center - const originalCorners = this.corners.map((p) => [ - p.x - this.x, - p.y - this.y, - ]); - - // Scale matrix const scale = 4 / 5; - const sm = [ - [scale, 0], - [0, scale], - ]; - - const newCorners = multiplyMatrix(originalCorners, sm).map((p) => ({ - x: p[0] + this.x, - y: p[1] + this.y, + const newCorners = this.corners.map((p) => ({ + x: (p.x - this.x) * scale + this.x, + y: (p.y - this.y) * scale + this.y, })); this.graphic From c60500ab21c3a9c8da97e1f48276bb4287f74f26 Mon Sep 17 00:00:00 2001 From: extremebip Date: Wed, 19 Jun 2024 16:32:25 +0700 Subject: [PATCH 17/44] Refactor board tiles into 1D & create player type --- src/App.ts | 8 +++----- src/Game.ts | 14 +++++++++----- src/temp/board-mocker.ts | 15 ++++++--------- src/types/model/BasePlayer.d.ts | 6 ++++++ 4 files changed, 24 insertions(+), 19 deletions(-) create mode 100644 src/types/model/BasePlayer.d.ts diff --git a/src/App.ts b/src/App.ts index 178521f..818b00a 100644 --- a/src/App.ts +++ b/src/App.ts @@ -32,9 +32,7 @@ class BattleSimulatorClient { // Temporary code to draw grid here const grid = Grid.fromIterable( - cells.flatMap((row) => - row.map((cell) => MapTile.create(cell.coordinates, cell.cellNumber)) - ) + cells.map((cell) => MapTile.create(cell.coordinates, cell.cellNumber)) ); grid.forEach((tile) => pixiApp.stage.addChild(tile.render())); @@ -43,7 +41,7 @@ class BattleSimulatorClient { createPlayer() { const initialState = this.client.getInitialState(); - const playerPos = initialState.G.playerPos; + const playerPos = initialState.G.player.position; // const player = new Player(playerPos, "blue"); const player = Player.create(this.grid.getHex(playerPos)!, "blue"); @@ -68,7 +66,7 @@ class BattleSimulatorClient { if (state === null) return; this.player.destroy(); - const newPlayerPosition = state.G.playerPos; + const newPlayerPosition = state.G.player.position; const player = Player.create( this.grid.getHex(newPlayerPosition)!, "blue" diff --git a/src/Game.ts b/src/Game.ts index 7773f89..fc09597 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -4,10 +4,11 @@ import { BaseMapTile } from "./types/model/BaseMapTile"; import { HexCoordinates, distance } from "honeycomb-grid"; import { INVALID_MOVE } from "boardgame.io/core"; import BaseHex from "./model/Base/TileHex"; +import { BasePlayer } from "./types/model/BasePlayer"; export interface GameState { - cells: BaseMapTile[][]; - playerPos: HexCoordinates; + cells: BaseMapTile[]; + player: BasePlayer; } export const BattleSimulator: Game = { @@ -15,14 +16,17 @@ export const BattleSimulator: Game = { const cells = mockBoard(); return { cells: cells, - playerPos: mockPosition(cells), + player: { + position: mockPosition(cells), + power: 0, + }, }; }, moves: { movePlayer: ({ G }, target: HexCoordinates) => { - if (distance(BaseHex.settings, G.playerPos, target) > 1) + if (distance(BaseHex.settings, G.player.position, target) > 1) return INVALID_MOVE; - G.playerPos = target; + G.player.position = target; }, }, }; diff --git a/src/temp/board-mocker.ts b/src/temp/board-mocker.ts index b43ac04..d166878 100644 --- a/src/temp/board-mocker.ts +++ b/src/temp/board-mocker.ts @@ -7,22 +7,19 @@ function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); } -export const mockBoard = (): BaseMapTile[][] => { +export const mockBoard = (): BaseMapTile[] => { return Array.from(Array(10), (_, i) => Array.from(Array(10), (_, j) => ({ coordinates: { row: i, col: j }, cellNumber: Math.random() * 3 < 1 ? 1 : 0, })) - ); + ).flat(); }; -export const mockPosition = (board: BaseMapTile[][]): HexCoordinates => { - const availablePositions = board.map((row) => - row.map((c, i) => (c.cellNumber === 0 ? i : -1)).filter((c) => c > -1) - ); - const rowIdx = getRandomInt(0, availablePositions.length); - const colIdx = getRandomInt(0, availablePositions[rowIdx].length); - return { row: rowIdx, col: availablePositions[rowIdx][colIdx] }; +export const mockPosition = (board: BaseMapTile[]): HexCoordinates => { + const availablePositions = board.filter((tile) => tile.cellNumber === 0); + const randomTileIdx = getRandomInt(0, availablePositions.length); + return availablePositions[randomTileIdx].coordinates; }; export const multiplyMatrix = (mat1: number[][], mat2: number[][]) => { diff --git a/src/types/model/BasePlayer.d.ts b/src/types/model/BasePlayer.d.ts new file mode 100644 index 0000000..a2d3d43 --- /dev/null +++ b/src/types/model/BasePlayer.d.ts @@ -0,0 +1,6 @@ +import { HexCoordinates } from "honeycomb-grid"; + +export declare type BasePlayer = { + position: HexCoordinates; + power: number; +}; From 7c5fb9aebadbf7fac4e2ff7a9d154a321f714e55 Mon Sep 17 00:00:00 2001 From: extremebip Date: Wed, 19 Jun 2024 16:54:46 +0700 Subject: [PATCH 18/44] Add number absorption with player movement --- src/App.ts | 11 ++++++----- src/Game.ts | 16 +++++++++++++++- src/model/Player.ts | 8 ++++++-- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/App.ts b/src/App.ts index 818b00a..7049c53 100644 --- a/src/App.ts +++ b/src/App.ts @@ -44,7 +44,7 @@ class BattleSimulatorClient { const playerPos = initialState.G.player.position; // const player = new Player(playerPos, "blue"); - const player = Player.create(this.grid.getHex(playerPos)!, "blue"); + const player = Player.create(0, this.grid.getHex(playerPos)!, "blue"); pixiApp.stage.addChild(player.render()); return player; } @@ -67,12 +67,13 @@ class BattleSimulatorClient { this.player.destroy(); const newPlayerPosition = state.G.player.position; - const player = Player.create( - this.grid.getHex(newPlayerPosition)!, - "blue" - ); + const tile = this.grid.getHex(newPlayerPosition)!; + const player = Player.create(state.G.player.power, tile, "blue"); pixiApp.stage.addChild(player.render()); this.player = player; + + tile.cellNumber = 0; + tile.render(); // console.log("Before: ", { q: this.player.q, r: this.player.r }); // console.log("After: ", newPlayerPosition); // const cubePosition = toCube(TileHex.settings, newPlayerPosition); diff --git a/src/Game.ts b/src/Game.ts index fc09597..3b9b5a9 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1,7 +1,7 @@ import { Game } from "boardgame.io"; import { mockBoard, mockPosition } from "./temp/board-mocker"; import { BaseMapTile } from "./types/model/BaseMapTile"; -import { HexCoordinates, distance } from "honeycomb-grid"; +import { HexCoordinates, distance, toCube } from "honeycomb-grid"; import { INVALID_MOVE } from "boardgame.io/core"; import BaseHex from "./model/Base/TileHex"; import { BasePlayer } from "./types/model/BasePlayer"; @@ -27,6 +27,20 @@ export const BattleSimulator: Game = { if (distance(BaseHex.settings, G.player.position, target) > 1) return INVALID_MOVE; G.player.position = target; + + const currCoordinates = toCube(BaseHex.settings, target); + const targetCell = G.cells.filter((cell) => { + const cellCoordinates = toCube(BaseHex.settings, cell.coordinates); + return ( + currCoordinates.q === cellCoordinates.q && + currCoordinates.r === cellCoordinates.r + ); + })[0]; + + if (targetCell.cellNumber > 0) { + G.player.power += targetCell.cellNumber; + targetCell.cellNumber = 0; + } }, }, }; diff --git a/src/model/Player.ts b/src/model/Player.ts index 7e944d9..2dafa4d 100644 --- a/src/model/Player.ts +++ b/src/model/Player.ts @@ -18,9 +18,13 @@ class Player extends PlayerHex { // } // Temporary Create function for Hex Player - static create(initialTile: MapTile, colorKey: PlayerTeamColor) { + static create( + power: number, + initialTile: MapTile, + colorKey: PlayerTeamColor + ) { const hex = new Player({ q: initialTile.q, r: initialTile.r }); - hex.power = 0; + hex.power = power; hex.positionTile = initialTile; hex.colorKey = colorKey; hex.graphic = new PIXI.Graphics(); From bab0a8b58e3197699a1aff3ba0d592ac81397098 Mon Sep 17 00:00:00 2001 From: extremebip Date: Tue, 25 Jun 2024 14:28:32 +0700 Subject: [PATCH 19/44] Convert entire hex coordinate into cube coordinate --- src/Game.ts | 9 ++++----- src/model/MapTile.ts | 4 ++-- src/temp/board-mocker.ts | 7 ++++--- src/types/model/BaseMapTile.d.ts | 4 ++-- src/types/model/BasePlayer.d.ts | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Game.ts b/src/Game.ts index 3b9b5a9..e8b6008 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1,7 +1,7 @@ import { Game } from "boardgame.io"; import { mockBoard, mockPosition } from "./temp/board-mocker"; import { BaseMapTile } from "./types/model/BaseMapTile"; -import { HexCoordinates, distance, toCube } from "honeycomb-grid"; +import { PartialCubeCoordinates, distance, toCube } from "honeycomb-grid"; import { INVALID_MOVE } from "boardgame.io/core"; import BaseHex from "./model/Base/TileHex"; import { BasePlayer } from "./types/model/BasePlayer"; @@ -23,17 +23,16 @@ export const BattleSimulator: Game = { }; }, moves: { - movePlayer: ({ G }, target: HexCoordinates) => { + movePlayer: ({ G }, target: PartialCubeCoordinates) => { if (distance(BaseHex.settings, G.player.position, target) > 1) return INVALID_MOVE; G.player.position = target; const currCoordinates = toCube(BaseHex.settings, target); const targetCell = G.cells.filter((cell) => { - const cellCoordinates = toCube(BaseHex.settings, cell.coordinates); return ( - currCoordinates.q === cellCoordinates.q && - currCoordinates.r === cellCoordinates.r + currCoordinates.q === cell.coordinates.q && + currCoordinates.r === cell.coordinates.r ); })[0]; diff --git a/src/model/MapTile.ts b/src/model/MapTile.ts index feafbd1..ec08ea3 100644 --- a/src/model/MapTile.ts +++ b/src/model/MapTile.ts @@ -1,4 +1,4 @@ -import { HexCoordinates } from "honeycomb-grid"; +import { PartialCubeCoordinates } from "honeycomb-grid"; import { grid as gridColor } from "../../colors.json"; import * as PIXI from "pixi.js"; import BaseHex from "./Base/TileHex"; @@ -7,7 +7,7 @@ class MapTile extends BaseHex { cellNumber!: number; graphic!: PIXI.Graphics; - static create(coordinates: HexCoordinates, cellNumber: number) { + static create(coordinates: PartialCubeCoordinates, cellNumber: number) { const hex = new MapTile(coordinates); hex.cellNumber = cellNumber; hex.graphic = new PIXI.Graphics(); diff --git a/src/temp/board-mocker.ts b/src/temp/board-mocker.ts index d166878..9271038 100644 --- a/src/temp/board-mocker.ts +++ b/src/temp/board-mocker.ts @@ -1,5 +1,6 @@ -import { HexCoordinates } from "honeycomb-grid"; +import { PartialCubeCoordinates, toCube } from "honeycomb-grid"; import { BaseMapTile } from "../types/model/BaseMapTile"; +import TileHex from "../model/Base/TileHex"; function getRandomInt(min: number, max: number) { const minCeiled = Math.ceil(min); @@ -10,13 +11,13 @@ function getRandomInt(min: number, max: number) { export const mockBoard = (): BaseMapTile[] => { return Array.from(Array(10), (_, i) => Array.from(Array(10), (_, j) => ({ - coordinates: { row: i, col: j }, + coordinates: toCube(TileHex.settings, { row: i, col: j }), cellNumber: Math.random() * 3 < 1 ? 1 : 0, })) ).flat(); }; -export const mockPosition = (board: BaseMapTile[]): HexCoordinates => { +export const mockPosition = (board: BaseMapTile[]): PartialCubeCoordinates => { const availablePositions = board.filter((tile) => tile.cellNumber === 0); const randomTileIdx = getRandomInt(0, availablePositions.length); return availablePositions[randomTileIdx].coordinates; diff --git a/src/types/model/BaseMapTile.d.ts b/src/types/model/BaseMapTile.d.ts index 6a82396..70ade8e 100644 --- a/src/types/model/BaseMapTile.d.ts +++ b/src/types/model/BaseMapTile.d.ts @@ -1,6 +1,6 @@ -import { HexCoordinates } from "honeycomb-grid"; +import { PartialCubeCoordinates } from "honeycomb-grid"; export declare type BaseMapTile = { cellNumber: number; - coordinates: HexCoordinates; + coordinates: PartialCubeCoordinates; }; diff --git a/src/types/model/BasePlayer.d.ts b/src/types/model/BasePlayer.d.ts index a2d3d43..35b7681 100644 --- a/src/types/model/BasePlayer.d.ts +++ b/src/types/model/BasePlayer.d.ts @@ -1,6 +1,6 @@ -import { HexCoordinates } from "honeycomb-grid"; +import { PartialCubeCoordinates } from "honeycomb-grid"; export declare type BasePlayer = { - position: HexCoordinates; + position: PartialCubeCoordinates; power: number; }; From c6e121185aef8e23fe1b6d6cfa1eb56ce9fb3ea9 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 3 Nov 2024 21:31:37 +0700 Subject: [PATCH 20/44] Add multiplayers with movement --- notes.md | 4 +- src/App.bak.ts | 96 ++++++++++++++++++++++++++++++++++++++++ src/App.ts | 49 +++++++++++++------- src/Game.ts | 24 +++++----- src/temp/board-mocker.ts | 23 +++++++--- 5 files changed, 162 insertions(+), 34 deletions(-) create mode 100644 src/App.bak.ts diff --git a/notes.md b/notes.md index 68b4a23..b3a2376 100644 --- a/notes.md +++ b/notes.md @@ -2,7 +2,7 @@ Playground Roadmap 1. [x] Init project with boardgame.io & PIXI.js. 2. [x] Create the board with numbers. -3. [ ] Add player and movement. -4. [ ] Add multiplayer and win condition. +3. [x] Add player and movement. +4. [ ] Add multiplayer and win condition (technical back-end). 5. [ ] Add multiple units for each player. 6. [ ] Push playground into main. diff --git a/src/App.bak.ts b/src/App.bak.ts new file mode 100644 index 0000000..7049c53 --- /dev/null +++ b/src/App.bak.ts @@ -0,0 +1,96 @@ +import { Client } from "boardgame.io/client"; +import { BattleSimulator, GameState } from "./Game"; +import { + ClientState, + _ClientImpl, +} from "boardgame.io/dist/types/src/client/client"; +import MapTile from "./model/MapTile"; +import { Grid } from "honeycomb-grid"; +import * as PIXI from "pixi.js"; +import Player from "./model/Player"; + +class BattleSimulatorClient { + client: _ClientImpl; + pixiApp: PIXI.Application; + grid: Grid; + player: Player; + + constructor(pixiApp: PIXI.Application) { + this.client = Client({ game: BattleSimulator }); + this.client.start(); + this.pixiApp = pixiApp; + this.grid = this.createBoard(); + this.player = this.createPlayer(); + + this.attachListeners(); + this.client.subscribe((state) => this.update(state)); + } + + createBoard() { + const initialState = this.client.getInitialState(); + const cells = initialState.G.cells; + + // Temporary code to draw grid here + const grid = Grid.fromIterable( + cells.map((cell) => MapTile.create(cell.coordinates, cell.cellNumber)) + ); + + grid.forEach((tile) => pixiApp.stage.addChild(tile.render())); + return grid; + } + + createPlayer() { + const initialState = this.client.getInitialState(); + const playerPos = initialState.G.player.position; + + // const player = new Player(playerPos, "blue"); + const player = Player.create(0, this.grid.getHex(playerPos)!, "blue"); + pixiApp.stage.addChild(player.render()); + return player; + } + + attachListeners() { + document.addEventListener("click", ({ offsetX, offsetY }) => { + const tile = this.grid.pointToHex( + { x: offsetX, y: offsetY }, + { allowOutside: false } + ); + + if (tile !== undefined) { + this.client.moves.movePlayer({ q: tile.q, r: tile.r }); + } + }); + } + + update(state: ClientState) { + if (state === null) return; + this.player.destroy(); + + const newPlayerPosition = state.G.player.position; + const tile = this.grid.getHex(newPlayerPosition)!; + const player = Player.create(state.G.player.power, tile, "blue"); + pixiApp.stage.addChild(player.render()); + this.player = player; + + tile.cellNumber = 0; + tile.render(); + // console.log("Before: ", { q: this.player.q, r: this.player.r }); + // console.log("After: ", newPlayerPosition); + // const cubePosition = toCube(TileHex.settings, newPlayerPosition); + // const tilePosition = this.grid.getHex(newPlayerPosition); + // this.player = this.player.translate({ + // q: cubePosition.q - this.player.q, + // r: cubePosition.r - this.player.r, + // }); + } +} + +const pixiApp = new PIXI.Application(); +await pixiApp.init({ backgroundAlpha: 0 }); + +document.body.appendChild(pixiApp.canvas); + +// Debug Only +globalThis.__PIXI_APP__ = pixiApp; + +const app = new BattleSimulatorClient(pixiApp); diff --git a/src/App.ts b/src/App.ts index 7049c53..a5abdc4 100644 --- a/src/App.ts +++ b/src/App.ts @@ -13,14 +13,14 @@ class BattleSimulatorClient { client: _ClientImpl; pixiApp: PIXI.Application; grid: Grid; - player: Player; + players: Player[]; constructor(pixiApp: PIXI.Application) { this.client = Client({ game: BattleSimulator }); this.client.start(); this.pixiApp = pixiApp; this.grid = this.createBoard(); - this.player = this.createPlayer(); + this.players = this.createPlayers(); this.attachListeners(); this.client.subscribe((state) => this.update(state)); @@ -39,14 +39,20 @@ class BattleSimulatorClient { return grid; } - createPlayer() { + createPlayers() { const initialState = this.client.getInitialState(); - const playerPos = initialState.G.player.position; - - // const player = new Player(playerPos, "blue"); - const player = Player.create(0, this.grid.getHex(playerPos)!, "blue"); - pixiApp.stage.addChild(player.render()); - return player; + const players = initialState.G.players; + + return players.map((player, i) => { + // const player = new Player(playerPos, "blue"); + const playerTile = Player.create( + 0, + this.grid.getHex(player.position)!, + i == 0 ? "blue" : "red" + ); + pixiApp.stage.addChild(playerTile.render()); + return playerTile; + }); } attachListeners() { @@ -64,16 +70,25 @@ class BattleSimulatorClient { update(state: ClientState) { if (state === null) return; - this.player.destroy(); + this.players = this.players.map((player, i) => { + player.destroy(); + + const playerState = state.G.players[i]; + const newPlayerPosition = playerState.position; + const tile = this.grid.getHex(newPlayerPosition)!; + const newPlayer = Player.create( + playerState.power, + tile, + i == 0 ? "blue" : "red" + ); + pixiApp.stage.addChild(newPlayer.render()); + + tile.cellNumber = 0; + tile.render(); - const newPlayerPosition = state.G.player.position; - const tile = this.grid.getHex(newPlayerPosition)!; - const player = Player.create(state.G.player.power, tile, "blue"); - pixiApp.stage.addChild(player.render()); - this.player = player; + return newPlayer; + }); - tile.cellNumber = 0; - tile.render(); // console.log("Before: ", { q: this.player.q, r: this.player.r }); // console.log("After: ", newPlayerPosition); // const cubePosition = toCube(TileHex.settings, newPlayerPosition); diff --git a/src/Game.ts b/src/Game.ts index e8b6008..59f777a 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1,5 +1,5 @@ import { Game } from "boardgame.io"; -import { mockBoard, mockPosition } from "./temp/board-mocker"; +import { mockBoard, mockPlayers } from "./temp/board-mocker"; import { BaseMapTile } from "./types/model/BaseMapTile"; import { PartialCubeCoordinates, distance, toCube } from "honeycomb-grid"; import { INVALID_MOVE } from "boardgame.io/core"; @@ -8,7 +8,7 @@ import { BasePlayer } from "./types/model/BasePlayer"; export interface GameState { cells: BaseMapTile[]; - player: BasePlayer; + players: BasePlayer[]; } export const BattleSimulator: Game = { @@ -16,17 +16,21 @@ export const BattleSimulator: Game = { const cells = mockBoard(); return { cells: cells, - player: { - position: mockPosition(cells), - power: 0, - }, + players: mockPlayers(cells, 2), }; }, + minPlayers: 2, + maxPlayers: 2, + turn: { + minMoves: 1, + maxMoves: 1, + }, moves: { - movePlayer: ({ G }, target: PartialCubeCoordinates) => { - if (distance(BaseHex.settings, G.player.position, target) > 1) + movePlayer: ({ G, playerID }, target: PartialCubeCoordinates) => { + const player = G.players[+playerID]; + if (distance(BaseHex.settings, player.position, target) > 1) return INVALID_MOVE; - G.player.position = target; + player.position = target; const currCoordinates = toCube(BaseHex.settings, target); const targetCell = G.cells.filter((cell) => { @@ -37,7 +41,7 @@ export const BattleSimulator: Game = { })[0]; if (targetCell.cellNumber > 0) { - G.player.power += targetCell.cellNumber; + player.power += targetCell.cellNumber; targetCell.cellNumber = 0; } }, diff --git a/src/temp/board-mocker.ts b/src/temp/board-mocker.ts index 9271038..d773901 100644 --- a/src/temp/board-mocker.ts +++ b/src/temp/board-mocker.ts @@ -1,6 +1,7 @@ -import { PartialCubeCoordinates, toCube } from "honeycomb-grid"; -import { BaseMapTile } from "../types/model/BaseMapTile"; +import { toCube } from "honeycomb-grid"; import TileHex from "../model/Base/TileHex"; +import { BaseMapTile } from "../types/model/BaseMapTile"; +import { BasePlayer } from "../types/model/BasePlayer"; function getRandomInt(min: number, max: number) { const minCeiled = Math.ceil(min); @@ -17,10 +18,22 @@ export const mockBoard = (): BaseMapTile[] => { ).flat(); }; -export const mockPosition = (board: BaseMapTile[]): PartialCubeCoordinates => { +export const mockPlayers = ( + board: BaseMapTile[], + playerCount: number +): BasePlayer[] => { const availablePositions = board.filter((tile) => tile.cellNumber === 0); - const randomTileIdx = getRandomInt(0, availablePositions.length); - return availablePositions[randomTileIdx].coordinates; + const players: BasePlayer[] = []; + + for (let i = 0; i < playerCount; i++) { + const randomTileIdx = getRandomInt(0, availablePositions.length); + players.push({ + power: 0, + position: availablePositions.splice(randomTileIdx, 1)[0].coordinates, + }); + } + + return players; }; export const multiplyMatrix = (mat1: number[][], mat2: number[][]) => { From 1bdd44945641ea727d5dc776ee7fb98b7cf7da27 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 24 Nov 2024 20:11:59 +0700 Subject: [PATCH 21/44] Refactor move player into a controller --- src/App.ts | 3 ++- src/Game.ts | 32 +++----------------------- src/controller/move-controller.ts | 38 +++++++++++++++++++++++++++++++ src/types/GameState.ts | 7 ++++++ 4 files changed, 50 insertions(+), 30 deletions(-) create mode 100644 src/controller/move-controller.ts create mode 100644 src/types/GameState.ts diff --git a/src/App.ts b/src/App.ts index a5abdc4..2bf9a68 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,5 +1,5 @@ import { Client } from "boardgame.io/client"; -import { BattleSimulator, GameState } from "./Game"; +import { BattleSimulator } from "./Game"; import { ClientState, _ClientImpl, @@ -8,6 +8,7 @@ import MapTile from "./model/MapTile"; import { Grid } from "honeycomb-grid"; import * as PIXI from "pixi.js"; import Player from "./model/Player"; +import { GameState } from "./types/GameState"; class BattleSimulatorClient { client: _ClientImpl; diff --git a/src/Game.ts b/src/Game.ts index 59f777a..dddb9d3 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1,15 +1,7 @@ import { Game } from "boardgame.io"; import { mockBoard, mockPlayers } from "./temp/board-mocker"; -import { BaseMapTile } from "./types/model/BaseMapTile"; -import { PartialCubeCoordinates, distance, toCube } from "honeycomb-grid"; -import { INVALID_MOVE } from "boardgame.io/core"; -import BaseHex from "./model/Base/TileHex"; -import { BasePlayer } from "./types/model/BasePlayer"; - -export interface GameState { - cells: BaseMapTile[]; - players: BasePlayer[]; -} +import { GameState } from "./types/GameState"; +import MoveController from "./controller/move-controller"; export const BattleSimulator: Game = { setup: () => { @@ -26,24 +18,6 @@ export const BattleSimulator: Game = { maxMoves: 1, }, moves: { - movePlayer: ({ G, playerID }, target: PartialCubeCoordinates) => { - const player = G.players[+playerID]; - if (distance(BaseHex.settings, player.position, target) > 1) - return INVALID_MOVE; - player.position = target; - - const currCoordinates = toCube(BaseHex.settings, target); - const targetCell = G.cells.filter((cell) => { - return ( - currCoordinates.q === cell.coordinates.q && - currCoordinates.r === cell.coordinates.r - ); - })[0]; - - if (targetCell.cellNumber > 0) { - player.power += targetCell.cellNumber; - targetCell.cellNumber = 0; - } - }, + ...MoveController.publish(), }, }; diff --git a/src/controller/move-controller.ts b/src/controller/move-controller.ts new file mode 100644 index 0000000..1c74191 --- /dev/null +++ b/src/controller/move-controller.ts @@ -0,0 +1,38 @@ +import { PartialCubeCoordinates, distance, toCube } from "honeycomb-grid"; +import { INVALID_MOVE } from "boardgame.io/core"; +import { Move } from "boardgame.io"; +import { GameState } from "../types/GameState"; +import TileHex from "../model/Base/TileHex"; + +const movePlayer: Move = ( + { G, playerID }, + target: PartialCubeCoordinates +) => { + const player = G.players[+playerID]; + if (distance(TileHex.settings, player.position, target) > 1) + return INVALID_MOVE; + player.position = target; + + const currCoordinates = toCube(TileHex.settings, target); + const targetCell = G.cells.filter((cell) => { + return ( + currCoordinates.q === cell.coordinates.q && + currCoordinates.r === cell.coordinates.r + ); + })[0]; + + if (targetCell.cellNumber > 0) { + player.power += targetCell.cellNumber; + targetCell.cellNumber = 0; + } +}; + +class MoveController { + static publish() { + return { + movePlayer, + }; + } +} + +export default MoveController; diff --git a/src/types/GameState.ts b/src/types/GameState.ts new file mode 100644 index 0000000..ce6e0fe --- /dev/null +++ b/src/types/GameState.ts @@ -0,0 +1,7 @@ +import { BaseMapTile } from "./model/BaseMapTile"; +import { BasePlayer } from "./model/BasePlayer"; + +export declare type GameState = { + cells: BaseMapTile[]; + players: BasePlayer[]; +}; From b03bf3e6c37f241e0bd32810ee5e07cab9fffe13 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 7 Dec 2024 20:41:52 +0700 Subject: [PATCH 22/44] Add fighting mechanic & win condition --- .editorconfig | 5 +++- index.html | 3 ++- notes.md | 2 +- src/App.ts | 39 ++++++++++++++++++++++++++---- src/Game.ts | 7 ++++++ src/controller/fight-controller.ts | 37 ++++++++++++++++++++++++++++ src/controller/move-controller.ts | 4 +-- src/temp/board-mocker.ts | 2 ++ src/types/model/BasePlayer.d.ts | 2 ++ src/util/board.ts | 15 ++++++++++++ 10 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 src/controller/fight-controller.ts create mode 100644 src/util/board.ts diff --git a/.editorconfig b/.editorconfig index 6f87ae0..fe00236 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,4 +6,7 @@ end_of_line = lf insert_final_newline = true indent_style = space indent_size = 3 -trim_trailing_whitespace = true \ No newline at end of file +trim_trailing_whitespace = true + +[.html] +indent_size = 2 diff --git a/index.html b/index.html index 0030329..6017121 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,7 @@ Battle Simulator - +

+ diff --git a/notes.md b/notes.md index b3a2376..26c6a4a 100644 --- a/notes.md +++ b/notes.md @@ -3,6 +3,6 @@ Playground Roadmap 1. [x] Init project with boardgame.io & PIXI.js. 2. [x] Create the board with numbers. 3. [x] Add player and movement. -4. [ ] Add multiplayer and win condition (technical back-end). +4. [x] Add multiplayer and win condition (technical back-end). 5. [ ] Add multiple units for each player. 6. [ ] Push playground into main. diff --git a/src/App.ts b/src/App.ts index 2bf9a68..efba4fe 100644 --- a/src/App.ts +++ b/src/App.ts @@ -9,6 +9,7 @@ import { Grid } from "honeycomb-grid"; import * as PIXI from "pixi.js"; import Player from "./model/Player"; import { GameState } from "./types/GameState"; +import { getObjectFromStateAndCoord } from "./util/board"; class BattleSimulatorClient { client: _ClientImpl; @@ -62,19 +63,37 @@ class BattleSimulatorClient { { x: offsetX, y: offsetY }, { allowOutside: false } ); + const state = this.client.getState(); - if (tile !== undefined) { - this.client.moves.movePlayer({ q: tile.q, r: tile.r }); + if (state == null || tile === undefined) return; + + const coordinate = { q: tile.q, r: tile.r }; + const object = getObjectFromStateAndCoord(state.G, coordinate); + + if (object == null) { + this.client.moves.movePlayer(coordinate); + return; + } + + if (object.id != state.ctx.currentPlayer) { + this.client.moves.fight(object.id); } }); } update(state: ClientState) { if (state === null) return; - this.players = this.players.map((player, i) => { + const newPlayers = []; + + for (let i = 0; i < this.players.length; i++) { + const player = this.players[i]; player.destroy(); const playerState = state.G.players[i]; + if (!playerState.isAlive) { + continue; + } + const newPlayerPosition = playerState.position; const tile = this.grid.getHex(newPlayerPosition)!; const newPlayer = Player.create( @@ -87,8 +106,18 @@ class BattleSimulatorClient { tile.cellNumber = 0; tile.render(); - return newPlayer; - }); + newPlayers.push(newPlayer); + } + + this.players = newPlayers; + + if (state.ctx.gameover) { + const textGameOverElement = document.querySelector("#game-over-text")!; + textGameOverElement.textContent = + state.ctx.gameover.winner !== undefined + ? `Player ${state.ctx.gameover.winner} Win!` + : "It's a Draw!"; + } // console.log("Before: ", { q: this.player.q, r: this.player.r }); // console.log("After: ", newPlayerPosition); diff --git a/src/Game.ts b/src/Game.ts index dddb9d3..9e6d302 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -2,6 +2,7 @@ import { Game } from "boardgame.io"; import { mockBoard, mockPlayers } from "./temp/board-mocker"; import { GameState } from "./types/GameState"; import MoveController from "./controller/move-controller"; +import FightController from "./controller/fight-controller"; export const BattleSimulator: Game = { setup: () => { @@ -19,5 +20,11 @@ export const BattleSimulator: Game = { }, moves: { ...MoveController.publish(), + ...FightController.publish(), + }, + endIf: ({ G }) => { + const livingPlayers = G.players.filter((player) => player.isAlive); + if (livingPlayers.length == 0) return { draw: true }; + if (livingPlayers.length == 1) return { winner: livingPlayers[0].id }; }, }; diff --git a/src/controller/fight-controller.ts b/src/controller/fight-controller.ts new file mode 100644 index 0000000..b138dab --- /dev/null +++ b/src/controller/fight-controller.ts @@ -0,0 +1,37 @@ +import { Move } from "boardgame.io"; +import { GameState } from "../types/GameState"; +import TileHex from "../model/Base/TileHex"; +import { distance } from "honeycomb-grid"; +import { INVALID_MOVE } from "boardgame.io/core"; + +const fight: Move = ({ G, playerID }, targetPlayerId: string) => { + const currentPlayer = G.players[+playerID]; + const targetPlayer = G.players[+targetPlayerId]; + const dist = distance( + TileHex.settings, + currentPlayer.position, + targetPlayer.position + ); + if (dist > 1) return INVALID_MOVE; + + const high = + currentPlayer.power > targetPlayer.power ? currentPlayer : targetPlayer; + const low = + currentPlayer.power < targetPlayer.power ? currentPlayer : targetPlayer; + + high.power -= low.power; + low.power = 0; + + if (high.power === 0) high.isAlive = false; + if (low.power === 0) low.isAlive = false; +}; + +class FightController { + static publish() { + return { + fight, + }; + } +} + +export default FightController; diff --git a/src/controller/move-controller.ts b/src/controller/move-controller.ts index 1c74191..daff559 100644 --- a/src/controller/move-controller.ts +++ b/src/controller/move-controller.ts @@ -9,8 +9,8 @@ const movePlayer: Move = ( target: PartialCubeCoordinates ) => { const player = G.players[+playerID]; - if (distance(TileHex.settings, player.position, target) > 1) - return INVALID_MOVE; + const dist = distance(TileHex.settings, player.position, target); + if (dist > 1 || dist == 0) return INVALID_MOVE; player.position = target; const currCoordinates = toCube(TileHex.settings, target); diff --git a/src/temp/board-mocker.ts b/src/temp/board-mocker.ts index d773901..8caac49 100644 --- a/src/temp/board-mocker.ts +++ b/src/temp/board-mocker.ts @@ -28,8 +28,10 @@ export const mockPlayers = ( for (let i = 0; i < playerCount; i++) { const randomTileIdx = getRandomInt(0, availablePositions.length); players.push({ + id: i.toString(), power: 0, position: availablePositions.splice(randomTileIdx, 1)[0].coordinates, + isAlive: true, }); } diff --git a/src/types/model/BasePlayer.d.ts b/src/types/model/BasePlayer.d.ts index 35b7681..650306a 100644 --- a/src/types/model/BasePlayer.d.ts +++ b/src/types/model/BasePlayer.d.ts @@ -1,6 +1,8 @@ import { PartialCubeCoordinates } from "honeycomb-grid"; export declare type BasePlayer = { + id: string; position: PartialCubeCoordinates; power: number; + isAlive: boolean; }; diff --git a/src/util/board.ts b/src/util/board.ts new file mode 100644 index 0000000..0baccb4 --- /dev/null +++ b/src/util/board.ts @@ -0,0 +1,15 @@ +import { PartialCubeCoordinates } from "honeycomb-grid"; +import { GameState } from "../types/GameState"; + +export const getObjectFromStateAndCoord = ( + state: GameState, + coordinate: PartialCubeCoordinates +) => { + const filtered = state.players.filter( + (player) => + player.position.q === coordinate.q && + player.position.r === coordinate.r + ); + + return filtered.length > 0 ? filtered[0] : null; +}; From deb3a6cdcc8fc823e831c2deca94390063d62365 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 8 Dec 2024 14:56:18 +0700 Subject: [PATCH 23/44] Init Unit Class & States for Multiple Units --- src/App.ts | 160 +++++++++----------- src/Game.ts | 23 ++- src/model/Base/{PlayerHex.ts => UnitHex.ts} | 4 +- src/model/{Player.ts => Unit.ts} | 8 +- src/temp/board-mocker.ts | 18 ++- src/types/GameState.ts | 5 +- src/types/model/BaseUnit.d.ts | 9 ++ 7 files changed, 120 insertions(+), 107 deletions(-) rename src/model/Base/{PlayerHex.ts => UnitHex.ts} (61%) rename src/model/{Player.ts => Unit.ts} (91%) create mode 100644 src/types/model/BaseUnit.d.ts diff --git a/src/App.ts b/src/App.ts index efba4fe..a5f598d 100644 --- a/src/App.ts +++ b/src/App.ts @@ -7,7 +7,7 @@ import { import MapTile from "./model/MapTile"; import { Grid } from "honeycomb-grid"; import * as PIXI from "pixi.js"; -import Player from "./model/Player"; +import Unit from "./model/Unit"; import { GameState } from "./types/GameState"; import { getObjectFromStateAndCoord } from "./util/board"; @@ -15,17 +15,17 @@ class BattleSimulatorClient { client: _ClientImpl; pixiApp: PIXI.Application; grid: Grid; - players: Player[]; + units: Unit[]; constructor(pixiApp: PIXI.Application) { this.client = Client({ game: BattleSimulator }); this.client.start(); this.pixiApp = pixiApp; this.grid = this.createBoard(); - this.players = this.createPlayers(); + this.units = this.createUnits(); - this.attachListeners(); - this.client.subscribe((state) => this.update(state)); + // this.attachListeners(); + // this.client.subscribe((state) => this.update(state)); } createBoard() { @@ -41,93 +41,83 @@ class BattleSimulatorClient { return grid; } - createPlayers() { + createUnits() { const initialState = this.client.getInitialState(); - const players = initialState.G.players; + const units = initialState.G.units; - return players.map((player, i) => { - // const player = new Player(playerPos, "blue"); - const playerTile = Player.create( + return units.map((unit) => { + const unitTile = Unit.create( 0, - this.grid.getHex(player.position)!, - i == 0 ? "blue" : "red" + this.grid.getHex(unit.position)!, + unit.playerID == "0" ? "blue" : "red" ); - pixiApp.stage.addChild(playerTile.render()); - return playerTile; + pixiApp.stage.addChild(unitTile.render()); + return unitTile; }); } - attachListeners() { - document.addEventListener("click", ({ offsetX, offsetY }) => { - const tile = this.grid.pointToHex( - { x: offsetX, y: offsetY }, - { allowOutside: false } - ); - const state = this.client.getState(); - - if (state == null || tile === undefined) return; - - const coordinate = { q: tile.q, r: tile.r }; - const object = getObjectFromStateAndCoord(state.G, coordinate); - - if (object == null) { - this.client.moves.movePlayer(coordinate); - return; - } - - if (object.id != state.ctx.currentPlayer) { - this.client.moves.fight(object.id); - } - }); - } - - update(state: ClientState) { - if (state === null) return; - const newPlayers = []; - - for (let i = 0; i < this.players.length; i++) { - const player = this.players[i]; - player.destroy(); - - const playerState = state.G.players[i]; - if (!playerState.isAlive) { - continue; - } - - const newPlayerPosition = playerState.position; - const tile = this.grid.getHex(newPlayerPosition)!; - const newPlayer = Player.create( - playerState.power, - tile, - i == 0 ? "blue" : "red" - ); - pixiApp.stage.addChild(newPlayer.render()); - - tile.cellNumber = 0; - tile.render(); - - newPlayers.push(newPlayer); - } - - this.players = newPlayers; - - if (state.ctx.gameover) { - const textGameOverElement = document.querySelector("#game-over-text")!; - textGameOverElement.textContent = - state.ctx.gameover.winner !== undefined - ? `Player ${state.ctx.gameover.winner} Win!` - : "It's a Draw!"; - } - - // console.log("Before: ", { q: this.player.q, r: this.player.r }); - // console.log("After: ", newPlayerPosition); - // const cubePosition = toCube(TileHex.settings, newPlayerPosition); - // const tilePosition = this.grid.getHex(newPlayerPosition); - // this.player = this.player.translate({ - // q: cubePosition.q - this.player.q, - // r: cubePosition.r - this.player.r, - // }); - } + // attachListeners() { + // document.addEventListener("click", ({ offsetX, offsetY }) => { + // const tile = this.grid.pointToHex( + // { x: offsetX, y: offsetY }, + // { allowOutside: false } + // ); + // const state = this.client.getState(); + + // if (state == null || tile === undefined) return; + + // const coordinate = { q: tile.q, r: tile.r }; + // const object = getObjectFromStateAndCoord(state.G, coordinate); + + // if (object == null) { + // this.client.moves.movePlayer(coordinate); + // return; + // } + + // if (object.id != state.ctx.currentPlayer) { + // this.client.moves.fight(object.id); + // } + // }); + // } + + // update(state: ClientState) { + // if (state === null) return; + // const newPlayers = []; + + // for (let i = 0; i < this.players.length; i++) { + // const player = this.players[i]; + // player.destroy(); + + // const playerState = state.G.players[i]; + // if (!playerState.isAlive) { + // continue; + // } + + // const newPlayerPosition = playerState.position; + // const tile = this.grid.getHex(newPlayerPosition)!; + // const newPlayer = Player.create( + // playerState.power, + // tile, + // i == 0 ? "blue" : "red" + // ); + // pixiApp.stage.addChild(newPlayer.render()); + + // tile.cellNumber = 0; + // tile.render(); + + // newPlayers.push(newPlayer); + // } + + // this.players = newPlayers; + + // if (state.ctx.gameover) { + // const textGameOverElement = document.querySelector("#game-over-text")!; + // textGameOverElement.textContent = + // state.ctx.gameover.winner !== undefined + // ? `Player ${state.ctx.gameover.winner} Win!` + // : "It's a Draw!"; + // } + // } } const pixiApp = new PIXI.Application(); diff --git a/src/Game.ts b/src/Game.ts index 9e6d302..d762bb6 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1,5 +1,5 @@ import { Game } from "boardgame.io"; -import { mockBoard, mockPlayers } from "./temp/board-mocker"; +import { mockBoard, mockUnits } from "./temp/board-mocker"; import { GameState } from "./types/GameState"; import MoveController from "./controller/move-controller"; import FightController from "./controller/fight-controller"; @@ -7,9 +7,14 @@ import FightController from "./controller/fight-controller"; export const BattleSimulator: Game = { setup: () => { const cells = mockBoard(); + const units = mockUnits(cells, 2, 2); + const unitCountByPlayer: Record = {}; + for (let i = 0; i < 2; i++) unitCountByPlayer[i.toString()] = 2; + return { cells: cells, - players: mockPlayers(cells, 2), + units: units, + unitCountByPlayer, }; }, minPlayers: 2, @@ -22,9 +27,15 @@ export const BattleSimulator: Game = { ...MoveController.publish(), ...FightController.publish(), }, - endIf: ({ G }) => { - const livingPlayers = G.players.filter((player) => player.isAlive); - if (livingPlayers.length == 0) return { draw: true }; - if (livingPlayers.length == 1) return { winner: livingPlayers[0].id }; + endIf: ({ G, ctx }) => { + let aboveZeroUnitPlayerIds = []; + for (let i = 0; i < ctx.numPlayers; i++) { + if (G.unitCountByPlayer[i.toString()] > 0) + aboveZeroUnitPlayerIds.push(i.toString()); + } + + if (aboveZeroUnitPlayerIds.length == 0) return { draw: true }; + if (aboveZeroUnitPlayerIds.length == 1) + return { winner: aboveZeroUnitPlayerIds[0] }; }, }; diff --git a/src/model/Base/PlayerHex.ts b/src/model/Base/UnitHex.ts similarity index 61% rename from src/model/Base/PlayerHex.ts rename to src/model/Base/UnitHex.ts index 5a698ca..99792d5 100644 --- a/src/model/Base/PlayerHex.ts +++ b/src/model/Base/UnitHex.ts @@ -2,6 +2,6 @@ import { defineHex } from "honeycomb-grid"; import gameConfig from "../../../gameConfig"; -const PlayerHex = defineHex({ ...gameConfig.hex, dimensions: 30 }); +const UnitHex = defineHex({ ...gameConfig.hex, dimensions: 30 }); -export default PlayerHex; +export default UnitHex; diff --git a/src/model/Player.ts b/src/model/Unit.ts similarity index 91% rename from src/model/Player.ts rename to src/model/Unit.ts index 2dafa4d..6d0f481 100644 --- a/src/model/Player.ts +++ b/src/model/Unit.ts @@ -1,11 +1,11 @@ -import PlayerHex from "./Base/PlayerHex"; +import UnitHex from "./Base/UnitHex"; import MapTile from "./MapTile"; import * as PIXI from "pixi.js"; import { player as playerColor } from "../../colors.json"; import { PlayerTeamColor } from "../types/config/colors"; // class Player { -class Player extends PlayerHex { +class Unit extends UnitHex { power!: number; positionTile!: MapTile; colorKey!: PlayerTeamColor; @@ -23,7 +23,7 @@ class Player extends PlayerHex { initialTile: MapTile, colorKey: PlayerTeamColor ) { - const hex = new Player({ q: initialTile.q, r: initialTile.r }); + const hex = new Unit({ q: initialTile.q, r: initialTile.r }); hex.power = power; hex.positionTile = initialTile; hex.colorKey = colorKey; @@ -67,4 +67,4 @@ class Player extends PlayerHex { } } -export default Player; +export default Unit; diff --git a/src/temp/board-mocker.ts b/src/temp/board-mocker.ts index 8caac49..db2721e 100644 --- a/src/temp/board-mocker.ts +++ b/src/temp/board-mocker.ts @@ -1,7 +1,7 @@ import { toCube } from "honeycomb-grid"; import TileHex from "../model/Base/TileHex"; import { BaseMapTile } from "../types/model/BaseMapTile"; -import { BasePlayer } from "../types/model/BasePlayer"; +import { BaseUnit } from "../types/model/BaseUnit"; function getRandomInt(min: number, max: number) { const minCeiled = Math.ceil(min); @@ -18,24 +18,26 @@ export const mockBoard = (): BaseMapTile[] => { ).flat(); }; -export const mockPlayers = ( +export const mockUnits = ( board: BaseMapTile[], - playerCount: number -): BasePlayer[] => { + playerCount: number, + unitPerPlayerCount: number +): BaseUnit[] => { const availablePositions = board.filter((tile) => tile.cellNumber === 0); - const players: BasePlayer[] = []; + const mockUnits: BaseUnit[] = []; - for (let i = 0; i < playerCount; i++) { + for (let i = 0; i < playerCount * unitPerPlayerCount; i++) { const randomTileIdx = getRandomInt(0, availablePositions.length); - players.push({ + mockUnits.push({ id: i.toString(), + playerID: (i % 2).toString(), power: 0, position: availablePositions.splice(randomTileIdx, 1)[0].coordinates, isAlive: true, }); } - return players; + return mockUnits; }; export const multiplyMatrix = (mat1: number[][], mat2: number[][]) => { diff --git a/src/types/GameState.ts b/src/types/GameState.ts index ce6e0fe..d3e60dc 100644 --- a/src/types/GameState.ts +++ b/src/types/GameState.ts @@ -1,7 +1,8 @@ import { BaseMapTile } from "./model/BaseMapTile"; -import { BasePlayer } from "./model/BasePlayer"; +import { BaseUnit } from "./model/BaseUnit"; export declare type GameState = { cells: BaseMapTile[]; - players: BasePlayer[]; + units: BaseUnit[]; + unitCountByPlayer: Record; }; diff --git a/src/types/model/BaseUnit.d.ts b/src/types/model/BaseUnit.d.ts new file mode 100644 index 0000000..3737c7f --- /dev/null +++ b/src/types/model/BaseUnit.d.ts @@ -0,0 +1,9 @@ +import { PartialCubeCoordinates } from "honeycomb-grid"; + +export declare type BaseUnit = { + id: string; + playerID: string; + position: PartialCubeCoordinates; + power: number; + isAlive: boolean; +}; From d29e92ab48e5cde413f3b7896dd83680b51d3589 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 8 Dec 2024 23:55:07 +0700 Subject: [PATCH 24/44] Add debug panel & ClientState --- index.html | 7 ++++- src/App.ts | 19 ++++++++++--- src/client/DebugPanel.ts | 56 ++++++++++++++++++++++++++++++++++++++ src/types/ClientState.d.ts | 7 +++++ 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 src/client/DebugPanel.ts create mode 100644 src/types/ClientState.d.ts diff --git a/index.html b/index.html index 6017121..f99540b 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,12 @@ Battle Simulator -

+
+
+

+
+
+
diff --git a/src/App.ts b/src/App.ts index a5f598d..5c9b4ff 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,21 +1,21 @@ import { Client } from "boardgame.io/client"; import { BattleSimulator } from "./Game"; -import { - ClientState, - _ClientImpl, -} from "boardgame.io/dist/types/src/client/client"; +import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; import MapTile from "./model/MapTile"; import { Grid } from "honeycomb-grid"; import * as PIXI from "pixi.js"; import Unit from "./model/Unit"; import { GameState } from "./types/GameState"; import { getObjectFromStateAndCoord } from "./util/board"; +import DebugPanel from "./client/DebugPanel"; +import { ClientState } from "./types/ClientState"; class BattleSimulatorClient { client: _ClientImpl; pixiApp: PIXI.Application; grid: Grid; units: Unit[]; + clientState: ClientState; constructor(pixiApp: PIXI.Application) { this.client = Client({ game: BattleSimulator }); @@ -23,6 +23,17 @@ class BattleSimulatorClient { this.pixiApp = pixiApp; this.grid = this.createBoard(); this.units = this.createUnits(); + const clientState = { + selectedUnit: null, + markedUnitIds: new Set(), + }; + + // FOR DEV & DEBUG + const debugPanel = new DebugPanel("#debug-panel"); + this.clientState = debugPanel.watch(clientState); + + // FOR PRODUCTION + // this.clientState = clientState; // this.attachListeners(); // this.client.subscribe((state) => this.update(state)); diff --git a/src/client/DebugPanel.ts b/src/client/DebugPanel.ts new file mode 100644 index 0000000..527a761 --- /dev/null +++ b/src/client/DebugPanel.ts @@ -0,0 +1,56 @@ +import { ClientState } from "../types/ClientState"; + +export default class DebugPanel { + debugPanelElement: Element; + + constructor(panelSelector: string) { + this.debugPanelElement = document.querySelector(panelSelector)!; + } + + watch(clientState: ClientState) { + this.debugPanelElement.innerHTML = + this.getHtmlContentFromState(clientState); + return new Proxy(clientState, { + set: (t, p, v) => { + Reflect.set(t, p, v); + this.debugPanelElement.innerHTML = this.getHtmlContentFromState(t); + return true; + }, + }); + } + + getHtmlContentFromState(state: ClientState) { + let html = ""; + for (const prop in state) { + const val = state[prop as keyof ClientState]; + html += `${prop}: ${this.getStringContentFrom(val)}
`; + } + return html; + } + + getStringContentFrom(val: any): string { + if (typeof val === "string") return `"${val}"`; + if (typeof val === "number") return val.toString(); + if (typeof val === "boolean") return val ? "true" : "false"; + if (val instanceof Set) { + const items = [...val.values()] + .map((item) => this.getStringContentFrom(item)) + .join(); + return `Set(${val.size}) [${items}]`; + } + + if (val instanceof Map) { + const items = [...val.entries()] + .map( + (item) => + `${this.getStringContentFrom( + item[0] + )}: ${this.getStringContentFrom(item[1])}` + ) + .join(); + return `Map(${val.size}) {${items}}`; + } + + return JSON.stringify(val); + } +} diff --git a/src/types/ClientState.d.ts b/src/types/ClientState.d.ts new file mode 100644 index 0000000..5a37a97 --- /dev/null +++ b/src/types/ClientState.d.ts @@ -0,0 +1,7 @@ +import Unit from "../model/Unit"; +import { BaseUnit } from "./model/BaseUnit"; + +export declare type ClientState = { + selectedUnit: BaseUnit | null; + markedUnitIds: Set; // Need better name +}; From ddbec4500b42bb8b21a4ff6fb6d6969af0d0720a Mon Sep 17 00:00:00 2001 From: extremebip Date: Thu, 3 Apr 2025 23:38:51 +0700 Subject: [PATCH 25/44] Add move unit by click handler --- src/App.ts | 21 +++++---- src/client/ClickHandler.ts | 77 +++++++++++++++++++++++++++++++ src/controller/move-controller.ts | 16 ++++--- src/util/board.ts | 7 ++- src/util/game-state.ts | 6 +++ 5 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 src/client/ClickHandler.ts create mode 100644 src/util/game-state.ts diff --git a/src/App.ts b/src/App.ts index 5c9b4ff..b536597 100644 --- a/src/App.ts +++ b/src/App.ts @@ -6,7 +6,7 @@ import { Grid } from "honeycomb-grid"; import * as PIXI from "pixi.js"; import Unit from "./model/Unit"; import { GameState } from "./types/GameState"; -import { getObjectFromStateAndCoord } from "./util/board"; +import ClickHandler from "./client/ClickHandler"; import DebugPanel from "./client/DebugPanel"; import { ClientState } from "./types/ClientState"; @@ -35,7 +35,7 @@ class BattleSimulatorClient { // FOR PRODUCTION // this.clientState = clientState; - // this.attachListeners(); + this.attachListeners(); // this.client.subscribe((state) => this.update(state)); } @@ -67,13 +67,16 @@ class BattleSimulatorClient { }); } - // attachListeners() { - // document.addEventListener("click", ({ offsetX, offsetY }) => { - // const tile = this.grid.pointToHex( - // { x: offsetX, y: offsetY }, - // { allowOutside: false } - // ); - // const state = this.client.getState(); + attachListeners() { + const clickHandler = new ClickHandler( + this.client, + this.grid, + this.clientState + ); + document.addEventListener("click", ({ offsetX, offsetY }) => + clickHandler.handle(offsetX, offsetY) + ); + } // if (state == null || tile === undefined) return; diff --git a/src/client/ClickHandler.ts b/src/client/ClickHandler.ts new file mode 100644 index 0000000..0c0bc4a --- /dev/null +++ b/src/client/ClickHandler.ts @@ -0,0 +1,77 @@ +import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; +import MapTile from "../model/MapTile"; +import { Grid } from "honeycomb-grid"; +import { GameState } from "../types/GameState"; +import { getObjectFromStateAndCoord } from "../util/board"; +import { ClientState } from "../types/ClientState"; +import { BaseUnit } from "../types/model/BaseUnit"; + +export default class ClickHandler { + gameClient: _ClientImpl; + grid: Grid; + clientState: ClientState; + + constructor( + gameClient: _ClientImpl, + grid: Grid, + clientState: ClientState + ) { + this.gameClient = gameClient; + this.grid = grid; + this.clientState = clientState; + } + + handle(offsetX: number, offsetY: number) { + const tile = this.grid.pointToHex( + { x: offsetX, y: offsetY }, + { allowOutside: false } + ); + + if (tile === undefined) { + if (this.clientState.selectedUnit != null) { + this.clientState.selectedUnit = null; + } + + return; + } + + const state = this.gameClient.getState(); + if (state == null) return; + + const coordinate = { q: tile.q, r: tile.r }; + const target = getObjectFromStateAndCoord(state.G, coordinate); + + if (this.clientState.selectedUnit == null) { + this.resolveSelection(state.ctx.currentPlayer, target); + return; + } + + if (target == null) { + const res = this.gameClient.moves.moveUnit( + this.clientState.selectedUnit.id, + coordinate + ); + } + // else if (target.id != state.ctx.currentPlayer) { + // this.gameClient.moves.fight(target.id); + // } + + this.clientState.markedUnitIds.add(this.clientState.selectedUnit.id); + this.clientState.selectedUnit = null; + } + + resolveSelection(currentPlayerId: string, object: BaseUnit | null) { + if (object == null) return; + if (object.playerID != currentPlayerId) { + alert("Please select your own unit!"); + return; + } + + if (this.clientState.markedUnitIds.has(object.id)) { + alert("This unit has done an action, choose another unit!"); + return; + } + + this.clientState.selectedUnit = object; + } +} diff --git a/src/controller/move-controller.ts b/src/controller/move-controller.ts index daff559..f8fe98d 100644 --- a/src/controller/move-controller.ts +++ b/src/controller/move-controller.ts @@ -3,15 +3,19 @@ import { INVALID_MOVE } from "boardgame.io/core"; import { Move } from "boardgame.io"; import { GameState } from "../types/GameState"; import TileHex from "../model/Base/TileHex"; +import { getUnitFromId } from "../util/game-state"; -const movePlayer: Move = ( +const moveUnit: Move = ( { G, playerID }, + unitID: string, target: PartialCubeCoordinates ) => { - const player = G.players[+playerID]; - const dist = distance(TileHex.settings, player.position, target); + const unit = getUnitFromId(G.units, unitID); + if (unit == null) return INVALID_MOVE; + + const dist = distance(TileHex.settings, unit.position, target); if (dist > 1 || dist == 0) return INVALID_MOVE; - player.position = target; + unit.position = target; const currCoordinates = toCube(TileHex.settings, target); const targetCell = G.cells.filter((cell) => { @@ -22,7 +26,7 @@ const movePlayer: Move = ( })[0]; if (targetCell.cellNumber > 0) { - player.power += targetCell.cellNumber; + unit.power += targetCell.cellNumber; targetCell.cellNumber = 0; } }; @@ -30,7 +34,7 @@ const movePlayer: Move = ( class MoveController { static publish() { return { - movePlayer, + moveUnit, }; } } diff --git a/src/util/board.ts b/src/util/board.ts index 0baccb4..8ab3853 100644 --- a/src/util/board.ts +++ b/src/util/board.ts @@ -5,10 +5,9 @@ export const getObjectFromStateAndCoord = ( state: GameState, coordinate: PartialCubeCoordinates ) => { - const filtered = state.players.filter( - (player) => - player.position.q === coordinate.q && - player.position.r === coordinate.r + const filtered = state.units.filter( + (unit) => + unit.position.q === coordinate.q && unit.position.r === coordinate.r ); return filtered.length > 0 ? filtered[0] : null; diff --git a/src/util/game-state.ts b/src/util/game-state.ts new file mode 100644 index 0000000..3ebe03f --- /dev/null +++ b/src/util/game-state.ts @@ -0,0 +1,6 @@ +import { BaseUnit } from "../types/model/BaseUnit"; + +export const getUnitFromId = (units: BaseUnit[], unitID: string) => { + const filteredUnits = units.filter((unit) => unit.id == unitID); + return filteredUnits.length === 0 ? null : filteredUnits[0]; +}; From f2c3d2eb4df703c4689b956cef39644caaba4376 Mon Sep 17 00:00:00 2001 From: extremebip Date: Thu, 3 Apr 2025 23:39:18 +0700 Subject: [PATCH 26/44] Add unit re-renderer + logic for colors --- colors.json | 10 +-- notes.md | 22 +++++- src/App.ts | 111 ++++++++++++++---------------- src/Game.ts | 4 +- src/model/Unit.ts | 41 +++++------ src/model/builder/unit-builder.ts | 18 +++++ src/types/config/colors.d.ts | 4 +- 7 files changed, 123 insertions(+), 87 deletions(-) create mode 100644 src/model/builder/unit-builder.ts diff --git a/colors.json b/colors.json index 041b676..64f1e9f 100644 --- a/colors.json +++ b/colors.json @@ -3,14 +3,16 @@ "background": "#000", "primary": "#0d6efd" }, - "player": { + "unit": { "red": { "primary": "#c40000", - "active": "#ff6868" + "active": "#ff6868", + "disabled": "#ff686880" }, "blue": { - "primary": "#004aff", - "active": "#71afff" + "primary": "#4e90e6", + "active": "#71afff", + "disabled": "#71afff80" } } } diff --git a/notes.md b/notes.md index 26c6a4a..00d026d 100644 --- a/notes.md +++ b/notes.md @@ -5,4 +5,24 @@ Playground Roadmap 3. [x] Add player and movement. 4. [x] Add multiplayer and win condition (technical back-end). 5. [ ] Add multiple units for each player. -6. [ ] Push playground into main. +6. [ ] Add icon +7. [ ] Represent powers into HP bar (?) +8. [ ] Push playground into main. + +Multiple Units +Logic: +1. Select which unit to move + 1. Must be your own unit + 2. Must not do an action before +2. Mark the unit as selected in the client +3. Execute an action for the unit + 1. Move + 2. Fight +4. Mark the unit as "done" (already done an action) +5. Loop to (1) until all units have completed an action +6. End Turn, go to another player + +Low Level Logic: +1. Select unit to move + - Set unit ID to activeUnitID +2. diff --git a/src/App.ts b/src/App.ts index b536597..e52148b 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,6 +1,9 @@ import { Client } from "boardgame.io/client"; import { BattleSimulator } from "./Game"; -import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; +import { + ClientState as ServerState, + _ClientImpl, +} from "boardgame.io/dist/types/src/client/client"; import MapTile from "./model/MapTile"; import { Grid } from "honeycomb-grid"; import * as PIXI from "pixi.js"; @@ -9,6 +12,7 @@ import { GameState } from "./types/GameState"; import ClickHandler from "./client/ClickHandler"; import DebugPanel from "./client/DebugPanel"; import { ClientState } from "./types/ClientState"; +import UnitBuilder from "./model/builder/unit-builder"; class BattleSimulatorClient { client: _ClientImpl; @@ -36,7 +40,7 @@ class BattleSimulatorClient { // this.clientState = clientState; this.attachListeners(); - // this.client.subscribe((state) => this.update(state)); + this.client.subscribe((state) => this.update(state)); } createBoard() { @@ -58,11 +62,12 @@ class BattleSimulatorClient { return units.map((unit) => { const unitTile = Unit.create( + unit.id, 0, this.grid.getHex(unit.position)!, unit.playerID == "0" ? "blue" : "red" ); - pixiApp.stage.addChild(unitTile.render()); + pixiApp.stage.addChild(UnitBuilder.renderPrimary(unitTile)); return unitTile; }); } @@ -78,66 +83,56 @@ class BattleSimulatorClient { ); } - // if (state == null || tile === undefined) return; - - // const coordinate = { q: tile.q, r: tile.r }; - // const object = getObjectFromStateAndCoord(state.G, coordinate); - - // if (object == null) { - // this.client.moves.movePlayer(coordinate); - // return; - // } - - // if (object.id != state.ctx.currentPlayer) { - // this.client.moves.fight(object.id); - // } - // }); - // } - - // update(state: ClientState) { - // if (state === null) return; - // const newPlayers = []; - - // for (let i = 0; i < this.players.length; i++) { - // const player = this.players[i]; - // player.destroy(); - - // const playerState = state.G.players[i]; - // if (!playerState.isAlive) { - // continue; - // } - - // const newPlayerPosition = playerState.position; - // const tile = this.grid.getHex(newPlayerPosition)!; - // const newPlayer = Player.create( - // playerState.power, - // tile, - // i == 0 ? "blue" : "red" - // ); - // pixiApp.stage.addChild(newPlayer.render()); - - // tile.cellNumber = 0; - // tile.render(); - - // newPlayers.push(newPlayer); - // } - - // this.players = newPlayers; - - // if (state.ctx.gameover) { - // const textGameOverElement = document.querySelector("#game-over-text")!; - // textGameOverElement.textContent = - // state.ctx.gameover.winner !== undefined - // ? `Player ${state.ctx.gameover.winner} Win!` - // : "It's a Draw!"; - // } - // } + update(state: ServerState) { + if (state === null) return; + const newUnits = []; + + for (let i = 0; i < this.units.length; i++) { + const unit = this.units[i]; + unit.destroy(); + + const unitState = state.G.units[i]; + if (!unitState.isAlive) { + continue; + } + + const newUnitPosition = unitState.position; + const tile = this.grid.getHex(newUnitPosition)!; + const newUnit = Unit.create( + unitState.id, + unitState.power, + tile, + unitState.playerID == "0" ? "blue" : "red" + ); + + if (this.clientState.markedUnitIds.has(unitState.id)) { + pixiApp.stage.addChild(UnitBuilder.renderPrimary(newUnit)); + } else { + pixiApp.stage.addChild(UnitBuilder.renderDisabled(newUnit)); + } + + tile.cellNumber = 0; + tile.render(); + + newUnits.push(newUnit); + } + + this.units = newUnits; + + // if (state.ctx.gameover) { + // const textGameOverElement = document.querySelector("#game-over-text")!; + // textGameOverElement.textContent = + // state.ctx.gameover.winner !== undefined + // ? `Player ${state.ctx.gameover.winner} Win!` + // : "It's a Draw!"; + // } + } } const pixiApp = new PIXI.Application(); await pixiApp.init({ backgroundAlpha: 0 }); -document.body.appendChild(pixiApp.canvas); +document.querySelector("#game")!.appendChild(pixiApp.canvas); // Debug Only globalThis.__PIXI_APP__ = pixiApp; diff --git a/src/Game.ts b/src/Game.ts index d762bb6..9e8c9d0 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -20,8 +20,8 @@ export const BattleSimulator: Game = { minPlayers: 2, maxPlayers: 2, turn: { - minMoves: 1, - maxMoves: 1, + // minMoves: 1, + // maxMoves: 1, }, moves: { ...MoveController.publish(), diff --git a/src/model/Unit.ts b/src/model/Unit.ts index 6d0f481..5cf1145 100644 --- a/src/model/Unit.ts +++ b/src/model/Unit.ts @@ -1,29 +1,26 @@ import UnitHex from "./Base/UnitHex"; import MapTile from "./MapTile"; import * as PIXI from "pixi.js"; -import { player as playerColor } from "../../colors.json"; +import { unit as unitColor } from "../../colors.json"; import { PlayerTeamColor } from "../types/config/colors"; // class Player { class Unit extends UnitHex { + unitID!: string; power!: number; positionTile!: MapTile; colorKey!: PlayerTeamColor; graphic!: PIXI.Graphics; - // constructor(initialTile: MapTile, colorKey: string) { - // this.power = 0; - // this.positionTile = initialTile; - // this.colorKey = colorKey; - // } - // Temporary Create function for Hex Player static create( + unitID: string, power: number, initialTile: MapTile, colorKey: PlayerTeamColor ) { const hex = new Unit({ q: initialTile.q, r: initialTile.r }); + hex.unitID = unitID; hex.power = power; hex.positionTile = initialTile; hex.colorKey = colorKey; @@ -31,11 +28,7 @@ class Unit extends UnitHex { return hex; } - render() { - this.graphic.clear(); - - const color = playerColor[this.colorKey].active; - + drawPoly() { // Turns original hex corner into shrinked corner // => Linear Transformation - Scaling // https://gamemath.com/book/matrixtransforms.html @@ -45,24 +38,32 @@ class Unit extends UnitHex { y: (p.y - this.y) * scale + this.y, })); - this.graphic - .poly(newCorners) - .fill({ color }) - .stroke({ width: 1, color: "#999999" }); + this.graphic.poly(newCorners).stroke({ width: 1, color: "#999999" }); - this.graphic.removeChildren(); + return this; + } + fillColorStyle(style: "primary" | "active" | "disabled") { + const color = unitColor[this.colorKey][style]; + this.graphic.fill({ color }); + return this; + } + + addText() { const text = new PIXI.Text({ text: this.power }); text.x = this.x - text.width / 2; text.y = this.y - text.height / 2; this.graphic.addChild(text); - - return this.graphic; } - destroy() { + reset() { this.graphic.removeChildren(); this.graphic.clear(); + return this; + } + + destroy() { + this.reset(); this.graphic.destroy(); } } diff --git a/src/model/builder/unit-builder.ts b/src/model/builder/unit-builder.ts new file mode 100644 index 0000000..b0840c4 --- /dev/null +++ b/src/model/builder/unit-builder.ts @@ -0,0 +1,18 @@ +import Unit from "../Unit"; + +export default class UnitBuilder { + static renderPrimary(unit: Unit) { + unit.reset().drawPoly().fillColorStyle("primary").addText(); + return unit.graphic; + } + + static renderActive(unit: Unit) { + unit.reset().drawPoly().fillColorStyle("active").addText(); + return unit.graphic; + } + + static renderDisabled(unit: Unit) { + unit.reset().drawPoly().fillColorStyle("disabled").addText(); + return unit.graphic; + } +} diff --git a/src/types/config/colors.d.ts b/src/types/config/colors.d.ts index 685a6e3..58c3a64 100644 --- a/src/types/config/colors.d.ts +++ b/src/types/config/colors.d.ts @@ -1,4 +1,4 @@ import * as Config from "../../../colors.json"; -export type Player = typeof Config.player; -export type PlayerTeamColor = keyof typeof Config.player; +export type Player = typeof Config.unit; +export type PlayerTeamColor = keyof typeof Config.unit; From eb4853ca8cfe921ebb2f5b7530559bd2e05f1137 Mon Sep 17 00:00:00 2001 From: extremebip Date: Fri, 4 Apr 2025 00:26:34 +0700 Subject: [PATCH 27/44] Add end of turn button & event handling --- index.html | 1 + src/App.ts | 20 ++++++++++++++++++-- src/client/EndTurnHandler.ts | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/client/EndTurnHandler.ts diff --git a/index.html b/index.html index f99540b..6ff5e97 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,7 @@

+
diff --git a/src/App.ts b/src/App.ts index e52148b..e281990 100644 --- a/src/App.ts +++ b/src/App.ts @@ -13,6 +13,7 @@ import ClickHandler from "./client/ClickHandler"; import DebugPanel from "./client/DebugPanel"; import { ClientState } from "./types/ClientState"; import UnitBuilder from "./model/builder/unit-builder"; +import EndTurnHandler from "./client/EndTurnHandler"; class BattleSimulatorClient { client: _ClientImpl; @@ -78,9 +79,22 @@ class BattleSimulatorClient { this.grid, this.clientState ); - document.addEventListener("click", ({ offsetX, offsetY }) => + + this.pixiApp.canvas.addEventListener("click", ({ offsetX, offsetY }) => clickHandler.handle(offsetX, offsetY) ); + + const endTurnHandler = new EndTurnHandler(this.client, this.clientState); + + document + .querySelector("#end-turn-button") + ?.addEventListener("click", () => { + const confirm = window.confirm( + "Are you sure you want to end the turn?" + ); + + if (confirm) endTurnHandler.handle(); + }); } update(state: ServerState) { @@ -132,7 +146,9 @@ class BattleSimulatorClient { const pixiApp = new PIXI.Application(); await pixiApp.init({ backgroundAlpha: 0 }); -document.querySelector("#game")!.appendChild(pixiApp.canvas); +document + .querySelector("#game")! + .insertBefore(pixiApp.canvas, document.querySelector("#end-turn-button")); // Debug Only globalThis.__PIXI_APP__ = pixiApp; diff --git a/src/client/EndTurnHandler.ts b/src/client/EndTurnHandler.ts new file mode 100644 index 0000000..9d894d3 --- /dev/null +++ b/src/client/EndTurnHandler.ts @@ -0,0 +1,18 @@ +import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; +import { GameState } from "../types/GameState"; +import { ClientState } from "../types/ClientState"; + +export default class EndTurnHandler { + gameClient: _ClientImpl; + clientState: ClientState; + + constructor(gameClient: _ClientImpl, clientState: ClientState) { + this.gameClient = gameClient; + this.clientState = clientState; + } + + handle() { + this.gameClient.events.endTurn?.(); + this.clientState.markedUnitIds.clear(); + } +} From 25611849b8e58d9f5a285112fb626b42c9e0d1f4 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 5 Apr 2025 23:36:52 +0700 Subject: [PATCH 28/44] Add multi unit fighting mechanic --- src/client/ClickHandler.ts | 30 +++++++++++++++++++++++++----- src/controller/fight-controller.ts | 30 +++++++++++++++++++++--------- src/temp/board-mocker.ts | 2 +- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/client/ClickHandler.ts b/src/client/ClickHandler.ts index 0c0bc4a..281d917 100644 --- a/src/client/ClickHandler.ts +++ b/src/client/ClickHandler.ts @@ -46,18 +46,20 @@ export default class ClickHandler { return; } + let isActionSuccessful = true; if (target == null) { const res = this.gameClient.moves.moveUnit( this.clientState.selectedUnit.id, coordinate ); + } else { + isActionSuccessful = this.handleFighting(target); } - // else if (target.id != state.ctx.currentPlayer) { - // this.gameClient.moves.fight(target.id); - // } - this.clientState.markedUnitIds.add(this.clientState.selectedUnit.id); - this.clientState.selectedUnit = null; + if (isActionSuccessful) { + this.clientState.markedUnitIds.add(this.clientState.selectedUnit.id); + this.clientState.selectedUnit = null; + } } resolveSelection(currentPlayerId: string, object: BaseUnit | null) { @@ -74,4 +76,22 @@ export default class ClickHandler { this.clientState.selectedUnit = object; } + + handleFighting(target: BaseUnit) { + const selectedUnit = this.clientState.selectedUnit; + if (selectedUnit === null) { + alert("No unit is being selected!"); + return false; + } + + if (selectedUnit.playerID === target.playerID) { + // Validation may be removed in the future + alert("You cannot attack your own unit!"); + return false; + } + + this.gameClient.moves.fight(selectedUnit.id, target.id); + + return true; + } } diff --git a/src/controller/fight-controller.ts b/src/controller/fight-controller.ts index b138dab..f3b9b5c 100644 --- a/src/controller/fight-controller.ts +++ b/src/controller/fight-controller.ts @@ -3,27 +3,39 @@ import { GameState } from "../types/GameState"; import TileHex from "../model/Base/TileHex"; import { distance } from "honeycomb-grid"; import { INVALID_MOVE } from "boardgame.io/core"; +import { getUnitFromId } from "../util/game-state"; + +const fight: Move = ( + { G }, + selectedUnitID: string, + targetUnitID: string +) => { + const selectedUnit = getUnitFromId(G.units, selectedUnitID); + const targetUnit = getUnitFromId(G.units, targetUnitID); + if (selectedUnit == null || targetUnit == null) return INVALID_MOVE; -const fight: Move = ({ G, playerID }, targetPlayerId: string) => { - const currentPlayer = G.players[+playerID]; - const targetPlayer = G.players[+targetPlayerId]; const dist = distance( TileHex.settings, - currentPlayer.position, - targetPlayer.position + selectedUnit.position, + targetUnit.position ); if (dist > 1) return INVALID_MOVE; const high = - currentPlayer.power > targetPlayer.power ? currentPlayer : targetPlayer; + selectedUnit.power > targetUnit.power ? selectedUnit : targetUnit; const low = - currentPlayer.power < targetPlayer.power ? currentPlayer : targetPlayer; + selectedUnit.power > targetUnit.power ? targetUnit : selectedUnit; high.power -= low.power; low.power = 0; - if (high.power === 0) high.isAlive = false; - if (low.power === 0) low.isAlive = false; + if (high.power === 0) { + high.isAlive = false; + G.unitCountByPlayer[high.playerID]--; + } + + low.isAlive = false; + G.unitCountByPlayer[low.playerID]--; }; class FightController { diff --git a/src/temp/board-mocker.ts b/src/temp/board-mocker.ts index db2721e..60ccd48 100644 --- a/src/temp/board-mocker.ts +++ b/src/temp/board-mocker.ts @@ -31,7 +31,7 @@ export const mockUnits = ( mockUnits.push({ id: i.toString(), playerID: (i % 2).toString(), - power: 0, + power: 1, position: availablePositions.splice(randomTileIdx, 1)[0].coordinates, isAlive: true, }); From 0efa4df2fbb9175b4708ae0ee6e0fcf0f279fe27 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 5 Apr 2025 23:48:58 +0700 Subject: [PATCH 29/44] Refactor App units from array to map --- src/App.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/App.ts b/src/App.ts index e281990..24cb2d2 100644 --- a/src/App.ts +++ b/src/App.ts @@ -14,12 +14,13 @@ import DebugPanel from "./client/DebugPanel"; import { ClientState } from "./types/ClientState"; import UnitBuilder from "./model/builder/unit-builder"; import EndTurnHandler from "./client/EndTurnHandler"; +import { getUnitFromId } from "./util/game-state"; class BattleSimulatorClient { client: _ClientImpl; pixiApp: PIXI.Application; grid: Grid; - units: Unit[]; + units: Map; clientState: ClientState; constructor(pixiApp: PIXI.Application) { @@ -59,9 +60,10 @@ class BattleSimulatorClient { createUnits() { const initialState = this.client.getInitialState(); - const units = initialState.G.units; + const gameUnits = initialState.G.units; + const units = new Map(); - return units.map((unit) => { + gameUnits.forEach((unit) => { const unitTile = Unit.create( unit.id, 0, @@ -69,8 +71,10 @@ class BattleSimulatorClient { unit.playerID == "0" ? "blue" : "red" ); pixiApp.stage.addChild(UnitBuilder.renderPrimary(unitTile)); - return unitTile; + units.set(unit.id, unitTile); }); + + return units; } attachListeners() { @@ -99,14 +103,15 @@ class BattleSimulatorClient { update(state: ServerState) { if (state === null) return; - const newUnits = []; + const renderedUnitIDs = this.units.keys(); - for (let i = 0; i < this.units.length; i++) { - const unit = this.units[i]; + for (const unitID of renderedUnitIDs) { + const unit = this.units.get(unitID)!; unit.destroy(); - const unitState = state.G.units[i]; - if (!unitState.isAlive) { + const unitState = getUnitFromId(state.G.units, unitID); + if (unitState == null || !unitState.isAlive) { + this.units.delete(unitID); continue; } @@ -128,11 +133,9 @@ class BattleSimulatorClient { tile.cellNumber = 0; tile.render(); - newUnits.push(newUnit); + this.units.set(unitID, newUnit); } - this.units = newUnits; - // if (state.ctx.gameover) { // const textGameOverElement = document.querySelector("#game-over-text")!; // textGameOverElement.textContent = From 2c61634721d228c20c06e536a2075dbbeb3b7cc8 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 5 Apr 2025 23:58:54 +0700 Subject: [PATCH 30/44] Refactor selection & movement over dead unit --- src/client/ClickHandler.ts | 43 +++++++++++++++++++++++++------------- src/util/board.ts | 4 ++-- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/client/ClickHandler.ts b/src/client/ClickHandler.ts index 281d917..b5f7e96 100644 --- a/src/client/ClickHandler.ts +++ b/src/client/ClickHandler.ts @@ -1,8 +1,8 @@ import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; import MapTile from "../model/MapTile"; -import { Grid } from "honeycomb-grid"; +import { Grid, PartialCubeCoordinates } from "honeycomb-grid"; import { GameState } from "../types/GameState"; -import { getObjectFromStateAndCoord } from "../util/board"; +import { getUnitsFromStateAndCoord } from "../util/board"; import { ClientState } from "../types/ClientState"; import { BaseUnit } from "../types/model/BaseUnit"; @@ -39,19 +39,18 @@ export default class ClickHandler { if (state == null) return; const coordinate = { q: tile.q, r: tile.r }; - const target = getObjectFromStateAndCoord(state.G, coordinate); + const unitsOnCoord = getUnitsFromStateAndCoord(state.G, coordinate); if (this.clientState.selectedUnit == null) { - this.resolveSelection(state.ctx.currentPlayer, target); + this.resolveSelection(state.ctx.currentPlayer, unitsOnCoord); return; } + const target = unitsOnCoord.filter((unit) => unit.isAlive).shift(); + let isActionSuccessful = true; - if (target == null) { - const res = this.gameClient.moves.moveUnit( - this.clientState.selectedUnit.id, - coordinate - ); + if (target == null || !target.isAlive) { + isActionSuccessful = this.handleMovement(coordinate); } else { isActionSuccessful = this.handleFighting(target); } @@ -62,19 +61,35 @@ export default class ClickHandler { } } - resolveSelection(currentPlayerId: string, object: BaseUnit | null) { - if (object == null) return; - if (object.playerID != currentPlayerId) { + resolveSelection(currentPlayerId: string, unitsOnCoord: BaseUnit[]) { + const firstLivingUnit = unitsOnCoord + .filter((unit) => unit.isAlive) + .shift(); + + if (firstLivingUnit == null) return; + if (firstLivingUnit.playerID != currentPlayerId) { alert("Please select your own unit!"); return; } - if (this.clientState.markedUnitIds.has(object.id)) { + if (this.clientState.markedUnitIds.has(firstLivingUnit.id)) { alert("This unit has done an action, choose another unit!"); return; } - this.clientState.selectedUnit = object; + this.clientState.selectedUnit = firstLivingUnit; + } + + handleMovement(coordinate: PartialCubeCoordinates) { + const selectedUnit = this.clientState.selectedUnit; + if (selectedUnit === null) { + alert("No unit is being selected!"); + return false; + } + + this.gameClient.moves.moveUnit(selectedUnit.id, coordinate); + + return true; } handleFighting(target: BaseUnit) { diff --git a/src/util/board.ts b/src/util/board.ts index 8ab3853..a6ea361 100644 --- a/src/util/board.ts +++ b/src/util/board.ts @@ -1,7 +1,7 @@ import { PartialCubeCoordinates } from "honeycomb-grid"; import { GameState } from "../types/GameState"; -export const getObjectFromStateAndCoord = ( +export const getUnitsFromStateAndCoord = ( state: GameState, coordinate: PartialCubeCoordinates ) => { @@ -10,5 +10,5 @@ export const getObjectFromStateAndCoord = ( unit.position.q === coordinate.q && unit.position.r === coordinate.r ); - return filtered.length > 0 ? filtered[0] : null; + return filtered; }; From eecbe6de7f31e829612af2c86c0037c3dca24bf9 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 5 Apr 2025 23:59:56 +0700 Subject: [PATCH 31/44] Re-enable game over message --- src/App.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/App.ts b/src/App.ts index 24cb2d2..e45e6e3 100644 --- a/src/App.ts +++ b/src/App.ts @@ -136,13 +136,13 @@ class BattleSimulatorClient { this.units.set(unitID, newUnit); } - // if (state.ctx.gameover) { - // const textGameOverElement = document.querySelector("#game-over-text")!; - // textGameOverElement.textContent = - // state.ctx.gameover.winner !== undefined - // ? `Player ${state.ctx.gameover.winner} Win!` - // : "It's a Draw!"; - // } + if (state.ctx.gameover) { + const textGameOverElement = document.querySelector("#game-over-text")!; + textGameOverElement.textContent = + state.ctx.gameover.winner !== undefined + ? `Player ${state.ctx.gameover.winner} Win!` + : "It's a Draw!"; + } } } From 69a4235b8d2867464a20346fc047470560a237a1 Mon Sep 17 00:00:00 2001 From: extremebip Date: Tue, 8 Apr 2025 23:49:25 +0700 Subject: [PATCH 32/44] Init renderer class --- gameConfig.ts | 1 + src/Renderer.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/Renderer.ts diff --git a/gameConfig.ts b/gameConfig.ts index 751cd05..e06065f 100644 --- a/gameConfig.ts +++ b/gameConfig.ts @@ -6,4 +6,5 @@ export default { origin: "topLeft", orientation: Orientation.FLAT, }, + fps: 30, } as const; diff --git a/src/Renderer.ts b/src/Renderer.ts new file mode 100644 index 0000000..bdfa1fb --- /dev/null +++ b/src/Renderer.ts @@ -0,0 +1,30 @@ +import gameConfig from "../gameConfig"; +import type { Application } from "pixi.js"; + +class Renderer { + events: any[]; + pixiApp: Application; + + constructor(app: Application) { + this.pixiApp = app; + this.events = []; + + let elapsed = 0.0; + this.pixiApp.ticker.add((delta) => { + const now = new Date().getTime(); + const diff = now - elapsed; + const tickLimit = 1000 / gameConfig.fps; + if (diff < tickLimit) return; + + elapsed = now; + }); + } + + processEvents() { + // TODO + } + + addEvent(event: any) { + this.events.push(event); + } +} From ac6db4b0003d25f2e9bdcfd15a07e6773c055db7 Mon Sep 17 00:00:00 2001 From: Phobez <36761666+Phobez@users.noreply.github.com> Date: Thu, 10 Apr 2025 20:10:00 +0700 Subject: [PATCH 33/44] Finish Renderer.ts --- src/Renderer.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Renderer.ts b/src/Renderer.ts index bdfa1fb..c80914a 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -9,19 +9,27 @@ class Renderer { this.pixiApp = app; this.events = []; - let elapsed = 0.0; - this.pixiApp.ticker.add((delta) => { - const now = new Date().getTime(); - const diff = now - elapsed; + let lastTick = performance.now(); + + this.pixiApp.ticker.add((delta: number) => { + const now = performance.now(); const tickLimit = 1000 / gameConfig.fps; - if (diff < tickLimit) return; - elapsed = now; + if (now - lastTick >= tickLimit) { + this.processEvents(delta); + + lastTick = now; + } }); } - processEvents() { - // TODO + processEvents(delta: number) { + for (const event of this.events) { + // TODO: process event + // Maybe make event types? + } + + this.events = []; } addEvent(event: any) { From d3fb640cd6557f75bd25f30b82885cd97bc42e03 Mon Sep 17 00:00:00 2001 From: Phobez <36761666+Phobez@users.noreply.github.com> Date: Fri, 11 Apr 2025 08:00:55 +0700 Subject: [PATCH 34/44] Connect App and Renderer --- src/App.ts | 3 +++ src/Renderer.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/App.ts b/src/App.ts index e45e6e3..a59adad 100644 --- a/src/App.ts +++ b/src/App.ts @@ -15,6 +15,7 @@ import { ClientState } from "./types/ClientState"; import UnitBuilder from "./model/builder/unit-builder"; import EndTurnHandler from "./client/EndTurnHandler"; import { getUnitFromId } from "./util/game-state"; +import Renderer from "./Renderer" class BattleSimulatorClient { client: _ClientImpl; @@ -22,11 +23,13 @@ class BattleSimulatorClient { grid: Grid; units: Map; clientState: ClientState; + renderer: Renderer; constructor(pixiApp: PIXI.Application) { this.client = Client({ game: BattleSimulator }); this.client.start(); this.pixiApp = pixiApp; + this.renderer = new Renderer(pixiApp); this.grid = this.createBoard(); this.units = this.createUnits(); const clientState = { diff --git a/src/Renderer.ts b/src/Renderer.ts index c80914a..9290065 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -29,6 +29,8 @@ class Renderer { // Maybe make event types? } + console.log("Process events called!") + this.events = []; } @@ -36,3 +38,5 @@ class Renderer { this.events.push(event); } } + +export default Renderer; \ No newline at end of file From a889aba3ac1ee7fd5a08bc18f099c9a2080bc4af Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 12 Apr 2025 20:55:43 +0700 Subject: [PATCH 35/44] Add event, handler types + base render classes --- src/Renderer.ts | 9 +++-- src/render/model/base/unit-hex.ts | 7 ++++ src/render/model/entity.ts | 46 +++++++++++++++++++++++ src/render/model/unit/unit.ts | 50 +++++++++++++++++++++++++ src/types/model/base/event.d.ts | 62 +++++++++++++++++++++++++++++++ 5 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 src/render/model/base/unit-hex.ts create mode 100644 src/render/model/entity.ts create mode 100644 src/render/model/unit/unit.ts create mode 100644 src/types/model/base/event.d.ts diff --git a/src/Renderer.ts b/src/Renderer.ts index 9290065..2e1ae74 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -1,8 +1,9 @@ import gameConfig from "../gameConfig"; import type { Application } from "pixi.js"; +import { Event, RenderEventParamMap } from "./types/model/base/event"; class Renderer { - events: any[]; + events: Event[]; pixiApp: Application; constructor(app: Application) { @@ -29,14 +30,14 @@ class Renderer { // Maybe make event types? } - console.log("Process events called!") + console.log("Process events called!"); this.events = []; } - addEvent(event: any) { + addEvent(event: Event) { this.events.push(event); } } -export default Renderer; \ No newline at end of file +export default Renderer; diff --git a/src/render/model/base/unit-hex.ts b/src/render/model/base/unit-hex.ts new file mode 100644 index 0000000..8f22a32 --- /dev/null +++ b/src/render/model/base/unit-hex.ts @@ -0,0 +1,7 @@ +import { defineHex } from "honeycomb-grid"; +import gameConfig from "../../../../gameConfig"; + +export default class UnitHex extends defineHex({ + ...gameConfig.hex, + dimensions: 30, +}) {} diff --git a/src/render/model/entity.ts b/src/render/model/entity.ts new file mode 100644 index 0000000..f2de10c --- /dev/null +++ b/src/render/model/entity.ts @@ -0,0 +1,46 @@ +import { Graphics } from "pixi.js"; +import { + EntityEventParamMap, + RenderEventHandler, +} from "../../types/model/base/event"; + +export default abstract class Entity implements RenderEventHandler { + id: string; + graphic: Graphics; + state: T; + eventHandlers: Record void>; + + constructor(id: string, state: T) { + this.id = id; + this.graphic = new Graphics(); + this.state = state; + this.eventHandlers = {}; + } + + reset() { + this.graphic.removeChildren(); + this.graphic.clear(); + return this; + } + + destroy() { + this.reset(); + this.graphic.destroy(); + } + + abstract addSubscribers(): void; + + addSubscriber( + event: E, + handler: (parameter: EntityEventParamMap[E]) => void + ): void { + this.eventHandlers[event] = handler; + } + + consume( + event: E, + parameter: EntityEventParamMap[E] + ) { + this.eventHandlers[event](parameter); + } +} diff --git a/src/render/model/unit/unit.ts b/src/render/model/unit/unit.ts new file mode 100644 index 0000000..339c74b --- /dev/null +++ b/src/render/model/unit/unit.ts @@ -0,0 +1,50 @@ +import { Hex } from "honeycomb-grid"; +import Entity from "../entity"; +import UnitHex from "../base/unit-hex"; +import { BaseUnit } from "../../../types/model/BaseUnit"; +import { unit as unitColor } from "../../../../colors.json"; +import { Text } from "pixi.js"; + +export default class Unit extends Entity { + hex: Hex; + isSelected: boolean; + + constructor(id: string, gameState: BaseUnit) { + super(id, gameState); + this.hex = new UnitHex(gameState.position); + this.isSelected = false; + } + + drawBase() { + // Turns original hex corner into shrinked corner + // => Linear Transformation - Scaling + // https://gamemath.com/book/matrixtransforms.html + const scale = 4 / 5; + const newCorners = this.hex.corners.map((p) => ({ + x: (p.x - this.hex.x) * scale + this.hex.x, + y: (p.y - this.hex.y) * scale + this.hex.y, + })); + + this.graphic.poly(newCorners).stroke({ width: 1, color: "#999999" }); + + return this; + } + + fill(style: "primary" | "active" | "disabled") { + const colorKey = this.state.playerID === "0" ? "blue" : "red"; + const color = unitColor[colorKey][style]; + this.graphic.fill({ color }); + return this; + } + + displayPower() { + const text = new Text({ text: this.state.power }); + text.x = this.hex.x - text.width / 2; + text.y = this.hex.y - text.height / 2; + this.graphic.addChild(text); + } + + addSubscribers() { + // this.addSubscriber("selected", (param) => {}); + } +} diff --git a/src/types/model/base/event.d.ts b/src/types/model/base/event.d.ts new file mode 100644 index 0000000..62e5a4b --- /dev/null +++ b/src/types/model/base/event.d.ts @@ -0,0 +1,62 @@ +import { PartialCubeCoordinates } from "honeycomb-grid"; + +export interface EntityEventParamMap { + selected: boolean; + move: PartialCubeCoordinates; + damaged: number; + die: void; +} + +export interface RenderEventParamMap extends EntityEventParamMap {} + +// export interface RenderEvent { +// object_id: string; +// name: K; +// parameter: RenderEventParamMap[K]; +// } + +// export interface EventHandlers { +// [event: E]: (parameter: RenderEventParamMap[E]) => void; +// } + +// type Test = Record; + +export interface RenderEventHandler { + eventHandlers: Record void>; // Nuclear option + // eventHandlers: { + // [E in RenderEventParamMap]: (parameter: RenderEventParamMap[E]) => void; + // }; + // eventHandlers: Record< + // Partial, + // (parameter: RenderEventParamMap[keyof RenderEventParamMap]) => void + // >; + // eventHandlers: EventHandlers; + // eventHandlers: Map< + // keyof RenderEventParamMap, + // (parameter: RenderEventParamMap[keyof RenderEventParamMap]) => void + // >; + // consume( + // event: E, + // parameter: RenderEventParamMap[E] + // ): void; + // process( + // event: E, + // parameter: EntityEventParamMap[E], + // handler: (event: E, parameter: RenderEventParamMap[E]) => void + // ): void; + addSubscribers(): void; + addSubscriber( + event: E, + handler: (parameter: EntityEventParamMap[E]) => void + ): void; + consume( + event: E, + parameter: EntityEventParamMap[E] + ); +} + +export interface Event { + object_id: string; + event: E; + parameter: RenderEventParamMap[E]; +} From 0a7302308bda1eeec9445d20d3b8f01e97c7ba1d Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 12 Apr 2025 21:08:08 +0700 Subject: [PATCH 36/44] Add missing this to unit displayPower() --- src/render/model/unit/unit.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/render/model/unit/unit.ts b/src/render/model/unit/unit.ts index 339c74b..41cd2ea 100644 --- a/src/render/model/unit/unit.ts +++ b/src/render/model/unit/unit.ts @@ -42,6 +42,7 @@ export default class Unit extends Entity { text.x = this.hex.x - text.width / 2; text.y = this.hex.y - text.height / 2; this.graphic.addChild(text); + return this; } addSubscribers() { From 219be1c8d2b15822ef36092459ac0290513807e6 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 12 Apr 2025 22:53:20 +0700 Subject: [PATCH 37/44] Add addSubscribers calling from entity constructor --- src/render/model/entity.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/render/model/entity.ts b/src/render/model/entity.ts index f2de10c..4b051f2 100644 --- a/src/render/model/entity.ts +++ b/src/render/model/entity.ts @@ -15,6 +15,7 @@ export default abstract class Entity implements RenderEventHandler { this.graphic = new Graphics(); this.state = state; this.eventHandlers = {}; + this.addSubscribers(); } reset() { From 2016c4dae6c9c969bbdfaee44c527d33e7c02edf Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 12 Apr 2025 22:53:56 +0700 Subject: [PATCH 38/44] Add Map tile class --- src/render/model/base/map-tile-hex.ts | 4 ++ src/render/model/map/tile.ts | 75 +++++++++++++++++++++++++++ src/types/model/base/event.d.ts | 20 +++---- 3 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 src/render/model/base/map-tile-hex.ts create mode 100644 src/render/model/map/tile.ts diff --git a/src/render/model/base/map-tile-hex.ts b/src/render/model/base/map-tile-hex.ts new file mode 100644 index 0000000..eb51154 --- /dev/null +++ b/src/render/model/base/map-tile-hex.ts @@ -0,0 +1,4 @@ +import { defineHex } from "honeycomb-grid"; +import gameConfig from "../../../../gameConfig"; + +export default class MapTileHex extends defineHex(gameConfig.hex) {} diff --git a/src/render/model/map/tile.ts b/src/render/model/map/tile.ts new file mode 100644 index 0000000..bba5e61 --- /dev/null +++ b/src/render/model/map/tile.ts @@ -0,0 +1,75 @@ +import { Hex, PartialCubeCoordinates } from "honeycomb-grid"; +import { grid as gridColor } from "../../../../colors.json"; +import MapTileHex from "../base/map-tile-hex"; +import { Graphics, Text } from "pixi.js"; +import { + EntityEventParamMap, + RenderEventHandler, +} from "../../../types/model/base/event"; + +export default class Tile implements RenderEventHandler { + hex: Hex; + cellNumber: number; + graphic: Graphics; + eventHandlers: Record void>; + + constructor(coordinates: PartialCubeCoordinates, cellNumber: number) { + this.hex = new MapTileHex(coordinates); + this.cellNumber = cellNumber; + this.graphic = new Graphics(); + this.eventHandlers = {}; + this.addSubscribers(); + } + + drawBase() { + let cellColor = gridColor.background; + if (this.cellNumber > 0) { + cellColor = gridColor.primary; + } + + this.graphic + .poly(this.hex.corners) + .fill({ color: cellColor }) + .stroke({ width: 1, color: "#999999" }); + + return this; + } + + displayCellNumber() { + if (this.cellNumber > 0) { + const text = new Text({ text: this.cellNumber }); + text.x = this.hex.x - text.width / 2; + text.y = this.hex.y - text.height / 2; + this.graphic.addChild(text); + } + + return this; + } + + reset() { + this.graphic.removeChildren(); + this.graphic.clear(); + return this; + } + + destroy() { + this.reset(); + this.graphic.destroy(); + } + + addSubscribers(): void {} + + addSubscriber( + event: E, + handler: (parameter: EntityEventParamMap[E]) => void + ): void { + this.eventHandlers[event] = handler; + } + + consume( + event: E, + parameter: EntityEventParamMap[E] + ) { + this.eventHandlers[event](parameter); + } +} diff --git a/src/types/model/base/event.d.ts b/src/types/model/base/event.d.ts index 62e5a4b..c30a929 100644 --- a/src/types/model/base/event.d.ts +++ b/src/types/model/base/event.d.ts @@ -7,7 +7,11 @@ export interface EntityEventParamMap { die: void; } -export interface RenderEventParamMap extends EntityEventParamMap {} +export interface MapTileEventParamMap {} + +export interface RenderEventParamMap + extends EntityEventParamMap, + MapTileEventParamMap {} // export interface RenderEvent { // object_id: string; @@ -19,10 +23,10 @@ export interface RenderEventParamMap extends EntityEventParamMap {} // [event: E]: (parameter: RenderEventParamMap[E]) => void; // } -// type Test = Record; - export interface RenderEventHandler { eventHandlers: Record void>; // Nuclear option + + // NONE OF THIS SHIT WORKS // eventHandlers: { // [E in RenderEventParamMap]: (parameter: RenderEventParamMap[E]) => void; // }; @@ -35,15 +39,7 @@ export interface RenderEventHandler { // keyof RenderEventParamMap, // (parameter: RenderEventParamMap[keyof RenderEventParamMap]) => void // >; - // consume( - // event: E, - // parameter: RenderEventParamMap[E] - // ): void; - // process( - // event: E, - // parameter: EntityEventParamMap[E], - // handler: (event: E, parameter: RenderEventParamMap[E]) => void - // ): void; + addSubscribers(): void; addSubscriber( event: E, From e0a4c8a90156eff3834a96d6d6fc769624f63f21 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 12 Apr 2025 23:29:18 +0700 Subject: [PATCH 39/44] Move main App into bak --- src/App.bak.ts | 146 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 40 deletions(-) diff --git a/src/App.bak.ts b/src/App.bak.ts index 7049c53..a8cf5f4 100644 --- a/src/App.bak.ts +++ b/src/App.bak.ts @@ -1,26 +1,48 @@ import { Client } from "boardgame.io/client"; -import { BattleSimulator, GameState } from "./Game"; +import { BattleSimulator } from "./Game"; import { - ClientState, + ClientState as ServerState, _ClientImpl, } from "boardgame.io/dist/types/src/client/client"; import MapTile from "./model/MapTile"; import { Grid } from "honeycomb-grid"; import * as PIXI from "pixi.js"; -import Player from "./model/Player"; +import Unit from "./model/Unit"; +import { GameState } from "./types/GameState"; +import ClickHandler from "./client/ClickHandler"; +import DebugPanel from "./client/DebugPanel"; +import { ClientState } from "./types/ClientState"; +import UnitBuilder from "./model/builder/unit-builder"; +import EndTurnHandler from "./client/EndTurnHandler"; +import { getUnitFromId } from "./util/game-state"; +import Renderer from "./Renderer"; class BattleSimulatorClient { client: _ClientImpl; pixiApp: PIXI.Application; grid: Grid; - player: Player; + units: Map; + clientState: ClientState; + renderer: Renderer; constructor(pixiApp: PIXI.Application) { this.client = Client({ game: BattleSimulator }); this.client.start(); this.pixiApp = pixiApp; + this.renderer = new Renderer(pixiApp); this.grid = this.createBoard(); - this.player = this.createPlayer(); + this.units = this.createUnits(); + const clientState = { + selectedUnit: null, + markedUnitIds: new Set(), + }; + + // FOR DEV & DEBUG + const debugPanel = new DebugPanel("#debug-panel"); + this.clientState = debugPanel.watch(clientState); + + // FOR PRODUCTION + // this.clientState = clientState; this.attachListeners(); this.client.subscribe((state) => this.update(state)); @@ -39,56 +61,100 @@ class BattleSimulatorClient { return grid; } - createPlayer() { + createUnits() { const initialState = this.client.getInitialState(); - const playerPos = initialState.G.player.position; + const gameUnits = initialState.G.units; + const units = new Map(); + + gameUnits.forEach((unit) => { + const unitTile = Unit.create( + unit.id, + 0, + this.grid.getHex(unit.position)!, + unit.playerID == "0" ? "blue" : "red" + ); + pixiApp.stage.addChild(UnitBuilder.renderPrimary(unitTile)); + units.set(unit.id, unitTile); + }); - // const player = new Player(playerPos, "blue"); - const player = Player.create(0, this.grid.getHex(playerPos)!, "blue"); - pixiApp.stage.addChild(player.render()); - return player; + return units; } attachListeners() { - document.addEventListener("click", ({ offsetX, offsetY }) => { - const tile = this.grid.pointToHex( - { x: offsetX, y: offsetY }, - { allowOutside: false } - ); + const clickHandler = new ClickHandler( + this.client, + this.grid, + this.clientState + ); - if (tile !== undefined) { - this.client.moves.movePlayer({ q: tile.q, r: tile.r }); - } - }); + this.pixiApp.canvas.addEventListener("click", ({ offsetX, offsetY }) => + clickHandler.handle(offsetX, offsetY) + ); + + const endTurnHandler = new EndTurnHandler(this.client, this.clientState); + + document + .querySelector("#end-turn-button") + ?.addEventListener("click", () => { + const confirm = window.confirm( + "Are you sure you want to end the turn?" + ); + + if (confirm) endTurnHandler.handle(); + }); } - update(state: ClientState) { + update(state: ServerState) { if (state === null) return; - this.player.destroy(); - - const newPlayerPosition = state.G.player.position; - const tile = this.grid.getHex(newPlayerPosition)!; - const player = Player.create(state.G.player.power, tile, "blue"); - pixiApp.stage.addChild(player.render()); - this.player = player; - - tile.cellNumber = 0; - tile.render(); - // console.log("Before: ", { q: this.player.q, r: this.player.r }); - // console.log("After: ", newPlayerPosition); - // const cubePosition = toCube(TileHex.settings, newPlayerPosition); - // const tilePosition = this.grid.getHex(newPlayerPosition); - // this.player = this.player.translate({ - // q: cubePosition.q - this.player.q, - // r: cubePosition.r - this.player.r, - // }); + const renderedUnitIDs = this.units.keys(); + + for (const unitID of renderedUnitIDs) { + const unit = this.units.get(unitID)!; + unit.destroy(); + + const unitState = getUnitFromId(state.G.units, unitID); + if (unitState == null || !unitState.isAlive) { + this.units.delete(unitID); + continue; + } + + const newUnitPosition = unitState.position; + const tile = this.grid.getHex(newUnitPosition)!; + const newUnit = Unit.create( + unitState.id, + unitState.power, + tile, + unitState.playerID == "0" ? "blue" : "red" + ); + + if (this.clientState.markedUnitIds.has(unitState.id)) { + pixiApp.stage.addChild(UnitBuilder.renderPrimary(newUnit)); + } else { + pixiApp.stage.addChild(UnitBuilder.renderDisabled(newUnit)); + } + + tile.cellNumber = 0; + tile.render(); + + this.units.set(unitID, newUnit); + } + + if (state.ctx.gameover) { + const textGameOverElement = document.querySelector("#game-over-text")!; + textGameOverElement.textContent = + state.ctx.gameover.winner !== undefined + ? `Player ${state.ctx.gameover.winner} Win!` + : "It's a Draw!"; + } } } const pixiApp = new PIXI.Application(); await pixiApp.init({ backgroundAlpha: 0 }); -document.body.appendChild(pixiApp.canvas); +document + .querySelector("#game")! + .insertBefore(pixiApp.canvas, document.querySelector("#end-turn-button")); // Debug Only globalThis.__PIXI_APP__ = pixiApp; From fe88fd479fdcabebb36e8564960d001ae8c8496d Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 12 Apr 2025 23:30:20 +0700 Subject: [PATCH 40/44] Add initial map generator --- src/App.ts | 128 ++++-------------------------------- src/render/map-generator.ts | 30 +++++++++ 2 files changed, 43 insertions(+), 115 deletions(-) create mode 100644 src/render/map-generator.ts diff --git a/src/App.ts b/src/App.ts index a59adad..6b29c23 100644 --- a/src/App.ts +++ b/src/App.ts @@ -4,34 +4,35 @@ import { ClientState as ServerState, _ClientImpl, } from "boardgame.io/dist/types/src/client/client"; -import MapTile from "./model/MapTile"; -import { Grid } from "honeycomb-grid"; +import { Grid, Hex } from "honeycomb-grid"; import * as PIXI from "pixi.js"; -import Unit from "./model/Unit"; import { GameState } from "./types/GameState"; -import ClickHandler from "./client/ClickHandler"; import DebugPanel from "./client/DebugPanel"; import { ClientState } from "./types/ClientState"; -import UnitBuilder from "./model/builder/unit-builder"; -import EndTurnHandler from "./client/EndTurnHandler"; -import { getUnitFromId } from "./util/game-state"; -import Renderer from "./Renderer" +import Renderer from "./Renderer"; +import MapGenerator from "./render/map-generator"; class BattleSimulatorClient { client: _ClientImpl; pixiApp: PIXI.Application; - grid: Grid; - units: Map; + grid: Grid; clientState: ClientState; renderer: Renderer; constructor(pixiApp: PIXI.Application) { this.client = Client({ game: BattleSimulator }); this.client.start(); + const initialStates = this.client.getInitialState(); + this.pixiApp = pixiApp; this.renderer = new Renderer(pixiApp); - this.grid = this.createBoard(); - this.units = this.createUnits(); + + const [grid, tiles] = new MapGenerator( + initialStates.G.cells, + pixiApp + ).generate(); + this.grid = grid; + const clientState = { selectedUnit: null, markedUnitIds: new Set(), @@ -43,109 +44,6 @@ class BattleSimulatorClient { // FOR PRODUCTION // this.clientState = clientState; - - this.attachListeners(); - this.client.subscribe((state) => this.update(state)); - } - - createBoard() { - const initialState = this.client.getInitialState(); - const cells = initialState.G.cells; - - // Temporary code to draw grid here - const grid = Grid.fromIterable( - cells.map((cell) => MapTile.create(cell.coordinates, cell.cellNumber)) - ); - - grid.forEach((tile) => pixiApp.stage.addChild(tile.render())); - return grid; - } - - createUnits() { - const initialState = this.client.getInitialState(); - const gameUnits = initialState.G.units; - const units = new Map(); - - gameUnits.forEach((unit) => { - const unitTile = Unit.create( - unit.id, - 0, - this.grid.getHex(unit.position)!, - unit.playerID == "0" ? "blue" : "red" - ); - pixiApp.stage.addChild(UnitBuilder.renderPrimary(unitTile)); - units.set(unit.id, unitTile); - }); - - return units; - } - - attachListeners() { - const clickHandler = new ClickHandler( - this.client, - this.grid, - this.clientState - ); - - this.pixiApp.canvas.addEventListener("click", ({ offsetX, offsetY }) => - clickHandler.handle(offsetX, offsetY) - ); - - const endTurnHandler = new EndTurnHandler(this.client, this.clientState); - - document - .querySelector("#end-turn-button") - ?.addEventListener("click", () => { - const confirm = window.confirm( - "Are you sure you want to end the turn?" - ); - - if (confirm) endTurnHandler.handle(); - }); - } - - update(state: ServerState) { - if (state === null) return; - const renderedUnitIDs = this.units.keys(); - - for (const unitID of renderedUnitIDs) { - const unit = this.units.get(unitID)!; - unit.destroy(); - - const unitState = getUnitFromId(state.G.units, unitID); - if (unitState == null || !unitState.isAlive) { - this.units.delete(unitID); - continue; - } - - const newUnitPosition = unitState.position; - const tile = this.grid.getHex(newUnitPosition)!; - const newUnit = Unit.create( - unitState.id, - unitState.power, - tile, - unitState.playerID == "0" ? "blue" : "red" - ); - - if (this.clientState.markedUnitIds.has(unitState.id)) { - pixiApp.stage.addChild(UnitBuilder.renderPrimary(newUnit)); - } else { - pixiApp.stage.addChild(UnitBuilder.renderDisabled(newUnit)); - } - - tile.cellNumber = 0; - tile.render(); - - this.units.set(unitID, newUnit); - } - - if (state.ctx.gameover) { - const textGameOverElement = document.querySelector("#game-over-text")!; - textGameOverElement.textContent = - state.ctx.gameover.winner !== undefined - ? `Player ${state.ctx.gameover.winner} Win!` - : "It's a Draw!"; - } } } diff --git a/src/render/map-generator.ts b/src/render/map-generator.ts new file mode 100644 index 0000000..49434d2 --- /dev/null +++ b/src/render/map-generator.ts @@ -0,0 +1,30 @@ +import { Grid } from "honeycomb-grid"; +import { BaseMapTile } from "../types/model/BaseMapTile"; +import { Application } from "pixi.js"; +import Tile from "./model/map/tile"; +import MapTileHex from "./model/base/map-tile-hex"; + +export default class MapGenerator { + tileStates: BaseMapTile[]; + pixiApp: Application; + + constructor(tileStates: BaseMapTile[], pixiApp: Application) { + this.tileStates = tileStates; + this.pixiApp = pixiApp; + } + + generate(): [Grid, Tile[]] { + const tiles = this.tileStates.map( + (tileState) => new Tile(tileState.coordinates, tileState.cellNumber) + ); + + const hexGrid = Grid.fromIterable(tiles.map((tile) => tile.hex)); + + tiles.forEach((tile) => { + tile.drawBase().displayCellNumber(); + this.pixiApp.stage.addChild(tile.graphic); + }); + + return [hexGrid, tiles]; + } +} From 68d82e4fa77b546995ba7851ca54c8b8a9358110 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 13 Apr 2025 00:04:52 +0700 Subject: [PATCH 41/44] Add initial unit generator --- src/App.ts | 9 ++++++++ src/render/unit-generator.ts | 43 ++++++++++++++++++++++++++++++++++++ src/util/id-generator.ts | 7 ++++++ 3 files changed, 59 insertions(+) create mode 100644 src/render/unit-generator.ts create mode 100644 src/util/id-generator.ts diff --git a/src/App.ts b/src/App.ts index 6b29c23..9699f8e 100644 --- a/src/App.ts +++ b/src/App.ts @@ -11,11 +11,14 @@ import DebugPanel from "./client/DebugPanel"; import { ClientState } from "./types/ClientState"; import Renderer from "./Renderer"; import MapGenerator from "./render/map-generator"; +import Unit from "./render/model/unit/unit"; +import UnitGenerator from "./render/unit-generator"; class BattleSimulatorClient { client: _ClientImpl; pixiApp: PIXI.Application; grid: Grid; + units: Map; clientState: ClientState; renderer: Renderer; @@ -33,6 +36,12 @@ class BattleSimulatorClient { ).generate(); this.grid = grid; + this.units = new UnitGenerator( + initialStates.G.units, + initialStates.ctx.currentPlayer, + pixiApp + ).generate(); + const clientState = { selectedUnit: null, markedUnitIds: new Set(), diff --git a/src/render/unit-generator.ts b/src/render/unit-generator.ts new file mode 100644 index 0000000..51d701f --- /dev/null +++ b/src/render/unit-generator.ts @@ -0,0 +1,43 @@ +import { Application } from "pixi.js"; +import { BaseUnit } from "../types/model/BaseUnit"; +import Unit from "./model/unit/unit"; +import generateId from "../util/id-generator"; + +export default class UnitGenerator { + unitStates: BaseUnit[]; + startingPlayerId: string; + pixiApp: Application; + + constructor( + unitStates: BaseUnit[], + startingPlayerId: string, + pixiApp: Application + ) { + this.unitStates = unitStates; + this.startingPlayerId = startingPlayerId; + this.pixiApp = pixiApp; + } + + generate() { + const units = new Map(); + this.unitStates.forEach((unitState) => { + // Just to make TypeScript happy + const generatedId = generateId().next(); + const unit = new Unit( + generatedId.done ? "" : generatedId.value, + unitState + ); + + let fillStyle: "primary" | "active" = "primary"; + if (unitState.playerID === this.startingPlayerId) { + fillStyle = "active"; + } + + unit.drawBase().fill(fillStyle).displayPower(); + this.pixiApp.stage.addChild(unit.graphic); + units.set(unitState.id, unit); + }); + + return units; + } +} diff --git a/src/util/id-generator.ts b/src/util/id-generator.ts new file mode 100644 index 0000000..289f560 --- /dev/null +++ b/src/util/id-generator.ts @@ -0,0 +1,7 @@ +export default function* generateId() { + let curr = 1; + while (true) { + yield curr.toString(); + curr++; + } +} From b2bdb26e34b469e94c7b4007cce1022da61bf0b9 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 13 Apr 2025 17:41:01 +0700 Subject: [PATCH 42/44] Readjust click handler + selecting event --- src/App.ts | 36 ++++++++--- src/Renderer.ts | 20 ++++-- src/client/ClickHandler.ts | 118 +++++++++++++++++++++------------- src/client/DebugPanel.ts | 9 ++- src/render/model/unit/unit.ts | 5 +- src/render/unit-generator.ts | 6 +- src/types/ClientState.d.ts | 6 +- src/util/board.ts | 11 ++-- src/util/id-generator.ts | 6 +- 9 files changed, 142 insertions(+), 75 deletions(-) diff --git a/src/App.ts b/src/App.ts index 9699f8e..777972f 100644 --- a/src/App.ts +++ b/src/App.ts @@ -13,12 +13,12 @@ import Renderer from "./Renderer"; import MapGenerator from "./render/map-generator"; import Unit from "./render/model/unit/unit"; import UnitGenerator from "./render/unit-generator"; +import ClickHandler from "./client/ClickHandler"; class BattleSimulatorClient { client: _ClientImpl; pixiApp: PIXI.Application; grid: Grid; - units: Map; clientState: ClientState; renderer: Renderer; @@ -27,32 +27,50 @@ class BattleSimulatorClient { this.client.start(); const initialStates = this.client.getInitialState(); - this.pixiApp = pixiApp; - this.renderer = new Renderer(pixiApp); - - const [grid, tiles] = new MapGenerator( + const [grid, _] = new MapGenerator( initialStates.G.cells, pixiApp ).generate(); this.grid = grid; - this.units = new UnitGenerator( + const units = new UnitGenerator( initialStates.G.units, initialStates.ctx.currentPlayer, pixiApp ).generate(); + this.pixiApp = pixiApp; + this.renderer = new Renderer(pixiApp, units); + const clientState = { + units: units, selectedUnit: null, markedUnitIds: new Set(), }; // FOR DEV & DEBUG - const debugPanel = new DebugPanel("#debug-panel"); - this.clientState = debugPanel.watch(clientState); + // BUGGED BECAUSE ERROR WHEN DEALING WITH CIRCULAR REFERENCES + // const excludeLists = new Set(["units"]); + // const debugPanel = new DebugPanel("#debug-panel", excludeLists); + // this.clientState = debugPanel.watch(clientState); // FOR PRODUCTION - // this.clientState = clientState; + this.clientState = clientState; + + this.attachListeners(); + } + + attachListeners() { + const clickHandler = new ClickHandler( + this.client, + this.grid, + this.clientState, + this.renderer + ); + + this.pixiApp.canvas.addEventListener("click", ({ offsetX, offsetY }) => + clickHandler.handle(offsetX, offsetY) + ); } } diff --git a/src/Renderer.ts b/src/Renderer.ts index 2e1ae74..c161a6f 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -1,14 +1,17 @@ import gameConfig from "../gameConfig"; import type { Application } from "pixi.js"; import { Event, RenderEventParamMap } from "./types/model/base/event"; +import Unit from "./render/model/unit/unit"; class Renderer { events: Event[]; pixiApp: Application; + units: Map; - constructor(app: Application) { + constructor(app: Application, units: Map) { this.pixiApp = app; this.events = []; + this.units = units; let lastTick = performance.now(); @@ -26,17 +29,20 @@ class Renderer { processEvents(delta: number) { for (const event of this.events) { - // TODO: process event - // Maybe make event types? + const unit = this.units.get(event.object_id); + if (unit === undefined) continue; + unit.consume(event.event, event.parameter); } - console.log("Process events called!"); - this.events = []; } - addEvent(event: Event) { - this.events.push(event); + addEvent( + object_id: string, + event: E, + parameter: RenderEventParamMap[E] + ) { + this.events.push({ object_id, event, parameter }); } } diff --git a/src/client/ClickHandler.ts b/src/client/ClickHandler.ts index b5f7e96..6ca2718 100644 --- a/src/client/ClickHandler.ts +++ b/src/client/ClickHandler.ts @@ -1,24 +1,28 @@ import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; -import MapTile from "../model/MapTile"; -import { Grid, PartialCubeCoordinates } from "honeycomb-grid"; +import { Grid, Hex, PartialCubeCoordinates } from "honeycomb-grid"; import { GameState } from "../types/GameState"; -import { getUnitsFromStateAndCoord } from "../util/board"; +import { getUnitsFromClientUnitsAndCoord } from "../util/board"; import { ClientState } from "../types/ClientState"; import { BaseUnit } from "../types/model/BaseUnit"; +import Renderer from "../Renderer"; +import Unit from "../render/model/unit/unit"; export default class ClickHandler { gameClient: _ClientImpl; - grid: Grid; + grid: Grid; clientState: ClientState; + renderer: Renderer; constructor( gameClient: _ClientImpl, - grid: Grid, - clientState: ClientState + grid: Grid, + clientState: ClientState, + renderer: Renderer ) { this.gameClient = gameClient; this.grid = grid; this.clientState = clientState; + this.renderer = renderer; } handle(offsetX: number, offsetY: number) { @@ -29,6 +33,11 @@ export default class ClickHandler { if (tile === undefined) { if (this.clientState.selectedUnit != null) { + this.renderer.addEvent( + this.clientState.selectedUnit!.id, + "selected", + false + ); this.clientState.selectedUnit = null; } @@ -39,74 +48,95 @@ export default class ClickHandler { if (state == null) return; const coordinate = { q: tile.q, r: tile.r }; - const unitsOnCoord = getUnitsFromStateAndCoord(state.G, coordinate); + const unitsOnCoord = getUnitsFromClientUnitsAndCoord( + [...this.clientState.units.values()], + coordinate + ); if (this.clientState.selectedUnit == null) { - this.resolveSelection(state.ctx.currentPlayer, unitsOnCoord); + const isUnitSelected = this.resolveSelection( + state.ctx.currentPlayer, + unitsOnCoord + ); + + if (isUnitSelected) { + this.renderer.addEvent( + this.clientState.selectedUnit!.id, + "selected", + true + ); + } + return; } - const target = unitsOnCoord.filter((unit) => unit.isAlive).shift(); + // const target = unitsOnCoord.filter((unit) => unit.isAlive).shift(); - let isActionSuccessful = true; - if (target == null || !target.isAlive) { - isActionSuccessful = this.handleMovement(coordinate); - } else { - isActionSuccessful = this.handleFighting(target); - } + let isActionSuccessful = false; + // if (target == null || !target.isAlive) { + // isActionSuccessful = this.handleMovement(coordinate); + // } else { + // isActionSuccessful = this.handleFighting(target); + // } if (isActionSuccessful) { this.clientState.markedUnitIds.add(this.clientState.selectedUnit.id); + this.renderer.addEvent( + this.clientState.selectedUnit!.id, + "selected", + false + ); this.clientState.selectedUnit = null; } } - resolveSelection(currentPlayerId: string, unitsOnCoord: BaseUnit[]) { + resolveSelection(currentPlayerId: string, unitsOnCoord: Unit[]) { const firstLivingUnit = unitsOnCoord - .filter((unit) => unit.isAlive) + .filter((unit) => unit.state.isAlive) .shift(); - if (firstLivingUnit == null) return; - if (firstLivingUnit.playerID != currentPlayerId) { + if (firstLivingUnit == null) return false; + if (firstLivingUnit.state.playerID != currentPlayerId) { alert("Please select your own unit!"); - return; + return false; } if (this.clientState.markedUnitIds.has(firstLivingUnit.id)) { alert("This unit has done an action, choose another unit!"); - return; + return false; } this.clientState.selectedUnit = firstLivingUnit; + return true; } - handleMovement(coordinate: PartialCubeCoordinates) { - const selectedUnit = this.clientState.selectedUnit; - if (selectedUnit === null) { - alert("No unit is being selected!"); - return false; - } + // handleMovement(coordinate: PartialCubeCoordinates) { + // const selectedUnit = this.clientState.selectedUnit; + // if (selectedUnit === null) { + // alert("No unit is being selected!"); + // return false; + // } - this.gameClient.moves.moveUnit(selectedUnit.id, coordinate); + // this.gameClient.moves.moveUnit(selectedUnit.id, coordinate); - return true; - } + // return true; + // } - handleFighting(target: BaseUnit) { - const selectedUnit = this.clientState.selectedUnit; - if (selectedUnit === null) { - alert("No unit is being selected!"); - return false; - } + // handleFighting(target: BaseUnit) { + // const selectedUnit = this.clientState.selectedUnit; + // if (selectedUnit === null) { + // alert("No unit is being selected!"); + // return false; + // } - if (selectedUnit.playerID === target.playerID) { - // Validation may be removed in the future - alert("You cannot attack your own unit!"); - return false; - } + // if (selectedUnit.playerID === target.playerID) { + // // Validation may be removed in the future + // alert("You cannot attack your own unit!"); + // return false; + // } - this.gameClient.moves.fight(selectedUnit.id, target.id); + // this.gameClient.moves.fight(selectedUnit.id, target.id); - return true; - } + // return true; + // } } diff --git a/src/client/DebugPanel.ts b/src/client/DebugPanel.ts index 527a761..94510e4 100644 --- a/src/client/DebugPanel.ts +++ b/src/client/DebugPanel.ts @@ -2,9 +2,11 @@ import { ClientState } from "../types/ClientState"; export default class DebugPanel { debugPanelElement: Element; + excludeLists: Set; - constructor(panelSelector: string) { + constructor(panelSelector: string, excludeLists: Set) { this.debugPanelElement = document.querySelector(panelSelector)!; + this.excludeLists = excludeLists; } watch(clientState: ClientState) { @@ -23,7 +25,10 @@ export default class DebugPanel { let html = ""; for (const prop in state) { const val = state[prop as keyof ClientState]; - html += `${prop}: ${this.getStringContentFrom(val)}
`; + const content = this.excludeLists.has(prop) + ? "NOT SHOWN" + : this.getStringContentFrom(val); + html += `${prop}: ${content}
`; } return html; } diff --git a/src/render/model/unit/unit.ts b/src/render/model/unit/unit.ts index 41cd2ea..8b1b69e 100644 --- a/src/render/model/unit/unit.ts +++ b/src/render/model/unit/unit.ts @@ -46,6 +46,9 @@ export default class Unit extends Entity { } addSubscribers() { - // this.addSubscriber("selected", (param) => {}); + this.addSubscriber("selected", (isSelected) => { + if (isSelected) this.reset().drawBase().fill("primary").displayPower(); + else this.reset().drawBase().fill("active").displayPower(); + }); } } diff --git a/src/render/unit-generator.ts b/src/render/unit-generator.ts index 51d701f..4d7a8de 100644 --- a/src/render/unit-generator.ts +++ b/src/render/unit-generator.ts @@ -1,7 +1,7 @@ import { Application } from "pixi.js"; import { BaseUnit } from "../types/model/BaseUnit"; import Unit from "./model/unit/unit"; -import generateId from "../util/id-generator"; +import idGenerator from "../util/id-generator"; export default class UnitGenerator { unitStates: BaseUnit[]; @@ -22,7 +22,7 @@ export default class UnitGenerator { const units = new Map(); this.unitStates.forEach((unitState) => { // Just to make TypeScript happy - const generatedId = generateId().next(); + const generatedId = idGenerator.next(); const unit = new Unit( generatedId.done ? "" : generatedId.value, unitState @@ -35,7 +35,7 @@ export default class UnitGenerator { unit.drawBase().fill(fillStyle).displayPower(); this.pixiApp.stage.addChild(unit.graphic); - units.set(unitState.id, unit); + units.set(generatedId.done ? "" : generatedId.value, unit); }); return units; diff --git a/src/types/ClientState.d.ts b/src/types/ClientState.d.ts index 5a37a97..54bcdd9 100644 --- a/src/types/ClientState.d.ts +++ b/src/types/ClientState.d.ts @@ -1,7 +1,7 @@ -import Unit from "../model/Unit"; -import { BaseUnit } from "./model/BaseUnit"; +import Unit from "../render/model/unit/unit"; export declare type ClientState = { - selectedUnit: BaseUnit | null; + units: Map; + selectedUnit: Unit | null; markedUnitIds: Set; // Need better name }; diff --git a/src/util/board.ts b/src/util/board.ts index a6ea361..923fe25 100644 --- a/src/util/board.ts +++ b/src/util/board.ts @@ -1,13 +1,14 @@ import { PartialCubeCoordinates } from "honeycomb-grid"; -import { GameState } from "../types/GameState"; +import Unit from "../render/model/unit/unit"; -export const getUnitsFromStateAndCoord = ( - state: GameState, +export const getUnitsFromClientUnitsAndCoord = ( + units: Unit[], coordinate: PartialCubeCoordinates ) => { - const filtered = state.units.filter( + const filtered = units.filter( (unit) => - unit.position.q === coordinate.q && unit.position.r === coordinate.r + unit.state.position.q === coordinate.q && + unit.state.position.r === coordinate.r ); return filtered; diff --git a/src/util/id-generator.ts b/src/util/id-generator.ts index 289f560..d812bec 100644 --- a/src/util/id-generator.ts +++ b/src/util/id-generator.ts @@ -1,7 +1,11 @@ -export default function* generateId() { +function* generateId() { let curr = 1; while (true) { yield curr.toString(); curr++; } } + +const idGenerator = generateId(); + +export default idGenerator; From 5f125d1b4074d7d5ea2ebf02238dd926370f7f99 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sat, 31 May 2025 22:50:30 +0700 Subject: [PATCH 43/44] Refactor types naming --- src/types/model/BasePlayer.d.ts | 8 -------- src/types/model/base/{event.d.ts => render-event.d.ts} | 2 +- src/types/model/{BaseMapTile.d.ts => map-tile-state.d.ts} | 2 +- src/types/model/{BaseUnit.d.ts => unit-state.d.ts} | 2 +- 4 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 src/types/model/BasePlayer.d.ts rename src/types/model/base/{event.d.ts => render-event.d.ts} (95%) rename src/types/model/{BaseMapTile.d.ts => map-tile-state.d.ts} (77%) rename src/types/model/{BaseUnit.d.ts => unit-state.d.ts} (83%) diff --git a/src/types/model/BasePlayer.d.ts b/src/types/model/BasePlayer.d.ts deleted file mode 100644 index 650306a..0000000 --- a/src/types/model/BasePlayer.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { PartialCubeCoordinates } from "honeycomb-grid"; - -export declare type BasePlayer = { - id: string; - position: PartialCubeCoordinates; - power: number; - isAlive: boolean; -}; diff --git a/src/types/model/base/event.d.ts b/src/types/model/base/render-event.d.ts similarity index 95% rename from src/types/model/base/event.d.ts rename to src/types/model/base/render-event.d.ts index c30a929..a5c4a03 100644 --- a/src/types/model/base/event.d.ts +++ b/src/types/model/base/render-event.d.ts @@ -51,7 +51,7 @@ export interface RenderEventHandler { ); } -export interface Event { +export interface RenderEvent { object_id: string; event: E; parameter: RenderEventParamMap[E]; diff --git a/src/types/model/BaseMapTile.d.ts b/src/types/model/map-tile-state.d.ts similarity index 77% rename from src/types/model/BaseMapTile.d.ts rename to src/types/model/map-tile-state.d.ts index 70ade8e..0afaf38 100644 --- a/src/types/model/BaseMapTile.d.ts +++ b/src/types/model/map-tile-state.d.ts @@ -1,6 +1,6 @@ import { PartialCubeCoordinates } from "honeycomb-grid"; -export declare type BaseMapTile = { +export declare type MapTileState = { cellNumber: number; coordinates: PartialCubeCoordinates; }; diff --git a/src/types/model/BaseUnit.d.ts b/src/types/model/unit-state.d.ts similarity index 83% rename from src/types/model/BaseUnit.d.ts rename to src/types/model/unit-state.d.ts index 3737c7f..9206bcc 100644 --- a/src/types/model/BaseUnit.d.ts +++ b/src/types/model/unit-state.d.ts @@ -1,6 +1,6 @@ import { PartialCubeCoordinates } from "honeycomb-grid"; -export declare type BaseUnit = { +export declare type UnitState = { id: string; playerID: string; position: PartialCubeCoordinates; From f91ced2515de3ca69589ae13092401ea514cab28 Mon Sep 17 00:00:00 2001 From: extremebip Date: Sun, 1 Jun 2025 01:36:43 +0700 Subject: [PATCH 44/44] Refactor client-server communication + movement system --- src/App.ts | 55 +++++++++++++++++++++++++----- src/Game.ts | 2 ++ src/Renderer.ts | 7 ++-- src/client/ClickHandler.ts | 51 +++++++++++++-------------- src/controller/move-controller.ts | 19 +++++++++-- src/plugins/game-event-plugin.ts | 54 +++++++++++++++++++++++++++++ src/render/map-generator.ts | 6 ++-- src/render/model/entity.ts | 2 +- src/render/model/map/tile.ts | 2 +- src/render/model/unit/unit.ts | 19 +++++++++-- src/render/unit-generator.ts | 24 +++++++------ src/temp/board-mocker.ts | 12 +++---- src/types/ClientState.d.ts | 20 +++++++++++ src/types/GameState.ts | 8 ++--- src/types/model/base/game-event.ts | 10 ++++++ src/util/game-state.ts | 4 +-- 16 files changed, 226 insertions(+), 69 deletions(-) create mode 100644 src/plugins/game-event-plugin.ts create mode 100644 src/types/model/base/game-event.ts diff --git a/src/App.ts b/src/App.ts index 777972f..50f6645 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,19 +1,16 @@ import { Client } from "boardgame.io/client"; import { BattleSimulator } from "./Game"; -import { - ClientState as ServerState, - _ClientImpl, -} from "boardgame.io/dist/types/src/client/client"; +import { _ClientImpl } from "boardgame.io/dist/types/src/client/client"; import { Grid, Hex } from "honeycomb-grid"; import * as PIXI from "pixi.js"; import { GameState } from "./types/GameState"; import DebugPanel from "./client/DebugPanel"; -import { ClientState } from "./types/ClientState"; +import { ClientState, ServerState } from "./types/ClientState"; import Renderer from "./Renderer"; import MapGenerator from "./render/map-generator"; -import Unit from "./render/model/unit/unit"; import UnitGenerator from "./render/unit-generator"; import ClickHandler from "./client/ClickHandler"; +import { UnitGameEvent } from "./types/model/base/game-event"; class BattleSimulatorClient { client: _ClientImpl; @@ -33,7 +30,7 @@ class BattleSimulatorClient { ).generate(); this.grid = grid; - const units = new UnitGenerator( + const [units, unitIdToClientUnitId] = new UnitGenerator( initialStates.G.units, initialStates.ctx.currentPlayer, pixiApp @@ -42,8 +39,9 @@ class BattleSimulatorClient { this.pixiApp = pixiApp; this.renderer = new Renderer(pixiApp, units); - const clientState = { + const clientState: ClientState = { units: units, + unitIdToClientUnitId: unitIdToClientUnitId, selectedUnit: null, markedUnitIds: new Set(), }; @@ -58,6 +56,7 @@ class BattleSimulatorClient { this.clientState = clientState; this.attachListeners(); + this.client.subscribe((state) => this.update(state)); } attachListeners() { @@ -72,6 +71,46 @@ class BattleSimulatorClient { clickHandler.handle(offsetX, offsetY) ); } + + update(state: ServerState) { + // When an invalid move happens, it sends update 2 times to the client + // This logic is used because the first update will contain "transients" property + if (state === null || state.hasOwnProperty("transients")) return; + + console.log(state.plugins.gameEvent); + + const gameEventState = state.plugins.gameEvent; + const lastErrorMessage = + gameEventState.api?.lastErrorMessage || + gameEventState.data.lastErrorMessage; + + if (lastErrorMessage !== null) { + alert(lastErrorMessage); + return; + } + + if (gameEventState.data.eventQueue.length === 0) return; + + for (const pushedEvent of gameEventState.data.eventQueue) { + const serverUnit = state.G.units.filter( + (unitState) => unitState.id === pushedEvent.unit_id + )[0]!; + const clientUnitId = this.clientState.unitIdToClientUnitId.get( + pushedEvent.unit_id + )!; + const clientUnit = this.clientState.units.get(clientUnitId)!; + + if (pushedEvent.event === UnitGameEvent.Move) { + clientUnit.state.position = serverUnit.position; + this.renderer.addEvent(clientUnitId, "move", serverUnit.position); + } + } + + const selectedUnit = this.clientState.selectedUnit!; + this.renderer.addEvent(selectedUnit.id, "selected", false); + this.clientState.markedUnitIds.add(selectedUnit.id); + this.clientState.selectedUnit = null; + } } const pixiApp = new PIXI.Application(); diff --git a/src/Game.ts b/src/Game.ts index 9e8c9d0..546f842 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -3,6 +3,7 @@ import { mockBoard, mockUnits } from "./temp/board-mocker"; import { GameState } from "./types/GameState"; import MoveController from "./controller/move-controller"; import FightController from "./controller/fight-controller"; +import GameEventPlugin from "./plugins/game-event-plugin"; export const BattleSimulator: Game = { setup: () => { @@ -19,6 +20,7 @@ export const BattleSimulator: Game = { }, minPlayers: 2, maxPlayers: 2, + plugins: [GameEventPlugin()], turn: { // minMoves: 1, // maxMoves: 1, diff --git a/src/Renderer.ts b/src/Renderer.ts index c161a6f..5f85e17 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -1,10 +1,13 @@ import gameConfig from "../gameConfig"; import type { Application } from "pixi.js"; -import { Event, RenderEventParamMap } from "./types/model/base/event"; +import { + RenderEvent, + RenderEventParamMap, +} from "./types/model/base/render-event"; import Unit from "./render/model/unit/unit"; class Renderer { - events: Event[]; + events: RenderEvent[]; pixiApp: Application; units: Map; diff --git a/src/client/ClickHandler.ts b/src/client/ClickHandler.ts index 6ca2718..766f288 100644 --- a/src/client/ClickHandler.ts +++ b/src/client/ClickHandler.ts @@ -3,7 +3,7 @@ import { Grid, Hex, PartialCubeCoordinates } from "honeycomb-grid"; import { GameState } from "../types/GameState"; import { getUnitsFromClientUnitsAndCoord } from "../util/board"; import { ClientState } from "../types/ClientState"; -import { BaseUnit } from "../types/model/BaseUnit"; +import { UnitState } from "../types/model/unit-state"; import Renderer from "../Renderer"; import Unit from "../render/model/unit/unit"; @@ -70,24 +70,25 @@ export default class ClickHandler { return; } - // const target = unitsOnCoord.filter((unit) => unit.isAlive).shift(); + const target = unitsOnCoord.filter((unit) => unit.state.isAlive).shift(); - let isActionSuccessful = false; - // if (target == null || !target.isAlive) { - // isActionSuccessful = this.handleMovement(coordinate); - // } else { + // let isActionSuccessful = false; + if (target == null || !target.state.isAlive) { + this.handleMovement(coordinate); + } + // else { // isActionSuccessful = this.handleFighting(target); // } - if (isActionSuccessful) { - this.clientState.markedUnitIds.add(this.clientState.selectedUnit.id); - this.renderer.addEvent( - this.clientState.selectedUnit!.id, - "selected", - false - ); - this.clientState.selectedUnit = null; - } + // if (isActionSuccessful) { + // this.clientState.markedUnitIds.add(this.clientState.selectedUnit.id); + // this.renderer.addEvent( + // this.clientState.selectedUnit!.id, + // "selected", + // false + // ); + // this.clientState.selectedUnit = null; + // } } resolveSelection(currentPlayerId: string, unitsOnCoord: Unit[]) { @@ -110,19 +111,19 @@ export default class ClickHandler { return true; } - // handleMovement(coordinate: PartialCubeCoordinates) { - // const selectedUnit = this.clientState.selectedUnit; - // if (selectedUnit === null) { - // alert("No unit is being selected!"); - // return false; - // } + handleMovement(coordinate: PartialCubeCoordinates) { + const selectedUnit = this.clientState.selectedUnit; + if (selectedUnit === null) { + alert("No unit is being selected!"); + return false; + } - // this.gameClient.moves.moveUnit(selectedUnit.id, coordinate); + this.gameClient.moves.moveUnit(selectedUnit.state.id, coordinate); - // return true; - // } + return true; + } - // handleFighting(target: BaseUnit) { + // handleFighting(target: UnitState) { // const selectedUnit = this.clientState.selectedUnit; // if (selectedUnit === null) { // alert("No unit is being selected!"); diff --git a/src/controller/move-controller.ts b/src/controller/move-controller.ts index f8fe98d..e403c5a 100644 --- a/src/controller/move-controller.ts +++ b/src/controller/move-controller.ts @@ -4,17 +4,25 @@ import { Move } from "boardgame.io"; import { GameState } from "../types/GameState"; import TileHex from "../model/Base/TileHex"; import { getUnitFromId } from "../util/game-state"; +import { UnitGameEvent } from "../types/model/base/game-event"; const moveUnit: Move = ( - { G, playerID }, + { G, playerID, ...plugins }, unitID: string, target: PartialCubeCoordinates ) => { const unit = getUnitFromId(G.units, unitID); - if (unit == null) return INVALID_MOVE; + if (unit == null) { + plugins.gameEvent.lastErrorMessage = "No unit is being selected!"; + return INVALID_MOVE; + } const dist = distance(TileHex.settings, unit.position, target); - if (dist > 1 || dist == 0) return INVALID_MOVE; + if (dist > 1 || dist == 0) { + plugins.gameEvent.lastErrorMessage = + "This unit can only move exactly one tile!"; + return INVALID_MOVE; + } unit.position = target; const currCoordinates = toCube(TileHex.settings, target); @@ -29,6 +37,11 @@ const moveUnit: Move = ( unit.power += targetCell.cellNumber; targetCell.cellNumber = 0; } + + plugins.gameEvent.enqueue({ + unit_id: unit.id, + event: UnitGameEvent.Move, + }); }; class MoveController { diff --git a/src/plugins/game-event-plugin.ts b/src/plugins/game-event-plugin.ts new file mode 100644 index 0000000..ca53754 --- /dev/null +++ b/src/plugins/game-event-plugin.ts @@ -0,0 +1,54 @@ +import { GameEvent } from "../types/model/base/game-event"; +import { Plugin } from "boardgame.io"; + +export interface GameEventData { + lastErrorMessage: string | null; + eventQueue: GameEvent[]; // If this slows the game, change data structure to queue instead +} + +export interface GameEventAPI extends GameEventData { + enqueue(event: GameEvent): void; + dequeue(): GameEvent | null; +} + +export interface GameEventPlugin { + gameEvent: GameEventAPI; +} + +const GameEventPlugin = (): Plugin => ({ + name: "gameEvent", + setup: () => ({ lastErrorMessage: null, eventQueue: [] }), + flush: ({ api }) => ({ + lastErrorMessage: api.lastErrorMessage, + eventQueue: api.eventQueue, + }), + api: () => { + const eventQueue: GameEvent[] = []; + + const enqueue = (event: GameEvent) => { + eventQueue.push(event); + }; + + const dequeue = () => { + if (eventQueue.length === 0) return null; + return eventQueue.splice(0, 1)[0]; + }; + + return { + lastErrorMessage: null, + eventQueue, + enqueue, + dequeue, + }; + }, + // fnWrap: + // (move, moveType) => + // ({ G, gameEvent, ...rest }, ...args) => { + // console.log(moveType); + // const result = move({ G, gameEvent, ...rest }, ...args); + // if (result !== INVALID_MOVE) gameEvent.lastErrorMessage = null; + // return G; + // }, +}); + +export default GameEventPlugin; diff --git a/src/render/map-generator.ts b/src/render/map-generator.ts index 49434d2..3ecb220 100644 --- a/src/render/map-generator.ts +++ b/src/render/map-generator.ts @@ -1,14 +1,14 @@ import { Grid } from "honeycomb-grid"; -import { BaseMapTile } from "../types/model/BaseMapTile"; +import { MapTileState } from "../types/model/map-tile-state"; import { Application } from "pixi.js"; import Tile from "./model/map/tile"; import MapTileHex from "./model/base/map-tile-hex"; export default class MapGenerator { - tileStates: BaseMapTile[]; + tileStates: MapTileState[]; pixiApp: Application; - constructor(tileStates: BaseMapTile[], pixiApp: Application) { + constructor(tileStates: MapTileState[], pixiApp: Application) { this.tileStates = tileStates; this.pixiApp = pixiApp; } diff --git a/src/render/model/entity.ts b/src/render/model/entity.ts index 4b051f2..34ffe82 100644 --- a/src/render/model/entity.ts +++ b/src/render/model/entity.ts @@ -2,7 +2,7 @@ import { Graphics } from "pixi.js"; import { EntityEventParamMap, RenderEventHandler, -} from "../../types/model/base/event"; +} from "../../types/model/base/render-event"; export default abstract class Entity implements RenderEventHandler { id: string; diff --git a/src/render/model/map/tile.ts b/src/render/model/map/tile.ts index bba5e61..152bc25 100644 --- a/src/render/model/map/tile.ts +++ b/src/render/model/map/tile.ts @@ -5,7 +5,7 @@ import { Graphics, Text } from "pixi.js"; import { EntityEventParamMap, RenderEventHandler, -} from "../../../types/model/base/event"; +} from "../../../types/model/base/render-event"; export default class Tile implements RenderEventHandler { hex: Hex; diff --git a/src/render/model/unit/unit.ts b/src/render/model/unit/unit.ts index 8b1b69e..f05fea3 100644 --- a/src/render/model/unit/unit.ts +++ b/src/render/model/unit/unit.ts @@ -1,15 +1,15 @@ import { Hex } from "honeycomb-grid"; import Entity from "../entity"; import UnitHex from "../base/unit-hex"; -import { BaseUnit } from "../../../types/model/BaseUnit"; +import { UnitState } from "../../../types/model/unit-state"; import { unit as unitColor } from "../../../../colors.json"; import { Text } from "pixi.js"; -export default class Unit extends Entity { +export default class Unit extends Entity { hex: Hex; isSelected: boolean; - constructor(id: string, gameState: BaseUnit) { + constructor(id: string, gameState: UnitState) { super(id, gameState); this.hex = new UnitHex(gameState.position); this.isSelected = false; @@ -50,5 +50,18 @@ export default class Unit extends Entity { if (isSelected) this.reset().drawBase().fill("primary").displayPower(); else this.reset().drawBase().fill("active").displayPower(); }); + + this.addSubscriber("move", (coordinate) => { + // this.state.position.q = coordinate.q!; + // this.state.position.r = coordinate.r!; + + const delta = { + q: coordinate.q! - this.hex.q, + r: coordinate.r! - this.hex.r, + }; + + this.hex = this.hex.translate(delta); + this.reset().drawBase().fill("primary").displayPower(); + }); } } diff --git a/src/render/unit-generator.ts b/src/render/unit-generator.ts index 4d7a8de..e70f2c5 100644 --- a/src/render/unit-generator.ts +++ b/src/render/unit-generator.ts @@ -1,15 +1,15 @@ import { Application } from "pixi.js"; -import { BaseUnit } from "../types/model/BaseUnit"; +import { UnitState } from "../types/model/unit-state"; import Unit from "./model/unit/unit"; import idGenerator from "../util/id-generator"; export default class UnitGenerator { - unitStates: BaseUnit[]; + unitStates: UnitState[]; startingPlayerId: string; pixiApp: Application; constructor( - unitStates: BaseUnit[], + unitStates: UnitState[], startingPlayerId: string, pixiApp: Application ) { @@ -18,15 +18,16 @@ export default class UnitGenerator { this.pixiApp = pixiApp; } - generate() { + generate(): [Map, Map] { const units = new Map(); + const unitIdToClientUnitId = new Map(); this.unitStates.forEach((unitState) => { // Just to make TypeScript happy - const generatedId = idGenerator.next(); - const unit = new Unit( - generatedId.done ? "" : generatedId.value, - unitState - ); + const generatedIdObj = idGenerator.next(); + const generatedId = generatedIdObj.done ? "" : generatedIdObj.value; + + // MUST DO THIS! Because `unitState` is from `const initialStates` which is constant & readonly. + const unit = new Unit(generatedId, { ...unitState }); let fillStyle: "primary" | "active" = "primary"; if (unitState.playerID === this.startingPlayerId) { @@ -35,9 +36,10 @@ export default class UnitGenerator { unit.drawBase().fill(fillStyle).displayPower(); this.pixiApp.stage.addChild(unit.graphic); - units.set(generatedId.done ? "" : generatedId.value, unit); + units.set(generatedId, unit); + unitIdToClientUnitId.set(unitState.id, generatedId); }); - return units; + return [units, unitIdToClientUnitId]; } } diff --git a/src/temp/board-mocker.ts b/src/temp/board-mocker.ts index 60ccd48..b6b4b61 100644 --- a/src/temp/board-mocker.ts +++ b/src/temp/board-mocker.ts @@ -1,7 +1,7 @@ import { toCube } from "honeycomb-grid"; import TileHex from "../model/Base/TileHex"; -import { BaseMapTile } from "../types/model/BaseMapTile"; -import { BaseUnit } from "../types/model/BaseUnit"; +import { MapTileState } from "../types/model/map-tile-state"; +import { UnitState } from "../types/model/unit-state"; function getRandomInt(min: number, max: number) { const minCeiled = Math.ceil(min); @@ -9,7 +9,7 @@ function getRandomInt(min: number, max: number) { return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); } -export const mockBoard = (): BaseMapTile[] => { +export const mockBoard = (): MapTileState[] => { return Array.from(Array(10), (_, i) => Array.from(Array(10), (_, j) => ({ coordinates: toCube(TileHex.settings, { row: i, col: j }), @@ -19,12 +19,12 @@ export const mockBoard = (): BaseMapTile[] => { }; export const mockUnits = ( - board: BaseMapTile[], + board: MapTileState[], playerCount: number, unitPerPlayerCount: number -): BaseUnit[] => { +): UnitState[] => { const availablePositions = board.filter((tile) => tile.cellNumber === 0); - const mockUnits: BaseUnit[] = []; + const mockUnits: UnitState[] = []; for (let i = 0; i < playerCount * unitPerPlayerCount; i++) { const randomTileIdx = getRandomInt(0, availablePositions.length); diff --git a/src/types/ClientState.d.ts b/src/types/ClientState.d.ts index 54bcdd9..406fb31 100644 --- a/src/types/ClientState.d.ts +++ b/src/types/ClientState.d.ts @@ -1,7 +1,27 @@ +import { ClientState as BaseServerState } from "boardgame.io/dist/types/src/client/client"; import Unit from "../render/model/unit/unit"; +import { DefaultPluginAPIs } from "boardgame.io"; +import { + GameEventAPI, + GameEventData, + GameEventPlugin, +} from "../plugins/game-event-plugin"; export declare type ClientState = { units: Map; + unitIdToClientUnitId: Map; selectedUnit: Unit | null; markedUnitIds: Set; // Need better name }; + +type PluginData = { + api?: A; + data: D; +}; + +export type ServerState = BaseServerState & { + plugins: { + gameEvent: PluginData; + // There are other plugin data, but will only add ours + }; +}; diff --git a/src/types/GameState.ts b/src/types/GameState.ts index d3e60dc..0294fef 100644 --- a/src/types/GameState.ts +++ b/src/types/GameState.ts @@ -1,8 +1,8 @@ -import { BaseMapTile } from "./model/BaseMapTile"; -import { BaseUnit } from "./model/BaseUnit"; +import { MapTileState } from "./model/map-tile-state"; +import { UnitState } from "./model/unit-state"; export declare type GameState = { - cells: BaseMapTile[]; - units: BaseUnit[]; + cells: MapTileState[]; + units: UnitState[]; unitCountByPlayer: Record; }; diff --git a/src/types/model/base/game-event.ts b/src/types/model/base/game-event.ts new file mode 100644 index 0000000..181fcab --- /dev/null +++ b/src/types/model/base/game-event.ts @@ -0,0 +1,10 @@ +export enum UnitGameEvent { + Move, + Damaged, + Die, +} + +export interface GameEvent { + unit_id: string; // May need to change to entity_id in the future + event: UnitGameEvent; +} diff --git a/src/util/game-state.ts b/src/util/game-state.ts index 3ebe03f..3ffb8de 100644 --- a/src/util/game-state.ts +++ b/src/util/game-state.ts @@ -1,6 +1,6 @@ -import { BaseUnit } from "../types/model/BaseUnit"; +import { UnitState } from "../types/model/unit-state"; -export const getUnitFromId = (units: BaseUnit[], unitID: string) => { +export const getUnitFromId = (units: UnitState[], unitID: string) => { const filteredUnits = units.filter((unit) => unit.id == unitID); return filteredUnits.length === 0 ? null : filteredUnits[0]; };