mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-09 19:18:04 +00:00
format codebase with prettier
This commit is contained in:
@@ -1,243 +1,298 @@
|
||||
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
||||
import { Cell, Execution, Game, Player, PlayerID, PlayerType, TerrainType, TerraNullius } from "../game/Game";
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerID,
|
||||
PlayerType,
|
||||
TerrainType,
|
||||
TerraNullius,
|
||||
} from "../game/Game";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { MessageType } from '../game/Game';
|
||||
import { MessageType } from "../game/Game";
|
||||
import { renderNumber } from "../../client/Utils";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class AttackExecution implements Execution {
|
||||
private breakAlliance = false
|
||||
private active: boolean = true;
|
||||
private toConquer: PriorityQueue<TileContainer> = new PriorityQueue<TileContainer>((a: TileContainer, b: TileContainer) => {
|
||||
if (a.priority == b.priority) {
|
||||
if (a.tick == b.tick) {
|
||||
return 0
|
||||
// return this.random.nextInt(-1, 1)
|
||||
}
|
||||
return a.tick - b.tick
|
||||
private breakAlliance = false;
|
||||
private active: boolean = true;
|
||||
private toConquer: PriorityQueue<TileContainer> =
|
||||
new PriorityQueue<TileContainer>((a: TileContainer, b: TileContainer) => {
|
||||
if (a.priority == b.priority) {
|
||||
if (a.tick == b.tick) {
|
||||
return 0;
|
||||
// return this.random.nextInt(-1, 1)
|
||||
}
|
||||
return a.priority - b.priority
|
||||
return a.tick - b.tick;
|
||||
}
|
||||
return a.priority - b.priority;
|
||||
});
|
||||
private random = new PseudoRandom(123)
|
||||
private random = new PseudoRandom(123);
|
||||
|
||||
private _owner: Player
|
||||
private target: Player | TerraNullius
|
||||
private _owner: Player;
|
||||
private target: Player | TerraNullius;
|
||||
|
||||
private mg: Game
|
||||
private mg: Game;
|
||||
|
||||
private border = new Set<TileRef>()
|
||||
private border = new Set<TileRef>();
|
||||
|
||||
constructor(
|
||||
private troops: number | null,
|
||||
private _ownerID: PlayerID,
|
||||
private _targetID: PlayerID | null,
|
||||
private sourceTile: TileRef | null,
|
||||
private removeTroops: boolean = true,
|
||||
) { }
|
||||
constructor(
|
||||
private troops: number | null,
|
||||
private _ownerID: PlayerID,
|
||||
private _targetID: PlayerID | null,
|
||||
private sourceTile: TileRef | null,
|
||||
private removeTroops: boolean = true,
|
||||
) {}
|
||||
|
||||
public targetID(): PlayerID {
|
||||
return this._targetID
|
||||
public targetID(): PlayerID {
|
||||
return this._targetID;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number) {
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
this.mg = mg;
|
||||
|
||||
this._owner = mg.player(this._ownerID);
|
||||
this.target =
|
||||
this._targetID == this.mg.terraNullius().id()
|
||||
? mg.terraNullius()
|
||||
: mg.player(this._targetID);
|
||||
|
||||
if (this._owner == this.target) {
|
||||
throw new Error(`Player ${this._owner} cannot attack itself`);
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
if (this.troops == null) {
|
||||
this.troops = this.mg.config().attackAmount(this._owner, this.target);
|
||||
}
|
||||
this.troops = Math.min(this._owner.troops(), this.troops);
|
||||
if (this.removeTroops) {
|
||||
this._owner.removeTroops(this.troops);
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number) {
|
||||
if (!this.active) {
|
||||
return
|
||||
for (const exec of mg.executions()) {
|
||||
if (exec.isActive() && exec instanceof AttackExecution && exec != this) {
|
||||
const otherAttack = exec as AttackExecution;
|
||||
// Target has opposing attack, cancel them out
|
||||
if (
|
||||
this.target.isPlayer() &&
|
||||
otherAttack._targetID == this._ownerID &&
|
||||
this._targetID == otherAttack._ownerID
|
||||
) {
|
||||
if (otherAttack.troops > this.troops) {
|
||||
otherAttack.troops -= this.troops;
|
||||
// otherAttack.calculateToConquer()
|
||||
this.active = false;
|
||||
return;
|
||||
} else {
|
||||
this.troops -= otherAttack.troops;
|
||||
otherAttack.active = false;
|
||||
}
|
||||
}
|
||||
this.mg = mg
|
||||
|
||||
this._owner = mg.player(this._ownerID)
|
||||
this.target = this._targetID == this.mg.terraNullius().id() ? mg.terraNullius() : mg.player(this._targetID)
|
||||
|
||||
if (this._owner == this.target) {
|
||||
throw new Error(`Player ${this._owner} cannot attack itself`)
|
||||
// Existing attack on same target, add troops
|
||||
if (
|
||||
otherAttack._owner == this._owner &&
|
||||
otherAttack._targetID == this._targetID &&
|
||||
this.sourceTile == otherAttack.sourceTile
|
||||
) {
|
||||
otherAttack.troops += this.troops;
|
||||
otherAttack.refreshToConquer();
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.troops == null) {
|
||||
this.troops = this.mg.config().attackAmount(this._owner, this.target)
|
||||
}
|
||||
this.troops = Math.min(this._owner.troops(), this.troops)
|
||||
if (this.removeTroops) {
|
||||
this._owner.removeTroops(this.troops)
|
||||
}
|
||||
|
||||
for (const exec of mg.executions()) {
|
||||
if (exec.isActive() && exec instanceof AttackExecution && exec != this) {
|
||||
const otherAttack = exec as AttackExecution
|
||||
// Target has opposing attack, cancel them out
|
||||
if (this.target.isPlayer() && otherAttack._targetID == this._ownerID && this._targetID == otherAttack._ownerID) {
|
||||
if (otherAttack.troops > this.troops) {
|
||||
otherAttack.troops -= this.troops
|
||||
// otherAttack.calculateToConquer()
|
||||
this.active = false
|
||||
return
|
||||
} else {
|
||||
this.troops -= otherAttack.troops
|
||||
otherAttack.active = false
|
||||
}
|
||||
}
|
||||
// Existing attack on same target, add troops
|
||||
if (otherAttack._owner == this._owner && otherAttack._targetID == this._targetID && this.sourceTile == otherAttack.sourceTile) {
|
||||
otherAttack.troops += this.troops
|
||||
otherAttack.refreshToConquer()
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this._owner.type() != PlayerType.Bot && this.target.isPlayer() && this.target.type() == PlayerType.Human) {
|
||||
mg.displayMessage(`You are being attacked by ${this._owner.displayName()}`, MessageType.ERROR, this._targetID)
|
||||
}
|
||||
if (this.sourceTile != null) {
|
||||
this.addNeighbors(this.sourceTile)
|
||||
} else {
|
||||
this.refreshToConquer()
|
||||
}
|
||||
|
||||
if (this.target.isPlayer()) {
|
||||
if (this._owner.isAlliedWith(this.target)) {
|
||||
// No updates should happen in init.
|
||||
this.breakAlliance = true
|
||||
}
|
||||
this.target.updateRelation(this._owner, -80)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (
|
||||
this._owner.type() != PlayerType.Bot &&
|
||||
this.target.isPlayer() &&
|
||||
this.target.type() == PlayerType.Human
|
||||
) {
|
||||
mg.displayMessage(
|
||||
`You are being attacked by ${this._owner.displayName()}`,
|
||||
MessageType.ERROR,
|
||||
this._targetID,
|
||||
);
|
||||
}
|
||||
if (this.sourceTile != null) {
|
||||
this.addNeighbors(this.sourceTile);
|
||||
} else {
|
||||
this.refreshToConquer();
|
||||
}
|
||||
|
||||
private refreshToConquer() {
|
||||
this.toConquer.clear()
|
||||
this.border.clear()
|
||||
for (const tile of this._owner.borderTiles()) {
|
||||
this.addNeighbors(tile)
|
||||
}
|
||||
if (this.target.isPlayer()) {
|
||||
if (this._owner.isAlliedWith(this.target)) {
|
||||
// No updates should happen in init.
|
||||
this.breakAlliance = true;
|
||||
}
|
||||
this.target.updateRelation(this._owner, -80);
|
||||
}
|
||||
}
|
||||
|
||||
private refreshToConquer() {
|
||||
this.toConquer.clear();
|
||||
this.border.clear();
|
||||
for (const tile of this._owner.borderTiles()) {
|
||||
this.addNeighbors(tile);
|
||||
}
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
const alliance = this._owner.allianceWith(this.target as Player);
|
||||
if (this.breakAlliance && alliance != null) {
|
||||
this.breakAlliance = false;
|
||||
this._owner.breakAlliance(alliance);
|
||||
}
|
||||
if (this.target.isPlayer() && this._owner.isAlliedWith(this.target)) {
|
||||
// In this case a new alliance was created AFTER the attack started.
|
||||
this._owner.addTroops(this.troops);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (!this.active) {
|
||||
return
|
||||
}
|
||||
const alliance = this._owner.allianceWith(this.target as Player)
|
||||
if (this.breakAlliance && alliance != null) {
|
||||
this.breakAlliance = false
|
||||
this._owner.breakAlliance(alliance)
|
||||
}
|
||||
if (this.target.isPlayer() && this._owner.isAlliedWith(this.target)) {
|
||||
// In this case a new alliance was created AFTER the attack started.
|
||||
this._owner.addTroops(this.troops)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
let numTilesPerTick = this.mg
|
||||
.config()
|
||||
.attackTilesPerTick(
|
||||
this.troops,
|
||||
this._owner,
|
||||
this.target,
|
||||
this.border.size + this.random.nextInt(0, 5),
|
||||
);
|
||||
// consolex.log(`num tiles per tick: ${numTilesPerTick}`)
|
||||
// consolex.log(`num execs: ${this.mg.executions().length}`)
|
||||
|
||||
let numTilesPerTick = this.mg.config().attackTilesPerTick(this.troops, this._owner, this.target, this.border.size + this.random.nextInt(0, 5))
|
||||
// consolex.log(`num tiles per tick: ${numTilesPerTick}`)
|
||||
// consolex.log(`num execs: ${this.mg.executions().length}`)
|
||||
while (numTilesPerTick > 0) {
|
||||
if (this.troops < 1) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.toConquer.size() == 0) {
|
||||
this.refreshToConquer();
|
||||
this.active = false;
|
||||
this._owner.addTroops(this.troops);
|
||||
return;
|
||||
}
|
||||
|
||||
while (numTilesPerTick > 0) {
|
||||
if (this.troops < 1) {
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
const tileToConquer = this.toConquer.dequeue().tile;
|
||||
this.border.delete(tileToConquer);
|
||||
|
||||
if (this.toConquer.size() == 0) {
|
||||
this.refreshToConquer()
|
||||
this.active = false
|
||||
this._owner.addTroops(this.troops)
|
||||
return
|
||||
}
|
||||
|
||||
const tileToConquer = this.toConquer.dequeue().tile
|
||||
this.border.delete(tileToConquer)
|
||||
|
||||
const onBorder = this.mg.neighbors(tileToConquer).filter(t => this.mg.owner(t) == this._owner).length > 0
|
||||
if (this.mg.owner(tileToConquer) != this.target || !onBorder) {
|
||||
continue
|
||||
}
|
||||
this.addNeighbors(tileToConquer)
|
||||
const { attackerTroopLoss, defenderTroopLoss, tilesPerTickUsed } = this.mg.config().attackLogic(this.mg, this.troops, this._owner, this.target, tileToConquer)
|
||||
numTilesPerTick -= tilesPerTickUsed
|
||||
this.troops -= attackerTroopLoss
|
||||
if (this.target.isPlayer()) {
|
||||
this.target.removeTroops(defenderTroopLoss)
|
||||
}
|
||||
this._owner.conquer(tileToConquer)
|
||||
this.handleDeadDefender()
|
||||
}
|
||||
const onBorder =
|
||||
this.mg
|
||||
.neighbors(tileToConquer)
|
||||
.filter((t) => this.mg.owner(t) == this._owner).length > 0;
|
||||
if (this.mg.owner(tileToConquer) != this.target || !onBorder) {
|
||||
continue;
|
||||
}
|
||||
this.addNeighbors(tileToConquer);
|
||||
const { attackerTroopLoss, defenderTroopLoss, tilesPerTickUsed } = this.mg
|
||||
.config()
|
||||
.attackLogic(
|
||||
this.mg,
|
||||
this.troops,
|
||||
this._owner,
|
||||
this.target,
|
||||
tileToConquer,
|
||||
);
|
||||
numTilesPerTick -= tilesPerTickUsed;
|
||||
this.troops -= attackerTroopLoss;
|
||||
if (this.target.isPlayer()) {
|
||||
this.target.removeTroops(defenderTroopLoss);
|
||||
}
|
||||
this._owner.conquer(tileToConquer);
|
||||
this.handleDeadDefender();
|
||||
}
|
||||
}
|
||||
|
||||
private addNeighbors(tile: TileRef) {
|
||||
for (const neighbor of this.mg.neighbors(tile)) {
|
||||
if (this.mg.isWater(neighbor) || this.mg.owner(neighbor) != this.target) {
|
||||
continue
|
||||
private addNeighbors(tile: TileRef) {
|
||||
for (const neighbor of this.mg.neighbors(tile)) {
|
||||
if (this.mg.isWater(neighbor) || this.mg.owner(neighbor) != this.target) {
|
||||
continue;
|
||||
}
|
||||
this.border.add(neighbor);
|
||||
let numOwnedByMe = this.mg
|
||||
.neighbors(neighbor)
|
||||
.filter((t) => this.mg.owner(t) == this._owner).length;
|
||||
let dist = 0;
|
||||
if (numOwnedByMe > 2) {
|
||||
numOwnedByMe = 10;
|
||||
}
|
||||
let mag = 0;
|
||||
switch (this.mg.terrainType(tile)) {
|
||||
case TerrainType.Plains:
|
||||
mag = 1;
|
||||
break;
|
||||
case TerrainType.Highland:
|
||||
mag = 1.5;
|
||||
break;
|
||||
case TerrainType.Mountain:
|
||||
mag = 2;
|
||||
break;
|
||||
}
|
||||
this.toConquer.enqueue(
|
||||
new TileContainer(
|
||||
neighbor,
|
||||
dist / 100 + this.random.nextInt(0, 2) - numOwnedByMe + mag,
|
||||
this.mg.ticks(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleDeadDefender() {
|
||||
if (this.target.isPlayer() && this.target.numTilesOwned() < 100) {
|
||||
const gold = this.target.gold();
|
||||
this.mg.displayMessage(
|
||||
`Conquered ${this.target.displayName()} received ${renderNumber(gold)} gold`,
|
||||
MessageType.SUCCESS,
|
||||
this._owner.id(),
|
||||
);
|
||||
this.target.removeGold(gold);
|
||||
this._owner.addGold(gold);
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
for (const tile of this.target.tiles()) {
|
||||
const borders = this.mg
|
||||
.neighbors(tile)
|
||||
.some((t) => this.mg.owner(t) == this._owner);
|
||||
if (borders) {
|
||||
this._owner.conquer(tile);
|
||||
} else {
|
||||
for (const neighbor of this.mg.neighbors(tile)) {
|
||||
const no = this.mg.owner(neighbor);
|
||||
if (no.isPlayer() && no != this.target) {
|
||||
this.mg.player(no.id()).conquer(tile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.border.add(neighbor)
|
||||
let numOwnedByMe = this.mg.neighbors(neighbor)
|
||||
.filter(t => this.mg.owner(t) == this._owner)
|
||||
.length
|
||||
let dist = 0
|
||||
if (numOwnedByMe > 2) {
|
||||
numOwnedByMe = 10
|
||||
}
|
||||
let mag = 0
|
||||
switch (this.mg.terrainType(tile)) {
|
||||
case TerrainType.Plains:
|
||||
mag = 1
|
||||
break
|
||||
case TerrainType.Highland:
|
||||
mag = 1.5
|
||||
break
|
||||
case TerrainType.Mountain:
|
||||
mag = 2
|
||||
break
|
||||
}
|
||||
this.toConquer.enqueue(new TileContainer(
|
||||
neighbor,
|
||||
dist / 100 + this.random.nextInt(0, 2) - numOwnedByMe + mag,
|
||||
this.mg.ticks()
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleDeadDefender() {
|
||||
if (this.target.isPlayer() && this.target.numTilesOwned() < 100) {
|
||||
const gold = this.target.gold()
|
||||
this.mg.displayMessage(`Conquered ${this.target.displayName()} received ${renderNumber(gold)} gold`, MessageType.SUCCESS, this._owner.id())
|
||||
this.target.removeGold(gold)
|
||||
this._owner.addGold(gold)
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
for (const tile of this.target.tiles()) {
|
||||
const borders = this.mg.neighbors(tile).some(t => this.mg.owner(t) == this._owner)
|
||||
if (borders) {
|
||||
this._owner.conquer(tile)
|
||||
} else {
|
||||
for (const neighbor of this.mg.neighbors(tile)) {
|
||||
const no = this.mg.owner(neighbor)
|
||||
if (no.isPlayer() && no != this.target) {
|
||||
this.mg.player(no.id()).conquer(tile)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this._owner
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
owner(): Player {
|
||||
return this._owner;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TileContainer {
|
||||
constructor(public readonly tile: TileRef, public readonly priority: number, public readonly tick: number) { }
|
||||
}
|
||||
constructor(
|
||||
public readonly tile: TileRef,
|
||||
public readonly priority: number,
|
||||
public readonly tick: number,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Cell, Execution, Game, Player, Unit, PlayerID, TerrainType, UnitType } from "../game/Game";
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
Unit,
|
||||
PlayerID,
|
||||
TerrainType,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
@@ -8,133 +17,157 @@ import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class BattleshipExecution implements Execution {
|
||||
private random: PseudoRandom
|
||||
private random: PseudoRandom;
|
||||
|
||||
private _owner: Player
|
||||
private active = true
|
||||
private battleship: Unit = null
|
||||
private mg: Game = null
|
||||
private _owner: Player;
|
||||
private active = true;
|
||||
private battleship: Unit = null;
|
||||
private mg: Game = null;
|
||||
|
||||
private pathfinder: PathFinder
|
||||
private pathfinder: PathFinder;
|
||||
|
||||
private patrolTile: TileRef;
|
||||
private patrolTile: TileRef;
|
||||
|
||||
// TODO: put in config
|
||||
private searchRange = 100
|
||||
private attackRate = 5
|
||||
private lastAttack = 0
|
||||
// TODO: put in config
|
||||
private searchRange = 100;
|
||||
private attackRate = 5;
|
||||
private lastAttack = 0;
|
||||
|
||||
private alreadyTargeted = new Set<Unit>()
|
||||
private alreadyTargeted = new Set<Unit>();
|
||||
|
||||
constructor(
|
||||
private playerID: PlayerID,
|
||||
private patrolCenterTile: TileRef,
|
||||
) { }
|
||||
constructor(
|
||||
private playerID: PlayerID,
|
||||
private patrolCenterTile: TileRef,
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false);
|
||||
this._owner = mg.player(this.playerID);
|
||||
this.mg = mg;
|
||||
this.patrolTile = this.patrolCenterTile;
|
||||
this.random = new PseudoRandom(mg.ticks());
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
||||
this._owner = mg.player(this.playerID)
|
||||
this.mg = mg
|
||||
this.patrolTile = this.patrolCenterTile
|
||||
this.random = new PseudoRandom(mg.ticks())
|
||||
tick(ticks: number): void {
|
||||
this.alreadyTargeted.forEach((u) => {
|
||||
if (!u.isActive()) {
|
||||
this.alreadyTargeted.delete(u);
|
||||
}
|
||||
});
|
||||
if (this.battleship == null) {
|
||||
const spawn = this._owner.canBuild(UnitType.Battleship, this.patrolTile);
|
||||
if (spawn == false) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.battleship = this._owner.buildUnit(UnitType.Battleship, 0, spawn);
|
||||
return;
|
||||
}
|
||||
if (!this.battleship.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
this.alreadyTargeted.forEach(u => {
|
||||
if (!u.isActive()) {
|
||||
this.alreadyTargeted.delete(u)
|
||||
}
|
||||
})
|
||||
if (this.battleship == null) {
|
||||
const spawn = this._owner.canBuild(UnitType.Battleship, this.patrolTile)
|
||||
if (spawn == false) {
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
this.battleship = this._owner.buildUnit(UnitType.Battleship, 0, spawn)
|
||||
return
|
||||
}
|
||||
if (!this.battleship.isActive()) {
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
|
||||
if (this.mg.ticks() % 2 == 0) {
|
||||
const result = this.pathfinder.nextTile(this.battleship.tile(), this.patrolTile)
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.patrolTile = this.randomTile()
|
||||
break
|
||||
case PathFindResultType.NextTile:
|
||||
this.battleship.move(result.tile)
|
||||
break
|
||||
case PathFindResultType.Pending:
|
||||
return
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to patrol tile`)
|
||||
this.patrolTile = this.randomTile()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (this.mg.ticks() - this.lastAttack < this.attackRate) {
|
||||
return
|
||||
}
|
||||
|
||||
let ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
|
||||
.filter(u => this.mg.manhattanDist(u.tile(), this.battleship.tile()) < 100)
|
||||
.filter(u => u.owner() != this.battleship.owner())
|
||||
.filter(u => u != this.battleship)
|
||||
.filter(u => !u.owner().isAlliedWith(this.battleship.owner()))
|
||||
.filter(u => !this.alreadyTargeted.has(u))
|
||||
.sort(distSortUnit(this.mg, this.battleship));
|
||||
|
||||
const friendlyDestroyerNearby = this.battleship.owner().units(UnitType.Destroyer)
|
||||
.filter(d => this.mg.manhattanDist(d.tile(), this.battleship.tile()) < 120)
|
||||
.length > 0
|
||||
|
||||
if (friendlyDestroyerNearby) {
|
||||
// Don't attack trade ships to allow friendly destroyer to capture them
|
||||
ships = ships.filter(s => s.type() != UnitType.TradeShip)
|
||||
}
|
||||
|
||||
if (ships.length > 0) {
|
||||
const toAttack = ships[0]
|
||||
if (!toAttack.hasHealth()) {
|
||||
// Don't send multiple shells to target if it can be one-shotted.
|
||||
this.alreadyTargeted.add(toAttack)
|
||||
}
|
||||
this.lastAttack = this.mg.ticks()
|
||||
this.mg.addExecution(new ShellExecution(this.battleship.tile(), this.battleship.owner(), this.battleship, toAttack))
|
||||
}
|
||||
if (this.mg.ticks() % 2 == 0) {
|
||||
const result = this.pathfinder.nextTile(
|
||||
this.battleship.tile(),
|
||||
this.patrolTile,
|
||||
);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.patrolTile = this.randomTile();
|
||||
break;
|
||||
case PathFindResultType.NextTile:
|
||||
this.battleship.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
return;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to patrol tile`);
|
||||
this.patrolTile = this.randomTile();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this._owner
|
||||
if (this.mg.ticks() - this.lastAttack < this.attackRate) {
|
||||
return;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
let ships = this.mg
|
||||
.units(
|
||||
UnitType.TransportShip,
|
||||
UnitType.Destroyer,
|
||||
UnitType.TradeShip,
|
||||
UnitType.Battleship,
|
||||
)
|
||||
.filter(
|
||||
(u) => this.mg.manhattanDist(u.tile(), this.battleship.tile()) < 100,
|
||||
)
|
||||
.filter((u) => u.owner() != this.battleship.owner())
|
||||
.filter((u) => u != this.battleship)
|
||||
.filter((u) => !u.owner().isAlliedWith(this.battleship.owner()))
|
||||
.filter((u) => !this.alreadyTargeted.has(u))
|
||||
.sort(distSortUnit(this.mg, this.battleship));
|
||||
|
||||
const friendlyDestroyerNearby =
|
||||
this.battleship
|
||||
.owner()
|
||||
.units(UnitType.Destroyer)
|
||||
.filter(
|
||||
(d) => this.mg.manhattanDist(d.tile(), this.battleship.tile()) < 120,
|
||||
).length > 0;
|
||||
|
||||
if (friendlyDestroyerNearby) {
|
||||
// Don't attack trade ships to allow friendly destroyer to capture them
|
||||
ships = ships.filter((s) => s.type() != UnitType.TradeShip);
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
if (ships.length > 0) {
|
||||
const toAttack = ships[0];
|
||||
if (!toAttack.hasHealth()) {
|
||||
// Don't send multiple shells to target if it can be one-shotted.
|
||||
this.alreadyTargeted.add(toAttack);
|
||||
}
|
||||
this.lastAttack = this.mg.ticks();
|
||||
this.mg.addExecution(
|
||||
new ShellExecution(
|
||||
this.battleship.tile(),
|
||||
this.battleship.owner(),
|
||||
this.battleship,
|
||||
toAttack,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
randomTile(): TileRef {
|
||||
while (true) {
|
||||
const x = this.mg.x(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
const y = this.mg.y(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
if (!this.mg.isValidCoord(x, y)) {
|
||||
continue
|
||||
}
|
||||
const tile = this.mg.ref(x, y)
|
||||
if (!this.mg.isOcean(tile)) {
|
||||
continue
|
||||
}
|
||||
return tile
|
||||
}
|
||||
owner(): Player {
|
||||
return this._owner;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
randomTile(): TileRef {
|
||||
while (true) {
|
||||
const x =
|
||||
this.mg.x(this.patrolCenterTile) +
|
||||
this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
|
||||
const y =
|
||||
this.mg.y(this.patrolCenterTile) +
|
||||
this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
|
||||
if (!this.mg.isValidCoord(x, y)) {
|
||||
continue;
|
||||
}
|
||||
const tile = this.mg.ref(x, y);
|
||||
if (!this.mg.isOcean(tile)) {
|
||||
continue;
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +1,118 @@
|
||||
import { Cell, Execution, Game, Player, PlayerType, TerraNullius } from "../game/Game"
|
||||
import { PseudoRandom } from "../PseudoRandom"
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerType,
|
||||
TerraNullius,
|
||||
} from "../game/Game";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { simpleHash } from "../Util";
|
||||
import { AttackExecution } from "./AttackExecution";
|
||||
|
||||
export class BotExecution implements Execution {
|
||||
private active = true;
|
||||
private random: PseudoRandom;
|
||||
private attackRate: number;
|
||||
private mg: Game;
|
||||
private neighborsTerraNullius = true;
|
||||
|
||||
private active = true
|
||||
private random: PseudoRandom;
|
||||
private attackRate: number
|
||||
private mg: Game
|
||||
private neighborsTerraNullius = true
|
||||
constructor(private bot: Player) {
|
||||
this.random = new PseudoRandom(simpleHash(bot.id()));
|
||||
this.attackRate = this.random.nextInt(10, 50);
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number) {
|
||||
this.mg = mg;
|
||||
// this.neighborsTerra = this.bot.neighbors().filter(n => n == this.gs.terraNullius()).length > 0
|
||||
}
|
||||
|
||||
constructor(private bot: Player) {
|
||||
this.random = new PseudoRandom(simpleHash(bot.id()))
|
||||
this.attackRate = this.random.nextInt(10, 50)
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
tick(ticks: number) {
|
||||
if (!this.bot.isAlive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number) {
|
||||
this.mg = mg
|
||||
// this.neighborsTerra = this.bot.neighbors().filter(n => n == this.gs.terraNullius()).length > 0
|
||||
if (ticks % this.attackRate != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (!this.bot.isAlive()) {
|
||||
this.active = false
|
||||
return
|
||||
this.bot.incomingAllianceRequests().forEach((ar) => {
|
||||
if (ar.requestor().isTraitor()) {
|
||||
ar.reject();
|
||||
} else {
|
||||
ar.accept();
|
||||
}
|
||||
});
|
||||
|
||||
const traitors = this.bot
|
||||
.neighbors()
|
||||
.filter((n) => n.isPlayer() && n.isTraitor()) as Player[];
|
||||
if (traitors.length > 0) {
|
||||
const toAttack = this.random.randElement(traitors);
|
||||
const odds = this.bot.isAlliedWith(toAttack) ? 6 : 3;
|
||||
if (this.random.chance(odds)) {
|
||||
this.sendAttack(toAttack);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.neighborsTerraNullius) {
|
||||
for (const b of this.bot.borderTiles()) {
|
||||
for (const n of this.mg.neighbors(b)) {
|
||||
if (!this.mg.hasOwner(n) && this.mg.isLand(n)) {
|
||||
this.sendAttack(this.mg.terraNullius());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ticks % this.attackRate != 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.bot.incomingAllianceRequests().forEach(ar => {
|
||||
if (ar.requestor().isTraitor()) {
|
||||
ar.reject()
|
||||
} else {
|
||||
ar.accept()
|
||||
}
|
||||
})
|
||||
|
||||
const traitors = this.bot.neighbors().filter(n => n.isPlayer() && n.isTraitor()) as Player[]
|
||||
if (traitors.length > 0) {
|
||||
const toAttack = this.random.randElement(traitors)
|
||||
const odds = this.bot.isAlliedWith(toAttack) ? 6 : 3
|
||||
if (this.random.chance(odds)) {
|
||||
this.sendAttack(toAttack)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (this.neighborsTerraNullius) {
|
||||
for (const b of this.bot.borderTiles()) {
|
||||
for (const n of this.mg.neighbors(b)) {
|
||||
if (!this.mg.hasOwner(n) && this.mg.isLand(n)) {
|
||||
this.sendAttack(this.mg.terraNullius())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
this.neighborsTerraNullius = false
|
||||
}
|
||||
|
||||
const border = Array.from(this.bot.borderTiles())
|
||||
.flatMap(t => this.mg.neighbors(t))
|
||||
.filter(t => this.mg.hasOwner(t) && this.mg.owner(t) != this.bot)
|
||||
|
||||
if (border.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const toAttack = border[this.random.nextInt(0, border.length)]
|
||||
const owner = this.mg.owner(toAttack)
|
||||
|
||||
if (owner.isPlayer()) {
|
||||
if (this.bot.isAlliedWith(owner)) {
|
||||
return
|
||||
}
|
||||
if (owner.type() == PlayerType.FakeHuman) {
|
||||
if (!this.random.chance(2)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
this.sendAttack(owner)
|
||||
}
|
||||
this.neighborsTerraNullius = false;
|
||||
}
|
||||
|
||||
sendAttack(toAttack: Player | TerraNullius) {
|
||||
this.mg.addExecution(new AttackExecution(
|
||||
this.bot.troops() / 20,
|
||||
this.bot.id(),
|
||||
toAttack.isPlayer() ? toAttack.id() : null,
|
||||
null,
|
||||
null
|
||||
))
|
||||
const border = Array.from(this.bot.borderTiles())
|
||||
.flatMap((t) => this.mg.neighbors(t))
|
||||
.filter((t) => this.mg.hasOwner(t) && this.mg.owner(t) != this.bot);
|
||||
|
||||
if (border.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this.bot
|
||||
}
|
||||
const toAttack = border[this.random.nextInt(0, border.length)];
|
||||
const owner = this.mg.owner(toAttack);
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
if (owner.isPlayer()) {
|
||||
if (this.bot.isAlliedWith(owner)) {
|
||||
return;
|
||||
}
|
||||
if (owner.type() == PlayerType.FakeHuman) {
|
||||
if (!this.random.chance(2)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.sendAttack(owner);
|
||||
}
|
||||
|
||||
sendAttack(toAttack: Player | TerraNullius) {
|
||||
this.mg.addExecution(
|
||||
new AttackExecution(
|
||||
this.bot.troops() / 20,
|
||||
this.bot.id(),
|
||||
toAttack.isPlayer() ? toAttack.id() : null,
|
||||
null,
|
||||
null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this.bot;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,63 +6,65 @@ import { GameID, SpawnIntent } from "../Schemas";
|
||||
import { simpleHash } from "../Util";
|
||||
import { BOT_NAME_PREFIXES, BOT_NAME_SUFFIXES } from "./utils/BotNames";
|
||||
|
||||
|
||||
export class BotSpawner {
|
||||
private random: PseudoRandom
|
||||
private bots: SpawnIntent[] = [];
|
||||
private random: PseudoRandom;
|
||||
private bots: SpawnIntent[] = [];
|
||||
|
||||
constructor(private gs: Game, gameID: GameID) {
|
||||
this.random = new PseudoRandom(simpleHash(gameID))
|
||||
}
|
||||
constructor(
|
||||
private gs: Game,
|
||||
gameID: GameID,
|
||||
) {
|
||||
this.random = new PseudoRandom(simpleHash(gameID));
|
||||
}
|
||||
|
||||
spawnBots(numBots: number): SpawnIntent[] {
|
||||
let tries = 0
|
||||
while (this.bots.length < numBots) {
|
||||
if (tries > 10000) {
|
||||
consolex.log('too many retries while spawning bots, giving up')
|
||||
return this.bots
|
||||
}
|
||||
const botName = this.randomBotName();
|
||||
const spawn = this.spawnBot(botName);
|
||||
if (spawn != null) {
|
||||
this.bots.push(spawn);
|
||||
} else {
|
||||
tries++
|
||||
}
|
||||
}
|
||||
spawnBots(numBots: number): SpawnIntent[] {
|
||||
let tries = 0;
|
||||
while (this.bots.length < numBots) {
|
||||
if (tries > 10000) {
|
||||
consolex.log("too many retries while spawning bots, giving up");
|
||||
return this.bots;
|
||||
}
|
||||
const botName = this.randomBotName();
|
||||
const spawn = this.spawnBot(botName);
|
||||
if (spawn != null) {
|
||||
this.bots.push(spawn);
|
||||
} else {
|
||||
tries++;
|
||||
}
|
||||
}
|
||||
return this.bots;
|
||||
}
|
||||
|
||||
spawnBot(botName: string): SpawnIntent | null {
|
||||
const tile = this.randTile()
|
||||
if (!this.gs.isLand(tile)) {
|
||||
return null
|
||||
}
|
||||
for (const spawn of this.bots) {
|
||||
if (this.gs.manhattanDist(this.gs.ref(spawn.x, spawn.y), tile) < 30) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'spawn',
|
||||
playerID: this.random.nextID(),
|
||||
name: botName,
|
||||
playerType: PlayerType.Bot,
|
||||
x: this.gs.x(tile),
|
||||
y: this.gs.y(tile)
|
||||
};
|
||||
spawnBot(botName: string): SpawnIntent | null {
|
||||
const tile = this.randTile();
|
||||
if (!this.gs.isLand(tile)) {
|
||||
return null;
|
||||
}
|
||||
for (const spawn of this.bots) {
|
||||
if (this.gs.manhattanDist(this.gs.ref(spawn.x, spawn.y), tile) < 30) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: "spawn",
|
||||
playerID: this.random.nextID(),
|
||||
name: botName,
|
||||
playerType: PlayerType.Bot,
|
||||
x: this.gs.x(tile),
|
||||
y: this.gs.y(tile),
|
||||
};
|
||||
}
|
||||
|
||||
private randomBotName(): string {
|
||||
const prefixIndex = this.random.nextInt(0, BOT_NAME_PREFIXES.length);
|
||||
const suffixIndex = this.random.nextInt(0, BOT_NAME_SUFFIXES.length);
|
||||
return `${BOT_NAME_PREFIXES[prefixIndex]} ${BOT_NAME_SUFFIXES[suffixIndex]}`;
|
||||
}
|
||||
private randomBotName(): string {
|
||||
const prefixIndex = this.random.nextInt(0, BOT_NAME_PREFIXES.length);
|
||||
const suffixIndex = this.random.nextInt(0, BOT_NAME_SUFFIXES.length);
|
||||
return `${BOT_NAME_PREFIXES[prefixIndex]} ${BOT_NAME_SUFFIXES[suffixIndex]}`;
|
||||
}
|
||||
|
||||
private randTile(): TileRef {
|
||||
return this.gs.ref(
|
||||
this.random.nextInt(0, this.gs.width()),
|
||||
this.random.nextInt(0, this.gs.height())
|
||||
)
|
||||
}
|
||||
private randTile(): TileRef {
|
||||
return this.gs.ref(
|
||||
this.random.nextInt(0, this.gs.width()),
|
||||
this.random.nextInt(0, this.gs.height()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,55 @@
|
||||
import { consolex } from "../Consolex";
|
||||
import { Execution, Game, Player, Unit, PlayerID, UnitType } from "../game/Game";
|
||||
import {
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
Unit,
|
||||
PlayerID,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class CityExecution implements Execution {
|
||||
private player: Player;
|
||||
private mg: Game;
|
||||
private city: Unit;
|
||||
private active: boolean = true;
|
||||
|
||||
private player: Player
|
||||
private mg: Game
|
||||
private city: Unit
|
||||
private active: boolean = true
|
||||
constructor(
|
||||
private ownerId: PlayerID,
|
||||
private tile: TileRef,
|
||||
) {}
|
||||
|
||||
constructor(private ownerId: PlayerID, private tile: TileRef) { }
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
this.player = mg.player(this.ownerId);
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.player = mg.player(this.ownerId)
|
||||
tick(ticks: number): void {
|
||||
if (this.city == null) {
|
||||
const spawnTile = this.player.canBuild(UnitType.City, this.tile);
|
||||
if (spawnTile == false) {
|
||||
consolex.warn("cannot build city");
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.city = this.player.buildUnit(UnitType.City, 0, spawnTile);
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.city == null) {
|
||||
const spawnTile = this.player.canBuild(UnitType.City, this.tile)
|
||||
if (spawnTile == false) {
|
||||
consolex.warn('cannot build city')
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
this.city = this.player.buildUnit(UnitType.City, 0, spawnTile)
|
||||
}
|
||||
if (!this.city.isActive()) {
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
if (!this.city.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,76 @@
|
||||
import { consolex } from "../Consolex";
|
||||
import { Cell, DefenseBonus, Execution, Game, Player, Unit, PlayerID, UnitType } from "../game/Game";
|
||||
import {
|
||||
Cell,
|
||||
DefenseBonus,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
Unit,
|
||||
PlayerID,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import { manhattanDistFN, TileRef } from "../game/GameMap";
|
||||
|
||||
export class DefensePostExecution implements Execution {
|
||||
private player: Player;
|
||||
private mg: Game;
|
||||
private post: Unit;
|
||||
private active: boolean = true;
|
||||
|
||||
private player: Player
|
||||
private mg: Game
|
||||
private post: Unit
|
||||
private active: boolean = true
|
||||
private defenseBonuses: DefenseBonus[] = [];
|
||||
|
||||
private defenseBonuses: DefenseBonus[] = []
|
||||
constructor(
|
||||
private ownerId: PlayerID,
|
||||
private tile: TileRef,
|
||||
) {}
|
||||
|
||||
constructor(private ownerId: PlayerID, private tile: TileRef) { }
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
this.player = mg.player(this.ownerId);
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.player = mg.player(this.ownerId)
|
||||
tick(ticks: number): void {
|
||||
if (this.post == null) {
|
||||
const spawnTile = this.player.canBuild(UnitType.DefensePost, this.tile);
|
||||
if (spawnTile == false) {
|
||||
consolex.warn("cannot build Defense Post");
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.post = this.player.buildUnit(UnitType.DefensePost, 0, spawnTile);
|
||||
this.mg
|
||||
.bfs(
|
||||
spawnTile,
|
||||
manhattanDistFN(spawnTile, this.mg.config().defensePostRange()),
|
||||
)
|
||||
.forEach((t) => {
|
||||
if (this.mg.isLake(t)) {
|
||||
this.defenseBonuses.push(
|
||||
this.mg.addTileDefenseBonus(
|
||||
t,
|
||||
this.post,
|
||||
this.mg.config().defensePostDefenseBonus(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.post == null) {
|
||||
const spawnTile = this.player.canBuild(UnitType.DefensePost, this.tile)
|
||||
if (spawnTile == false) {
|
||||
consolex.warn('cannot build Defense Post')
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
this.post = this.player.buildUnit(UnitType.DefensePost, 0, spawnTile)
|
||||
this.mg.bfs(spawnTile, manhattanDistFN(spawnTile, this.mg.config().defensePostRange())).forEach(t => {
|
||||
if (this.mg.isLake(t)) {
|
||||
this.defenseBonuses.push(
|
||||
this.mg.addTileDefenseBonus(t, this.post, this.mg.config().defensePostDefenseBonus())
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!this.post.isActive()) {
|
||||
this.defenseBonuses.forEach(df => this.mg.removeTileDefenseBonus(df))
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
if (!this.post.isActive()) {
|
||||
this.defenseBonuses.forEach((df) => this.mg.removeTileDefenseBonus(df));
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Cell, Execution, Game, Player, Unit, PlayerID, TerrainType, UnitType } from "../game/Game";
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
Unit,
|
||||
PlayerID,
|
||||
TerrainType,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
@@ -7,142 +16,163 @@ import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class DestroyerExecution implements Execution {
|
||||
private random: PseudoRandom
|
||||
private random: PseudoRandom;
|
||||
|
||||
private _owner: Player
|
||||
private active = true
|
||||
private destroyer: Unit = null
|
||||
private mg: Game = null
|
||||
private _owner: Player;
|
||||
private active = true;
|
||||
private destroyer: Unit = null;
|
||||
private mg: Game = null;
|
||||
|
||||
private target: Unit = null
|
||||
private pathfinder: PathFinder
|
||||
private target: Unit = null;
|
||||
private pathfinder: PathFinder;
|
||||
|
||||
private patrolTile: TileRef;
|
||||
private patrolTile: TileRef;
|
||||
|
||||
// TODO: put in config
|
||||
private searchRange = 100
|
||||
// TODO: put in config
|
||||
private searchRange = 100;
|
||||
|
||||
constructor(
|
||||
private playerID: PlayerID,
|
||||
private patrolCenterTile: TileRef,
|
||||
) { }
|
||||
constructor(
|
||||
private playerID: PlayerID,
|
||||
private patrolCenterTile: TileRef,
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false);
|
||||
this._owner = mg.player(this.playerID);
|
||||
this.mg = mg;
|
||||
this.patrolTile = this.patrolCenterTile;
|
||||
this.random = new PseudoRandom(mg.ticks());
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
||||
this._owner = mg.player(this.playerID)
|
||||
this.mg = mg
|
||||
this.patrolTile = this.patrolCenterTile
|
||||
this.random = new PseudoRandom(mg.ticks())
|
||||
tick(ticks: number): void {
|
||||
if (this.destroyer == null) {
|
||||
const spawn = this._owner.canBuild(UnitType.Destroyer, this.patrolTile);
|
||||
if (spawn == false) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.destroyer = this._owner.buildUnit(UnitType.Destroyer, 0, spawn);
|
||||
return;
|
||||
}
|
||||
if (!this.destroyer.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
if (this.target != null && !this.target.isActive()) {
|
||||
this.target = null;
|
||||
}
|
||||
if (this.target == null) {
|
||||
const ships = this.mg
|
||||
.units(
|
||||
UnitType.TransportShip,
|
||||
UnitType.Destroyer,
|
||||
UnitType.TradeShip,
|
||||
UnitType.Battleship,
|
||||
)
|
||||
.filter(
|
||||
(u) => this.mg.manhattanDist(u.tile(), this.destroyer.tile()) < 100,
|
||||
)
|
||||
.filter(
|
||||
(u) =>
|
||||
u.type() != UnitType.Destroyer ||
|
||||
u.health() < this.destroyer.health(),
|
||||
) // only attack Destroyers weaker than it.
|
||||
.filter((u) => u.owner() != this.destroyer.owner())
|
||||
.filter((u) => u != this.destroyer)
|
||||
.filter((u) => !u.owner().isAlliedWith(this.destroyer.owner()));
|
||||
if (ships.length == 0) {
|
||||
const result = this.pathfinder.nextTile(
|
||||
this.destroyer.tile(),
|
||||
this.patrolTile,
|
||||
);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.patrolTile = this.randomTile();
|
||||
break;
|
||||
case PathFindResultType.NextTile:
|
||||
this.destroyer.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
return;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to patrol tile`);
|
||||
this.patrolTile = this.randomTile();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.target = ships.sort(distSortUnit(this.mg, this.destroyer))[0];
|
||||
}
|
||||
if (!this.target.isActive() || this.target.owner() == this._owner) {
|
||||
// Incase another destroyer captured or destroyed target
|
||||
this.target = null;
|
||||
return;
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.destroyer == null) {
|
||||
const spawn = this._owner.canBuild(UnitType.Destroyer, this.patrolTile)
|
||||
if (spawn == false) {
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
this.destroyer = this._owner.buildUnit(UnitType.Destroyer, 0, spawn)
|
||||
return
|
||||
}
|
||||
if (!this.destroyer.isActive()) {
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
if (this.target != null && !this.target.isActive()) {
|
||||
this.target = null
|
||||
}
|
||||
if (this.target == null) {
|
||||
const ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
|
||||
.filter(u => this.mg.manhattanDist(u.tile(), this.destroyer.tile()) < 100)
|
||||
.filter(u => u.type() != UnitType.Destroyer || u.health() < this.destroyer.health()) // only attack Destroyers weaker than it.
|
||||
.filter(u => u.owner() != this.destroyer.owner())
|
||||
.filter(u => u != this.destroyer)
|
||||
.filter(u => !u.owner().isAlliedWith(this.destroyer.owner()))
|
||||
if (ships.length == 0) {
|
||||
const result = this.pathfinder.nextTile(this.destroyer.tile(), this.patrolTile)
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.patrolTile = this.randomTile()
|
||||
break
|
||||
case PathFindResultType.NextTile:
|
||||
this.destroyer.move(result.tile)
|
||||
break
|
||||
case PathFindResultType.Pending:
|
||||
return
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to patrol tile`)
|
||||
this.patrolTile = this.randomTile()
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
this.target = ships.sort(distSortUnit(this.mg, this.destroyer))[0]
|
||||
}
|
||||
if (!this.target.isActive() || this.target.owner() == this._owner) {
|
||||
// Incase another destroyer captured or destroyed target
|
||||
this.target = null
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const result = this.pathfinder.nextTile(this.destroyer.tile(), this.target.tile(), 5)
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
switch (this.target.type()) {
|
||||
case UnitType.TransportShip:
|
||||
case UnitType.Battleship:
|
||||
this.target.delete()
|
||||
break
|
||||
case UnitType.TradeShip:
|
||||
this.owner().captureUnit(this.target)
|
||||
break
|
||||
case UnitType.Destroyer:
|
||||
const health = this.target.health()
|
||||
this.target.modifyHealth(-this.destroyer.health())
|
||||
this.destroyer.modifyHealth(-health)
|
||||
break
|
||||
}
|
||||
this.target = null
|
||||
return
|
||||
case PathFindResultType.NextTile:
|
||||
this.destroyer.move(result.tile)
|
||||
break
|
||||
case PathFindResultType.Pending:
|
||||
break
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to target`)
|
||||
break
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const result = this.pathfinder.nextTile(
|
||||
this.destroyer.tile(),
|
||||
this.target.tile(),
|
||||
5,
|
||||
);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
switch (this.target.type()) {
|
||||
case UnitType.TransportShip:
|
||||
case UnitType.Battleship:
|
||||
this.target.delete();
|
||||
break;
|
||||
case UnitType.TradeShip:
|
||||
this.owner().captureUnit(this.target);
|
||||
break;
|
||||
case UnitType.Destroyer:
|
||||
const health = this.target.health();
|
||||
this.target.modifyHealth(-this.destroyer.health());
|
||||
this.destroyer.modifyHealth(-health);
|
||||
break;
|
||||
}
|
||||
this.target = null;
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
this.destroyer.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
break;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`path not found to target`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this._owner
|
||||
owner(): Player {
|
||||
return this._owner;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
randomTile(): TileRef {
|
||||
while (true) {
|
||||
const x =
|
||||
this.mg.x(this.patrolCenterTile) +
|
||||
this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
|
||||
const y =
|
||||
this.mg.y(this.patrolCenterTile) +
|
||||
this.random.nextInt(-this.searchRange / 2, this.searchRange / 2);
|
||||
if (!this.mg.isValidCoord(x, y)) {
|
||||
continue;
|
||||
}
|
||||
const tile = this.mg.ref(x, y);
|
||||
if (!this.mg.isOcean(tile)) {
|
||||
continue;
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
randomTile(): TileRef {
|
||||
while (true) {
|
||||
const x = this.mg.x(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
const y = this.mg.y(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||
if (!this.mg.isValidCoord(x, y)) {
|
||||
continue
|
||||
}
|
||||
const tile = this.mg.ref(x, y)
|
||||
if (!this.mg.isOcean(tile)) {
|
||||
continue
|
||||
}
|
||||
return tile
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,47 +2,46 @@ import { consolex } from "../Consolex";
|
||||
import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
|
||||
export class DonateExecution implements Execution {
|
||||
private sender: Player;
|
||||
private recipient: Player;
|
||||
|
||||
private sender: Player
|
||||
private recipient: Player
|
||||
private active = true;
|
||||
|
||||
private active = true
|
||||
constructor(
|
||||
private senderID: PlayerID,
|
||||
private recipientID: PlayerID,
|
||||
private troops: number | null,
|
||||
) {}
|
||||
|
||||
constructor(
|
||||
private senderID: PlayerID,
|
||||
private recipientID: PlayerID,
|
||||
private troops: number | null
|
||||
) { }
|
||||
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.sender = mg.player(this.senderID)
|
||||
this.recipient = mg.player(this.recipientID)
|
||||
if (this.troops == null) {
|
||||
this.troops = mg.config().defaultDonationAmount(this.sender)
|
||||
}
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.sender = mg.player(this.senderID);
|
||||
this.recipient = mg.player(this.recipientID);
|
||||
if (this.troops == null) {
|
||||
this.troops = mg.config().defaultDonationAmount(this.sender);
|
||||
}
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.sender.canDonate(this.recipient)) {
|
||||
this.sender.donate(this.recipient, this.troops)
|
||||
this.recipient.updateRelation(this.sender, 50)
|
||||
} else {
|
||||
consolex.warn(`cannot send tropps from ${this.sender} to ${this.recipient}`)
|
||||
}
|
||||
this.active = false
|
||||
tick(ticks: number): void {
|
||||
if (this.sender.canDonate(this.recipient)) {
|
||||
this.sender.donate(this.recipient, this.troops);
|
||||
this.recipient.updateRelation(this.sender, 50);
|
||||
} else {
|
||||
consolex.warn(
|
||||
`cannot send tropps from ${this.sender} to ${this.recipient}`,
|
||||
);
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +1,59 @@
|
||||
import { consolex } from "../Consolex";
|
||||
import { AllPlayers, Execution, Game, Player, PlayerID, PlayerType, UnitType } from "../game/Game";
|
||||
import {
|
||||
AllPlayers,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerID,
|
||||
PlayerType,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
|
||||
export class EmojiExecution implements Execution {
|
||||
private requestor: Player;
|
||||
private recipient: Player | typeof AllPlayers;
|
||||
|
||||
private requestor: Player
|
||||
private recipient: Player | typeof AllPlayers
|
||||
private active = true;
|
||||
|
||||
private active = true
|
||||
constructor(
|
||||
private senderID: PlayerID,
|
||||
private recipientID: PlayerID | typeof AllPlayers,
|
||||
private emoji: string,
|
||||
) {}
|
||||
|
||||
constructor(
|
||||
private senderID: PlayerID,
|
||||
private recipientID: PlayerID | typeof AllPlayers,
|
||||
private emoji: string
|
||||
) { }
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.requestor = mg.player(this.senderID);
|
||||
this.recipient =
|
||||
this.recipientID == AllPlayers ? AllPlayers : mg.player(this.recipientID);
|
||||
}
|
||||
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.requestor = mg.player(this.senderID)
|
||||
this.recipient = this.recipientID == AllPlayers ? AllPlayers : mg.player(this.recipientID)
|
||||
tick(ticks: number): void {
|
||||
if (this.requestor.canSendEmoji(this.recipient)) {
|
||||
this.requestor.sendEmoji(this.recipient, this.emoji);
|
||||
if (
|
||||
this.emoji == "🖕" &&
|
||||
this.recipient != AllPlayers &&
|
||||
this.recipient.type() == PlayerType.FakeHuman
|
||||
) {
|
||||
this.recipient.updateRelation(this.requestor, -100);
|
||||
}
|
||||
} else {
|
||||
consolex.warn(
|
||||
`cannot send emoji from ${this.requestor} to ${this.recipient}`,
|
||||
);
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.requestor.canSendEmoji(this.recipient)) {
|
||||
this.requestor.sendEmoji(this.recipient, this.emoji)
|
||||
if (this.emoji == "🖕" && this.recipient != AllPlayers && this.recipient.type() == PlayerType.FakeHuman) {
|
||||
this.recipient.updateRelation(this.requestor, -100)
|
||||
}
|
||||
} else {
|
||||
consolex.warn(`cannot send emoji from ${this.requestor} to ${this.recipient}`)
|
||||
}
|
||||
this.active = false
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
import { Cell, Execution, Game, Player, PlayerInfo, TerraNullius, PlayerType, Alliance, UnitType } from "../game/Game";
|
||||
import { AttackIntent, BoatAttackIntentSchema, GameID, Intent, Turn } from "../Schemas";
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerInfo,
|
||||
TerraNullius,
|
||||
PlayerType,
|
||||
Alliance,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import {
|
||||
AttackIntent,
|
||||
BoatAttackIntentSchema,
|
||||
GameID,
|
||||
Intent,
|
||||
Turn,
|
||||
} from "../Schemas";
|
||||
import { AttackExecution } from "./AttackExecution";
|
||||
import { SpawnExecution } from "./SpawnExecution";
|
||||
import { BotSpawner } from "./BotSpawner";
|
||||
@@ -23,104 +39,148 @@ import { DefensePostExecution } from "./DefensePostExecution";
|
||||
import { CityExecution } from "./CityExecution";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
|
||||
|
||||
export class Executor {
|
||||
// private random = new PseudoRandom(999)
|
||||
private random: PseudoRandom = null;
|
||||
|
||||
constructor(
|
||||
private mg: Game,
|
||||
private gameID: GameID,
|
||||
) {
|
||||
// Add one to avoid id collisions with bots.
|
||||
this.random = new PseudoRandom(simpleHash(gameID) + 1);
|
||||
}
|
||||
|
||||
// private random = new PseudoRandom(999)
|
||||
private random: PseudoRandom = null
|
||||
createExecs(turn: Turn): Execution[] {
|
||||
return turn.intents.map((i) => this.createExec(i));
|
||||
}
|
||||
|
||||
constructor(private mg: Game, private gameID: GameID) {
|
||||
// Add one to avoid id collisions with bots.
|
||||
this.random = new PseudoRandom(simpleHash(gameID) + 1)
|
||||
}
|
||||
|
||||
createExecs(turn: Turn): Execution[] {
|
||||
return turn.intents.map(i => this.createExec(i))
|
||||
}
|
||||
|
||||
createExec(intent: Intent): Execution {
|
||||
switch (intent.type) {
|
||||
case "attack": {
|
||||
return new AttackExecution(
|
||||
intent.troops,
|
||||
intent.attackerID,
|
||||
intent.targetID,
|
||||
null
|
||||
);
|
||||
}
|
||||
case "spawn":
|
||||
return new SpawnExecution(
|
||||
new PlayerInfo(sanitize(intent.name), intent.playerType, intent.clientID, intent.playerID),
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case "boat":
|
||||
return new TransportShipExecution(
|
||||
intent.attackerID,
|
||||
intent.targetID,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
intent.troops
|
||||
);
|
||||
case "allianceRequest":
|
||||
return new AllianceRequestExecution(intent.requestor, intent.recipient);
|
||||
case "allianceRequestReply":
|
||||
return new AllianceRequestReplyExecution(intent.requestor, intent.recipient, intent.accept);
|
||||
case "breakAlliance":
|
||||
return new BreakAllianceExecution(intent.requestor, intent.recipient);
|
||||
case "targetPlayer":
|
||||
return new TargetPlayerExecution(intent.requestor, intent.target);
|
||||
case "emoji":
|
||||
return new EmojiExecution(intent.sender, intent.recipient, intent.emoji);
|
||||
case "donate":
|
||||
return new DonateExecution(intent.sender, intent.recipient, intent.troops);
|
||||
case "troop_ratio":
|
||||
return new SetTargetTroopRatioExecution(intent.player, intent.ratio);
|
||||
case "build_unit":
|
||||
switch (intent.unit) {
|
||||
case UnitType.AtomBomb:
|
||||
case UnitType.HydrogenBomb:
|
||||
return new NukeExecution(intent.unit, intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.Destroyer:
|
||||
return new DestroyerExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.Battleship:
|
||||
return new BattleshipExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.Port:
|
||||
return new PortExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.MissileSilo:
|
||||
return new MissileSiloExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.DefensePost:
|
||||
return new DefensePostExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
case UnitType.City:
|
||||
return new CityExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||
default:
|
||||
throw Error(`unit type ${intent.unit} not supported`)
|
||||
}
|
||||
default:
|
||||
throw new Error(`intent type ${intent} not found`);
|
||||
createExec(intent: Intent): Execution {
|
||||
switch (intent.type) {
|
||||
case "attack": {
|
||||
return new AttackExecution(
|
||||
intent.troops,
|
||||
intent.attackerID,
|
||||
intent.targetID,
|
||||
null,
|
||||
);
|
||||
}
|
||||
case "spawn":
|
||||
return new SpawnExecution(
|
||||
new PlayerInfo(
|
||||
sanitize(intent.name),
|
||||
intent.playerType,
|
||||
intent.clientID,
|
||||
intent.playerID,
|
||||
),
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
);
|
||||
case "boat":
|
||||
return new TransportShipExecution(
|
||||
intent.attackerID,
|
||||
intent.targetID,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
intent.troops,
|
||||
);
|
||||
case "allianceRequest":
|
||||
return new AllianceRequestExecution(intent.requestor, intent.recipient);
|
||||
case "allianceRequestReply":
|
||||
return new AllianceRequestReplyExecution(
|
||||
intent.requestor,
|
||||
intent.recipient,
|
||||
intent.accept,
|
||||
);
|
||||
case "breakAlliance":
|
||||
return new BreakAllianceExecution(intent.requestor, intent.recipient);
|
||||
case "targetPlayer":
|
||||
return new TargetPlayerExecution(intent.requestor, intent.target);
|
||||
case "emoji":
|
||||
return new EmojiExecution(
|
||||
intent.sender,
|
||||
intent.recipient,
|
||||
intent.emoji,
|
||||
);
|
||||
case "donate":
|
||||
return new DonateExecution(
|
||||
intent.sender,
|
||||
intent.recipient,
|
||||
intent.troops,
|
||||
);
|
||||
case "troop_ratio":
|
||||
return new SetTargetTroopRatioExecution(intent.player, intent.ratio);
|
||||
case "build_unit":
|
||||
switch (intent.unit) {
|
||||
case UnitType.AtomBomb:
|
||||
case UnitType.HydrogenBomb:
|
||||
return new NukeExecution(
|
||||
intent.unit,
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
);
|
||||
case UnitType.Destroyer:
|
||||
return new DestroyerExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
);
|
||||
case UnitType.Battleship:
|
||||
return new BattleshipExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
);
|
||||
case UnitType.Port:
|
||||
return new PortExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
);
|
||||
case UnitType.MissileSilo:
|
||||
return new MissileSiloExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
);
|
||||
case UnitType.DefensePost:
|
||||
return new DefensePostExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
);
|
||||
case UnitType.City:
|
||||
return new CityExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y),
|
||||
);
|
||||
default:
|
||||
throw Error(`unit type ${intent.unit} not supported`);
|
||||
}
|
||||
default:
|
||||
throw new Error(`intent type ${intent} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
spawnBots(numBots: number): Execution[] {
|
||||
return new BotSpawner(this.mg, this.gameID).spawnBots(numBots).map(i => this.createExec(i))
|
||||
spawnBots(numBots: number): Execution[] {
|
||||
return new BotSpawner(this.mg, this.gameID)
|
||||
.spawnBots(numBots)
|
||||
.map((i) => this.createExec(i));
|
||||
}
|
||||
|
||||
fakeHumanExecutions(): Execution[] {
|
||||
const execs = [];
|
||||
for (const nation of this.mg.nations()) {
|
||||
execs.push(
|
||||
new FakeHumanExecution(
|
||||
this.gameID,
|
||||
new PlayerInfo(
|
||||
nation.name,
|
||||
PlayerType.FakeHuman,
|
||||
null,
|
||||
this.random.nextID(),
|
||||
),
|
||||
nation.cell,
|
||||
nation.strength *
|
||||
this.mg
|
||||
.config()
|
||||
.difficultyModifier(this.mg.config().gameConfig().difficulty),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fakeHumanExecutions(): Execution[] {
|
||||
const execs = []
|
||||
for (const nation of this.mg.nations()) {
|
||||
execs.push(new FakeHumanExecution(
|
||||
this.gameID,
|
||||
new PlayerInfo(
|
||||
nation.name,
|
||||
PlayerType.FakeHuman,
|
||||
null,
|
||||
this.random.nextID()
|
||||
),
|
||||
nation.cell,
|
||||
nation.strength * this.mg.config().difficultyModifier(this.mg.config().gameConfig().difficulty)
|
||||
))
|
||||
}
|
||||
return execs
|
||||
}
|
||||
|
||||
}
|
||||
return execs;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,46 +1,53 @@
|
||||
import { consolex } from "../Consolex";
|
||||
import { Cell, Execution, Game, Player, Unit, PlayerID, UnitType } from "../game/Game";
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
Unit,
|
||||
PlayerID,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class MissileSiloExecution implements Execution {
|
||||
private active = true;
|
||||
private mg: Game;
|
||||
private player: Player;
|
||||
private silo: Unit;
|
||||
|
||||
private active = true
|
||||
private mg: Game
|
||||
private player: Player
|
||||
private silo: Unit
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private tile: TileRef,
|
||||
) {}
|
||||
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private tile: TileRef
|
||||
) { }
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
this.player = mg.player(this._owner);
|
||||
}
|
||||
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.player = mg.player(this._owner)
|
||||
tick(ticks: number): void {
|
||||
if (this.silo == null) {
|
||||
if (!this.player.canBuild(UnitType.MissileSilo, this.tile)) {
|
||||
consolex.warn(
|
||||
`player ${this.player} cannot build port at ${this.tile}`,
|
||||
);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, this.tile);
|
||||
}
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.silo == null) {
|
||||
if (!this.player.canBuild(UnitType.MissileSilo, this.tile)) {
|
||||
consolex.warn(`player ${this.player} cannot build port at ${this.tile}`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, this.tile)
|
||||
}
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+120
-104
@@ -1,5 +1,14 @@
|
||||
import { nextTick } from "process";
|
||||
import { Cell, Execution, Game, Player, PlayerID, Unit, UnitType, TerraNullius } from "../game/Game";
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerID,
|
||||
Unit,
|
||||
UnitType,
|
||||
TerraNullius,
|
||||
} from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
@@ -7,125 +16,132 @@ import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class NukeExecution implements Execution {
|
||||
private player: Player;
|
||||
|
||||
private player: Player
|
||||
private active = true;
|
||||
|
||||
private active = true
|
||||
private mg: Game;
|
||||
|
||||
private mg: Game
|
||||
private nuke: Unit;
|
||||
|
||||
private nuke: Unit
|
||||
private pathFinder: PathFinder;
|
||||
constructor(
|
||||
private type: UnitType.AtomBomb | UnitType.HydrogenBomb,
|
||||
private senderID: PlayerID,
|
||||
private dst: TileRef,
|
||||
) {}
|
||||
|
||||
private pathFinder: PathFinder
|
||||
constructor(
|
||||
private type: UnitType.AtomBomb | UnitType.HydrogenBomb,
|
||||
private senderID: PlayerID,
|
||||
private dst: TileRef,
|
||||
) { }
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, true);
|
||||
this.player = mg.player(this.senderID);
|
||||
}
|
||||
|
||||
public target(): Player | TerraNullius {
|
||||
return this.mg.owner(this.dst);
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, true)
|
||||
this.player = mg.player(this.senderID)
|
||||
tick(ticks: number): void {
|
||||
if (this.nuke == null) {
|
||||
const spawn = this.player.canBuild(this.type, this.dst);
|
||||
if (spawn == false) {
|
||||
consolex.warn(`cannot build Nuke`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.nuke = this.player.buildUnit(this.type, 0, spawn);
|
||||
}
|
||||
|
||||
public target(): Player | TerraNullius {
|
||||
return this.mg.owner(this.dst)
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const result = this.pathFinder.nextTile(this.nuke.tile(), this.dst);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.nuke.move(result.tile);
|
||||
this.detonate();
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
this.nuke.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
break;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.warn(
|
||||
`nuke cannot find path from ${this.nuke.tile()} to ${this.dst}`,
|
||||
);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.nuke == null) {
|
||||
const spawn = this.player.canBuild(this.type, this.dst)
|
||||
if (spawn == false) {
|
||||
consolex.warn(`cannot build Nuke`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
this.nuke = this.player.buildUnit(this.type, 0, spawn)
|
||||
private detonate() {
|
||||
const magnitude =
|
||||
this.type == UnitType.AtomBomb
|
||||
? { inner: 15, outer: 40 }
|
||||
: { inner: 140, outer: 160 };
|
||||
const rand = new PseudoRandom(this.mg.ticks());
|
||||
const toDestroy = this.mg.bfs(this.dst, (_, n: TileRef) => {
|
||||
const d = this.mg.euclideanDist(this.dst, n);
|
||||
return (d <= magnitude.inner || rand.chance(2)) && d <= magnitude.outer;
|
||||
});
|
||||
|
||||
const ratio = Object.fromEntries(
|
||||
this.mg
|
||||
.players()
|
||||
.map((p) => [p.id(), (p.troops() + p.workers()) / p.numTilesOwned()]),
|
||||
);
|
||||
const attacked = new Map<Player, number>();
|
||||
for (const tile of toDestroy) {
|
||||
const owner = this.mg.owner(tile);
|
||||
if (owner.isPlayer()) {
|
||||
const mp = this.mg.player(owner.id());
|
||||
mp.relinquish(tile);
|
||||
mp.removeTroops(2 * ratio[mp.id()]);
|
||||
if (!attacked.has(mp)) {
|
||||
attacked.set(mp, 0);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const result = this.pathFinder.nextTile(this.nuke.tile(), this.dst)
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.nuke.move(result.tile)
|
||||
this.detonate()
|
||||
return
|
||||
case PathFindResultType.NextTile:
|
||||
this.nuke.move(result.tile)
|
||||
break
|
||||
case PathFindResultType.Pending:
|
||||
break
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.warn(`nuke cannot find path from ${this.nuke.tile()} to ${this.dst}`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
const prev = attacked.get(mp);
|
||||
attacked.set(mp, prev + 1);
|
||||
}
|
||||
if (this.mg.isLand(tile)) {
|
||||
this.mg.setFallout(tile, true);
|
||||
}
|
||||
}
|
||||
for (const [other, tilesDestroyed] of attacked) {
|
||||
if (tilesDestroyed > 100) {
|
||||
const alliance = this.player.allianceWith(other);
|
||||
if (alliance != null) {
|
||||
this.player.breakAlliance(alliance);
|
||||
}
|
||||
}
|
||||
|
||||
private detonate() {
|
||||
const magnitude = this.type == UnitType.AtomBomb ? { inner: 15, outer: 40 } : { inner: 140, outer: 160 }
|
||||
const rand = new PseudoRandom(this.mg.ticks())
|
||||
const toDestroy = this.mg.bfs(this.dst, (_, n: TileRef) => {
|
||||
const d = this.mg.euclideanDist(this.dst, n)
|
||||
return (d <= magnitude.inner || rand.chance(2)) && d <= magnitude.outer
|
||||
})
|
||||
|
||||
const ratio = Object.fromEntries(
|
||||
this.mg.players().map(p => [p.id(), (p.troops() + p.workers()) / p.numTilesOwned()])
|
||||
)
|
||||
const attacked = new Map<Player, number>()
|
||||
for (const tile of toDestroy) {
|
||||
const owner = this.mg.owner(tile)
|
||||
if (owner.isPlayer()) {
|
||||
const mp = this.mg.player(owner.id())
|
||||
mp.relinquish(tile)
|
||||
mp.removeTroops(2 * ratio[mp.id()])
|
||||
if (!attacked.has(mp)) {
|
||||
attacked.set(mp, 0)
|
||||
}
|
||||
const prev = attacked.get(mp)
|
||||
attacked.set(mp, prev + 1)
|
||||
}
|
||||
if (this.mg.isLand(tile)) {
|
||||
this.mg.setFallout(tile, true)
|
||||
}
|
||||
if (other != this.player) {
|
||||
other.updateRelation(this.player, -100);
|
||||
}
|
||||
for (const [other, tilesDestroyed] of attacked) {
|
||||
if (tilesDestroyed > 100) {
|
||||
const alliance = this.player.allianceWith(other)
|
||||
if (alliance != null) {
|
||||
this.player.breakAlliance(alliance)
|
||||
}
|
||||
if (other != this.player) {
|
||||
other.updateRelation(this.player, -100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const unit of this.mg.units()) {
|
||||
if (
|
||||
unit.type() != UnitType.AtomBomb &&
|
||||
unit.type() != UnitType.HydrogenBomb
|
||||
) {
|
||||
if (this.mg.euclideanDist(this.dst, unit.tile()) < magnitude.outer) {
|
||||
unit.delete();
|
||||
}
|
||||
|
||||
for (const unit of this.mg.units()) {
|
||||
if (unit.type() != UnitType.AtomBomb && unit.type() != UnitType.HydrogenBomb) {
|
||||
if (this.mg.euclideanDist(this.dst, unit.tile()) < magnitude.outer) {
|
||||
unit.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
this.active = false
|
||||
this.nuke.delete(false)
|
||||
}
|
||||
}
|
||||
this.active = false;
|
||||
this.nuke.delete(false);
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this.player
|
||||
}
|
||||
owner(): Player {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,211 +1,226 @@
|
||||
import { Config } from "../configuration/Config"
|
||||
import { Execution, Game, Player, PlayerID, TerraNullius, UnitType } from "../game/Game"
|
||||
import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util"
|
||||
import { GameImpl } from "../game/GameImpl"
|
||||
import { consolex } from "../Consolex"
|
||||
import { GameMap, TileRef } from "../game/GameMap"
|
||||
import { Config } from "../configuration/Config";
|
||||
import {
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerID,
|
||||
TerraNullius,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util";
|
||||
import { GameImpl } from "../game/GameImpl";
|
||||
import { consolex } from "../Consolex";
|
||||
import { GameMap, TileRef } from "../game/GameMap";
|
||||
|
||||
export class PlayerExecution implements Execution {
|
||||
private readonly ticksPerClusterCalc = 20;
|
||||
|
||||
private readonly ticksPerClusterCalc = 20
|
||||
private player: Player;
|
||||
private config: Config;
|
||||
private lastCalc = 0;
|
||||
private mg: Game;
|
||||
private active = true;
|
||||
|
||||
private player: Player
|
||||
private config: Config
|
||||
private lastCalc = 0
|
||||
private mg: Game
|
||||
private active = true
|
||||
constructor(private playerID: PlayerID) {}
|
||||
|
||||
constructor(private playerID: PlayerID) {
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number) {
|
||||
this.mg = mg;
|
||||
this.config = mg.config();
|
||||
this.player = mg.player(this.playerID);
|
||||
this.lastCalc =
|
||||
ticks + (simpleHash(this.player.name()) % this.ticksPerClusterCalc);
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
this.player.decayRelations();
|
||||
this.player.units().forEach((u) => {
|
||||
if (u.health() <= 0) {
|
||||
u.delete();
|
||||
return;
|
||||
}
|
||||
u.modifyHealth(1);
|
||||
const tileOwner = this.mg.owner(u.tile());
|
||||
if (u.info().territoryBound) {
|
||||
if (tileOwner.isPlayer()) {
|
||||
if (tileOwner != this.player) {
|
||||
this.mg.player(tileOwner.id()).captureUnit(u);
|
||||
}
|
||||
} else {
|
||||
u.delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!this.player.isAlive()) {
|
||||
this.player.units().forEach((u) => {
|
||||
if (
|
||||
u.type() != UnitType.AtomBomb &&
|
||||
u.type() != UnitType.HydrogenBomb
|
||||
) {
|
||||
u.delete();
|
||||
}
|
||||
});
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
const popInc = this.config.populationIncreaseRate(this.player);
|
||||
this.player.addWorkers(popInc * (1 - this.player.targetTroopRatio())); // (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);
|
||||
this.player.addTroops(adjustRate);
|
||||
this.player.removeWorkers(adjustRate);
|
||||
|
||||
const alliances = Array.from(this.player.alliances());
|
||||
for (const alliance of alliances) {
|
||||
if (
|
||||
this.mg.ticks() - alliance.createdAt() >
|
||||
this.mg.config().allianceDuration()
|
||||
) {
|
||||
alliance.expire();
|
||||
}
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number) {
|
||||
this.mg = mg
|
||||
this.config = mg.config()
|
||||
this.player = mg.player(this.playerID)
|
||||
this.lastCalc = ticks + (simpleHash(this.player.name()) % this.ticksPerClusterCalc)
|
||||
if (ticks - this.lastCalc > this.ticksPerClusterCalc) {
|
||||
if (this.player.lastTileChange() > this.lastCalc) {
|
||||
this.lastCalc = ticks;
|
||||
const start = performance.now();
|
||||
this.removeClusters();
|
||||
const end = performance.now();
|
||||
if (end - start > 1000) {
|
||||
consolex.log(`player ${this.player.name()}, took ${end - start}ms`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeClusters() {
|
||||
const clusters = this.calculateClusters();
|
||||
clusters.sort((a, b) => b.size - a.size);
|
||||
|
||||
const main = clusters.shift();
|
||||
this.player.largestClusterBoundingBox = calculateBoundingBox(this.mg, main);
|
||||
const surroundedBy = this.surroundedBySamePlayer(main);
|
||||
if (surroundedBy && !this.player.isAlliedWith(surroundedBy)) {
|
||||
this.removeCluster(main);
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
this.player.decayRelations()
|
||||
this.player.units().forEach(u => {
|
||||
if (u.health() <= 0) {
|
||||
u.delete()
|
||||
return
|
||||
}
|
||||
u.modifyHealth(1)
|
||||
const tileOwner = this.mg.owner(u.tile())
|
||||
if (u.info().territoryBound) {
|
||||
if (tileOwner.isPlayer()) {
|
||||
if (tileOwner != this.player) {
|
||||
this.mg.player(tileOwner.id()).captureUnit(u)
|
||||
}
|
||||
} else {
|
||||
u.delete()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!this.player.isAlive()) {
|
||||
this.player.units().forEach(u => {
|
||||
if (u.type() != UnitType.AtomBomb && u.type() != UnitType.HydrogenBomb) {
|
||||
u.delete()
|
||||
}
|
||||
})
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const popInc = this.config.populationIncreaseRate(this.player)
|
||||
this.player.addWorkers(popInc * (1 - this.player.targetTroopRatio()))// (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)
|
||||
this.player.addTroops(adjustRate)
|
||||
this.player.removeWorkers(adjustRate)
|
||||
|
||||
const alliances = Array.from(this.player.alliances())
|
||||
for (const alliance of alliances) {
|
||||
if (this.mg.ticks() - alliance.createdAt() > this.mg.config().allianceDuration()) {
|
||||
alliance.expire()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (ticks - this.lastCalc > this.ticksPerClusterCalc) {
|
||||
if (this.player.lastTileChange() > this.lastCalc) {
|
||||
this.lastCalc = ticks
|
||||
const start = performance.now()
|
||||
this.removeClusters()
|
||||
const end = performance.now()
|
||||
if (end - start > 1000) {
|
||||
consolex.log(`player ${this.player.name()}, took ${end - start}ms`)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const cluster of clusters) {
|
||||
if (this.isSurrounded(cluster)) {
|
||||
this.removeCluster(cluster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private removeClusters() {
|
||||
const clusters = this.calculateClusters()
|
||||
clusters.sort((a, b) => b.size - a.size);
|
||||
|
||||
const main = clusters.shift()
|
||||
this.player.largestClusterBoundingBox = calculateBoundingBox(this.mg, main)
|
||||
const surroundedBy = this.surroundedBySamePlayer(main)
|
||||
if (surroundedBy && !this.player.isAlliedWith(surroundedBy)) {
|
||||
this.removeCluster(main)
|
||||
}
|
||||
|
||||
for (const cluster of clusters) {
|
||||
if (this.isSurrounded(cluster)) {
|
||||
this.removeCluster(cluster)
|
||||
}
|
||||
}
|
||||
private surroundedBySamePlayer(cluster: Set<TileRef>): false | Player {
|
||||
const enemies = new Set<number>();
|
||||
for (const ref of cluster) {
|
||||
if (
|
||||
this.mg.isOceanShore(ref) ||
|
||||
this.mg.neighbors(ref).some((n) => !this.mg.hasOwner(n))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
this.mg
|
||||
.neighbors(ref)
|
||||
.filter((n) => this.mg.ownerID(n) != this.player.smallID())
|
||||
.forEach((p) => enemies.add(this.mg.ownerID(p)));
|
||||
if (enemies.size != 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private surroundedBySamePlayer(cluster: Set<TileRef>): false | Player {
|
||||
const enemies = new Set<number>()
|
||||
for (const ref of cluster) {
|
||||
if (this.mg.isOceanShore(ref) || this.mg.neighbors(ref).some(n => !this.mg.hasOwner(n))) {
|
||||
return false
|
||||
}
|
||||
this.mg.neighbors(ref)
|
||||
.filter(n => this.mg.ownerID(n) != this.player.smallID())
|
||||
.forEach(p => enemies.add(this.mg.ownerID(p)))
|
||||
if (enemies.size != 1) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (enemies.size != 1) {
|
||||
return false
|
||||
}
|
||||
return this.mg.playerBySmallID(Array.from(enemies)[0]) as Player
|
||||
if (enemies.size != 1) {
|
||||
return false;
|
||||
}
|
||||
return this.mg.playerBySmallID(Array.from(enemies)[0]) as Player;
|
||||
}
|
||||
|
||||
private isSurrounded(cluster: Set<TileRef>): boolean {
|
||||
let enemyTiles = new Set<TileRef>()
|
||||
for (const tr of cluster) {
|
||||
if (this.mg.isOceanShore(tr)) {
|
||||
return false
|
||||
}
|
||||
this.mg.neighbors(tr)
|
||||
.filter(n => this.mg.ownerID(n) != this.player.smallID())
|
||||
.forEach(n => enemyTiles.add(n))
|
||||
}
|
||||
if (enemyTiles.size == 0) {
|
||||
return false
|
||||
}
|
||||
const enemyBox = calculateBoundingBox(this.mg, enemyTiles)
|
||||
const clusterBox = calculateBoundingBox(this.mg, cluster)
|
||||
return inscribed(enemyBox, clusterBox)
|
||||
private isSurrounded(cluster: Set<TileRef>): boolean {
|
||||
let enemyTiles = new Set<TileRef>();
|
||||
for (const tr of cluster) {
|
||||
if (this.mg.isOceanShore(tr)) {
|
||||
return false;
|
||||
}
|
||||
this.mg
|
||||
.neighbors(tr)
|
||||
.filter((n) => this.mg.ownerID(n) != this.player.smallID())
|
||||
.forEach((n) => enemyTiles.add(n));
|
||||
}
|
||||
|
||||
private removeCluster(cluster: Set<TileRef>) {
|
||||
const result = new Set<number>(); // Use Set to automatically deduplicate ownerIDs
|
||||
for (const t of cluster) {
|
||||
for (const neighbor of this.mg.neighbors(t)) {
|
||||
if (this.mg.ownerID(neighbor) != this.player.smallID()) {
|
||||
result.add(this.mg.ownerID(neighbor));
|
||||
}
|
||||
}
|
||||
}
|
||||
const mode = getMode(result)
|
||||
if (!this.mg.playerBySmallID(mode).isPlayer()) {
|
||||
return
|
||||
}
|
||||
const firstTile = cluster.values().next().value
|
||||
const filter = (_, t: TileRef): boolean => this.mg.ownerID(t) == this.mg.ownerID(firstTile)
|
||||
const tiles = this.mg.bfs(firstTile, filter)
|
||||
|
||||
const modePlayer = this.mg.playerBySmallID(mode)
|
||||
if (!modePlayer.isPlayer()) {
|
||||
consolex.warn('mode player is null')
|
||||
return
|
||||
}
|
||||
for (const tile of tiles) {
|
||||
(modePlayer as Player).conquer(tile)
|
||||
}
|
||||
if (enemyTiles.size == 0) {
|
||||
return false;
|
||||
}
|
||||
const enemyBox = calculateBoundingBox(this.mg, enemyTiles);
|
||||
const clusterBox = calculateBoundingBox(this.mg, cluster);
|
||||
return inscribed(enemyBox, clusterBox);
|
||||
}
|
||||
|
||||
private calculateClusters(): Set<TileRef>[] {
|
||||
const seen = new Set<TileRef>()
|
||||
const border = this.player.borderTiles()
|
||||
const clusters: Set<TileRef>[] = []
|
||||
for (const tile of border) {
|
||||
if (seen.has(tile)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const cluster = new Set<TileRef>()
|
||||
const queue: TileRef[] = [tile]
|
||||
seen.add(tile)
|
||||
while (queue.length > 0) {
|
||||
const curr = queue.shift()
|
||||
cluster.add(curr)
|
||||
|
||||
const neighbors = (this.mg as GameImpl).neighborsWithDiag(curr)
|
||||
for (const neighbor of neighbors) {
|
||||
if (border.has(neighbor) && !seen.has(neighbor)) {
|
||||
queue.push(neighbor)
|
||||
seen.add(neighbor)
|
||||
}
|
||||
}
|
||||
}
|
||||
clusters.push(cluster)
|
||||
private removeCluster(cluster: Set<TileRef>) {
|
||||
const result = new Set<number>(); // Use Set to automatically deduplicate ownerIDs
|
||||
for (const t of cluster) {
|
||||
for (const neighbor of this.mg.neighbors(t)) {
|
||||
if (this.mg.ownerID(neighbor) != this.player.smallID()) {
|
||||
result.add(this.mg.ownerID(neighbor));
|
||||
}
|
||||
return clusters
|
||||
}
|
||||
}
|
||||
const mode = getMode(result);
|
||||
if (!this.mg.playerBySmallID(mode).isPlayer()) {
|
||||
return;
|
||||
}
|
||||
const firstTile = cluster.values().next().value;
|
||||
const filter = (_, t: TileRef): boolean =>
|
||||
this.mg.ownerID(t) == this.mg.ownerID(firstTile);
|
||||
const tiles = this.mg.bfs(firstTile, filter);
|
||||
|
||||
owner(): Player {
|
||||
return this.player
|
||||
const modePlayer = this.mg.playerBySmallID(mode);
|
||||
if (!modePlayer.isPlayer()) {
|
||||
consolex.warn("mode player is null");
|
||||
return;
|
||||
}
|
||||
for (const tile of tiles) {
|
||||
(modePlayer as Player).conquer(tile);
|
||||
}
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
private calculateClusters(): Set<TileRef>[] {
|
||||
const seen = new Set<TileRef>();
|
||||
const border = this.player.borderTiles();
|
||||
const clusters: Set<TileRef>[] = [];
|
||||
for (const tile of border) {
|
||||
if (seen.has(tile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const cluster = new Set<TileRef>();
|
||||
const queue: TileRef[] = [tile];
|
||||
seen.add(tile);
|
||||
while (queue.length > 0) {
|
||||
const curr = queue.shift();
|
||||
cluster.add(curr);
|
||||
|
||||
const neighbors = (this.mg as GameImpl).neighborsWithDiag(curr);
|
||||
for (const neighbor of neighbors) {
|
||||
if (border.has(neighbor) && !seen.has(neighbor)) {
|
||||
queue.push(neighbor);
|
||||
seen.add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
clusters.push(cluster);
|
||||
}
|
||||
}
|
||||
return clusters;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
}
|
||||
|
||||
+123
-106
@@ -1,4 +1,14 @@
|
||||
import { AllPlayers, Cell, Execution, Game, Player, Unit, PlayerID, TerrainType, UnitType } from "../game/Game";
|
||||
import {
|
||||
AllPlayers,
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
Unit,
|
||||
PlayerID,
|
||||
TerrainType,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
@@ -8,126 +18,133 @@ import { MiniAStar } from "../pathfinding/MiniAStar";
|
||||
import { manhattanDistFN, TileRef } from "../game/GameMap";
|
||||
|
||||
export class PortExecution implements Execution {
|
||||
private active = true;
|
||||
private mg: Game;
|
||||
private port: Unit;
|
||||
private random: PseudoRandom;
|
||||
private portPaths = new Map<Unit, TileRef[]>();
|
||||
private computingPaths = new Map<Unit, MiniAStar>();
|
||||
|
||||
private active = true
|
||||
private mg: Game
|
||||
private port: Unit
|
||||
private random: PseudoRandom
|
||||
private portPaths = new Map<Unit, TileRef[]>()
|
||||
private computingPaths = new Map<Unit, MiniAStar>()
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private tile: TileRef,
|
||||
) {}
|
||||
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private tile: TileRef,
|
||||
) { }
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
this.random = new PseudoRandom(mg.ticks());
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.port == null) {
|
||||
// TODO: use canBuild
|
||||
const tile = this.tile;
|
||||
const player = this.mg.player(this._owner);
|
||||
if (!player.canBuild(UnitType.Port, tile)) {
|
||||
consolex.warn(`player ${player} cannot build port at ${this.tile}`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
const spawns = Array.from(this.mg.bfs(tile, manhattanDistFN(tile, 20)))
|
||||
.filter((t) => this.mg.isOceanShore(t) && this.mg.owner(t) == player)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile),
|
||||
);
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.random = new PseudoRandom(mg.ticks())
|
||||
if (spawns.length == 0) {
|
||||
consolex.warn(`cannot find spawn for port`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.port = player.buildUnit(UnitType.Port, 0, spawns[0]);
|
||||
}
|
||||
if (!this.port.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
const alliedPorts = this.player()
|
||||
.alliances()
|
||||
.map((a) => a.other(this.player()))
|
||||
.flatMap((p) => p.units(UnitType.Port));
|
||||
const alliedPortsSet = new Set(alliedPorts);
|
||||
|
||||
if (this.port == null) {
|
||||
// TODO: use canBuild
|
||||
const tile = this.tile
|
||||
const player = this.mg.player(this._owner)
|
||||
if (!player.canBuild(UnitType.Port, tile)) {
|
||||
consolex.warn(`player ${player} cannot build port at ${this.tile}`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
const spawns = Array.from(this.mg.bfs(tile, manhattanDistFN(tile, 20)))
|
||||
.filter(t => this.mg.isOceanShore(t) && this.mg.owner(t) == player)
|
||||
.sort((a, b) => this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile))
|
||||
const allyConnections = new Set(
|
||||
Array.from(this.portPaths.keys()).map((p) => p.owner()),
|
||||
);
|
||||
allyConnections;
|
||||
|
||||
if (spawns.length == 0) {
|
||||
consolex.warn(`cannot find spawn for port`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
this.port = player.buildUnit(UnitType.Port, 0, spawns[0])
|
||||
}
|
||||
if (!this.port.isActive()) {
|
||||
this.active = false
|
||||
return
|
||||
for (const port of alliedPorts) {
|
||||
if (allyConnections.has(port.owner())) {
|
||||
continue;
|
||||
}
|
||||
allyConnections.add(port.owner());
|
||||
if (this.computingPaths.has(port)) {
|
||||
const aStar = this.computingPaths.get(port);
|
||||
switch (aStar.compute()) {
|
||||
case PathFindResultType.Completed:
|
||||
this.portPaths.set(port, aStar.reconstructPath());
|
||||
this.computingPaths.delete(port);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
break;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.warn(`path not found to port`);
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
const alliedPorts = this.player().alliances().map(a => a.other(this.player())).flatMap(p => p.units(UnitType.Port))
|
||||
const alliedPortsSet = new Set(alliedPorts)
|
||||
|
||||
const allyConnections = new Set(Array.from(this.portPaths.keys()).map(p => p.owner()))
|
||||
allyConnections
|
||||
|
||||
|
||||
for (const port of alliedPorts) {
|
||||
if (allyConnections.has(port.owner())) {
|
||||
continue
|
||||
}
|
||||
allyConnections.add(port.owner())
|
||||
if (this.computingPaths.has(port)) {
|
||||
const aStar = this.computingPaths.get(port)
|
||||
switch (aStar.compute()) {
|
||||
case PathFindResultType.Completed:
|
||||
this.portPaths.set(port, aStar.reconstructPath())
|
||||
this.computingPaths.delete(port)
|
||||
break
|
||||
case PathFindResultType.Pending:
|
||||
break
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.warn(`path not found to port`)
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const pf = new MiniAStar(
|
||||
this.mg.map(),
|
||||
this.mg.miniMap(),
|
||||
this.port.tile(),
|
||||
port.tile(),
|
||||
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
|
||||
10_000,
|
||||
25
|
||||
)
|
||||
this.computingPaths.set(port, pf)
|
||||
}
|
||||
|
||||
for (const port of this.portPaths.keys()) {
|
||||
if (!port.isActive() || !alliedPortsSet.has(port)) {
|
||||
this.portPaths.delete(port)
|
||||
this.computingPaths.delete(port)
|
||||
}
|
||||
}
|
||||
|
||||
const portConnections = Array.from(this.portPaths.keys())
|
||||
|
||||
if (portConnections.length > 0 && this.random.chance(this.mg.config().tradeShipSpawnRate())) {
|
||||
const port = this.random.randElement(portConnections)
|
||||
const path = this.portPaths.get(port)
|
||||
if (path != null) {
|
||||
const pf = PathFinder.Mini(this.mg, 10, false)
|
||||
this.mg.addExecution(new TradeShipExecution(this.player().id(), this.port, port, pf, path))
|
||||
}
|
||||
}
|
||||
const pf = new MiniAStar(
|
||||
this.mg.map(),
|
||||
this.mg.miniMap(),
|
||||
this.port.tile(),
|
||||
port.tile(),
|
||||
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
|
||||
10_000,
|
||||
25,
|
||||
);
|
||||
this.computingPaths.set(port, pf);
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
for (const port of this.portPaths.keys()) {
|
||||
if (!port.isActive() || !alliedPortsSet.has(port)) {
|
||||
this.portPaths.delete(port);
|
||||
this.computingPaths.delete(port);
|
||||
}
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
const portConnections = Array.from(this.portPaths.keys());
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
if (
|
||||
portConnections.length > 0 &&
|
||||
this.random.chance(this.mg.config().tradeShipSpawnRate())
|
||||
) {
|
||||
const port = this.random.randElement(portConnections);
|
||||
const path = this.portPaths.get(port);
|
||||
if (path != null) {
|
||||
const pf = PathFinder.Mini(this.mg, 10, false);
|
||||
this.mg.addExecution(
|
||||
new TradeShipExecution(this.player().id(), this.port, port, pf, path),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
player(): Player {
|
||||
return this.port.owner()
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
player(): Player {
|
||||
return this.port.owner();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,37 +2,39 @@ import { consolex } from "../Consolex";
|
||||
import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
|
||||
export class SetTargetTroopRatioExecution implements Execution {
|
||||
private player: Player;
|
||||
|
||||
private player: Player
|
||||
private active = true;
|
||||
|
||||
private active = true
|
||||
constructor(
|
||||
private playerID: PlayerID,
|
||||
private targetTroopsRatio: number,
|
||||
) {}
|
||||
|
||||
constructor(private playerID: PlayerID, private targetTroopsRatio: number) { }
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.player = mg.player(this.playerID);
|
||||
}
|
||||
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.player = mg.player(this.playerID)
|
||||
tick(ticks: number): void {
|
||||
if (this.targetTroopsRatio < 0 || this.targetTroopsRatio > 1) {
|
||||
consolex.warn(
|
||||
`target troop ratio of ${this.targetTroopsRatio} for player ${this.player} invalid`,
|
||||
);
|
||||
} else {
|
||||
this.player.setTargetTroopRatio(this.targetTroopsRatio);
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.targetTroopsRatio < 0 || this.targetTroopsRatio > 1) {
|
||||
consolex.warn(`target troop ratio of ${this.targetTroopsRatio} for player ${this.player} invalid`)
|
||||
} else {
|
||||
this.player.setTargetTroopRatio(this.targetTroopsRatio)
|
||||
}
|
||||
this.active = false
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,62 +5,71 @@ import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class ShellExecution implements Execution {
|
||||
private active = true;
|
||||
private pathFinder: PathFinder;
|
||||
private shell: Unit;
|
||||
|
||||
private active = true
|
||||
private pathFinder: PathFinder
|
||||
private shell: Unit
|
||||
constructor(
|
||||
private spawn: TileRef,
|
||||
private _owner: Player,
|
||||
private ownerUnit: Unit,
|
||||
private target: Unit,
|
||||
) {}
|
||||
|
||||
constructor(private spawn: TileRef, private _owner: Player, private ownerUnit: Unit, private target: Unit) {
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.pathFinder = PathFinder.Mini(mg, 2000, true, 10);
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.shell == null) {
|
||||
this.shell = this._owner.buildUnit(UnitType.Shell, 0, this.spawn);
|
||||
}
|
||||
if (!this.shell.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!this.target.isActive() ||
|
||||
!this.ownerUnit.isActive() ||
|
||||
this.target.owner() == this.shell.owner()
|
||||
) {
|
||||
this.shell.delete(false);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const result = this.pathFinder.nextTile(
|
||||
this.shell.tile(),
|
||||
this.target.tile(),
|
||||
3,
|
||||
);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.active = false;
|
||||
this.target.modifyHealth(-this.shell.info().damage);
|
||||
this.shell.delete(false);
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
this.shell.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
return;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`Shell ${this.shell} could not find target`);
|
||||
this.active = false;
|
||||
this.shell.delete(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.pathFinder = PathFinder.Mini(mg, 2000, true, 10)
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.shell == null) {
|
||||
this.shell = this._owner.buildUnit(UnitType.Shell, 0, this.spawn)
|
||||
}
|
||||
if (!this.shell.isActive()) {
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
if (!this.target.isActive() || !this.ownerUnit.isActive() || this.target.owner() == this.shell.owner()) {
|
||||
this.shell.delete(false)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const result = this.pathFinder.nextTile(this.shell.tile(), this.target.tile(), 3)
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.active = false
|
||||
this.target.modifyHealth(-this.shell.info().damage)
|
||||
this.shell.delete(false)
|
||||
return
|
||||
case PathFindResultType.NextTile:
|
||||
this.shell.move(result.tile)
|
||||
break
|
||||
case PathFindResultType.Pending:
|
||||
return
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.log(`Shell ${this.shell} could not find target`)
|
||||
this.active = false
|
||||
this.shell.delete(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,68 @@
|
||||
import { Cell, Execution, Game, Player, PlayerInfo, PlayerType } from "../game/Game"
|
||||
import { TileRef } from "../game/GameMap"
|
||||
import { BotExecution } from "./BotExecution"
|
||||
import { PlayerExecution } from "./PlayerExecution"
|
||||
import { getSpawnTiles } from "./Util"
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerInfo,
|
||||
PlayerType,
|
||||
} from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { BotExecution } from "./BotExecution";
|
||||
import { PlayerExecution } from "./PlayerExecution";
|
||||
import { getSpawnTiles } from "./Util";
|
||||
|
||||
export class SpawnExecution implements Execution {
|
||||
active: boolean = true;
|
||||
private mg: Game;
|
||||
|
||||
active: boolean = true
|
||||
private mg: Game
|
||||
constructor(
|
||||
private playerInfo: PlayerInfo,
|
||||
private tile: TileRef,
|
||||
) {}
|
||||
|
||||
constructor(
|
||||
private playerInfo: PlayerInfo,
|
||||
private tile: TileRef
|
||||
) { }
|
||||
init(mg: Game, ticks: number) {
|
||||
this.mg = mg;
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number) {
|
||||
this.mg = mg
|
||||
tick(ticks: number) {
|
||||
this.active = false;
|
||||
|
||||
if (!this.mg.inSpawnPhase()) {
|
||||
return;
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
this.active = false
|
||||
|
||||
if (!this.mg.inSpawnPhase()) {
|
||||
return
|
||||
}
|
||||
|
||||
const existing = this.mg.players().find(p => p.id() == this.playerInfo.id)
|
||||
if (existing) {
|
||||
existing.tiles().forEach(t => existing.relinquish(t))
|
||||
getSpawnTiles(this.mg, this.tile).forEach(t => {
|
||||
existing.conquer(t)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const player = this.mg.addPlayer(this.playerInfo, this.mg.config().startManpower(this.playerInfo))
|
||||
getSpawnTiles(this.mg, this.tile).forEach(t => {
|
||||
player.conquer(t)
|
||||
})
|
||||
this.mg.addExecution(new PlayerExecution(player.id()))
|
||||
if (player.type() == PlayerType.Bot) {
|
||||
this.mg.addExecution(new BotExecution(player))
|
||||
}
|
||||
const existing = this.mg
|
||||
.players()
|
||||
.find((p) => p.id() == this.playerInfo.id);
|
||||
if (existing) {
|
||||
existing.tiles().forEach((t) => existing.relinquish(t));
|
||||
getSpawnTiles(this.mg, this.tile).forEach((t) => {
|
||||
existing.conquer(t);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
const player = this.mg.addPlayer(
|
||||
this.playerInfo,
|
||||
this.mg.config().startManpower(this.playerInfo),
|
||||
);
|
||||
getSpawnTiles(this.mg, this.tile).forEach((t) => {
|
||||
player.conquer(t);
|
||||
});
|
||||
this.mg.addExecution(new PlayerExecution(player.id()));
|
||||
if (player.type() == PlayerType.Bot) {
|
||||
this.mg.addExecution(new BotExecution(player));
|
||||
}
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return true
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
|
||||
export class TargetPlayerExecution implements Execution {
|
||||
private requestor: Player;
|
||||
private target: Player;
|
||||
|
||||
private requestor: Player
|
||||
private target: Player
|
||||
private active = true;
|
||||
|
||||
private active = true
|
||||
constructor(
|
||||
private requestorID: PlayerID,
|
||||
private targetID: PlayerID,
|
||||
) {}
|
||||
|
||||
constructor(private requestorID: PlayerID, private targetID: PlayerID) { }
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.requestor = mg.player(this.requestorID);
|
||||
this.target = mg.player(this.targetID);
|
||||
}
|
||||
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.requestor = mg.player(this.requestorID)
|
||||
this.target = mg.player(this.targetID)
|
||||
tick(ticks: number): void {
|
||||
if (this.requestor.canTarget(this.target)) {
|
||||
this.requestor.target(this.target);
|
||||
this.target.updateRelation(this.requestor, -40);
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.requestor.canTarget(this.target)) {
|
||||
this.requestor.target(this.target)
|
||||
this.target.updateRelation(this.requestor, -40)
|
||||
}
|
||||
this.active = false
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,131 +1,157 @@
|
||||
import { MessageType } from '../game/Game';
|
||||
import { MessageType } from "../game/Game";
|
||||
import { renderNumber } from "../../client/Utils";
|
||||
import { AllPlayers, Cell, Execution, Game, Unit, Player, PlayerID, UnitType } from "../game/Game";
|
||||
import {
|
||||
AllPlayers,
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Unit,
|
||||
Player,
|
||||
PlayerID,
|
||||
UnitType,
|
||||
} from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { distSortUnit } from "../Util";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from '../game/GameMap';
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class TradeShipExecution implements Execution {
|
||||
private active = true;
|
||||
private mg: Game;
|
||||
private origOwner: Player;
|
||||
private tradeShip: Unit;
|
||||
private index = 0;
|
||||
private wasCaptured = false;
|
||||
|
||||
private active = true
|
||||
private mg: Game
|
||||
private origOwner: Player
|
||||
private tradeShip: Unit
|
||||
private index = 0
|
||||
private wasCaptured = false
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private srcPort: Unit,
|
||||
private dstPort: Unit,
|
||||
private pathFinder: PathFinder,
|
||||
// don't modify
|
||||
private path: TileRef[],
|
||||
) {}
|
||||
|
||||
constructor(
|
||||
private _owner: PlayerID,
|
||||
private srcPort: Unit,
|
||||
private dstPort: Unit,
|
||||
private pathFinder: PathFinder,
|
||||
// don't modify
|
||||
private path: TileRef[]
|
||||
) { }
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
this.origOwner = mg.player(this._owner);
|
||||
}
|
||||
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.origOwner = mg.player(this._owner)
|
||||
tick(ticks: number): void {
|
||||
if (this.tradeShip == null) {
|
||||
const spawn = this.origOwner.canBuild(
|
||||
UnitType.TradeShip,
|
||||
this.srcPort.tile(),
|
||||
);
|
||||
if (spawn == false) {
|
||||
consolex.warn(`cannot build trade ship`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, 0, spawn);
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.tradeShip == null) {
|
||||
const spawn = this.origOwner.canBuild(UnitType.TradeShip, this.srcPort.tile())
|
||||
if (spawn == false) {
|
||||
consolex.warn(`cannot build trade ship`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
this.tradeShip = this.origOwner.buildUnit(UnitType.TradeShip, 0, spawn)
|
||||
}
|
||||
|
||||
if (!this.tradeShip.isActive()) {
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
|
||||
if (this.origOwner != this.tradeShip.owner()) {
|
||||
// Store as vairable in case ship is recaptured by previous owner
|
||||
this.wasCaptured = true
|
||||
}
|
||||
|
||||
if (!this.wasCaptured && (!this.dstPort.isActive() || !this.tradeShip.owner().isAlliedWith(this.dstPort.owner()))) {
|
||||
this.tradeShip.delete(false)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
|
||||
if (this.wasCaptured) {
|
||||
const ports = this.tradeShip.owner().units(UnitType.Port).sort(distSortUnit(this.mg, this.tradeShip))
|
||||
if (ports.length == 0) {
|
||||
this.tradeShip.delete(false)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
const dstPort = ports[0]
|
||||
const result = this.pathFinder.nextTile(this.tradeShip.tile(), dstPort.tile())
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
const gold = this.mg.config().tradeShipGold(this.mg.manhattanDist(this.srcPort.tile(), dstPort.tile()))
|
||||
this.tradeShip.owner().addGold(gold)
|
||||
this.mg.displayMessage(
|
||||
`Received ${renderNumber(gold)} gold from ship captured from ${this.origOwner.displayName()}`,
|
||||
MessageType.SUCCESS,
|
||||
this.tradeShip.owner().id()
|
||||
)
|
||||
this.tradeShip.delete(false)
|
||||
break
|
||||
case PathFindResultType.Pending:
|
||||
// Fire unit event to rerender.
|
||||
this.tradeShip.move(this.tradeShip.tile())
|
||||
break
|
||||
case PathFindResultType.NextTile:
|
||||
this.tradeShip.move(result.tile)
|
||||
break
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.warn('captured trade ship cannot find route')
|
||||
this.active = false
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (this.index >= this.path.length) {
|
||||
this.active = false
|
||||
const gold = this.mg.config().tradeShipGold(this.mg.manhattanDist(this.srcPort.tile(), this.dstPort.tile()))
|
||||
this.srcPort.owner().addGold(gold)
|
||||
this.dstPort.owner().addGold(gold)
|
||||
this.mg.displayMessage(
|
||||
`Received ${renderNumber(gold)} gold from trade with ${this.srcPort.owner().displayName()}`,
|
||||
MessageType.SUCCESS,
|
||||
this.dstPort.owner().id()
|
||||
)
|
||||
this.mg.displayMessage(
|
||||
`Received ${renderNumber(gold)} gold from trade with ${this.dstPort.owner().displayName()}`,
|
||||
MessageType.SUCCESS,
|
||||
this.srcPort.owner().id()
|
||||
)
|
||||
this.tradeShip.delete(false)
|
||||
return
|
||||
}
|
||||
this.tradeShip.move(this.path[this.index])
|
||||
this.index++
|
||||
if (!this.tradeShip.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
if (this.origOwner != this.tradeShip.owner()) {
|
||||
// Store as vairable in case ship is recaptured by previous owner
|
||||
this.wasCaptured = true;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
if (
|
||||
!this.wasCaptured &&
|
||||
(!this.dstPort.isActive() ||
|
||||
!this.tradeShip.owner().isAlliedWith(this.dstPort.owner()))
|
||||
) {
|
||||
this.tradeShip.delete(false);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
if (this.wasCaptured) {
|
||||
const ports = this.tradeShip
|
||||
.owner()
|
||||
.units(UnitType.Port)
|
||||
.sort(distSortUnit(this.mg, this.tradeShip));
|
||||
if (ports.length == 0) {
|
||||
this.tradeShip.delete(false);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
const dstPort = ports[0];
|
||||
const result = this.pathFinder.nextTile(
|
||||
this.tradeShip.tile(),
|
||||
dstPort.tile(),
|
||||
);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
const gold = this.mg
|
||||
.config()
|
||||
.tradeShipGold(
|
||||
this.mg.manhattanDist(this.srcPort.tile(), dstPort.tile()),
|
||||
);
|
||||
this.tradeShip.owner().addGold(gold);
|
||||
this.mg.displayMessage(
|
||||
`Received ${renderNumber(gold)} gold from ship captured from ${this.origOwner.displayName()}`,
|
||||
MessageType.SUCCESS,
|
||||
this.tradeShip.owner().id(),
|
||||
);
|
||||
this.tradeShip.delete(false);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
// Fire unit event to rerender.
|
||||
this.tradeShip.move(this.tradeShip.tile());
|
||||
break;
|
||||
case PathFindResultType.NextTile:
|
||||
this.tradeShip.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.PathNotFound:
|
||||
consolex.warn("captured trade ship cannot find route");
|
||||
this.active = false;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
if (this.index >= this.path.length) {
|
||||
this.active = false;
|
||||
const gold = this.mg
|
||||
.config()
|
||||
.tradeShipGold(
|
||||
this.mg.manhattanDist(this.srcPort.tile(), this.dstPort.tile()),
|
||||
);
|
||||
this.srcPort.owner().addGold(gold);
|
||||
this.dstPort.owner().addGold(gold);
|
||||
this.mg.displayMessage(
|
||||
`Received ${renderNumber(gold)} gold from trade with ${this.srcPort.owner().displayName()}`,
|
||||
MessageType.SUCCESS,
|
||||
this.dstPort.owner().id(),
|
||||
);
|
||||
this.mg.displayMessage(
|
||||
`Received ${renderNumber(gold)} gold from trade with ${this.dstPort.owner().displayName()}`,
|
||||
MessageType.SUCCESS,
|
||||
this.srcPort.owner().id(),
|
||||
);
|
||||
this.tradeShip.delete(false);
|
||||
return;
|
||||
}
|
||||
this.tradeShip.move(this.path[this.index]);
|
||||
this.index++;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
import { Unit, Cell, Execution, Game, Player, PlayerID, TerraNullius, UnitType, TerrainType } from "../game/Game";
|
||||
import {
|
||||
Unit,
|
||||
Cell,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerID,
|
||||
TerraNullius,
|
||||
UnitType,
|
||||
TerrainType,
|
||||
} from "../game/Game";
|
||||
import { AttackExecution } from "./AttackExecution";
|
||||
import { MessageType } from '../game/Game';
|
||||
import { MessageType } from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { consolex } from "../Consolex";
|
||||
@@ -8,138 +18,154 @@ import { TileRef } from "../game/GameMap";
|
||||
import { targetTransportTile } from "../Util";
|
||||
|
||||
export class TransportShipExecution implements Execution {
|
||||
private lastMove: number;
|
||||
|
||||
private lastMove: number
|
||||
// TODO: make this configurable
|
||||
private ticksPerMove = 1;
|
||||
|
||||
// TODO: make this configurable
|
||||
private ticksPerMove = 1
|
||||
private active = true;
|
||||
|
||||
private active = true
|
||||
private mg: Game;
|
||||
private attacker: Player;
|
||||
private target: Player | TerraNullius;
|
||||
|
||||
private mg: Game
|
||||
private attacker: Player
|
||||
private target: Player | TerraNullius
|
||||
// TODO make private
|
||||
public path: TileRef[];
|
||||
private src: TileRef | null;
|
||||
private dst: TileRef | null;
|
||||
|
||||
// TODO make private
|
||||
public path: TileRef[]
|
||||
private src: TileRef | null
|
||||
private dst: TileRef | null
|
||||
private boat: Unit;
|
||||
|
||||
private pathFinder: PathFinder;
|
||||
|
||||
private boat: Unit
|
||||
constructor(
|
||||
private attackerID: PlayerID,
|
||||
private targetID: PlayerID | null,
|
||||
private ref: TileRef,
|
||||
private troops: number | null,
|
||||
) {}
|
||||
|
||||
private pathFinder: PathFinder
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private attackerID: PlayerID,
|
||||
private targetID: PlayerID | null,
|
||||
private ref: TileRef,
|
||||
private troops: number | null,
|
||||
) { }
|
||||
init(mg: Game, ticks: number) {
|
||||
this.lastMove = ticks;
|
||||
this.mg = mg;
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, false, 2);
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
this.attacker = mg.player(this.attackerID);
|
||||
|
||||
if (
|
||||
this.attacker.units(UnitType.TransportShip).length >=
|
||||
mg.config().boatMaxNumber()
|
||||
) {
|
||||
mg.displayMessage(
|
||||
`No boats available, max ${mg.config().boatMaxNumber()}`,
|
||||
MessageType.WARN,
|
||||
this.attackerID,
|
||||
);
|
||||
this.active = false;
|
||||
this.attacker.addTroops(this.troops);
|
||||
return;
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number) {
|
||||
this.lastMove = ticks
|
||||
this.mg = mg
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, false, 2)
|
||||
if (this.targetID == null || this.targetID == this.mg.terraNullius().id()) {
|
||||
this.target = mg.terraNullius();
|
||||
} else {
|
||||
this.target = mg.player(this.targetID);
|
||||
}
|
||||
|
||||
this.attacker = mg.player(this.attackerID)
|
||||
if (this.troops == null) {
|
||||
this.troops = this.mg
|
||||
.config()
|
||||
.boatAttackAmount(this.attacker, this.target);
|
||||
}
|
||||
|
||||
if (this.attacker.units(UnitType.TransportShip).length >= mg.config().boatMaxNumber()) {
|
||||
mg.displayMessage(`No boats available, max ${mg.config().boatMaxNumber()}`, MessageType.WARN, this.attackerID)
|
||||
this.active = false
|
||||
this.attacker.addTroops(this.troops)
|
||||
return
|
||||
this.troops = Math.min(this.troops, this.attacker.troops());
|
||||
|
||||
this.dst = targetTransportTile(this.mg, this.ref);
|
||||
if (this.dst == null) {
|
||||
consolex.warn(
|
||||
`${this.attacker} cannot send ship to ${this.target}, cannot find attack tile`,
|
||||
);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
const src = this.attacker.canBuild(UnitType.TransportShip, this.dst);
|
||||
if (src == false) {
|
||||
consolex.warn(`can't build transport ship`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.src = src;
|
||||
|
||||
this.boat = this.attacker.buildUnit(
|
||||
UnitType.TransportShip,
|
||||
this.troops,
|
||||
this.src,
|
||||
);
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (!this.active) {
|
||||
return;
|
||||
}
|
||||
if (!this.boat.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
if (ticks - this.lastMove < this.ticksPerMove) {
|
||||
return;
|
||||
}
|
||||
this.lastMove = ticks;
|
||||
|
||||
const result = this.pathFinder.nextTile(this.boat.tile(), this.dst);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
if (this.mg.owner(this.dst) == this.attacker) {
|
||||
this.attacker.addTroops(this.troops);
|
||||
this.boat.delete(false);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.targetID == null || this.targetID == this.mg.terraNullius().id()) {
|
||||
this.target = mg.terraNullius()
|
||||
if (this.target.isPlayer() && this.attacker.isAlliedWith(this.target)) {
|
||||
this.target.addTroops(this.troops);
|
||||
} else {
|
||||
this.target = mg.player(this.targetID)
|
||||
this.attacker.conquer(this.dst);
|
||||
this.mg.addExecution(
|
||||
new AttackExecution(
|
||||
this.troops,
|
||||
this.attacker.id(),
|
||||
this.targetID,
|
||||
this.dst,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (this.troops == null) {
|
||||
this.troops = this.mg.config().boatAttackAmount(this.attacker, this.target)
|
||||
}
|
||||
|
||||
this.troops = Math.min(this.troops, this.attacker.troops())
|
||||
|
||||
this.dst = targetTransportTile(this.mg, this.ref)
|
||||
if (this.dst == null) {
|
||||
consolex.warn(`${this.attacker} cannot send ship to ${this.target}, cannot find attack tile`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
const src = this.attacker.canBuild(UnitType.TransportShip, this.dst)
|
||||
if (src == false) {
|
||||
consolex.warn(`can't build transport ship`)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
|
||||
this.src = src
|
||||
|
||||
this.boat = this.attacker.buildUnit(UnitType.TransportShip, this.troops, this.src)
|
||||
this.boat.delete(false);
|
||||
this.active = false;
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
this.boat.move(result.tile);
|
||||
break;
|
||||
case PathFindResultType.Pending:
|
||||
break;
|
||||
case PathFindResultType.PathNotFound:
|
||||
// TODO: add to poisoned port list
|
||||
consolex.warn(`path not found tot dst`);
|
||||
this.boat.delete(false);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (!this.active) {
|
||||
return
|
||||
}
|
||||
if (!this.boat.isActive()) {
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
if (ticks - this.lastMove < this.ticksPerMove) {
|
||||
return
|
||||
}
|
||||
this.lastMove = ticks
|
||||
|
||||
const result = this.pathFinder.nextTile(this.boat.tile(), this.dst)
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
if (this.mg.owner(this.dst) == this.attacker) {
|
||||
this.attacker.addTroops(this.troops)
|
||||
this.boat.delete(false)
|
||||
this.active = false
|
||||
return
|
||||
}
|
||||
if (this.target.isPlayer() && this.attacker.isAlliedWith(this.target)) {
|
||||
this.target.addTroops(this.troops)
|
||||
} else {
|
||||
this.attacker.conquer(this.dst)
|
||||
this.mg.addExecution(
|
||||
new AttackExecution(this.troops, this.attacker.id(), this.targetID, this.dst, false)
|
||||
)
|
||||
}
|
||||
this.boat.delete(false)
|
||||
this.active = false
|
||||
return
|
||||
case PathFindResultType.NextTile:
|
||||
this.boat.move(result.tile)
|
||||
break
|
||||
case PathFindResultType.Pending:
|
||||
break
|
||||
case PathFindResultType.PathNotFound:
|
||||
// TODO: add to poisoned port list
|
||||
consolex.warn(`path not found tot dst`)
|
||||
this.boat.delete(false)
|
||||
this.active = false
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this.attacker
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
owner(): Player {
|
||||
return this.attacker;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+46
-42
@@ -1,52 +1,56 @@
|
||||
import { euclDistFN, GameMap, TileRef } from "../game/GameMap";
|
||||
|
||||
|
||||
export function getSpawnTiles(gm: GameMap, tile: TileRef): TileRef[] {
|
||||
return Array.from(gm.bfs(tile, euclDistFN(tile, 4)))
|
||||
.filter(t => !gm.hasOwner(t) && gm.isLand(t))
|
||||
return Array.from(gm.bfs(tile, euclDistFN(tile, 4))).filter(
|
||||
(t) => !gm.hasOwner(t) && gm.isLand(t),
|
||||
);
|
||||
}
|
||||
|
||||
export function closestTwoTiles(gm: GameMap, x: Iterable<TileRef>, y: Iterable<TileRef>): { x: TileRef, y: TileRef } {
|
||||
const xSorted = Array.from(x).sort((a, b) => gm.x(a) - gm.x(b));
|
||||
const ySorted = Array.from(y).sort((a, b) => gm.x(a) - gm.x(b));
|
||||
export function closestTwoTiles(
|
||||
gm: GameMap,
|
||||
x: Iterable<TileRef>,
|
||||
y: Iterable<TileRef>,
|
||||
): { x: TileRef; y: TileRef } {
|
||||
const xSorted = Array.from(x).sort((a, b) => gm.x(a) - gm.x(b));
|
||||
const ySorted = Array.from(y).sort((a, b) => gm.x(a) - gm.x(b));
|
||||
|
||||
if (xSorted.length == 0 || ySorted.length == 0) {
|
||||
return null;
|
||||
if (xSorted.length == 0 || ySorted.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
let minDistance = Infinity;
|
||||
let result = { x: xSorted[0], y: ySorted[0] };
|
||||
|
||||
while (i < xSorted.length && j < ySorted.length) {
|
||||
const currentX = xSorted[i];
|
||||
const currentY = ySorted[j];
|
||||
|
||||
const distance =
|
||||
Math.abs(gm.x(currentX) - gm.x(currentY)) +
|
||||
Math.abs(gm.y(currentX) - gm.y(currentY));
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
result = { x: currentX, y: currentY };
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
let minDistance = Infinity;
|
||||
let result = { x: xSorted[0], y: ySorted[0] };
|
||||
|
||||
while (i < xSorted.length && j < ySorted.length) {
|
||||
const currentX = xSorted[i];
|
||||
const currentY = ySorted[j];
|
||||
|
||||
const distance =
|
||||
Math.abs(gm.x(currentX) - gm.x(currentY)) +
|
||||
Math.abs(gm.y(currentX) - gm.y(currentY));
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
result = { x: currentX, y: currentY };
|
||||
}
|
||||
|
||||
// If we're at the end of X, must move Y forward
|
||||
if (i === xSorted.length - 1) {
|
||||
j++;
|
||||
}
|
||||
// If we're at the end of Y, must move X forward
|
||||
else if (j === ySorted.length - 1) {
|
||||
i++;
|
||||
}
|
||||
// Otherwise, move whichever pointer has smaller x value
|
||||
else if (gm.x(currentX) < gm.x(currentY)) {
|
||||
i++;
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
// If we're at the end of X, must move Y forward
|
||||
if (i === xSorted.length - 1) {
|
||||
j++;
|
||||
}
|
||||
// If we're at the end of Y, must move X forward
|
||||
else if (j === ySorted.length - 1) {
|
||||
i++;
|
||||
}
|
||||
// Otherwise, move whichever pointer has smaller x value
|
||||
else if (gm.x(currentX) < gm.x(currentY)) {
|
||||
i++;
|
||||
} else {
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,49 +1,53 @@
|
||||
import { EventBus, GameEvent } from "../EventBus"
|
||||
import { Execution, Game, Player, PlayerID } from "../game/Game"
|
||||
import { EventBus, GameEvent } from "../EventBus";
|
||||
import { Execution, Game, Player, PlayerID } from "../game/Game";
|
||||
|
||||
export class WinEvent implements GameEvent {
|
||||
constructor(public readonly winner: Player) { }
|
||||
constructor(public readonly winner: Player) {}
|
||||
}
|
||||
|
||||
export class WinCheckExecution implements Execution {
|
||||
private active = true;
|
||||
|
||||
private active = true
|
||||
private mg: Game;
|
||||
|
||||
private mg: Game
|
||||
constructor() {}
|
||||
|
||||
constructor() {
|
||||
init(mg: Game, ticks: number) {
|
||||
this.mg = mg;
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (ticks % 10 != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
init(mg: Game, ticks: number) {
|
||||
this.mg = mg
|
||||
const sorted = this.mg
|
||||
.players()
|
||||
.sort((a, b) => b.numTilesOwned() - a.numTilesOwned());
|
||||
if (sorted.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
tick(ticks: number) {
|
||||
if (ticks % 10 != 0) {
|
||||
return
|
||||
}
|
||||
const sorted = this.mg.players().sort((a, b) => b.numTilesOwned() - a.numTilesOwned())
|
||||
if (sorted.length == 0) {
|
||||
return
|
||||
}
|
||||
const max = sorted[0]
|
||||
const numTilesWithoutFallout = this.mg.numLandTiles() - this.mg.numTilesWithFallout()
|
||||
if (max.numTilesOwned() / numTilesWithoutFallout * 100 > this.mg.config().percentageTilesOwnedToWin()) {
|
||||
this.mg.setWinner(max)
|
||||
console.log(`${max.name()} has won the game`)
|
||||
this.active = false
|
||||
}
|
||||
const max = sorted[0];
|
||||
const numTilesWithoutFallout =
|
||||
this.mg.numLandTiles() - this.mg.numTilesWithFallout();
|
||||
if (
|
||||
(max.numTilesOwned() / numTilesWithoutFallout) * 100 >
|
||||
this.mg.config().percentageTilesOwnedToWin()
|
||||
) {
|
||||
this.mg.setWinner(max);
|
||||
console.log(`${max.name()} has won the game`);
|
||||
this.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,51 @@
|
||||
import { consolex } from "../../Consolex";
|
||||
import {AllianceRequest, Execution, Game, Player, PlayerID} from "../../game/Game";
|
||||
import {
|
||||
AllianceRequest,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerID,
|
||||
} from "../../game/Game";
|
||||
|
||||
export class AllianceRequestExecution implements Execution {
|
||||
private active = true
|
||||
private mg: Game = null
|
||||
private requestor: Player;
|
||||
private recipient: Player
|
||||
private active = true;
|
||||
private mg: Game = null;
|
||||
private requestor: Player;
|
||||
private recipient: Player;
|
||||
|
||||
constructor(private requestorID: PlayerID, private recipientID: PlayerID) { }
|
||||
constructor(
|
||||
private requestorID: PlayerID,
|
||||
private recipientID: PlayerID,
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.requestor = mg.player(this.requestorID)
|
||||
this.recipient = mg.player(this.recipientID)
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
this.requestor = mg.player(this.requestorID);
|
||||
this.recipient = mg.player(this.recipientID);
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.requestor.isAlliedWith(this.recipient)) {
|
||||
consolex.warn("already allied");
|
||||
} else if (
|
||||
this.requestor.recentOrPendingAllianceRequestWith(this.recipient)
|
||||
) {
|
||||
consolex.warn("recent or pending alliance request");
|
||||
} else {
|
||||
this.requestor.createAllianceRequest(this.recipient);
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.requestor.isAlliedWith(this.recipient)) {
|
||||
consolex.warn('already allied')
|
||||
} else if (this.requestor.recentOrPendingAllianceRequestWith(this.recipient)) {
|
||||
consolex.warn('recent or pending alliance request')
|
||||
} else {
|
||||
this.requestor.createAllianceRequest(this.recipient)
|
||||
}
|
||||
this.active = false
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,61 @@
|
||||
import { consolex } from "../../Consolex";
|
||||
import { AllianceRequest, Execution, Game, Player, PlayerID } from "../../game/Game";
|
||||
import {
|
||||
AllianceRequest,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerID,
|
||||
} from "../../game/Game";
|
||||
|
||||
export class AllianceRequestReplyExecution implements Execution {
|
||||
private active = true
|
||||
private mg: Game = null
|
||||
private requestor: Player;
|
||||
private recipient: Player
|
||||
private active = true;
|
||||
private mg: Game = null;
|
||||
private requestor: Player;
|
||||
private recipient: Player;
|
||||
|
||||
constructor(private requestorID: PlayerID, private recipientID: PlayerID, private accept: boolean) { }
|
||||
constructor(
|
||||
private requestorID: PlayerID,
|
||||
private recipientID: PlayerID,
|
||||
private accept: boolean,
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg
|
||||
this.requestor = mg.player(this.requestorID)
|
||||
this.recipient = mg.player(this.recipientID)
|
||||
}
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
this.requestor = mg.player(this.requestorID);
|
||||
this.recipient = mg.player(this.recipientID);
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.requestor.isAlliedWith(this.recipient)) {
|
||||
consolex.warn('already allied')
|
||||
tick(ticks: number): void {
|
||||
if (this.requestor.isAlliedWith(this.recipient)) {
|
||||
consolex.warn("already allied");
|
||||
} else {
|
||||
const request = this.requestor
|
||||
.outgoingAllianceRequests()
|
||||
.find((ar) => ar.recipient() == this.recipient);
|
||||
if (request == null) {
|
||||
consolex.warn("no alliance request found");
|
||||
} else {
|
||||
if (this.accept) {
|
||||
request.accept();
|
||||
this.requestor.updateRelation(this.recipient, 100);
|
||||
this.recipient.updateRelation(this.requestor, 100);
|
||||
} else {
|
||||
const request = this.requestor.outgoingAllianceRequests().find(ar => ar.recipient() == this.recipient)
|
||||
if (request == null) {
|
||||
consolex.warn('no alliance request found')
|
||||
} else {
|
||||
if (this.accept) {
|
||||
request.accept()
|
||||
this.requestor.updateRelation(this.recipient, 100)
|
||||
this.recipient.updateRelation(this.requestor, 100)
|
||||
} else {
|
||||
request.reject()
|
||||
}
|
||||
}
|
||||
request.reject();
|
||||
}
|
||||
this.active = false
|
||||
}
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,54 @@
|
||||
import { consolex } from "../../Consolex";
|
||||
import { AllianceRequest, Execution, Game, Player, PlayerID } from "../../game/Game";
|
||||
import {
|
||||
AllianceRequest,
|
||||
Execution,
|
||||
Game,
|
||||
Player,
|
||||
PlayerID,
|
||||
} from "../../game/Game";
|
||||
|
||||
export class BreakAllianceExecution implements Execution {
|
||||
private active = true
|
||||
private requestor: Player;
|
||||
private recipient: Player
|
||||
private mg: Game
|
||||
private active = true;
|
||||
private requestor: Player;
|
||||
private recipient: Player;
|
||||
private mg: Game;
|
||||
|
||||
constructor(private requestorID: PlayerID, private recipientID: PlayerID) { }
|
||||
constructor(
|
||||
private requestorID: PlayerID,
|
||||
private recipientID: PlayerID,
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.requestor = mg.player(this.requestorID)
|
||||
this.recipient = mg.player(this.recipientID)
|
||||
this.mg = mg
|
||||
}
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.requestor = mg.player(this.requestorID);
|
||||
this.recipient = mg.player(this.recipientID);
|
||||
this.mg = mg;
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
const alliance = this.requestor.allianceWith(this.recipient)
|
||||
if (alliance == null) {
|
||||
consolex.warn('cant break alliance, not allied')
|
||||
} else {
|
||||
this.requestor.breakAlliance(alliance)
|
||||
this.recipient.updateRelation(this.requestor, -200)
|
||||
for (const player of this.mg.players()) {
|
||||
if (player != this.requestor) {
|
||||
player.updateRelation(this.requestor, -40)
|
||||
}
|
||||
}
|
||||
tick(ticks: number): void {
|
||||
const alliance = this.requestor.allianceWith(this.recipient);
|
||||
if (alliance == null) {
|
||||
consolex.warn("cant break alliance, not allied");
|
||||
} else {
|
||||
this.requestor.breakAlliance(alliance);
|
||||
this.recipient.updateRelation(this.requestor, -200);
|
||||
for (const player of this.mg.players()) {
|
||||
if (player != this.requestor) {
|
||||
player.updateRelation(this.requestor, -40);
|
||||
}
|
||||
this.active = false
|
||||
}
|
||||
}
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null
|
||||
}
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,233 @@
|
||||
export const BOT_NAME_PREFIXES = [
|
||||
"Akkadian", "Babylonian", "Assyrian", "Sumerian", "Hittite", "Phoenician",
|
||||
"Canaanite", "Minoan", "Mycenaean", "Etruscan", "Scythian", "Thracian",
|
||||
"Dacian", "Illyrian", "Median", "Chaldean",
|
||||
"Roman", "Greek", "Byzantine", "Persian", "Parthian", "Seleucid",
|
||||
"Ptolemaic", "Palmyrene", "Macedonian", "Carthaginian",
|
||||
"Ming", "Tang", "Song", "Yuan",
|
||||
"Mauryan", "Kushan", "Rajput", "Mughal", "Satavahana", "Vijayanagara",
|
||||
"Egyptian", "Nubian", "Kushite", "Aksumite", "Ethiopian", "Songhai",
|
||||
"Malian", "Ghanaian", "Benin", "Ashanti", "Zulu", "Tuareg", "Berber",
|
||||
"Kanem-Bornu", "Buganda", "Mossi", "Swahili", "Somali", "Wolof",
|
||||
"Umayyad", "Abbasid", "Ayyubid", "Fatimid", "Mamluk", "Seljuk",
|
||||
"Safavid", "Ottoman", "Almoravid", "Almohad", "Rashidun", "Ziyarid",
|
||||
"Frankish", "Visigothic", "Ostrogothic", "Viking", "Norman", "Saxon",
|
||||
"Anglo-Saxon", "Celtic", "Gaulish", "Carolingian", "Merovingian",
|
||||
"Capetian", "Plantagenet", "Tudor", "Stuart", "Habsburg", "Romanov",
|
||||
"Lancaster", "York", "Bourbon", "Napoleonic",
|
||||
"British", "French", "Spanish", "Portuguese", "Dutch", "Russian",
|
||||
"German", "Italian", "Swedish", "Norwegian", "Danish", "Polish",
|
||||
"Hungarian", "Austrian", "Swiss", "Czech", "Slovak", "Serbian",
|
||||
"Croatian", "Bosnian", "Montenegrin", "Bulgarian", "Romanian",
|
||||
"Apache", "Sioux", "Cherokee", "Navajo", "Iroquois", "Inuit", "Arawak",
|
||||
"Carib", "Taino", "Aztec", "Mayan", "Incan", "Mapuche", "Guarani",
|
||||
"Tupi", "Yanomami", "Zuni", "Hopi", "Kiowa", "Comanche", "Shoshone",
|
||||
"Japanese", "Ryukyu", "Ainu", "Cham", "Khmer", "Thai", "Vietnamese",
|
||||
"Burmese", "Balinese", "Malay", "Filipino", "Mongolian",
|
||||
"Korean", "Tibetan", "Manchu", "Uyghur", "Hmong", "Karen", "Pyu",
|
||||
"Hawaiian", "Fijian", "Tongan", "Samoan", "Maori", "Micronesian",
|
||||
"Hebrew", "Armenian", "Georgian", "Phoenician", "Assyrian", "Chaldean",
|
||||
"Kurdish", "Turkic", "Kazakh", "Uzbek", "Kyrgyz", "Tajik", "Uighur",
|
||||
"Pashtun", "Baloch", "Afghan", "Persian",
|
||||
]
|
||||
export const BOT_NAME_SUFFIXES = [
|
||||
"Empire", "Dynasty", "Kingdom", "Sultanate", "Confederation", "Union",
|
||||
"Republic", "Caliphate", "Dominion", "Realm", "State",
|
||||
"Federation", "Territory", "Commonwealth", "League", "Duchy", "Province",
|
||||
"Protectorate", "Colony", "Mandate", "Free State","Canton", "Region", "Nation",
|
||||
"Assembly", "Hierarchy", "Archduchy", "Grand Duchy","Metropolis", "Cluster",
|
||||
"Alliance", "Tribunal", "Council", "Confederacy", "Order", "Regime",
|
||||
"Dominion", "Syndicate","Guild", "Corporation", "Patriarchy",
|
||||
"Matriarchy","Legion", "Horde", "Clan", "Brotherhood", "Sisterhood","Ascendancy", "Supremacy",
|
||||
"Province","Kingdoms", "Tribes", "Dominion", "Assembly", "Republics"
|
||||
"Akkadian",
|
||||
"Babylonian",
|
||||
"Assyrian",
|
||||
"Sumerian",
|
||||
"Hittite",
|
||||
"Phoenician",
|
||||
"Canaanite",
|
||||
"Minoan",
|
||||
"Mycenaean",
|
||||
"Etruscan",
|
||||
"Scythian",
|
||||
"Thracian",
|
||||
"Dacian",
|
||||
"Illyrian",
|
||||
"Median",
|
||||
"Chaldean",
|
||||
"Roman",
|
||||
"Greek",
|
||||
"Byzantine",
|
||||
"Persian",
|
||||
"Parthian",
|
||||
"Seleucid",
|
||||
"Ptolemaic",
|
||||
"Palmyrene",
|
||||
"Macedonian",
|
||||
"Carthaginian",
|
||||
"Ming",
|
||||
"Tang",
|
||||
"Song",
|
||||
"Yuan",
|
||||
"Mauryan",
|
||||
"Kushan",
|
||||
"Rajput",
|
||||
"Mughal",
|
||||
"Satavahana",
|
||||
"Vijayanagara",
|
||||
"Egyptian",
|
||||
"Nubian",
|
||||
"Kushite",
|
||||
"Aksumite",
|
||||
"Ethiopian",
|
||||
"Songhai",
|
||||
"Malian",
|
||||
"Ghanaian",
|
||||
"Benin",
|
||||
"Ashanti",
|
||||
"Zulu",
|
||||
"Tuareg",
|
||||
"Berber",
|
||||
"Kanem-Bornu",
|
||||
"Buganda",
|
||||
"Mossi",
|
||||
"Swahili",
|
||||
"Somali",
|
||||
"Wolof",
|
||||
"Umayyad",
|
||||
"Abbasid",
|
||||
"Ayyubid",
|
||||
"Fatimid",
|
||||
"Mamluk",
|
||||
"Seljuk",
|
||||
"Safavid",
|
||||
"Ottoman",
|
||||
"Almoravid",
|
||||
"Almohad",
|
||||
"Rashidun",
|
||||
"Ziyarid",
|
||||
"Frankish",
|
||||
"Visigothic",
|
||||
"Ostrogothic",
|
||||
"Viking",
|
||||
"Norman",
|
||||
"Saxon",
|
||||
"Anglo-Saxon",
|
||||
"Celtic",
|
||||
"Gaulish",
|
||||
"Carolingian",
|
||||
"Merovingian",
|
||||
"Capetian",
|
||||
"Plantagenet",
|
||||
"Tudor",
|
||||
"Stuart",
|
||||
"Habsburg",
|
||||
"Romanov",
|
||||
"Lancaster",
|
||||
"York",
|
||||
"Bourbon",
|
||||
"Napoleonic",
|
||||
"British",
|
||||
"French",
|
||||
"Spanish",
|
||||
"Portuguese",
|
||||
"Dutch",
|
||||
"Russian",
|
||||
"German",
|
||||
"Italian",
|
||||
"Swedish",
|
||||
"Norwegian",
|
||||
"Danish",
|
||||
"Polish",
|
||||
"Hungarian",
|
||||
"Austrian",
|
||||
"Swiss",
|
||||
"Czech",
|
||||
"Slovak",
|
||||
"Serbian",
|
||||
"Croatian",
|
||||
"Bosnian",
|
||||
"Montenegrin",
|
||||
"Bulgarian",
|
||||
"Romanian",
|
||||
"Apache",
|
||||
"Sioux",
|
||||
"Cherokee",
|
||||
"Navajo",
|
||||
"Iroquois",
|
||||
"Inuit",
|
||||
"Arawak",
|
||||
"Carib",
|
||||
"Taino",
|
||||
"Aztec",
|
||||
"Mayan",
|
||||
"Incan",
|
||||
"Mapuche",
|
||||
"Guarani",
|
||||
"Tupi",
|
||||
"Yanomami",
|
||||
"Zuni",
|
||||
"Hopi",
|
||||
"Kiowa",
|
||||
"Comanche",
|
||||
"Shoshone",
|
||||
"Japanese",
|
||||
"Ryukyu",
|
||||
"Ainu",
|
||||
"Cham",
|
||||
"Khmer",
|
||||
"Thai",
|
||||
"Vietnamese",
|
||||
"Burmese",
|
||||
"Balinese",
|
||||
"Malay",
|
||||
"Filipino",
|
||||
"Mongolian",
|
||||
"Korean",
|
||||
"Tibetan",
|
||||
"Manchu",
|
||||
"Uyghur",
|
||||
"Hmong",
|
||||
"Karen",
|
||||
"Pyu",
|
||||
"Hawaiian",
|
||||
"Fijian",
|
||||
"Tongan",
|
||||
"Samoan",
|
||||
"Maori",
|
||||
"Micronesian",
|
||||
"Hebrew",
|
||||
"Armenian",
|
||||
"Georgian",
|
||||
"Phoenician",
|
||||
"Assyrian",
|
||||
"Chaldean",
|
||||
"Kurdish",
|
||||
"Turkic",
|
||||
"Kazakh",
|
||||
"Uzbek",
|
||||
"Kyrgyz",
|
||||
"Tajik",
|
||||
"Uighur",
|
||||
"Pashtun",
|
||||
"Baloch",
|
||||
"Afghan",
|
||||
"Persian",
|
||||
];
|
||||
export const BOT_NAME_SUFFIXES = [
|
||||
"Empire",
|
||||
"Dynasty",
|
||||
"Kingdom",
|
||||
"Sultanate",
|
||||
"Confederation",
|
||||
"Union",
|
||||
"Republic",
|
||||
"Caliphate",
|
||||
"Dominion",
|
||||
"Realm",
|
||||
"State",
|
||||
"Federation",
|
||||
"Territory",
|
||||
"Commonwealth",
|
||||
"League",
|
||||
"Duchy",
|
||||
"Province",
|
||||
"Protectorate",
|
||||
"Colony",
|
||||
"Mandate",
|
||||
"Free State",
|
||||
"Canton",
|
||||
"Region",
|
||||
"Nation",
|
||||
"Assembly",
|
||||
"Hierarchy",
|
||||
"Archduchy",
|
||||
"Grand Duchy",
|
||||
"Metropolis",
|
||||
"Cluster",
|
||||
"Alliance",
|
||||
"Tribunal",
|
||||
"Council",
|
||||
"Confederacy",
|
||||
"Order",
|
||||
"Regime",
|
||||
"Dominion",
|
||||
"Syndicate",
|
||||
"Guild",
|
||||
"Corporation",
|
||||
"Patriarchy",
|
||||
"Matriarchy",
|
||||
"Legion",
|
||||
"Horde",
|
||||
"Clan",
|
||||
"Brotherhood",
|
||||
"Sisterhood",
|
||||
"Ascendancy",
|
||||
"Supremacy",
|
||||
"Province",
|
||||
"Kingdoms",
|
||||
"Tribes",
|
||||
"Dominion",
|
||||
"Assembly",
|
||||
"Republics",
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user