From bb8c24e230e615dd330200dc461fc66139c89b9e Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 24 Aug 2024 12:12:46 -0700 Subject: [PATCH] move config to Game --- TODO.txt | 1 - src/client/ClientGame.ts | 16 +++++----- src/client/graphics/TerrainRenderer.ts | 37 +++++++++++++++++++++++ src/core/Game.ts | 2 ++ src/core/GameImpl.ts | 28 +++++++++++++++-- src/core/execution/AttackExecution.ts | 7 ++--- src/core/execution/BoatAttackExecution.ts | 3 +- src/core/execution/BotExecution.ts | 20 ++++++------ src/core/execution/Executor.ts | 12 +++----- src/core/execution/PlayerExecution.ts | 8 +++-- src/core/execution/SpawnExecution.ts | 19 ++++++------ 11 files changed, 102 insertions(+), 51 deletions(-) create mode 100644 src/client/graphics/TerrainRenderer.ts diff --git a/TODO.txt b/TODO.txt index 836e0877d..d12553cb0 100644 --- a/TODO.txt +++ b/TODO.txt @@ -43,7 +43,6 @@ * BUG: boat doesn't work if on lake if other player not on same lake DONE 8/23/2024 * Allow boats to attack TerraNullius DONE 8/23/2024 * try vintage theme DONE 8/24/2023 -* Boats can go diagonally * Make lobby background the terrain map * improve menu (keep highlighted when click, allow deselect lobby) * add shader to dim border diff --git a/src/client/ClientGame.ts b/src/client/ClientGame.ts index 3ca4902c1..2f9122085 100644 --- a/src/client/ClientGame.ts +++ b/src/client/ClientGame.ts @@ -13,7 +13,7 @@ import {bfs, manhattanDist} from "../core/Util"; export function createClientGame(name: string, clientID: ClientID, gameID: GameID, config: Config, terrainMap: TerrainMap): ClientGame { let eventBus = new EventBus() - let gs = createGame(terrainMap, eventBus) + let gs = createGame(terrainMap, eventBus, config) let gameRenderer = new GameRenderer(gs, config.theme(), document.createElement("canvas")) return new ClientGame( @@ -24,8 +24,7 @@ export function createClientGame(name: string, clientID: ClientID, gameID: GameI gs, gameRenderer, new InputHandler(eventBus), - new Executor(gs, config), - config + new Executor(gs) ) } @@ -52,8 +51,7 @@ export class ClientGame { private gs: Game, private renderer: GameRenderer, private input: InputHandler, - private executor: Executor, - private config: Config + private executor: Executor ) { } public join() { @@ -117,7 +115,7 @@ export class ClientGame { this.renderer.initialize() this.input.initialize() - this.gs.addExecution(...this.executor.spawnBots(this.config.numBots())) + this.gs.addExecution(...this.executor.spawnBots(this.gs.config().numBots())) this.intervalID = setInterval(() => this.tick(), 10); } @@ -197,15 +195,15 @@ export class ClientGame { .flatMap(t => t.neighbors()) .filter(n => n.isShore()) if (tn.length > 0) { - this.sendBoatAttackIntent(targetID, tn[0].cell(), this.config.player().boatAttackAmount(this.myPlayer, owner)) + this.sendBoatAttackIntent(targetID, tn[0].cell(), this.gs.config().player().boatAttackAmount(this.myPlayer, owner)) return } if (this.myPlayer.sharesBorderWith(tile.owner())) { - this.sendAttackIntent(targetID, cell, this.config.player().attackAmount(this.myPlayer, owner)) + this.sendAttackIntent(targetID, cell, this.gs.config().player().attackAmount(this.myPlayer, owner)) } else if (owner.isPlayer()) { console.log('going to send boat') - this.sendBoatAttackIntent(targetID, cell, this.config.player().boatAttackAmount(this.myPlayer, owner)) + this.sendBoatAttackIntent(targetID, cell, this.gs.config().player().boatAttackAmount(this.myPlayer, owner)) } } } diff --git a/src/client/graphics/TerrainRenderer.ts b/src/client/graphics/TerrainRenderer.ts new file mode 100644 index 000000000..cbd266462 --- /dev/null +++ b/src/client/graphics/TerrainRenderer.ts @@ -0,0 +1,37 @@ +import {inherits} from "util" +import {Game} from "../../core/Game"; + +export class TerrainRenderer { + private canvas: HTMLCanvasElement + private context: CanvasRenderingContext2D + private imageData: ImageData + + + constructor(private game: Game) { } + + init() { + this.canvas = document.createElement('canvas'); + this.context = this.canvas.getContext("2d") + + this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height()) + this.initImageData() + this.canvas = document.createElement('canvas'); + const backgroundCtx = this.canvas.getContext('2d'); + this.canvas.width = this.game.width(); + this.canvas.height = this.game.height(); + backgroundCtx.putImageData(this.imageData, 0, 0); + } + + initImageData() { + const theme = this.game.config().theme() + this.game.forEachTile((tile) => { + let terrainColor = theme.terrainColor(tile) + const index = (tile.cell().y * this.game.width()) + tile.cell().x + const offset = index * 4 + this.imageData.data[offset] = terrainColor.rgba.r; + this.imageData.data[offset + 1] = terrainColor.rgba.g; + this.imageData.data[offset + 2] = terrainColor.rgba.b; + this.imageData.data[offset + 3] = terrainColor.rgba.a * 255 | 0 + }) + } +} \ No newline at end of file diff --git a/src/core/Game.ts b/src/core/Game.ts index c96a9da38..c722670f0 100644 --- a/src/core/Game.ts +++ b/src/core/Game.ts @@ -1,3 +1,4 @@ +import {Config} from "./configuration/Config" import {GameEvent} from "./EventBus" import {ClientID, GameID} from "./Schemas" @@ -120,6 +121,7 @@ export interface Game { terraNullius(): TerraNullius tick(): void addExecution(...exec: Execution[]): void + config(): Config } export interface MutableGame extends Game { diff --git a/src/core/GameImpl.ts b/src/core/GameImpl.ts index 121e13f25..a9155e63f 100644 --- a/src/core/GameImpl.ts +++ b/src/core/GameImpl.ts @@ -1,9 +1,10 @@ +import {Config} from "./configuration/Config"; import {EventBus} from "./EventBus"; import {Cell, Execution, MutableGame, Game, MutablePlayer, PlayerEvent, PlayerID, PlayerInfo, Player, TerraNullius, Tile, TileEvent, Boat, MutableBoat, BoatEvent} from "./Game"; import {Terrain, TerrainMap, TerrainType} from "./TerrainMapLoader"; -export function createGame(terrainMap: TerrainMap, eventBus: EventBus): Game { - return new GameImpl(terrainMap, eventBus) +export function createGame(terrainMap: TerrainMap, eventBus: EventBus, config: Config): Game { + return new GameImpl(terrainMap, eventBus, config) } type CellString = string @@ -19,6 +20,7 @@ class TileImpl implements Tile { private readonly _cell: Cell, private readonly _terrain: Terrain ) { } + isLake(): boolean { return !this.isLand() && !this.isOcean() } @@ -228,7 +230,7 @@ export class GameImpl implements MutableGame { private _height: number _terraNullius: TerraNulliusImpl - constructor(terrainMap: TerrainMap, private eventBus: EventBus) { + constructor(terrainMap: TerrainMap, private eventBus: EventBus, private _config: Config) { this._terraNullius = new TerraNulliusImpl(this) this._width = terrainMap.width(); this._height = terrainMap.height(); @@ -241,6 +243,9 @@ export class GameImpl implements MutableGame { } } } + config(): Config { + return this._config + } tick() { this.executions().forEach(e => e.tick(this.ticks)) @@ -354,6 +359,23 @@ export class GameImpl implements MutableGame { return ns } + neighborsWithDiag(tile: Tile): Tile[] { + const x = tile.cell().x + const y = tile.cell().y + const ns: TileImpl[] = [] + for (let dx = -1; dx <= 1; dx++) { + for (let dy = -1; dy <= 1; dy++) { + if (dx === 0 && dy === 0) continue // Skip the center tile + const newX = x + dx + const newY = y + dy + if (newX >= 0 && newX < this._width && newY >= 0 && newY < this._height) { + ns.push(this.map[newX][newY]) + } + } + } + return ns + } + private assertIsOnMap(cell: Cell) { if (!this.isOnMap(cell)) { throw new Error(`cell ${cell.toString()} is not on map`) diff --git a/src/core/execution/AttackExecution.ts b/src/core/execution/AttackExecution.ts index 740c68dbf..4ca090421 100644 --- a/src/core/execution/AttackExecution.ts +++ b/src/core/execution/AttackExecution.ts @@ -22,7 +22,6 @@ export class AttackExecution implements Execution { private _ownerID: PlayerID, private targetID: PlayerID | null, private targetCell: Cell | null, - private config: Config ) { } init(mg: MutableGame, ticks: number) { @@ -68,11 +67,11 @@ export class AttackExecution implements Execution { if (!this.active) { return } - if (ticks < this.config.turnsUntilGameStart()) { + if (ticks < this.mg.config().turnsUntilGameStart()) { return } - let numTilesPerTick = this.config.player().attackTilesPerTick(this._owner, this.target, this.numTilesWithEnemy) + let numTilesPerTick = this.mg.config().player().attackTilesPerTick(this._owner, this.target, this.numTilesWithEnemy) if (this.targetCell != null) { numTilesPerTick /= 2 } @@ -107,7 +106,7 @@ export class AttackExecution implements Execution { badTiles++ continue } - const {attackerTroopLoss, defenderTroopLoss, tilesPerTickUsed} = this.config.player().attackLogic(this._owner, this.target, tileToConquer) + const {attackerTroopLoss, defenderTroopLoss, tilesPerTickUsed} = this.mg.config().player().attackLogic(this._owner, this.target, tileToConquer) numTilesPerTick -= tilesPerTickUsed this.troops -= attackerTroopLoss if (this.target.isPlayer()) { diff --git a/src/core/execution/BoatAttackExecution.ts b/src/core/execution/BoatAttackExecution.ts index d942751b0..2fc67f8e6 100644 --- a/src/core/execution/BoatAttackExecution.ts +++ b/src/core/execution/BoatAttackExecution.ts @@ -36,7 +36,6 @@ export class BoatAttackExecution implements Execution { private targetID: PlayerID | null, private cell: Cell, private troops: number, - private config: Config ) { } init(mg: MutableGame, ticks: number) { @@ -102,7 +101,7 @@ export class BoatAttackExecution implements Execution { return } this.attacker.conquer(this.dst) - this.mg.addExecution(new AttackExecution(this.troops, this.attacker.id(), this.targetID, null, this.config)) + this.mg.addExecution(new AttackExecution(this.troops, this.attacker.id(), this.targetID, null)) this.boat.delete() this.active = false return diff --git a/src/core/execution/BotExecution.ts b/src/core/execution/BotExecution.ts index 357dcc028..d087bfcc9 100644 --- a/src/core/execution/BotExecution.ts +++ b/src/core/execution/BotExecution.ts @@ -8,24 +8,23 @@ export class BotExecution implements Execution { private active = true private random: PseudoRandom; private attackRate: number - private gs: MutableGame + private mg: MutableGame private neighborsTerra = true - constructor(private bot: MutablePlayer, private config: Config) { - + constructor(private bot: MutablePlayer) { this.random = new PseudoRandom(bot.id()) this.attackRate = this.random.nextInt(10, 50) } - init(gs: MutableGame, ticks: number) { - this.gs = gs + init(mg: MutableGame, ticks: number) { + this.mg = mg // this.neighborsTerra = this.bot.neighbors().filter(n => n == this.gs.terraNullius()).length > 0 } tick(ticks: number) { - if (ticks < this.config.turnsUntilGameStart()) { + if (ticks < this.mg.config().turnsUntilGameStart()) { return } @@ -38,8 +37,8 @@ export class BotExecution implements Execution { if (this.neighborsTerra) { for (const b of this.bot.borderTiles()) { for (const n of b.neighbors()) { - if (n.owner() == this.gs.terraNullius() && n.isLand()) { - this.sendAttack(this.gs.terraNullius()) + if (n.owner() == this.mg.terraNullius() && n.isLand()) { + this.sendAttack(this.mg.terraNullius()) return } } @@ -57,12 +56,11 @@ export class BotExecution implements Execution { } sendAttack(toAttack: Player | TerraNullius) { - this.gs.addExecution(new AttackExecution( + this.mg.addExecution(new AttackExecution( this.bot.troops() / 20, this.bot.id(), toAttack.isPlayer() ? toAttack.id() : null, - null, - this.config + null )) } diff --git a/src/core/execution/Executor.ts b/src/core/execution/Executor.ts index dcae2545c..54ef68955 100644 --- a/src/core/execution/Executor.ts +++ b/src/core/execution/Executor.ts @@ -5,12 +5,11 @@ import {AttackExecution} from "./AttackExecution"; import {SpawnExecution} from "./SpawnExecution"; import {BotSpawner} from "./BotSpawner"; import {BoatAttackExecution} from "./BoatAttackExecution"; -import {Config, PlayerConfig} from "../configuration/Config"; export class Executor { - constructor(private gs: Game, private config: Config) { + constructor(private gs: Game) { } @@ -24,22 +23,19 @@ export class Executor { intent.troops, intent.attackerID, intent.targetID, - new Cell(intent.targetX, intent.targetY), - this.config + new Cell(intent.targetX, intent.targetY) ) } else if (intent.type == "spawn") { return new SpawnExecution( new PlayerInfo(intent.name, intent.isBot, intent.clientID), - new Cell(intent.x, intent.y), - this.config + new Cell(intent.x, intent.y) ) } else if (intent.type == "boat") { return new BoatAttackExecution( intent.attackerID, intent.targetID, new Cell(intent.x, intent.y), - intent.troops, - this.config + intent.troops ) } else { throw new Error(`intent type ${intent} not found`) diff --git a/src/core/execution/PlayerExecution.ts b/src/core/execution/PlayerExecution.ts index 20fbc622b..c8c02bfd2 100644 --- a/src/core/execution/PlayerExecution.ts +++ b/src/core/execution/PlayerExecution.ts @@ -4,12 +4,14 @@ import {Execution, MutableGame, MutablePlayer, PlayerID} from "../Game" export class PlayerExecution implements Execution { private player: MutablePlayer + private config: Config - constructor(private playerID: PlayerID, private config: Config) { + constructor(private playerID: PlayerID) { } - init(gs: MutableGame, ticks: number) { - this.player = gs.player(this.playerID) + init(mg: MutableGame, ticks: number) { + this.config = mg.config() + this.player = mg.player(this.playerID) } tick(ticks: number) { diff --git a/src/core/execution/SpawnExecution.ts b/src/core/execution/SpawnExecution.ts index d4ea07e77..bb41a5dfd 100644 --- a/src/core/execution/SpawnExecution.ts +++ b/src/core/execution/SpawnExecution.ts @@ -7,30 +7,29 @@ import {getSpawnCells} from "./Util" export class SpawnExecution implements Execution { active: boolean = true - private gs: MutableGame + private mg: MutableGame constructor( private playerInfo: PlayerInfo, - private cell: Cell, - private config: Config + private cell: Cell ) { } - init(gs: MutableGame, ticks: number) { - this.gs = gs + init(mg: MutableGame, ticks: number) { + this.mg = mg } tick(ticks: number) { if (!this.isActive()) { return } - const player = this.gs.addPlayer(this.playerInfo, this.config.player().startTroops(this.playerInfo)) - getSpawnCells(this.gs, this.cell).forEach(c => { - player.conquer(this.gs.tile(c)) + const player = this.mg.addPlayer(this.playerInfo, this.mg.config().player().startTroops(this.playerInfo)) + getSpawnCells(this.mg, this.cell).forEach(c => { + player.conquer(this.mg.tile(c)) }) - this.gs.addExecution(new PlayerExecution(player.id(), this.config)) + this.mg.addExecution(new PlayerExecution(player.id())) if (player.info().isBot) { - this.gs.addExecution(new BotExecution(player, this.config)) + this.mg.addExecution(new BotExecution(player)) } this.active = false }