tiles now have lake or ocean method

This commit is contained in:
evanpelle
2024-08-23 17:53:55 -07:00
parent f52b62a354
commit 41d7c77d2d
14 changed files with 89 additions and 70 deletions
+5 -5
View File
@@ -40,15 +40,15 @@
* only put imageDataOnce, draw territories on top DONE 8/23/2024
* have boats not get close to shore DONE 8/23/2024
* improve terrain colors DONE 8/23/2024
* BUG: boat doesn't work if on lake if other player not on same lake
* try vintage theme
* BUG: boat doesn't work if on lake if other player not on same lake
* BUG: boat doesn't work if on lake if other player not on same lake DONE 8/23/2024
* Allow boats to attack TerraNullius
* improve menu
* try vintage theme
* add shader to dim border
* remove player.info()
* improve menu
* give time to (re) spawn at start of game
* BUG: ocean is considered TerraNullius ?
* BUG: fix hotreload (priority queue breaks it)
* PERF: use hierarchical a* search for boats
* Add terrain elevation to map
* Add terrain elevation to map
* Boats can go diagonally
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

+9 -1
View File
@@ -7,6 +7,7 @@ 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";
import {bfs, manhattanDist} from "../core/Util";
@@ -189,10 +190,17 @@ export class ClientGame {
const owner = tile.owner()
const targetID = owner.isPlayer() ? owner.id() : null
if (tile.owner() != this.myPlayer && tile.isLand()) {
// const ocean = Array.from(bfs(tile, 4))
// .filter(t => t.isOcean)
// .sort((a, b) => manhattanDist(tile.cell(), a.cell()) - manhattanDist(tile.cell(), b.cell()))
// if (ocean.length > 0) {
// this.sendBoatAttackIntent(targetID, cell, this.config.player().boatAttackAmount(this.myPlayer, owner))
// return
// }
if (this.myPlayer.sharesBorderWith(tile.owner())) {
this.sendAttackIntent(targetID, cell, this.config.player().attackAmount(this.myPlayer, owner))
} else if (owner.isPlayer()) {
// TODO verify on ocean
console.log('going to send boat')
this.sendBoatAttackIntent(targetID, cell, this.config.player().boatAttackAmount(this.myPlayer, owner))
}
+4 -20
View File
@@ -3,7 +3,7 @@ import {Cell, Game, PlayerEvent, Tile, TileEvent, Player, Execution, BoatEvent}
import {Theme} from "../../core/configuration/Config";
import {DragEvent, ZoomEvent} from "../InputHandler";
import {NameRenderer} from "./NameRenderer";
import {manhattanDist} from "../../core/Util";
import {bfs, manhattanDist} from "../../core/Util";
import {PseudoRandom} from "../../core/PseudoRandom";
@@ -146,30 +146,14 @@ export class GameRenderer {
}
boatEvent(event: BoatEvent) {
this.bfs(event.oldTile, 2).forEach(t => this.paintTerritory(t))
bfs(event.oldTile, 2).forEach(t => this.paintTerritory(t))
if (event.boat.isActive()) {
this.bfs(event.boat.tile(), 2).forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.boat.owner().id())))
this.bfs(event.boat.tile(), 1).forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().id())))
bfs(event.boat.tile(), 2).forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.boat.owner().id())))
bfs(event.boat.tile(), 1).forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().id())))
}
}
private bfs(tile: Tile, dist: number): Set<Tile> {
const seen = new Set<Tile>
const q: Tile[] = []
q.push(tile)
while (q.length > 0) {
const curr = q.pop()
seen.add(curr)
for (const n of curr.neighbors()) {
if (!seen.has(n) && manhattanDist(tile.cell(), n.cell()) <= dist) {
q.push(n)
}
}
}
return seen
}
resize(width: number, height: number): void {
this.canvas.width = Math.ceil(width / window.devicePixelRatio);
this.canvas.height = Math.ceil(height / window.devicePixelRatio);
+1 -1
View File
@@ -74,7 +74,7 @@ export function createGrid(game: Game, player: Player, boundingBox: {min: Point;
const cell = new Cell(x * scalingFactor, y * scalingFactor);
if (game.isOnMap(cell)) {
const tile = game.tile(cell);
grid[x - scaledBoundingBox.min.x][y - scaledBoundingBox.min.y] = tile.owner() === player; // TODO: okay if lake
grid[x - scaledBoundingBox.min.x][y - scaledBoundingBox.min.y] = tile.isLake() || tile.owner() === player; // TODO: okay if lake
}
}
}
+4 -2
View File
@@ -23,8 +23,8 @@ export interface ExecutionView {
}
export interface Execution extends ExecutionView {
init(mg: MutableGame, ticks: number)
tick(ticks: number)
init(mg: MutableGame, ticks: number): void
tick(ticks: number): void
owner(): MutablePlayer
}
@@ -42,6 +42,8 @@ export interface Tile {
isShore(): boolean
isWater(): boolean
isShorelineWater(): boolean
isOcean(): boolean
isLake(): boolean
magnitude(): number
owner(): Player | TerraNullius
hasOwner(): boolean
+6
View File
@@ -19,6 +19,12 @@ class TileImpl implements Tile {
private readonly _cell: Cell,
private readonly _terrain: Terrain
) { }
isLake(): boolean {
return !this.isLand() && !this.isOcean()
}
isOcean(): boolean {
return this._terrain.ocean
}
magnitude(): number {
return this._terrain.magnitude
}
+4 -1
View File
@@ -24,6 +24,7 @@ export enum TerrainType {
export class Terrain {
public shoreline: boolean = false
public ocean: boolean = false
public magnitude: number = 0
constructor(public type: TerrainType) { }
}
@@ -55,10 +56,12 @@ export function loadTerrainMap(): TerrainMap {
const packedByte = fileData.charCodeAt(4 + y * width + x); // +4 to skip dimension bytes
const type = (packedByte & 0b10000000) ? TerrainType.Land : TerrainType.Water;
const shoreline = !!(packedByte & 0b01000000);
const magnitude = packedByte & 0b00111111;
const ocean = !!(packedByte & 0b00100000);
const magnitude = packedByte & 0b00011111;
terrain[x][y] = new Terrain(type);
terrain[x][y].shoreline = shoreline;
terrain[x][y].ocean = ocean;
terrain[x][y].magnitude = magnitude;
}
}
-33
View File
@@ -1,33 +0,0 @@
import {EventBus, GameEvent} from "./EventBus";
import {Config} from "./configuration/Config";
export class TickEvent implements GameEvent {
constructor(public readonly tickCount: number) { }
}
export class Ticker {
private ticker: NodeJS.Timeout;
private tickCount: number;
constructor(private tickInterval: number, private eventBus: EventBus) {
}
start() {
this.tickCount = 0;
this.ticker = setInterval(() => this.tick(), this.tickInterval);
}
stop() {
clearInterval(this.ticker);
}
private tick() {
this.eventBus.emit(new TickEvent(this.tickCount))
this.tickCount++;
}
getTickCount(): number {
return this.tickCount;
}
}
+17 -1
View File
@@ -1,4 +1,4 @@
import {Cell} from "./Game";
import {Cell, Tile} from "./Game";
export function manhattanDist(c1: Cell, c2: Cell): number {
return Math.abs(c1.x - c2.x) + Math.abs(c1.y - c2.y);
@@ -6,4 +6,20 @@ export function manhattanDist(c1: Cell, c2: Cell): number {
export function within(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
export function bfs(tile: Tile, dist: number): Set<Tile> {
const seen = new Set<Tile>
const q: Tile[] = []
q.push(tile)
while (q.length > 0) {
const curr = q.pop()
seen.add(curr)
for (const n of curr.neighbors()) {
if (!seen.has(n) && manhattanDist(tile.cell(), n.cell()) <= dist) {
q.push(n)
}
}
}
return seen
}
+1 -1
View File
@@ -117,7 +117,7 @@ export class BoatAttackExecution implements Execution {
}
private closestShoreTileToTarget(player: Player, target: Cell): Tile | null {
const shoreTiles = Array.from(player.borderTiles()).filter(t => t.onShore())
const shoreTiles = Array.from(player.borderTiles()).filter(t => t.onShore() && t.neighbors().filter(n => n.isOcean()).length > 0)
if (shoreTiles.length == 0) {
return null
}
+38 -5
View File
@@ -39,6 +39,7 @@ export enum TerrainType {
export class Terrain {
public shoreline: boolean = false
public magnitude: number = 0
public ocean: boolean
constructor(public type: TerrainType) { }
}
@@ -57,12 +58,12 @@ export async function loadTerrainMap(): Promise<void> {
for (let x = 0; x < img.width; x++) {
for (let y = 0; y < img.height; y++) {
const color = img.getPixelRGBA(x, y);
const red = (color >> 24) & 0xff;
const alpha = color & 0xff;
if (red > 100) {
terrain[x][y] = new Terrain(TerrainType.Land)
} else {
if (alpha < 20) { // transparent
terrain[x][y] = new Terrain(TerrainType.Water);
} else {
terrain[x][y] = new Terrain(TerrainType.Land)
}
}
}
@@ -70,6 +71,7 @@ export async function loadTerrainMap(): Promise<void> {
const shorelineWaters = processShore(terrain)
processDistToLand(shorelineWaters, terrain)
processOcean(terrain)
const packed = packTerrain(terrain)
const outputPath = path.join(__dirname, '..', '..', 'resources', 'World.bin');
fs.writeFile(outputPath, packed);
@@ -163,7 +165,10 @@ function packTerrain(map: Terrain[][]): Uint8Array {
if (terrain.shoreline) {
packedByte |= 0b01000000;
}
packedByte |= Math.min(Math.ceil(terrain.magnitude / 2), 63);
if (terrain.ocean) {
packedByte |= 0b00100000;
}
packedByte |= Math.min(Math.ceil(terrain.magnitude / 2), 31);
packedData[4 + y * width + x] = packedByte;
}
@@ -172,6 +177,34 @@ function packTerrain(map: Terrain[][]): Uint8Array {
return packedData;
}
function processOcean(map: Terrain[][]) {
const queue: Coord[] = [{x: 0, y: 0}];
const visited = new Set<string>();
while (queue.length > 0) {
const coord = queue.shift()!;
const key = `${coord.x},${coord.y}`;
if (visited.has(key)) continue;
visited.add(key);
const terrain = map[coord.x][coord.y];
if (terrain.type === TerrainType.Water) {
terrain.ocean = true;
// Check neighbors
for (const [dx, dy] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) {
const newX = coord.x + dx;
const newY = coord.y + dy;
if (newX >= 0 && newX < map.length && newY >= 0 && newY < map[0].length) {
queue.push({x: newX, y: newY});
}
}
}
}
}
function logBinaryAsBits(data: Uint8Array, length: number = 8) {
const bits = Array.from(data.slice(0, length))
.map(b => b.toString(2).padStart(8, '0'))