create troop slider

This commit is contained in:
Evan
2024-10-27 11:21:01 -07:00
parent df0b5a91ac
commit 2afecf0f39
17 changed files with 2673 additions and 2401 deletions
+2470 -2324
View File
File diff suppressed because it is too large Load Diff
+21 -5
View File
@@ -1,8 +1,8 @@
import {Config} from "../core/configuration/Config" import { Config } from "../core/configuration/Config"
import {EventBus, GameEvent} from "../core/EventBus" import { EventBus, GameEvent } from "../core/EventBus"
import {AllianceRequest, AllPlayers, Cell, Player, PlayerID, PlayerType} from "../core/game/Game" import { AllianceRequest, AllPlayers, Cell, Player, PlayerID, PlayerType } from "../core/game/Game"
import {ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema} from "../core/Schemas" import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema } from "../core/Schemas"
import {LocalServer} from "./LocalServer" import { LocalServer } from "./LocalServer"
export class SendAllianceRequestIntentEvent implements GameEvent { export class SendAllianceRequestIntentEvent implements GameEvent {
@@ -76,6 +76,12 @@ export class SendNukeIntentEvent implements GameEvent {
) { } ) { }
} }
export class SendSetTargetTroopRatioEvent implements GameEvent {
constructor(
public readonly ratio: number,
) { }
}
export class Transport { export class Transport {
private socket: WebSocket private socket: WebSocket
@@ -108,6 +114,7 @@ export class Transport {
this.eventBus.on(SendEmojiIntentEvent, (e) => this.onSendEmojiIntent(e)) this.eventBus.on(SendEmojiIntentEvent, (e) => this.onSendEmojiIntent(e))
this.eventBus.on(SendDonateIntentEvent, (e) => this.onSendDonateIntent(e)) this.eventBus.on(SendDonateIntentEvent, (e) => this.onSendDonateIntent(e))
this.eventBus.on(SendNukeIntentEvent, (e) => this.onSendNukeIntent(e)) this.eventBus.on(SendNukeIntentEvent, (e) => this.onSendNukeIntent(e))
this.eventBus.on(SendSetTargetTroopRatioEvent, (e) => this.onSendSetTargetTroopRatioEvent(e))
} }
connect(onconnect: () => void, onmessage: (message: ServerMessage) => void) { connect(onconnect: () => void, onmessage: (message: ServerMessage) => void) {
@@ -298,6 +305,15 @@ export class Transport {
}) })
} }
private onSendSetTargetTroopRatioEvent(event: SendSetTargetTroopRatioEvent) {
this.sendIntent({
type: "troop_ratio",
clientID: this.clientID,
player: this.playerID,
ratio: event.ratio,
})
}
private sendIntent(intent: Intent) { private sendIntent(intent: Intent) {
if (this.isLocal || this.socket.readyState === WebSocket.OPEN) { if (this.isLocal || this.socket.readyState === WebSocket.OPEN) {
const msg = ClientIntentMessageSchema.parse({ const msg = ClientIntentMessageSchema.parse({
+10 -10
View File
@@ -1,16 +1,16 @@
import {AllPlayers, Cell, Game, Player, PlayerType} from "../../../core/game/Game" import { AllPlayers, Cell, Game, Player, PlayerType } from "../../../core/game/Game"
import {PseudoRandom} from "../../../core/PseudoRandom" import { PseudoRandom } from "../../../core/PseudoRandom"
import {calculateBoundingBox} from "../../../core/Util" import { calculateBoundingBox } from "../../../core/Util"
import {Theme} from "../../../core/configuration/Config" import { Theme } from "../../../core/configuration/Config"
import {Layer} from "./Layer" import { Layer } from "./Layer"
import {placeName} from "../NameBoxCalculator" import { placeName } from "../NameBoxCalculator"
import {TransformHandler} from "../TransformHandler" import { TransformHandler } from "../TransformHandler"
import {renderTroops} from "../Utils" import { renderTroops } from "../Utils"
import traitorIcon from '../../../../resources/images/TraitorIcon.png'; import traitorIcon from '../../../../resources/images/TraitorIcon.png';
import allianceIcon from '../../../../resources/images/AllianceIcon.png'; import allianceIcon from '../../../../resources/images/AllianceIcon.png';
import crownIcon from '../../../../resources/images/CrownIcon.png'; import crownIcon from '../../../../resources/images/CrownIcon.png';
import targetIcon from '../../../../resources/images/TargetIcon.png'; import targetIcon from '../../../../resources/images/TargetIcon.png';
import {ClientID} from "../../../core/Schemas" import { ClientID } from "../../../core/Schemas"
class RenderInfo { class RenderInfo {
@@ -19,7 +19,7 @@ class RenderInfo {
public player: Player, public player: Player,
public lastRenderCalc: number, public lastRenderCalc: number,
public lastBoundingCalculated: number, public lastBoundingCalculated: number,
public boundingBox: {min: Cell, max: Cell}, public boundingBox: { min: Cell, max: Cell },
public location: Cell, public location: Cell,
public fontSize: number public fontSize: number
) { } ) { }
+15 -1
View File
@@ -14,6 +14,7 @@ export type Intent = SpawnIntent
| EmojiIntent | EmojiIntent
| DonateIntent | DonateIntent
| NukeIntent | NukeIntent
| TargetTroopRatioIntent
export type AttackIntent = z.infer<typeof AttackIntentSchema> export type AttackIntent = z.infer<typeof AttackIntentSchema>
export type SpawnIntent = z.infer<typeof SpawnIntentSchema> export type SpawnIntent = z.infer<typeof SpawnIntentSchema>
@@ -25,6 +26,7 @@ export type TargetPlayerIntent = z.infer<typeof TargetPlayerIntentSchema>
export type EmojiIntent = z.infer<typeof EmojiIntentSchema> export type EmojiIntent = z.infer<typeof EmojiIntentSchema>
export type DonateIntent = z.infer<typeof DonateIntentSchema> export type DonateIntent = z.infer<typeof DonateIntentSchema>
export type NukeIntent = z.infer<typeof NukeIntentSchema> export type NukeIntent = z.infer<typeof NukeIntentSchema>
export type TargetTroopRatioIntent = z.infer<typeof TargetTroopRatioSchema>
export type Turn = z.infer<typeof TurnSchema> export type Turn = z.infer<typeof TurnSchema>
export type GameConfig = z.infer<typeof GameConfigSchema> export type GameConfig = z.infer<typeof GameConfigSchema>
@@ -65,7 +67,7 @@ const EmojiSchema = z.string().refine(
); );
// Zod schemas // Zod schemas
const BaseIntentSchema = z.object({ const BaseIntentSchema = z.object({
type: z.enum(['attack', 'spawn', 'boat', 'name', 'targetPlayer', 'emoji']), type: z.enum(['attack', 'spawn', 'boat', 'name', 'targetPlayer', 'emoji', 'nuke', 'troop_ratio']),
clientID: z.string(), clientID: z.string(),
}); });
@@ -99,6 +101,11 @@ export const BoatAttackIntentSchema = BaseIntentSchema.extend({
y: z.number(), y: z.number(),
}) })
export const UpdateNameIntentSchema = BaseIntentSchema.extend({
type: z.literal('updateName'),
name: z.string(),
})
export const AllianceRequestIntentSchema = BaseIntentSchema.extend({ export const AllianceRequestIntentSchema = BaseIntentSchema.extend({
type: z.literal('allianceRequest'), type: z.literal('allianceRequest'),
requestor: z.string(), requestor: z.string(),
@@ -146,6 +153,12 @@ export const NukeIntentSchema = BaseIntentSchema.extend({
magnitude: z.number().nullable(), magnitude: z.number().nullable(),
}) })
export const TargetTroopRatioSchema = BaseIntentSchema.extend({
type: z.literal('troop_ratio'),
player: z.string(),
ratio: z.number().min(0).max(1),
})
const IntentSchema = z.union([ const IntentSchema = z.union([
AttackIntentSchema, AttackIntentSchema,
SpawnIntentSchema, SpawnIntentSchema,
@@ -157,6 +170,7 @@ const IntentSchema = z.union([
EmojiIntentSchema, EmojiIntentSchema,
DonateIntentSchema, DonateIntentSchema,
NukeIntentSchema, NukeIntentSchema,
TargetTroopRatioSchema,
]); ]);
const TurnSchema = z.object({ const TurnSchema = z.object({
+10 -8
View File
@@ -1,8 +1,8 @@
import {Player, PlayerID, PlayerInfo, TerraNullius, Tick, Tile} from "../game/Game"; import { Player, PlayerID, PlayerInfo, TerraNullius, Tick, Tile } from "../game/Game";
import {Colord, colord} from "colord"; import { Colord, colord } from "colord";
import {devConfig} from "./DevConfig"; import { devConfig } from "./DevConfig";
import {defaultConfig} from "./DefaultConfig"; import { defaultConfig } from "./DefaultConfig";
import {GameID} from "../Schemas"; import { GameID } from "../Schemas";
export enum GameEnv { export enum GameEnv {
Dev, Dev,
@@ -33,8 +33,10 @@ export interface Config {
numBots(): number numBots(): number
numSpawnPhaseTurns(): number numSpawnPhaseTurns(): number
startTroops(playerInfo: PlayerInfo): number startManpower(playerInfo: PlayerInfo): number
troopAdditionRate(player: Player): number manpowerAdditionRate(player: Player): number
goldAdditionRate(player: Player): number
troopAdjustmentRate(player: Player): number
attackTilesPerTick(attacker: Player, defender: Player | TerraNullius, numAdjacentTilesWithEnemy: number): number attackTilesPerTick(attacker: Player, defender: Player | TerraNullius, numAdjacentTilesWithEnemy: number): number
attackLogic(attackTroops: number, attacker: Player, defender: Player | TerraNullius, tileToConquer: Tile): { attackLogic(attackTroops: number, attacker: Player, defender: Player | TerraNullius, tileToConquer: Tile): {
attackerTroopLoss: number, attackerTroopLoss: number,
@@ -42,7 +44,7 @@ export interface Config {
tilesPerTickUsed: number tilesPerTickUsed: number
} }
attackAmount(attacker: Player, defender: Player | TerraNullius): number attackAmount(attacker: Player, defender: Player | TerraNullius): number
maxTroops(player: Player): number maxManpower(player: Player): number
boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number
boatMaxDistance(): number boatMaxDistance(): number
boatMaxNumber(): number boatMaxNumber(): number
+26 -8
View File
@@ -95,7 +95,7 @@ export class DefaultConfig implements Config {
return { return {
attackerTroopLoss: attacker.type() == PlayerType.Bot ? mag / 10 : mag / 5, attackerTroopLoss: attacker.type() == PlayerType.Bot ? mag / 10 : mag / 5,
defenderTroopLoss: 0, defenderTroopLoss: 0,
tilesPerTickUsed: within(this.startTroops(attacker.info()) / (attackTroops * 5), .2, 3) * Math.max(10, speed / 1.5) tilesPerTickUsed: within(this.startManpower(attacker.info()) / (attackTroops * 5), .2, 3) * Math.max(10, speed / 1.5)
} }
} }
} }
@@ -120,7 +120,7 @@ export class DefaultConfig implements Config {
} }
} }
startTroops(playerInfo: PlayerInfo): number { startManpower(playerInfo: PlayerInfo): number {
if (playerInfo.playerType == PlayerType.Bot) { if (playerInfo.playerType == PlayerType.Bot) {
return 10000 return 10000
} }
@@ -130,16 +130,17 @@ export class DefaultConfig implements Config {
return 25000 return 25000
} }
maxTroops(player: Player): number { maxManpower(player: Player): number {
const troops = Math.sqrt(player.numTilesOwned()) * 3000 + 50000 let max = Math.sqrt(player.numTilesOwned()) * 3000 + 50000
const troops = Math.min(max, 2_000_000)
if (player.type() == PlayerType.Bot) { if (player.type() == PlayerType.Bot) {
return troops return troops
} }
return troops * 2 return troops * 2
} }
troopAdditionRate(player: Player): number { manpowerAdditionRate(player: Player): number {
let max = this.maxTroops(player) let max = this.maxManpower(player)
let toAdd = 10 + (player.troops() + Math.sqrt(player.troops() * player.numTilesOwned())) / 100 let toAdd = 10 + (player.troops() + Math.sqrt(player.troops() * player.numTilesOwned())) / 100
@@ -154,10 +155,27 @@ export class DefaultConfig implements Config {
if (player.type() == PlayerType.Bot) { if (player.type() == PlayerType.Bot) {
toAdd *= .7 toAdd *= .7
} }
return toAdd
return Math.min(player.troops() + toAdd, max) }
goldAdditionRate(player: Player): number {
return player.numTilesOwned() / 100
}
troopAdjustmentRate(player: Player): number {
const maxDiff = player.manpower() / 250 + this.manpowerAdditionRate(player)
const target = player.manpower() * player.targetTroopRatio()
const diff = target - player.troops()
if (Math.abs(diff) < maxDiff) {
return diff
}
const adjustment = maxDiff * Math.sign(diff)
// Can ramp down troops much faster
if (adjustment < 0) {
return adjustment * 5
}
return adjustment
} }
} }
export const defaultConfig = new DefaultConfig() export const defaultConfig = new DefaultConfig()
+1
View File
@@ -159,6 +159,7 @@ export class AttackExecution implements Execution {
this.troops -= attackerTroopLoss this.troops -= attackerTroopLoss
if (this.target.isPlayer()) { if (this.target.isPlayer()) {
this.target.removeTroops(defenderTroopLoss) this.target.removeTroops(defenderTroopLoss)
this.target.removeManpower(defenderTroopLoss)
} }
this._owner.conquer(tileToConquer) this._owner.conquer(tileToConquer)
this.checkDefenderDead() this.checkDefenderDead()
+9 -9
View File
@@ -1,8 +1,8 @@
import {PriorityQueue} from "@datastructures-js/priority-queue"; import { PriorityQueue } from "@datastructures-js/priority-queue";
import {Boat, Cell, Execution, MutableBoat, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, TileEvent} from "../game/Game"; import { Boat, Cell, Execution, MutableBoat, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, TileEvent } from "../game/Game";
import {and, bfs, manhattanDistWrapped, sourceDstOceanShore} from "../Util"; import { and, bfs, manhattanDistWrapped, sourceDstOceanShore } from "../Util";
import {AttackExecution} from "./AttackExecution"; import { AttackExecution } from "./AttackExecution";
import {DisplayMessageEvent, MessageType} from "../../client/graphics/layers/EventsDisplay"; import { DisplayMessageEvent, MessageType } from "../../client/graphics/layers/EventsDisplay";
export class BoatAttackExecution implements Execution { export class BoatAttackExecution implements Execution {
@@ -145,14 +145,14 @@ export class BoatAttackExecution implements Execution {
} }
export class AStar { export class AStar {
private openSet: PriorityQueue<{tile: Tile, fScore: number}>; private openSet: PriorityQueue<{ tile: Tile, fScore: number }>;
private cameFrom: Map<Tile, Tile>; private cameFrom: Map<Tile, Tile>;
private gScore: Map<Tile, number>; private gScore: Map<Tile, number>;
private current: Tile | null; private current: Tile | null;
public completed: boolean; public completed: boolean;
constructor(private src: Tile, private dst: Tile) { constructor(private src: Tile, private dst: Tile) {
this.openSet = new PriorityQueue<{tile: Tile, fScore: number}>( this.openSet = new PriorityQueue<{ tile: Tile, fScore: number }>(
(a, b) => a.fScore - b.fScore (a, b) => a.fScore - b.fScore
); );
this.cameFrom = new Map<Tile, Tile>(); this.cameFrom = new Map<Tile, Tile>();
@@ -161,7 +161,7 @@ export class AStar {
this.completed = false; this.completed = false;
this.gScore.set(src, 0); this.gScore.set(src, 0);
this.openSet.enqueue({tile: src, fScore: this.heuristic(src, dst)}); this.openSet.enqueue({ tile: src, fScore: this.heuristic(src, dst) });
} }
compute(iterations: number): boolean { compute(iterations: number): boolean {
@@ -189,7 +189,7 @@ export class AStar {
this.gScore.set(neighbor, tentativeGScore); this.gScore.set(neighbor, tentativeGScore);
const fScore = tentativeGScore + this.heuristic(neighbor, this.dst); const fScore = tentativeGScore + this.heuristic(neighbor, this.dst);
this.openSet.enqueue({tile: neighbor, fScore: fScore}); this.openSet.enqueue({ tile: neighbor, fScore: fScore });
} }
} }
} }
+2
View File
@@ -71,6 +71,8 @@ export class Executor {
return new DonateExecution(intent.sender, intent.recipient, intent.troops) return new DonateExecution(intent.sender, intent.recipient, intent.troops)
} else if (intent.type == "nuke") { } else if (intent.type == "nuke") {
return new NukeExecution(intent.sender, new Cell(intent.x, intent.y), intent.magnitude) return new NukeExecution(intent.sender, new Cell(intent.x, intent.y), intent.magnitude)
} else if (intent.type == "troop_ratio") {
return new SetTargetTroopRatioExecution(intent.player, intent.ratio)
} else { } else {
throw new Error(`intent type ${intent} not found`) throw new Error(`intent type ${intent} not found`)
} }
+1 -1
View File
@@ -64,7 +64,7 @@ export class FakeHumanExecution implements Execution {
return return
} }
if (this.player.troops() < this.mg.config().maxTroops(this.player) / 4) { if (this.player.troops() < this.mg.config().maxManpower(this.player) / 4) {
return return
} }
+3 -3
View File
@@ -1,6 +1,6 @@
import {Cell, Execution, MutableGame, MutablePlayer, PlayerID, Tile} from "../game/Game"; import { Cell, Execution, MutableGame, MutablePlayer, PlayerID, Tile } from "../game/Game";
import {PseudoRandom} from "../PseudoRandom"; import { PseudoRandom } from "../PseudoRandom";
import {bfs, dist, euclideanDist, manhattanDist} from "../Util"; import { bfs, dist, euclideanDist, manhattanDist } from "../Util";
export class NukeExecution implements Execution { export class NukeExecution implements Execution {
+7 -5
View File
@@ -1,7 +1,7 @@
import {Config} from "../configuration/Config" import { Config } from "../configuration/Config"
import {Execution, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile} from "../game/Game" import { Execution, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile } from "../game/Game"
import {bfs, calculateBoundingBox, getMode, inscribed, simpleHash} from "../Util" import { bfs, calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util"
import {GameImpl} from "../game/GameImpl" import { GameImpl } from "../game/GameImpl"
export class PlayerExecution implements Execution { export class PlayerExecution implements Execution {
@@ -30,7 +30,9 @@ export class PlayerExecution implements Execution {
if (ticks < this.config.numSpawnPhaseTurns()) { if (ticks < this.config.numSpawnPhaseTurns()) {
return return
} }
this.player.setTroops(this.config.troopAdditionRate(this.player)) this.player.addManpower(this.config.manpowerAdditionRate(this.player))
this.player.addGold(this.config.goldAdditionRate(this.player))
this.player.addTroops(this.config.troopAdjustmentRate(this.player))
const alliances = Array.from(this.player.alliances()) const alliances = Array.from(this.player.alliances())
for (const alliance of alliances) { for (const alliance of alliances) {
@@ -0,0 +1,37 @@
import { Execution, MutableGame, MutablePlayer, PlayerID } from "../game/Game";
export class SetTargetTroopRatioExecution implements Execution {
private player: MutablePlayer
private active = true
constructor(private playerID: PlayerID, private targetTroopsRatio: number) { }
init(mg: MutableGame, ticks: number): void {
this.player = mg.player(this.playerID)
}
tick(ticks: number): void {
if (this.targetTroopsRatio < 0 || this.targetTroopsRatio > 1) {
console.warn(`target troop ratio of ${this.targetTroopsRatio} for player ${this.player} invalid`)
} else {
this.player.setTargetTroopRatio(this.targetTroopsRatio)
}
this.active = false
}
owner(): MutablePlayer {
return null
}
isActive(): boolean {
return this.active
}
activeDuringSpawnPhase(): boolean {
return false
}
}
+5 -5
View File
@@ -1,7 +1,7 @@
import {Cell, Execution, MutableGame, MutablePlayer, PlayerInfo, PlayerType} from "../game/Game" import { Cell, Execution, MutableGame, MutablePlayer, PlayerInfo, PlayerType } from "../game/Game"
import {BotExecution} from "./BotExecution" import { BotExecution } from "./BotExecution"
import {PlayerExecution} from "./PlayerExecution" import { PlayerExecution } from "./PlayerExecution"
import {getSpawnCells} from "./Util" import { getSpawnCells } from "./Util"
export class SpawnExecution implements Execution { export class SpawnExecution implements Execution {
@@ -33,7 +33,7 @@ export class SpawnExecution implements Execution {
return return
} }
const player = this.mg.addPlayer(this.playerInfo, this.mg.config().startTroops(this.playerInfo)) const player = this.mg.addPlayer(this.playerInfo, this.mg.config().startManpower(this.playerInfo))
getSpawnCells(this.mg, this.cell).forEach(c => { getSpawnCells(this.mg, this.cell).forEach(c => {
player.conquer(this.mg.tile(c)) player.conquer(this.mg.tile(c))
}) })
+11 -6
View File
@@ -8,7 +8,7 @@ import { DonateExecution } from "../execution/DonateExecution"
export type PlayerID = string export type PlayerID = string
export type Tick = number export type Tick = number
export type Currency = number export type Gold = number
export const AllPlayers = "AllPlayers" as const; export const AllPlayers = "AllPlayers" as const;
@@ -195,11 +195,13 @@ export interface Player {
canSendEmoji(recipient: Player | typeof AllPlayers): boolean canSendEmoji(recipient: Player | typeof AllPlayers): boolean
outgoingEmojis(): EmojiMessage[] outgoingEmojis(): EmojiMessage[]
canDonate(recipient: Player): boolean canDonate(recipient: Player): boolean
currency(): Currency gold(): Gold
manpower(): number
// Number between 0, 1
targetTroopRatio(): number
} }
export interface MutablePlayer extends Player { export interface MutablePlayer extends Player {
setName(name: string): void
setTroops(troops: number): void setTroops(troops: number): void
addTroops(troops: number): void addTroops(troops: number): void
removeTroops(troops: number): number removeTroops(troops: number): number
@@ -220,8 +222,11 @@ export interface MutablePlayer extends Player {
transitiveTargets(): MutablePlayer[] transitiveTargets(): MutablePlayer[]
sendEmoji(recipient: Player | typeof AllPlayers, emoji: string): void sendEmoji(recipient: Player | typeof AllPlayers, emoji: string): void
donate(recipient: MutablePlayer, troops: number): void donate(recipient: MutablePlayer, troops: number): void
addCurrency(toAdd: Currency): void addGold(toAdd: Gold): void
removeCurrency(toRemove: Currency): void removeGold(toRemove: Gold): void
addManpower(toAdd: number): void
removeManpower(toRemove: number): void
setTargetTroopRatio(target: number): void
} }
export interface Game { export interface Game {
@@ -253,7 +258,7 @@ export interface MutableGame extends Game {
player(id: PlayerID): MutablePlayer player(id: PlayerID): MutablePlayer
playerByClientID(id: ClientID): MutablePlayer | null playerByClientID(id: ClientID): MutablePlayer | null
players(): MutablePlayer[] players(): MutablePlayer[]
addPlayer(playerInfo: PlayerInfo, troops: number): MutablePlayer addPlayer(playerInfo: PlayerInfo, manpower: number): MutablePlayer
executions(): Execution[] executions(): Execution[]
boats(): MutableBoat[] boats(): MutableBoat[]
} }
+2 -2
View File
@@ -210,8 +210,8 @@ export class GameImpl implements MutableGame {
return this.player(id) return this.player(id)
} }
addPlayer(playerInfo: PlayerInfo, troops: number): MutablePlayer { addPlayer(playerInfo: PlayerInfo, manpower: number): MutablePlayer {
let player = new PlayerImpl(this, playerInfo, troops) let player = new PlayerImpl(this, playerInfo, manpower)
this._players.set(playerInfo.id, player) this._players.set(playerInfo.id, player)
this.eventBus.emit(new PlayerEvent(player)) this.eventBus.emit(new PlayerEvent(player))
return player return player
+43 -14
View File
@@ -19,7 +19,10 @@ class Donation {
export class PlayerImpl implements MutablePlayer { export class PlayerImpl implements MutablePlayer {
private _currency: number
private _gold: Gold
private _troops: number
private _targetTroopRatio = .5
isTraitor_ = false isTraitor_ = false
@@ -39,7 +42,7 @@ export class PlayerImpl implements MutablePlayer {
private sentDonations: Donation[] = [] private sentDonations: Donation[] = []
constructor(private gs: GameImpl, private readonly playerInfo: PlayerInfo, private _troops) { constructor(private gs: GameImpl, private readonly playerInfo: PlayerInfo, private _manpower: number) {
this._name = playerInfo.name; this._name = playerInfo.name;
this._displayerName = processName(this._name) this._displayerName = processName(this._name)
} }
@@ -63,8 +66,6 @@ export class PlayerImpl implements MutablePlayer {
return this.playerInfo.playerType; return this.playerInfo.playerType;
} }
setName(name: string) {
}
addBoat(troops: number, tile: Tile, target: Player | TerraNullius): BoatImpl { addBoat(troops: number, tile: Tile, target: Player | TerraNullius): BoatImpl {
const b = new BoatImpl(this.gs, tile, troops, this, target as PlayerImpl | TerraNulliusImpl); const b = new BoatImpl(this.gs, tile, troops, this, target as PlayerImpl | TerraNulliusImpl);
@@ -112,10 +113,17 @@ export class PlayerImpl implements MutablePlayer {
} }
addTroops(troops: number): void { addTroops(troops: number): void {
if (troops < 0) {
this.removeTroops(-1 * troops)
return
}
this._troops += Math.floor(troops); this._troops += Math.floor(troops);
} }
removeTroops(troops: number): number { removeTroops(troops: number): number {
const toRemove = Math.floor(Math.min(this._troops, troops)) if (troops <= 1) {
return 0
}
const toRemove = Math.floor(Math.min(this._troops - 1, troops))
this._troops -= toRemove; this._troops -= toRemove;
return toRemove return toRemove
} }
@@ -271,23 +279,44 @@ export class PlayerImpl implements MutablePlayer {
this.gs.displayMessage(`Recieved ${renderTroops(troops)} troops from ${this.name()}`, MessageType.SUCCESS, recipient.id()) this.gs.displayMessage(`Recieved ${renderTroops(troops)} troops from ${this.name()}`, MessageType.SUCCESS, recipient.id())
} }
currency(): Currency { gold(): Gold {
return this._currency return this._gold
} }
addCurrency(toAdd: Currency): void { addGold(toAdd: Gold): void {
this._currency += toAdd this._gold += toAdd
} }
removeCurrency(toRemove: Currency): void { removeGold(toRemove: Gold): void {
if (toRemove > this._currency) { if (toRemove > this._gold) {
throw Error(`cannot remove ${toRemove} from ${this} because only has ${this._currency}`) throw Error(`cannot remove ${toRemove} from ${this} because only has ${this._gold}`)
} }
this._currency -= toRemove this._gold -= toRemove
}
manpower(): number {
return this._manpower
}
addManpower(toAdd: number): void {
this._manpower += toAdd
}
removeManpower(toRemove: number): void {
this._manpower = Math.max(1, this._manpower - toRemove)
}
targetTroopRatio(): number {
return this._targetTroopRatio
}
setTargetTroopRatio(target: number): void {
if (target < 0 || target > 1) {
throw new Error(`invalid targetTroopRatio ${target} set on player ${PlayerImpl}`)
}
this._targetTroopRatio = target
} }
hash(): number { hash(): number {
return simpleHash(this.id()) * (this.troops() + this.numTilesOwned()); return simpleHash(this.id()) * (this.manpower() + this.numTilesOwned());
} }
toString(): string { toString(): string {
return `Player:{name:${this.info().name},clientID:${this.info().clientID},isAlive:${this.isAlive()},troops:${this._troops},numTileOwned:${this.numTilesOwned()}}]`; return `Player:{name:${this.info().name},clientID:${this.info().clientID},isAlive:${this.isAlive()},troops:${this._troops},numTileOwned:${this.numTilesOwned()}}]`;