store values as bigints to prevent floating point drift

This commit is contained in:
Evan
2025-02-28 18:45:12 -08:00
parent 9b85651ad8
commit d726fd66b5
4 changed files with 55 additions and 31 deletions
+16
View File
@@ -306,3 +306,19 @@ export function generateID(): GameID {
);
return nanoid();
}
export function toInt(num: number): bigint {
return BigInt(Math.floor(num));
}
export function maxInt(a: bigint, b: bigint): bigint {
return a > b ? a : b;
}
export function minInt(a: bigint, b: bigint): bigint {
return a < b ? a : b;
}
export function withinInt(num: bigint, min: bigint, max: bigint): bigint {
const atLeastMin = maxInt(num, min);
return minInt(atLeastMin, max);
}
+1 -1
View File
@@ -76,7 +76,7 @@ export class PlayerExecution implements Execution {
}
const popInc = this.config.populationIncreaseRate(this.player);
this.player.addWorkers(popInc * (1 - this.player.targetTroopRatio())); // (1 - this.player.targetTroopRatio()))
this.player.addWorkers(popInc * (1 - this.player.targetTroopRatio()));
this.player.addTroops(popInc * this.player.targetTroopRatio());
this.player.addGold(this.config.goldAdditionRate(this.player));
const adjustRate = this.config.troopAdjustmentRate(this.player);
+29 -25
View File
@@ -26,9 +26,12 @@ import {
assertNever,
closestOceanShoreFromPlayer,
distSortUnit,
maxInt,
minInt,
simpleHash,
sourceDstOceanShore,
targetTransportTile,
toInt,
within,
} from "../Util";
import { CellString, GameImpl } from "./GameImpl";
@@ -37,7 +40,6 @@ import { MessageType } from "./Game";
import { renderTroops } from "../../client/Utils";
import { TerraNulliusImpl } from "./TerraNulliusImpl";
import { andFN, manhattanDistFN, TileRef } from "./GameMap";
import { Emoji } from "discord.js";
import { AttackImpl } from "./AttackImpl";
interface Target {
@@ -55,10 +57,12 @@ class Donation {
export class PlayerImpl implements Player {
public _lastTileChange: number = 0;
private _gold: Gold;
private _troops: number;
private _workers: number;
private _targetTroopRatio: number = 1;
private _gold: bigint;
private _troops: bigint;
private _workers: bigint;
// 0 to 100
private _targetTroopRatio: bigint = 100n;
isTraitor_ = false;
@@ -88,14 +92,14 @@ export class PlayerImpl implements Player {
private mg: GameImpl,
private _smallID: number,
private readonly playerInfo: PlayerInfo,
startPopulation: number,
startTroops: number,
) {
this._flag = playerInfo.flag;
this._name = playerInfo.name;
this._targetTroopRatio = 1;
this._troops = startPopulation * this._targetTroopRatio;
this._workers = startPopulation * (1 - this._targetTroopRatio);
this._gold = 0;
this._targetTroopRatio = 100n;
this._troops = toInt(startTroops);
this._workers = 0n;
this._gold = 0n;
this._displayName = this._name; // processName(this._name)
}
@@ -117,7 +121,7 @@ export class PlayerImpl implements Player {
playerType: this.type(),
isAlive: this.isAlive(),
tilesOwned: this.numTilesOwned(),
gold: this._gold,
gold: Number(this._gold),
population: this.population(),
workers: this.workers(),
troops: this.troops(),
@@ -234,7 +238,7 @@ export class PlayerImpl implements Player {
return true as const;
}
setTroops(troops: number) {
this._troops = Math.floor(troops);
this._troops = toInt(troops);
}
conquer(tile: TileRef) {
this.mg.conquer(this, tile);
@@ -503,11 +507,11 @@ export class PlayerImpl implements Player {
}
gold(): Gold {
return this._gold;
return Number(this._gold);
}
addGold(toAdd: Gold): void {
this._gold += toAdd;
this._gold += toInt(toAdd);
}
removeGold(toRemove: Gold): void {
@@ -516,24 +520,24 @@ export class PlayerImpl implements Player {
`Player ${this} does not enough gold (${toRemove} vs ${this._gold}))`,
);
}
this._gold -= toRemove;
this._gold -= toInt(toRemove);
}
population(): number {
return this._troops + this._workers;
return Number(this._troops + this._workers);
}
workers(): number {
return Math.max(1, this._workers);
return Math.max(1, Number(this._workers));
}
addWorkers(toAdd: number): void {
this._workers += toAdd;
this._workers += toInt(toAdd);
}
removeWorkers(toRemove: number): void {
this._workers = Math.max(1, this._workers - toRemove);
this._workers = maxInt(1n, this._workers - toInt(toRemove));
}
targetTroopRatio(): number {
return this._targetTroopRatio;
return Number(this._targetTroopRatio) / 100;
}
setTargetTroopRatio(target: number): void {
@@ -542,11 +546,11 @@ export class PlayerImpl implements Player {
`invalid targetTroopRatio ${target} set on player ${PlayerImpl}`,
);
}
this._targetTroopRatio = target;
this._targetTroopRatio = toInt(target * 100);
}
troops(): number {
return this._troops;
return Number(this._troops);
}
addTroops(troops: number): void {
@@ -554,15 +558,15 @@ export class PlayerImpl implements Player {
this.removeTroops(-1 * troops);
return;
}
this._troops += Math.floor(troops);
this._troops += toInt(troops);
}
removeTroops(troops: number): number {
if (troops <= 1) {
return 0;
}
const toRemove = Math.floor(Math.min(this._troops - 1, troops));
const toRemove = minInt(this._troops, toInt(troops));
this._troops -= toRemove;
return toRemove;
return Number(toRemove);
}
captureUnit(unit: Unit): void {
+9 -5
View File
@@ -1,7 +1,7 @@
import { MessageType } from "./Game";
import { UnitUpdate } from "./GameUpdates";
import { GameUpdateType } from "./GameUpdates";
import { simpleHash, within } from "../Util";
import { simpleHash, toInt, within, withinInt } from "../Util";
import { Unit, TerraNullius, UnitType, Player, UnitInfo } from "./Game";
import { GameImpl } from "./GameImpl";
import { PlayerImpl } from "./PlayerImpl";
@@ -9,7 +9,7 @@ import { TileRef } from "./GameMap";
export class UnitImpl implements Unit {
private _active = true;
private _health: number;
private _health: bigint;
private _lastTile: TileRef = null;
private _constructionType: UnitType = undefined;
@@ -37,7 +37,7 @@ export class UnitImpl implements Unit {
isActive: this._active,
pos: this._tile,
lastPos: this._lastTile,
health: this.hasHealth() ? this._health : undefined,
health: this.hasHealth() ? Number(this._health) : undefined,
constructionType: this._constructionType,
};
}
@@ -65,7 +65,7 @@ export class UnitImpl implements Unit {
return this._troops;
}
health(): number {
return this._health;
return Number(this._health);
}
hasHealth(): boolean {
return this.info().maxHealth != undefined;
@@ -94,7 +94,11 @@ export class UnitImpl implements Unit {
}
modifyHealth(delta: number): void {
this._health = within(this._health + delta, 0, this.info().maxHealth ?? 1);
this._health = withinInt(
this._health + toInt(delta),
0n,
toInt(this.info().maxHealth ?? 1),
);
}
delete(displayMessage: boolean = true): void {