From 39464f1d05c1d196c6093e7e359ba550479ff678 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Sat, 31 Aug 2024 12:20:15 -0700 Subject: [PATCH] make territory translucent --- TODO.txt | 5 +- src/client/graphics/GameRenderer.ts | 79 ++++++------------ src/client/graphics/TerrainRenderer.ts | 5 +- src/client/graphics/TerritoryRenderer.ts | 102 +++++++++++++++++++++++ src/core/configuration/DevConfig.ts | 2 +- src/core/configuration/PastelTheme.ts | 33 +++++++- 6 files changed, 165 insertions(+), 61 deletions(-) create mode 100644 src/client/graphics/TerritoryRenderer.ts diff --git a/TODO.txt b/TODO.txt index ff64cc283..a36eb8921 100644 --- a/TODO.txt +++ b/TODO.txt @@ -63,6 +63,7 @@ * PERF: load terrain map async DONE 8/30/2024 * if completely surrended, lose piece of land DONE 8/30/2024 * Add terrain elevation to map DONE 8/30/2024 +* Make territory translucent DONE 8/31/2024 * Have terrain affect attack * improve pastel territory colors * PERF: enable CDN @@ -70,11 +71,11 @@ * end game when no players left (or after 1 hour or so?) * use better favicon * BUG: tiles get left behind during conquer -* REFACTOR: give terranullius an ID, game.player() returns terranullius -* REFACTOR: ocean is considered TerraNullius ? * Create exit to menu button * Make fake humans * Load terrain dataImage in background * BUG: shore tiles left behind during conquer * BUG: when sending boat to TerraNullius, only takes one tile * directed expansion +* REFACTOR: give terranullius an ID, game.player() returns terranullius +* REFACTOR: ocean is considered TerraNullius ? diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 76dd79fab..f60ddde9b 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -3,10 +3,8 @@ 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 {bfs, dist, manhattanDist} from "../../core/Util"; -import {PseudoRandom} from "../../core/PseudoRandom"; import {TerrainRenderer} from "./TerrainRenderer"; -import {PriorityQueue} from "@datastructures-js/priority-queue"; +import {TerritoryRenderer} from "./TerritoryRenderer"; export class GameRenderer { private territoryCanvas: HTMLCanvasElement @@ -21,15 +19,14 @@ export class GameRenderer { private context: CanvasRenderingContext2D private nameRenderer: NameRenderer; + private territoryRenderer: TerritoryRenderer; + private theme: Theme - private random = new PseudoRandom(123) - - private tileToRenderQueue: PriorityQueue<{tileEvent: TileEvent, lastUpdate: number}> = new PriorityQueue((a, b) => {return a.lastUpdate - b.lastUpdate}) - constructor(private gs: Game, private terrainRenderer: TerrainRenderer) { this.theme = gs.config().theme() this.nameRenderer = new NameRenderer(gs, this.theme) + this.territoryRenderer = new TerritoryRenderer(gs) } initialize() { @@ -45,6 +42,7 @@ export class GameRenderer { this.nameRenderer.initialize() this.terrainRenderer.init() + this.territoryRenderer.init() document.body.appendChild(this.canvas); @@ -56,6 +54,8 @@ export class GameRenderer { this.territoryCanvas.width = this.gs.width(); this.territoryCanvas.height = this.gs.height(); this.territoryContext = this.territoryCanvas.getContext('2d') + this.territoryContext.globalAlpha = 0.4; + requestAnimationFrame(() => this.renderGame()); } @@ -67,7 +67,6 @@ export class GameRenderer { } renderGame() { - // Set background this.context.fillStyle = this.theme.backgroundColor().toHex(); this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); @@ -94,15 +93,7 @@ export class GameRenderer { ); this.terrainRenderer.draw(this.context) - this.renderTerritory() - - this.context.drawImage( - this.territoryCanvas, - -this.gs.width() / 2, - -this.gs.height() / 2, - this.gs.width(), - this.gs.height() - ) + this.territoryRenderer.draw(this.context) const [upperLeft, bottomRight] = this.boundingRect() this.nameRenderer.render(this.context, this.scale, upperLeft, bottomRight) @@ -111,24 +102,9 @@ export class GameRenderer { this.renderUIBar() - requestAnimationFrame(() => this.renderGame()); } - renderTerritory() { - let numToRender = Math.floor(this.tileToRenderQueue.size() / 10) - if (numToRender == 0) { - numToRender = this.tileToRenderQueue.size() - } - - while (numToRender > 0) { - numToRender-- - const event = this.tileToRenderQueue.pop().tileEvent - this.paintTerritory(event.tile) - event.tile.neighbors().forEach(t => this.paintTerritory(t)) - } - } - renderUIBar() { if (!this.gs.inSpawnPhase()) { @@ -153,18 +129,15 @@ export class GameRenderer { } tileUpdate(event: TileEvent) { - this.tileToRenderQueue.push({tileEvent: event, lastUpdate: this.gs.ticks() + this.random.nextFloat(0, .5)}) + this.territoryRenderer.tileUpdate(event) + // this.tileToRenderQueue.push({tileEvent: event, lastUpdate: this.gs.ticks() + this.random.nextFloat(0, .5)}) } playerEvent(event: PlayerEvent) { } boatEvent(event: BoatEvent) { - bfs(event.oldTile, dist(event.oldTile, 2)).forEach(t => this.paintTerritory(t)) - if (event.boat.isActive()) { - bfs(event.boat.tile(), dist(event.boat.tile(), 2)).forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.boat.owner().id()))) - bfs(event.boat.tile(), dist(event.boat.tile(), 1)).forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().id()))) - } + this.territoryRenderer.boatEvent(event) } resize(width: number, height: number): void { @@ -172,22 +145,24 @@ export class GameRenderer { this.canvas.height = Math.ceil(height / window.devicePixelRatio); } - paintTerritory(tile: Tile) { - if (!tile.hasOwner()) { - this.clearCell(tile.cell()) - return - } - // this.territoryContext.clearRect(tile.cell().x, tile.cell().y, 1, 1); - if (tile.isBorder()) { - this.territoryContext.fillStyle = this.theme.borderColor(tile.owner().id()).toRgbString() - } else { - this.territoryContext.fillStyle = this.theme.territoryColor(tile.owner().id()).toRgbString() - } - this.territoryContext.fillRect(tile.cell().x, tile.cell().y, 1, 1); - } + // paintTerritory(tile: Tile) { + // this.clearCell(tile.cell()) + // // if (!tile.hasOwner()) { + // // this.clearCell(tile.cell()) + // // return + // // } + // // this.territoryContext.clearRect(tile.cell().x, tile.cell().y, 1, 1); + // if (tile.isBorder()) { + // this.territoryContext.fillStyle = this.theme.borderColor(tile.owner().id()).toRgbString() + // } else { + // this.territoryContext.fillStyle = this.theme.territoryColor(tile.owner().id()).alpha(100).toHex() + // } + // this.territoryContext.fillRect(tile.cell().x, tile.cell().y, 1, 1); + // } paintCell(cell: Cell, color: Colord) { - this.territoryContext.fillStyle = color.toRgbString() + color = color.alpha(10) // Assign the result back to color + this.territoryContext.fillStyle = color.toHslString() this.territoryContext.fillRect(cell.x, cell.y, 1, 1); } diff --git a/src/client/graphics/TerrainRenderer.ts b/src/client/graphics/TerrainRenderer.ts index 2397edae9..1c2fb9d4e 100644 --- a/src/client/graphics/TerrainRenderer.ts +++ b/src/client/graphics/TerrainRenderer.ts @@ -1,5 +1,6 @@ import {inherits} from "util" import {Game} from "../../core/Game"; +import {throws} from "assert"; export class TerrainRenderer { private canvas: HTMLCanvasElement @@ -15,11 +16,9 @@ export class TerrainRenderer { this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height()) this.initImageData() - this.canvas = document.createElement('canvas'); - const backgroundCtx = this.canvas.getContext('2d'); this.canvas.width = this.game.width(); this.canvas.height = this.game.height(); - backgroundCtx.putImageData(this.imageData, 0, 0); + this.context.putImageData(this.imageData, 0, 0); } initImageData() { diff --git a/src/client/graphics/TerritoryRenderer.ts b/src/client/graphics/TerritoryRenderer.ts new file mode 100644 index 000000000..b502a2d9c --- /dev/null +++ b/src/client/graphics/TerritoryRenderer.ts @@ -0,0 +1,102 @@ +import {PriorityQueue} from "@datastructures-js/priority-queue"; +import {BoatEvent, Cell, Game, Tile, TileEvent} from "../../core/Game"; +import {PseudoRandom} from "../../core/PseudoRandom"; +import {Colord} from "colord"; +import {bfs, dist} from "../../core/Util"; +import {Theme} from "../../core/configuration/Config"; + +export class TerritoryRenderer { + private canvas: HTMLCanvasElement + private context: CanvasRenderingContext2D + private imageData: ImageData + + private tileToRenderQueue: PriorityQueue<{tileEvent: TileEvent, lastUpdate: number}> = new PriorityQueue((a, b) => {return a.lastUpdate - b.lastUpdate}) + private random = new PseudoRandom(123) + private theme: Theme = null + + + + constructor(private game: Game) { + this.theme = game.config().theme() + } + + init() { + this.canvas = document.createElement('canvas'); + this.context = this.canvas.getContext("2d") + + this.imageData = this.context.getImageData(0, 0, this.game.width(), this.game.height()) + this.canvas.width = this.game.width(); + this.canvas.height = this.game.height(); + this.context.putImageData(this.imageData, 0, 0); + } + + draw(context: CanvasRenderingContext2D) { + this.renderTerritory() + this.context.putImageData(this.imageData, 0, 0); + context.drawImage( + this.canvas, + -this.game.width() / 2, + -this.game.height() / 2, + this.game.width(), + this.game.height() + ) + } + + boatEvent(event: BoatEvent) { + bfs(event.oldTile, dist(event.oldTile, 2)).forEach(t => this.clearCell(t.cell())) + if (event.boat.isActive()) { + bfs(event.boat.tile(), dist(event.boat.tile(), 2)).forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.boat.owner().id()), 255)) + bfs(event.boat.tile(), dist(event.boat.tile(), 1)).forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().id()), 255)) + } + } + + renderTerritory() { + let numToRender = Math.floor(this.tileToRenderQueue.size() / 10) + if (numToRender == 0) { + numToRender = this.tileToRenderQueue.size() + } + + while (numToRender > 0) { + numToRender-- + const event = this.tileToRenderQueue.pop().tileEvent + this.paintTerritory(event.tile) + event.tile.neighbors().forEach(t => this.paintTerritory(t)) + } + } + + paintTerritory(tile: Tile) { + if (tile.isBorder()) { + this.paintCell( + tile.cell(), + this.theme.borderColor(tile.owner().id()), + 255 + ) + } else { + this.paintCell( + tile.cell(), + this.theme.territoryColor(tile.owner().id()), + 75 + ) + } + } + + paintCell(cell: Cell, color: Colord, alpha: number) { + const index = (cell.y * this.game.width()) + cell.x + const offset = index * 4 + this.imageData.data[offset] = color.rgba.r; + this.imageData.data[offset + 1] = color.rgba.g; + this.imageData.data[offset + 2] = color.rgba.b; + this.imageData.data[offset + 3] = alpha + } + + clearCell(cell: Cell) { + const index = (cell.y * this.game.width()) + cell.x; + const offset = index * 4; + this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent) + } + + + tileUpdate(event: TileEvent) { + this.tileToRenderQueue.push({tileEvent: event, lastUpdate: this.game.ticks() + this.random.nextFloat(0, .5)}) + } +} \ No newline at end of file diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 87e4a33dc..310ff4afb 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -16,7 +16,7 @@ export const devConfig = new class extends DefaultConfig { } numBots(): number { - return 0 + return 250 } startTroops(playerInfo: PlayerInfo): number { diff --git a/src/core/configuration/PastelTheme.ts b/src/core/configuration/PastelTheme.ts index 119200bda..2e73f40bd 100644 --- a/src/core/configuration/PastelTheme.ts +++ b/src/core/configuration/PastelTheme.ts @@ -90,10 +90,37 @@ export const pastelTheme = new class implements Theme { if (tile.isShore()) { return this.shore } + // let mag = Math.min(tile.magnitude() + 4, 15) + // if (mag < 3) { + // mag = 0 + // } + let mag = tile.magnitude() + // if (mag < 3) { + // return colord({ + // r: 0, + // g: 0, + // b: 0 + // }) + // } + + if (mag < 2) { + mag = 0 + } + + // else if (mag < 5) { + // mag = 1 + // } else if (mag < 8) { + // mag = 10 + // } else { + // mag = 15 + // } + + const delta = 8 * mag + return colord({ - r: 174 + 5 * tile.magnitude(), - g: 163 + 5 * tile.magnitude(), - b: 128 + 5 * tile.magnitude() + r: 190 + delta, + g: 193 + delta, + b: 138 + delta }) } else { const w = this.water.rgba