mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-18 17:53:29 +00:00
thread_split: convert all tile to tileref
This commit is contained in:
@@ -4,8 +4,7 @@ import { EventBus } from "../core/EventBus";
|
|||||||
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
|
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
|
||||||
import { InputHandler, MouseUpEvent, ZoomEvent, DragEvent, MouseDownEvent } from "./InputHandler"
|
import { InputHandler, MouseUpEvent, ZoomEvent, DragEvent, MouseDownEvent } from "./InputHandler"
|
||||||
import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientMessageSchema, GameConfig, GameID, Intent, ServerMessage, ServerMessageSchema, ServerSyncMessage, Turn } from "../core/Schemas";
|
import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientMessageSchema, GameConfig, GameID, Intent, ServerMessage, ServerMessageSchema, ServerSyncMessage, Turn } from "../core/Schemas";
|
||||||
import { loadTerrainFromFile, loadTerrainMap, TerrainMapImpl } from "../core/game/TerrainMapLoader";
|
import { loadTerrainFromFile, loadTerrainMap } from "../core/game/TerrainMapLoader";
|
||||||
import { and, bfs, dist, generateID, manhattanDist } from "../core/Util";
|
|
||||||
import { SendAttackIntentEvent, SendSpawnIntentEvent, Transport } from "./Transport";
|
import { SendAttackIntentEvent, SendSpawnIntentEvent, Transport } from "./Transport";
|
||||||
import { createCanvas } from "./Utils";
|
import { createCanvas } from "./Utils";
|
||||||
import { MessageType } from '../core/game/Game';
|
import { MessageType } from '../core/game/Game';
|
||||||
@@ -72,10 +71,10 @@ export function joinLobby(lobbyConfig: LobbyConfig, onjoin: () => void): () => v
|
|||||||
export async function createClientGame(lobbyConfig: LobbyConfig, gameConfig: GameConfig, eventBus: EventBus, transport: Transport): Promise<ClientGameRunner> {
|
export async function createClientGame(lobbyConfig: LobbyConfig, gameConfig: GameConfig, eventBus: EventBus, transport: Transport): Promise<ClientGameRunner> {
|
||||||
const config = getConfig(gameConfig)
|
const config = getConfig(gameConfig)
|
||||||
|
|
||||||
const terrainMap = await loadTerrainMap(gameConfig.gameMap);
|
const gameMap = await loadTerrainMap(gameConfig.gameMap);
|
||||||
const worker = new WorkerClient(lobbyConfig.gameID, gameConfig)
|
const worker = new WorkerClient(lobbyConfig.gameID, gameConfig)
|
||||||
await worker.initialize()
|
await worker.initialize()
|
||||||
const gameView = new GameView(worker, config, terrainMap.terrain)
|
const gameView = new GameView(worker, config, gameMap.gameMap)
|
||||||
|
|
||||||
|
|
||||||
consolex.log('going to init path finder')
|
consolex.log('going to init path finder')
|
||||||
@@ -177,12 +176,12 @@ export class ClientGameRunner {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const cell = this.renderer.transformHandler.screenToWorldCoordinates(event.x, event.y)
|
const cell = this.renderer.transformHandler.screenToWorldCoordinates(event.x, event.y)
|
||||||
if (!this.gameView.isOnMap(cell)) {
|
if (!this.gameView.isValidCoord(cell.x, cell.y)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
consolex.log(`clicked cell ${cell}`)
|
consolex.log(`clicked cell ${cell}`)
|
||||||
const tile = this.gameView.tile(cell)
|
const tile = this.gameView.ref(cell.x, cell.y)
|
||||||
if (tile.terrain().isLand() && !tile.hasOwner() && this.gameView.inSpawnPhase()) {
|
if (this.gameView.isLand(tile) && !this.gameView.hasOwner(tile) && this.gameView.inSpawnPhase()) {
|
||||||
this.eventBus.emit(new SendSpawnIntentEvent(cell))
|
this.eventBus.emit(new SendSpawnIntentEvent(cell))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -200,7 +199,7 @@ export class ClientGameRunner {
|
|||||||
if (actions.canAttack) {
|
if (actions.canAttack) {
|
||||||
this.eventBus.emit(
|
this.eventBus.emit(
|
||||||
new SendAttackIntentEvent(
|
new SendAttackIntentEvent(
|
||||||
tile.owner().id(),
|
this.gameView.owner(tile).id(),
|
||||||
this.myPlayer.troops() * this.renderer.uiState.attackRatio
|
this.myPlayer.troops() * this.renderer.uiState.attackRatio
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Config, ServerConfig } from "../core/configuration/Config"
|
import { Config, ServerConfig } from "../core/configuration/Config"
|
||||||
import { SendLogEvent } from "../core/Consolex"
|
import { SendLogEvent } from "../core/Consolex"
|
||||||
import { EventBus, GameEvent } from "../core/EventBus"
|
import { EventBus, GameEvent } from "../core/EventBus"
|
||||||
import { AllianceRequest, AllPlayers, Cell, GameType, Player, PlayerID, PlayerType, Tile, UnitType } from "../core/game/Game"
|
import { AllianceRequest, AllPlayers, Cell, GameType, Player, PlayerID, PlayerType, UnitType } from "../core/game/Game"
|
||||||
import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ClientPingMessageSchema, GameConfig, ClientLogMessageSchema } from "../core/Schemas"
|
import { ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ClientPingMessageSchema, GameConfig, ClientLogMessageSchema } from "../core/Schemas"
|
||||||
import { LobbyConfig } from "./ClientGameRunner"
|
import { LobbyConfig } from "./ClientGameRunner"
|
||||||
import { LocalServer } from "./LocalServer"
|
import { LocalServer } from "./LocalServer"
|
||||||
@@ -298,10 +298,6 @@ export class Transport {
|
|||||||
attackerID: this.lobbyConfig.playerID,
|
attackerID: this.lobbyConfig.playerID,
|
||||||
targetID: event.targetID,
|
targetID: event.targetID,
|
||||||
troops: event.troops,
|
troops: event.troops,
|
||||||
sourceX: null,
|
|
||||||
sourceY: null,
|
|
||||||
targetX: null,
|
|
||||||
targetY: null,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Game, Player, Tile, Cell, NameViewData } from '../../core/game/Game';
|
import { Game, Player, Cell, NameViewData } from '../../core/game/Game';
|
||||||
import { GameView } from '../../core/GameView';
|
|
||||||
import { calculateBoundingBox, within } from '../../core/Util';
|
import { calculateBoundingBox, within } from '../../core/Util';
|
||||||
|
|
||||||
export interface Point {
|
export interface Point {
|
||||||
@@ -17,12 +16,7 @@ export interface Rectangle {
|
|||||||
|
|
||||||
|
|
||||||
export function placeName(game: Game, player: Player): NameViewData {
|
export function placeName(game: Game, player: Player): NameViewData {
|
||||||
return {
|
const boundingBox = calculateBoundingBox(game, player.borderTiles());
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
size: 0
|
|
||||||
}
|
|
||||||
const boundingBox = calculateBoundingBox(player.borderTiles());
|
|
||||||
|
|
||||||
|
|
||||||
const rawScalingFactor = (boundingBox.max.x - boundingBox.min.x) / 100
|
const rawScalingFactor = (boundingBox.max.x - boundingBox.min.x) / 100
|
||||||
@@ -72,8 +66,8 @@ export function createGrid(game: Game, player: Player, boundingBox: { min: Point
|
|||||||
for (let y = scaledBoundingBox.min.y; y <= scaledBoundingBox.max.y; y++) {
|
for (let y = scaledBoundingBox.min.y; y <= scaledBoundingBox.max.y; y++) {
|
||||||
const cell = new Cell(x * scalingFactor, y * scalingFactor);
|
const cell = new Cell(x * scalingFactor, y * scalingFactor);
|
||||||
if (game.isOnMap(cell)) {
|
if (game.isOnMap(cell)) {
|
||||||
const tile = game.tile(cell);
|
const tile = game.ref(cell.x, cell.y);
|
||||||
grid[x - scaledBoundingBox.min.x][y - scaledBoundingBox.min.y] = tile.terrain().isLake() || tile.owner() === player; // TODO: okay if lake
|
grid[x - scaledBoundingBox.min.x][y - scaledBoundingBox.min.y] = this.game.isLake(tile) || this.game.owner(tile) === player; // TODO: okay if lake
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { colord } from "colord";
|
import { colord } from "colord";
|
||||||
import { EventBus } from "../../core/EventBus"
|
import { EventBus } from "../../core/EventBus"
|
||||||
import { Cell, Game, Player } from "../../core/game/Game";
|
import { Cell, Game, Player } from "../../core/game/Game";
|
||||||
import { calculateBoundingBox, calculateBoundingBoxCenter, manhattanDist } from "../../core/Util";
|
import { calculateBoundingBox, calculateBoundingBoxCenter } from "../../core/Util";
|
||||||
import { ZoomEvent, DragEvent } from "../InputHandler";
|
import { ZoomEvent, DragEvent } from "../InputHandler";
|
||||||
import { GoToPlayerEvent } from "./layers/Leaderboard";
|
import { GoToPlayerEvent } from "./layers/Leaderboard";
|
||||||
import { placeName } from "./NameBoxCalculator";
|
import { placeName } from "./NameBoxCalculator";
|
||||||
@@ -131,7 +131,7 @@ export class TransformHandler {
|
|||||||
const { screenX, screenY } = this.screenCenter()
|
const { screenX, screenY } = this.screenCenter()
|
||||||
const screenMapCenter = new Cell(screenX, screenY)
|
const screenMapCenter = new Cell(screenX, screenY)
|
||||||
|
|
||||||
if (manhattanDist(screenMapCenter, this.target) < 2) {
|
if (this.game.manhattanDist(this.game.ref(screenX, screenY), this.game.ref(this.target.x, this.target.y)) < 2) {
|
||||||
this.clearTarget()
|
this.clearTarget()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { LitElement, html, css } from 'lit';
|
import { LitElement, html, css } from 'lit';
|
||||||
import { customElement, property, state } from 'lit/decorators.js';
|
import { customElement, property, state } from 'lit/decorators.js';
|
||||||
import { Layer } from './Layer';
|
import { Layer } from './Layer';
|
||||||
import { Game, Player } from '../../../core/game/Game';
|
|
||||||
import { ClientID } from '../../../core/Schemas';
|
import { ClientID } from '../../../core/Schemas';
|
||||||
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||||
import { EventBus, GameEvent } from '../../../core/EventBus';
|
import { EventBus, GameEvent } from '../../../core/EventBus';
|
||||||
@@ -59,7 +58,7 @@ export class Leaderboard extends LitElement implements Layer {
|
|||||||
.map((player, index) => ({
|
.map((player, index) => ({
|
||||||
name: player.displayName(),
|
name: player.displayName(),
|
||||||
position: index + 1,
|
position: index + 1,
|
||||||
score: formatPercentage(player.numTilesOwned() / this.game.terrainMap().numLandTiles()),
|
score: formatPercentage(player.numTilesOwned() / this.game.numLandTiles()),
|
||||||
gold: renderNumber(player.gold()),
|
gold: renderNumber(player.gold()),
|
||||||
isMyPlayer: player == myPlayer,
|
isMyPlayer: player == myPlayer,
|
||||||
player: player
|
player: player
|
||||||
@@ -78,7 +77,7 @@ export class Leaderboard extends LitElement implements Layer {
|
|||||||
this.players.push({
|
this.players.push({
|
||||||
name: myPlayer.displayName(),
|
name: myPlayer.displayName(),
|
||||||
position: place,
|
position: place,
|
||||||
score: formatPercentage(myPlayer.numTilesOwned() / this.game.terrainMap().numLandTiles()),
|
score: formatPercentage(myPlayer.numTilesOwned() / this.game.numLandTiles()),
|
||||||
gold: renderNumber(myPlayer.gold()),
|
gold: renderNumber(myPlayer.gold()),
|
||||||
isMyPlayer: true,
|
isMyPlayer: true,
|
||||||
player: myPlayer
|
player: myPlayer
|
||||||
|
|||||||
@@ -6,10 +6,25 @@ import { ClientID } from '../../../core/Schemas';
|
|||||||
import { EventBus } from '../../../core/EventBus';
|
import { EventBus } from '../../../core/EventBus';
|
||||||
import { TransformHandler } from '../TransformHandler';
|
import { TransformHandler } from '../TransformHandler';
|
||||||
import { MouseMoveEvent } from '../../InputHandler';
|
import { MouseMoveEvent } from '../../InputHandler';
|
||||||
import { euclideanDist, distSortUnit } from '../../../core/Util';
|
|
||||||
import { renderNumber, renderTroops } from '../../Utils';
|
|
||||||
import { PauseGameEvent } from '../../Transport';
|
|
||||||
import { GameView, PlayerView } from '../../../core/GameView';
|
import { GameView, PlayerView } from '../../../core/GameView';
|
||||||
|
import { TileRef } from '../../../core/game/GameMap';
|
||||||
|
import { PauseGameEvent } from '../../Transport';
|
||||||
|
|
||||||
|
function euclideanDistWorld(coord: { x: number, y: number }, tileRef: TileRef, game: GameView): number {
|
||||||
|
const x = game.x(tileRef);
|
||||||
|
const y = game.y(tileRef);
|
||||||
|
const dx = coord.x - x;
|
||||||
|
const dy = coord.y - y;
|
||||||
|
return Math.sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function distSortUnitWorld(coord: { x: number, y: number }, game: GameView) {
|
||||||
|
return (a: Unit, b: Unit) => {
|
||||||
|
const distA = euclideanDistWorld(coord, a.tile(), game);
|
||||||
|
const distB = euclideanDistWorld(coord, b.tile(), game);
|
||||||
|
return distA - distB;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@customElement('player-info-overlay')
|
@customElement('player-info-overlay')
|
||||||
export class PlayerInfoOverlay extends LitElement implements Layer {
|
export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||||
@@ -29,13 +44,13 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
|||||||
private player: Player | null = null;
|
private player: Player | null = null;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private playerProfile: PlayerProfile | null = null
|
private playerProfile: PlayerProfile | null = null;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private unit: Unit | null = null;
|
private unit: Unit | null = null;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private showPauseButton: boolean = true
|
private showPauseButton: boolean = true;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private _isInfoVisible: boolean = false;
|
private _isInfoVisible: boolean = false;
|
||||||
@@ -43,39 +58,40 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
|||||||
@state()
|
@state()
|
||||||
private _isPaused: boolean = false;
|
private _isPaused: boolean = false;
|
||||||
|
|
||||||
private _isActive = false
|
private _isActive = false;
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.eventBus.on(MouseMoveEvent, (e: MouseMoveEvent) => this.onMouseEvent(e));
|
this.eventBus.on(MouseMoveEvent, (e: MouseMoveEvent) => this.onMouseEvent(e));
|
||||||
this._isActive = true
|
this._isActive = true;
|
||||||
this.showPauseButton = this.game.config().gameConfig().gameType == GameType.Singleplayer
|
this.showPauseButton = this.game.config().gameConfig().gameType == GameType.Singleplayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMouseEvent(event: MouseMoveEvent) {
|
private onMouseEvent(event: MouseMoveEvent) {
|
||||||
this.setVisible(false);
|
this.setVisible(false);
|
||||||
this.unit = null;
|
this.unit = null;
|
||||||
const lastPlayer = this.player
|
|
||||||
this.player = null;
|
this.player = null;
|
||||||
|
|
||||||
const worldCoord = this.transform.screenToWorldCoordinates(event.x, event.y);
|
const worldCoord = this.transform.screenToWorldCoordinates(event.x, event.y);
|
||||||
if (!this.game.isOnMap(worldCoord)) {
|
if (!this.game.isValidCoord(worldCoord.x, worldCoord.y)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tile = this.game.tile(worldCoord);
|
const tile = this.game.ref(worldCoord.x, worldCoord.y);
|
||||||
const owner = tile.owner();
|
if (!tile) return;
|
||||||
|
|
||||||
if (owner.isPlayer()) {
|
const owner = this.game.owner(tile);
|
||||||
|
|
||||||
|
if (owner && owner.isPlayer()) {
|
||||||
this.player = owner;
|
this.player = owner;
|
||||||
(this.player as PlayerView).profile().then(p => {
|
(this.player as PlayerView).profile().then(p => {
|
||||||
console.log(`got profile ${JSON.stringify(p)}`)
|
console.log(`got profile ${JSON.stringify(p)}`);
|
||||||
this.playerProfile = p
|
this.playerProfile = p;
|
||||||
})
|
});
|
||||||
this.setVisible(true);
|
this.setVisible(true);
|
||||||
} else if (!tile.terrain().isLand()) {
|
} else if (!this.game.isLand(tile)) {
|
||||||
const units = this.game.units(UnitType.Destroyer, UnitType.Battleship, UnitType.TradeShip)
|
const units = this.game.units(UnitType.Destroyer, UnitType.Battleship, UnitType.TradeShip)
|
||||||
.filter(u => euclideanDist(worldCoord, u.tile().cell()) < 50)
|
.filter(u => euclideanDistWorld(worldCoord, u.tile(), this.game) < 50)
|
||||||
.sort(distSortUnit(tile));
|
.sort(distSortUnitWorld(worldCoord, this.game));
|
||||||
|
|
||||||
if (units.length > 0) {
|
if (units.length > 0) {
|
||||||
this.unit = units[0];
|
this.unit = units[0];
|
||||||
@@ -120,28 +136,28 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
|||||||
private renderPlayerInfo(player: Player) {
|
private renderPlayerInfo(player: Player) {
|
||||||
const myPlayer = this.myPlayer();
|
const myPlayer = this.myPlayer();
|
||||||
const isAlly = (myPlayer?.isAlliedWith(player) || player == this.myPlayer()) ?? false;
|
const isAlly = (myPlayer?.isAlliedWith(player) || player == this.myPlayer()) ?? false;
|
||||||
let relationHtml = null
|
let relationHtml = null;
|
||||||
if (player.type() == PlayerType.FakeHuman && myPlayer != null) {
|
if (player.type() == PlayerType.FakeHuman && myPlayer != null) {
|
||||||
let classType = ''
|
let classType = '';
|
||||||
let relationName = ''
|
let relationName = '';
|
||||||
const relation = this.playerProfile?.relations[myPlayer.smallID()] ?? Relation.Neutral
|
const relation = this.playerProfile?.relations[myPlayer.smallID()] ?? Relation.Neutral;
|
||||||
switch (relation) {
|
switch (relation) {
|
||||||
case Relation.Hostile:
|
case Relation.Hostile:
|
||||||
classType = 'hostile'
|
classType = 'hostile';
|
||||||
relationName = 'Hostile'
|
relationName = 'Hostile';
|
||||||
break
|
break;
|
||||||
case Relation.Distrustful:
|
case Relation.Distrustful:
|
||||||
classType = 'distrustful'
|
classType = 'distrustful';
|
||||||
relationName = 'Distrustful'
|
relationName = 'Distrustful';
|
||||||
break
|
break;
|
||||||
case Relation.Neutral:
|
case Relation.Neutral:
|
||||||
classType = 'neutral'
|
classType = 'neutral';
|
||||||
relationName = 'Neutral'
|
relationName = 'Neutral';
|
||||||
break
|
break;
|
||||||
case Relation.Friendly:
|
case Relation.Friendly:
|
||||||
classType = 'friendly'
|
classType = 'friendly';
|
||||||
relationName = 'Friendly'
|
relationName = 'Friendly';
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
relationHtml = html`<div class="type-label">Attitude: <span class="${classType}">${relationName}</span></div>`;
|
relationHtml = html`<div class="type-label">Attitude: <span class="${classType}">${relationName}</span></div>`;
|
||||||
@@ -149,8 +165,8 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
|||||||
return html`
|
return html`
|
||||||
<div class="info-content">
|
<div class="info-content">
|
||||||
<div class="player-name ${isAlly ? 'ally' : ''}">${player.name()}</div>
|
<div class="player-name ${isAlly ? 'ally' : ''}">${player.name()}</div>
|
||||||
<div class="type-label">Troops: ${renderTroops(player.troops())}</div>
|
<div class="type-label">Troops: ${player.troops()}</div>
|
||||||
<div class="type-label">Gold: ${renderNumber(player.gold())}</div>
|
<div class="type-label">Gold: ${player.gold()}</div>
|
||||||
${relationHtml == null ? '' : relationHtml}
|
${relationHtml == null ? '' : relationHtml}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -173,7 +189,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this._isActive) {
|
if (!this._isActive) {
|
||||||
return html``
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -277,6 +293,19 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hostile {
|
||||||
|
color: #ff4444;
|
||||||
|
}
|
||||||
|
.distrustful {
|
||||||
|
color: #ff8888;
|
||||||
|
}
|
||||||
|
.neutral {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
.friendly {
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.container {
|
.container {
|
||||||
top: 5px;
|
top: 5px;
|
||||||
@@ -302,19 +331,6 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
|||||||
.type-label {
|
.type-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
.hostile {
|
|
||||||
color: #ff4444;
|
|
||||||
}
|
|
||||||
.distrustful {
|
|
||||||
color: #ff8888;
|
|
||||||
}
|
|
||||||
.neutral {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
.friendly {
|
|
||||||
color: #4CAF50;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import { colord, Colord } from "colord";
|
import { colord, Colord } from "colord";
|
||||||
import { Theme } from "../../../core/configuration/Config";
|
import { Theme } from "../../../core/configuration/Config";
|
||||||
import { Unit, Cell, Game, Tile, UnitType } from "../../../core/game/Game";
|
|
||||||
import { bfs, dist, euclDist } from "../../../core/Util";
|
|
||||||
import { Layer } from "./Layer";
|
import { Layer } from "./Layer";
|
||||||
import { EventBus } from "../../../core/EventBus";
|
import { EventBus } from "../../../core/EventBus";
|
||||||
|
|
||||||
@@ -10,6 +8,8 @@ import missileSiloIcon from '../../../../resources/images/MissileSiloUnit.png';
|
|||||||
import shieldIcon from '../../../../resources/images/ShieldIcon.png';
|
import shieldIcon from '../../../../resources/images/ShieldIcon.png';
|
||||||
import cityIcon from '../../../../resources/images/CityIcon.png';
|
import cityIcon from '../../../../resources/images/CityIcon.png';
|
||||||
import { GameView } from "../../../core/GameView";
|
import { GameView } from "../../../core/GameView";
|
||||||
|
import { Cell, Unit, UnitType } from "../../../core/game/Game";
|
||||||
|
import { euclDistFN } from "../../../core/game/GameMap";
|
||||||
|
|
||||||
interface UnitRenderConfig {
|
interface UnitRenderConfig {
|
||||||
icon: string;
|
icon: string;
|
||||||
@@ -17,14 +17,13 @@ interface UnitRenderConfig {
|
|||||||
territoryRadius: number;
|
territoryRadius: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class StructureLayer implements Layer {
|
export class StructureLayer implements Layer {
|
||||||
private canvas: HTMLCanvasElement;
|
private canvas: HTMLCanvasElement;
|
||||||
private context: CanvasRenderingContext2D;
|
private context: CanvasRenderingContext2D;
|
||||||
private unitImages: Map<string, HTMLImageElement> = new Map();
|
private unitImages: Map<string, HTMLImageElement> = new Map();
|
||||||
private theme: Theme = null;
|
private theme: Theme = null;
|
||||||
|
|
||||||
private seenUnits = new Set<Unit>()
|
private seenUnits = new Set<Unit>();
|
||||||
|
|
||||||
// Configuration for supported unit types only
|
// Configuration for supported unit types only
|
||||||
private readonly unitConfigs: Partial<Record<UnitType, UnitRenderConfig>> = {
|
private readonly unitConfigs: Partial<Record<UnitType, UnitRenderConfig>> = {
|
||||||
@@ -70,20 +69,20 @@ export class StructureLayer implements Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
this.game.units().forEach(u => this.handleUnitRendering(u))
|
this.game.units().forEach(u => this.handleUnitRendering(u));
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.redraw()
|
this.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
redraw() {
|
redraw() {
|
||||||
console.log('structure layer redrawing')
|
console.log('structure layer redrawing');
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = document.createElement('canvas');
|
||||||
this.context = this.canvas.getContext("2d", { alpha: true });
|
this.context = this.canvas.getContext("2d", { alpha: true });
|
||||||
this.canvas.width = this.game.width();
|
this.canvas.width = this.game.width();
|
||||||
this.canvas.height = this.game.height();
|
this.canvas.height = this.game.height();
|
||||||
this.game.units().forEach(u => this.handleUnitRendering(u))
|
this.game.units().forEach(u => this.handleUnitRendering(u));
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLayer(context: CanvasRenderingContext2D) {
|
renderLayer(context: CanvasRenderingContext2D) {
|
||||||
@@ -106,11 +105,11 @@ export class StructureLayer implements Layer {
|
|||||||
|
|
||||||
if (unit.isActive() && this.seenUnits.has(unit)) {
|
if (unit.isActive() && this.seenUnits.has(unit)) {
|
||||||
// Already rendered, so don't do anything.
|
// Already rendered, so don't do anything.
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (!unit.isActive() && !this.seenUnits.has(unit)) {
|
if (!unit.isActive() && !this.seenUnits.has(unit)) {
|
||||||
// Has been deleted and render is cleared so don't do anything.
|
// Has been deleted and render is cleared so don't do anything.
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = this.unitConfigs[unitType];
|
const config = this.unitConfigs[unitType];
|
||||||
@@ -119,14 +118,15 @@ export class StructureLayer implements Layer {
|
|||||||
if (!config || !unitImage) return;
|
if (!config || !unitImage) return;
|
||||||
|
|
||||||
// Clear previous rendering
|
// Clear previous rendering
|
||||||
bfs(unit.tile(), euclDist(unit.tile(), config.borderRadius))
|
for (const tile of this.game.bfs(unit.tile(), euclDistFN(unit.tile(), config.borderRadius))) {
|
||||||
.forEach(t => this.clearCell(t.cell()));
|
this.clearCell(new Cell(this.game.x(tile), this.game.y(tile)));
|
||||||
|
}
|
||||||
|
|
||||||
if (!unit.isActive()) {
|
if (!unit.isActive()) {
|
||||||
this.seenUnits.delete(unit)
|
this.seenUnits.delete(unit);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.seenUnits.add(unit)
|
this.seenUnits.add(unit);
|
||||||
|
|
||||||
// Create temporary canvas for icon processing
|
// Create temporary canvas for icon processing
|
||||||
const tempCanvas = document.createElement('canvas');
|
const tempCanvas = document.createElement('canvas');
|
||||||
@@ -138,16 +138,25 @@ export class StructureLayer implements Layer {
|
|||||||
tempContext.drawImage(unitImage, 0, 0);
|
tempContext.drawImage(unitImage, 0, 0);
|
||||||
const iconData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
const iconData = tempContext.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||||
|
|
||||||
const cell = unit.tile().cell();
|
const startX = this.game.x(unit.tile()) - Math.floor(tempCanvas.width / 2);
|
||||||
const startX = cell.x - Math.floor(tempCanvas.width / 2);
|
const startY = this.game.y(unit.tile()) - Math.floor(tempCanvas.height / 2);
|
||||||
const startY = cell.y - Math.floor(tempCanvas.height / 2);
|
|
||||||
|
|
||||||
// Draw border and territory
|
// Draw border and territory
|
||||||
bfs(unit.tile(), euclDist(unit.tile(), config.borderRadius))
|
for (const tile of this.game.bfs(unit.tile(), euclDistFN(unit.tile(), config.borderRadius))) {
|
||||||
.forEach(t => this.paintCell(t.cell(), this.theme.borderColor(unit.owner().info()), 255));
|
this.paintCell(
|
||||||
|
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||||
|
this.theme.borderColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bfs(unit.tile(), euclDist(unit.tile(), config.territoryRadius))
|
for (const tile of this.game.bfs(unit.tile(), euclDistFN(unit.tile(), config.territoryRadius))) {
|
||||||
.forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(unit.owner().info()), 130));
|
this.paintCell(
|
||||||
|
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||||
|
this.theme.territoryColor(unit.owner().info()),
|
||||||
|
130
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw the icon
|
// Draw the icon
|
||||||
this.renderIcon(iconData, startX, startY, tempCanvas.width, tempCanvas.height, unit);
|
this.renderIcon(iconData, startX, startY, tempCanvas.width, tempCanvas.height, unit);
|
||||||
@@ -184,7 +193,7 @@ export class StructureLayer implements Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
paintCell(cell: Cell, color: Colord, alpha: number) {
|
paintCell(cell: Cell, color: Colord, alpha: number) {
|
||||||
this.clearCell(cell)
|
this.clearCell(cell);
|
||||||
this.context.fillStyle = color.alpha(alpha / 255).toRgbString();
|
this.context.fillStyle = color.alpha(alpha / 255).toRgbString();
|
||||||
this.context.fillRect(cell.x, cell.y, 1, 1);
|
this.context.fillRect(cell.x, cell.y, 1, 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import { inherits } from "util"
|
|
||||||
import { Game } from "../../../core/game/Game";
|
|
||||||
import { throws } from "assert";
|
|
||||||
import { Layer } from "./Layer";
|
import { Layer } from "./Layer";
|
||||||
import { TransformHandler } from "../TransformHandler";
|
|
||||||
import { GameView } from "../../../core/GameView";
|
import { GameView } from "../../../core/GameView";
|
||||||
|
|
||||||
export class TerrainLayer implements Layer {
|
export class TerrainLayer implements Layer {
|
||||||
@@ -37,8 +33,9 @@ export class TerrainLayer implements Layer {
|
|||||||
initImageData() {
|
initImageData() {
|
||||||
const theme = this.game.config().theme()
|
const theme = this.game.config().theme()
|
||||||
this.game.forEachTile((tile) => {
|
this.game.forEachTile((tile) => {
|
||||||
let terrainColor = theme.terrainColor(tile)
|
let terrainColor = theme.terrainColor(this.game, tile)
|
||||||
const index = (tile.cell().y * this.game.width()) + tile.cell().x
|
// TODO: isn'te tileref and index the same?
|
||||||
|
const index = (this.game.y(tile) * this.game.width()) + this.game.x(tile)
|
||||||
const offset = index * 4
|
const offset = index * 4
|
||||||
this.imageData.data[offset] = terrainColor.rgba.r;
|
this.imageData.data[offset] = terrainColor.rgba.r;
|
||||||
this.imageData.data[offset + 1] = terrainColor.rgba.g;
|
this.imageData.data[offset + 1] = terrainColor.rgba.g;
|
||||||
|
|||||||
@@ -1,90 +1,91 @@
|
|||||||
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
||||||
import { Cell, Game, Player, PlayerType, Tile, Unit, UnitType, UnitUpdate } from "../../../core/game/Game";
|
import { Cell, Game, Player, PlayerType, Unit, UnitType, UnitUpdate } from "../../../core/game/Game";
|
||||||
import { PseudoRandom } from "../../../core/PseudoRandom";
|
import { PseudoRandom } from "../../../core/PseudoRandom";
|
||||||
import { colord, Colord } from "colord";
|
import { colord, Colord } from "colord";
|
||||||
import { bfs, dist, euclDist, euclideanDist } from "../../../core/Util";
|
|
||||||
import { Theme } from "../../../core/configuration/Config";
|
import { Theme } from "../../../core/configuration/Config";
|
||||||
import { Layer } from "./Layer";
|
import { Layer } from "./Layer";
|
||||||
import { EventBus } from "../../../core/EventBus";
|
import { EventBus } from "../../../core/EventBus";
|
||||||
import { AlternateViewEvent, DragEvent, MouseDownEvent } from "../../InputHandler";
|
import { AlternateViewEvent, DragEvent, MouseDownEvent } from "../../InputHandler";
|
||||||
import { GameView, PlayerView } from "../../../core/GameView";
|
import { GameView, PlayerView } from "../../../core/GameView";
|
||||||
|
import { euclDistFN, TileRef } from "../../../core/game/GameMap";
|
||||||
|
|
||||||
export class TerritoryLayer implements Layer {
|
export class TerritoryLayer implements Layer {
|
||||||
private canvas: HTMLCanvasElement
|
private canvas: HTMLCanvasElement;
|
||||||
private context: CanvasRenderingContext2D
|
private context: CanvasRenderingContext2D;
|
||||||
private imageData: ImageData
|
private imageData: ImageData;
|
||||||
|
|
||||||
private tileToRenderQueue: PriorityQueue<{ tile: Tile, lastUpdate: number }> = new PriorityQueue((a, b) => { return a.lastUpdate - b.lastUpdate })
|
private tileToRenderQueue: PriorityQueue<{ tile: TileRef, lastUpdate: number }> = new PriorityQueue((a, b) => { return a.lastUpdate - b.lastUpdate });
|
||||||
private random = new PseudoRandom(123)
|
private random = new PseudoRandom(123);
|
||||||
private theme: Theme = null
|
private theme: Theme = null;
|
||||||
|
|
||||||
// Used for spawn highlighting
|
// Used for spawn highlighting
|
||||||
private highlightCanvas: HTMLCanvasElement
|
private highlightCanvas: HTMLCanvasElement;
|
||||||
private highlightContext: CanvasRenderingContext2D
|
private highlightContext: CanvasRenderingContext2D;
|
||||||
|
|
||||||
private alternativeView = false
|
private alternativeView = false;
|
||||||
private lastDragTime = 0
|
private lastDragTime = 0;
|
||||||
private nodrawDragDuration = 200
|
private nodrawDragDuration = 200;
|
||||||
|
|
||||||
private refreshRate = 50
|
|
||||||
private lastRefresh = 0
|
|
||||||
|
|
||||||
|
private refreshRate = 50;
|
||||||
|
private lastRefresh = 0;
|
||||||
|
|
||||||
constructor(private game: GameView, private eventBus: EventBus) {
|
constructor(private game: GameView, private eventBus: EventBus) {
|
||||||
this.theme = game.config().theme()
|
this.theme = game.config().theme();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldTransform(): boolean {
|
shouldTransform(): boolean {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
this.game.recentlyUpdatedTiles()
|
this.game.recentlyUpdatedTiles()
|
||||||
.forEach(t => this.enqueueTile(t))
|
.forEach(t => this.enqueueTile(t));
|
||||||
|
|
||||||
|
|
||||||
if (!this.game.inSpawnPhase()) {
|
if (!this.game.inSpawnPhase()) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (this.game.ticks() % 5 == 0) {
|
if (this.game.ticks() % 5 == 0) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.highlightContext.clearRect(0, 0, this.game.width(), this.game.height());
|
this.highlightContext.clearRect(0, 0, this.game.width(), this.game.height());
|
||||||
const humans = this.game.playerViews()
|
const humans = this.game.playerViews()
|
||||||
.filter(p => p.type() == PlayerType.Human)
|
.filter(p => p.type() == PlayerType.Human);
|
||||||
|
|
||||||
for (const human of humans) {
|
for (const human of humans) {
|
||||||
const center = human.nameLocation()
|
const center = human.nameLocation();
|
||||||
if (!center) {
|
if (!center) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
const centerTile = this.game.tile(new Cell(center.x, center.y))
|
const centerTile = this.game.ref(center.x, center.y)
|
||||||
if (!centerTile) {
|
if (!centerTile) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
for (const tile of bfs(centerTile, euclDist(centerTile, 9))) {
|
for (const tile of this.game.bfs(centerTile, euclDistFN(centerTile, 9))) {
|
||||||
if (!tile.hasOwner()) {
|
if (!this.game.hasOwner(tile)) {
|
||||||
this.paintHighlightCell(tile.cell(), this.theme.spawnHighlightColor(), 255)
|
this.paintHighlightCell(
|
||||||
|
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||||
|
this.theme.spawnHighlightColor(),
|
||||||
|
255
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.eventBus.on(AlternateViewEvent, e => { this.alternativeView = e.alternateView })
|
this.eventBus.on(AlternateViewEvent, e => { this.alternativeView = e.alternateView; });
|
||||||
this.eventBus.on(DragEvent, e => { this.lastDragTime = Date.now() })
|
this.eventBus.on(DragEvent, e => { this.lastDragTime = Date.now(); });
|
||||||
this.redraw()
|
this.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
redraw() {
|
redraw() {
|
||||||
console.log('redrew territory layer')
|
console.log('redrew territory layer');
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = document.createElement('canvas');
|
||||||
this.context = this.canvas.getContext("2d")
|
this.context = this.canvas.getContext("2d");
|
||||||
|
|
||||||
this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height())
|
this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height());
|
||||||
this.initImageData()
|
this.initImageData();
|
||||||
this.canvas.width = this.game.width();
|
this.canvas.width = this.game.width();
|
||||||
this.canvas.height = this.game.height();
|
this.canvas.height = this.game.height();
|
||||||
this.context.putImageData(this.imageData, 0, 0);
|
this.context.putImageData(this.imageData, 0, 0);
|
||||||
@@ -96,26 +97,27 @@ export class TerritoryLayer implements Layer {
|
|||||||
this.highlightCanvas.height = this.game.height();
|
this.highlightCanvas.height = this.game.height();
|
||||||
|
|
||||||
this.game.forEachTile(t => {
|
this.game.forEachTile(t => {
|
||||||
this.paintTerritory(t)
|
this.paintTerritory(t);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initImageData() {
|
initImageData() {
|
||||||
this.game.forEachTile((tile) => {
|
this.game.forEachTile((tile) => {
|
||||||
const index = (tile.cell().y * this.game.width()) + tile.cell().x
|
const cell = new Cell(this.game.x(tile), this.game.y(tile));
|
||||||
const offset = index * 4
|
const index = (cell.y * this.game.width()) + cell.x;
|
||||||
this.imageData.data[offset + 3] = 0
|
const offset = index * 4;
|
||||||
})
|
this.imageData.data[offset + 3] = 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLayer(context: CanvasRenderingContext2D) {
|
renderLayer(context: CanvasRenderingContext2D) {
|
||||||
if (Date.now() > this.lastDragTime + this.nodrawDragDuration && Date.now() > this.lastRefresh + this.refreshRate) {
|
if (Date.now() > this.lastDragTime + this.nodrawDragDuration && Date.now() > this.lastRefresh + this.refreshRate) {
|
||||||
this.lastRefresh = Date.now()
|
this.lastRefresh = Date.now();
|
||||||
this.renderTerritory()
|
this.renderTerritory();
|
||||||
this.context.putImageData(this.imageData, 0, 0);
|
this.context.putImageData(this.imageData, 0, 0);
|
||||||
}
|
}
|
||||||
if (this.alternativeView) {
|
if (this.alternativeView) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
context.drawImage(
|
context.drawImage(
|
||||||
@@ -124,7 +126,7 @@ export class TerritoryLayer implements Layer {
|
|||||||
-this.game.height() / 2,
|
-this.game.height() / 2,
|
||||||
this.game.width(),
|
this.game.width(),
|
||||||
this.game.height()
|
this.game.height()
|
||||||
)
|
);
|
||||||
if (this.game.inSpawnPhase()) {
|
if (this.game.inSpawnPhase()) {
|
||||||
context.drawImage(
|
context.drawImage(
|
||||||
this.highlightCanvas,
|
this.highlightCanvas,
|
||||||
@@ -137,62 +139,60 @@ export class TerritoryLayer implements Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderTerritory() {
|
renderTerritory() {
|
||||||
let numToRender = Math.floor(this.tileToRenderQueue.size() / 5)
|
let numToRender = Math.floor(this.tileToRenderQueue.size() / 5);
|
||||||
if (numToRender == 0 || this.game.inSpawnPhase()) {
|
if (numToRender == 0 || this.game.inSpawnPhase()) {
|
||||||
numToRender = this.tileToRenderQueue.size()
|
numToRender = this.tileToRenderQueue.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
while (numToRender > 0) {
|
while (numToRender > 0) {
|
||||||
numToRender--
|
numToRender--;
|
||||||
const tile = this.tileToRenderQueue.pop().tile
|
const tile = this.tileToRenderQueue.pop().tile;
|
||||||
this.paintTerritory(tile)
|
this.paintTerritory(tile);
|
||||||
tile.neighbors().forEach(t => this.paintTerritory(t, true))
|
for (const neighbor of this.game.neighbors(tile)) {
|
||||||
|
this.paintTerritory(neighbor, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paintTerritory(tile: Tile, isBorder: boolean = false) {
|
paintTerritory(tile: TileRef, isBorder: boolean = false) {
|
||||||
if (isBorder && !tile.hasOwner()) {
|
if (isBorder && !this.game.hasOwner(tile)) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (!tile.hasOwner()) {
|
if (!this.game.hasOwner(tile)) {
|
||||||
if (tile.hasFallout()) {
|
if (this.game.hasFallout(tile)) {
|
||||||
this.paintCell(tile.cell(), this.theme.falloutColor(), 150)
|
this.paintCell(
|
||||||
return
|
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||||
|
this.theme.falloutColor(),
|
||||||
|
150
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
this.clearCell(tile.cell())
|
this.clearCell(new Cell(this.game.x(tile), this.game.y(tile)));
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const owner = tile.owner() as Player
|
const owner = this.game.owner(tile) as Player;
|
||||||
if (tile.isBorder()) {
|
if (this.game.isBorder(tile)) {
|
||||||
if (tile.hasDefenseBonus()) {
|
this.paintCell(
|
||||||
this.paintCell(
|
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||||
tile.cell(),
|
this.theme.borderColor(owner.info()),
|
||||||
this.theme.defendedBorderColor(owner.info()),
|
255
|
||||||
255
|
);
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.paintCell(
|
|
||||||
tile.cell(),
|
|
||||||
this.theme.borderColor(owner.info()),
|
|
||||||
255
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.paintCell(
|
this.paintCell(
|
||||||
tile.cell(),
|
new Cell(this.game.x(tile), this.game.y(tile)),
|
||||||
this.theme.territoryColor(owner.info()),
|
this.theme.territoryColor(owner.info()),
|
||||||
150
|
150
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paintCell(cell: Cell, color: Colord, alpha: number) {
|
paintCell(cell: Cell, color: Colord, alpha: number) {
|
||||||
const index = (cell.y * this.game.width()) + cell.x
|
const index = (cell.y * this.game.width()) + cell.x;
|
||||||
const offset = index * 4
|
const offset = index * 4;
|
||||||
this.imageData.data[offset] = color.rgba.r;
|
this.imageData.data[offset] = color.rgba.r;
|
||||||
this.imageData.data[offset + 1] = color.rgba.g;
|
this.imageData.data[offset + 1] = color.rgba.g;
|
||||||
this.imageData.data[offset + 2] = color.rgba.b;
|
this.imageData.data[offset + 2] = color.rgba.b;
|
||||||
this.imageData.data[offset + 3] = alpha
|
this.imageData.data[offset + 3] = alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCell(cell: Cell) {
|
clearCell(cell: Cell) {
|
||||||
@@ -201,12 +201,15 @@ export class TerritoryLayer implements Layer {
|
|||||||
this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
|
this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
|
||||||
}
|
}
|
||||||
|
|
||||||
enqueueTile(tile: Tile) {
|
enqueueTile(tile: TileRef) {
|
||||||
this.tileToRenderQueue.push({ tile: tile, lastUpdate: this.game.ticks() + this.random.nextFloat(0, .5) })
|
this.tileToRenderQueue.push({
|
||||||
|
tile: tile,
|
||||||
|
lastUpdate: this.game.ticks() + this.random.nextFloat(0, .5)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
paintHighlightCell(cell: Cell, color: Colord, alpha: number) {
|
paintHighlightCell(cell: Cell, color: Colord, alpha: number) {
|
||||||
this.clearCell(cell)
|
this.clearCell(cell);
|
||||||
this.highlightContext.fillStyle = color.alpha(alpha / 255).toRgbString();
|
this.highlightContext.fillStyle = color.alpha(alpha / 255).toRgbString();
|
||||||
this.highlightContext.fillRect(cell.x, cell.y, 1, 1);
|
this.highlightContext.fillRect(cell.x, cell.y, 1, 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Colord } from "colord";
|
import { Colord } from "colord";
|
||||||
import { Theme } from "../../../core/configuration/Config";
|
import { Theme } from "../../../core/configuration/Config";
|
||||||
import { Unit, Cell, Game, Tile, UnitType, Player, UnitUpdate } from "../../../core/game/Game";
|
import { Unit, UnitType, Player, UnitUpdate } from "../../../core/game/Game";
|
||||||
import { bfs, dist, euclDist } from "../../../core/Util";
|
|
||||||
import { Layer } from "./Layer";
|
import { Layer } from "./Layer";
|
||||||
import { EventBus } from "../../../core/EventBus";
|
import { EventBus } from "../../../core/EventBus";
|
||||||
import { AlternateViewEvent } from "../../InputHandler";
|
import { AlternateViewEvent } from "../../InputHandler";
|
||||||
import { ClientID } from "../../../core/Schemas";
|
import { ClientID } from "../../../core/Schemas";
|
||||||
import { GameView } from "../../../core/GameView";
|
import { GameView } from "../../../core/GameView";
|
||||||
|
import { euclDistFN, manhattanDistFN, TileRef } from "../../../core/game/GameMap";
|
||||||
|
|
||||||
enum Relationship {
|
enum Relationship {
|
||||||
Self,
|
Self,
|
||||||
@@ -18,15 +18,15 @@ export class UnitLayer implements Layer {
|
|||||||
private canvas: HTMLCanvasElement;
|
private canvas: HTMLCanvasElement;
|
||||||
private context: CanvasRenderingContext2D;
|
private context: CanvasRenderingContext2D;
|
||||||
|
|
||||||
private boatToTrail = new Map<Unit, Set<Tile>>();
|
private boatToTrail = new Map<Unit, Set<TileRef>>();
|
||||||
|
|
||||||
private theme: Theme = null;
|
private theme: Theme = null;
|
||||||
|
|
||||||
private alternateView = false
|
private alternateView = false;
|
||||||
|
|
||||||
private myPlayer: Player | null = null
|
private myPlayer: Player | null = null;
|
||||||
|
|
||||||
private oldShellTile = new Map<Unit, Tile>()
|
private oldShellTile = new Map<Unit, TileRef>();
|
||||||
|
|
||||||
constructor(private game: GameView, private eventBus: EventBus, private clientID: ClientID) {
|
constructor(private game: GameView, private eventBus: EventBus, private clientID: ClientID) {
|
||||||
this.theme = game.config().theme();
|
this.theme = game.config().theme();
|
||||||
@@ -38,17 +38,17 @@ export class UnitLayer implements Layer {
|
|||||||
|
|
||||||
tick() {
|
tick() {
|
||||||
if (this.myPlayer == null) {
|
if (this.myPlayer == null) {
|
||||||
this.myPlayer = this.game.playerByClientID(this.clientID)
|
this.myPlayer = this.game.playerByClientID(this.clientID);
|
||||||
}
|
}
|
||||||
for (const unit of this.game.units()) {
|
for (const unit of this.game.units()) {
|
||||||
if (unit.wasUpdated())
|
if (unit.wasUpdated())
|
||||||
this.onUnitEvent(unit)
|
this.onUnitEvent(unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.eventBus.on(AlternateViewEvent, e => this.onAlternativeViewEvent(e))
|
this.eventBus.on(AlternateViewEvent, e => this.onAlternativeViewEvent(e));
|
||||||
this.redraw()
|
this.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLayer(context: CanvasRenderingContext2D) {
|
renderLayer(context: CanvasRenderingContext2D) {
|
||||||
@@ -62,11 +62,10 @@ export class UnitLayer implements Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onAlternativeViewEvent(event: AlternateViewEvent) {
|
onAlternativeViewEvent(event: AlternateViewEvent) {
|
||||||
this.alternateView = event.alternateView
|
this.alternateView = event.alternateView;
|
||||||
this.redraw()
|
this.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
redraw() {
|
redraw() {
|
||||||
this.canvas = document.createElement('canvas');
|
this.canvas = document.createElement('canvas');
|
||||||
this.context = this.canvas.getContext("2d");
|
this.context = this.canvas.getContext("2d");
|
||||||
@@ -80,15 +79,15 @@ export class UnitLayer implements Layer {
|
|||||||
|
|
||||||
private relationship(unit: Unit): Relationship {
|
private relationship(unit: Unit): Relationship {
|
||||||
if (this.myPlayer == null) {
|
if (this.myPlayer == null) {
|
||||||
return Relationship.Enemy
|
return Relationship.Enemy;
|
||||||
}
|
}
|
||||||
if (this.myPlayer == unit.owner()) {
|
if (this.myPlayer == unit.owner()) {
|
||||||
return Relationship.Self
|
return Relationship.Self;
|
||||||
}
|
}
|
||||||
if (this.myPlayer.isAlliedWith(unit.owner())) {
|
if (this.myPlayer.isAlliedWith(unit.owner())) {
|
||||||
return Relationship.Ally
|
return Relationship.Ally;
|
||||||
}
|
}
|
||||||
return Relationship.Enemy
|
return Relationship.Enemy;
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnitEvent(unit: Unit) {
|
onUnitEvent(unit: Unit) {
|
||||||
@@ -103,137 +102,262 @@ export class UnitLayer implements Layer {
|
|||||||
this.handleBattleshipEvent(unit);
|
this.handleBattleshipEvent(unit);
|
||||||
break;
|
break;
|
||||||
case UnitType.Shell:
|
case UnitType.Shell:
|
||||||
this.handleShellEvent(unit)
|
this.handleShellEvent(unit);
|
||||||
break;
|
break;
|
||||||
case UnitType.TradeShip:
|
case UnitType.TradeShip:
|
||||||
this.handleTradeShipEvent(unit)
|
this.handleTradeShipEvent(unit);
|
||||||
break;
|
break;
|
||||||
case UnitType.AtomBomb:
|
case UnitType.AtomBomb:
|
||||||
case UnitType.HydrogenBomb:
|
case UnitType.HydrogenBomb:
|
||||||
this.handleNuke(unit)
|
this.handleNuke(unit);
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleDestroyerEvent(unit: Unit) {
|
private handleDestroyerEvent(unit: Unit) {
|
||||||
const rel = this.relationship(unit)
|
const rel = this.relationship(unit);
|
||||||
bfs(unit.lastTile(), euclDist(unit.lastTile(), 4)).forEach(t => {
|
|
||||||
this.clearCell(t.cell());
|
// 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()) {
|
if (!unit.isActive()) {
|
||||||
return
|
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
bfs(unit.tile(), euclDist(unit.tile(), 4))
|
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.borderColor(unit.owner().info()), 255));
|
|
||||||
bfs(unit.tile(), dist(unit.tile(), 3))
|
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.territoryColor(unit.owner().info()), 255));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleBattleshipEvent(unit: Unit) {
|
private handleBattleshipEvent(unit: Unit) {
|
||||||
const rel = this.relationship(unit)
|
const rel = this.relationship(unit);
|
||||||
bfs(unit.lastTile(), euclDist(unit.lastTile(), 6)).forEach(t => {
|
|
||||||
this.clearCell(t.cell());
|
// Clear previous area
|
||||||
});
|
for (const t of this.game.bfs(unit.lastTile(), euclDistFN(unit.lastTile(), 6))) {
|
||||||
|
this.clearCell(this.game.x(t), this.game.y(t));
|
||||||
|
}
|
||||||
|
|
||||||
if (!unit.isActive()) {
|
if (!unit.isActive()) {
|
||||||
return
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint outer territory
|
||||||
|
for (const t of this.game.bfs(unit.tile(), euclDistFN(unit.tile(), 5))) {
|
||||||
|
this.paintCell(
|
||||||
|
this.game.x(t),
|
||||||
|
this.game.y(t),
|
||||||
|
rel,
|
||||||
|
this.theme.territoryColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint border
|
||||||
|
for (const t of this.game.bfs(unit.tile(), manhattanDistFN(unit.tile(), 4))) {
|
||||||
|
this.paintCell(
|
||||||
|
this.game.x(t),
|
||||||
|
this.game.y(t),
|
||||||
|
rel,
|
||||||
|
this.theme.borderColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint inner territory
|
||||||
|
for (const t of this.game.bfs(unit.tile(), euclDistFN(unit.tile(), 1))) {
|
||||||
|
this.paintCell(
|
||||||
|
this.game.x(t),
|
||||||
|
this.game.y(t),
|
||||||
|
rel,
|
||||||
|
this.theme.territoryColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
}
|
}
|
||||||
bfs(unit.tile(), euclDist(unit.tile(), 5))
|
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.territoryColor(unit.owner().info()), 255));
|
|
||||||
bfs(unit.tile(), dist(unit.tile(), 4))
|
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.borderColor(unit.owner().info()), 255));
|
|
||||||
bfs(unit.tile(), euclDist(unit.tile(), 1))
|
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.territoryColor(unit.owner().info()), 255));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleShellEvent(unit: Unit) {
|
private handleShellEvent(unit: Unit) {
|
||||||
const rel = this.relationship(unit)
|
const rel = this.relationship(unit);
|
||||||
|
|
||||||
this.clearCell(unit.lastTile().cell())
|
// Clear current and previous positions
|
||||||
|
this.clearCell(this.game.x(unit.lastTile()), this.game.y(unit.lastTile()));
|
||||||
if (this.oldShellTile.has(unit)) {
|
if (this.oldShellTile.has(unit)) {
|
||||||
this.clearCell(this.oldShellTile.get(unit).cell())
|
const oldTile = this.oldShellTile.get(unit);
|
||||||
|
this.clearCell(this.game.x(oldTile), this.game.y(oldTile));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.oldShellTile.set(unit, unit.lastTile())
|
this.oldShellTile.set(unit, unit.lastTile());
|
||||||
if (!unit.isActive()) {
|
if (!unit.isActive()) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
this.paintCell(unit.tile().cell(), rel, this.theme.borderColor(unit.owner().info()), 255)
|
|
||||||
this.paintCell(unit.lastTile().cell(), rel, this.theme.borderColor(unit.owner().info()), 255)
|
// Paint current and previous positions
|
||||||
|
this.paintCell(
|
||||||
|
this.game.x(unit.tile()),
|
||||||
|
this.game.y(unit.tile()),
|
||||||
|
rel,
|
||||||
|
this.theme.borderColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
|
this.paintCell(
|
||||||
|
this.game.x(unit.lastTile()),
|
||||||
|
this.game.y(unit.lastTile()),
|
||||||
|
rel,
|
||||||
|
this.theme.borderColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private handleNuke(unit: Unit) {
|
private handleNuke(unit: Unit) {
|
||||||
const rel = this.relationship(unit)
|
const rel = this.relationship(unit);
|
||||||
bfs(unit.lastTile(), euclDist(unit.lastTile(), 2)).forEach(t => {
|
|
||||||
this.clearCell(t.cell());
|
// Clear previous area
|
||||||
});
|
for (const t of this.game.bfs(unit.lastTile(), euclDistFN(unit.lastTile(), 2))) {
|
||||||
if (unit.isActive()) {
|
this.clearCell(this.game.x(t), this.game.y(t));
|
||||||
bfs(unit.tile(), euclDist(unit.tile(), 2))
|
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.borderColor(unit.owner().info()), 255));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (unit.isActive()) {
|
||||||
|
// Paint area
|
||||||
|
for (const t of this.game.bfs(unit.tile(), euclDistFN(unit.tile(), 2))) {
|
||||||
|
this.paintCell(
|
||||||
|
this.game.x(t),
|
||||||
|
this.game.y(t),
|
||||||
|
rel,
|
||||||
|
this.theme.borderColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTradeShipEvent(unit: Unit) {
|
private handleTradeShipEvent(unit: Unit) {
|
||||||
const rel = this.relationship(unit)
|
const rel = this.relationship(unit);
|
||||||
bfs(unit.lastTile(), euclDist(unit.lastTile(), 3)).forEach(t => {
|
|
||||||
this.clearCell(t.cell());
|
// Clear previous area
|
||||||
});
|
for (const t of this.game.bfs(unit.lastTile(), euclDistFN(unit.lastTile(), 3))) {
|
||||||
if (unit.isActive()) {
|
this.clearCell(this.game.x(t), this.game.y(t));
|
||||||
bfs(unit.tile(), dist(unit.tile(), 2))
|
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.territoryColor(unit.owner().info()), 255));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unit.isActive()) {
|
if (unit.isActive()) {
|
||||||
bfs(unit.tile(), dist(unit.tile(), 1))
|
// Paint territory
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.borderColor(unit.owner().info()), 255));
|
for (const t of this.game.bfs(unit.tile(), manhattanDistFN(unit.tile(), 2))) {
|
||||||
|
this.paintCell(
|
||||||
|
this.game.x(t),
|
||||||
|
this.game.y(t),
|
||||||
|
rel,
|
||||||
|
this.theme.territoryColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint border
|
||||||
|
for (const t of this.game.bfs(unit.tile(), manhattanDistFN(unit.tile(), 1))) {
|
||||||
|
this.paintCell(
|
||||||
|
this.game.x(t),
|
||||||
|
this.game.y(t),
|
||||||
|
rel,
|
||||||
|
this.theme.borderColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleBoatEvent(unit: Unit) {
|
private handleBoatEvent(unit: Unit) {
|
||||||
const rel = this.relationship(unit)
|
const rel = this.relationship(unit);
|
||||||
|
|
||||||
if (!this.boatToTrail.has(unit)) {
|
if (!this.boatToTrail.has(unit)) {
|
||||||
this.boatToTrail.set(unit, new Set<Tile>());
|
this.boatToTrail.set(unit, new Set<TileRef>());
|
||||||
}
|
}
|
||||||
const trail = this.boatToTrail.get(unit);
|
const trail = this.boatToTrail.get(unit);
|
||||||
trail.add(unit.lastTile());
|
trail.add(unit.lastTile());
|
||||||
bfs(unit.lastTile(), dist(unit.lastTile(), 3)).forEach(t => {
|
|
||||||
this.clearCell(t.cell());
|
// Clear previous area
|
||||||
});
|
for (const t of this.game.bfs(unit.lastTile(), manhattanDistFN(unit.lastTile(), 3))) {
|
||||||
|
this.clearCell(this.game.x(t), this.game.y(t));
|
||||||
|
}
|
||||||
|
|
||||||
if (unit.isActive()) {
|
if (unit.isActive()) {
|
||||||
|
// Paint trail
|
||||||
for (const t of trail) {
|
for (const t of trail) {
|
||||||
this.paintCell(t.cell(), rel, this.theme.territoryColor(unit.owner().info()), 150);
|
this.paintCell(
|
||||||
|
this.game.x(t),
|
||||||
|
this.game.y(t),
|
||||||
|
rel,
|
||||||
|
this.theme.territoryColor(unit.owner().info()),
|
||||||
|
150
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paint border
|
||||||
|
for (const t of this.game.bfs(unit.tile(), manhattanDistFN(unit.tile(), 2))) {
|
||||||
|
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(), 1))) {
|
||||||
|
this.paintCell(
|
||||||
|
this.game.x(t),
|
||||||
|
this.game.y(t),
|
||||||
|
rel,
|
||||||
|
this.theme.territoryColor(unit.owner().info()),
|
||||||
|
255
|
||||||
|
);
|
||||||
}
|
}
|
||||||
bfs(unit.tile(), dist(unit.tile(), 2))
|
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.borderColor(unit.owner().info()), 255));
|
|
||||||
bfs(unit.tile(), dist(unit.tile(), 1))
|
|
||||||
.forEach(t => this.paintCell(t.cell(), rel, this.theme.territoryColor(unit.owner().info()), 255));
|
|
||||||
} else {
|
} else {
|
||||||
trail.forEach(t => this.clearCell(t.cell()));
|
for (const t of trail) {
|
||||||
|
this.clearCell(this.game.x(t), this.game.y(t));
|
||||||
|
}
|
||||||
this.boatToTrail.delete(unit);
|
this.boatToTrail.delete(unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paintCell(cell: Cell, relationship: Relationship, color: Colord, alpha: number) {
|
paintCell(x: number, y: number, relationship: Relationship, color: Colord, alpha: number) {
|
||||||
this.clearCell(cell)
|
this.clearCell(x, y);
|
||||||
if (this.alternateView) {
|
if (this.alternateView) {
|
||||||
switch (relationship) {
|
switch (relationship) {
|
||||||
case Relationship.Self:
|
case Relationship.Self:
|
||||||
this.context.fillStyle = this.theme.selfColor().toRgbString()
|
this.context.fillStyle = this.theme.selfColor().toRgbString();
|
||||||
break
|
break;
|
||||||
case Relationship.Ally:
|
case Relationship.Ally:
|
||||||
this.context.fillStyle = this.theme.allyColor().toRgbString()
|
this.context.fillStyle = this.theme.allyColor().toRgbString();
|
||||||
break
|
break;
|
||||||
case Relationship.Enemy:
|
case Relationship.Enemy:
|
||||||
this.context.fillStyle = this.theme.enemyColor().toRgbString()
|
this.context.fillStyle = this.theme.enemyColor().toRgbString();
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.context.fillStyle = color.alpha(alpha / 255).toRgbString();
|
this.context.fillStyle = color.alpha(alpha / 255).toRgbString();
|
||||||
}
|
}
|
||||||
this.context.fillRect(cell.x, cell.y, 1, 1);
|
this.context.fillRect(x, y, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearCell(cell: Cell) {
|
clearCell(x: number, y: number) {
|
||||||
this.context.clearRect(cell.x, cell.y, 1, 1);
|
this.context.clearRect(x, y, 1, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,7 @@ export class BuildMenu extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showMenu(player: PlayerView, clickedCell: Cell) {
|
showMenu(player: PlayerView, clickedCell: Cell) {
|
||||||
player.actions(this.game.tile(clickedCell)).then(actions => {
|
player.actions(this.game.ref(clickedCell.x, clickedCell.y)).then(actions => {
|
||||||
console.log(`got actions: ${JSON.stringify(actions)}`)
|
console.log(`got actions: ${JSON.stringify(actions)}`)
|
||||||
this.playerActions = actions
|
this.playerActions = actions
|
||||||
this.myPlayer = player;
|
this.myPlayer = player;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { EventBus } from "../../../../core/EventBus";
|
import { EventBus } from "../../../../core/EventBus";
|
||||||
import { AllPlayers, Cell, Game, Player, PlayerActions, Tile, UnitType } from "../../../../core/game/Game";
|
import { AllPlayers, Cell, Game, Player, PlayerActions, } from "../../../../core/game/Game";
|
||||||
import { ClientID } from "../../../../core/Schemas";
|
import { ClientID } from "../../../../core/Schemas";
|
||||||
import { and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore, targetTransportTile } from "../../../../core/Util";
|
|
||||||
import { ContextMenuEvent, MouseUpEvent, ShowBuildMenuEvent } from "../../../InputHandler";
|
import { ContextMenuEvent, MouseUpEvent, ShowBuildMenuEvent } from "../../../InputHandler";
|
||||||
import { SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent } from "../../../Transport";
|
import { SendAllianceRequestIntentEvent, SendAttackIntentEvent, SendBoatAttackIntentEvent, SendBreakAllianceIntentEvent, SendDonateIntentEvent, SendEmojiIntentEvent, SendSpawnIntentEvent, SendTargetPlayerIntentEvent } from "../../../Transport";
|
||||||
import { TransformHandler } from "../../TransformHandler";
|
import { TransformHandler } from "../../TransformHandler";
|
||||||
@@ -21,6 +20,7 @@ import { UIState } from "../../UIState";
|
|||||||
import { BuildMenu } from "./BuildMenu";
|
import { BuildMenu } from "./BuildMenu";
|
||||||
import { consolex } from "../../../../core/Consolex";
|
import { consolex } from "../../../../core/Consolex";
|
||||||
import { GameView, PlayerView } from "../../../../core/GameView";
|
import { GameView, PlayerView } from "../../../../core/GameView";
|
||||||
|
import { TileRef } from "../../../../core/game/GameMap";
|
||||||
|
|
||||||
|
|
||||||
enum Slot {
|
enum Slot {
|
||||||
@@ -54,7 +54,7 @@ export class RadialMenu implements Layer {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private eventBus: EventBus,
|
private eventBus: EventBus,
|
||||||
private game: GameView,
|
private g: GameView,
|
||||||
private transformHandler: TransformHandler,
|
private transformHandler: TransformHandler,
|
||||||
private clientID: ClientID,
|
private clientID: ClientID,
|
||||||
private emojiTable: EmojiTable,
|
private emojiTable: EmojiTable,
|
||||||
@@ -70,10 +70,10 @@ export class RadialMenu implements Layer {
|
|||||||
if (clickedCell == null) {
|
if (clickedCell == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!this.game.isOnMap(clickedCell)) {
|
if (!this.g.isValidCoord(clickedCell.x, clickedCell.y)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const p = this.game.playerByClientID(this.clientID)
|
const p = this.g.playerByClientID(this.clientID)
|
||||||
if (p == null) {
|
if (p == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -233,19 +233,19 @@ export class RadialMenu implements Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.clickedCell = this.transformHandler.screenToWorldCoordinates(event.x, event.y)
|
this.clickedCell = this.transformHandler.screenToWorldCoordinates(event.x, event.y)
|
||||||
if (!this.game.isOnMap(this.clickedCell)) {
|
if (!this.g.isValidCoord(this.clickedCell.x, this.clickedCell.y)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const tile = this.game.tile(this.clickedCell)
|
const tile = this.g.ref(this.clickedCell.x, this.clickedCell.y)
|
||||||
|
|
||||||
if (this.game.inSpawnPhase()) {
|
if (this.g.inSpawnPhase()) {
|
||||||
if (tile.terrain().isLand() && !tile.hasOwner()) {
|
if (this.g.isLand(tile) && !this.g.hasOwner(tile)) {
|
||||||
this.enableCenterButton(true)
|
this.enableCenterButton(true)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const myPlayer = this.game.playerViews().find(p => p.clientID() == this.clientID)
|
const myPlayer = this.g.playerViews().find(p => p.clientID() == this.clientID)
|
||||||
if (!myPlayer) {
|
if (!myPlayer) {
|
||||||
consolex.warn('my player not found')
|
consolex.warn('my player not found')
|
||||||
return
|
return
|
||||||
@@ -255,13 +255,13 @@ export class RadialMenu implements Layer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private handlePlayerActions(myPlayer: PlayerView, actions: PlayerActions, tile: Tile) {
|
private handlePlayerActions(myPlayer: PlayerView, actions: PlayerActions, tile: TileRef) {
|
||||||
this.activateMenuElement(Slot.Build, "#ebe250", buildIcon, () => {
|
this.activateMenuElement(Slot.Build, "#ebe250", buildIcon, () => {
|
||||||
this.buildMenu.showMenu(myPlayer, this.clickedCell)
|
this.buildMenu.showMenu(myPlayer, this.clickedCell)
|
||||||
})
|
})
|
||||||
if (actions.interaction?.canSendEmoji) {
|
if (actions.interaction?.canSendEmoji) {
|
||||||
this.activateMenuElement(Slot.Emoji, "#00a6a4", emojiIcon, () => {
|
this.activateMenuElement(Slot.Emoji, "#00a6a4", emojiIcon, () => {
|
||||||
const target = tile.owner() == myPlayer ? AllPlayers : (tile.owner() as Player)
|
const target = this.g.owner(tile) == myPlayer ? AllPlayers : (this.g.owner(tile) as Player)
|
||||||
this.emojiTable.onEmojiClicked = (emoji: string) => {
|
this.emojiTable.onEmojiClicked = (emoji: string) => {
|
||||||
this.emojiTable.hideTable()
|
this.emojiTable.hideTable()
|
||||||
this.eventBus.emit(new SendEmojiIntentEvent(target, emoji))
|
this.eventBus.emit(new SendEmojiIntentEvent(target, emoji))
|
||||||
@@ -274,7 +274,7 @@ export class RadialMenu implements Layer {
|
|||||||
this.activateMenuElement(Slot.Boat, "#3f6ab1", boatIcon, () => {
|
this.activateMenuElement(Slot.Boat, "#3f6ab1", boatIcon, () => {
|
||||||
this.eventBus.emit(
|
this.eventBus.emit(
|
||||||
new SendBoatAttackIntentEvent(
|
new SendBoatAttackIntentEvent(
|
||||||
tile.owner().id(),
|
this.g.owner(tile).id(),
|
||||||
this.clickedCell,
|
this.clickedCell,
|
||||||
this.uiState.attackRatio * myPlayer.troops()
|
this.uiState.attackRatio * myPlayer.troops()
|
||||||
)
|
)
|
||||||
@@ -285,10 +285,10 @@ export class RadialMenu implements Layer {
|
|||||||
this.enableCenterButton(true)
|
this.enableCenterButton(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tile.hasOwner()) {
|
if (!this.g.hasOwner(tile)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const other = tile.owner() as Player
|
const other = this.g.owner(tile) as Player
|
||||||
|
|
||||||
|
|
||||||
if (actions?.interaction.canDonate) {
|
if (actions?.interaction.canDonate) {
|
||||||
@@ -351,13 +351,13 @@ export class RadialMenu implements Layer {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
consolex.log('Center button clicked');
|
consolex.log('Center button clicked');
|
||||||
const clicked = this.game.tile(this.clickedCell)
|
const clicked = this.g.ref(this.clickedCell.x, this.clickedCell.y)
|
||||||
if (this.game.inSpawnPhase()) {
|
if (this.g.inSpawnPhase()) {
|
||||||
this.eventBus.emit(new SendSpawnIntentEvent(this.clickedCell))
|
this.eventBus.emit(new SendSpawnIntentEvent(this.clickedCell))
|
||||||
} else {
|
} else {
|
||||||
const myPlayer = this.game.players().find(p => p.clientID() == this.clientID)
|
const myPlayer = this.g.players().find(p => p.clientID() == this.clientID)
|
||||||
if (myPlayer != null && clicked.owner() != myPlayer) {
|
if (myPlayer != null && this.g.owner(clicked) != myPlayer) {
|
||||||
this.eventBus.emit(new SendAttackIntentEvent(clicked.owner().id(), this.uiState.attackRatio * myPlayer.troops()))
|
this.eventBus.emit(new SendAttackIntentEvent(this.g.owner(clicked).id(), this.uiState.attackRatio * myPlayer.troops()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.hideRadialMenu();
|
this.hideRadialMenu();
|
||||||
|
|||||||
+29
-28
@@ -4,17 +4,18 @@ import { getConfig } from "./configuration/Config";
|
|||||||
import { EventBus } from "./EventBus";
|
import { EventBus } from "./EventBus";
|
||||||
import { Executor } from "./execution/ExecutionManager";
|
import { Executor } from "./execution/ExecutionManager";
|
||||||
import { WinCheckExecution } from "./execution/WinCheckExecution";
|
import { WinCheckExecution } from "./execution/WinCheckExecution";
|
||||||
import { Cell, DisplayMessageUpdate, Game, GameUpdateType, MessageType, MutableGame, MutableTile, NameViewData, Player, PlayerActions, PlayerID, PlayerProfile, Tile, TileUpdate, UnitType, UnitUpdate } from "./game/Game";
|
import { Cell, DisplayMessageUpdate, Game, GameUpdateType, MessageType, MutableGame, NameViewData, Player, PlayerActions, PlayerID, PlayerProfile, UnitType } from "./game/Game";
|
||||||
import { createGame } from "./game/GameImpl";
|
import { createGame } from "./game/GameImpl";
|
||||||
import { loadTerrainMap } from "./game/TerrainMapLoader";
|
import { loadTerrainMap as loadGameMap } from "./game/TerrainMapLoader";
|
||||||
import { GameConfig, Turn } from "./Schemas";
|
import { GameConfig, Turn } from "./Schemas";
|
||||||
import { and, bfs, dist, targetTransportTile } from "./Util";
|
import { GameUpdateViewData} from "./GameView";
|
||||||
import { GameUpdateViewData, packTileData } from "./GameView";
|
import { andFN, manhattanDistFN, TileRef } from "./game/GameMap";
|
||||||
|
import { targetTransportTile } from "./Util";
|
||||||
|
|
||||||
export async function createGameRunner(gameID: string, gameConfig: GameConfig, callBack: (gu: GameUpdateViewData) => void): Promise<GameRunner> {
|
export async function createGameRunner(gameID: string, gameConfig: GameConfig, callBack: (gu: GameUpdateViewData) => void): Promise<GameRunner> {
|
||||||
const config = getConfig(gameConfig)
|
const config = getConfig(gameConfig)
|
||||||
const terrainMap = await loadTerrainMap(gameConfig.gameMap);
|
const gameMap = await loadGameMap(gameConfig.gameMap);
|
||||||
const game = createGame(terrainMap.gameMap, terrainMap.miniGameMap, terrainMap.nationMap, config)
|
const game = createGame(gameMap.gameMap, gameMap.miniGameMap, gameMap.nationMap, config)
|
||||||
const gr = new GameRunner(game as MutableGame, new Executor(game, gameID), callBack)
|
const gr = new GameRunner(game as MutableGame, new Executor(game, gameID), callBack)
|
||||||
gr.init()
|
gr.init()
|
||||||
return gr
|
return gr
|
||||||
@@ -69,12 +70,12 @@ export class GameRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Many tiles are updated to pack it into an array
|
// Many tiles are updated to pack it into an array
|
||||||
const packedTileUpdates = updates[GameUpdateType.Tile].map(u => packTileData(u as TileUpdate))
|
const packedTileUpdates = updates[GameUpdateType.Tile].map(u => u.update)
|
||||||
updates[GameUpdateType.Tile] = []
|
updates[GameUpdateType.Tile] = []
|
||||||
|
|
||||||
this.callBack({
|
this.callBack({
|
||||||
tick: this.game.ticks(),
|
tick: this.game.ticks(),
|
||||||
packedTileUpdates: packedTileUpdates,
|
packedTileUpdates: new BigUint64Array(packedTileUpdates),
|
||||||
updates: updates,
|
updates: updates,
|
||||||
playerNameViewData: this.playerViewData
|
playerNameViewData: this.playerViewData
|
||||||
})
|
})
|
||||||
@@ -83,15 +84,15 @@ export class GameRunner {
|
|||||||
|
|
||||||
public playerActions(playerID: PlayerID, x: number, y: number): PlayerActions {
|
public playerActions(playerID: PlayerID, x: number, y: number): PlayerActions {
|
||||||
const player = this.game.player(playerID)
|
const player = this.game.player(playerID)
|
||||||
const tile = this.game.tile(new Cell(x, y))
|
const tile = this.game.ref(x, y)
|
||||||
const actions = {
|
const actions = {
|
||||||
canBoat: this.canBoat(player, tile),
|
canBoat: this.canBoat(player, tile),
|
||||||
canAttack: this.canAttack(player, tile),
|
canAttack: this.canAttack(player, tile),
|
||||||
buildableUnits: Object.values(UnitType).filter(ut => player.canBuild(ut, tile) != false)
|
buildableUnits: Object.values(UnitType).filter(ut => player.canBuild(ut, tile) != false)
|
||||||
} as PlayerActions
|
} as PlayerActions
|
||||||
|
|
||||||
if (tile.hasOwner()) {
|
if (this.game.hasOwner(tile)) {
|
||||||
const other = tile.owner() as Player
|
const other = this.game.owner(tile) as Player
|
||||||
actions.interaction = {
|
actions.interaction = {
|
||||||
sharedBorder: player.sharesBorderWith(other),
|
sharedBorder: player.sharesBorderWith(other),
|
||||||
canSendEmoji: player.canSendEmoji(other),
|
canSendEmoji: player.canSendEmoji(other),
|
||||||
@@ -120,25 +121,25 @@ export class GameRunner {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private canBoat(myPlayer: Player, tile: Tile): boolean {
|
private canBoat(myPlayer: Player, tile: TileRef): boolean {
|
||||||
const other = tile.owner()
|
const other = this.game.owner(tile)
|
||||||
if (myPlayer.units(UnitType.TransportShip).length >= this.game.config().boatMaxNumber()) {
|
if (myPlayer.units(UnitType.TransportShip).length >= this.game.config().boatMaxNumber()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let myPlayerBordersOcean = false
|
let myPlayerBordersOcean = false
|
||||||
for (const bt of myPlayer.borderTiles()) {
|
for (const bt of myPlayer.borderTiles()) {
|
||||||
if (bt.terrain().isOceanShore()) {
|
if (this.game.isOceanShore(bt)) {
|
||||||
myPlayerBordersOcean = true
|
myPlayerBordersOcean = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let otherPlayerBordersOcean = false
|
let otherPlayerBordersOcean = false
|
||||||
if (!tile.hasOwner()) {
|
if (!this.game.hasOwner(tile)) {
|
||||||
otherPlayerBordersOcean = true
|
otherPlayerBordersOcean = true
|
||||||
} else {
|
} else {
|
||||||
for (const bt of (other as Player).borderTiles()) {
|
for (const bt of (other as Player).borderTiles()) {
|
||||||
if (bt.terrain().isOceanShore()) {
|
if (this.game.isOceanShore(bt)) {
|
||||||
otherPlayerBordersOcean = true
|
otherPlayerBordersOcean = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -150,8 +151,8 @@ export class GameRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nearOcean = false
|
let nearOcean = false
|
||||||
for (const t of bfs(tile, and(t => t.owner() == tile.owner() && t.terrain().isLand(), dist(tile, 25)))) {
|
for (const t of this.game.bfs(tile, andFN((gm, t) => gm.ownerID(t) == gm.ownerID(tile) && gm.isLand(t), manhattanDistFN(tile, 25)))) {
|
||||||
if (t.terrain().isOceanShore()) {
|
if (this.game.isOceanShore(t)) {
|
||||||
nearOcean = true
|
nearOcean = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -161,7 +162,7 @@ export class GameRunner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (myPlayerBordersOcean && otherPlayerBordersOcean) {
|
if (myPlayerBordersOcean && otherPlayerBordersOcean) {
|
||||||
const dst = targetTransportTile(this.game.width(), tile)
|
const dst = targetTransportTile(this.game, tile)
|
||||||
if (dst != null) {
|
if (dst != null) {
|
||||||
if (myPlayer.canBuild(UnitType.TransportShip, dst)) {
|
if (myPlayer.canBuild(UnitType.TransportShip, dst)) {
|
||||||
return true
|
return true
|
||||||
@@ -170,24 +171,24 @@ export class GameRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private canAttack(myPlayer: Player, tile: Tile): boolean {
|
private canAttack(myPlayer: Player, tile: TileRef): boolean {
|
||||||
if (tile.owner() == myPlayer) {
|
if (this.game.owner(tile) == myPlayer) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// TODO: fix event bus
|
// TODO: fix event bus
|
||||||
if (tile.owner().isPlayer() && myPlayer.isAlliedWith(tile.owner() as Player)) {
|
if (this.game.hasOwner(tile) && myPlayer.isAlliedWith(this.game.owner(tile) as Player)) {
|
||||||
// this.eventBus.emit(new DisplayMessageEvent("Cannot attack ally", MessageType.WARN))
|
// this.eventBus.emit(new DisplayMessageEvent("Cannot attack ally", MessageType.WARN))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (!tile.terrain().isLand()) {
|
if (!this.game.isLand(tile)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (tile.hasOwner()) {
|
if (this.game.hasOwner(tile)) {
|
||||||
return myPlayer.sharesBorderWith(tile.owner())
|
return myPlayer.sharesBorderWith(this.game.owner(tile))
|
||||||
} else {
|
} else {
|
||||||
for (const t of bfs(tile, and(t => !t.hasOwner() && t.terrain().isLand(), dist(tile, 200)))) {
|
for (const t of this.game.bfs(tile, andFN((gm, t) => !gm.hasOwner(t) && gm.isLand(t), manhattanDistFN(tile, 200)))) {
|
||||||
for (const n of t.neighbors()) {
|
for (const n of this.game.neighbors(t)) {
|
||||||
if (n.owner() == myPlayer) {
|
if (this.game.owner(n) == myPlayer) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-146
@@ -1,70 +1,10 @@
|
|||||||
import { GameUpdates, GameUpdateType, MapPos, MessageType, NameViewData, Player, PlayerActions, PlayerProfile, PlayerUpdate, Tile, TileUpdate, Unit, UnitUpdate } from './game/Game';
|
import { GameUpdates, GameUpdateType, MapPos, MessageType, NameViewData, Player, PlayerActions, PlayerProfile, PlayerUpdate, Unit, UnitUpdate } from './game/Game';
|
||||||
import { Config } from "./configuration/Config";
|
import { Config } from "./configuration/Config";
|
||||||
import { Alliance, AllianceRequest, AllPlayers, Cell, DefenseBonus, EmojiMessage, Execution, ExecutionView, Game, Gold, MutableTile, Nation, PlayerID, PlayerInfo, PlayerType, Relation, TerrainMap, TerrainTile, TerrainType, TerraNullius, Tick, UnitInfo, UnitType } from "./game/Game";
|
import { Alliance, AllianceRequest, AllPlayers, Cell, DefenseBonus, EmojiMessage, Execution, ExecutionView, Game, Gold, Nation, PlayerID, PlayerInfo, PlayerType, Relation, TerrainType, TerraNullius, Tick, UnitInfo, UnitType } from "./game/Game";
|
||||||
import { ClientID } from "./Schemas";
|
import { ClientID } from "./Schemas";
|
||||||
import { TerraNulliusImpl } from './game/TerraNulliusImpl';
|
import { TerraNulliusImpl } from './game/TerraNulliusImpl';
|
||||||
import { WorkerClient } from './worker/WorkerClient';
|
import { WorkerClient } from './worker/WorkerClient';
|
||||||
import { GameMapImpl, TileRef } from './game/GameMap';
|
import { GameMap, GameMapImpl, TileRef, TileUpdate } from './game/GameMap';
|
||||||
|
|
||||||
|
|
||||||
export class TileView {
|
|
||||||
|
|
||||||
private _neighbors: TileView[] = []
|
|
||||||
|
|
||||||
constructor(private game: GameView, public data: TileUpdate, private _terrain: TerrainTile) { }
|
|
||||||
|
|
||||||
ref(): TileRef {
|
|
||||||
if (!this.data) { return 0 }
|
|
||||||
|
|
||||||
return this.data.pos.x * this.game.width() + this.data.pos.y
|
|
||||||
}
|
|
||||||
type(): TerrainType {
|
|
||||||
return this._terrain.type()
|
|
||||||
}
|
|
||||||
owner(): PlayerView | TerraNullius {
|
|
||||||
if (!this.hasOwner()) {
|
|
||||||
return new TerraNulliusImpl()
|
|
||||||
}
|
|
||||||
return this.game.playerBySmallID(this.data?.ownerID)
|
|
||||||
}
|
|
||||||
hasOwner(): boolean {
|
|
||||||
return this.data?.ownerID !== undefined && this.data.ownerID !== 0;
|
|
||||||
}
|
|
||||||
isBorder(): boolean {
|
|
||||||
for (const n of this.neighbors()) {
|
|
||||||
if (n.data?.ownerID != this.data?.ownerID) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
isBorderUpdated(): boolean {
|
|
||||||
return this.data.isBorder
|
|
||||||
}
|
|
||||||
cell(): Cell {
|
|
||||||
return this._terrain.cell()
|
|
||||||
}
|
|
||||||
hasFallout(): boolean {
|
|
||||||
return this.data?.hasFallout
|
|
||||||
}
|
|
||||||
terrain(): TerrainTile {
|
|
||||||
return this._terrain
|
|
||||||
}
|
|
||||||
|
|
||||||
neighbors(): TileView[] {
|
|
||||||
if (this._neighbors.length == 0) {
|
|
||||||
this._neighbors = this._terrain.neighbors().map(t => this.game.tile(t.cell()))
|
|
||||||
}
|
|
||||||
return this._neighbors
|
|
||||||
}
|
|
||||||
|
|
||||||
hasDefenseBonus(): boolean {
|
|
||||||
return this.data?.hasDefenseBonus ?? false
|
|
||||||
}
|
|
||||||
cost(): number {
|
|
||||||
return this._terrain.cost()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnitView implements Unit {
|
export class UnitView implements Unit {
|
||||||
public _wasUpdated = true
|
public _wasUpdated = true
|
||||||
@@ -78,15 +18,15 @@ export class UnitView implements Unit {
|
|||||||
return this._wasUpdated
|
return this._wasUpdated
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTiles(): Tile[] {
|
lastTiles(): TileRef[] {
|
||||||
return this.lastPos.map(pos => this.gameView.tile(new Cell(pos.x, pos.y)))
|
return this.lastPos.map(pos => this.gameView.ref(pos.x, pos.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTile(): Tile {
|
lastTile(): TileRef {
|
||||||
if (this.lastPos.length == 0) {
|
if (this.lastPos.length == 0) {
|
||||||
return this.gameView.tile(new Cell(this.data.pos.x, this.data.pos.y))
|
return this.gameView.ref(this.data.pos.x, this.data.pos.y)
|
||||||
}
|
}
|
||||||
return this.gameView.tile(new Cell(this.lastPos[0].x, this.lastPos[0].y))
|
return this.gameView.ref(this.lastPos[0].x, this.lastPos[0].y)
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data: UnitUpdate) {
|
update(data: UnitUpdate) {
|
||||||
@@ -105,8 +45,8 @@ export class UnitView implements Unit {
|
|||||||
troops(): number {
|
troops(): number {
|
||||||
return this.data.troops
|
return this.data.troops
|
||||||
}
|
}
|
||||||
tile(): Tile {
|
tile(): TileRef {
|
||||||
return this.gameView.tile(new Cell(this.data.pos.x, this.data.pos.y))
|
return this.gameView.ref(this.data.pos.x, this.data.pos.y)
|
||||||
}
|
}
|
||||||
owner(): PlayerView {
|
owner(): PlayerView {
|
||||||
return this.gameView.playerBySmallID(this.data.ownerID)
|
return this.gameView.playerBySmallID(this.data.ownerID)
|
||||||
@@ -125,12 +65,12 @@ export class UnitView implements Unit {
|
|||||||
export class PlayerView implements Player {
|
export class PlayerView implements Player {
|
||||||
|
|
||||||
constructor(private game: GameView, public data: PlayerUpdate, public nameData: NameViewData) { }
|
constructor(private game: GameView, public data: PlayerUpdate, public nameData: NameViewData) { }
|
||||||
borderTiles(): ReadonlySet<Tile> {
|
borderTiles(): ReadonlySet<TileRef> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
async actions(tile: Tile): Promise<PlayerActions> {
|
async actions(tile: TileRef): Promise<PlayerActions> {
|
||||||
return this.game.worker.playerInteraction(this.id(), tile)
|
return this.game.worker.playerInteraction(this.id(), this.game.x(tile), this.game.y(tile))
|
||||||
}
|
}
|
||||||
|
|
||||||
nameLocation(): NameViewData {
|
nameLocation(): NameViewData {
|
||||||
@@ -192,9 +132,6 @@ export class PlayerView implements Player {
|
|||||||
allianceWith(other: Player): Alliance | null {
|
allianceWith(other: Player): Alliance | null {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
borderTileRefs(): ReadonlySet<TileRef> {
|
|
||||||
return new Set()
|
|
||||||
}
|
|
||||||
units(...types: UnitType[]): Unit[] {
|
units(...types: UnitType[]): Unit[] {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -246,7 +183,7 @@ export class PlayerView implements Player {
|
|||||||
canDonate(recipient: Player): boolean {
|
canDonate(recipient: Player): boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
canBuild(type: UnitType, targetTile: Tile): Tile | false {
|
canBuild(type: UnitType, targetTile: TileRef): TileRef | false {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
info(): PlayerInfo {
|
info(): PlayerInfo {
|
||||||
@@ -257,31 +194,21 @@ export class PlayerView implements Player {
|
|||||||
export interface GameUpdateViewData {
|
export interface GameUpdateViewData {
|
||||||
tick: number
|
tick: number
|
||||||
updates: GameUpdates
|
updates: GameUpdates
|
||||||
packedTileUpdates: Uint16Array[]
|
packedTileUpdates: BigUint64Array
|
||||||
playerNameViewData: Record<number, NameViewData>
|
playerNameViewData: Record<number, NameViewData>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GameView {
|
export class GameView implements GameMap {
|
||||||
private lastUpdate: GameUpdateViewData
|
private lastUpdate: GameUpdateViewData
|
||||||
private tiles: TileView[][] = []
|
|
||||||
private smallIDToID = new Map<number, PlayerID>()
|
private smallIDToID = new Map<number, PlayerID>()
|
||||||
private _players = new Map<PlayerID, PlayerView>()
|
private _players = new Map<PlayerID, PlayerView>()
|
||||||
private _units = new Map<number, UnitView>()
|
private _units = new Map<number, UnitView>()
|
||||||
private updatedTiles: TileView[] = []
|
private updatedTiles: TileRef[] = []
|
||||||
|
|
||||||
constructor(public worker: WorkerClient, private _config: Config, private _terrainMap: TerrainMap) {
|
constructor(public worker: WorkerClient, private _config: Config, private _map: GameMap) {
|
||||||
// Initialize the 2D array
|
|
||||||
this.tiles = Array(_terrainMap.width()).fill(null).map(() => Array(_terrainMap.height()).fill(null));
|
|
||||||
|
|
||||||
// Fill the array with new TileView objects
|
|
||||||
for (let x = 0; x < _terrainMap.width(); x++) {
|
|
||||||
for (let y = 0; y < _terrainMap.height(); y++) {
|
|
||||||
this.tiles[x][y] = new TileView(this, null, _terrainMap.terrain(new Cell(x, y)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.lastUpdate = {
|
this.lastUpdate = {
|
||||||
tick: 0,
|
tick: 0,
|
||||||
packedTileUpdates: [],
|
packedTileUpdates: new BigUint64Array([]),
|
||||||
// TODO: make this empty map instead of null?
|
// TODO: make this empty map instead of null?
|
||||||
updates: null,
|
updates: null,
|
||||||
playerNameViewData: {},
|
playerNameViewData: {},
|
||||||
@@ -295,12 +222,9 @@ export class GameView {
|
|||||||
public update(gu: GameUpdateViewData) {
|
public update(gu: GameUpdateViewData) {
|
||||||
this.lastUpdate = gu
|
this.lastUpdate = gu
|
||||||
|
|
||||||
const updated = new Set<MapPos>()
|
this.lastUpdate.packedTileUpdates.forEach(tu => {
|
||||||
this.lastUpdate.packedTileUpdates.map(tu => unpackTileData(tu)).forEach(tu => {
|
this.updatedTiles.push(this.updateTile(tu))
|
||||||
this.tiles[tu.pos.x][tu.pos.y].data = tu
|
|
||||||
updated.add(tu.pos)
|
|
||||||
})
|
})
|
||||||
this.updatedTiles = Array.from(updated).map(pos => this.tiles[pos.x][pos.y])
|
|
||||||
|
|
||||||
gu.updates[GameUpdateType.Player].forEach((pu) => {
|
gu.updates[GameUpdateType.Player].forEach((pu) => {
|
||||||
this.smallIDToID.set(pu.smallID, pu.id);
|
this.smallIDToID.set(pu.smallID, pu.id);
|
||||||
@@ -324,7 +248,7 @@ export class GameView {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
recentlyUpdatedTiles(): TileView[] {
|
recentlyUpdatedTiles(): TileRef[] {
|
||||||
return this.updatedTiles
|
return this.updatedTiles
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,26 +283,11 @@ export class GameView {
|
|||||||
players(): Player[] {
|
players(): Player[] {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
tile(cell: Cell): TileView {
|
|
||||||
return this.tiles[cell.x][cell.y]
|
owner(tile: TileRef): PlayerView {
|
||||||
}
|
return this.playerBySmallID(this.ownerID(tile))
|
||||||
isOnMap(cell: Cell): boolean {
|
|
||||||
return this._terrainMap.isOnMap(cell)
|
|
||||||
}
|
|
||||||
width(): number {
|
|
||||||
return this._terrainMap.width()
|
|
||||||
}
|
|
||||||
height(): number {
|
|
||||||
return this._terrainMap.height()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
forEachTile(fn: (tile: Tile) => void): void {
|
|
||||||
for (let x = 0; x < this._terrainMap.width(); x++) {
|
|
||||||
for (let y = 0; y < this._terrainMap.height(); y++) {
|
|
||||||
fn(this.tile(new Cell(x, y)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ticks(): Tick {
|
ticks(): Tick {
|
||||||
return this.lastUpdate.tick
|
return this.lastUpdate.tick
|
||||||
}
|
}
|
||||||
@@ -394,35 +303,37 @@ export class GameView {
|
|||||||
unitInfo(type: UnitType): UnitInfo {
|
unitInfo(type: UnitType): UnitInfo {
|
||||||
return this._config.unitInfo(type)
|
return this._config.unitInfo(type)
|
||||||
}
|
}
|
||||||
terrainMap(): TerrainMap {
|
|
||||||
return this._terrainMap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function packTileData(tile: TileUpdate): Uint16Array {
|
ref(x: number, y: number): TileRef { return this._map.ref(x, y) }
|
||||||
const packed = new Uint16Array(4);
|
x(ref: TileRef): number { return this._map.x(ref) }
|
||||||
packed[0] = tile.pos.x;
|
y(ref: TileRef): number { return this._map.y(ref) }
|
||||||
packed[1] = tile.pos.y;
|
cell(ref: TileRef): Cell { return this._map.cell(ref) }
|
||||||
packed[2] = tile.ownerID;
|
width(): number { return this._map.width() }
|
||||||
|
height(): number { return this._map.height() }
|
||||||
// Pack booleans into bits
|
numLandTiles(): number { return this._map.numLandTiles() }
|
||||||
packed[3] = (tile.hasFallout ? 1 : 0) |
|
isValidCoord(x: number, y: number): boolean { return this._map.isValidCoord(x, y) }
|
||||||
(tile.hasDefenseBonus ? 2 : 0) |
|
isLand(ref: TileRef): boolean { return this._map.isLake(ref) }
|
||||||
(tile.isBorder ? 4 : 0)
|
isOceanShore(ref: TileRef): boolean { return this._map.isOceanShore(ref) }
|
||||||
|
isOcean(ref: TileRef): boolean { return this._map.isOcean(ref) }
|
||||||
return packed;
|
isShoreline(ref: TileRef): boolean { return this._map.isShoreline(ref) }
|
||||||
}
|
magnitude(ref: TileRef): number { return this._map.magnitude(ref) }
|
||||||
|
ownerID(ref: TileRef): number { return this._map.ownerID(ref) }
|
||||||
export function unpackTileData(packed: Uint16Array): TileUpdate {
|
hasOwner(ref: TileRef): boolean { return this._map.hasOwner(ref) }
|
||||||
return {
|
setOwnerID(ref: TileRef, playerId: number): void { return this._map.setOwnerID(ref, playerId) }
|
||||||
type: GameUpdateType.Tile,
|
hasFallout(ref: TileRef): boolean { return this._map.hasFallout(ref) }
|
||||||
pos: {
|
setFallout(ref: TileRef, value: boolean): void { return this._map.setFallout(ref, value) }
|
||||||
x: packed[0],
|
isBorder(ref: TileRef): boolean { return this._map.isBorder(ref) }
|
||||||
y: packed[1],
|
setBorder(ref: TileRef, value: boolean): void { return this._map.setBorder(ref, value) }
|
||||||
},
|
neighbors(ref: TileRef): TileRef[] { return this._map.neighbors(ref) }
|
||||||
ownerID: packed[2],
|
isWater(ref: TileRef): boolean { return this._map.isWater(ref) }
|
||||||
hasFallout: !!(packed[3] & 1),
|
isLake(ref: TileRef): boolean { return this._map.isLake(ref) }
|
||||||
hasDefenseBonus: !!(packed[3] & 2),
|
isShore(ref: TileRef): boolean { return this._map.isShore(ref) }
|
||||||
isBorder: !!(packed[3] & 4),
|
cost(ref: TileRef): number { return this._map.cost(ref) }
|
||||||
};
|
terrainType(ref: TileRef): TerrainType { return this._map.terrainType(ref) }
|
||||||
|
forEachTile(fn: (tile: TileRef) => void): void { return this._map.forEachTile(fn) }
|
||||||
|
manhattanDist(c1: TileRef, c2: TileRef): number { return this._map.manhattanDist(c1, c2) }
|
||||||
|
euclideanDist(c1: TileRef, c2: TileRef): number { return this._map.euclideanDist(c1, c2) }
|
||||||
|
bfs(tile: TileRef, filter: (gm: GameMap, tile: TileRef) => boolean): Set<TileRef> { return this._map.bfs(tile, filter) }
|
||||||
|
toTileUpdate(tile: TileRef): bigint { return this._map.toTileUpdate(tile) }
|
||||||
|
updateTile(tu: TileUpdate): TileRef { return this._map.updateTile(tu) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,10 +100,6 @@ export const AttackIntentSchema = BaseIntentSchema.extend({
|
|||||||
attackerID: ID,
|
attackerID: ID,
|
||||||
targetID: ID.nullable(),
|
targetID: ID.nullable(),
|
||||||
troops: z.number().nullable(),
|
troops: z.number().nullable(),
|
||||||
sourceX: z.number().nullable(),
|
|
||||||
sourceY: z.number().nullable(),
|
|
||||||
targetX: z.number().nullable(),
|
|
||||||
targetY: z.number().nullable()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SpawnIntentSchema = BaseIntentSchema.extend({
|
export const SpawnIntentSchema = BaseIntentSchema.extend({
|
||||||
|
|||||||
+32
-66
@@ -3,22 +3,14 @@ import twemoji from 'twemoji';
|
|||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
|
|
||||||
import { Cell, Game, Player, TerraNullius, Tile, Unit } from "./game/Game";
|
import { Cell, Game, MutableGame, Player, Unit } from "./game/Game";
|
||||||
import { number } from 'zod';
|
|
||||||
import { GameConfig, GameID, GameRecord, PlayerRecord, Turn } from './Schemas';
|
import { GameConfig, GameID, GameRecord, PlayerRecord, Turn } from './Schemas';
|
||||||
import { customAlphabet, nanoid } from 'nanoid';
|
import { customAlphabet, nanoid } from 'nanoid';
|
||||||
import { GameView } from './GameView';
|
import { andFN, GameMap, manhattanDistFN, TileRef } from './game/GameMap';
|
||||||
import { TileRef } from './game/GameMap';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function manhattanDist(c1: Cell, c2: Cell): number {
|
|
||||||
return Math.abs(c1.x - c2.x) + Math.abs(c1.y - c2.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function euclideanDist(c1: Cell, c2: Cell): number {
|
|
||||||
return Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function manhattanDistWrapped(c1: Cell, c2: Cell, width: number): number {
|
export function manhattanDistWrapped(c1: Cell, c2: Cell, width: number): number {
|
||||||
// Calculate x distance
|
// Calculate x distance
|
||||||
@@ -37,95 +29,69 @@ export function within(value: number, min: number, max: number): number {
|
|||||||
return Math.min(Math.max(value, min), max);
|
return Math.min(Math.max(value, min), max);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function euclDist(root: Tile, dist: number): (tile: Tile) => boolean {
|
|
||||||
return (n: Tile) => euclideanDist(root.cell(), n.cell()) <= dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dist(root: Tile, dist: number): (tile: Tile) => boolean {
|
export function distSort(gm: GameMap, target: TileRef): (a: TileRef, b: TileRef) => number {
|
||||||
return (n: Tile) => manhattanDist(root.cell(), n.cell()) <= dist;
|
return (a: TileRef, b: TileRef) => {
|
||||||
}
|
return gm.manhattanDist(a, target) - gm.manhattanDist(b, target);
|
||||||
|
|
||||||
export function distSort(target: Tile): (a: Tile, b: Tile) => number {
|
|
||||||
return (a: Tile, b: Tile) => {
|
|
||||||
return manhattanDist(a.cell(), target.cell()) - manhattanDist(b.cell(), target.cell());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function distSortUnit(target: Unit | Tile): (a: Unit, b: Unit) => number {
|
export function distSortUnit(gm: GameMap, target: Unit | TileRef): (a: Unit, b: Unit) => number {
|
||||||
const targetCell = ('tile' in target) ? target.tile().cell() : target.cell();
|
const targetRef = (typeof target === 'number') ? target : target.tile()
|
||||||
|
|
||||||
return (a: Unit, b: Unit) => {
|
return (a: Unit, b: Unit) => {
|
||||||
return manhattanDist(a.tile().cell(), targetCell) - manhattanDist(b.tile().cell(), targetCell);
|
return gm.manhattanDist(a.tile(), targetRef) - gm.manhattanDist(b.tile(), targetRef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function and(x: (tile: Tile) => boolean, y: (tile: Tile) => boolean): (tile: Tile) => boolean {
|
|
||||||
return (tile: Tile) => x(tile) && y(tile)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: refactor to new file
|
// TODO: refactor to new file
|
||||||
export function sourceDstOceanShore(game: GameView, src: Player, tile: Tile): [Tile | null, Tile | null] {
|
export function sourceDstOceanShore(gm: MutableGame, src: Player, tile: TileRef): [TileRef | null, TileRef | null] {
|
||||||
const dst = tile.owner()
|
const dst = gm.owner(tile)
|
||||||
let srcTile = closestOceanShoreFromPlayer(src, tile, game.width())
|
let srcTile = closestOceanShoreFromPlayer(gm, src, tile)
|
||||||
let dstTile: Tile | null = null
|
let dstTile: TileRef | null = null
|
||||||
if (dst.isPlayer()) {
|
if (dst.isPlayer()) {
|
||||||
dstTile = closestOceanShoreFromPlayer(dst as Player, tile, game.width())
|
dstTile = closestOceanShoreFromPlayer(gm, dst as Player, tile)
|
||||||
} else {
|
} else {
|
||||||
dstTile = closestOceanShoreTN(tile, 300)
|
dstTile = closestOceanShoreTN(gm, tile, 300)
|
||||||
}
|
}
|
||||||
return [srcTile, dstTile]
|
return [srcTile, dstTile]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function targetTransportTile(gameWidth: number, tile: Tile): Tile | null {
|
export function targetTransportTile(gm: Game, tile: TileRef): TileRef | null {
|
||||||
const dst = tile.owner()
|
const dst = gm.playerBySmallID(gm.ownerID(tile))
|
||||||
let dstTile: Tile | null = null
|
let dstTile: TileRef | null = null
|
||||||
if (dst.isPlayer()) {
|
if (dst.isPlayer()) {
|
||||||
dstTile = closestOceanShoreFromPlayer(dst as Player, tile, gameWidth)
|
dstTile = closestOceanShoreFromPlayer(gm, dst as Player, tile)
|
||||||
} else {
|
} else {
|
||||||
dstTile = closestOceanShoreTN(tile, 300)
|
dstTile = closestOceanShoreTN(gm, tile, 300)
|
||||||
}
|
}
|
||||||
return dstTile
|
return dstTile
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closestOceanShoreFromPlayer(player: Player, target: Tile, width: number): Tile | null {
|
export function closestOceanShoreFromPlayer(gm: GameMap, player: Player, target: TileRef): TileRef | null {
|
||||||
const shoreTiles = Array.from(player.borderTiles()).filter(t => t.terrain().isOceanShore())
|
const shoreTiles = Array.from(player.borderTiles()).filter(t => gm.isOceanShore(t))
|
||||||
if (shoreTiles.length == 0) {
|
if (shoreTiles.length == 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return shoreTiles.reduce((closest, current) => {
|
return shoreTiles.reduce((closest, current) => {
|
||||||
const closestDistance = manhattanDistWrapped(target.cell(), closest.cell(), width);
|
const closestDistance = manhattanDistWrapped(gm.cell(target), gm.cell(closest), gm.width());
|
||||||
const currentDistance = manhattanDistWrapped(target.cell(), current.cell(), width);
|
const currentDistance = manhattanDistWrapped(gm.cell(target), gm.cell(current), gm.width());
|
||||||
return currentDistance < closestDistance ? current : closest;
|
return currentDistance < closestDistance ? current : closest;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function closestOceanShoreTN(tile: Tile, searchDist: number): Tile {
|
function closestOceanShoreTN(gm: GameMap, tile: TileRef, searchDist: number): TileRef {
|
||||||
const tn = Array.from(bfs(tile, and(t => !t.hasOwner(), dist(tile, searchDist))))
|
const tn = Array.from(gm.bfs(tile, andFN((_, t) => !gm.hasOwner(t), manhattanDistFN(tile, searchDist))))
|
||||||
.filter(t => t.terrain().isOceanShore())
|
.filter(t => gm.isOceanShore(t))
|
||||||
.sort((a, b) => manhattanDist(tile.cell(), a.cell()) - manhattanDist(tile.cell(), b.cell()))
|
.sort((a, b) => gm.manhattanDist(tile, a) - gm.manhattanDist(tile, b))
|
||||||
if (tn.length == 0) {
|
if (tn.length == 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return tn[0]
|
return tn[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bfs(tile: Tile, filter: (tile: Tile) => boolean): Set<Tile> {
|
|
||||||
const seen = new Map<TileRef, Tile>()
|
|
||||||
const q: Tile[] = []
|
|
||||||
q.push(tile)
|
|
||||||
while (q.length > 0) {
|
|
||||||
const curr = q.pop()
|
|
||||||
seen.set(curr.ref(), curr)
|
|
||||||
for (const n of curr.neighbors()) {
|
|
||||||
if (!seen.has(n.ref()) && filter(n)) {
|
|
||||||
q.push(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Set(seen.values())
|
|
||||||
}
|
|
||||||
|
|
||||||
export function simpleHash(str: string): number {
|
export function simpleHash(str: string): number {
|
||||||
let hash = 0;
|
let hash = 0;
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
@@ -136,11 +102,11 @@ export function simpleHash(str: string): number {
|
|||||||
return Math.abs(hash);
|
return Math.abs(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateBoundingBox(borderTiles: ReadonlySet<Tile>): { min: Cell; max: Cell } {
|
export function calculateBoundingBox(gm: GameMap, borderTiles: ReadonlySet<TileRef>): { min: Cell; max: Cell } {
|
||||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||||
|
|
||||||
borderTiles.forEach((tile: Tile) => {
|
borderTiles.forEach((tile: TileRef) => {
|
||||||
const cell = tile.cell();
|
const cell = gm.cell(tile);
|
||||||
minX = Math.min(minX, cell.x);
|
minX = Math.min(minX, cell.x);
|
||||||
minY = Math.min(minY, cell.y);
|
minY = Math.min(minY, cell.y);
|
||||||
maxX = Math.max(maxX, cell.x);
|
maxX = Math.max(maxX, cell.x);
|
||||||
@@ -150,8 +116,8 @@ export function calculateBoundingBox(borderTiles: ReadonlySet<Tile>): { min: Cel
|
|||||||
return { min: new Cell(minX, minY), max: new Cell(maxX, maxY) }
|
return { min: new Cell(minX, minY), max: new Cell(maxX, maxY) }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calculateBoundingBoxCenter(borderTiles: ReadonlySet<Tile>): Cell {
|
export function calculateBoundingBoxCenter(gm: GameMap, borderTiles: ReadonlySet<TileRef>): Cell {
|
||||||
const { min, max } = calculateBoundingBox(borderTiles)
|
const { min, max } = calculateBoundingBox(gm, borderTiles)
|
||||||
return new Cell(
|
return new Cell(
|
||||||
min.x + Math.floor((max.x - min.x) / 2),
|
min.x + Math.floor((max.x - min.x) / 2),
|
||||||
min.y + Math.floor((max.y - min.y) / 2)
|
min.y + Math.floor((max.y - min.y) / 2)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Difficulty, GameType, Gold, Player, PlayerID, PlayerInfo, TerraNullius, Tick, Tile, Unit, UnitInfo, UnitType } from "../game/Game";
|
import { Difficulty, GameType, Gold, Player, PlayerID, PlayerInfo, TerraNullius, Tick, UnitInfo, UnitType } from "../game/Game";
|
||||||
import { Colord, colord } from "colord";
|
import { Colord, colord } from "colord";
|
||||||
import { preprodConfig } from "./PreprodConfig";
|
import { preprodConfig } from "./PreprodConfig";
|
||||||
import { prodConfig } from "./ProdConfig";
|
import { prodConfig } from "./ProdConfig";
|
||||||
@@ -6,6 +6,7 @@ import { consolex } from "../Consolex";
|
|||||||
import { GameConfig } from "../Schemas";
|
import { GameConfig } from "../Schemas";
|
||||||
import { DefaultConfig } from "./DefaultConfig";
|
import { DefaultConfig } from "./DefaultConfig";
|
||||||
import { DevConfig, DevServerConfig } from "./DevConfig";
|
import { DevConfig, DevServerConfig } from "./DevConfig";
|
||||||
|
import { GameMap, TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export enum GameEnv {
|
export enum GameEnv {
|
||||||
Dev,
|
Dev,
|
||||||
@@ -61,7 +62,7 @@ export interface Config {
|
|||||||
goldAdditionRate(player: Player): number
|
goldAdditionRate(player: Player): number
|
||||||
troopAdjustmentRate(player: Player): number
|
troopAdjustmentRate(player: Player): number
|
||||||
attackTilesPerTick(attckTroops: number, attacker: Player, defender: Player | TerraNullius, numAdjacentTilesWithEnemy: number): number
|
attackTilesPerTick(attckTroops: number, attacker: Player, defender: Player | TerraNullius, numAdjacentTilesWithEnemy: number): number
|
||||||
attackLogic(attackTroops: number, attacker: Player, defender: Player | TerraNullius, tileToConquer: Tile): {
|
attackLogic(gm: GameMap, attackTroops: number, attacker: Player, defender: Player | TerraNullius, tileToConquer: TileRef): {
|
||||||
attackerTroopLoss: number,
|
attackerTroopLoss: number,
|
||||||
defenderTroopLoss: number,
|
defenderTroopLoss: number,
|
||||||
tilesPerTickUsed: number
|
tilesPerTickUsed: number
|
||||||
@@ -81,7 +82,7 @@ export interface Config {
|
|||||||
donateCooldown(): Tick
|
donateCooldown(): Tick
|
||||||
defaultDonationAmount(sender: Player): number
|
defaultDonationAmount(sender: Player): number
|
||||||
unitInfo(type: UnitType): UnitInfo
|
unitInfo(type: UnitType): UnitInfo
|
||||||
tradeShipGold(src: Unit, dst: Unit): Gold
|
tradeShipGold(dist: number): Gold
|
||||||
tradeShipSpawnRate(): number
|
tradeShipSpawnRate(): number
|
||||||
defensePostRange(): number
|
defensePostRange(): number
|
||||||
defensePostDefenseBonus(): number
|
defensePostDefenseBonus(): number
|
||||||
@@ -94,7 +95,7 @@ export interface Theme {
|
|||||||
territoryColor(playerInfo: PlayerInfo): Colord;
|
territoryColor(playerInfo: PlayerInfo): Colord;
|
||||||
borderColor(playerInfo: PlayerInfo): Colord;
|
borderColor(playerInfo: PlayerInfo): Colord;
|
||||||
defendedBorderColor(playerInfo: PlayerInfo): Colord;
|
defendedBorderColor(playerInfo: PlayerInfo): Colord;
|
||||||
terrainColor(tile: Tile): Colord;
|
terrainColor(gm: GameMap, tile: TileRef): Colord;
|
||||||
backgroundColor(): Colord;
|
backgroundColor(): Colord;
|
||||||
falloutColor(): Colord
|
falloutColor(): Colord
|
||||||
font(): string;
|
font(): string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Difficulty, GameType, Gold, MutableTile, Player, PlayerInfo, PlayerType, TerrainType, TerraNullius, Tick, Tile, Unit, UnitInfo, UnitType } from "../game/Game";
|
import { Difficulty, GameType, Gold, Player, PlayerInfo, PlayerType, TerrainType, TerraNullius, Tick, UnitInfo, UnitType } from "../game/Game";
|
||||||
|
import { GameMap, TileRef } from "../game/GameMap";
|
||||||
import { GameConfig } from "../Schemas";
|
import { GameConfig } from "../Schemas";
|
||||||
import { assertNever, distSort, manhattanDist, simpleHash, within } from "../Util";
|
import { assertNever, within } from "../Util";
|
||||||
import { Config, ServerConfig, Theme } from "./Config";
|
import { Config, ServerConfig, Theme } from "./Config";
|
||||||
import { pastelTheme } from "./PastelTheme";
|
import { pastelTheme } from "./PastelTheme";
|
||||||
|
|
||||||
@@ -62,8 +63,7 @@ export class DefaultConfig implements Config {
|
|||||||
spawnNPCs(): boolean {
|
spawnNPCs(): boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
tradeShipGold(src: Unit, dst: Unit): Gold {
|
tradeShipGold(dist: number): Gold {
|
||||||
const dist = manhattanDist(src.tile().cell(), dst.tile().cell())
|
|
||||||
return 10000 + 100 * Math.pow(dist, 1.1)
|
return 10000 + 100 * Math.pow(dist, 1.1)
|
||||||
}
|
}
|
||||||
tradeShipSpawnRate(): number {
|
tradeShipSpawnRate(): number {
|
||||||
@@ -186,10 +186,10 @@ export class DefaultConfig implements Config {
|
|||||||
}
|
}
|
||||||
theme(): Theme { return pastelTheme; }
|
theme(): Theme { return pastelTheme; }
|
||||||
|
|
||||||
attackLogic(attackTroops: number, attacker: Player, defender: Player | TerraNullius, tileToConquer: MutableTile): { attackerTroopLoss: number; defenderTroopLoss: number; tilesPerTickUsed: number } {
|
attackLogic(gm: GameMap, attackTroops: number, attacker: Player, defender: Player | TerraNullius, tileToConquer: TileRef): { attackerTroopLoss: number; defenderTroopLoss: number; tilesPerTickUsed: number } {
|
||||||
let mag = 0
|
let mag = 0
|
||||||
let speed = 0
|
let speed = 0
|
||||||
const type = tileToConquer.terrain().type()
|
const type = gm.terrainType(tileToConquer)
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TerrainType.Plains:
|
case TerrainType.Plains:
|
||||||
mag = 80
|
mag = 80
|
||||||
@@ -209,7 +209,7 @@ export class DefaultConfig implements Config {
|
|||||||
// TODO
|
// TODO
|
||||||
// mag *= tileToConquer.defenseBonus(attacker)
|
// mag *= tileToConquer.defenseBonus(attacker)
|
||||||
// speed *= tileToConquer.defenseBonus(attacker)
|
// speed *= tileToConquer.defenseBonus(attacker)
|
||||||
if (tileToConquer.hasFallout()) {
|
if (gm.hasFallout(tileToConquer)) {
|
||||||
mag *= this.falloutDefenseModifier()
|
mag *= this.falloutDefenseModifier()
|
||||||
speed *= this.falloutDefenseModifier()
|
speed *= this.falloutDefenseModifier()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Colord, colord, random } from "colord";
|
import { Colord, colord, random } from "colord";
|
||||||
import { PlayerID, PlayerInfo, TerrainType, Tile } from "../game/Game";
|
import { Game, PlayerID, PlayerInfo, TerrainType } from "../game/Game";
|
||||||
import { Theme } from "./Config";
|
import { Theme } from "./Config";
|
||||||
import { time } from "console";
|
import { time } from "console";
|
||||||
import { PseudoRandom } from "../PseudoRandom";
|
import { PseudoRandom } from "../PseudoRandom";
|
||||||
import { simpleHash } from "../Util";
|
import { simpleHash } from "../Util";
|
||||||
|
import { GameMap, TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export const pastelTheme = new class implements Theme {
|
export const pastelTheme = new class implements Theme {
|
||||||
|
|
||||||
@@ -154,19 +155,19 @@ export const pastelTheme = new class implements Theme {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
terrainColor(tile: Tile): Colord {
|
terrainColor(gm: GameMap, tile: TileRef): Colord {
|
||||||
let mag = tile.terrain().magnitude()
|
let mag = gm.magnitude(tile)
|
||||||
if (tile.terrain().isShore()) {
|
if (gm.isShore(tile)) {
|
||||||
return this.shore
|
return this.shore
|
||||||
}
|
}
|
||||||
switch (tile.terrain().type()) {
|
switch (gm.terrainType(tile)) {
|
||||||
case TerrainType.Ocean:
|
case TerrainType.Ocean:
|
||||||
case TerrainType.Lake:
|
case TerrainType.Lake:
|
||||||
const w = this.water.rgba
|
const w = this.water.rgba
|
||||||
if (tile.terrain().isShorelineWater()) {
|
if (gm.isShoreline(tile) && gm.isWater(tile)) {
|
||||||
return this.shorelineWater
|
return this.shorelineWater
|
||||||
}
|
}
|
||||||
if (tile.terrain().magnitude() < 7) {
|
if (gm.magnitude(tile) < 7) {
|
||||||
return colord({
|
return colord({
|
||||||
r: Math.max(w.r - 7 + mag, 0),
|
r: Math.max(w.r - 7 + mag, 0),
|
||||||
g: Math.max(w.g - 7 + mag, 0),
|
g: Math.max(w.g - 7 + mag, 0),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
||||||
import { Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerType, TerrainType, TerraNullius, Tile } from "../game/Game";
|
import { Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerType, TerrainType, TerraNullius } from "../game/Game";
|
||||||
import { PseudoRandom } from "../PseudoRandom";
|
import { PseudoRandom } from "../PseudoRandom";
|
||||||
import { manhattanDist } from "../Util";
|
|
||||||
import { MessageType } from '../game/Game';
|
import { MessageType } from '../game/Game';
|
||||||
import { renderNumber } from "../../client/Utils";
|
import { renderNumber } from "../../client/Utils";
|
||||||
import { TileRef } from "../game/GameMap";
|
import { TileRef } from "../game/GameMap";
|
||||||
@@ -32,8 +31,7 @@ export class AttackExecution implements Execution {
|
|||||||
private troops: number | null,
|
private troops: number | null,
|
||||||
private _ownerID: PlayerID,
|
private _ownerID: PlayerID,
|
||||||
private _targetID: PlayerID | null,
|
private _targetID: PlayerID | null,
|
||||||
private sourceCell: Cell | null,
|
private sourceTile: TileRef | null,
|
||||||
private targetCell: Cell | null,
|
|
||||||
private removeTroops: boolean = true,
|
private removeTroops: boolean = true,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -51,8 +49,6 @@ export class AttackExecution implements Execution {
|
|||||||
}
|
}
|
||||||
this.mg = mg
|
this.mg = mg
|
||||||
|
|
||||||
this.targetCell = null
|
|
||||||
|
|
||||||
this._owner = mg.player(this._ownerID)
|
this._owner = mg.player(this._ownerID)
|
||||||
this.target = this._targetID == this.mg.terraNullius().id() ? mg.terraNullius() : mg.player(this._targetID)
|
this.target = this._targetID == this.mg.terraNullius().id() ? mg.terraNullius() : mg.player(this._targetID)
|
||||||
|
|
||||||
@@ -84,7 +80,7 @@ export class AttackExecution implements Execution {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Existing attack on same target, add troops
|
// Existing attack on same target, add troops
|
||||||
if (otherAttack._owner == this._owner && otherAttack._targetID == this._targetID && this.sourceCell == otherAttack.sourceCell) {
|
if (otherAttack._owner == this._owner && otherAttack._targetID == this._targetID && this.sourceTile == otherAttack.sourceTile) {
|
||||||
otherAttack.troops += this.troops
|
otherAttack.troops += this.troops
|
||||||
otherAttack.refreshToConquer()
|
otherAttack.refreshToConquer()
|
||||||
this.active = false
|
this.active = false
|
||||||
@@ -95,8 +91,8 @@ export class AttackExecution implements Execution {
|
|||||||
if (this._owner.type() != PlayerType.Bot && this.target.isPlayer() && this.target.type() == PlayerType.Human) {
|
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)
|
mg.displayMessage(`You are being attacked by ${this._owner.displayName()}`, MessageType.ERROR, this._targetID)
|
||||||
}
|
}
|
||||||
if (this.sourceCell != null) {
|
if (this.sourceTile != null) {
|
||||||
this.addNeighbors(mg.tile(this.sourceCell))
|
this.addNeighbors(this.sourceTile)
|
||||||
} else {
|
} else {
|
||||||
this.refreshToConquer()
|
this.refreshToConquer()
|
||||||
}
|
}
|
||||||
@@ -154,14 +150,14 @@ export class AttackExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tileToConquer = this.toConquer.dequeue().tile
|
const tileToConquer = this.toConquer.dequeue().tile
|
||||||
this.border.delete(tileToConquer.ref())
|
this.border.delete(tileToConquer)
|
||||||
|
|
||||||
const onBorder = tileToConquer.neighbors().filter(t => t.owner() == this._owner).length > 0
|
const onBorder = this.mg.neighbors(tileToConquer).filter(t => this.mg.owner(t) == this._owner).length > 0
|
||||||
if (tileToConquer.owner() != this.target || !onBorder) {
|
if (this.mg.owner(tileToConquer) != this.target || !onBorder) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
this.addNeighbors(tileToConquer)
|
this.addNeighbors(tileToConquer)
|
||||||
const { attackerTroopLoss, defenderTroopLoss, tilesPerTickUsed } = this.mg.config().attackLogic(this.troops, this._owner, this.target, tileToConquer)
|
const { attackerTroopLoss, defenderTroopLoss, tilesPerTickUsed } = this.mg.config().attackLogic(this.mg, this.troops, this._owner, this.target, tileToConquer)
|
||||||
numTilesPerTick -= tilesPerTickUsed
|
numTilesPerTick -= tilesPerTickUsed
|
||||||
this.troops -= attackerTroopLoss
|
this.troops -= attackerTroopLoss
|
||||||
if (this.target.isPlayer()) {
|
if (this.target.isPlayer()) {
|
||||||
@@ -172,25 +168,22 @@ export class AttackExecution implements Execution {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private addNeighbors(tile: Tile) {
|
private addNeighbors(tile: TileRef) {
|
||||||
for (const neighbor of tile.neighbors()) {
|
for (const neighbor of this.mg.neighbors(tile)) {
|
||||||
if (neighbor.terrain().isWater() || neighbor.owner() != this.target) {
|
if (this.mg.isWater(neighbor) || this.mg.owner(neighbor) != this.target) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
this.border.add(neighbor.ref())
|
this.border.add(neighbor)
|
||||||
let numOwnedByMe = neighbor.neighbors()
|
let numOwnedByMe = this.mg.neighbors(neighbor)
|
||||||
.filter(t => t.terrain().isLand())
|
.filter(t => this.mg.isLake(t))
|
||||||
.filter(t => t.owner() == this._owner)
|
.filter(t => this.mg.owner(t) == this._owner)
|
||||||
.length
|
.length
|
||||||
let dist = 0
|
let dist = 0
|
||||||
if (this.targetCell != null) {
|
|
||||||
dist = manhattanDist(tile.cell(), this.targetCell)
|
|
||||||
}
|
|
||||||
if (numOwnedByMe > 2) {
|
if (numOwnedByMe > 2) {
|
||||||
numOwnedByMe = 10
|
numOwnedByMe = 10
|
||||||
}
|
}
|
||||||
let mag = 0
|
let mag = 0
|
||||||
switch (tile.terrain().type()) {
|
switch (this.mg.terrainType(tile)) {
|
||||||
case TerrainType.Plains:
|
case TerrainType.Plains:
|
||||||
mag = 1
|
mag = 1
|
||||||
break
|
break
|
||||||
@@ -218,11 +211,12 @@ export class AttackExecution implements Execution {
|
|||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
for (let i = 0; i < 10; i++) {
|
||||||
for (const tile of this.target.tiles()) {
|
for (const tile of this.target.tiles()) {
|
||||||
if (tile.borders(this._owner)) {
|
const borders = this.mg.neighbors(tile).some(t => this.mg.owner(t) == this._owner)
|
||||||
|
if (borders) {
|
||||||
this._owner.conquer(tile)
|
this._owner.conquer(tile)
|
||||||
} else {
|
} else {
|
||||||
for (const neighbor of tile.neighbors()) {
|
for (const neighbor of this.mg.neighbors(tile)) {
|
||||||
const no = neighbor.owner()
|
const no = this.mg.owner(neighbor)
|
||||||
if (no.isPlayer() && no != this.target) {
|
if (no.isPlayer() && no != this.target) {
|
||||||
this.mg.player(no.id()).conquer(tile)
|
this.mg.player(no.id()).conquer(tile)
|
||||||
break
|
break
|
||||||
@@ -246,5 +240,5 @@ export class AttackExecution implements Execution {
|
|||||||
|
|
||||||
|
|
||||||
class TileContainer {
|
class TileContainer {
|
||||||
constructor(public readonly tile: Tile, public readonly priority: number, public readonly tick: number) { }
|
constructor(public readonly tile: TileRef, public readonly priority: number, public readonly tick: number) { }
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Tile, Unit, UnitType } from "../game/Game";
|
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Unit, UnitType } from "../game/Game";
|
||||||
import { PathFinder } from "../pathfinding/PathFinding";
|
import { PathFinder } from "../pathfinding/PathFinding";
|
||||||
import { PathFindResultType } from "../pathfinding/AStar";
|
import { PathFindResultType } from "../pathfinding/AStar";
|
||||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
|
||||||
import { PseudoRandom } from "../PseudoRandom";
|
import { PseudoRandom } from "../PseudoRandom";
|
||||||
import { distSort, distSortUnit, manhattanDist } from "../Util";
|
import { distSort, distSortUnit } from "../Util";
|
||||||
import { ShellExecution } from "./ShellExecution";
|
import { ShellExecution } from "./ShellExecution";
|
||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
|
import { TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export class BattleshipExecution implements Execution {
|
export class BattleshipExecution implements Execution {
|
||||||
private random: PseudoRandom
|
private random: PseudoRandom
|
||||||
@@ -17,8 +17,7 @@ export class BattleshipExecution implements Execution {
|
|||||||
|
|
||||||
private pathfinder: PathFinder
|
private pathfinder: PathFinder
|
||||||
|
|
||||||
private patrolTile: Tile;
|
private patrolTile: TileRef;
|
||||||
private patrolCenterTile: Tile
|
|
||||||
|
|
||||||
// TODO: put in config
|
// TODO: put in config
|
||||||
private searchRange = 100
|
private searchRange = 100
|
||||||
@@ -29,7 +28,7 @@ export class BattleshipExecution implements Execution {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private playerID: PlayerID,
|
private playerID: PlayerID,
|
||||||
private cell: Cell,
|
private patrolCenterTile: TileRef,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
@@ -37,7 +36,6 @@ export class BattleshipExecution implements Execution {
|
|||||||
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
||||||
this._owner = mg.player(this.playerID)
|
this._owner = mg.player(this.playerID)
|
||||||
this.mg = mg
|
this.mg = mg
|
||||||
this.patrolCenterTile = mg.tile(this.cell)
|
|
||||||
this.patrolTile = this.patrolCenterTile
|
this.patrolTile = this.patrolCenterTile
|
||||||
this.random = new PseudoRandom(mg.ticks())
|
this.random = new PseudoRandom(mg.ticks())
|
||||||
}
|
}
|
||||||
@@ -85,15 +83,15 @@ export class BattleshipExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
|
let ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
|
||||||
.filter(u => manhattanDist(u.tile().cell(), this.battleship.tile().cell()) < 100)
|
.filter(u => this.mg.manhattanDist(u.tile(), this.battleship.tile()) < 100)
|
||||||
.filter(u => u.owner() != this.battleship.owner())
|
.filter(u => u.owner() != this.battleship.owner())
|
||||||
.filter(u => u != this.battleship)
|
.filter(u => u != this.battleship)
|
||||||
.filter(u => !u.owner().isAlliedWith(this.battleship.owner()))
|
.filter(u => !u.owner().isAlliedWith(this.battleship.owner()))
|
||||||
.filter(u => !this.alreadyTargeted.has(u))
|
.filter(u => !this.alreadyTargeted.has(u))
|
||||||
.sort(distSortUnit(this.battleship));
|
.sort(distSortUnit(this.mg, this.battleship));
|
||||||
|
|
||||||
const friendlyDestroyerNearby = this.battleship.owner().units(UnitType.Destroyer)
|
const friendlyDestroyerNearby = this.battleship.owner().units(UnitType.Destroyer)
|
||||||
.filter(d => manhattanDist(d.tile().cell(), this.battleship.tile().cell()) < 120)
|
.filter(d => this.mg.manhattanDist(d.tile(), this.battleship.tile()) < 120)
|
||||||
.length > 0
|
.length > 0
|
||||||
|
|
||||||
if (friendlyDestroyerNearby) {
|
if (friendlyDestroyerNearby) {
|
||||||
@@ -124,16 +122,15 @@ export class BattleshipExecution implements Execution {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
randomTile(): Tile {
|
randomTile(): TileRef {
|
||||||
while (true) {
|
while (true) {
|
||||||
const x = this.patrolCenterTile.cell().x + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
const x = this.mg.x(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||||
const y = this.patrolCenterTile.cell().y + 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)
|
||||||
const cell = new Cell(x, y)
|
if (!this.mg.isValidCoord(x, y)) {
|
||||||
if (!this.mg.isOnMap(cell)) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const tile = this.mg.tile(cell)
|
const tile = this.mg.ref(x, y)
|
||||||
if (!tile.terrain().isOcean()) {
|
if (!this.mg.isOcean(tile)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return tile
|
return tile
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ export class BotExecution implements Execution {
|
|||||||
|
|
||||||
if (this.neighborsTerraNullius) {
|
if (this.neighborsTerraNullius) {
|
||||||
for (const b of this.bot.borderTiles()) {
|
for (const b of this.bot.borderTiles()) {
|
||||||
for (const n of b.neighbors()) {
|
for (const n of this.mg.neighbors(b)) {
|
||||||
if (n.owner() == this.mg.terraNullius() && n.terrain().isLand()) {
|
if (!this.mg.hasOwner(n) && this.mg.isLake(n)) {
|
||||||
this.sendAttack(this.mg.terraNullius())
|
this.sendAttack(this.mg.terraNullius())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -65,14 +65,16 @@ export class BotExecution implements Execution {
|
|||||||
this.neighborsTerraNullius = false
|
this.neighborsTerraNullius = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const border = Array.from(this.bot.borderTiles()).flatMap(t => t.neighbors()).filter(t => t.hasOwner() && t.owner() != this.bot)
|
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) {
|
if (border.length == 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const toAttack = border[this.random.nextInt(0, border.length)]
|
const toAttack = border[this.random.nextInt(0, border.length)]
|
||||||
const owner = toAttack.owner()
|
const owner = this.mg.owner(toAttack)
|
||||||
|
|
||||||
if (owner.isPlayer()) {
|
if (owner.isPlayer()) {
|
||||||
if (this.bot.isAlliedWith(owner)) {
|
if (this.bot.isAlliedWith(owner)) {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
import {Cell, Game, PlayerType, Tile} from "../game/Game";
|
import { Cell, Game, PlayerType } from "../game/Game";
|
||||||
import {PseudoRandom} from "../PseudoRandom";
|
import { TileRef } from "../game/GameMap";
|
||||||
import {GameID, SpawnIntent} from "../Schemas";
|
import { PseudoRandom } from "../PseudoRandom";
|
||||||
import {bfs, dist as dist, manhattanDist, simpleHash} from "../Util";
|
import { GameID, SpawnIntent } from "../Schemas";
|
||||||
import {BOT_NAME_PREFIXES, BOT_NAME_SUFFIXES} from "./utils/BotNames";
|
import { simpleHash } from "../Util";
|
||||||
|
import { BOT_NAME_PREFIXES, BOT_NAME_SUFFIXES } from "./utils/BotNames";
|
||||||
|
|
||||||
|
|
||||||
export class BotSpawner {
|
export class BotSpawner {
|
||||||
@@ -34,11 +35,11 @@ export class BotSpawner {
|
|||||||
|
|
||||||
spawnBot(botName: string): SpawnIntent | null {
|
spawnBot(botName: string): SpawnIntent | null {
|
||||||
const tile = this.randTile()
|
const tile = this.randTile()
|
||||||
if (!tile.terrain().isLand()) {
|
if (!this.gs.isLand(tile)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
for (const spawn of this.bots) {
|
for (const spawn of this.bots) {
|
||||||
if (manhattanDist(new Cell(spawn.x, spawn.y), tile.cell()) < 30) {
|
if (this.gs.manhattanDist(this.gs.ref(spawn.x, spawn.y), tile) < 30) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,8 +48,8 @@ export class BotSpawner {
|
|||||||
playerID: this.random.nextID(),
|
playerID: this.random.nextID(),
|
||||||
name: botName,
|
name: botName,
|
||||||
playerType: PlayerType.Bot,
|
playerType: PlayerType.Bot,
|
||||||
x: tile.cell().x,
|
x: this.gs.x(tile),
|
||||||
y: tile.cell().y
|
y: this.gs.y(tile)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,10 +59,10 @@ export class BotSpawner {
|
|||||||
return `${BOT_NAME_PREFIXES[prefixIndex]} ${BOT_NAME_SUFFIXES[suffixIndex]}`;
|
return `${BOT_NAME_PREFIXES[prefixIndex]} ${BOT_NAME_SUFFIXES[suffixIndex]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private randTile(): Tile {
|
private randTile(): TileRef {
|
||||||
return this.gs.tile(new Cell(
|
return this.gs.ref(
|
||||||
this.random.nextInt(0, this.gs.width()),
|
this.random.nextInt(0, this.gs.width()),
|
||||||
this.random.nextInt(0, this.gs.height())
|
this.random.nextInt(0, this.gs.height())
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
import { Cell, DefenseBonus, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game";
|
import { Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, UnitType } from "../game/Game";
|
||||||
import { bfs, dist } from "../Util";
|
import { TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export class CityExecution implements Execution {
|
export class CityExecution implements Execution {
|
||||||
|
|
||||||
private player: MutablePlayer
|
private player: MutablePlayer
|
||||||
private mg: MutableGame
|
private mg: MutableGame
|
||||||
private city: MutableUnit
|
private city: MutableUnit
|
||||||
private tile: Tile
|
|
||||||
private active: boolean = true
|
private active: boolean = true
|
||||||
|
|
||||||
constructor(private ownerId: PlayerID, private cell: Cell) { }
|
constructor(private ownerId: PlayerID, private tile: TileRef) { }
|
||||||
|
|
||||||
init(mg: MutableGame, ticks: number): void {
|
init(mg: MutableGame, ticks: number): void {
|
||||||
this.mg = mg
|
this.mg = mg
|
||||||
this.tile = mg.tile(this.cell)
|
|
||||||
this.player = mg.player(this.ownerId)
|
this.player = mg.player(this.ownerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
import { Cell, DefenseBonus, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game";
|
import { Cell, DefenseBonus, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, UnitType } from "../game/Game";
|
||||||
import { bfs, dist } from "../Util";
|
import { manhattanDistFN, TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export class DefensePostExecution implements Execution {
|
export class DefensePostExecution implements Execution {
|
||||||
|
|
||||||
private player: MutablePlayer
|
private player: MutablePlayer
|
||||||
private mg: MutableGame
|
private mg: MutableGame
|
||||||
private post: MutableUnit
|
private post: MutableUnit
|
||||||
private tile: Tile
|
|
||||||
private active: boolean = true
|
private active: boolean = true
|
||||||
|
|
||||||
private defenseBonuses: DefenseBonus[] = []
|
private defenseBonuses: DefenseBonus[] = []
|
||||||
|
|
||||||
constructor(private ownerId: PlayerID, private cell: Cell) { }
|
constructor(private ownerId: PlayerID, private tile: TileRef) { }
|
||||||
|
|
||||||
init(mg: MutableGame, ticks: number): void {
|
init(mg: MutableGame, ticks: number): void {
|
||||||
this.mg = mg
|
this.mg = mg
|
||||||
this.tile = mg.tile(this.cell)
|
|
||||||
this.player = mg.player(this.ownerId)
|
this.player = mg.player(this.ownerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,9 +27,11 @@ export class DefensePostExecution implements Execution {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.post = this.player.buildUnit(UnitType.DefensePost, 0, spawnTile)
|
this.post = this.player.buildUnit(UnitType.DefensePost, 0, spawnTile)
|
||||||
bfs(spawnTile, dist(spawnTile, this.mg.config().defensePostRange())).forEach(t => {
|
this.mg.bfs(spawnTile, manhattanDistFN(spawnTile, this.mg.config().defensePostRange())).forEach(t => {
|
||||||
if (t.terrain().isLand()) {
|
if (this.mg.isLake(t)) {
|
||||||
this.defenseBonuses.push(this.mg.addTileDefenseBonus(t, this.post, this.mg.config().defensePostDefenseBonus()))
|
this.defenseBonuses.push(
|
||||||
|
this.mg.addTileDefenseBonus(t, this.post, this.mg.config().defensePostDefenseBonus())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, Tile, UnitType } from "../game/Game";
|
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, TerrainType, UnitType } from "../game/Game";
|
||||||
import { PathFinder } from "../pathfinding/PathFinding";
|
import { PathFinder } from "../pathfinding/PathFinding";
|
||||||
import { PathFindResultType } from "../pathfinding/AStar";
|
import { PathFindResultType } from "../pathfinding/AStar";
|
||||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
|
||||||
import { PseudoRandom } from "../PseudoRandom";
|
import { PseudoRandom } from "../PseudoRandom";
|
||||||
import { distSort, distSortUnit, manhattanDist } from "../Util";
|
import { distSort, distSortUnit } from "../Util";
|
||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
|
import { TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export class DestroyerExecution implements Execution {
|
export class DestroyerExecution implements Execution {
|
||||||
private random: PseudoRandom
|
private random: PseudoRandom
|
||||||
@@ -17,15 +17,14 @@ export class DestroyerExecution implements Execution {
|
|||||||
private target: MutableUnit = null
|
private target: MutableUnit = null
|
||||||
private pathfinder: PathFinder
|
private pathfinder: PathFinder
|
||||||
|
|
||||||
private patrolTile: Tile;
|
private patrolTile: TileRef;
|
||||||
private patrolCenterTile: Tile
|
|
||||||
|
|
||||||
// TODO: put in config
|
// TODO: put in config
|
||||||
private searchRange = 100
|
private searchRange = 100
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private playerID: PlayerID,
|
private playerID: PlayerID,
|
||||||
private cell: Cell,
|
private patrolCenterTile: TileRef,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ export class DestroyerExecution implements Execution {
|
|||||||
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
this.pathfinder = PathFinder.Mini(mg, 5000, false)
|
||||||
this._owner = mg.player(this.playerID)
|
this._owner = mg.player(this.playerID)
|
||||||
this.mg = mg
|
this.mg = mg
|
||||||
this.patrolCenterTile = mg.tile(this.cell)
|
|
||||||
this.patrolTile = this.patrolCenterTile
|
this.patrolTile = this.patrolCenterTile
|
||||||
this.random = new PseudoRandom(mg.ticks())
|
this.random = new PseudoRandom(mg.ticks())
|
||||||
}
|
}
|
||||||
@@ -57,7 +55,7 @@ export class DestroyerExecution implements Execution {
|
|||||||
}
|
}
|
||||||
if (this.target == null) {
|
if (this.target == null) {
|
||||||
const ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
|
const ships = this.mg.units(UnitType.TransportShip, UnitType.Destroyer, UnitType.TradeShip, UnitType.Battleship)
|
||||||
.filter(u => manhattanDist(u.tile().cell(), this.destroyer.tile().cell()) < 100)
|
.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.type() != UnitType.Destroyer || u.health() < this.destroyer.health()) // only attack Destroyers weaker than it.
|
||||||
.filter(u => u.owner() != this.destroyer.owner())
|
.filter(u => u.owner() != this.destroyer.owner())
|
||||||
.filter(u => u != this.destroyer)
|
.filter(u => u != this.destroyer)
|
||||||
@@ -80,7 +78,7 @@ export class DestroyerExecution implements Execution {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.target = ships.sort(distSortUnit(this.destroyer))[0]
|
this.target = ships.sort(distSortUnit(this.mg, this.destroyer))[0]
|
||||||
}
|
}
|
||||||
if (!this.target.isActive() || this.target.owner() == this._owner) {
|
if (!this.target.isActive() || this.target.owner() == this._owner) {
|
||||||
// Incase another destroyer captured or destroyed target
|
// Incase another destroyer captured or destroyed target
|
||||||
@@ -132,16 +130,15 @@ export class DestroyerExecution implements Execution {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
randomTile(): Tile {
|
randomTile(): TileRef {
|
||||||
while (true) {
|
while (true) {
|
||||||
const x = this.patrolCenterTile.cell().x + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
const x = this.mg.x(this.patrolCenterTile) + this.random.nextInt(-this.searchRange / 2, this.searchRange / 2)
|
||||||
const y = this.patrolCenterTile.cell().y + 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)
|
||||||
const cell = new Cell(x, y)
|
if (!this.mg.isValidCoord(x, y)) {
|
||||||
if (!this.mg.isOnMap(cell)) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const tile = this.mg.tile(cell)
|
const tile = this.mg.ref(x, y)
|
||||||
if (!tile.terrain().isOcean()) {
|
if (!this.mg.isOcean(tile)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return tile
|
return tile
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerInfo, TerraNullius, Tile, PlayerType, Alliance, UnitType } from "../game/Game";
|
import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerInfo, TerraNullius, PlayerType, Alliance, UnitType } from "../game/Game";
|
||||||
import { AttackIntent, BoatAttackIntentSchema, GameID, Intent, Turn } from "../Schemas";
|
import { AttackIntent, BoatAttackIntentSchema, GameID, Intent, Turn } from "../Schemas";
|
||||||
import { AttackExecution } from "./AttackExecution";
|
import { AttackExecution } from "./AttackExecution";
|
||||||
import { SpawnExecution } from "./SpawnExecution";
|
import { SpawnExecution } from "./SpawnExecution";
|
||||||
@@ -21,6 +21,7 @@ import { MissileSiloExecution } from "./MissileSiloExecution";
|
|||||||
import { BattleshipExecution } from "./BattleshipExecution";
|
import { BattleshipExecution } from "./BattleshipExecution";
|
||||||
import { DefensePostExecution } from "./DefensePostExecution";
|
import { DefensePostExecution } from "./DefensePostExecution";
|
||||||
import { CityExecution } from "./CityExecution";
|
import { CityExecution } from "./CityExecution";
|
||||||
|
import { TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ export class Executor {
|
|||||||
// private random = new PseudoRandom(999)
|
// private random = new PseudoRandom(999)
|
||||||
private random: PseudoRandom = null
|
private random: PseudoRandom = null
|
||||||
|
|
||||||
constructor(private gs: Game, private gameID: GameID) {
|
constructor(private mg: Game, private gameID: GameID) {
|
||||||
// Add one to avoid id collisions with bots.
|
// Add one to avoid id collisions with bots.
|
||||||
this.random = new PseudoRandom(simpleHash(gameID) + 1)
|
this.random = new PseudoRandom(simpleHash(gameID) + 1)
|
||||||
}
|
}
|
||||||
@@ -42,30 +43,23 @@ export class Executor {
|
|||||||
createExec(intent: Intent): Execution {
|
createExec(intent: Intent): Execution {
|
||||||
switch (intent.type) {
|
switch (intent.type) {
|
||||||
case "attack": {
|
case "attack": {
|
||||||
const source: Cell | null = intent.sourceX != null && intent.sourceY != null
|
|
||||||
? new Cell(intent.sourceX, intent.sourceY)
|
|
||||||
: null;
|
|
||||||
const target: Cell | null = intent.targetX != null && intent.targetY != null
|
|
||||||
? new Cell(intent.targetX, intent.targetY)
|
|
||||||
: null;
|
|
||||||
return new AttackExecution(
|
return new AttackExecution(
|
||||||
intent.troops,
|
intent.troops,
|
||||||
intent.attackerID,
|
intent.attackerID,
|
||||||
intent.targetID,
|
intent.targetID,
|
||||||
source,
|
null
|
||||||
target,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "spawn":
|
case "spawn":
|
||||||
return new SpawnExecution(
|
return new SpawnExecution(
|
||||||
new PlayerInfo(sanitize(intent.name), intent.playerType, intent.clientID, intent.playerID),
|
new PlayerInfo(sanitize(intent.name), intent.playerType, intent.clientID, intent.playerID),
|
||||||
new Cell(intent.x, intent.y)
|
this.mg.ref(intent.x, intent.y)
|
||||||
);
|
);
|
||||||
case "boat":
|
case "boat":
|
||||||
return new TransportShipExecution(
|
return new TransportShipExecution(
|
||||||
intent.attackerID,
|
intent.attackerID,
|
||||||
intent.targetID,
|
intent.targetID,
|
||||||
new Cell(intent.x, intent.y),
|
this.mg.ref(intent.x, intent.y),
|
||||||
intent.troops
|
intent.troops
|
||||||
);
|
);
|
||||||
case "allianceRequest":
|
case "allianceRequest":
|
||||||
@@ -86,19 +80,19 @@ export class Executor {
|
|||||||
switch (intent.unit) {
|
switch (intent.unit) {
|
||||||
case UnitType.AtomBomb:
|
case UnitType.AtomBomb:
|
||||||
case UnitType.HydrogenBomb:
|
case UnitType.HydrogenBomb:
|
||||||
return new NukeExecution(intent.unit, intent.player, new Cell(intent.x, intent.y))
|
return new NukeExecution(intent.unit, intent.player, this.mg.ref(intent.x, intent.y))
|
||||||
case UnitType.Destroyer:
|
case UnitType.Destroyer:
|
||||||
return new DestroyerExecution(intent.player, new Cell(intent.x, intent.y))
|
return new DestroyerExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||||
case UnitType.Battleship:
|
case UnitType.Battleship:
|
||||||
return new BattleshipExecution(intent.player, new Cell(intent.x, intent.y))
|
return new BattleshipExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||||
case UnitType.Port:
|
case UnitType.Port:
|
||||||
return new PortExecution(intent.player, new Cell(intent.x, intent.y))
|
return new PortExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||||
case UnitType.MissileSilo:
|
case UnitType.MissileSilo:
|
||||||
return new MissileSiloExecution(intent.player, new Cell(intent.x, intent.y))
|
return new MissileSiloExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||||
case UnitType.DefensePost:
|
case UnitType.DefensePost:
|
||||||
return new DefensePostExecution(intent.player, new Cell(intent.x, intent.y))
|
return new DefensePostExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||||
case UnitType.City:
|
case UnitType.City:
|
||||||
return new CityExecution(intent.player, new Cell(intent.x, intent.y))
|
return new CityExecution(intent.player, this.mg.ref(intent.x, intent.y))
|
||||||
default:
|
default:
|
||||||
throw Error(`unit type ${intent.unit} not supported`)
|
throw Error(`unit type ${intent.unit} not supported`)
|
||||||
}
|
}
|
||||||
@@ -108,12 +102,12 @@ export class Executor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
spawnBots(numBots: number): Execution[] {
|
spawnBots(numBots: number): Execution[] {
|
||||||
return new BotSpawner(this.gs, this.gameID).spawnBots(numBots).map(i => this.createExec(i))
|
return new BotSpawner(this.mg, this.gameID).spawnBots(numBots).map(i => this.createExec(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
fakeHumanExecutions(): Execution[] {
|
fakeHumanExecutions(): Execution[] {
|
||||||
const execs = []
|
const execs = []
|
||||||
for (const nation of this.gs.nations()) {
|
for (const nation of this.mg.nations()) {
|
||||||
execs.push(new FakeHumanExecution(
|
execs.push(new FakeHumanExecution(
|
||||||
this.gameID,
|
this.gameID,
|
||||||
new PlayerInfo(
|
new PlayerInfo(
|
||||||
@@ -123,7 +117,7 @@ export class Executor {
|
|||||||
this.random.nextID()
|
this.random.nextID()
|
||||||
),
|
),
|
||||||
nation.cell,
|
nation.cell,
|
||||||
nation.strength * this.gs.config().difficultyModifier(this.gs.config().gameConfig().difficulty)
|
nation.strength * this.mg.config().difficultyModifier(this.mg.config().gameConfig().difficulty)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
return execs
|
return execs
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { AllianceRequest, Cell, Difficulty, Execution, MutableGame, MutablePlayer, Player, PlayerInfo, PlayerType, Relation, TerrainType, TerraNullius, Tick, Tile, UnitType } from "../game/Game"
|
import { AllianceRequest, Cell, Difficulty, Execution, MutableGame, MutablePlayer, Player, PlayerInfo, PlayerType, Relation, TerrainType, TerraNullius, UnitType } from "../game/Game"
|
||||||
import { PseudoRandom } from "../PseudoRandom"
|
import { PseudoRandom } from "../PseudoRandom"
|
||||||
import { and, bfs, calculateBoundingBox, dist, euclDist, manhattanDist, simpleHash } from "../Util";
|
|
||||||
import { AttackExecution } from "./AttackExecution";
|
import { AttackExecution } from "./AttackExecution";
|
||||||
import { TransportShipExecution } from "./TransportShipExecution";
|
import { TransportShipExecution } from "./TransportShipExecution";
|
||||||
import { SpawnExecution } from "./SpawnExecution";
|
import { SpawnExecution } from "./SpawnExecution";
|
||||||
@@ -15,6 +14,8 @@ import { MissileSiloExecution } from "./MissileSiloExecution";
|
|||||||
import { EmojiExecution } from "./EmojiExecution";
|
import { EmojiExecution } from "./EmojiExecution";
|
||||||
import { AllianceRequestReplyExecution } from "./alliance/AllianceRequestReplyExecution";
|
import { AllianceRequestReplyExecution } from "./alliance/AllianceRequestReplyExecution";
|
||||||
import { closestTwoTiles } from "./Util";
|
import { closestTwoTiles } from "./Util";
|
||||||
|
import { calculateBoundingBox, simpleHash } from "../Util";
|
||||||
|
import { andFN, manhattanDistFN, TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export class FakeHumanExecution implements Execution {
|
export class FakeHumanExecution implements Execution {
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
this.mg.addExecution(new SpawnExecution(
|
this.mg.addExecution(new SpawnExecution(
|
||||||
this.playerInfo,
|
this.playerInfo,
|
||||||
rl.cell()
|
rl
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -89,7 +90,9 @@ export class FakeHumanExecution implements Execution {
|
|||||||
this.handleEnemies()
|
this.handleEnemies()
|
||||||
this.handleUnits()
|
this.handleUnits()
|
||||||
|
|
||||||
const enemyborder = Array.from(this.player.borderTiles()).flatMap(t => t.neighbors()).filter(t => t.terrain().isLand() && t.owner() != this.player)
|
const enemyborder = Array.from(this.player.borderTiles())
|
||||||
|
.flatMap(t => this.mg.neighbors(t))
|
||||||
|
.filter(t => this.mg.isLake(t) && this.mg.ownerID(t) != this.player.smallID())
|
||||||
|
|
||||||
if (enemyborder.length == 0) {
|
if (enemyborder.length == 0) {
|
||||||
if (this.random.chance(5)) {
|
if (this.random.chance(5)) {
|
||||||
@@ -102,7 +105,7 @@ export class FakeHumanExecution implements Execution {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const enemiesWithTN = enemyborder.map(t => t.owner())
|
const enemiesWithTN = enemyborder.map(t => this.mg.playerBySmallID(this.mg.ownerID(t)))
|
||||||
if (enemiesWithTN.filter(o => !o.isPlayer()).length > 0) {
|
if (enemiesWithTN.filter(o => !o.isPlayer()).length > 0) {
|
||||||
this.sendAttack(this.mg.terraNullius())
|
this.sendAttack(this.mg.terraNullius())
|
||||||
return
|
return
|
||||||
@@ -224,15 +227,15 @@ export class FakeHumanExecution implements Execution {
|
|||||||
if (tile == null) {
|
if (tile == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for (const t of bfs(tile, dist(tile, 15))) {
|
for (const t of this.mg.bfs(tile, manhattanDistFN(tile, 15))) {
|
||||||
// Make sure we nuke at least 15 tiles in border
|
// Make sure we nuke at least 15 tiles in border
|
||||||
if (t.hasOwner() && t.owner() != other) {
|
if (this.mg.owner(t) != other) {
|
||||||
continue outer
|
continue outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.player.canBuild(UnitType.AtomBomb, tile)) {
|
if (this.player.canBuild(UnitType.AtomBomb, tile)) {
|
||||||
this.mg.addExecution(
|
this.mg.addExecution(
|
||||||
new NukeExecution(UnitType.AtomBomb, this.player.id(), tile.cell())
|
new NukeExecution(UnitType.AtomBomb, this.player.id(), tile)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -241,17 +244,18 @@ export class FakeHumanExecution implements Execution {
|
|||||||
|
|
||||||
private maybeSendBoatAttack(other: Player) {
|
private maybeSendBoatAttack(other: Player) {
|
||||||
const closest = closestTwoTiles(
|
const closest = closestTwoTiles(
|
||||||
Array.from(this.player.borderTiles()).filter(t => t.terrain().isOceanShore()),
|
this.mg,
|
||||||
Array.from(other.borderTiles()).filter(t => t.terrain().isOceanShore())
|
Array.from(this.player.borderTiles()).filter(t => this.mg.isOceanShore(t)),
|
||||||
|
Array.from(other.borderTiles()).filter(t => this.mg.isOceanShore(t))
|
||||||
)
|
)
|
||||||
if (closest == null) {
|
if (closest == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (manhattanDist(closest.x.cell(), closest.y.cell()) < this.mg.config().boatMaxDistance()) {
|
if (this.mg.manhattanDist(closest.x, closest.y) < this.mg.config().boatMaxDistance()) {
|
||||||
this.mg.addExecution(new TransportShipExecution(
|
this.mg.addExecution(new TransportShipExecution(
|
||||||
this.player.id(),
|
this.player.id(),
|
||||||
other.id(),
|
other.id(),
|
||||||
closest.y.cell(),
|
closest.y,
|
||||||
this.player.troops() / 5
|
this.player.troops() / 5
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -260,24 +264,24 @@ export class FakeHumanExecution implements Execution {
|
|||||||
private handleUnits() {
|
private handleUnits() {
|
||||||
const ports = this.player.units(UnitType.Port)
|
const ports = this.player.units(UnitType.Port)
|
||||||
if (ports.length == 0 && this.player.gold() > this.cost(UnitType.Port)) {
|
if (ports.length == 0 && this.player.gold() > this.cost(UnitType.Port)) {
|
||||||
const oceanTiles = Array.from(this.player.borderTiles()).filter(t => t.terrain().isOceanShore())
|
const oceanTiles = Array.from(this.player.borderTiles()).filter(t => this.mg.isOceanShore(t))
|
||||||
if (oceanTiles.length > 0) {
|
if (oceanTiles.length > 0) {
|
||||||
const buildTile = this.random.randElement(oceanTiles)
|
const buildTile = this.random.randElement(oceanTiles)
|
||||||
this.mg.addExecution(new PortExecution(this.player.id(), buildTile.cell()))
|
this.mg.addExecution(new PortExecution(this.player.id(), buildTile))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.maybeSpawnStructure(UnitType.City, 2, t => new CityExecution(this.player.id(), t.cell()))
|
this.maybeSpawnStructure(UnitType.City, 2, t => new CityExecution(this.player.id(), t))
|
||||||
if (this.maybeSpawnWarship(UnitType.Destroyer)) {
|
if (this.maybeSpawnWarship(UnitType.Destroyer)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.maybeSpawnWarship(UnitType.Battleship)) {
|
if (this.maybeSpawnWarship(UnitType.Battleship)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.maybeSpawnStructure(UnitType.MissileSilo, 1, t => new MissileSiloExecution(this.player.id(), t.cell()))
|
this.maybeSpawnStructure(UnitType.MissileSilo, 1, t => new MissileSiloExecution(this.player.id(), t))
|
||||||
}
|
}
|
||||||
|
|
||||||
private maybeSpawnStructure(type: UnitType, maxNum: number, build: (tile: Tile) => Execution) {
|
private maybeSpawnStructure(type: UnitType, maxNum: number, build: (tile: TileRef) => Execution) {
|
||||||
const units = this.player.units(type)
|
const units = this.player.units(type)
|
||||||
if (units.length >= maxNum) {
|
if (units.length >= maxNum) {
|
||||||
return
|
return
|
||||||
@@ -315,10 +319,10 @@ export class FakeHumanExecution implements Execution {
|
|||||||
}
|
}
|
||||||
switch (shipType) {
|
switch (shipType) {
|
||||||
case UnitType.Destroyer:
|
case UnitType.Destroyer:
|
||||||
this.mg.addExecution(new DestroyerExecution(this.player.id(), targetTile.cell()))
|
this.mg.addExecution(new DestroyerExecution(this.player.id(), targetTile))
|
||||||
break
|
break
|
||||||
case UnitType.Battleship:
|
case UnitType.Battleship:
|
||||||
this.mg.addExecution(new BattleshipExecution(this.player.id(), targetTile.cell()))
|
this.mg.addExecution(new BattleshipExecution(this.player.id(), targetTile))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -326,8 +330,8 @@ export class FakeHumanExecution implements Execution {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private randTerritoryTile(p: Player): Tile | null {
|
private randTerritoryTile(p: Player): TileRef | null {
|
||||||
const boundingBox = calculateBoundingBox(p.borderTiles())
|
const boundingBox = calculateBoundingBox(this.mg, p.borderTiles())
|
||||||
for (let i = 0; i < 100; i++) {
|
for (let i = 0; i < 100; i++) {
|
||||||
const randX = this.random.nextInt(boundingBox.min.x, boundingBox.max.x)
|
const randX = this.random.nextInt(boundingBox.min.x, boundingBox.max.x)
|
||||||
const randY = this.random.nextInt(boundingBox.min.y, boundingBox.max.y)
|
const randY = this.random.nextInt(boundingBox.min.y, boundingBox.max.y)
|
||||||
@@ -335,29 +339,28 @@ export class FakeHumanExecution implements Execution {
|
|||||||
// Sanity check should never happen
|
// Sanity check should never happen
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const randTile = this.mg.tile(new Cell(randX, randY))
|
const randTile = this.mg.ref(randX, randY)
|
||||||
if (randTile.owner() == p) {
|
if (this.mg.owner(randTile) == p) {
|
||||||
return randTile
|
return randTile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private warshipSpawnTile(portTile: Tile): Tile | null {
|
private warshipSpawnTile(portTile: TileRef): TileRef | null {
|
||||||
const radius = this.mg.config().boatMaxDistance() / 2
|
const radius = this.mg.config().boatMaxDistance() / 2
|
||||||
for (let attempts = 0; attempts < 50; attempts++) {
|
for (let attempts = 0; attempts < 50; attempts++) {
|
||||||
const randX = this.random.nextInt(portTile.cell().x - radius, portTile.cell().x + radius)
|
const randX = this.random.nextInt(this.mg.x(portTile) - radius, this.mg.x(portTile) + radius)
|
||||||
const randY = this.random.nextInt(portTile.cell().y - radius, portTile.cell().y + radius)
|
const randY = this.random.nextInt(this.mg.y(portTile) - radius, this.mg.y(portTile) + radius)
|
||||||
const cell = new Cell(randX, randY)
|
if (!this.mg.isValidCoord(randX, randY)) {
|
||||||
if (!this.mg.isOnMap(cell)) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
const tile = this.mg.ref(randX, randY)
|
||||||
// Sanity check
|
// Sanity check
|
||||||
if (manhattanDist(cell, portTile.cell()) >= this.mg.config().boatMaxDistance()) {
|
if (this.mg.manhattanDist(tile, portTile) >= this.mg.config().boatMaxDistance()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const tile = this.mg.tile(cell)
|
if (!this.mg.isOcean(tile)) {
|
||||||
if (!tile.terrain().isOcean()) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return tile
|
return tile
|
||||||
@@ -394,13 +397,13 @@ export class FakeHumanExecution implements Execution {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
sendBoat(tries: number = 0, oceanShore: Tile[] = null) {
|
sendBoat(tries: number = 0, oceanShore: TileRef[] = null) {
|
||||||
if (tries > 10) {
|
if (tries > 10) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oceanShore == null) {
|
if (oceanShore == null) {
|
||||||
oceanShore = Array.from(this.player.borderTileRefs()).filter(t => this.mg.isOceanShore(t)).map(tr => this.mg.fromRef(tr))
|
oceanShore = Array.from(this.player.borderTiles()).filter(t => this.mg.isOceanShore(t))
|
||||||
}
|
}
|
||||||
if (oceanShore.length == 0) {
|
if (oceanShore.length == 0) {
|
||||||
return
|
return
|
||||||
@@ -408,11 +411,11 @@ export class FakeHumanExecution implements Execution {
|
|||||||
|
|
||||||
const src = this.random.randElement(oceanShore)
|
const src = this.random.randElement(oceanShore)
|
||||||
const otherShore = Array.from(
|
const otherShore = Array.from(
|
||||||
bfs(
|
this.mg.bfs(
|
||||||
src,
|
src,
|
||||||
and((t) => t.terrain().isOcean() || t.terrain().isOceanShore(), dist(src, 200))
|
andFN((gm, t) => gm.isOcean(t) || gm.isOceanShore(t), manhattanDistFN(src, 200))
|
||||||
)
|
)
|
||||||
).filter(t => t.terrain().isOceanShore() && t.owner() != this.player)
|
).filter(t => this.mg.isOceanShore(t) && this.mg.owner(t) != this.player)
|
||||||
|
|
||||||
if (otherShore.length == 0) {
|
if (otherShore.length == 0) {
|
||||||
return
|
return
|
||||||
@@ -423,14 +426,14 @@ export class FakeHumanExecution implements Execution {
|
|||||||
if (this.isSmallIsland(dst)) {
|
if (this.isSmallIsland(dst)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (dst.owner().isPlayer() && this.player.isAlliedWith(dst.owner() as Player)) {
|
if (this.mg.owner(dst).isPlayer() && this.player.isAlliedWith(this.mg.owner(dst) as Player)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mg.addExecution(new TransportShipExecution(
|
this.mg.addExecution(new TransportShipExecution(
|
||||||
this.player.id(),
|
this.player.id(),
|
||||||
dst.hasOwner() ? dst.owner().id() : null,
|
this.mg.hasOwner(dst) ? this.mg.owner(dst).id() : null,
|
||||||
dst.cell(),
|
dst,
|
||||||
this.player.troops() / 5,
|
this.player.troops() / 5,
|
||||||
))
|
))
|
||||||
return
|
return
|
||||||
@@ -438,21 +441,19 @@ export class FakeHumanExecution implements Execution {
|
|||||||
this.sendBoat(tries + 1, oceanShore)
|
this.sendBoat(tries + 1, oceanShore)
|
||||||
}
|
}
|
||||||
|
|
||||||
randomLand(): Tile | null {
|
randomLand(): TileRef | null {
|
||||||
const delta = 25
|
const delta = 25
|
||||||
let tries = 0
|
let tries = 0
|
||||||
while (tries < 50) {
|
while (tries < 50) {
|
||||||
tries++
|
tries++
|
||||||
const cell = new Cell(
|
const x = this.random.nextInt(this.cell.x - delta, this.cell.x + delta)
|
||||||
this.random.nextInt(this.cell.x - delta, this.cell.x + delta),
|
const y = this.random.nextInt(this.cell.y - delta, this.cell.y + delta)
|
||||||
this.random.nextInt(this.cell.y - delta, this.cell.y + delta)
|
if (!this.mg.isValidCoord(x, y)) {
|
||||||
)
|
|
||||||
if (!this.mg.isOnMap(cell)) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const tile = this.mg.tile(cell)
|
const tile = this.mg.ref(x, y)
|
||||||
if (tile.terrain().isLand() && !tile.hasOwner()) {
|
if (this.mg.isLand(tile) && !this.mg.hasOwner(tile)) {
|
||||||
if (tile.terrain().type() == TerrainType.Mountain && this.random.chance(2)) {
|
if (this.mg.terrainType(tile) == TerrainType.Mountain && this.random.chance(2)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return tile
|
return tile
|
||||||
@@ -471,8 +472,8 @@ export class FakeHumanExecution implements Execution {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
isSmallIsland(tile: Tile): boolean {
|
isSmallIsland(tile: TileRef): boolean {
|
||||||
return bfs(tile, and((t) => t.terrain().isLand(), dist(tile, 10))).size < 50
|
return this.mg.bfs(tile, andFN((gm, t) => gm.isLand(t), manhattanDistFN(tile, 10))).size < 50
|
||||||
}
|
}
|
||||||
|
|
||||||
owner(): MutablePlayer {
|
owner(): MutablePlayer {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game";
|
import { Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, UnitType } from "../game/Game";
|
||||||
|
import { TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export class MissileSiloExecution implements Execution {
|
export class MissileSiloExecution implements Execution {
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ export class MissileSiloExecution implements Execution {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _owner: PlayerID,
|
private _owner: PlayerID,
|
||||||
private cell: Cell
|
private tile: TileRef
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
@@ -21,13 +22,12 @@ export class MissileSiloExecution implements Execution {
|
|||||||
|
|
||||||
tick(ticks: number): void {
|
tick(ticks: number): void {
|
||||||
if (this.silo == null) {
|
if (this.silo == null) {
|
||||||
const tile = this.mg.tile(this.cell)
|
if (!this.player.canBuild(UnitType.MissileSilo, this.tile)) {
|
||||||
if (!this.player.canBuild(UnitType.MissileSilo, tile)) {
|
consolex.warn(`player ${this.player} cannot build port at ${this.tile}`)
|
||||||
consolex.warn(`player ${this.player} cannot build port at ${this.cell}`)
|
|
||||||
this.active = false
|
this.active = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, tile)
|
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, this.tile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { nextTick } from "process";
|
import { nextTick } from "process";
|
||||||
import { Cell, Execution, MutableGame, MutablePlayer, PlayerID, Tile, MutableUnit, UnitType, Player, TerraNullius } from "../game/Game";
|
import { Cell, Execution, MutableGame, MutablePlayer, PlayerID, MutableUnit, UnitType, Player, TerraNullius } from "../game/Game";
|
||||||
import { PathFinder } from "../pathfinding/PathFinding";
|
import { PathFinder } from "../pathfinding/PathFinding";
|
||||||
import { PathFindResultType } from "../pathfinding/AStar";
|
import { PathFindResultType } from "../pathfinding/AStar";
|
||||||
import { PseudoRandom } from "../PseudoRandom";
|
import { PseudoRandom } from "../PseudoRandom";
|
||||||
import { bfs, dist, distSortUnit, euclideanDist, manhattanDist } from "../Util";
|
|
||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
|
import { TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export class NukeExecution implements Execution {
|
export class NukeExecution implements Execution {
|
||||||
|
|
||||||
@@ -15,13 +15,12 @@ export class NukeExecution implements Execution {
|
|||||||
private mg: MutableGame
|
private mg: MutableGame
|
||||||
|
|
||||||
private nuke: MutableUnit
|
private nuke: MutableUnit
|
||||||
private dst: Tile
|
|
||||||
|
|
||||||
private pathFinder: PathFinder
|
private pathFinder: PathFinder
|
||||||
constructor(
|
constructor(
|
||||||
private type: UnitType.AtomBomb | UnitType.HydrogenBomb,
|
private type: UnitType.AtomBomb | UnitType.HydrogenBomb,
|
||||||
private senderID: PlayerID,
|
private senderID: PlayerID,
|
||||||
private cell: Cell,
|
private dst: TileRef,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
@@ -29,11 +28,10 @@ export class NukeExecution implements Execution {
|
|||||||
this.mg = mg
|
this.mg = mg
|
||||||
this.pathFinder = PathFinder.Mini(mg, 10_000, true)
|
this.pathFinder = PathFinder.Mini(mg, 10_000, true)
|
||||||
this.player = mg.player(this.senderID)
|
this.player = mg.player(this.senderID)
|
||||||
this.dst = this.mg.tile(this.cell)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public target(): Player | TerraNullius {
|
public target(): Player | TerraNullius {
|
||||||
return this.dst.owner()
|
return this.mg.owner(this.dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
tick(ticks: number): void {
|
tick(ticks: number): void {
|
||||||
@@ -70,9 +68,8 @@ export class NukeExecution implements Execution {
|
|||||||
private detonate() {
|
private detonate() {
|
||||||
const magnitude = this.type == UnitType.AtomBomb ? { inner: 15, outer: 40 } : { inner: 140, outer: 160 }
|
const magnitude = this.type == UnitType.AtomBomb ? { inner: 15, outer: 40 } : { inner: 140, outer: 160 }
|
||||||
const rand = new PseudoRandom(this.mg.ticks())
|
const rand = new PseudoRandom(this.mg.ticks())
|
||||||
const tile = this.mg.tile(this.cell)
|
const toDestroy = this.mg.bfs(this.dst, (_, n: TileRef) => {
|
||||||
const toDestroy = bfs(tile, (n: Tile) => {
|
const d = this.mg.euclideanDist(this.dst, n)
|
||||||
const d = euclideanDist(tile.cell(), n.cell())
|
|
||||||
return (d <= magnitude.inner || rand.chance(2)) && d <= magnitude.outer
|
return (d <= magnitude.inner || rand.chance(2)) && d <= magnitude.outer
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -81,7 +78,7 @@ export class NukeExecution implements Execution {
|
|||||||
)
|
)
|
||||||
const attacked = new Map<MutablePlayer, number>()
|
const attacked = new Map<MutablePlayer, number>()
|
||||||
for (const tile of toDestroy) {
|
for (const tile of toDestroy) {
|
||||||
const owner = tile.owner()
|
const owner = this.mg.owner(tile)
|
||||||
if (owner.isPlayer()) {
|
if (owner.isPlayer()) {
|
||||||
const mp = this.mg.player(owner.id())
|
const mp = this.mg.player(owner.id())
|
||||||
mp.relinquish(tile)
|
mp.relinquish(tile)
|
||||||
@@ -92,8 +89,8 @@ export class NukeExecution implements Execution {
|
|||||||
const prev = attacked.get(mp)
|
const prev = attacked.get(mp)
|
||||||
attacked.set(mp, prev + 1)
|
attacked.set(mp, prev + 1)
|
||||||
}
|
}
|
||||||
if (tile.terrain().isLand()) {
|
if (this.mg.isLand(tile)) {
|
||||||
this.mg.addFallout(tile)
|
this.mg.setFallout(tile, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [other, tilesDestroyed] of attacked) {
|
for (const [other, tilesDestroyed] of attacked) {
|
||||||
@@ -110,7 +107,7 @@ export class NukeExecution implements Execution {
|
|||||||
|
|
||||||
for (const unit of this.mg.units()) {
|
for (const unit of this.mg.units()) {
|
||||||
if (unit.type() != UnitType.AtomBomb && unit.type() != UnitType.HydrogenBomb) {
|
if (unit.type() != UnitType.AtomBomb && unit.type() != UnitType.HydrogenBomb) {
|
||||||
if (euclideanDist(this.cell, unit.tile().cell()) < magnitude.outer) {
|
if (this.mg.euclideanDist(this.dst, unit.tile()) < magnitude.outer) {
|
||||||
unit.delete()
|
unit.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Config } from "../configuration/Config"
|
import { Config } from "../configuration/Config"
|
||||||
import { Execution, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, UnitType } from "../game/Game"
|
import { Execution, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, UnitType } from "../game/Game"
|
||||||
import { bfs, calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util"
|
import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util"
|
||||||
import { GameImpl } from "../game/GameImpl"
|
import { GameImpl } from "../game/GameImpl"
|
||||||
import { consolex } from "../Consolex"
|
import { consolex } from "../Consolex"
|
||||||
import { TileRef } from "../game/GameMap"
|
import { GameMap, TileRef } from "../game/GameMap"
|
||||||
|
|
||||||
export class PlayerExecution implements Execution {
|
export class PlayerExecution implements Execution {
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export class PlayerExecution implements Execution {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
u.modifyHealth(1)
|
u.modifyHealth(1)
|
||||||
const tileOwner = u.tile().owner()
|
const tileOwner = this.mg.owner(u.tile())
|
||||||
if (u.info().territoryBound) {
|
if (u.info().territoryBound) {
|
||||||
if (tileOwner.isPlayer()) {
|
if (tileOwner.isPlayer()) {
|
||||||
if (tileOwner != this.player) {
|
if (tileOwner != this.player) {
|
||||||
@@ -80,8 +80,7 @@ export class PlayerExecution implements Execution {
|
|||||||
if (this.player.lastTileChange() > this.lastCalc) {
|
if (this.player.lastTileChange() > this.lastCalc) {
|
||||||
this.lastCalc = ticks
|
this.lastCalc = ticks
|
||||||
const start = performance.now()
|
const start = performance.now()
|
||||||
// TODO
|
this.removeClusters()
|
||||||
// this.removeClusters()
|
|
||||||
const end = performance.now()
|
const end = performance.now()
|
||||||
if (end - start > 1000) {
|
if (end - start > 1000) {
|
||||||
consolex.log(`player ${this.player.name()}, took ${end - start}ms`)
|
consolex.log(`player ${this.player.name()}, took ${end - start}ms`)
|
||||||
@@ -143,8 +142,8 @@ export class PlayerExecution implements Execution {
|
|||||||
if (enemyTiles.size == 0) {
|
if (enemyTiles.size == 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const enemyBox = calculateBoundingBox(new Set(Array.from(enemyTiles).map(tr => this.mg.fromRef(tr))))
|
const enemyBox = calculateBoundingBox(this.mg, enemyTiles)
|
||||||
const clusterBox = calculateBoundingBox(new Set(Array.from(cluster).map(tr => this.mg.fromRef(tr))))
|
const clusterBox = calculateBoundingBox(this.mg, cluster)
|
||||||
return inscribed(enemyBox, clusterBox)
|
return inscribed(enemyBox, clusterBox)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,8 +160,8 @@ export class PlayerExecution implements Execution {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const firstTile = arr[0]
|
const firstTile = arr[0]
|
||||||
const filter = (n: Tile): boolean => n.owner().smallID() == this.mg.ownerID(firstTile)
|
const filter = (_, t: TileRef): boolean => this.mg.ownerID(t) == this.mg.ownerID(firstTile)
|
||||||
const tiles = bfs(this.mg.fromRef(firstTile), filter)
|
const tiles = this.mg.bfs(firstTile, filter)
|
||||||
|
|
||||||
const modePlayer = this.mg.playerBySmallID(mode)
|
const modePlayer = this.mg.playerBySmallID(mode)
|
||||||
if (!modePlayer.isPlayer()) {
|
if (!modePlayer.isPlayer()) {
|
||||||
@@ -175,7 +174,7 @@ export class PlayerExecution implements Execution {
|
|||||||
|
|
||||||
private calculateClusters(): Set<TileRef>[] {
|
private calculateClusters(): Set<TileRef>[] {
|
||||||
const seen = new Set<TileRef>()
|
const seen = new Set<TileRef>()
|
||||||
const border = this.player.borderTileRefs()
|
const border = this.player.borderTiles()
|
||||||
const clusters: Set<TileRef>[] = []
|
const clusters: Set<TileRef>[] = []
|
||||||
for (const tile of border) {
|
for (const tile of border) {
|
||||||
if (seen.has(tile)) {
|
if (seen.has(tile)) {
|
||||||
@@ -191,12 +190,12 @@ export class PlayerExecution implements Execution {
|
|||||||
const curr = queue.shift()
|
const curr = queue.shift()
|
||||||
cluster.add(curr)
|
cluster.add(curr)
|
||||||
|
|
||||||
const neighbors = (this.mg as GameImpl).neighborsWithDiag(this.mg.fromRef(curr))
|
const neighbors = (this.mg as GameImpl).neighborsWithDiag(curr)
|
||||||
for (const neighbor of neighbors) {
|
for (const neighbor of neighbors) {
|
||||||
if (neighbor.isBorder() && border.has(neighbor.ref())) {
|
if (this.mg.isBorder(neighbor) && border.has(neighbor)) {
|
||||||
if (!seen.has(neighbor.ref())) {
|
if (!seen.has(neighbor)) {
|
||||||
queue.push(neighbor.ref())
|
queue.push(neighbor)
|
||||||
seen.add(neighbor.ref())
|
seen.add(neighbor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, TerrainType, Tile, Unit, UnitType } from "../game/Game";
|
import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, TerrainType, UnitType } from "../game/Game";
|
||||||
import { PathFinder } from "../pathfinding/PathFinding";
|
import { PathFinder } from "../pathfinding/PathFinding";
|
||||||
import { PathFindResultType } from "../pathfinding/AStar";
|
import { PathFindResultType } from "../pathfinding/AStar";
|
||||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
|
||||||
import { PseudoRandom } from "../PseudoRandom";
|
import { PseudoRandom } from "../PseudoRandom";
|
||||||
import { bfs, dist, manhattanDist } from "../Util";
|
|
||||||
import { TradeShipExecution } from "./TradeShipExecution";
|
import { TradeShipExecution } from "./TradeShipExecution";
|
||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
import { MiniAStar } from "../pathfinding/MiniAStar";
|
import { MiniAStar } from "../pathfinding/MiniAStar";
|
||||||
import { TileRef } from "../game/GameMap";
|
import { manhattanDistFN, TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export class PortExecution implements Execution {
|
export class PortExecution implements Execution {
|
||||||
|
|
||||||
@@ -15,12 +13,12 @@ export class PortExecution implements Execution {
|
|||||||
private mg: MutableGame
|
private mg: MutableGame
|
||||||
private port: MutableUnit
|
private port: MutableUnit
|
||||||
private random: PseudoRandom
|
private random: PseudoRandom
|
||||||
private portPaths = new Map<MutableUnit, Tile[]>()
|
private portPaths = new Map<MutableUnit, TileRef[]>()
|
||||||
private computingPaths = new Map<MutableUnit, MiniAStar>()
|
private computingPaths = new Map<MutableUnit, MiniAStar>()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _owner: PlayerID,
|
private _owner: PlayerID,
|
||||||
private cell: Cell,
|
private tile: TileRef,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
@@ -33,16 +31,16 @@ export class PortExecution implements Execution {
|
|||||||
|
|
||||||
if (this.port == null) {
|
if (this.port == null) {
|
||||||
// TODO: use canBuild
|
// TODO: use canBuild
|
||||||
const tile = this.mg.tile(this.cell)
|
const tile = this.tile
|
||||||
const player = this.mg.player(this._owner)
|
const player = this.mg.player(this._owner)
|
||||||
if (!player.canBuild(UnitType.Port, tile)) {
|
if (!player.canBuild(UnitType.Port, tile)) {
|
||||||
consolex.warn(`player ${player} cannot build port at ${this.cell}`)
|
consolex.warn(`player ${player} cannot build port at ${this.tile}`)
|
||||||
this.active = false
|
this.active = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const spawns = Array.from(bfs(tile, dist(tile, 20)))
|
const spawns = Array.from(this.mg.bfs(tile, manhattanDistFN(tile, 20)))
|
||||||
.filter(t => t.terrain().isOceanShore() && t.owner() == player)
|
.filter(t => this.mg.isOceanShore(t) && this.mg.owner(t) == player)
|
||||||
.sort((a, b) => manhattanDist(a.cell(), tile.cell()) - manhattanDist(b.cell(), tile.cell()))
|
.sort((a, b) => this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile))
|
||||||
|
|
||||||
if (spawns.length == 0) {
|
if (spawns.length == 0) {
|
||||||
consolex.warn(`cannot find spawn for port`)
|
consolex.warn(`cannot find spawn for port`)
|
||||||
@@ -73,7 +71,7 @@ export class PortExecution implements Execution {
|
|||||||
const aStar = this.computingPaths.get(port)
|
const aStar = this.computingPaths.get(port)
|
||||||
switch (aStar.compute()) {
|
switch (aStar.compute()) {
|
||||||
case PathFindResultType.Completed:
|
case PathFindResultType.Completed:
|
||||||
this.portPaths.set(port, aStar.reconstructPath().map(cell => this.mg.tile(cell)))
|
this.portPaths.set(port, aStar.reconstructPath())
|
||||||
this.computingPaths.delete(port)
|
this.computingPaths.delete(port)
|
||||||
break
|
break
|
||||||
case PathFindResultType.Pending:
|
case PathFindResultType.Pending:
|
||||||
@@ -88,8 +86,8 @@ export class PortExecution implements Execution {
|
|||||||
const pf = new MiniAStar(
|
const pf = new MiniAStar(
|
||||||
this.mg.map(),
|
this.mg.map(),
|
||||||
this.mg.miniMap(),
|
this.mg.miniMap(),
|
||||||
this.port.tile().ref(),
|
this.port.tile(),
|
||||||
port.tile().ref(),
|
port.tile(),
|
||||||
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
|
(tr: TileRef) => this.mg.miniMap().isOcean(tr),
|
||||||
10_000,
|
10_000,
|
||||||
25
|
25
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Execution, MutableGame, MutablePlayer, MutableUnit, Tile, Unit, UnitType } from "../game/Game";
|
import { Execution, MutableGame, MutablePlayer, MutableUnit, Unit, UnitType } from "../game/Game";
|
||||||
import { PathFinder } from "../pathfinding/PathFinding";
|
import { PathFinder } from "../pathfinding/PathFinding";
|
||||||
import { PathFindResultType } from "../pathfinding/AStar";
|
import { PathFindResultType } from "../pathfinding/AStar";
|
||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
|
import { TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export class ShellExecution implements Execution {
|
export class ShellExecution implements Execution {
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ export class ShellExecution implements Execution {
|
|||||||
private pathFinder: PathFinder
|
private pathFinder: PathFinder
|
||||||
private shell: MutableUnit
|
private shell: MutableUnit
|
||||||
|
|
||||||
constructor(private spawn: Tile, private _owner: MutablePlayer, private ownerUnit: Unit, private target: MutableUnit) {
|
constructor(private spawn: TileRef, private _owner: MutablePlayer, private ownerUnit: Unit, private target: MutableUnit) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Cell, Execution, MutableGame, MutablePlayer, PlayerInfo, PlayerType } from "../game/Game"
|
import { Cell, Execution, MutableGame, MutablePlayer, PlayerInfo, PlayerType } from "../game/Game"
|
||||||
|
import { TileRef } from "../game/GameMap"
|
||||||
import { BotExecution } from "./BotExecution"
|
import { BotExecution } from "./BotExecution"
|
||||||
import { PlayerExecution } from "./PlayerExecution"
|
import { PlayerExecution } from "./PlayerExecution"
|
||||||
import { getSpawnTiles } from "./Util"
|
import { getSpawnTiles } from "./Util"
|
||||||
@@ -10,7 +11,7 @@ export class SpawnExecution implements Execution {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private playerInfo: PlayerInfo,
|
private playerInfo: PlayerInfo,
|
||||||
private cell: Cell
|
private tile: TileRef
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
init(mg: MutableGame, ticks: number) {
|
init(mg: MutableGame, ticks: number) {
|
||||||
@@ -25,17 +26,16 @@ export class SpawnExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existing = this.mg.players().find(p => p.id() == this.playerInfo.id)
|
const existing = this.mg.players().find(p => p.id() == this.playerInfo.id)
|
||||||
const tile = this.mg.tile(this.cell)
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.tiles().forEach(t => existing.relinquish(t))
|
existing.tiles().forEach(t => existing.relinquish(t))
|
||||||
getSpawnTiles(tile).forEach(t => {
|
getSpawnTiles(this.mg, this.tile).forEach(t => {
|
||||||
existing.conquer(t)
|
existing.conquer(t)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const player = this.mg.addPlayer(this.playerInfo, this.mg.config().startManpower(this.playerInfo))
|
const player = this.mg.addPlayer(this.playerInfo, this.mg.config().startManpower(this.playerInfo))
|
||||||
getSpawnTiles(tile).forEach(t => {
|
getSpawnTiles(this.mg, this.tile).forEach(t => {
|
||||||
player.conquer(t)
|
player.conquer(t)
|
||||||
})
|
})
|
||||||
this.mg.addExecution(new PlayerExecution(player.id()))
|
this.mg.addExecution(new PlayerExecution(player.id()))
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { MessageType } from '../game/Game';
|
import { MessageType } from '../game/Game';
|
||||||
import { renderNumber } from "../../client/Utils";
|
import { renderNumber } from "../../client/Utils";
|
||||||
import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, Tile, Unit, UnitType } from "../game/Game";
|
import { AllPlayers, Cell, Execution, MutableGame, MutablePlayer, MutableUnit, Player, PlayerID, UnitType } from "../game/Game";
|
||||||
import { PathFinder } from "../pathfinding/PathFinding";
|
import { PathFinder } from "../pathfinding/PathFinding";
|
||||||
import { PathFindResultType } from "../pathfinding/AStar";
|
import { PathFindResultType } from "../pathfinding/AStar";
|
||||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
import { distSortUnit } from "../Util";
|
||||||
import { PseudoRandom } from "../PseudoRandom";
|
|
||||||
import { bfs, dist, distSortUnit, manhattanDist } from "../Util";
|
|
||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
|
import { TileRef } from '../game/GameMap';
|
||||||
|
|
||||||
export class TradeShipExecution implements Execution {
|
export class TradeShipExecution implements Execution {
|
||||||
|
|
||||||
@@ -23,7 +22,7 @@ export class TradeShipExecution implements Execution {
|
|||||||
private dstPort: MutableUnit,
|
private dstPort: MutableUnit,
|
||||||
private pathFinder: PathFinder,
|
private pathFinder: PathFinder,
|
||||||
// don't modify
|
// don't modify
|
||||||
private path: Tile[]
|
private path: TileRef[]
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ export class TradeShipExecution implements Execution {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.wasCaptured) {
|
if (this.wasCaptured) {
|
||||||
const ports = this.tradeShip.owner().units(UnitType.Port).sort(distSortUnit(this.tradeShip))
|
const ports = this.tradeShip.owner().units(UnitType.Port).sort(distSortUnit(this.mg, this.tradeShip))
|
||||||
if (ports.length == 0) {
|
if (ports.length == 0) {
|
||||||
this.tradeShip.delete(false)
|
this.tradeShip.delete(false)
|
||||||
this.active = false
|
this.active = false
|
||||||
@@ -70,7 +69,7 @@ export class TradeShipExecution implements Execution {
|
|||||||
const result = this.pathFinder.nextTile(this.tradeShip.tile(), dstPort.tile())
|
const result = this.pathFinder.nextTile(this.tradeShip.tile(), dstPort.tile())
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case PathFindResultType.Completed:
|
case PathFindResultType.Completed:
|
||||||
const gold = this.mg.config().tradeShipGold(this.srcPort, dstPort)
|
const gold = this.mg.config().tradeShipGold(this.mg.manhattanDist(this.srcPort.tile(), dstPort.tile()))
|
||||||
this.tradeShip.owner().addGold(gold)
|
this.tradeShip.owner().addGold(gold)
|
||||||
this.mg.displayMessage(
|
this.mg.displayMessage(
|
||||||
`Received ${renderNumber(gold)} gold from ship captured from ${this.origOwner.displayName()}`,
|
`Received ${renderNumber(gold)} gold from ship captured from ${this.origOwner.displayName()}`,
|
||||||
@@ -97,7 +96,7 @@ export class TradeShipExecution implements Execution {
|
|||||||
|
|
||||||
if (this.index >= this.path.length) {
|
if (this.index >= this.path.length) {
|
||||||
this.active = false
|
this.active = false
|
||||||
const gold = this.mg.config().tradeShipGold(this.srcPort, this.dstPort)
|
const gold = this.mg.config().tradeShipGold(this.mg.manhattanDist(this.srcPort.tile(), this.dstPort.tile()))
|
||||||
this.srcPort.owner().addGold(gold)
|
this.srcPort.owner().addGold(gold)
|
||||||
this.dstPort.owner().addGold(gold)
|
this.dstPort.owner().addGold(gold)
|
||||||
this.mg.displayMessage(
|
this.mg.displayMessage(
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Unit, Cell, Execution, MutableUnit, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, Tile, UnitType, TerrainType } from "../game/Game";
|
import { Unit, Cell, Execution, MutableUnit, MutableGame, MutablePlayer, Player, PlayerID, TerraNullius, UnitType, TerrainType } from "../game/Game";
|
||||||
import { and, bfs, manhattanDistWrapped, sourceDstOceanShore, targetTransportTile } from "../Util";
|
|
||||||
import { AttackExecution } from "./AttackExecution";
|
import { AttackExecution } from "./AttackExecution";
|
||||||
import { MessageType } from '../game/Game';
|
import { MessageType } from '../game/Game';
|
||||||
import { DisplayMessageUpdate } from '../game/Game';
|
|
||||||
import { PathFinder } from "../pathfinding/PathFinding";
|
import { PathFinder } from "../pathfinding/PathFinding";
|
||||||
import { PathFindResultType } from "../pathfinding/AStar";
|
import { PathFindResultType } from "../pathfinding/AStar";
|
||||||
import { SerialAStar } from "../pathfinding/SerialAStar";
|
|
||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
|
import { TileRef } from "../game/GameMap";
|
||||||
|
import { targetTransportTile } from "../Util";
|
||||||
|
|
||||||
export class TransportShipExecution implements Execution {
|
export class TransportShipExecution implements Execution {
|
||||||
|
|
||||||
@@ -22,9 +21,9 @@ export class TransportShipExecution implements Execution {
|
|||||||
private target: MutablePlayer | TerraNullius
|
private target: MutablePlayer | TerraNullius
|
||||||
|
|
||||||
// TODO make private
|
// TODO make private
|
||||||
public path: Tile[]
|
public path: TileRef[]
|
||||||
private src: Tile | null
|
private src: TileRef | null
|
||||||
private dst: Tile | null
|
private dst: TileRef | null
|
||||||
|
|
||||||
|
|
||||||
private boat: MutableUnit
|
private boat: MutableUnit
|
||||||
@@ -34,7 +33,7 @@ export class TransportShipExecution implements Execution {
|
|||||||
constructor(
|
constructor(
|
||||||
private attackerID: PlayerID,
|
private attackerID: PlayerID,
|
||||||
private targetID: PlayerID | null,
|
private targetID: PlayerID | null,
|
||||||
private cell: Cell,
|
private ref: TileRef,
|
||||||
private troops: number | null,
|
private troops: number | null,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ export class TransportShipExecution implements Execution {
|
|||||||
|
|
||||||
this.troops = Math.min(this.troops, this.attacker.troops())
|
this.troops = Math.min(this.troops, this.attacker.troops())
|
||||||
|
|
||||||
this.dst = targetTransportTile(this.mg.width(), this.mg.tile(this.cell))
|
this.dst = targetTransportTile(this.mg, this.ref)
|
||||||
if (this.dst == null) {
|
if (this.dst == null) {
|
||||||
consolex.warn(`${this.attacker} cannot send ship to ${this.target}, cannot find attack tile`)
|
consolex.warn(`${this.attacker} cannot send ship to ${this.target}, cannot find attack tile`)
|
||||||
this.active = false
|
this.active = false
|
||||||
@@ -102,7 +101,7 @@ export class TransportShipExecution implements Execution {
|
|||||||
const result = this.pathFinder.nextTile(this.boat.tile(), this.dst)
|
const result = this.pathFinder.nextTile(this.boat.tile(), this.dst)
|
||||||
switch (result.type) {
|
switch (result.type) {
|
||||||
case PathFindResultType.Completed:
|
case PathFindResultType.Completed:
|
||||||
if (this.dst.owner() == this.attacker) {
|
if (this.mg.owner(this.dst) == this.attacker) {
|
||||||
this.attacker.addTroops(this.troops)
|
this.attacker.addTroops(this.troops)
|
||||||
this.boat.delete(false)
|
this.boat.delete(false)
|
||||||
this.active = false
|
this.active = false
|
||||||
@@ -113,7 +112,7 @@ export class TransportShipExecution implements Execution {
|
|||||||
} else {
|
} else {
|
||||||
this.attacker.conquer(this.dst)
|
this.attacker.conquer(this.dst)
|
||||||
this.mg.addExecution(
|
this.mg.addExecution(
|
||||||
new AttackExecution(this.troops, this.attacker.id(), this.targetID, this.dst.cell(), null, false)
|
new AttackExecution(this.troops, this.attacker.id(), this.targetID, this.dst, false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
this.boat.delete(false)
|
this.boat.delete(false)
|
||||||
|
|||||||
+10
-11
@@ -1,15 +1,14 @@
|
|||||||
import { Game, Cell, Tile } from "../game/Game";
|
import { euclDistFN, GameMap, TileRef } from "../game/GameMap";
|
||||||
import { and, bfs, euclDist } from "../Util";
|
|
||||||
|
|
||||||
|
|
||||||
export function getSpawnTiles(tile: Tile): Tile[] {
|
export function getSpawnTiles(gm: GameMap, tile: TileRef): TileRef[] {
|
||||||
return Array.from(bfs(tile, euclDist(tile, 4)))
|
return Array.from(gm.bfs(tile, euclDistFN(tile, 4)))
|
||||||
.filter(t => !t.hasOwner() && t.terrain().isLand())
|
.filter(t => !gm.hasOwner(t) && gm.isLand(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closestTwoTiles(x: Iterable<Tile>, y: Iterable<Tile>): { x: Tile, y: Tile } {
|
export function closestTwoTiles(gm: GameMap, x: Iterable<TileRef>, y: Iterable<TileRef>): { x: TileRef, y: TileRef } {
|
||||||
const xSorted = Array.from(x).sort((a, b) => a.cell().x - b.cell().x);
|
const xSorted = Array.from(x).sort((a, b) => gm.x(a) - gm.x(b));
|
||||||
const ySorted = Array.from(y).sort((a, b) => a.cell().x - b.cell().x);
|
const ySorted = Array.from(y).sort((a, b) => gm.x(a) - gm.x(b));
|
||||||
|
|
||||||
if (xSorted.length == 0 || ySorted.length == 0) {
|
if (xSorted.length == 0 || ySorted.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -25,8 +24,8 @@ export function closestTwoTiles(x: Iterable<Tile>, y: Iterable<Tile>): { x: Tile
|
|||||||
const currentY = ySorted[j];
|
const currentY = ySorted[j];
|
||||||
|
|
||||||
const distance =
|
const distance =
|
||||||
Math.abs(currentX.cell().x - currentY.cell().x) +
|
Math.abs(gm.x(currentX) - gm.x(currentY)) +
|
||||||
Math.abs(currentX.cell().y - currentY.cell().y);
|
Math.abs(gm.y(currentX) - gm.y(currentY));
|
||||||
|
|
||||||
if (distance < minDistance) {
|
if (distance < minDistance) {
|
||||||
minDistance = distance;
|
minDistance = distance;
|
||||||
@@ -42,7 +41,7 @@ export function closestTwoTiles(x: Iterable<Tile>, y: Iterable<Tile>): { x: Tile
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
// Otherwise, move whichever pointer has smaller x value
|
// Otherwise, move whichever pointer has smaller x value
|
||||||
else if (currentX.cell().x < currentY.cell().x) {
|
else if (gm.x(currentX) < gm.x(currentY)) {
|
||||||
i++;
|
i++;
|
||||||
} else {
|
} else {
|
||||||
j++;
|
j++;
|
||||||
|
|||||||
+22
-82
@@ -1,7 +1,7 @@
|
|||||||
import { Config } from "../configuration/Config"
|
import { Config } from "../configuration/Config"
|
||||||
import { GameEvent } from "../EventBus"
|
import { GameEvent } from "../EventBus"
|
||||||
import { ClientID, GameConfig, GameID } from "../Schemas"
|
import { ClientID, GameConfig, GameID } from "../Schemas"
|
||||||
import { GameMap, GameMapImpl, TileRef } from "./GameMap"
|
import { GameMap, GameMapImpl, TileRef, TileUpdate } from "./GameMap"
|
||||||
|
|
||||||
export type PlayerID = string
|
export type PlayerID = string
|
||||||
export type Tick = number
|
export type Tick = number
|
||||||
@@ -15,7 +15,7 @@ type UpdateTypeMap<T extends GameUpdateType> = Extract<GameUpdate, { type: T }>;
|
|||||||
|
|
||||||
// Then use it to create the record type
|
// Then use it to create the record type
|
||||||
export type GameUpdates = {
|
export type GameUpdates = {
|
||||||
[K in GameUpdateType]: UpdateTypeMap<K>[];
|
[K in GameUpdateType]: UpdateTypeMap<K>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MapPos {
|
export interface MapPos {
|
||||||
@@ -176,76 +176,26 @@ export class PlayerInfo {
|
|||||||
) { }
|
) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TerrainMap {
|
|
||||||
terrain(cell: Cell): TerrainTile
|
|
||||||
neighbors(terrainTile: TerrainTile): TerrainTile[]
|
|
||||||
width(): number
|
|
||||||
height(): number
|
|
||||||
isOnMap(cell: Cell): boolean
|
|
||||||
numLandTiles(): number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TerrainTileKey = string
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export interface TerrainTile {
|
|
||||||
isLand(): boolean
|
|
||||||
isShore(): boolean
|
|
||||||
isOceanShore(): boolean
|
|
||||||
isWater(): boolean
|
|
||||||
isShorelineWater(): boolean
|
|
||||||
isOcean(): boolean
|
|
||||||
isLake(): boolean
|
|
||||||
type(): TerrainType
|
|
||||||
magnitude(): number
|
|
||||||
equals(other: TerrainTile): boolean
|
|
||||||
cell(): Cell
|
|
||||||
neighbors(): TerrainTile[]
|
|
||||||
cost(): number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DefenseBonus {
|
export interface DefenseBonus {
|
||||||
// Unit providing the defense bonus
|
// Unit providing the defense bonus
|
||||||
unit: Unit
|
unit: Unit
|
||||||
amount: number
|
amount: number
|
||||||
tile: Tile
|
tile: TileRef
|
||||||
}
|
|
||||||
|
|
||||||
export interface Tile {
|
|
||||||
owner(): Player | TerraNullius
|
|
||||||
hasOwner(): boolean
|
|
||||||
isBorder(): boolean
|
|
||||||
cell(): Cell
|
|
||||||
hasFallout(): boolean
|
|
||||||
terrain(): TerrainTile
|
|
||||||
neighbors(): Tile[]
|
|
||||||
hasDefenseBonus(): boolean
|
|
||||||
ref(): TileRef
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MutableTile extends Tile {
|
|
||||||
// defense bonus against this player
|
|
||||||
defenseBonus(player: Player): number
|
|
||||||
borders(other: Player | TerraNullius): boolean
|
|
||||||
neighborsWrapped(): Tile[]
|
|
||||||
defenseBonuses(): DefenseBonus[]
|
|
||||||
toUpdate(isBorderOnly: boolean): TileUpdate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Unit {
|
export interface Unit {
|
||||||
type(): UnitType
|
type(): UnitType
|
||||||
troops(): number
|
troops(): number
|
||||||
tile(): Tile
|
tile(): TileRef
|
||||||
owner(): Player
|
owner(): Player
|
||||||
isActive(): boolean
|
isActive(): boolean
|
||||||
hasHealth(): boolean
|
hasHealth(): boolean
|
||||||
health(): number
|
health(): number
|
||||||
lastTile(): Tile
|
lastTile(): TileRef
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MutableUnit extends Unit {
|
export interface MutableUnit extends Unit {
|
||||||
move(tile: Tile): void
|
move(tile: TileRef): void
|
||||||
owner(): MutablePlayer
|
owner(): MutablePlayer
|
||||||
setTroops(troops: number): void
|
setTroops(troops: number): void
|
||||||
info(): UnitInfo
|
info(): UnitInfo
|
||||||
@@ -255,7 +205,6 @@ export interface MutableUnit extends Unit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TerraNullius {
|
export interface TerraNullius {
|
||||||
ownsTile(cell: Cell): boolean
|
|
||||||
isPlayer(): false
|
isPlayer(): false
|
||||||
id(): PlayerID // always zero, maybe make it TerraNulliusID?
|
id(): PlayerID // always zero, maybe make it TerraNulliusID?
|
||||||
clientID(): ClientID
|
clientID(): ClientID
|
||||||
@@ -272,8 +221,7 @@ export interface Player {
|
|||||||
type(): PlayerType
|
type(): PlayerType
|
||||||
units(...types: UnitType[]): Unit[]
|
units(...types: UnitType[]): Unit[]
|
||||||
isAlive(): boolean
|
isAlive(): boolean
|
||||||
borderTileRefs(): ReadonlySet<TileRef>
|
borderTiles(): ReadonlySet<TileRef>
|
||||||
borderTiles(): ReadonlySet<Tile>
|
|
||||||
isPlayer(): this is Player
|
isPlayer(): this is Player
|
||||||
numTilesOwned(): number
|
numTilesOwned(): number
|
||||||
sharesBorderWith(other: Player | TerraNullius): boolean
|
sharesBorderWith(other: Player | TerraNullius): boolean
|
||||||
@@ -306,7 +254,7 @@ export interface Player {
|
|||||||
troops(): number
|
troops(): number
|
||||||
|
|
||||||
// If can build returns the spawn tile, false otherwise
|
// If can build returns the spawn tile, false otherwise
|
||||||
canBuild(type: UnitType, targetTile: Tile): Tile | false
|
canBuild(type: UnitType, targetTile: TileRef): TileRef | false
|
||||||
lastTileChange(): Tick
|
lastTileChange(): Tick
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,11 +263,9 @@ export interface MutablePlayer extends Player {
|
|||||||
targets(): Player[]
|
targets(): Player[]
|
||||||
// Targets of player and all allies.
|
// Targets of player and all allies.
|
||||||
neighbors(): (Player | TerraNullius)[]
|
neighbors(): (Player | TerraNullius)[]
|
||||||
tiles(): ReadonlySet<MutableTile>
|
tiles(): ReadonlySet<TileRef>
|
||||||
ownsTile(cell: Cell): boolean
|
conquer(tile: TileRef): void
|
||||||
tiles(): ReadonlySet<Tile>
|
relinquish(tile: TileRef): void
|
||||||
conquer(tile: Tile): void
|
|
||||||
relinquish(tile: Tile): void
|
|
||||||
executions(): Execution[]
|
executions(): Execution[]
|
||||||
neighbors(): (MutablePlayer | TerraNullius)[]
|
neighbors(): (MutablePlayer | TerraNullius)[]
|
||||||
units(...types: UnitType[]): MutableUnit[]
|
units(...types: UnitType[]): MutableUnit[]
|
||||||
@@ -348,7 +294,7 @@ export interface MutablePlayer extends Player {
|
|||||||
addTroops(troops: number): void
|
addTroops(troops: number): void
|
||||||
removeTroops(troops: number): number
|
removeTroops(troops: number): number
|
||||||
|
|
||||||
buildUnit(type: UnitType, troops: number, tile: Tile): MutableUnit
|
buildUnit(type: UnitType, troops: number, tile: TileRef): MutableUnit
|
||||||
captureUnit(unit: MutableUnit): void
|
captureUnit(unit: MutableUnit): void
|
||||||
|
|
||||||
toUpdate(): PlayerUpdate
|
toUpdate(): PlayerUpdate
|
||||||
@@ -360,11 +306,10 @@ export interface Game extends GameMap {
|
|||||||
playerByClientID(id: ClientID): Player | null
|
playerByClientID(id: ClientID): Player | null
|
||||||
hasPlayer(id: PlayerID): boolean
|
hasPlayer(id: PlayerID): boolean
|
||||||
players(): Player[]
|
players(): Player[]
|
||||||
tile(cell: Cell): Tile
|
|
||||||
isOnMap(cell: Cell): boolean
|
isOnMap(cell: Cell): boolean
|
||||||
width(): number
|
width(): number
|
||||||
height(): number
|
height(): number
|
||||||
forEachTile(fn: (tile: Tile) => void): void
|
forEachTile(fn: (tile: TileRef) => void): void
|
||||||
executions(): ExecutionView[]
|
executions(): ExecutionView[]
|
||||||
terraNullius(): TerraNullius
|
terraNullius(): TerraNullius
|
||||||
executeNextTick(): GameUpdates
|
executeNextTick(): GameUpdates
|
||||||
@@ -377,13 +322,12 @@ export interface Game extends GameMap {
|
|||||||
units(...types: UnitType[]): Unit[]
|
units(...types: UnitType[]): Unit[]
|
||||||
unitInfo(type: UnitType): UnitInfo
|
unitInfo(type: UnitType): UnitInfo
|
||||||
playerBySmallID(id: number): Player | TerraNullius
|
playerBySmallID(id: number): Player | TerraNullius
|
||||||
fromRef(ref: TileRef): Tile
|
map(): GameMap
|
||||||
map(): GameMapImpl
|
miniMap(): GameMap
|
||||||
miniMap(): GameMapImpl
|
owner(ref: TileRef): Player | TerraNullius
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MutableGame extends Game {
|
export interface MutableGame extends Game {
|
||||||
tile(cell: Cell): MutableTile
|
|
||||||
player(id: PlayerID): MutablePlayer
|
player(id: PlayerID): MutablePlayer
|
||||||
playerByClientID(id: ClientID): MutablePlayer | null
|
playerByClientID(id: ClientID): MutablePlayer | null
|
||||||
players(): MutablePlayer[]
|
players(): MutablePlayer[]
|
||||||
@@ -391,9 +335,9 @@ export interface MutableGame extends Game {
|
|||||||
addPlayer(playerInfo: PlayerInfo, manpower: number): MutablePlayer
|
addPlayer(playerInfo: PlayerInfo, manpower: number): MutablePlayer
|
||||||
executions(): Execution[]
|
executions(): Execution[]
|
||||||
units(...types: UnitType[]): MutableUnit[]
|
units(...types: UnitType[]): MutableUnit[]
|
||||||
addTileDefenseBonus(tile: Tile, unit: Unit, amount: number): DefenseBonus
|
addTileDefenseBonus(tile: TileRef, unit: Unit, amount: number): DefenseBonus
|
||||||
removeTileDefenseBonus(bonus: DefenseBonus): void
|
removeTileDefenseBonus(bonus: DefenseBonus): void
|
||||||
addFallout(tile: Tile): void
|
addFallout(tile: TileRef): void
|
||||||
setWinner(winner: Player): void
|
setWinner(winner: Player): void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +382,7 @@ export interface PlayerInteraction {
|
|||||||
canDonate: boolean
|
canDonate: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GameUpdate = TileUpdate
|
export type GameUpdate = TileUpdateWrapper
|
||||||
| UnitUpdate
|
| UnitUpdate
|
||||||
| PlayerUpdate
|
| PlayerUpdate
|
||||||
| AllianceRequestUpdate
|
| AllianceRequestUpdate
|
||||||
@@ -450,13 +394,9 @@ export type GameUpdate = TileUpdate
|
|||||||
| EmojiUpdate
|
| EmojiUpdate
|
||||||
| WinUpdate
|
| WinUpdate
|
||||||
|
|
||||||
export interface TileUpdate {
|
export interface TileUpdateWrapper {
|
||||||
type: GameUpdateType.Tile
|
type: GameUpdateType.Tile,
|
||||||
ownerID: number
|
update: TileUpdate
|
||||||
pos: MapPos
|
|
||||||
isBorder: boolean
|
|
||||||
hasFallout: boolean
|
|
||||||
hasDefenseBonus: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnitUpdate {
|
export interface UnitUpdate {
|
||||||
|
|||||||
+75
-74
@@ -1,18 +1,17 @@
|
|||||||
import { Config } from "../configuration/Config";
|
import { Config } from "../configuration/Config";
|
||||||
import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerID, PlayerInfo, Player, TerraNullius, Tile, Unit, MutableAllianceRequest, Alliance, Nation, UnitType, UnitInfo, TerrainMap, DefenseBonus, MutableTile, GameUpdate, GameUpdateType, AllPlayers, GameUpdates } from "./Game";
|
import { Cell, Execution, MutableGame, Game, MutablePlayer, PlayerID, PlayerInfo, Player, TerraNullius, Unit, MutableAllianceRequest, Alliance, Nation, UnitType, UnitInfo, DefenseBonus, GameUpdate, GameUpdateType, AllPlayers, GameUpdates, TerrainType } from "./Game";
|
||||||
import { NationMap, TerrainMapImpl } from "./TerrainMapLoader";
|
import { NationMap } from "./TerrainMapLoader";
|
||||||
import { PlayerImpl } from "./PlayerImpl";
|
import { PlayerImpl } from "./PlayerImpl";
|
||||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||||
import { TileImpl } from "./TileImpl";
|
|
||||||
import { AllianceRequestImpl } from "./AllianceRequestImpl";
|
import { AllianceRequestImpl } from "./AllianceRequestImpl";
|
||||||
import { AllianceImpl } from "./AllianceImpl";
|
import { AllianceImpl } from "./AllianceImpl";
|
||||||
import { ClientID, GameConfig } from "../Schemas";
|
import { ClientID, GameConfig } from "../Schemas";
|
||||||
import { MessageType } from './Game';
|
import { MessageType } from './Game';
|
||||||
import { UnitImpl } from "./UnitImpl";
|
import { UnitImpl } from "./UnitImpl";
|
||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
import { GameMapImpl, TileRef } from "./GameMap";
|
import { GameMap, GameMapImpl, TileRef, TileUpdate } from "./GameMap";
|
||||||
|
|
||||||
export function createGame(gameMap: GameMapImpl, miniGameMap: GameMapImpl, nationMap: NationMap, config: Config): Game {
|
export function createGame(gameMap: GameMap, miniGameMap: GameMap, nationMap: NationMap, config: Config): Game {
|
||||||
return new GameImpl(gameMap, miniGameMap, nationMap, config)
|
return new GameImpl(gameMap, miniGameMap, nationMap, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +42,8 @@ export class GameImpl implements MutableGame {
|
|||||||
private updates: GameUpdates = createGameUpdatesMap()
|
private updates: GameUpdates = createGameUpdatesMap()
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _map: GameMapImpl,
|
private _map: GameMap,
|
||||||
private miniGameMap: GameMapImpl,
|
private miniGameMap: GameMap,
|
||||||
nationMap: NationMap,
|
nationMap: NationMap,
|
||||||
private _config: Config,
|
private _config: Config,
|
||||||
) {
|
) {
|
||||||
@@ -58,16 +57,19 @@ export class GameImpl implements MutableGame {
|
|||||||
n.strength
|
n.strength
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
owner(ref: TileRef): Player | TerraNullius {
|
||||||
|
return this.playerBySmallID(this.ownerID(ref))
|
||||||
|
}
|
||||||
playerBySmallID(id: number): Player | TerraNullius {
|
playerBySmallID(id: number): Player | TerraNullius {
|
||||||
if (id == 0) {
|
if (id == 0) {
|
||||||
return this.terraNullius()
|
return this.terraNullius()
|
||||||
}
|
}
|
||||||
return this._playersBySmallID[id - 1]
|
return this._playersBySmallID[id - 1]
|
||||||
}
|
}
|
||||||
map(): GameMapImpl {
|
map(): GameMap {
|
||||||
return this._map
|
return this._map
|
||||||
}
|
}
|
||||||
miniMap(): GameMapImpl {
|
miniMap(): GameMap {
|
||||||
return this.miniGameMap
|
return this.miniGameMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,16 +84,18 @@ export class GameImpl implements MutableGame {
|
|||||||
return old
|
return old
|
||||||
}
|
}
|
||||||
|
|
||||||
addFallout(tile: Tile) {
|
addFallout(tile: TileRef) {
|
||||||
const ti = tile as TileImpl
|
if (this.hasOwner(tile)) {
|
||||||
if (tile.hasOwner()) {
|
|
||||||
throw Error(`cannot set fallout, tile ${tile} has owner`)
|
throw Error(`cannot set fallout, tile ${tile} has owner`)
|
||||||
}
|
}
|
||||||
this._map.setFallout(tile.ref(), true)
|
this._map.setFallout(tile, true)
|
||||||
this.addUpdate(ti.toUpdate())
|
this.addUpdate({
|
||||||
|
type: GameUpdateType.Tile,
|
||||||
|
update: this.toTileUpdate(tile)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addTileDefenseBonus(tile: Tile, unit: Unit, amount: number): DefenseBonus {
|
addTileDefenseBonus(tile: TileRef, unit: Unit, amount: number): DefenseBonus {
|
||||||
// TODO!!
|
// TODO!!
|
||||||
const df = { unit: unit, tile: tile, amount: amount };
|
const df = { unit: unit, tile: tile, amount: amount };
|
||||||
// (tile as TileImpl)._defenseBonuses.push(df)
|
// (tile as TileImpl)._defenseBonuses.push(df)
|
||||||
@@ -257,13 +261,6 @@ export class GameImpl implements MutableGame {
|
|||||||
this.unInitExecs = this.unInitExecs.filter(execution => execution !== exec)
|
this.unInitExecs = this.unInitExecs.filter(execution => execution !== exec)
|
||||||
}
|
}
|
||||||
|
|
||||||
forEachTile(fn: (tile: Tile) => void): void {
|
|
||||||
for (let x = 0; x < this._width; x++) {
|
|
||||||
for (let y = 0; y < this._height; y++) {
|
|
||||||
fn(this.tile(new Cell(x, y)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
playerView(id: PlayerID): MutablePlayer {
|
playerView(id: PlayerID): MutablePlayer {
|
||||||
return this.player(id)
|
return this.player(id)
|
||||||
@@ -294,11 +291,6 @@ export class GameImpl implements MutableGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
tile(cell: Cell): MutableTile {
|
|
||||||
this.assertIsOnMap(cell)
|
|
||||||
return new TileImpl(this, this._map.ref(cell.x, cell.y))
|
|
||||||
}
|
|
||||||
|
|
||||||
isOnMap(cell: Cell): boolean {
|
isOnMap(cell: Cell): boolean {
|
||||||
return cell.x >= 0
|
return cell.x >= 0
|
||||||
&& cell.x < this._width
|
&& cell.x < this._width
|
||||||
@@ -306,22 +298,17 @@ export class GameImpl implements MutableGame {
|
|||||||
&& cell.y < this._height
|
&& cell.y < this._height
|
||||||
}
|
}
|
||||||
|
|
||||||
fromRef(ref: TileRef): Tile {
|
neighborsWithDiag(tile: TileRef): TileRef[] {
|
||||||
return new TileImpl(this, ref)
|
const x = this.x(tile)
|
||||||
}
|
const y = this.y(tile)
|
||||||
|
const ns: TileRef[] = []
|
||||||
|
|
||||||
neighborsWithDiag(tile: Tile): Tile[] {
|
|
||||||
const x = tile.cell().x
|
|
||||||
const y = tile.cell().y
|
|
||||||
const ns: Tile[] = []
|
|
||||||
for (let dx = -1; dx <= 1; dx++) {
|
for (let dx = -1; dx <= 1; dx++) {
|
||||||
for (let dy = -1; dy <= 1; dy++) {
|
for (let dy = -1; dy <= 1; dy++) {
|
||||||
if (dx === 0 && dy === 0) continue // Skip the center tile
|
if (dx === 0 && dy === 0) continue // Skip the center tile
|
||||||
const newX = x + dx
|
const newX = x + dx
|
||||||
const newY = y + dy
|
const newY = y + dy
|
||||||
if (newX >= 0 && newX < this._width && newY >= 0 && newY < this._height) {
|
if (newX >= 0 && newX < this._width && newY >= 0 && newY < this._height) {
|
||||||
ns.push(this.fromRef(this._map.ref(newX, newY)))
|
ns.push(this._map.ref(newX, newY))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,75 +321,78 @@ export class GameImpl implements MutableGame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conquer(owner: PlayerImpl, tile: Tile): void {
|
conquer(owner: PlayerImpl, tile: TileRef): void {
|
||||||
if (!tile.terrain().isLand()) {
|
if (!this.isLand(tile)) {
|
||||||
throw Error(`cannot conquer water`)
|
throw Error(`cannot conquer water`)
|
||||||
}
|
}
|
||||||
const tileImpl = tile as TileImpl
|
let previousOwner = this.owner(tile) as TerraNullius | PlayerImpl
|
||||||
let previousOwner = tileImpl.owner() as TerraNullius | PlayerImpl
|
|
||||||
if (previousOwner.isPlayer()) {
|
if (previousOwner.isPlayer()) {
|
||||||
previousOwner._lastTileChange = this._ticks
|
previousOwner._lastTileChange = this._ticks
|
||||||
previousOwner._tiles.delete(tile.cell().toString())
|
previousOwner._tiles.delete(tile)
|
||||||
previousOwner._borderTiles.delete(tileImpl.ref())
|
previousOwner._borderTiles.delete(tile)
|
||||||
this._map.setBorder(tileImpl.ref(), false)
|
this._map.setBorder(tile, false)
|
||||||
}
|
}
|
||||||
this._map.setOwnerID(tileImpl.ref(), owner.smallID())
|
this._map.setOwnerID(tile, owner.smallID())
|
||||||
owner._tiles.set(tile.cell().toString(), tile)
|
owner._tiles.add(tile)
|
||||||
owner._lastTileChange = this._ticks
|
owner._lastTileChange = this._ticks
|
||||||
this.updateBorders(tile)
|
this.updateBorders(tile)
|
||||||
this._map.setFallout(tileImpl.ref(), false)
|
this._map.setFallout(tile, false)
|
||||||
this.addUpdate((tile as TileImpl).toUpdate())
|
this.addUpdate({
|
||||||
|
type: GameUpdateType.Tile,
|
||||||
|
update: this.toTileUpdate(tile)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
relinquish(tile: Tile) {
|
relinquish(tile: TileRef) {
|
||||||
if (!tile.hasOwner()) {
|
if (!this.hasOwner(tile)) {
|
||||||
throw new Error(`Cannot relinquish tile because it is unowned: cell ${tile.cell().toString()}`)
|
throw new Error(`Cannot relinquish tile because it is unowned`)
|
||||||
}
|
}
|
||||||
if (tile.terrain().isWater()) {
|
if (this.isWater(tile)) {
|
||||||
throw new Error("Cannot relinquish water")
|
throw new Error("Cannot relinquish water")
|
||||||
}
|
}
|
||||||
|
|
||||||
const tileImpl = tile as TileImpl
|
let previousOwner = this.owner(tile) as PlayerImpl
|
||||||
let previousOwner = tileImpl.owner() as PlayerImpl
|
|
||||||
previousOwner._lastTileChange = this._ticks
|
previousOwner._lastTileChange = this._ticks
|
||||||
previousOwner._tiles.delete(tile.cell().toString())
|
previousOwner._tiles.delete(tile)
|
||||||
previousOwner._borderTiles.delete(tileImpl.ref())
|
previousOwner._borderTiles.delete(tile)
|
||||||
this._map.setBorder(tileImpl.ref(), false)
|
this._map.setBorder(tile, false)
|
||||||
|
|
||||||
this._map.setOwnerID(tileImpl.ref(), 0)
|
this._map.setOwnerID(tile, 0)
|
||||||
this.updateBorders(tile)
|
this.updateBorders(tile)
|
||||||
this.addUpdate(
|
this.addUpdate({
|
||||||
(tile as TileImpl).toUpdate()
|
type: GameUpdateType.Tile,
|
||||||
|
update: this.toTileUpdate(tile)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateBorders(tile: Tile) {
|
private updateBorders(tile: TileRef) {
|
||||||
const tiles: TileImpl[] = []
|
const tiles: TileRef[] = []
|
||||||
tiles.push(tile as TileImpl)
|
tiles.push(tile)
|
||||||
tile.neighbors().forEach(t => tiles.push(t as TileImpl))
|
this.neighbors(tile).forEach(t => tiles.push(t))
|
||||||
|
|
||||||
for (const t of tiles) {
|
for (const t of tiles) {
|
||||||
if (!t.hasOwner()) {
|
if (!this.hasOwner(t)) {
|
||||||
this._map.setBorder(t.ref(), false)
|
this._map.setBorder(t, false)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (this.calcIsBorder(t)) {
|
if (this.calcIsBorder(t)) {
|
||||||
(t.owner() as PlayerImpl)._borderTiles.add(t.ref());
|
(this.owner(t) as PlayerImpl)._borderTiles.add(t);
|
||||||
this._map.setBorder(t.ref(), true)
|
this._map.setBorder(t, true)
|
||||||
} else {
|
} else {
|
||||||
(t.owner() as PlayerImpl)._borderTiles.delete(t.ref());
|
(this.owner(t) as PlayerImpl)._borderTiles.delete(t);
|
||||||
this._map.setBorder(t.ref(), false)
|
this._map.setBorder(t, false)
|
||||||
}
|
}
|
||||||
// this.updates.push(t.toUpdate())
|
// this.updates.push(t.toUpdate())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private calcIsBorder(tile: Tile): boolean {
|
private calcIsBorder(tile: TileRef): boolean {
|
||||||
if (!tile.hasOwner()) {
|
if (!this.hasOwner(tile)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for (const neighbor of (tile as MutableTile).neighbors()) {
|
for (const neighbor of this.neighbors(tile)) {
|
||||||
let bordersEnemy = tile.owner() != neighbor.owner()
|
let bordersEnemy = this.owner(tile) != this.owner(neighbor)
|
||||||
if (bordersEnemy) {
|
if (bordersEnemy) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -517,6 +507,17 @@ export class GameImpl implements MutableGame {
|
|||||||
isBorder(ref: TileRef): boolean { return this._map.isBorder(ref) }
|
isBorder(ref: TileRef): boolean { return this._map.isBorder(ref) }
|
||||||
setBorder(ref: TileRef, value: boolean): void { return this._map.setBorder(ref, value) }
|
setBorder(ref: TileRef, value: boolean): void { return this._map.setBorder(ref, value) }
|
||||||
neighbors(ref: TileRef): TileRef[] { return this._map.neighbors(ref) }
|
neighbors(ref: TileRef): TileRef[] { return this._map.neighbors(ref) }
|
||||||
|
isWater(ref: TileRef): boolean { return this._map.isWater(ref) }
|
||||||
|
isLake(ref: TileRef): boolean { return this._map.isLake(ref) }
|
||||||
|
isShore(ref: TileRef): boolean { return this._map.isShore(ref) }
|
||||||
|
cost(ref: TileRef): number { return this._map.cost(ref) }
|
||||||
|
terrainType(ref: TileRef): TerrainType { return this._map.terrainType(ref) }
|
||||||
|
forEachTile(fn: (tile: TileRef) => void): void { return this._map.forEachTile(fn) }
|
||||||
|
manhattanDist(c1: TileRef, c2: TileRef): number { return this._map.manhattanDist(c1, c2) }
|
||||||
|
euclideanDist(c1: TileRef, c2: TileRef): number { return this._map.euclideanDist(c1, c2) }
|
||||||
|
bfs(tile: TileRef, filter: (gm: GameMap, tile: TileRef) => boolean): Set<TileRef> { return this._map.bfs(tile, filter) }
|
||||||
|
toTileUpdate(tile: TileRef): bigint { return this._map.toTileUpdate(tile) }
|
||||||
|
updateTile(tu: TileUpdate): TileRef { return this._map.updateTile(tu) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Or a more dynamic approach that will catch new enum values:
|
// Or a more dynamic approach that will catch new enum values:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Cell, TerrainType } from "./Game";
|
import { Cell, TerrainType } from "./Game";
|
||||||
|
|
||||||
export type TileRef = number;
|
export type TileRef = number;
|
||||||
|
export type TileUpdate = bigint
|
||||||
|
|
||||||
export interface GameMap {
|
export interface GameMap {
|
||||||
ref(x: number, y: number): TileRef
|
ref(x: number, y: number): TileRef
|
||||||
@@ -29,6 +30,20 @@ export interface GameMap {
|
|||||||
isBorder(ref: TileRef): boolean
|
isBorder(ref: TileRef): boolean
|
||||||
setBorder(ref: TileRef, value: boolean): void
|
setBorder(ref: TileRef, value: boolean): void
|
||||||
neighbors(ref: TileRef): TileRef[]
|
neighbors(ref: TileRef): TileRef[]
|
||||||
|
isWater(ref: TileRef): boolean
|
||||||
|
isLake(ref: TileRef): boolean
|
||||||
|
isShore(ref: TileRef): boolean
|
||||||
|
cost(ref: TileRef): number
|
||||||
|
terrainType(ref: TileRef): TerrainType
|
||||||
|
forEachTile(fn: (tile: TileRef) => void): void
|
||||||
|
|
||||||
|
|
||||||
|
manhattanDist(c1: TileRef, c2: TileRef): number
|
||||||
|
euclideanDist(c1: TileRef, c2: TileRef): number
|
||||||
|
bfs(tile: TileRef, filter: (gm: GameMap, tile: TileRef) => boolean): Set<TileRef>
|
||||||
|
|
||||||
|
toTileUpdate(tile: TileRef): bigint
|
||||||
|
updateTile(tu: TileUpdate): TileRef
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GameMapImpl implements GameMap {
|
export class GameMapImpl implements GameMap {
|
||||||
@@ -180,7 +195,7 @@ export class GameMapImpl implements GameMap {
|
|||||||
return this.magnitude(ref) < 10 ? 2 : 1;
|
return this.magnitude(ref) < 10 ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTerrainType(ref: TileRef): TerrainType {
|
terrainType(ref: TileRef): TerrainType {
|
||||||
if (this.isLand(ref)) {
|
if (this.isLand(ref)) {
|
||||||
const magnitude = this.magnitude(ref);
|
const magnitude = this.magnitude(ref);
|
||||||
if (magnitude < 10) return TerrainType.Plains;
|
if (magnitude < 10) return TerrainType.Plains;
|
||||||
@@ -205,4 +220,64 @@ export class GameMapImpl implements GameMap {
|
|||||||
|
|
||||||
return neighbors;
|
return neighbors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forEachTile(fn: (tile: TileRef) => void): void {
|
||||||
|
for (let x = 0; x < this.width_; x++) {
|
||||||
|
for (let y = 0; y < this.height_; y++) {
|
||||||
|
fn(this.ref(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manhattanDist(c1: TileRef, c2: TileRef): number {
|
||||||
|
return Math.abs(this.x(c1) - this.x(c2)) + Math.abs(this.y(c1) - this.y(c2));
|
||||||
|
}
|
||||||
|
euclideanDist(c1: TileRef, c2: TileRef): number {
|
||||||
|
return Math.sqrt(Math.pow(this.x(c1) - this.x(c2), 2) + Math.pow(this.y(c1) - this.y(c2), 2));
|
||||||
|
}
|
||||||
|
bfs(tile: TileRef, filter: (gm: GameMap, tile: TileRef) => boolean): Set<TileRef> {
|
||||||
|
const seen = new Set<TileRef>()
|
||||||
|
const q: TileRef[] = []
|
||||||
|
q.push(tile)
|
||||||
|
while (q.length > 0) {
|
||||||
|
const curr = q.pop()
|
||||||
|
seen.add(curr)
|
||||||
|
for (const n of this.neighbors(curr)) {
|
||||||
|
if (!seen.has(n) && filter(this, n)) {
|
||||||
|
q.push(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return seen
|
||||||
|
}
|
||||||
|
|
||||||
|
toTileUpdate(tile: TileRef): bigint {
|
||||||
|
// Pack the tile reference and state into a bigint
|
||||||
|
// Format: [32 bits for tile reference][16 bits for state]
|
||||||
|
return (BigInt(tile) << 16n) | BigInt(this.state[tile]);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTile(tu: TileUpdate): TileRef {
|
||||||
|
// Extract tile reference and state from the TileUpdate
|
||||||
|
// Last 16 bits are state, rest is tile reference
|
||||||
|
const tileRef = Number(tu >> 16n);
|
||||||
|
const state = Number(tu & 0xFFFFn);
|
||||||
|
|
||||||
|
// Update the state for this tile
|
||||||
|
this.state[tileRef] = state;
|
||||||
|
|
||||||
|
return tileRef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function euclDistFN(root: TileRef, dist: number): (gm: GameMap, tile: TileRef) => boolean {
|
||||||
|
return (gm: GameMap, n: TileRef) => gm.euclideanDist(root, n) <= dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function manhattanDistFN(root: TileRef, dist: number): (gm: GameMap, tile: TileRef) => boolean {
|
||||||
|
return (gm: GameMap, n: TileRef) => gm.manhattanDist(root, n) <= dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function andFN(x: (gm: GameMap, tile: TileRef) => boolean, y: (gm: GameMap, tile: TileRef) => boolean): (gm: GameMap, tile: TileRef) => boolean {
|
||||||
|
return (gm: GameMap, tile: TileRef) => x(gm, tile) && y(gm, tile)
|
||||||
}
|
}
|
||||||
+64
-70
@@ -1,13 +1,12 @@
|
|||||||
import { MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, EmojiMessage, AllPlayers, Gold, UnitType, Unit, MutableUnit, Relation, MutableTile, PlayerUpdate, GameUpdateType } from "./Game";
|
import { MutablePlayer, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, EmojiMessage, AllPlayers, Gold, UnitType, Unit, MutableUnit, Relation, PlayerUpdate, GameUpdateType } from "./Game";
|
||||||
import { ClientID } from "../Schemas";
|
import { ClientID } from "../Schemas";
|
||||||
import { assertNever, bfs, closestOceanShoreFromPlayer, dist, distSortUnit, manhattanDist, manhattanDistWrapped, processName, simpleHash, sourceDstOceanShore, within } from "../Util";
|
import { assertNever, closestOceanShoreFromPlayer, distSortUnit, simpleHash, sourceDstOceanShore, within } from "../Util";
|
||||||
import { CellString, GameImpl } from "./GameImpl";
|
import { CellString, GameImpl } from "./GameImpl";
|
||||||
import { UnitImpl } from "./UnitImpl";
|
import { UnitImpl } from "./UnitImpl";
|
||||||
import { TileImpl } from "./TileImpl";
|
|
||||||
import { MessageType } from './Game';
|
import { MessageType } from './Game';
|
||||||
import { renderTroops } from "../../client/Utils";
|
import { renderTroops } from "../../client/Utils";
|
||||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||||
import { TileRef } from "./GameMap";
|
import { manhattanDistFN, TileRef } from "./GameMap";
|
||||||
|
|
||||||
interface Target {
|
interface Target {
|
||||||
tick: Tick
|
tick: Tick
|
||||||
@@ -32,7 +31,7 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
public _borderTiles: Set<TileRef> = new Set();
|
public _borderTiles: Set<TileRef> = new Set();
|
||||||
|
|
||||||
public _units: UnitImpl[] = [];
|
public _units: UnitImpl[] = [];
|
||||||
public _tiles: Map<CellString, Tile> = new Map<CellString, TileImpl>();
|
public _tiles: Set<TileRef>
|
||||||
|
|
||||||
private _name: string;
|
private _name: string;
|
||||||
private _displayName: string;
|
private _displayName: string;
|
||||||
@@ -48,7 +47,7 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
private relations = new Map<Player, number>()
|
private relations = new Map<Player, number>()
|
||||||
|
|
||||||
|
|
||||||
constructor(private gs: GameImpl, private _smallID: number, private readonly playerInfo: PlayerInfo, startPopulation: number) {
|
constructor(private mg: GameImpl, private _smallID: number, private readonly playerInfo: PlayerInfo, startPopulation: number) {
|
||||||
this._name = playerInfo.name;
|
this._name = playerInfo.name;
|
||||||
this._targetTroopRatio = 1
|
this._targetTroopRatio = 1
|
||||||
this._troops = startPopulation * this._targetTroopRatio;
|
this._troops = startPopulation * this._targetTroopRatio;
|
||||||
@@ -112,8 +111,8 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
|
|
||||||
sharesBorderWith(other: Player | TerraNullius): boolean {
|
sharesBorderWith(other: Player | TerraNullius): boolean {
|
||||||
for (const border of this._borderTiles) {
|
for (const border of this._borderTiles) {
|
||||||
for (const neighbor of this.gs.map().neighbors(border)) {
|
for (const neighbor of this.mg.map().neighbors(border)) {
|
||||||
if (this.gs.map().ownerID(neighbor) == other.smallID()) {
|
if (this.mg.map().ownerID(neighbor) == other.smallID()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,26 +123,22 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
return this._tiles.size;
|
return this._tiles.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles(): ReadonlySet<MutableTile> {
|
tiles(): ReadonlySet<TileRef> {
|
||||||
return new Set(this._tiles.values()) as Set<MutableTile>;
|
return new Set(this._tiles.values()) as Set<TileRef>;
|
||||||
}
|
}
|
||||||
|
|
||||||
borderTileRefs(): ReadonlySet<TileRef> {
|
borderTiles(): ReadonlySet<TileRef> {
|
||||||
return this._borderTiles;
|
return this._borderTiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
borderTiles(): ReadonlySet<Tile> {
|
|
||||||
return new Set(Array.from(this._borderTiles).map(tr => this.gs.fromRef(tr)))
|
|
||||||
}
|
|
||||||
|
|
||||||
neighbors(): (MutablePlayer | TerraNullius)[] {
|
neighbors(): (MutablePlayer | TerraNullius)[] {
|
||||||
const ns: Set<(MutablePlayer | TerraNullius)> = new Set();
|
const ns: Set<(MutablePlayer | TerraNullius)> = new Set();
|
||||||
for (const border of this.borderTileRefs()) {
|
for (const border of this.borderTiles()) {
|
||||||
for (const neighbor of this.gs.map().neighbors(border)) {
|
for (const neighbor of this.mg.map().neighbors(border)) {
|
||||||
if (this.gs.map().isLake(neighbor)) {
|
if (this.mg.map().isLake(neighbor)) {
|
||||||
const owner = this.gs.map().ownerID(neighbor)
|
const owner = this.mg.map().ownerID(neighbor)
|
||||||
if (owner != this.smallID()) {
|
if (owner != this.smallID()) {
|
||||||
ns.add(this.gs.playerBySmallID(owner) as PlayerImpl | TerraNulliusImpl);
|
ns.add(this.mg.playerBySmallID(owner) as PlayerImpl | TerraNulliusImpl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,31 +147,30 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isPlayer(): this is MutablePlayer { return true as const; }
|
isPlayer(): this is MutablePlayer { return true as const; }
|
||||||
ownsTile(cell: Cell): boolean { return this._tiles.has(cell.toString()); }
|
|
||||||
setTroops(troops: number) { this._troops = Math.floor(troops); }
|
setTroops(troops: number) { this._troops = Math.floor(troops); }
|
||||||
conquer(tile: Tile) { this.gs.conquer(this, tile); }
|
conquer(tile: TileRef) { this.mg.conquer(this, tile); }
|
||||||
relinquish(tile: Tile) {
|
relinquish(tile: TileRef) {
|
||||||
if (tile.owner() != this) {
|
if (this.mg.owner(tile) != this) {
|
||||||
throw new Error(`Cannot relinquish tile not owned by this player`);
|
throw new Error(`Cannot relinquish tile not owned by this player`);
|
||||||
}
|
}
|
||||||
this.gs.relinquish(tile);
|
this.mg.relinquish(tile);
|
||||||
}
|
}
|
||||||
info(): PlayerInfo { return this.playerInfo; }
|
info(): PlayerInfo { return this.playerInfo; }
|
||||||
isAlive(): boolean { return this._tiles.size > 0; }
|
isAlive(): boolean { return this._tiles.size > 0; }
|
||||||
executions(): Execution[] {
|
executions(): Execution[] {
|
||||||
return this.gs.executions().filter(exec => exec.owner().id() == this.id());
|
return this.mg.executions().filter(exec => exec.owner().id() == this.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
incomingAllianceRequests(): MutableAllianceRequest[] {
|
incomingAllianceRequests(): MutableAllianceRequest[] {
|
||||||
return this.gs.allianceRequests.filter(ar => ar.recipient() == this)
|
return this.mg.allianceRequests.filter(ar => ar.recipient() == this)
|
||||||
}
|
}
|
||||||
|
|
||||||
outgoingAllianceRequests(): MutableAllianceRequest[] {
|
outgoingAllianceRequests(): MutableAllianceRequest[] {
|
||||||
return this.gs.allianceRequests.filter(ar => ar.requestor() == this)
|
return this.mg.allianceRequests.filter(ar => ar.requestor() == this)
|
||||||
}
|
}
|
||||||
|
|
||||||
alliances(): MutableAlliance[] {
|
alliances(): MutableAlliance[] {
|
||||||
return this.gs.alliances_.filter(a => a.requestor() == this || a.recipient() == this)
|
return this.mg.alliances_.filter(a => a.requestor() == this || a.recipient() == this)
|
||||||
}
|
}
|
||||||
|
|
||||||
allies(): MutablePlayer[] {
|
allies(): MutablePlayer[] {
|
||||||
@@ -212,13 +206,13 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const delta = this.gs.ticks() - recent[0].createdAt()
|
const delta = this.mg.ticks() - recent[0].createdAt()
|
||||||
|
|
||||||
return delta < this.gs.config().allianceRequestCooldown()
|
return delta < this.mg.config().allianceRequestCooldown()
|
||||||
}
|
}
|
||||||
|
|
||||||
breakAlliance(alliance: Alliance): void {
|
breakAlliance(alliance: Alliance): void {
|
||||||
this.gs.breakAlliance(this, alliance)
|
this.mg.breakAlliance(this, alliance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -230,7 +224,7 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
if (this.isAlliedWith(recipient)) {
|
if (this.isAlliedWith(recipient)) {
|
||||||
throw new Error(`cannot create alliance request, already allies`)
|
throw new Error(`cannot create alliance request, already allies`)
|
||||||
}
|
}
|
||||||
return this.gs.createAllianceRequest(this, recipient as MutablePlayer)
|
return this.mg.createAllianceRequest(this, recipient as MutablePlayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
relation(other: Player): Relation {
|
relation(other: Player): Relation {
|
||||||
@@ -291,7 +285,7 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for (const t of this.targets_) {
|
for (const t of this.targets_) {
|
||||||
if (this.gs.ticks() - t.tick < this.gs.config().targetCooldown()) {
|
if (this.mg.ticks() - t.tick < this.mg.config().targetCooldown()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,13 +293,13 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
target(other: Player): void {
|
target(other: Player): void {
|
||||||
this.targets_.push({ tick: this.gs.ticks(), target: other })
|
this.targets_.push({ tick: this.mg.ticks(), target: other })
|
||||||
this.gs.target(this, other)
|
this.mg.target(this, other)
|
||||||
}
|
}
|
||||||
|
|
||||||
targets(): PlayerImpl[] {
|
targets(): PlayerImpl[] {
|
||||||
return this.targets_
|
return this.targets_
|
||||||
.filter(t => this.gs.ticks() - t.tick < this.gs.config().targetDuration())
|
.filter(t => this.mg.ticks() - t.tick < this.mg.config().targetDuration())
|
||||||
.map(t => t.target as PlayerImpl)
|
.map(t => t.target as PlayerImpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,21 +313,21 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
if (recipient == this) {
|
if (recipient == this) {
|
||||||
throw Error(`Cannot send emoji to oneself: ${this}`)
|
throw Error(`Cannot send emoji to oneself: ${this}`)
|
||||||
}
|
}
|
||||||
const msg = new EmojiMessage(this, recipient, emoji, this.gs.ticks())
|
const msg = new EmojiMessage(this, recipient, emoji, this.mg.ticks())
|
||||||
this.outgoingEmojis_.push(msg)
|
this.outgoingEmojis_.push(msg)
|
||||||
this.gs.sendEmojiUpdate(this, recipient, emoji)
|
this.mg.sendEmojiUpdate(this, recipient, emoji)
|
||||||
}
|
}
|
||||||
|
|
||||||
outgoingEmojis(): EmojiMessage[] {
|
outgoingEmojis(): EmojiMessage[] {
|
||||||
return this.outgoingEmojis_
|
return this.outgoingEmojis_
|
||||||
.filter(e => this.gs.ticks() - e.createdAt < this.gs.config().emojiMessageDuration())
|
.filter(e => this.mg.ticks() - e.createdAt < this.mg.config().emojiMessageDuration())
|
||||||
.sort((a, b) => b.createdAt - a.createdAt)
|
.sort((a, b) => b.createdAt - a.createdAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
canSendEmoji(recipient: Player | typeof AllPlayers): boolean {
|
canSendEmoji(recipient: Player | typeof AllPlayers): boolean {
|
||||||
const prevMsgs = this.outgoingEmojis_.filter(msg => msg.recipient == recipient)
|
const prevMsgs = this.outgoingEmojis_.filter(msg => msg.recipient == recipient)
|
||||||
for (const msg of prevMsgs) {
|
for (const msg of prevMsgs) {
|
||||||
if (this.gs.ticks() - msg.createdAt < this.gs.config().emojiMessageCooldown()) {
|
if (this.mg.ticks() - msg.createdAt < this.mg.config().emojiMessageCooldown()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -346,7 +340,7 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
}
|
}
|
||||||
for (const donation of this.sentDonations) {
|
for (const donation of this.sentDonations) {
|
||||||
if (donation.recipient == recipient) {
|
if (donation.recipient == recipient) {
|
||||||
if (this.gs.ticks() - donation.tick < this.gs.config().donateCooldown()) {
|
if (this.mg.ticks() - donation.tick < this.mg.config().donateCooldown()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,10 +349,10 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
donate(recipient: MutablePlayer, troops: number): void {
|
donate(recipient: MutablePlayer, troops: number): void {
|
||||||
this.sentDonations.push(new Donation(recipient, this.gs.ticks()))
|
this.sentDonations.push(new Donation(recipient, this.mg.ticks()))
|
||||||
recipient.addTroops(this.removeTroops(troops))
|
recipient.addTroops(this.removeTroops(troops))
|
||||||
this.gs.displayMessage(`Sent ${renderTroops(troops)} troops to ${recipient.name()}`, MessageType.INFO, this.id())
|
this.mg.displayMessage(`Sent ${renderTroops(troops)} troops to ${recipient.name()}`, MessageType.INFO, this.id())
|
||||||
this.gs.displayMessage(`Recieved ${renderTroops(troops)} troops from ${this.name()}`, MessageType.SUCCESS, recipient.id())
|
this.mg.displayMessage(`Recieved ${renderTroops(troops)} troops from ${this.name()}`, MessageType.SUCCESS, recipient.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
gold(): Gold {
|
gold(): Gold {
|
||||||
@@ -426,24 +420,24 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
(prev as PlayerImpl)._units = (prev as PlayerImpl)._units.filter(u => u != unit);
|
(prev as PlayerImpl)._units = (prev as PlayerImpl)._units.filter(u => u != unit);
|
||||||
(unit as UnitImpl)._owner = this
|
(unit as UnitImpl)._owner = this
|
||||||
this._units.push(unit as UnitImpl)
|
this._units.push(unit as UnitImpl)
|
||||||
this.gs.fireUnitUpdateEvent(unit)
|
this.mg.fireUnitUpdateEvent(unit)
|
||||||
this.gs.displayMessage(`${unit.type()} captured by ${this.displayName()}`, MessageType.ERROR, prev.id())
|
this.mg.displayMessage(`${unit.type()} captured by ${this.displayName()}`, MessageType.ERROR, prev.id())
|
||||||
this.gs.displayMessage(`Captured ${unit.type()} from ${prev.displayName()}`, MessageType.SUCCESS, this.id())
|
this.mg.displayMessage(`Captured ${unit.type()} from ${prev.displayName()}`, MessageType.SUCCESS, this.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUnit(type: UnitType, troops: number, spawnTile: Tile): UnitImpl {
|
buildUnit(type: UnitType, troops: number, spawnTile: TileRef): UnitImpl {
|
||||||
const cost = this.gs.unitInfo(type).cost(this)
|
const cost = this.mg.unitInfo(type).cost(this)
|
||||||
const b = new UnitImpl(type, this.gs, spawnTile, troops, this.gs.nextUnitID(), this);
|
const b = new UnitImpl(type, this.mg, spawnTile, troops, this.mg.nextUnitID(), this);
|
||||||
this._units.push(b);
|
this._units.push(b);
|
||||||
this.removeGold(cost)
|
this.removeGold(cost)
|
||||||
this.removeTroops(troops)
|
this.removeTroops(troops)
|
||||||
this.gs.fireUnitUpdateEvent(b);
|
this.mg.fireUnitUpdateEvent(b);
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
canBuild(unitType: UnitType, targetTile: Tile): Tile | false {
|
canBuild(unitType: UnitType, targetTile: TileRef): TileRef | false {
|
||||||
const cost = this.gs.unitInfo(unitType).cost(this)
|
const cost = this.mg.unitInfo(unitType).cost(this)
|
||||||
if (!this.isAlive() || this.gold() < cost) {
|
if (!this.isAlive() || this.gold() < cost) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -473,56 +467,56 @@ export class PlayerImpl implements MutablePlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nukeSpawn(tile: Tile): Tile | false {
|
nukeSpawn(tile: TileRef): TileRef | false {
|
||||||
const spawns = this.units(UnitType.MissileSilo).map(u => u as Unit).sort(distSortUnit(tile))
|
const spawns = this.units(UnitType.MissileSilo).map(u => u as Unit).sort(distSortUnit(this.mg, tile))
|
||||||
if (spawns.length == 0) {
|
if (spawns.length == 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return spawns[0].tile()
|
return spawns[0].tile()
|
||||||
}
|
}
|
||||||
|
|
||||||
portSpawn(tile: Tile): Tile | false {
|
portSpawn(tile: TileRef): TileRef | false {
|
||||||
const spawns = Array.from(bfs(tile, dist(tile, 20)))
|
const spawns = Array.from(this.mg.bfs(tile, manhattanDistFN(tile, 20)))
|
||||||
.filter(t => t.owner() == this && t.terrain().isOceanShore())
|
.filter(t => this.mg.owner(t) == this && this.mg.isOceanShore(t))
|
||||||
.sort((a, b) => manhattanDist(a.cell(), tile.cell()) - manhattanDist(b.cell(), tile.cell()))
|
.sort((a, b) => this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile))
|
||||||
if (spawns.length == 0) {
|
if (spawns.length == 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return spawns[0]
|
return spawns[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
warshipSpawn(tile: Tile): Tile | false {
|
warshipSpawn(tile: TileRef): TileRef | false {
|
||||||
if (!tile.terrain().isOcean()) {
|
if (!this.mg.isOcean(tile)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const spawns = this.units(UnitType.Port)
|
const spawns = this.units(UnitType.Port)
|
||||||
.filter(u => manhattanDist(u.tile().cell(), tile.cell()) < this.gs.config().boatMaxDistance())
|
.filter(u => this.mg.manhattanDist(u.tile(), tile) < this.mg.config().boatMaxDistance())
|
||||||
.sort((a, b) => manhattanDist(a.tile().cell(), tile.cell()) - manhattanDist(b.tile().cell(), tile.cell()))
|
.sort((a, b) => this.mg.manhattanDist(a.tile(), tile) - this.mg.manhattanDist(b.tile(), tile))
|
||||||
if (spawns.length == 0) {
|
if (spawns.length == 0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return spawns[0].tile()
|
return spawns[0].tile()
|
||||||
}
|
}
|
||||||
|
|
||||||
landBasedStructureSpawn(tile: Tile): Tile | false {
|
landBasedStructureSpawn(tile: TileRef): TileRef | false {
|
||||||
if (tile.owner() != this) {
|
if (this.mg.owner(tile) != this) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return tile
|
return tile
|
||||||
}
|
}
|
||||||
|
|
||||||
transportShipSpawn(targetTile: Tile): Tile | false {
|
transportShipSpawn(targetTile: TileRef): TileRef | false {
|
||||||
if (!targetTile.terrain().isOceanShore()) {
|
if (!this.mg.isOceanShore(targetTile)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const spawn = closestOceanShoreFromPlayer(this, targetTile, this.gs.width())
|
const spawn = closestOceanShoreFromPlayer(this.mg, this, targetTile)
|
||||||
if (spawn == null) {
|
if (spawn == null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return spawn
|
return spawn
|
||||||
}
|
}
|
||||||
|
|
||||||
tradeShipSpawn(targetTile: Tile): Tile | false {
|
tradeShipSpawn(targetTile: TileRef): TileRef | false {
|
||||||
const spawns = this.units(UnitType.Port).filter(u => u.tile() == targetTile)
|
const spawns = this.units(UnitType.Port).filter(u => u.tile() == targetTile)
|
||||||
if (spawns.length == 0) {
|
if (spawns.length == 0) {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { ClientID } from "../Schemas";
|
import { ClientID } from "../Schemas";
|
||||||
import { TerraNullius, Cell, Tile, PlayerID } from "./Game";
|
import { TerraNullius, Cell, PlayerID } from "./Game";
|
||||||
import { GameImpl } from "./GameImpl";
|
import { GameImpl } from "./GameImpl";
|
||||||
|
import { TileRef } from "./GameMap";
|
||||||
|
|
||||||
|
|
||||||
export class TerraNulliusImpl implements TerraNullius {
|
export class TerraNulliusImpl implements TerraNullius {
|
||||||
public tiles: Map<Cell, Tile> = new Map<Cell, Tile>();
|
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -20,8 +20,5 @@ export class TerraNulliusImpl implements TerraNullius {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
ownsTile(cell: Cell): boolean {
|
|
||||||
return this.tiles.has(cell);
|
|
||||||
}
|
|
||||||
isPlayer(): false { return false as const; }
|
isPlayer(): false { return false as const; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Cell, GameMapType, TerrainMap, TerrainTile, TerrainType } from './Game';
|
import { Cell, GameMapType, TerrainType } from './Game';
|
||||||
import { consolex } from '../Consolex';
|
import { consolex } from '../Consolex';
|
||||||
import { NationMap } from './TerrainMapLoader';
|
import { NationMap } from './TerrainMapLoader';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { consolex } from '../Consolex';
|
import { consolex } from '../Consolex';
|
||||||
import { Cell, GameMapType, TerrainMap, TerrainTile, TerrainType } from './Game';
|
import { Cell, GameMapType, TerrainType } from './Game';
|
||||||
import { GameMapImpl } from './GameMap';
|
import { GameMap, GameMapImpl } from './GameMap';
|
||||||
import { terrainMapFileLoader } from './TerrainMapFileLoader';
|
import { terrainMapFileLoader } from './TerrainMapFileLoader';
|
||||||
|
|
||||||
const loadedMaps = new Map<GameMapType, { nationMap: NationMap, gameMap: GameMapImpl, miniGameMap: GameMapImpl, terrain: TerrainMap }>()
|
const loadedMaps = new Map<GameMapType, { nationMap: NationMap, gameMap: GameMap, miniGameMap: GameMap }>()
|
||||||
|
|
||||||
export interface NationMap {
|
export interface NationMap {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -18,138 +18,7 @@ export interface Nation {
|
|||||||
strength: number;
|
strength: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function loadTerrainMap(map: GameMapType): Promise<{ nationMap: NationMap, gameMap: GameMap, miniGameMap: GameMap }> {
|
||||||
export class TerrainTileImpl implements TerrainTile {
|
|
||||||
public shoreline: boolean = false
|
|
||||||
public _magnitude: number = 0
|
|
||||||
public ocean = false
|
|
||||||
public land = false
|
|
||||||
|
|
||||||
constructor(private map: TerrainMap, public _type: TerrainType, private _cell: Cell) { }
|
|
||||||
|
|
||||||
key(): string {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
|
|
||||||
equals(other: TerrainTile): boolean {
|
|
||||||
return this._cell.x == other.cell().x && this._cell.y == other.cell().y
|
|
||||||
}
|
|
||||||
type(): TerrainType {
|
|
||||||
return this._type
|
|
||||||
}
|
|
||||||
isLake(): boolean {
|
|
||||||
return !this.isLand() && !this.isOcean();
|
|
||||||
}
|
|
||||||
isOcean(): boolean {
|
|
||||||
return this.ocean;
|
|
||||||
}
|
|
||||||
magnitude(): number {
|
|
||||||
return this._magnitude;
|
|
||||||
}
|
|
||||||
isShore(): boolean {
|
|
||||||
return this.isLand() && this.shoreline;
|
|
||||||
}
|
|
||||||
isOceanShore(): boolean {
|
|
||||||
return this.isShore() && this.neighbors().filter(n => n.isOcean()).length > -1;
|
|
||||||
}
|
|
||||||
isShorelineWater(): boolean {
|
|
||||||
return this.isWater() && this.shoreline;
|
|
||||||
}
|
|
||||||
isLand(): boolean {
|
|
||||||
return this.land;
|
|
||||||
}
|
|
||||||
isWater(): boolean {
|
|
||||||
return !this.land;
|
|
||||||
}
|
|
||||||
cost(): number {
|
|
||||||
return this._magnitude < 10 ? 2 : 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cell(): Cell {
|
|
||||||
return this._cell
|
|
||||||
}
|
|
||||||
|
|
||||||
neighbors(): TerrainTile[] {
|
|
||||||
const positions = [
|
|
||||||
{ x: this._cell.x - 1, y: this._cell.y }, // Left
|
|
||||||
{ x: this._cell.x + 1, y: this._cell.y }, // Right
|
|
||||||
{ x: this._cell.x, y: this._cell.y - 1 }, // Up
|
|
||||||
{ x: this._cell.x, y: this._cell.y + 1 } // Down
|
|
||||||
];
|
|
||||||
|
|
||||||
return positions
|
|
||||||
.filter(pos => pos.x >= 0 && pos.x < this.map.width() &&
|
|
||||||
pos.y >= 0 && pos.y < this.map.height())
|
|
||||||
.map(pos => this.map.terrain(new Cell(pos.x, pos.y)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TerrainMapImpl implements TerrainMap {
|
|
||||||
public rawData: Uint8Array;
|
|
||||||
public width_: number;
|
|
||||||
public height_: number;
|
|
||||||
public _numLandTiles: number;
|
|
||||||
public nationMap: NationMap;
|
|
||||||
|
|
||||||
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
terrain(cell: Cell): TerrainTileImpl {
|
|
||||||
|
|
||||||
const idx = cell.y * this.width_ + cell.x;
|
|
||||||
const packedByte = this.rawData[idx];
|
|
||||||
|
|
||||||
const isLand: boolean = (packedByte & 0b10000000) !== 0;
|
|
||||||
const shoreline = !!(packedByte & 0b01000000);
|
|
||||||
const ocean = !!(packedByte & 0b00100000);
|
|
||||||
const magnitude = packedByte & 0b00011111;
|
|
||||||
|
|
||||||
let type: TerrainType;
|
|
||||||
if (isLand) {
|
|
||||||
if (magnitude < 10) {
|
|
||||||
type = TerrainType.Plains;
|
|
||||||
} else if (magnitude < 20) {
|
|
||||||
type = TerrainType.Highland;
|
|
||||||
} else {
|
|
||||||
type = TerrainType.Mountain;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
type = ocean ? TerrainType.Ocean : TerrainType.Lake;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tile = new TerrainTileImpl(this, type, cell);
|
|
||||||
tile.shoreline = shoreline;
|
|
||||||
tile._magnitude = magnitude;
|
|
||||||
tile.ocean = ocean;
|
|
||||||
tile.land = isLand;
|
|
||||||
|
|
||||||
return tile;
|
|
||||||
}
|
|
||||||
|
|
||||||
isOnMap(cell: Cell): boolean {
|
|
||||||
return cell.x >= 0 && cell.x < this.width_ &&
|
|
||||||
cell.y >= 0 && cell.y < this.height_;
|
|
||||||
}
|
|
||||||
|
|
||||||
width(): number {
|
|
||||||
return this.width_;
|
|
||||||
}
|
|
||||||
|
|
||||||
height(): number {
|
|
||||||
return this.height_;
|
|
||||||
}
|
|
||||||
|
|
||||||
numLandTiles(): number {
|
|
||||||
return this._numLandTiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
neighbors(terrainTile: TerrainTile): TerrainTile[] {
|
|
||||||
return (terrainTile as TerrainTileImpl).neighbors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function loadTerrainMap(map: GameMapType): Promise<{ nationMap: NationMap, gameMap: GameMapImpl, miniGameMap: GameMapImpl, terrain: TerrainMap }> {
|
|
||||||
if (loadedMaps.has(map)) {
|
if (loadedMaps.has(map)) {
|
||||||
return loadedMaps.get(map)
|
return loadedMaps.get(map)
|
||||||
}
|
}
|
||||||
@@ -157,12 +26,12 @@ export async function loadTerrainMap(map: GameMapType): Promise<{ nationMap: Nat
|
|||||||
|
|
||||||
const gameMap = await loadTerrainFromFile(mapFiles.mapBin)
|
const gameMap = await loadTerrainFromFile(mapFiles.mapBin)
|
||||||
const miniGameMap = await loadTerrainFromFile(mapFiles.miniMapBin)
|
const miniGameMap = await loadTerrainFromFile(mapFiles.miniMapBin)
|
||||||
const result = { nationMap: mapFiles.nationMap, gameMap: gameMap.map, miniGameMap: miniGameMap.map, terrain: gameMap.terrain }
|
const result = { nationMap: mapFiles.nationMap, gameMap: gameMap, miniGameMap: miniGameMap }
|
||||||
loadedMaps.set(map, result)
|
loadedMaps.set(map, result)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadTerrainFromFile(fileData: string): Promise<{ map: GameMapImpl, terrain: TerrainMap }> {
|
export async function loadTerrainFromFile(fileData: string): Promise<GameMap> {
|
||||||
const width = (fileData.charCodeAt(1) << 8) | fileData.charCodeAt(0);
|
const width = (fileData.charCodeAt(1) << 8) | fileData.charCodeAt(0);
|
||||||
const height = (fileData.charCodeAt(3) << 8) | fileData.charCodeAt(2);
|
const height = (fileData.charCodeAt(3) << 8) | fileData.charCodeAt(2);
|
||||||
|
|
||||||
@@ -170,24 +39,19 @@ export async function loadTerrainFromFile(fileData: string): Promise<{ map: Game
|
|||||||
throw new Error(`Invalid data: buffer size ${fileData.length} incorrect for ${width}x${height} terrain plus 4 bytes for dimensions.`);
|
throw new Error(`Invalid data: buffer size ${fileData.length} incorrect for ${width}x${height} terrain plus 4 bytes for dimensions.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const m = new TerrainMapImpl();
|
|
||||||
m.width_ = width;
|
|
||||||
m.height_ = height;
|
|
||||||
|
|
||||||
// Store raw data in Uint8Array
|
// Store raw data in Uint8Array
|
||||||
m.rawData = new Uint8Array(width * height);
|
const rawData = new Uint8Array(width * height);
|
||||||
let numLand = 0;
|
let numLand = 0;
|
||||||
|
|
||||||
// Copy data starting after the header
|
// Copy data starting after the header
|
||||||
for (let i = 0; i < width * height; i++) {
|
for (let i = 0; i < width * height; i++) {
|
||||||
const packedByte = fileData.charCodeAt(i + 4);
|
const packedByte = fileData.charCodeAt(i + 4);
|
||||||
m.rawData[i] = packedByte;
|
rawData[i] = packedByte;
|
||||||
if (packedByte & 0b10000000) numLand++;
|
if (packedByte & 0b10000000) numLand++;
|
||||||
}
|
}
|
||||||
const gm = new GameMapImpl(width, height, m.rawData, numLand)
|
return new GameMapImpl(width, height, rawData, numLand)
|
||||||
|
|
||||||
m._numLandTiles = numLand;
|
|
||||||
return { map: gm, terrain: m }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { TerrainMapImpl } from "./TerrainMapLoader";
|
|
||||||
export enum SearchMapTileType {
|
export enum SearchMapTileType {
|
||||||
Land,
|
Land,
|
||||||
Shore,
|
Shore,
|
||||||
|
|||||||
@@ -1,156 +0,0 @@
|
|||||||
import { Tile, Cell, TerrainType, Player, TerraNullius, MutablePlayer, TerrainTile, DefenseBonus, MutableTile, TileUpdate, GameUpdateType, TerrainTileKey } from "./Game";
|
|
||||||
import { TerrainMapImpl, TerrainTileImpl } from "./TerrainMapLoader";
|
|
||||||
import { GameImpl } from "./GameImpl";
|
|
||||||
import { PlayerImpl } from "./PlayerImpl";
|
|
||||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
|
||||||
import { GameMapImpl, TileRef } from "./GameMap";
|
|
||||||
|
|
||||||
|
|
||||||
export class TileImpl implements MutableTile {
|
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly gs: GameImpl,
|
|
||||||
private ref_: TileRef
|
|
||||||
) { }
|
|
||||||
terrain(): TerrainTile {
|
|
||||||
return new TerrainRef(this.gs.map(), this.ref_)
|
|
||||||
}
|
|
||||||
|
|
||||||
neighborsWrapped(): Tile[] {
|
|
||||||
// TODO: implement!
|
|
||||||
return this.neighbors()
|
|
||||||
}
|
|
||||||
|
|
||||||
ref(): TileRef {
|
|
||||||
return this.ref_
|
|
||||||
}
|
|
||||||
|
|
||||||
toUpdate(): TileUpdate {
|
|
||||||
return {
|
|
||||||
type: GameUpdateType.Tile,
|
|
||||||
pos: {
|
|
||||||
x: this.x(),
|
|
||||||
y: this.y()
|
|
||||||
},
|
|
||||||
ownerID: this.owner().smallID(),
|
|
||||||
hasFallout: this.hasFallout(),
|
|
||||||
hasDefenseBonus: this.hasDefenseBonus(),
|
|
||||||
isBorder: this.isBorder(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasFallout(): boolean {
|
|
||||||
return this.gs.map().hasFallout(this.ref_)
|
|
||||||
}
|
|
||||||
|
|
||||||
type(): TerrainType {
|
|
||||||
return this.gs.map().getTerrainType(this.ref_)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasDefenseBonus(): boolean {
|
|
||||||
return this.defenseBonuses.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
defenseBonus(player: Player): number {
|
|
||||||
// TODO!
|
|
||||||
return 0
|
|
||||||
// if (this.owner() == player) {
|
|
||||||
// throw Error(`cannot get defense bonus of tile already owned by player, ${player}`)
|
|
||||||
// }
|
|
||||||
// let bonusAmount = 0
|
|
||||||
// for (const bonus of this._defenseBonuses) {
|
|
||||||
// if (bonus.unit.owner() != player) {
|
|
||||||
// bonusAmount += bonus.amount
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return Math.max(bonusAmount, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
defenseBonuses(): DefenseBonus[] {
|
|
||||||
// TODO!
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
borders(other: Player | TerraNullius): boolean {
|
|
||||||
for (const n of this.neighbors()) {
|
|
||||||
if (n.owner() == other) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasOwner(): boolean { return this.owner().smallID() != 0 }
|
|
||||||
|
|
||||||
owner(): MutablePlayer | TerraNullius {
|
|
||||||
const ownerID = this.gs.map().ownerID(this.ref_)
|
|
||||||
if (ownerID == 0) {
|
|
||||||
return this.gs.terraNullius()
|
|
||||||
}
|
|
||||||
return this.gs.playerBySmallID(ownerID) as MutablePlayer
|
|
||||||
}
|
|
||||||
isBorder(): boolean { return this.gs.map().isBorder(this.ref_); }
|
|
||||||
|
|
||||||
cell(): Cell { return new Cell(this.x(), this.y()); }
|
|
||||||
|
|
||||||
x(): number {
|
|
||||||
return this.gs.map().x(this.ref_)
|
|
||||||
}
|
|
||||||
y(): number {
|
|
||||||
return this.gs.map().y(this.ref_)
|
|
||||||
}
|
|
||||||
|
|
||||||
neighbors(): Tile[] {
|
|
||||||
return this.gs.neighbors(this.ref()).map(n => this.gs.fromRef(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TerrainRef implements TerrainTile {
|
|
||||||
|
|
||||||
constructor(private map: GameMapImpl, private ref: TileRef) { }
|
|
||||||
|
|
||||||
isLand(): boolean {
|
|
||||||
return this.map.isLand(this.ref)
|
|
||||||
}
|
|
||||||
isShore(): boolean {
|
|
||||||
return this.map.isShore(this.ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
isOceanShore(): boolean {
|
|
||||||
return this.isShore() && this.neighbors().filter(n => n.isOcean()).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
isWater(): boolean {
|
|
||||||
return !this.map.isLand(this.ref)
|
|
||||||
}
|
|
||||||
isShorelineWater(): boolean {
|
|
||||||
return this.isWater() && this.isShore()
|
|
||||||
}
|
|
||||||
isOcean(): boolean {
|
|
||||||
return this.map.isOcean(this.ref)
|
|
||||||
}
|
|
||||||
isLake(): boolean {
|
|
||||||
return this.isWater() && !this.isOcean()
|
|
||||||
}
|
|
||||||
type(): TerrainType {
|
|
||||||
return this.map.getTerrainType(this.ref)
|
|
||||||
}
|
|
||||||
magnitude(): number {
|
|
||||||
return this.map.magnitude(this.ref)
|
|
||||||
}
|
|
||||||
equals(other: TerrainTile): boolean {
|
|
||||||
return this.ref == (other as TerrainRef).ref
|
|
||||||
}
|
|
||||||
cell(): Cell {
|
|
||||||
return this.map.cell(this.ref)
|
|
||||||
}
|
|
||||||
neighbors(): TerrainTile[] {
|
|
||||||
return this.map.neighbors(this.ref).map(tr => new TerrainRef(this.map, tr))
|
|
||||||
}
|
|
||||||
cost(): number {
|
|
||||||
return this.map.cost(this.ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+20
-18
@@ -1,25 +1,26 @@
|
|||||||
import { GameUpdateType, MessageType, UnitUpdate } from './Game';
|
import { GameUpdateType, MessageType, UnitUpdate } from './Game';
|
||||||
import { simpleHash, within } from "../Util";
|
import { simpleHash, within } from "../Util";
|
||||||
import { MutableUnit, Tile, TerraNullius, UnitType, Player, UnitInfo } from "./Game";
|
import { MutableUnit, TerraNullius, UnitType, Player, UnitInfo } from "./Game";
|
||||||
import { GameImpl } from "./GameImpl";
|
import { GameImpl } from "./GameImpl";
|
||||||
import { PlayerImpl } from "./PlayerImpl";
|
import { PlayerImpl } from "./PlayerImpl";
|
||||||
|
import { TileRef } from './GameMap';
|
||||||
|
|
||||||
|
|
||||||
export class UnitImpl implements MutableUnit {
|
export class UnitImpl implements MutableUnit {
|
||||||
private _active = true;
|
private _active = true;
|
||||||
private _health: number
|
private _health: number
|
||||||
private _lastTile: Tile = null
|
private _lastTile: TileRef = null
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private _type: UnitType,
|
private _type: UnitType,
|
||||||
private g: GameImpl,
|
private mg: GameImpl,
|
||||||
private _tile: Tile,
|
private _tile: TileRef,
|
||||||
private _troops: number,
|
private _troops: number,
|
||||||
private _id: number,
|
private _id: number,
|
||||||
public _owner: PlayerImpl,
|
public _owner: PlayerImpl,
|
||||||
) {
|
) {
|
||||||
// default to half health (or 1 is no health specified)
|
// default to half health (or 1 is no health specified)
|
||||||
this._health = (this.g.unitInfo(_type).maxHealth ?? 2) / 2
|
this._health = (this.mg.unitInfo(_type).maxHealth ?? 2) / 2
|
||||||
this._lastTile = _tile
|
this._lastTile = _tile
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,8 +33,9 @@ export class UnitImpl implements MutableUnit {
|
|||||||
troops: this._troops,
|
troops: this._troops,
|
||||||
ownerID: this._owner.smallID(),
|
ownerID: this._owner.smallID(),
|
||||||
isActive: this._active,
|
isActive: this._active,
|
||||||
pos: this._tile.cell().pos(),
|
pos: { x: this.mg.x(this._tile), y: this.mg.y(this._tile) },
|
||||||
lastPos: this._lastTile.cell().pos()
|
lastPos: { x: this.mg.x(this._lastTile), y: this.mg.y(this._lastTile) }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,17 +43,17 @@ export class UnitImpl implements MutableUnit {
|
|||||||
return this._type
|
return this._type
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTile(): Tile {
|
lastTile(): TileRef {
|
||||||
return this._lastTile
|
return this._lastTile
|
||||||
}
|
}
|
||||||
|
|
||||||
move(tile: Tile): void {
|
move(tile: TileRef): void {
|
||||||
if (tile == null) {
|
if (tile == null) {
|
||||||
throw new Error("tile cannot be null")
|
throw new Error("tile cannot be null")
|
||||||
}
|
}
|
||||||
this._lastTile = this._tile
|
this._lastTile = this._tile
|
||||||
this._tile = tile;
|
this._tile = tile;
|
||||||
this.g.fireUnitUpdateEvent(this);
|
this.mg.fireUnitUpdateEvent(this);
|
||||||
}
|
}
|
||||||
setTroops(troops: number): void {
|
setTroops(troops: number): void {
|
||||||
this._troops = troops;
|
this._troops = troops;
|
||||||
@@ -65,23 +67,23 @@ export class UnitImpl implements MutableUnit {
|
|||||||
hasHealth(): boolean {
|
hasHealth(): boolean {
|
||||||
return this.info().maxHealth != undefined
|
return this.info().maxHealth != undefined
|
||||||
}
|
}
|
||||||
tile(): Tile {
|
tile(): TileRef {
|
||||||
return this._tile;
|
return this._tile
|
||||||
}
|
}
|
||||||
owner(): PlayerImpl {
|
owner(): PlayerImpl {
|
||||||
return this._owner;
|
return this._owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
info(): UnitInfo {
|
info(): UnitInfo {
|
||||||
return this.g.unitInfo(this._type)
|
return this.mg.unitInfo(this._type)
|
||||||
}
|
}
|
||||||
|
|
||||||
setOwner(newOwner: Player): void {
|
setOwner(newOwner: Player): void {
|
||||||
const oldOwner = this._owner
|
const oldOwner = this._owner
|
||||||
oldOwner._units = oldOwner._units.filter(u => u != this)
|
oldOwner._units = oldOwner._units.filter(u => u != this)
|
||||||
this._owner = newOwner as PlayerImpl
|
this._owner = newOwner as PlayerImpl
|
||||||
this.g.fireUnitUpdateEvent(this)
|
this.mg.fireUnitUpdateEvent(this)
|
||||||
this.g.displayMessage(
|
this.mg.displayMessage(
|
||||||
`Your ${this.type()} was captured by ${newOwner.displayName()}`,
|
`Your ${this.type()} was captured by ${newOwner.displayName()}`,
|
||||||
MessageType.ERROR,
|
MessageType.ERROR,
|
||||||
oldOwner.id()
|
oldOwner.id()
|
||||||
@@ -103,9 +105,9 @@ export class UnitImpl implements MutableUnit {
|
|||||||
}
|
}
|
||||||
this._owner._units = this._owner._units.filter(b => b != this);
|
this._owner._units = this._owner._units.filter(b => b != this);
|
||||||
this._active = false;
|
this._active = false;
|
||||||
this.g.fireUnitUpdateEvent(this);
|
this.mg.fireUnitUpdateEvent(this);
|
||||||
if (displayMessage) {
|
if (displayMessage) {
|
||||||
this.g.displayMessage(`Your ${this.type()} was destroyed`, MessageType.ERROR, this.owner().id())
|
this.mg.displayMessage(`Your ${this.type()} was destroyed`, MessageType.ERROR, this.owner().id())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isActive(): boolean {
|
isActive(): boolean {
|
||||||
@@ -113,7 +115,7 @@ export class UnitImpl implements MutableUnit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hash(): number {
|
hash(): number {
|
||||||
return this.tile().cell().x + this.tile().cell().y + simpleHash(this.type())
|
return this.tile() + simpleHash(this.type())
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Cell, TerrainType, Tile } from "../game/Game";
|
import { TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
export interface AStar {
|
export interface AStar {
|
||||||
compute(): PathFindResultType
|
compute(): PathFindResultType
|
||||||
reconstructPath(): Cell[]
|
reconstructPath(): TileRef[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PathFindResultType {
|
export enum PathFindResultType {
|
||||||
@@ -12,12 +12,12 @@ export enum PathFindResultType {
|
|||||||
PathNotFound
|
PathNotFound
|
||||||
} export type TileResult = {
|
} export type TileResult = {
|
||||||
type: PathFindResultType.NextTile;
|
type: PathFindResultType.NextTile;
|
||||||
tile: Tile;
|
tile: TileRef;
|
||||||
} | {
|
} | {
|
||||||
type: PathFindResultType.Pending;
|
type: PathFindResultType.Pending;
|
||||||
} | {
|
} | {
|
||||||
type: PathFindResultType.Completed;
|
type: PathFindResultType.Completed;
|
||||||
tile: Tile;
|
tile: TileRef;
|
||||||
} | {
|
} | {
|
||||||
type: PathFindResultType.PathNotFound;
|
type: PathFindResultType.PathNotFound;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { GameManager } from "../../server/GameManager";
|
import { Cell, } from "../game/Game";
|
||||||
import { Cell, Game, TerrainMap, TerrainType } from "../game/Game";
|
import { GameMap, GameMapImpl, TileRef } from "../game/GameMap";
|
||||||
import { GameMapImpl, TileRef } from "../game/GameMap";
|
|
||||||
import { AStar, PathFindResultType, } from "./AStar";
|
import { AStar, PathFindResultType, } from "./AStar";
|
||||||
import { SerialAStar } from "./SerialAStar";
|
import { SerialAStar } from "./SerialAStar";
|
||||||
|
|
||||||
@@ -10,8 +9,8 @@ export class MiniAStar implements AStar {
|
|||||||
private aStar: SerialAStar
|
private aStar: SerialAStar
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private gameMap: GameMapImpl,
|
private gameMap: GameMap,
|
||||||
private miniMap: GameMapImpl,
|
private miniMap: GameMap,
|
||||||
private src: TileRef,
|
private src: TileRef,
|
||||||
private dst: TileRef,
|
private dst: TileRef,
|
||||||
private canMove: (t: TileRef) => boolean,
|
private canMove: (t: TileRef) => boolean,
|
||||||
@@ -40,10 +39,10 @@ export class MiniAStar implements AStar {
|
|||||||
return this.aStar.compute()
|
return this.aStar.compute()
|
||||||
}
|
}
|
||||||
|
|
||||||
reconstructPath(): Cell[] {
|
reconstructPath(): TileRef[] {
|
||||||
const upscaled = upscalePath(this.aStar.reconstructPath())
|
const upscaled = upscalePath(this.aStar.reconstructPath().map(tr => new Cell(this.gameMap.x(tr), this.gameMap.y(tr))))
|
||||||
upscaled.push(new Cell(this.gameMap.x(this.dst), this.gameMap.y(this.dst)))
|
upscaled.push(new Cell(this.gameMap.x(this.dst), this.gameMap.y(this.dst)))
|
||||||
return upscaled
|
return upscaled.map(c => this.gameMap.ref(c.x, c.y))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Cell, Game, TerrainTile, TerrainType, Tile } from "../game/Game";
|
import { Cell, Game } from "../game/Game";
|
||||||
import { manhattanDist } from "../Util";
|
|
||||||
import { AStar, PathFindResultType, TileResult } from "./AStar";
|
import { AStar, PathFindResultType, TileResult } from "./AStar";
|
||||||
import { SerialAStar } from "./SerialAStar";
|
import { SerialAStar } from "./SerialAStar";
|
||||||
import { MiniAStar } from "./MiniAStar";
|
import { MiniAStar } from "./MiniAStar";
|
||||||
@@ -8,29 +7,27 @@ import { TileRef } from "../game/GameMap";
|
|||||||
|
|
||||||
export class PathFinder {
|
export class PathFinder {
|
||||||
|
|
||||||
private curr: Tile = null
|
private curr: TileRef = null
|
||||||
private dst: Tile = null
|
private dst: TileRef = null
|
||||||
private path: Cell[]
|
private path: TileRef[]
|
||||||
private aStar: AStar
|
private aStar: AStar
|
||||||
private computeFinished = true
|
private computeFinished = true
|
||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
private game: Game,
|
private game: Game,
|
||||||
private newAStar: (curr: Tile, dst: Tile) => AStar
|
private newAStar: (curr: TileRef, dst: TileRef) => AStar
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
public static Mini(game: Game, iterations: number, canMoveOnLand: boolean, maxTries: number = 20) {
|
public static Mini(game: Game, iterations: number, canMoveOnLand: boolean, maxTries: number = 20) {
|
||||||
return new PathFinder(
|
return new PathFinder(
|
||||||
game,
|
game,
|
||||||
(curr: Tile, dst: Tile) => {
|
(curr: TileRef, dst: TileRef) => {
|
||||||
const currRef = game.map().ref(curr.cell().x, curr.cell().y)
|
|
||||||
const dstRef = game.map().ref(dst.cell().x, dst.cell().y)
|
|
||||||
return new MiniAStar(
|
return new MiniAStar(
|
||||||
game.map(),
|
game.map(),
|
||||||
game.miniMap(),
|
game.miniMap(),
|
||||||
currRef,
|
curr,
|
||||||
dstRef,
|
dst,
|
||||||
(tr: TileRef): boolean => {
|
(tr: TileRef): boolean => {
|
||||||
if (canMoveOnLand) {
|
if (canMoveOnLand) {
|
||||||
return true
|
return true
|
||||||
@@ -44,7 +41,7 @@ export class PathFinder {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTile(curr: Tile, dst: Tile, dist: number = 1): TileResult {
|
nextTile(curr: TileRef, dst: TileRef, dist: number = 1): TileResult {
|
||||||
if (curr == null) {
|
if (curr == null) {
|
||||||
consolex.error('curr is null')
|
consolex.error('curr is null')
|
||||||
}
|
}
|
||||||
@@ -52,7 +49,7 @@ export class PathFinder {
|
|||||||
consolex.error('dst is null')
|
consolex.error('dst is null')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manhattanDist(curr.cell(), dst.cell()) < dist) {
|
if (this.game.manhattanDist(curr, dst) < dist) {
|
||||||
return { type: PathFindResultType.Completed, tile: curr }
|
return { type: PathFindResultType.Completed, tile: curr }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +62,7 @@ export class PathFinder {
|
|||||||
this.computeFinished = false
|
this.computeFinished = false
|
||||||
return this.nextTile(curr, dst)
|
return this.nextTile(curr, dst)
|
||||||
} else {
|
} else {
|
||||||
return { type: PathFindResultType.NextTile, tile: this.game.tile(this.path.shift()) }
|
return { type: PathFindResultType.NextTile, tile: this.path.shift() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,11 +80,11 @@ export class PathFinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldRecompute(curr: Tile, dst: Tile) {
|
private shouldRecompute(curr: TileRef, dst: TileRef) {
|
||||||
if (this.path == null || this.curr == null || this.dst == null) {
|
if (this.path == null || this.curr == null || this.dst == null) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const dist = manhattanDist(curr.cell(), dst.cell())
|
const dist = this.game.manhattanDist(curr, dst)
|
||||||
let tolerance = 10
|
let tolerance = 10
|
||||||
if (dist > 50) {
|
if (dist > 50) {
|
||||||
tolerance = 10
|
tolerance = 10
|
||||||
@@ -98,7 +95,7 @@ export class PathFinder {
|
|||||||
} else {
|
} else {
|
||||||
tolerance = 0
|
tolerance = 0
|
||||||
}
|
}
|
||||||
if (manhattanDist(this.dst.cell(), dst.cell()) > tolerance) {
|
if (this.game.manhattanDist(this.dst, dst) > tolerance) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
import { PriorityQueue } from "@datastructures-js/priority-queue";
|
||||||
import { AStar } from "./AStar";
|
import { AStar } from "./AStar";
|
||||||
import { PathFindResultType } from "./AStar";
|
import { PathFindResultType } from "./AStar";
|
||||||
import { Cell } from "../game/Game";
|
|
||||||
import { consolex } from "../Consolex";
|
import { consolex } from "../Consolex";
|
||||||
import { GameMapImpl, TileRef } from "../game/GameMap";
|
import { GameMap, GameMapImpl, TileRef } from "../game/GameMap";
|
||||||
|
|
||||||
|
|
||||||
export class SerialAStar implements AStar {
|
export class SerialAStar implements AStar {
|
||||||
@@ -22,7 +21,7 @@ export class SerialAStar implements AStar {
|
|||||||
private canMove: (t: TileRef) => boolean,
|
private canMove: (t: TileRef) => boolean,
|
||||||
private iterations: number,
|
private iterations: number,
|
||||||
private maxTries: number,
|
private maxTries: number,
|
||||||
private gameMap: GameMapImpl
|
private gameMap: GameMap
|
||||||
) {
|
) {
|
||||||
this.fwdOpenSet = new PriorityQueue<{ tile: TileRef; fScore: number; }>(
|
this.fwdOpenSet = new PriorityQueue<{ tile: TileRef; fScore: number; }>(
|
||||||
(a, b) => a.fScore - b.fScore
|
(a, b) => a.fScore - b.fScore
|
||||||
@@ -118,7 +117,7 @@ export class SerialAStar implements AStar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public reconstructPath(): Cell[] {
|
public reconstructPath(): TileRef[] {
|
||||||
if (!this.meetingPoint) return [];
|
if (!this.meetingPoint) return [];
|
||||||
|
|
||||||
// Reconstruct path from start to meeting point
|
// Reconstruct path from start to meeting point
|
||||||
@@ -136,6 +135,6 @@ export class SerialAStar implements AStar {
|
|||||||
fwdPath.push(current);
|
fwdPath.push(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fwdPath.map(sn => new Cell(this.gameMap.x(sn), this.gameMap.y(sn)));
|
return fwdPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { PlayerActions, PlayerID, PlayerInfo, PlayerProfile, Tile } from "../game/Game";
|
import { PlayerActions, PlayerID, PlayerInfo, PlayerProfile } from "../game/Game";
|
||||||
import { GameUpdateViewData } from "../GameView";
|
import { GameUpdateViewData } from "../GameView";
|
||||||
import { GameConfig, GameID, Turn } from "../Schemas";
|
import { GameConfig, GameID, Turn } from "../Schemas";
|
||||||
import { generateID } from "../Util";
|
import { generateID } from "../Util";
|
||||||
@@ -115,7 +115,7 @@ export class WorkerClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
playerInteraction(playerID: PlayerID, tile: Tile): Promise<PlayerActions> {
|
playerInteraction(playerID: PlayerID, x: number, y: number): Promise<PlayerActions> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!this.isInitialized) {
|
if (!this.isInitialized) {
|
||||||
reject(new Error('Worker not initialized'));
|
reject(new Error('Worker not initialized'));
|
||||||
@@ -134,8 +134,8 @@ export class WorkerClient {
|
|||||||
type: 'player_actions',
|
type: 'player_actions',
|
||||||
id: messageId,
|
id: messageId,
|
||||||
playerID: playerID,
|
playerID: playerID,
|
||||||
x: tile.cell().x,
|
x: x,
|
||||||
y: tile.cell().y
|
y: y
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { decodePNGFromStream } from 'pureimage'; import path from 'path';
|
|||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { createReadStream } from 'fs';
|
import { createReadStream } from 'fs';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { TerrainTile } from '../core/game/Game';
|
|
||||||
|
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|||||||
Reference in New Issue
Block a user