move unit info to config

This commit is contained in:
Evan
2024-11-16 21:09:25 -08:00
parent e4130c1f04
commit b5a8339eb3
17 changed files with 98 additions and 69 deletions
+1 -1
View File
@@ -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"
+15 -15
View File
@@ -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' : ''}
>
<img src=${item.icon} alt="${item.item.type}" width="40" height="40">
<span class="build-name">${item.item.type}</span>
<img src=${item.icon} alt="${item.unitType}" width="40" height="40">
<span class="build-name">${item.unitType}</span>
<span class="build-cost">
${renderNumber(item.item.cost)}
${renderNumber(this.game ? this.game.unitInfo(item.unitType).cost : 0)}
<img src=${goldCoinIcon} alt="gold" width="12" height="12" style="vertical-align: middle;">
</span>
</button>
+1
View File
@@ -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);
}
+4 -1
View File
@@ -200,4 +200,7 @@ export function processName(name: string): string {
ALLOWED_URI_REGEXP: /^https:\/\/cdn\.jsdelivr\.net\/gh\/twitter\/twemoji/,
ADD_ATTR: ['style']
});
}
}
export function assertNever(x: never): never {
throw new Error('Unexpected value: ' + x);
}
+2 -1
View File
@@ -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 {
+32 -4
View File
@@ -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()
+8 -2
View File
@@ -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
+2 -3
View File
@@ -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()) {
+3 -3
View File
@@ -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()) {
+3 -4
View File
@@ -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`)
+3 -4
View File
@@ -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])
}
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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) {
+9 -10
View File
@@ -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`)
}
}
+6 -16
View File
@@ -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 {
+4 -1
View File
@@ -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_
}
+2 -1
View File
@@ -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;
}