improved terrain api

This commit is contained in:
evanpelle
2024-08-21 19:51:01 -07:00
parent 3b9ccf49b1
commit 5e7c206f0d
16 changed files with 61 additions and 52 deletions
File diff suppressed because one or more lines are too long
Binary file not shown.
+1 -2
View File
@@ -1,10 +1,9 @@
import {getConfig} from "../core/configuration/Config";
import {defaultConfig} from "../core/configuration/DefaultConfig";
import {devConfig} from "../core/configuration/DevConfig";
import {TerrainMap} from "../core/Game";
import {PseudoRandom} from "../core/PseudoRandom";
import {GameID, Lobby, ServerMessage, ServerMessageSchema} from "../core/Schemas";
import {loadTerrainMap} from "../core/TerrainMapLoader";
import {loadTerrainMap, TerrainMap} from "../core/TerrainMapLoader";
import {ClientGame, createClientGame} from "./ClientGame";
import {v4 as uuidv4} from 'uuid';
import backgroundImage from '../../resources/images/empty_map.png';
+3 -2
View File
@@ -1,11 +1,12 @@
import {Executor} from "../core/execution/Executor";
import {Cell, MutableGame, PlayerEvent, PlayerID, MutablePlayer, TerrainMap, TileEvent, Player, Game, BoatEvent, TerrainTypes} from "../core/Game";
import {Cell, MutableGame, PlayerEvent, PlayerID, MutablePlayer, TileEvent, Player, Game, BoatEvent} from "../core/Game";
import {createGame} from "../core/GameImpl";
import {EventBus} from "../core/EventBus";
import {Config} from "../core/configuration/Config";
import {GameRenderer} from "./graphics/GameRenderer";
import {InputHandler, MouseUpEvent, ZoomEvent, DragEvent, MouseDownEvent} from "./InputHandler"
import {ClientID, ClientIntentMessageSchema, ClientJoinMessageSchema, ClientLeaveMessageSchema, ClientMessageSchema, GameID, Intent, ServerMessage, ServerMessageSchema, ServerSyncMessage, Turn} from "../core/Schemas";
import {TerrainMap} from "../core/TerrainMapLoader";
@@ -187,7 +188,7 @@ export class ClientGame {
const owner = tile.owner()
const targetID = owner.isPlayer() ? owner.id() : null
if (tile.owner() != this.myPlayer && tile.terrain() == TerrainTypes.Land) {
if (tile.owner() != this.myPlayer && tile.isLand()) {
if (this.myPlayer.sharesBorderWith(tile.owner())) {
this.sendAttackIntent(targetID, cell, this.config.player().attackAmount(this.myPlayer, owner))
} else if (owner.isPlayer()) {
+1 -1
View File
@@ -163,7 +163,7 @@ export class GameRenderer {
}
paintTile(tile: Tile) {
let terrainColor = this.theme.terrainColor(tile.terrain())
let terrainColor = this.theme.terrainColor(tile)
this.paintCell(tile.cell(), terrainColor)
const owner = tile.owner()
if (owner.isPlayer()) {
+1 -1
View File
@@ -1,4 +1,4 @@
import {Game, Player, Tile, Cell, TerrainTypes} from '../../core/Game';
import {Game, Player, Tile, Cell} from '../../core/Game';
import {within} from '../../core/Util';
export interface Point {
+2 -22
View File
@@ -37,35 +37,15 @@ export class PlayerInfo {
) { }
}
// TODO: make terrain api better.
export class Terrain {
constructor(
public readonly expansionCost: number,
public readonly expansionTime: number,
) { }
}
export type TerrainType = typeof TerrainTypes[keyof typeof TerrainTypes];
export const TerrainTypes = {
Land: new Terrain(1, 1),
Water: new Terrain(0, 0)
}
export interface TerrainMap {
terrain(cell: Cell): Terrain
width(): number
height(): number
}
export interface Tile {
isLand(): boolean
isWater(): boolean
owner(): Player | TerraNullius
hasOwner(): boolean
isBorder(): boolean
borders(other: Player | TerraNullius): boolean
isInterior(): boolean
cell(): Cell
terrain(): Terrain
game(): Game
neighbors(): Tile[]
onShore(): boolean
+11 -5
View File
@@ -1,5 +1,6 @@
import {EventBus} from "./EventBus";
import {Cell, Execution, MutableGame, Game, MutablePlayer, PlayerEvent, PlayerID, PlayerInfo, Player, TerrainMap, TerrainType, TerrainTypes, TerraNullius, Tile, TileEvent, Boat, MutableBoat, BoatEvent} from "./Game";
import {Cell, Execution, MutableGame, Game, MutablePlayer, PlayerEvent, PlayerID, PlayerInfo, Player, TerraNullius, Tile, TileEvent, Boat, MutableBoat, BoatEvent} from "./Game";
import {TerrainMap, TerrainType, TerrainTypes} from "./TerrainMapLoader";
export function createGame(terrainMap: TerrainMap, eventBus: EventBus): Game {
return new GameImpl(terrainMap, eventBus)
@@ -18,6 +19,12 @@ class TileImpl implements Tile {
private readonly _cell: Cell,
private readonly _terrain: TerrainType
) { }
isLand(): boolean {
return this._terrain == TerrainTypes.Land
}
isWater(): boolean {
return this._terrain == TerrainTypes.Water
}
borders(other: Player | TerraNullius): boolean {
for (const n of this.neighbors()) {
@@ -30,7 +37,7 @@ class TileImpl implements Tile {
onShore(): boolean {
return this.neighbors()
.filter(t => t.terrain() == TerrainTypes.Water)
.filter(t => t.isWater())
.length > 0
}
@@ -39,7 +46,6 @@ class TileImpl implements Tile {
isBorder(): boolean {return this._isBorder}
isInterior(): boolean {return this.hasOwner() && !this.isBorder()}
cell(): Cell {return this._cell}
terrain(): TerrainType {return this._terrain}
neighbors(): Tile[] {
if (this._neighbors == null) {
@@ -131,7 +137,7 @@ export class PlayerImpl implements MutablePlayer {
const ns: Set<(MutablePlayer | TerraNullius)> = new Set()
for (const border of this.borderTiles()) {
for (const neighbor of border.neighbors()) {
if (neighbor.terrain() == TerrainTypes.Land && neighbor.owner() != this) {
if (neighbor.isLand() && neighbor.owner() != this) {
ns.add((neighbor as TileImpl)._owner)
}
}
@@ -353,7 +359,7 @@ export class GameImpl implements MutableGame {
if (!owner.isPlayer()) {
throw new Error("Must be a player")
}
if (tile.terrain() == TerrainTypes.Water) {
if (tile.isWater()) {
throw new Error("Cannot conquer water")
}
const tileImpl = tile as TileImpl
+23 -2
View File
@@ -1,12 +1,33 @@
import {Jimp as JimpType, JimpConstructors} from '@jimp/core';
import 'jimp';
import {TerrainMap, TerrainType, TerrainTypes} from './Game';
import {TerrainMapImpl} from './GameImpl';
import {TerrainTile} from '../../generated/protos';
import {Cell} from './Game';
declare const Jimp: JimpType & JimpConstructors;
// TODO: make terrain api better.
export class Terrain {
constructor(
public readonly expansionCost: number,
public readonly expansionTime: number,
) { }
}
export type TerrainType = typeof TerrainTypes[keyof typeof TerrainTypes];
export const TerrainTypes = {
Land: new Terrain(1, 1),
Water: new Terrain(0, 0)
}
export interface TerrainMap {
terrain(cell: Cell): Terrain
width(): number
height(): number
}
export async function loadTerrainMap(): Promise<TerrainMap> {
const imageModule = await import(`../../resources/maps/World.png`);
const imageUrl = imageModule.default;
+2 -2
View File
@@ -1,4 +1,4 @@
import {Player, PlayerID, PlayerInfo, TerrainType, TerrainTypes, TerraNullius, Tile} from "../Game";
import {Player, PlayerID, PlayerInfo, TerraNullius, Tile} from "../Game";
import {Colord, colord} from "colord";
import {devConfig} from "./DevConfig";
import {defaultConfig} from "./DefaultConfig";
@@ -41,7 +41,7 @@ export interface Theme {
playerInfoColor(id: PlayerID): Colord;
territoryColor(id: PlayerID): Colord;
borderColor(id: PlayerID): Colord;
terrainColor(tile: TerrainType): Colord;
terrainColor(tile: Tile): Colord;
backgroundColor(): Colord;
font(): string;
}
+5 -4
View File
@@ -1,5 +1,5 @@
import {Colord, colord} from "colord";
import {PlayerID, TerrainType, TerrainTypes} from "../Game";
import {PlayerID, Tile} from "../Game";
import {Theme} from "./Config";
export const pastelTheme = new class implements Theme {
@@ -75,11 +75,12 @@ export const pastelTheme = new class implements Theme {
})
}
terrainColor(tile: TerrainType): Colord {
if (tile == TerrainTypes.Land) {
terrainColor(tile: Tile): Colord {
if (tile.isLand()) {
return this.land;
} else {
return this.water;
}
return this.water;
}
backgroundColor(): Colord {
+3 -3
View File
@@ -1,5 +1,5 @@
import PriorityQueue from "priority-queue-typescript";
import {Cell, Execution, MutableGame, MutablePlayer, PlayerID, Player, TerrainTypes, TerraNullius, Tile} from "../Game";
import {Cell, Execution, MutableGame, MutablePlayer, PlayerID, TerraNullius, Tile} from "../Game";
import {PseudoRandom} from "../PseudoRandom";
import {manhattanDist} from "../Util";
import {Config, PlayerConfig} from "../configuration/Config";
@@ -145,13 +145,13 @@ export class AttackExecution implements Execution {
}
for (const tile of existingBorder) {
for (const neighbor of tile.neighbors()) {
if (neighbor.terrain() == TerrainTypes.Water || neighbor.owner() != this.target) {
if (neighbor.isWater() || neighbor.owner() != this.target) {
continue
}
newBorder.add(neighbor)
this.numTilesWithEnemy += 1
let numOwnedByMe = neighbor.neighbors()
.filter(t => t.terrain() == TerrainTypes.Land)
.filter(t => t.isLand())
.filter(t => t.owner() == this._owner)
.length
let dist = 0
+2 -2
View File
@@ -1,5 +1,5 @@
import PriorityQueue from "priority-queue-typescript";
import {Boat, Cell, Execution, MutableBoat, MutableGame, MutablePlayer, Player, PlayerID, TerrainTypes, Tile} from "../Game";
import {Boat, Cell, Execution, MutableBoat, MutableGame, MutablePlayer, Player, PlayerID, Tile} from "../Game";
import {manhattanDist} from "../Util";
import {AttackExecution} from "./AttackExecution";
import {Config, PlayerConfig} from "../configuration/Config";
@@ -165,7 +165,7 @@ export class AStar {
}
for (const neighbor of this.current.neighbors()) {
if (neighbor != this.dst && neighbor.terrain() != TerrainTypes.Water) continue; // Skip non-water tiles
if (neighbor != this.dst && neighbor.isLand()) continue; // Skip non-water tiles
const tentativeGScore = this.gScore.get(this.current)! + 1; // Assuming uniform cost
+2 -2
View File
@@ -1,5 +1,5 @@
import {Config, PlayerConfig} from "../configuration/Config";
import {Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerInfo, TerrainTypes, TerraNullius} from "../Game"
import {Cell, Execution, MutableGame, MutablePlayer, Player, PlayerID, PlayerInfo, TerraNullius} from "../Game"
import {PseudoRandom} from "../PseudoRandom"
import {AttackExecution} from "./AttackExecution";
@@ -38,7 +38,7 @@ export class BotExecution implements Execution {
if (this.neighborsTerra) {
for (const b of this.bot.borderTiles()) {
for (const n of b.neighbors()) {
if (n.owner() == this.gs.terraNullius() && n.terrain() == TerrainTypes.Land) {
if (n.owner() == this.gs.terraNullius() && n.isLand()) {
this.sendAttack(this.gs.terraNullius())
return
}
+2 -2
View File
@@ -1,4 +1,4 @@
import {Cell, Game, TerrainTypes} from "../Game";
import {Cell, Game} from "../Game";
import {PseudoRandom} from "../PseudoRandom";
import {SpawnIntent} from "../Schemas";
import {getSpawnCells} from "./Util";
@@ -19,7 +19,7 @@ export class BotSpawner {
this.numFreeTiles = 0;
this.gs.forEachTile(tile => {
if (tile.terrain() == TerrainTypes.Water) {
if (tile.isWater()) {
return;
}
if (tile.hasOwner()) {
+2 -2
View File
@@ -1,4 +1,4 @@
import {Game, Cell, TerrainTypes} from "../Game";
import {Game, Cell} from "../Game";
export function getSpawnCells(gs: Game, cell: Cell): Cell[] {
@@ -12,7 +12,7 @@ export function getSpawnCells(gs: Game, cell: Cell): Cell[] {
if (Math.abs(dx) === 2 && Math.abs(dy) === 2) {
continue;
}
if (gs.tile(c).terrain() != TerrainTypes.Land) {
if (gs.tile(c).isWater()) {
continue;
}
if (gs.tile(c).hasOwner()) {