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.unitType}
- ${renderNumber(item.item.cost)}
+ ${renderNumber(this.game ? this.game.unitInfo(item.unitType).cost : 0)}
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;
}