combine battleship + destroyer => warship.

This commit is contained in:
Evan
2025-02-02 14:31:30 -08:00
parent af0d6a289a
commit c109d23f9f
14 changed files with 159 additions and 403 deletions
@@ -22,7 +22,7 @@ import { renderNumber, renderTroops } from "../../Utils";
function euclideanDistWorld(
coord: { x: number; y: number },
tileRef: TileRef,
game: GameView,
game: GameView
): number {
const x = game.x(tileRef);
const y = game.y(tileRef);
@@ -71,7 +71,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
init() {
this.eventBus.on(MouseMoveEvent, (e: MouseMoveEvent) =>
this.onMouseEvent(e),
this.onMouseEvent(e)
);
this._isActive = true;
}
@@ -111,7 +111,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
this.setVisible(true);
} else if (!this.game.isLand(tile)) {
const units = this.game
.units(UnitType.Destroyer, UnitType.Battleship, UnitType.TradeShip)
.units(UnitType.Warship, UnitType.TradeShip, UnitType.TransportShip)
.filter((u) => euclideanDistWorld(worldCoord, u.tile(), this.game) < 50)
.sort(distSortUnitWorld(worldCoord, this.game));
+26 -70
View File
@@ -36,7 +36,7 @@ export class UnitLayer implements Layer {
constructor(
private game: GameView,
private eventBus: EventBus,
private clientID: ClientID,
private clientID: ClientID
) {
this.theme = game.config().theme();
}
@@ -65,7 +65,7 @@ export class UnitLayer implements Layer {
-this.game.width() / 2,
-this.game.height() / 2,
this.game.width(),
this.game.height(),
this.game.height()
);
}
@@ -103,11 +103,8 @@ export class UnitLayer implements Layer {
case UnitType.TransportShip:
this.handleBoatEvent(unit);
break;
case UnitType.Destroyer:
this.handleDestroyerEvent(unit);
break;
case UnitType.Battleship:
this.handleBattleshipEvent(unit);
case UnitType.Warship:
this.handleWarShipEvent(unit);
break;
case UnitType.Shell:
this.handleShellEvent(unit);
@@ -122,54 +119,13 @@ export class UnitLayer implements Layer {
}
}
private handleDestroyerEvent(unit: UnitView) {
private handleWarShipEvent(unit: UnitView) {
const rel = this.relationship(unit);
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
euclDistFN(unit.lastTile(), 4),
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
if (!unit.isActive()) {
return;
}
// Paint border
for (const t of this.game.bfs(unit.tile(), euclDistFN(unit.tile(), 4))) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
255,
);
}
// Paint territory
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 3),
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
255,
);
}
}
private handleBattleshipEvent(unit: UnitView) {
const rel = this.relationship(unit);
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
euclDistFN(unit.lastTile(), 6),
euclDistFN(unit.lastTile(), 6)
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -185,21 +141,21 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
255,
255
);
}
// Paint border
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 4),
manhattanDistFN(unit.tile(), 4)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
255,
255
);
}
@@ -210,7 +166,7 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
255,
255
);
}
}
@@ -236,14 +192,14 @@ export class UnitLayer implements Layer {
this.game.y(unit.tile()),
rel,
this.theme.borderColor(unit.owner().info()),
255,
255
);
this.paintCell(
this.game.x(unit.lastTile()),
this.game.y(unit.lastTile()),
rel,
this.theme.borderColor(unit.owner().info()),
255,
255
);
}
@@ -253,7 +209,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
euclDistFN(unit.lastTile(), 2),
euclDistFN(unit.lastTile(), 2)
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -266,7 +222,7 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
255,
255
);
}
}
@@ -278,7 +234,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
euclDistFN(unit.lastTile(), 3),
euclDistFN(unit.lastTile(), 3)
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -287,28 +243,28 @@ export class UnitLayer implements Layer {
// Paint territory
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 2),
manhattanDistFN(unit.tile(), 2)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
255,
255
);
}
// Paint border
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 1),
manhattanDistFN(unit.tile(), 1)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
255,
255
);
}
}
@@ -326,7 +282,7 @@ export class UnitLayer implements Layer {
// Clear previous area
for (const t of this.game.bfs(
unit.lastTile(),
manhattanDistFN(unit.lastTile(), 3),
manhattanDistFN(unit.lastTile(), 3)
)) {
this.clearCell(this.game.x(t), this.game.y(t));
}
@@ -339,35 +295,35 @@ export class UnitLayer implements Layer {
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
150,
150
);
}
// Paint border
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 2),
manhattanDistFN(unit.tile(), 2)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.borderColor(unit.owner().info()),
255,
255
);
}
// Paint territory
for (const t of this.game.bfs(
unit.tile(),
manhattanDistFN(unit.tile(), 1),
manhattanDistFN(unit.tile(), 1)
)) {
this.paintCell(
this.game.x(t),
this.game.y(t),
rel,
this.theme.territoryColor(unit.owner().info()),
255,
255
);
}
} else {
@@ -383,7 +339,7 @@ export class UnitLayer implements Layer {
y: number,
relationship: Relationship,
color: Colord,
alpha: number,
alpha: number
) {
this.clearCell(x, y);
if (this.alternateView) {
@@ -11,15 +11,13 @@ import {
import { BuildUnitIntentEvent } from "../../../Transport";
import atomBombIcon from "../../../../../resources/images/NukeIconWhite.svg";
import hydrogenBombIcon from "../../../../../resources/images/MushroomCloudIconWhite.svg";
import destroyerIcon from "../../../../../resources/images/DestroyerIconWhite.svg";
import battleshipIcon from "../../../../../resources/images/BattleshipIconWhite.svg";
import warshipIcon from "../../../../../resources/images/BattleshipIconWhite.svg";
import missileSiloIcon from "../../../../../resources/images/MissileSiloIconWhite.svg";
import goldCoinIcon from "../../../../../resources/images/GoldCoinIcon.svg";
import portIcon from "../../../../../resources/images/PortIcon.svg";
import shieldIcon from "../../../../../resources/images/ShieldIconWhite.svg";
import cityIcon from "../../../../../resources/images/CityIconWhite.svg";
import { renderNumber } from "../../../Utils";
import { ContextMenuEvent } from "../../../InputHandler";
import { GameView, PlayerView } from "../../../../core/game/GameView";
interface BuildItemDisplay {
@@ -31,8 +29,7 @@ const buildTable: BuildItemDisplay[][] = [
[
{ unitType: UnitType.AtomBomb, icon: atomBombIcon },
{ unitType: UnitType.HydrogenBomb, icon: hydrogenBombIcon },
{ unitType: UnitType.Destroyer, icon: destroyerIcon },
{ unitType: UnitType.Battleship, icon: battleshipIcon },
{ unitType: UnitType.Warship, icon: warshipIcon },
{ unitType: UnitType.Port, icon: portIcon },
{ unitType: UnitType.MissileSilo, icon: missileSiloIcon },
// { unitType: UnitType.DefensePost, icon: shieldIcon },
@@ -201,7 +198,7 @@ export class BuildMenu extends LitElement {
public onBuildSelected = (item: BuildItemDisplay) => {
this.eventBus.emit(
new BuildUnitIntentEvent(item.unitType, this.clickedCell),
new BuildUnitIntentEvent(item.unitType, this.clickedCell)
);
this.hideMenu();
};
@@ -233,7 +230,7 @@ export class BuildMenu extends LitElement {
? this.game
.unitInfo(item.unitType)
.cost(this.myPlayer)
: 0,
: 0
)}
<img
src=${goldCoinIcon}
@@ -244,10 +241,10 @@ export class BuildMenu extends LitElement {
/>
</span>
</button>
`,
`
)}
</div>
`,
`
)}
</div>
`;
+9 -17
View File
@@ -33,7 +33,7 @@ export abstract class DefaultServerConfig implements ServerConfig {
export class DefaultConfig implements Config {
constructor(
private _serverConfig: ServerConfig,
private _gameConfig: GameConfig,
private _gameConfig: GameConfig
) {}
gameConfig(): GameConfig {
@@ -88,20 +88,12 @@ export class DefaultConfig implements Config {
cost: () => 0,
territoryBound: false,
};
case UnitType.Destroyer:
case UnitType.Warship:
return {
cost: (p: Player) =>
(p.units(UnitType.Destroyer).length + 1) * 250_000,
cost: (p: Player) => (p.units(UnitType.Warship).length + 1) * 250_000,
territoryBound: false,
maxHealth: 1000,
};
case UnitType.Battleship:
return {
cost: (p: Player) =>
(p.units(UnitType.Battleship).length + 1) * 500_000,
territoryBound: false,
maxHealth: 5000,
};
case UnitType.Shell:
return {
cost: () => 0,
@@ -113,7 +105,7 @@ export class DefaultConfig implements Config {
cost: (p: Player) =>
Math.min(
1_000_000,
Math.pow(2, p.units(UnitType.Port).length) * 250_000,
Math.pow(2, p.units(UnitType.Port).length) * 250_000
),
territoryBound: true,
};
@@ -142,7 +134,7 @@ export class DefaultConfig implements Config {
cost: (p: Player) =>
Math.min(
250_000,
(p.units(UnitType.DefensePost).length + 1) * 50_000,
(p.units(UnitType.DefensePost).length + 1) * 50_000
),
territoryBound: true,
};
@@ -151,7 +143,7 @@ export class DefaultConfig implements Config {
cost: (p: Player) =>
Math.min(
1_000_000,
Math.pow(2, p.units(UnitType.City).length) * 125_000,
Math.pow(2, p.units(UnitType.City).length) * 125_000
),
territoryBound: true,
};
@@ -207,7 +199,7 @@ export class DefaultConfig implements Config {
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
tileToConquer: TileRef,
tileToConquer: TileRef
): {
attackerTroopLoss: number;
defenderTroopLoss: number;
@@ -271,7 +263,7 @@ export class DefaultConfig implements Config {
tilesPerTickUsed: within(
(2000 * Math.max(10, speed)) / attackTroops,
5,
100,
100
),
};
}
@@ -281,7 +273,7 @@ export class DefaultConfig implements Config {
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
numAdjacentTilesWithEnemy: number,
numAdjacentTilesWithEnemy: number
): number {
if (defender.isPlayer()) {
return (
+4
View File
@@ -29,6 +29,10 @@ export class DevConfig extends DefaultConfig {
return info;
}
tradeShipSpawnRate(): number {
return 10;
}
// percentageTilesOwnedToWin(): number {
// return 1
// }
-173
View File
@@ -1,173 +0,0 @@
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";
import { distSort, distSortUnit } from "../Util";
import { ShellExecution } from "./ShellExecution";
import { consolex } from "../Consolex";
import { TileRef } from "../game/GameMap";
export class BattleshipExecution implements Execution {
private random: PseudoRandom;
private _owner: Player;
private active = true;
private battleship: Unit = null;
private mg: Game = null;
private pathfinder: PathFinder;
private patrolTile: TileRef;
// TODO: put in config
private searchRange = 100;
private attackRate = 5;
private lastAttack = 0;
private alreadyTargeted = new Set<Unit>();
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());
}
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,
),
);
}
}
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;
}
}
}
+20 -29
View File
@@ -31,10 +31,9 @@ import { EmojiExecution } from "./EmojiExecution";
import { DonateExecution } from "./DonateExecution";
import { NukeExecution } from "./NukeExecution";
import { SetTargetTroopRatioExecution } from "./SetTargetTroopRatioExecution";
import { DestroyerExecution } from "./DestroyerExecution";
import { WarshipExecution } from "./WarshipExecution";
import { PortExecution } from "./PortExecution";
import { MissileSiloExecution } from "./MissileSiloExecution";
import { BattleshipExecution } from "./BattleshipExecution";
import { DefensePostExecution } from "./DefensePostExecution";
import { CityExecution } from "./CityExecution";
import { TileRef } from "../game/GameMap";
@@ -43,10 +42,7 @@ export class Executor {
// private random = new PseudoRandom(999)
private random: PseudoRandom = null;
constructor(
private mg: Game,
private gameID: GameID,
) {
constructor(private mg: Game, private gameID: GameID) {
// Add one to avoid id collisions with bots.
this.random = new PseudoRandom(simpleHash(gameID) + 1);
}
@@ -62,7 +58,7 @@ export class Executor {
intent.troops,
intent.attackerID,
intent.targetID,
null,
null
);
}
case "spawn":
@@ -71,16 +67,16 @@ export class Executor {
sanitize(intent.name),
intent.playerType,
intent.clientID,
intent.playerID,
intent.playerID
),
this.mg.ref(intent.x, intent.y),
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,
intent.troops
);
case "allianceRequest":
return new AllianceRequestExecution(intent.requestor, intent.recipient);
@@ -88,7 +84,7 @@ export class Executor {
return new AllianceRequestReplyExecution(
intent.requestor,
intent.recipient,
intent.accept,
intent.accept
);
case "breakAlliance":
return new BreakAllianceExecution(intent.requestor, intent.recipient);
@@ -98,13 +94,13 @@ export class Executor {
return new EmojiExecution(
intent.sender,
intent.recipient,
intent.emoji,
intent.emoji
);
case "donate":
return new DonateExecution(
intent.sender,
intent.recipient,
intent.troops,
intent.troops
);
case "troop_ratio":
return new SetTargetTroopRatioExecution(intent.player, intent.ratio);
@@ -115,37 +111,32 @@ export class Executor {
return new NukeExecution(
intent.unit,
intent.player,
this.mg.ref(intent.x, intent.y),
this.mg.ref(intent.x, intent.y)
);
case UnitType.Destroyer:
return new DestroyerExecution(
case UnitType.Warship:
return new WarshipExecution(
intent.player,
this.mg.ref(intent.x, intent.y),
);
case UnitType.Battleship:
return new BattleshipExecution(
intent.player,
this.mg.ref(intent.x, intent.y),
this.mg.ref(intent.x, intent.y)
);
case UnitType.Port:
return new PortExecution(
intent.player,
this.mg.ref(intent.x, intent.y),
this.mg.ref(intent.x, intent.y)
);
case UnitType.MissileSilo:
return new MissileSiloExecution(
intent.player,
this.mg.ref(intent.x, intent.y),
this.mg.ref(intent.x, intent.y)
);
case UnitType.DefensePost:
return new DefensePostExecution(
intent.player,
this.mg.ref(intent.x, intent.y),
this.mg.ref(intent.x, intent.y)
);
case UnitType.City:
return new CityExecution(
intent.player,
this.mg.ref(intent.x, intent.y),
this.mg.ref(intent.x, intent.y)
);
default:
throw Error(`unit type ${intent.unit} not supported`);
@@ -171,14 +162,14 @@ export class Executor {
nation.name,
PlayerType.FakeHuman,
null,
this.random.nextID(),
this.random.nextID()
),
nation.cell,
nation.strength *
this.mg
.config()
.difficultyModifier(this.mg.config().gameConfig().difficulty),
),
.difficultyModifier(this.mg.config().gameConfig().difficulty)
)
);
}
return execs;
+7 -24
View File
@@ -18,8 +18,7 @@ import { AttackExecution } from "./AttackExecution";
import { TransportShipExecution } from "./TransportShipExecution";
import { SpawnExecution } from "./SpawnExecution";
import { PortExecution } from "./PortExecution";
import { DestroyerExecution } from "./DestroyerExecution";
import { BattleshipExecution } from "./BattleshipExecution";
import { WarshipExecution } from "./WarshipExecution";
import { GameID } from "../Schemas";
import { consolex } from "../Consolex";
import { CityExecution } from "./CityExecution";
@@ -321,10 +320,7 @@ export class FakeHumanExecution implements Execution {
2,
(t) => new CityExecution(this.player.id(), t)
);
if (this.maybeSpawnWarship(UnitType.Destroyer)) {
return;
}
if (this.maybeSpawnWarship(UnitType.Battleship)) {
if (this.maybeSpawnWarship()) {
return;
}
this.maybeSpawnStructure(
@@ -359,41 +355,28 @@ export class FakeHumanExecution implements Execution {
this.mg.addExecution(build(tile));
}
private maybeSpawnWarship(
shipType: UnitType.Destroyer | UnitType.Battleship
): boolean {
private maybeSpawnWarship(): boolean {
if (!this.random.chance(50)) {
return false;
}
const ports = this.player.units(UnitType.Port);
const ships = this.player.units(shipType);
const ships = this.player.units(UnitType.Warship);
if (
ports.length > 0 &&
ships.length == 0 &&
this.player.gold() > this.cost(shipType)
this.player.gold() > this.cost(UnitType.Warship)
) {
const port = this.random.randElement(ports);
const targetTile = this.warshipSpawnTile(port.tile());
if (targetTile == null) {
return false;
}
const canBuild = this.player.canBuild(UnitType.Destroyer, targetTile);
const canBuild = this.player.canBuild(UnitType.Warship, targetTile);
if (canBuild == false) {
consolex.warn("cannot spawn destroyer");
return false;
}
switch (shipType) {
case UnitType.Destroyer:
this.mg.addExecution(
new DestroyerExecution(this.player.id(), targetTile)
);
break;
case UnitType.Battleship:
this.mg.addExecution(
new BattleshipExecution(this.player.id(), targetTile)
);
break;
}
this.mg.addExecution(new WarshipExecution(this.player.id(), targetTile));
return true;
}
return false;
+6 -9
View File
@@ -25,10 +25,7 @@ export class PortExecution implements Execution {
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;
@@ -49,7 +46,7 @@ export class PortExecution implements Execution {
.filter((t) => this.mg.isOceanShore(t) && this.mg.owner(t) == player)
.sort(
(a, b) =>
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile),
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile)
);
if (spawns.length == 0) {
@@ -71,7 +68,7 @@ export class PortExecution implements Execution {
const alliedPortsSet = new Set(alliedPorts);
const allyConnections = new Set(
Array.from(this.portPaths.keys()).map((p) => p.owner()),
Array.from(this.portPaths.keys()).map((p) => p.owner())
);
allyConnections;
@@ -103,7 +100,7 @@ export class PortExecution implements Execution {
port.tile(),
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
10_000,
25,
25
);
this.computingPaths.set(port, pf);
}
@@ -124,9 +121,9 @@ export class PortExecution implements Execution {
const port = this.random.randElement(portConnections);
const path = this.portPaths.get(port);
if (path != null) {
const pf = PathFinder.Mini(this.mg, 10, false);
const pf = PathFinder.Mini(this.mg, 10000, false);
this.mg.addExecution(
new TradeShipExecution(this.player().id(), this.port, port, pf, path),
new TradeShipExecution(this.player().id(), this.port, port, pf, path)
);
}
}
@@ -14,13 +14,14 @@ import { PseudoRandom } from "../PseudoRandom";
import { distSort, distSortUnit } from "../Util";
import { consolex } from "../Consolex";
import { TileRef } from "../game/GameMap";
import { ShellExecution } from "./ShellExecution";
export class DestroyerExecution implements Execution {
export class WarshipExecution implements Execution {
private random: PseudoRandom;
private _owner: Player;
private active = true;
private destroyer: Unit = null;
private warship: Unit = null;
private mg: Game = null;
private target: Unit = null;
@@ -31,10 +32,12 @@ export class DestroyerExecution implements Execution {
// TODO: put in config
private searchRange = 100;
constructor(
private playerID: PlayerID,
private patrolCenterTile: TileRef,
) {}
private shellAttackRate = 5;
private lastShellAttack = 0;
private alreadySentShell = new Set<Unit>();
constructor(private playerID: PlayerID, private patrolCenterTile: TileRef) {}
init(mg: Game, ticks: number): void {
this.pathfinder = PathFinder.Mini(mg, 5000, false);
@@ -45,16 +48,16 @@ export class DestroyerExecution implements Execution {
}
tick(ticks: number): void {
if (this.destroyer == null) {
const spawn = this._owner.canBuild(UnitType.Destroyer, this.patrolTile);
if (this.warship == null) {
const spawn = this._owner.canBuild(UnitType.Warship, this.patrolTile);
if (spawn == false) {
this.active = false;
return;
}
this.destroyer = this._owner.buildUnit(UnitType.Destroyer, 0, spawn);
this.warship = this._owner.buildUnit(UnitType.Warship, 0, spawn);
return;
}
if (!this.destroyer.isActive()) {
if (!this.warship.isActive()) {
this.active = false;
return;
}
@@ -63,34 +66,28 @@ export class DestroyerExecution implements Execution {
}
if (this.target == null) {
const ships = this.mg
.units(
UnitType.TransportShip,
UnitType.Destroyer,
UnitType.TradeShip,
UnitType.Battleship,
)
.units(UnitType.TransportShip, UnitType.Warship, UnitType.TradeShip)
.filter(
(u) => this.mg.manhattanDist(u.tile(), this.destroyer.tile()) < 100,
(u) => this.mg.manhattanDist(u.tile(), this.warship.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) {
.filter((u) => u.owner() != this.warship.owner())
.filter((u) => u != this.warship)
.filter((u) => !u.owner().isAlliedWith(this.warship.owner()))
.filter((u) => !this.alreadySentShell.has(u));
this.target = ships.sort(distSortUnit(this.mg, this.warship))[0] ?? null;
if (this.target == null || this.target.type() != UnitType.TradeShip) {
// Patrol unless we are hunting down a tradeship
const result = this.pathfinder.nextTile(
this.destroyer.tile(),
this.patrolTile,
this.warship.tile(),
this.patrolTile
);
switch (result.type) {
case PathFindResultType.Completed:
this.patrolTile = this.randomTile();
break;
case PathFindResultType.NextTile:
this.destroyer.move(result.tile);
this.warship.move(result.tile);
break;
case PathFindResultType.Pending:
return;
@@ -99,42 +96,53 @@ export class DestroyerExecution implements Execution {
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
if (
this.target == null ||
!this.target.isActive() ||
this.target.owner() == this._owner
) {
// In case another destroyer captured or destroyed target
this.target = null;
return;
}
if (this.target.type() != UnitType.TradeShip) {
if (this.mg.ticks() - this.lastShellAttack > this.shellAttackRate) {
this.lastShellAttack = this.mg.ticks();
this.mg.addExecution(
new ShellExecution(
this.warship.tile(),
this.warship.owner(),
this.warship,
this.target
)
);
if (!this.target.hasHealth()) {
// Don't send multiple shells to target that can be oneshotted
this.alreadySentShell.add(this.target);
this.target = null;
return;
}
}
// Only hunt down tradeships
return;
}
for (let i = 0; i < 2; i++) {
// target is trade ship so capture it.
const result = this.pathfinder.nextTile(
this.destroyer.tile(),
this.warship.tile(),
this.target.tile(),
5,
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.owner().captureUnit(this.target);
this.target = null;
return;
case PathFindResultType.NextTile:
this.destroyer.move(result.tile);
this.warship.move(result.tile);
break;
case PathFindResultType.Pending:
break;
+1 -2
View File
@@ -62,8 +62,7 @@ export interface UnitInfo {
export enum UnitType {
TransportShip = "Transport",
Destroyer = "Destroyer",
Battleship = "Battleship",
Warship = "Warship",
Shell = "Shell",
Port = "Port",
AtomBomb = "Atom Bomb",
+14 -12
View File
@@ -42,10 +42,7 @@ export class UnitView {
public _wasUpdated = true;
public lastPos: MapPos[] = [];
constructor(
private gameView: GameView,
private data: UnitUpdate,
) {
constructor(private gameView: GameView, private data: UnitUpdate) {
this.lastPos.push(data.pos);
}
@@ -101,14 +98,14 @@ export class PlayerView {
constructor(
private game: GameView,
public data: PlayerUpdate,
public nameData: NameViewData,
public nameData: NameViewData
) {}
async actions(tile: TileRef): Promise<PlayerActions> {
return this.game.worker.playerInteraction(
this.id(),
this.game.x(tile),
this.game.y(tile),
this.game.y(tile)
);
}
@@ -151,12 +148,12 @@ export class PlayerView {
}
allies(): PlayerView[] {
return this.data.allies.map(
(a) => this.game.playerBySmallID(a) as PlayerView,
(a) => this.game.playerBySmallID(a) as PlayerView
);
}
targets(): PlayerView[] {
return this.data.targets.map(
(id) => this.game.playerBySmallID(id) as PlayerView,
(id) => this.game.playerBySmallID(id) as PlayerView
);
}
gold(): Gold {
@@ -211,7 +208,7 @@ export class GameView implements GameMap {
public worker: WorkerClient,
private _config: Config,
private _map: GameMap,
private _myClientID: ClientID,
private _myClientID: ClientID
) {
this.lastUpdate = {
tick: 0,
@@ -242,7 +239,7 @@ export class GameView implements GameMap {
} else {
this._players.set(
pu.id,
new PlayerView(this, pu, gu.playerNameViewData[pu.id]),
new PlayerView(this, pu, gu.playerNameViewData[pu.id])
);
}
});
@@ -321,7 +318,12 @@ export class GameView implements GameMap {
return this._config;
}
units(...types: UnitType[]): UnitView[] {
return Array.from(this._units.values());
if (types.length == 0) {
return Array.from(this._units.values());
}
return Array.from(this._units.values()).filter((u) =>
types.includes(u.type())
);
}
unit(id: number): UnitView {
return this._units.get(id);
@@ -416,7 +418,7 @@ export class GameView implements GameMap {
}
bfs(
tile: TileRef,
filter: (gm: GameMap, tile: TileRef) => boolean,
filter: (gm: GameMap, tile: TileRef) => boolean
): Set<TileRef> {
return this._map.bfs(tile, filter);
}
+1 -2
View File
@@ -567,8 +567,7 @@ export class PlayerImpl implements Player {
return this.nukeSpawn(targetTile);
case UnitType.Port:
return this.portSpawn(targetTile);
case UnitType.Destroyer:
case UnitType.Battleship:
case UnitType.Warship:
return this.warshipSpawn(targetTile);
case UnitType.Shell:
return targetTile;
+4 -3
View File
@@ -18,7 +18,7 @@ export class UnitImpl implements Unit {
private _tile: TileRef,
private _troops: number,
private _id: number,
public _owner: PlayerImpl,
public _owner: PlayerImpl
) {
// default to half health (or 1 is no health specified)
this._health = (this.mg.unitInfo(_type).maxHealth ?? 2) / 2;
@@ -35,6 +35,7 @@ export class UnitImpl implements Unit {
isActive: this._active,
pos: { x: this.mg.x(this._tile), y: this.mg.y(this._tile) },
lastPos: { x: this.mg.x(this._lastTile), y: this.mg.y(this._lastTile) },
health: this.hasHealth() ? this._health : undefined,
};
}
@@ -85,7 +86,7 @@ export class UnitImpl implements Unit {
this.mg.displayMessage(
`Your ${this.type()} was captured by ${newOwner.displayName()}`,
MessageType.ERROR,
oldOwner.id(),
oldOwner.id()
);
}
@@ -104,7 +105,7 @@ export class UnitImpl implements Unit {
this.mg.displayMessage(
`Your ${this.type()} was destroyed`,
MessageType.ERROR,
this.owner().id(),
this.owner().id()
);
}
}