From b5a8339eb3719eb5f17f118ae1ce56856b654f90 Mon Sep 17 00:00:00 2001 From: Evan Date: Sat, 16 Nov 2024 21:09:25 -0800 Subject: [PATCH] move unit info to config --- src/client/Transport.ts | 2 +- .../graphics/layers/radial/BuildMenu.ts | 30 ++++++++-------- src/core/PathFinding.ts | 1 + src/core/Util.ts | 5 ++- src/core/configuration/Config.ts | 3 +- src/core/configuration/DefaultConfig.ts | 36 ++++++++++++++++--- src/core/configuration/DevConfig.ts | 10 ++++-- src/core/execution/DestroyerExecution.ts | 5 ++- src/core/execution/MissileSiloExecution.ts | 6 ++-- src/core/execution/NukeExecution.ts | 7 ++-- src/core/execution/PortExecution.ts | 7 ++-- src/core/execution/TradeShipExecution.ts | 4 +-- src/core/execution/TransportShipExecution.ts | 2 +- src/core/game/BuildValidator.ts | 19 +++++----- src/core/game/Game.ts | 22 ++++-------- src/core/game/GameImpl.ts | 5 ++- src/core/game/PlayerImpl.ts | 3 +- 17 files changed, 98 insertions(+), 69 deletions(-) diff --git a/src/client/Transport.ts b/src/client/Transport.ts index 11ac917b9..c85cab3c2 100644 --- a/src/client/Transport.ts +++ b/src/client/Transport.ts @@ -1,6 +1,6 @@ import { Config } from "../core/configuration/Config" import { EventBus, GameEvent } from "../core/EventBus" -import { AllianceRequest, AllPlayers, Cell, BuildItem, Player, PlayerID, PlayerType, Tile, UnitType } from "../core/game/Game" +import { AllianceRequest, AllPlayers, Cell, Player, PlayerID, PlayerType, Tile, UnitType } from "../core/game/Game" import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, BuildUnitIntentSchema, GameID, Intent, ServerMessage, ServerMessageSchema } from "../core/Schemas" import { LocalServer } from "./LocalServer" diff --git a/src/client/graphics/layers/radial/BuildMenu.ts b/src/client/graphics/layers/radial/BuildMenu.ts index 93f8a487a..2d0e3357a 100644 --- a/src/client/graphics/layers/radial/BuildMenu.ts +++ b/src/client/graphics/layers/radial/BuildMenu.ts @@ -1,7 +1,7 @@ import { LitElement, html, css } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { EventBus } from '../../../../core/EventBus'; -import { Cell, Game, BuildItem, BuildItems, Player, UnitType } from '../../../../core/game/Game'; +import { Cell, Game, Player, UnitType } from '../../../../core/game/Game'; import { BuildUnitIntentEvent, SendNukeIntentEvent } from '../../../Transport'; import nukeIcon from '../../../../../resources/images/NukeIconWhite.svg'; import destroyerIcon from '../../../../../resources/images/DestroyerIconWhite.svg'; @@ -13,16 +13,16 @@ import { BuildValidator } from '../../../../core/game/BuildValidator'; import { ContextMenuEvent } from '../../../InputHandler'; interface BuildItemDisplay { - item: BuildItem + unitType: UnitType icon: string; } const buildTable: BuildItemDisplay[][] = [ [ - { item: BuildItems.Nuke, icon: nukeIcon }, - { item: BuildItems.Destroyer, icon: destroyerIcon }, - { item: BuildItems.Port, icon: portIcon }, - { item: BuildItems.MissileSilo, icon: missileSiloIcon } + { unitType: UnitType.Nuke, icon: nukeIcon }, + { unitType: UnitType.Destroyer, icon: destroyerIcon }, + { unitType: UnitType.Port, icon: portIcon }, + { unitType: UnitType.MissileSilo, icon: missileSiloIcon } ] ]; @@ -152,21 +152,21 @@ export class BuildMenu extends LitElement { if (this.myPlayer == null) { return false } - return this.buildValidator.canBuild(this.myPlayer, this.game.tile(this.clickedCell), item.item) + return this.buildValidator.canBuild(this.myPlayer, this.game.tile(this.clickedCell), item.unitType) } public onBuildSelected = (item: BuildItemDisplay) => { - switch (item.item) { - case BuildItems.Nuke: + switch (item.unitType) { + case UnitType.Nuke: this.eventBus.emit(new SendNukeIntentEvent(this.myPlayer, this.clickedCell, null)) break - case BuildItems.Destroyer: + case UnitType.Destroyer: this.eventBus.emit(new BuildUnitIntentEvent(UnitType.Destroyer, this.clickedCell)) break - case BuildItems.Port: + case UnitType.Port: this.eventBus.emit(new BuildUnitIntentEvent(UnitType.Port, this.clickedCell)) break - case BuildItems.MissileSilo: + case UnitType.MissileSilo: this.eventBus.emit(new BuildUnitIntentEvent(UnitType.MissileSilo, this.clickedCell)) } this.hideMenu() @@ -184,10 +184,10 @@ export class BuildMenu extends LitElement { ?disabled=${!this.canBuild(item)} title=${!this.canBuild(item) ? 'Not enough money' : ''} > - ${item.item.type} - ${item.item.type} + ${item.unitType} + ${item.unitType} - ${renderNumber(item.item.cost)} + ${renderNumber(this.game ? this.game.unitInfo(item.unitType).cost : 0)} gold diff --git a/src/core/PathFinding.ts b/src/core/PathFinding.ts index e8dd5a6fc..6549c0a7e 100644 --- a/src/core/PathFinding.ts +++ b/src/core/PathFinding.ts @@ -98,6 +98,7 @@ export class AStar { } private heuristic(a: Tile, b: Tile): number { + // TODO use wrapped return 1.1 * Math.abs(a.cell().x - b.cell().x) + Math.abs(a.cell().y - b.cell().y); } diff --git a/src/core/Util.ts b/src/core/Util.ts index 1b8c4947d..60cd5864d 100644 --- a/src/core/Util.ts +++ b/src/core/Util.ts @@ -200,4 +200,7 @@ export function processName(name: string): string { ALLOWED_URI_REGEXP: /^https:\/\/cdn\.jsdelivr\.net\/gh\/twitter\/twemoji/, ADD_ATTR: ['style'] }); -} \ No newline at end of file +} +export function assertNever(x: never): never { + throw new Error('Unexpected value: ' + x); +} diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 1a1b868af..8c255b768 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -1,4 +1,4 @@ -import { Player, PlayerID, PlayerInfo, TerraNullius, Tick, Tile } from "../game/Game"; +import { Player, PlayerID, PlayerInfo, TerraNullius, Tick, Tile, UnitInfo, UnitType } from "../game/Game"; import { Colord, colord } from "colord"; import { devConfig } from "./DevConfig"; import { defaultConfig } from "./DefaultConfig"; @@ -56,6 +56,7 @@ export interface Config { emojiMessageDuration(): Tick donateCooldown(): Tick defaultDonationAmount(sender: Player): number + unitInfo(type: UnitType): UnitInfo } export interface Theme { diff --git a/src/core/configuration/DefaultConfig.ts b/src/core/configuration/DefaultConfig.ts index 332e4a534..f67fbeee4 100644 --- a/src/core/configuration/DefaultConfig.ts +++ b/src/core/configuration/DefaultConfig.ts @@ -1,12 +1,42 @@ -import { Player, PlayerInfo, PlayerType, TerrainType, TerraNullius, Tick, Tile } from "../game/Game"; +import { Player, PlayerInfo, PlayerType, TerrainType, TerraNullius, Tick, Tile, UnitInfo, UnitType } from "../game/Game"; import { GameID } from "../Schemas"; -import { simpleHash, within } from "../Util"; +import { assertNever, simpleHash, within } from "../Util"; import { Config, Theme } from "./Config"; import { pastelTheme } from "./PastelTheme"; export class DefaultConfig implements Config { + unitInfo(type: UnitType): UnitInfo { + switch (type) { + case UnitType.TransportShip: + return { + cost: 0, + } + case UnitType.Destroyer: + return { + cost: 100_000 + } + case UnitType.Port: + return { + cost: 300_000 + } + case UnitType.Nuke: + return { + cost: 1_000_000 + } + case UnitType.TradeShip: + return { + cost: 0 + } + case UnitType.MissileSilo: + return { + cost: 1_000_000 + } + default: + assertNever(type) + } + } defaultDonationAmount(sender: Player): number { return Math.floor(sender.troops() / 3) } @@ -178,6 +208,4 @@ export class DefaultConfig implements Config { } } - - export const defaultConfig = new DefaultConfig() \ No newline at end of file diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 329de537d..8c4f53e3a 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -1,12 +1,18 @@ -import { PlayerInfo } from "../game/Game"; +import { PlayerInfo, UnitInfo, UnitType } from "../game/Game"; import { DefaultConfig } from "./DefaultConfig"; export const devConfig = new class extends DefaultConfig { + unitInfo(type: UnitType): UnitInfo { + const info = super.unitInfo(type) + info.cost = 0 + return info + } + percentageTilesOwnedToWin(): number { return 95 } numSpawnPhaseTurns(): number { - return 80 + return 40 } gameCreationRate(): number { return 20 * 1000 diff --git a/src/core/execution/DestroyerExecution.ts b/src/core/execution/DestroyerExecution.ts index ec58a3b3c..1f78d1586 100644 --- a/src/core/execution/DestroyerExecution.ts +++ b/src/core/execution/DestroyerExecution.ts @@ -1,4 +1,4 @@ -import { BuildItems, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game"; +import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game"; import { AStar, PathFinder } from "../PathFinding"; import { PseudoRandom } from "../PseudoRandom"; import { distSort, distSortUnit, manhattanDist } from "../Util"; @@ -44,8 +44,7 @@ export class DestroyerExecution implements Execution { this.active = false return } - this.destroyer = this._owner.addUnit(UnitType.Destroyer, 0, spawns[0]) - this._owner.removeGold(BuildItems.Destroyer.cost) + this.destroyer = this._owner.buildUnit(UnitType.Destroyer, 0, spawns[0]) return } if (!this.destroyer.isActive()) { diff --git a/src/core/execution/MissileSiloExecution.ts b/src/core/execution/MissileSiloExecution.ts index d6afb9db5..365371a40 100644 --- a/src/core/execution/MissileSiloExecution.ts +++ b/src/core/execution/MissileSiloExecution.ts @@ -1,5 +1,5 @@ import { BuildValidator } from "../game/BuildValidator"; -import { AllPlayers, BuildItem, BuildItems, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game"; +import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game"; import { AStar, PathFinder } from "../PathFinding"; import { PseudoRandom } from "../PseudoRandom"; import { bfs, dist, manhattanDist } from "../Util"; @@ -26,12 +26,12 @@ export class MissileSiloExecution implements Execution { tick(ticks: number): void { if (this.silo == null) { const tile = this.mg.tile(this.cell) - if (!new BuildValidator(this.mg).canBuild(this.player, tile, BuildItems.MissileSilo)) { + if (!new BuildValidator(this.mg).canBuild(this.player, tile, UnitType.MissileSilo)) { console.warn(`player ${this.player} cannot build port at ${this.cell}`) this.active = false return } - this.silo = this.player.addUnit(UnitType.MissileSilo, 0, tile) + this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, tile) } if (!this.silo.tile().hasOwner()) { diff --git a/src/core/execution/NukeExecution.ts b/src/core/execution/NukeExecution.ts index 07ff36c9e..12d297e96 100644 --- a/src/core/execution/NukeExecution.ts +++ b/src/core/execution/NukeExecution.ts @@ -1,5 +1,5 @@ import { BuildValidator } from "../game/BuildValidator"; -import { Cell, Execution, BuildItems, MutableGame, MutablePlayer, PlayerID, Tile, MutableUnit, UnitType } from "../game/Game"; +import { Cell, Execution, MutableGame, MutablePlayer, PlayerID, Tile, MutableUnit, UnitType } from "../game/Game"; import { PathFinder } from "../PathFinding"; import { PseudoRandom } from "../PseudoRandom"; import { bfs, dist, distSortUnit, euclideanDist, manhattanDist } from "../Util"; @@ -35,11 +35,10 @@ export class NukeExecution implements Execution { tick(ticks: number): void { if (this.nuke == null) { - if (new BuildValidator(this.mg).canBuild(this.player, this.dst, BuildItems.Nuke)) { + if (new BuildValidator(this.mg).canBuild(this.player, this.dst, UnitType.Nuke)) { const spawn = this.player.units(UnitType.MissileSilo) .sort((a, b) => manhattanDist(a.tile().cell(), this.cell) - manhattanDist(b.tile().cell(), this.cell))[0] - this.nuke = this.player.addUnit(UnitType.Nuke, 0, spawn.tile()) - this.player.removeGold(BuildItems.Nuke.cost) + this.nuke = this.player.buildUnit(UnitType.Nuke, 0, spawn.tile()) this.pathFinder = new PathFinder(10_000, t => true) } else { console.warn(`cannot build Nuke`) diff --git a/src/core/execution/PortExecution.ts b/src/core/execution/PortExecution.ts index c798cc30e..05964344b 100644 --- a/src/core/execution/PortExecution.ts +++ b/src/core/execution/PortExecution.ts @@ -1,5 +1,5 @@ import { BuildValidator } from "../game/BuildValidator"; -import { AllPlayers, BuildItem, BuildItems, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game"; +import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game"; import { AStar, PathFinder } from "../PathFinding"; import { PseudoRandom } from "../PseudoRandom"; import { bfs, dist, manhattanDist } from "../Util"; @@ -30,7 +30,7 @@ export class PortExecution implements Execution { tick(ticks: number): void { if (this.port == null) { const tile = this.mg.tile(this.cell) - if (!new BuildValidator(this.mg).canBuild(this.player, tile, BuildItems.Port)) { + if (!new BuildValidator(this.mg).canBuild(this.player, tile, UnitType.Port)) { console.warn(`player ${this.player} cannot build port at ${this.cell}`) this.active = false return @@ -44,8 +44,7 @@ export class PortExecution implements Execution { this.active = false return } - this.port = this.player.addUnit(UnitType.Port, 0, spawns[0]) - this.player.removeGold(BuildItems.Port.cost) + this.port = this.player.buildUnit(UnitType.Port, 0, spawns[0]) } diff --git a/src/core/execution/TradeShipExecution.ts b/src/core/execution/TradeShipExecution.ts index 74c29cf4a..2578f22ef 100644 --- a/src/core/execution/TradeShipExecution.ts +++ b/src/core/execution/TradeShipExecution.ts @@ -1,5 +1,5 @@ import { BuildValidator } from "../game/BuildValidator"; -import { AllPlayers, BuildItem, BuildItems, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game"; +import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game"; import { AStar, PathFinder } from "../PathFinding"; import { PseudoRandom } from "../PseudoRandom"; import { bfs, dist, manhattanDist } from "../Util"; @@ -28,7 +28,7 @@ export class TradeShipExecution implements Execution { tick(ticks: number): void { if (this.tradeShip == null) { - this.tradeShip = this.player.addUnit(UnitType.TradeShip, 0, this.srcPort.tile()) + this.tradeShip = this.player.buildUnit(UnitType.TradeShip, 0, this.srcPort.tile()) } if (this.index >= this.path.length) { this.active = false diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts index b7634b91b..7708b4dfe 100644 --- a/src/core/execution/TransportShipExecution.ts +++ b/src/core/execution/TransportShipExecution.ts @@ -79,7 +79,7 @@ export class TransportShipExecution implements Execution { } - this.boat = this.attacker.addUnit(UnitType.TransportShip, this.troops, this.src) + this.boat = this.attacker.buildUnit(UnitType.TransportShip, this.troops, this.src) } tick(ticks: number) { diff --git a/src/core/game/BuildValidator.ts b/src/core/game/BuildValidator.ts index 7312b6353..725af9a32 100644 --- a/src/core/game/BuildValidator.ts +++ b/src/core/game/BuildValidator.ts @@ -1,24 +1,23 @@ import { bfs, dist, manhattanDist } from "../Util"; -import { BuildItem, BuildItems, Game, Player, Tile, UnitType } from "./Game"; +import { Game, Player, Tile, UnitType } from "./Game"; export class BuildValidator { constructor(private game: Game) { } - canBuild(player: Player, tile: Tile, item: BuildItem): boolean { - if (!player.isAlive() || player.gold() < item.cost) { + canBuild(player: Player, tile: Tile, unitType: UnitType): boolean { + const cost = this.game.unitInfo(unitType).cost + if (!player.isAlive() || player.gold() < cost) { return false } - switch (item) { - case BuildItems.Nuke: + switch (unitType) { + case UnitType.Nuke: return player.units(UnitType.MissileSilo).length > 0 - case BuildItems.Port: + case UnitType.Port: return this.canBuildPort(player, tile) - case BuildItems.Destroyer: + case UnitType.Destroyer: return this.canBuildDestroyer(player, tile) - case BuildItems.MissileSilo: + case UnitType.MissileSilo: return tile.owner() == player - default: - throw Error(`item ${item.type} not supported`) } } diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 9f5f60e57..d8c62feaa 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -22,6 +22,10 @@ export enum GameMap { Mena } +export interface UnitInfo { + cost: Gold +} + export enum UnitType { TransportShip = "Transport", Destroyer = "Destroyer", @@ -31,20 +35,6 @@ export enum UnitType { MissileSilo = "Missile Silo", } -export class BuildItem { - constructor( - public readonly type: UnitType, - public readonly cost: Gold - ) { } -} - -export const BuildItems = { - Nuke: new BuildItem(UnitType.Nuke, 1_000_000), - Destroyer: new BuildItem(UnitType.Destroyer, 100_000), - Port: new BuildItem(UnitType.Port, 300_000), - MissileSilo: new BuildItem(UnitType.MissileSilo, 1_000_000), -} as const; - export class Nation { constructor( public readonly name: string, @@ -253,8 +243,7 @@ export interface MutablePlayer extends Player { addTroops(troops: number): void removeTroops(troops: number): number - // TODO: make addUnit require gold - addUnit(type: UnitType, troops: number, tile: Tile): MutableUnit + buildUnit(type: UnitType, troops: number, tile: Tile): MutableUnit } export interface Game { @@ -280,6 +269,7 @@ export interface Game { config(): Config displayMessage(message: string, type: MessageType, playerID: PlayerID | null): void units(...types: UnitType[]): Unit[] + unitInfo(type: UnitType): UnitInfo } export interface MutableGame extends Game { diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 2e3dce159..a93d2c61c 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -1,7 +1,7 @@ import { info } from "console"; import { Config } from "../configuration/Config"; import { EventBus } from "../EventBus"; -import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerEvent, PlayerID, PlayerInfo, Player, TerraNullius, Tile, TileEvent, Unit, UnitEvent as UnitEvent, PlayerType, MutableAllianceRequest, AllianceRequestReplyEvent, AllianceRequestEvent, BrokeAllianceEvent, MutableAlliance, Alliance, AllianceExpiredEvent, Nation, UnitType } from "./Game"; +import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerEvent, PlayerID, PlayerInfo, Player, TerraNullius, Tile, TileEvent, Unit, UnitEvent as UnitEvent, PlayerType, MutableAllianceRequest, AllianceRequestReplyEvent, AllianceRequestEvent, BrokeAllianceEvent, MutableAlliance, Alliance, AllianceExpiredEvent, Nation, UnitType, UnitInfo } from "./Game"; import { TerrainMap } from "./TerrainMapLoader"; import { PlayerImpl } from "./PlayerImpl"; import { TerraNulliusImpl } from "./TerraNulliusImpl"; @@ -61,6 +61,9 @@ export class GameImpl implements MutableGame { units(...types: UnitType[]): UnitImpl[] { return Array.from(this._players.values()).flatMap(p => p.units(...types)) } + unitInfo(type: UnitType): UnitInfo { + return this.config().unitInfo(type) + } nations(): Nation[] { return this.nations_ } diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 7e7489df0..4f51a0257 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -73,9 +73,10 @@ export class PlayerImpl implements MutablePlayer { } - addUnit(type: UnitType, troops: number, tile: Tile): UnitImpl { + buildUnit(type: UnitType, troops: number, tile: Tile): UnitImpl { const b = new UnitImpl(type, this.gs, tile, troops, this); this._units.push(b); + this.removeGold(this.gs.unitInfo(type).cost) this.gs.fireUnitUpdateEvent(b, b.tile()); return b; }