mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-24 12:55:04 +00:00
implement mirv
This commit is contained in:
@@ -81,7 +81,7 @@ export class UnitLayer implements Layer {
|
||||
this.canvas.width = this.game.width();
|
||||
this.canvas.height = this.game.height();
|
||||
for (const unit of this.game.units()) {
|
||||
// this.onUnitEvent(new UnitEvent(unit, unit.tile()))
|
||||
this.onUnitEvent(unit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,8 +112,12 @@ export class UnitLayer implements Layer {
|
||||
case UnitType.TradeShip:
|
||||
this.handleTradeShipEvent(unit);
|
||||
break;
|
||||
case UnitType.MIRVWarhead:
|
||||
this.handleMIRVWarhead(unit);
|
||||
break;
|
||||
case UnitType.AtomBomb:
|
||||
case UnitType.HydrogenBomb:
|
||||
case UnitType.MIRV:
|
||||
this.handleNuke(unit);
|
||||
break;
|
||||
}
|
||||
@@ -228,6 +232,23 @@ export class UnitLayer implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
private handleMIRVWarhead(unit: UnitView) {
|
||||
const rel = this.relationship(unit);
|
||||
|
||||
this.clearCell(this.game.x(unit.lastTile()), this.game.y(unit.lastTile()));
|
||||
|
||||
if (unit.isActive()) {
|
||||
// Paint area
|
||||
this.paintCell(
|
||||
this.game.x(unit.tile()),
|
||||
this.game.y(unit.tile()),
|
||||
rel,
|
||||
this.theme.borderColor(unit.owner().info()),
|
||||
255
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleTradeShipEvent(unit: UnitView) {
|
||||
const rel = this.relationship(unit);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ interface BuildItemDisplay {
|
||||
const buildTable: BuildItemDisplay[][] = [
|
||||
[
|
||||
{ unitType: UnitType.AtomBomb, icon: atomBombIcon },
|
||||
{ unitType: UnitType.HydrogenBomb, icon: hydrogenBombIcon },
|
||||
{ unitType: UnitType.MIRV, icon: hydrogenBombIcon },
|
||||
{ unitType: UnitType.Warship, icon: warshipIcon },
|
||||
{ unitType: UnitType.Port, icon: portIcon },
|
||||
{ unitType: UnitType.MissileSilo, icon: missileSiloIcon },
|
||||
|
||||
@@ -119,6 +119,16 @@ export class DefaultConfig implements Config {
|
||||
cost: () => 5_000_000,
|
||||
territoryBound: false,
|
||||
};
|
||||
case UnitType.MIRV:
|
||||
return {
|
||||
cost: () => 5_000_000,
|
||||
territoryBound: false,
|
||||
};
|
||||
case UnitType.MIRVWarhead:
|
||||
return {
|
||||
cost: () => 0,
|
||||
territoryBound: false,
|
||||
};
|
||||
case UnitType.TradeShip:
|
||||
return {
|
||||
cost: () => 0,
|
||||
@@ -330,16 +340,11 @@ export class DefaultConfig implements Config {
|
||||
populationIncreaseRate(player: Player): number {
|
||||
let max = this.maxPopulation(player);
|
||||
|
||||
// const thing = Math.sqrt(player.population() + player.population() * player.workers())
|
||||
|
||||
let toAdd = 10 + Math.pow(player.population(), 0.73) / 4;
|
||||
|
||||
const ratio = 1 - player.population() / max;
|
||||
toAdd *= ratio;
|
||||
|
||||
if (player.type() == PlayerType.FakeHuman) {
|
||||
toAdd *= 1.0;
|
||||
}
|
||||
if (player.type() == PlayerType.Bot) {
|
||||
toAdd *= 0.7;
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ export class DevConfig extends DefaultConfig {
|
||||
// return 5000
|
||||
// }
|
||||
|
||||
numBots(): number {
|
||||
return 0;
|
||||
}
|
||||
spawnNPCs(): boolean {
|
||||
return false;
|
||||
}
|
||||
// numBots(): number {
|
||||
// return 0;
|
||||
// }
|
||||
// spawnNPCs(): boolean {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import { MissileSiloExecution } from "./MissileSiloExecution";
|
||||
import { DefensePostExecution } from "./DefensePostExecution";
|
||||
import { CityExecution } from "./CityExecution";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { MirvExecution } from "./MIRVExecution";
|
||||
|
||||
export class Executor {
|
||||
// private random = new PseudoRandom(999)
|
||||
@@ -113,6 +114,11 @@ export class Executor {
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case UnitType.MIRV:
|
||||
return new MirvExecution(
|
||||
intent.player,
|
||||
this.mg.ref(intent.x, intent.y)
|
||||
);
|
||||
case UnitType.Warship:
|
||||
return new WarshipExecution(
|
||||
intent.player,
|
||||
@@ -155,7 +161,6 @@ export class Executor {
|
||||
fakeHumanExecutions(): Execution[] {
|
||||
const execs = [];
|
||||
for (const nation of this.mg.nations()) {
|
||||
console.log(`got nation: ${nation.name}`);
|
||||
execs.push(
|
||||
new FakeHumanExecution(
|
||||
this.gameID,
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { nextTick } from "process";
|
||||
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";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { simpleHash } from "../Util";
|
||||
import { NukeExecution } from "./NukeExecution";
|
||||
|
||||
export class MirvExecution implements Execution {
|
||||
private player: Player;
|
||||
|
||||
private active = true;
|
||||
|
||||
private mg: Game;
|
||||
|
||||
private nuke: Unit;
|
||||
|
||||
private mirvRange = 350;
|
||||
private warheadCount = 1000;
|
||||
// private warheadRange = 5;
|
||||
|
||||
private random: PseudoRandom;
|
||||
|
||||
private pathFinder: PathFinder;
|
||||
|
||||
private targetPlayer: Player | TerraNullius;
|
||||
|
||||
constructor(private senderID: PlayerID, private dst: TileRef) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.random = new PseudoRandom(mg.ticks() + simpleHash(this.senderID));
|
||||
this.mg = mg;
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, true);
|
||||
this.player = mg.player(this.senderID);
|
||||
this.targetPlayer = this.mg.owner(this.dst);
|
||||
}
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.nuke == null) {
|
||||
const spawn = this.player.canBuild(UnitType.MIRV, this.dst);
|
||||
if (spawn == false) {
|
||||
consolex.warn(`cannot build MIRV`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.nuke = this.player.buildUnit(UnitType.MIRV, 0, spawn);
|
||||
}
|
||||
|
||||
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.separate();
|
||||
this.active = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private separate() {
|
||||
const dsts: TileRef[] = [this.dst];
|
||||
let attempts = 1000;
|
||||
while (attempts > 0 && dsts.length < this.warheadCount) {
|
||||
attempts--;
|
||||
const potential = this.randomLand(this.dst);
|
||||
if (potential == null) {
|
||||
continue;
|
||||
}
|
||||
dsts.push(potential);
|
||||
}
|
||||
console.log(`dsts: ${dsts.length}`);
|
||||
|
||||
for (const dst of dsts) {
|
||||
this.mg.addExecution(
|
||||
new NukeExecution(
|
||||
UnitType.MIRVWarhead,
|
||||
this.senderID,
|
||||
dst,
|
||||
this.nuke.tile(),
|
||||
this.random.nextInt(5, 9)
|
||||
)
|
||||
);
|
||||
}
|
||||
if (this.targetPlayer.isPlayer()) {
|
||||
const alliance = this.player.allianceWith(this.targetPlayer);
|
||||
if (alliance != null) {
|
||||
this.player.breakAlliance(alliance);
|
||||
}
|
||||
if (this.targetPlayer != this.player) {
|
||||
this.targetPlayer.updateRelation(this.player, -100);
|
||||
}
|
||||
}
|
||||
this.nuke.delete(false);
|
||||
}
|
||||
|
||||
randomLand(ref: TileRef): TileRef | null {
|
||||
let tries = 0;
|
||||
while (tries < 25) {
|
||||
tries++;
|
||||
const x = this.random.nextInt(
|
||||
this.mg.x(ref) - this.mirvRange,
|
||||
this.mg.x(ref) + this.mirvRange
|
||||
);
|
||||
const y = this.random.nextInt(
|
||||
this.mg.y(ref) - this.mirvRange,
|
||||
this.mg.y(ref) + this.mirvRange
|
||||
);
|
||||
if (!this.mg.isValidCoord(x, y)) {
|
||||
continue;
|
||||
}
|
||||
const tile = this.mg.ref(x, y);
|
||||
if (!this.mg.isLand(tile)) {
|
||||
continue;
|
||||
}
|
||||
if (this.mg.euclideanDist(tile, ref) > this.mirvRange) {
|
||||
continue;
|
||||
}
|
||||
if (this.mg.owner(tile) != this.targetPlayer) {
|
||||
continue;
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
activeDuringSpawnPhase(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { nextTick } from "process";
|
||||
import {
|
||||
Cell,
|
||||
Execution,
|
||||
@@ -9,32 +8,33 @@ import {
|
||||
UnitType,
|
||||
TerraNullius,
|
||||
} from "../game/Game";
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
|
||||
export class NukeExecution implements Execution {
|
||||
private player: Player;
|
||||
|
||||
private active = true;
|
||||
|
||||
private mg: Game;
|
||||
|
||||
private nuke: Unit;
|
||||
|
||||
private pathFinder: PathFinder;
|
||||
private random: PseudoRandom;
|
||||
|
||||
constructor(
|
||||
private type: UnitType.AtomBomb | UnitType.HydrogenBomb,
|
||||
private type:
|
||||
| UnitType.AtomBomb
|
||||
| UnitType.HydrogenBomb
|
||||
| UnitType.MIRVWarhead,
|
||||
private senderID: PlayerID,
|
||||
private dst: TileRef,
|
||||
private src?: TileRef,
|
||||
private speed: number = 4
|
||||
) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.mg = mg;
|
||||
this.pathFinder = PathFinder.Mini(mg, 10_000, true);
|
||||
this.player = mg.player(this.senderID);
|
||||
this.random = new PseudoRandom(ticks);
|
||||
}
|
||||
|
||||
public target(): Player | TerraNullius {
|
||||
@@ -43,7 +43,7 @@ export class NukeExecution implements Execution {
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.nuke == null) {
|
||||
const spawn = this.player.canBuild(this.type, this.dst);
|
||||
const spawn = this.src ?? this.player.canBuild(this.type, this.dst);
|
||||
if (spawn == false) {
|
||||
consolex.warn(`cannot build Nuke`);
|
||||
this.active = false;
|
||||
@@ -52,33 +52,60 @@ export class NukeExecution implements Execution {
|
||||
this.nuke = this.player.buildUnit(this.type, 0, spawn);
|
||||
}
|
||||
|
||||
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;
|
||||
for (let i = 0; i < this.speed; i++) {
|
||||
const x = this.mg.x(this.nuke.tile());
|
||||
const y = this.mg.y(this.nuke.tile());
|
||||
const dstX = this.mg.x(this.dst);
|
||||
const dstY = this.mg.y(this.dst);
|
||||
|
||||
// If we've reached the destination, detonate
|
||||
if (x === dstX && y === dstY) {
|
||||
this.detonate();
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate next position
|
||||
let nextX = x;
|
||||
let nextY = y;
|
||||
|
||||
const ratio = Math.floor(
|
||||
1 + Math.abs(dstY - y) / (Math.abs(dstX - x) + 1)
|
||||
);
|
||||
|
||||
if (this.random.chance(ratio) && x != dstX) {
|
||||
if (x < dstX) nextX++;
|
||||
else if (x > dstX) nextX--;
|
||||
} else {
|
||||
if (y < dstY) nextY++;
|
||||
else if (y > dstY) nextY--;
|
||||
}
|
||||
|
||||
// Move to next tile
|
||||
const nextTile = this.mg.ref(nextX, nextY);
|
||||
if (nextTile !== undefined) {
|
||||
this.nuke.move(nextTile);
|
||||
} else {
|
||||
consolex.warn(`invalid tile position ${nextX},${nextY}`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private detonate() {
|
||||
const magnitude =
|
||||
this.type == UnitType.AtomBomb
|
||||
? { inner: 15, outer: 40 }
|
||||
: { inner: 140, outer: 160 };
|
||||
let magnitude;
|
||||
switch (this.type) {
|
||||
case UnitType.MIRVWarhead:
|
||||
magnitude = { inner: 10, outer: 14 };
|
||||
break;
|
||||
case UnitType.AtomBomb:
|
||||
magnitude = { inner: 15, outer: 40 };
|
||||
break;
|
||||
case UnitType.HydrogenBomb:
|
||||
magnitude = { inner: 140, outer: 160 };
|
||||
break;
|
||||
}
|
||||
|
||||
const rand = new PseudoRandom(this.mg.ticks());
|
||||
const toDestroy = this.mg.bfs(this.dst, (_, n: TileRef) => {
|
||||
const d = this.mg.euclideanDist(this.dst, n);
|
||||
@@ -88,7 +115,7 @@ export class NukeExecution implements Execution {
|
||||
const ratio = Object.fromEntries(
|
||||
this.mg
|
||||
.players()
|
||||
.map((p) => [p.id(), (p.troops() + p.workers()) / p.numTilesOwned()]),
|
||||
.map((p) => [p.id(), (p.troops() + p.workers()) / p.numTilesOwned()])
|
||||
);
|
||||
const attacked = new Map<Player, number>();
|
||||
for (const tile of toDestroy) {
|
||||
@@ -108,7 +135,8 @@ export class NukeExecution implements Execution {
|
||||
}
|
||||
}
|
||||
for (const [other, tilesDestroyed] of attacked) {
|
||||
if (tilesDestroyed > 100) {
|
||||
if (tilesDestroyed > 100 && this.nuke.type() != UnitType.MIRVWarhead) {
|
||||
// Mirv warheads shouldn't break alliances
|
||||
const alliance = this.player.allianceWith(other);
|
||||
if (alliance != null) {
|
||||
this.player.breakAlliance(alliance);
|
||||
@@ -122,7 +150,9 @@ export class NukeExecution implements Execution {
|
||||
for (const unit of this.mg.units()) {
|
||||
if (
|
||||
unit.type() != UnitType.AtomBomb &&
|
||||
unit.type() != UnitType.HydrogenBomb
|
||||
unit.type() != UnitType.HydrogenBomb &&
|
||||
unit.type() != UnitType.MIRVWarhead &&
|
||||
unit.type() != UnitType.MIRV
|
||||
) {
|
||||
if (this.mg.euclideanDist(this.dst, unit.tile()) < magnitude.outer) {
|
||||
unit.delete();
|
||||
|
||||
@@ -59,7 +59,9 @@ export class PlayerExecution implements Execution {
|
||||
this.player.units().forEach((u) => {
|
||||
if (
|
||||
u.type() != UnitType.AtomBomb &&
|
||||
u.type() != UnitType.HydrogenBomb
|
||||
u.type() != UnitType.HydrogenBomb &&
|
||||
u.type() != UnitType.MIRVWarhead &&
|
||||
u.type() != UnitType.MIRV
|
||||
) {
|
||||
u.delete();
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ export enum UnitType {
|
||||
MissileSilo = "Missile Silo",
|
||||
DefensePost = "Defense Post",
|
||||
City = "City",
|
||||
MIRV = "MIRV",
|
||||
MIRVWarhead = "MIRV Warhead",
|
||||
}
|
||||
|
||||
export enum Relation {
|
||||
|
||||
@@ -562,9 +562,12 @@ export class PlayerImpl implements Player {
|
||||
return false;
|
||||
}
|
||||
switch (unitType) {
|
||||
case UnitType.MIRV:
|
||||
case UnitType.AtomBomb:
|
||||
case UnitType.HydrogenBomb:
|
||||
return this.nukeSpawn(targetTile);
|
||||
case UnitType.MIRVWarhead:
|
||||
return targetTile;
|
||||
case UnitType.Port:
|
||||
return this.portSpawn(targetTile);
|
||||
case UnitType.Warship:
|
||||
|
||||
Reference in New Issue
Block a user