Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/core/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,11 @@ export const flattenedEmojiTable: string[] = emojiTable.flat();
export function replacer(_key: string, value: any): any {
return typeof value === "bigint" ? value.toString() : value;
}

export function sigmoid(
value: number,
decayRate: number,
midpoint: number,
): number {
return 1 / (1 + Math.exp(-decayRate * (value - midpoint)));
}
2 changes: 1 addition & 1 deletion src/core/configuration/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export interface Config {
defaultDonationAmount(sender: Player): number;
unitInfo(type: UnitType): UnitInfo;
tradeShipGold(dist: number, numPorts: number): Gold;
tradeShipSpawnRate(numberOfPorts: number): number;
tradeShipSpawnRate(numTradeShips: number, numPlayerPorts: number): number;
trainGold(isFriendly: boolean): Gold;
trainSpawnRate(numberOfStations: number): number;
trainStationMinRange(): number;
Expand Down
49 changes: 31 additions & 18 deletions src/core/configuration/DefaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { PlayerView } from "../game/GameView";
import { UserSettings } from "../game/UserSettings";
import { GameConfig, GameID, TeamCountConfig } from "../Schemas";
import { NukeType } from "../StatsSchemas";
import { assertNever, simpleHash, within } from "../Util";
import { assertNever, sigmoid, simpleHash, within } from "../Util";
import { Config, GameEnv, NukeMagnitude, ServerConfig, Theme } from "./Config";
import { PastelTheme } from "./PastelTheme";
import { PastelThemeDark } from "./PastelThemeDark";
Expand Down Expand Up @@ -343,28 +343,41 @@ export class DefaultConfig implements Config {
}

tradeShipGold(dist: number, numPorts: number): Gold {
const baseGold = Math.floor(50000 + 100 * dist);
const basePortBonus = 0.25;
const diminishingFactor = 0.9;

const baseGold = Math.floor(50_000 + 100 * dist);
const basePortBonus = 0.5;
const diminishingFactor = 0.8;
let totalMultiplier = 1;
for (let i = 0; i < numPorts; i++) {
totalMultiplier += basePortBonus * Math.pow(diminishingFactor, i);
for (let i = 1; i < numPorts; i++) {
totalMultiplier += Math.max(
0.05,
basePortBonus * Math.pow(diminishingFactor, i - 1),
);
}

return BigInt(Math.floor(baseGold * totalMultiplier));
}

// Chance to spawn a trade ship in one second,
tradeShipSpawnRate(numTradeShips: number): number {
if (numTradeShips < 20) {
return 5;
}
if (numTradeShips <= 150) {
const additional = numTradeShips - 20;
return Math.floor(Math.pow(additional, 0.85) + 5);
}
return 1_000_000;
// Probability of trade ship spawn = 1 / tradeShipSpawnRate
tradeShipSpawnRate(numTradeShips: number, numPlayerPorts: number): number {
// Geometric mean of base spawn rate and port multiplier
const combined = Math.sqrt(
this.tradeShipBaseSpawn(numTradeShips) *
this.tradeShipPortMultiplier(numPlayerPorts),
);

return Math.floor(10 / combined);
}

private tradeShipBaseSpawn(numTradeShips: number): number {
const decayRate = Math.log(2) / 30;
return 1 - sigmoid(numTradeShips, decayRate, 100);
}

private tradeShipPortMultiplier(numPlayerPorts: number): number {
// Higher number => faster expected number of trade ships levels off
// This decays gradually to prevent the scenario where more ports => fewer trade ships
// Expected number of trade ships is proportional to numPlayerPorts * tradeShipPortMultiplier
const decayRate = 0.1;
return 1 / (1 + decayRate * numPlayerPorts);
}

unitInfo(type: UnitType): UnitInfo {
Expand Down
5 changes: 4 additions & 1 deletion src/core/execution/PortExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ export class PortExecution implements Execution {

shouldSpawnTradeShip(): boolean {
const numTradeShips = this.mg.unitCount(UnitType.TradeShip);
const spawnRate = this.mg.config().tradeShipSpawnRate(numTradeShips);
const numPlayerPorts = this.player.unitCount(UnitType.Port);
const spawnRate = this.mg
.config()
.tradeShipSpawnRate(numTradeShips, numPlayerPorts);
for (let i = 0; i < this.port!.level(); i++) {
if (this.random.chance(spawnRate)) {
return true;
Expand Down
Loading