diff --git a/TODO.txt b/TODO.txt index e54134a54..29bbf873f 100644 --- a/TODO.txt +++ b/TODO.txt @@ -83,24 +83,23 @@ * BUG: attacks starts slow but gets faster DONE 9/2/2024 * BUG: can't join game if click with 1s before start DONE 9/2/2024 * boat not going around world dist vs distwrapped DONE 9/2/2024 - -* test & deploy game +* test & deploy game DONE 9/2/2024 --- v2 Release +* put number of troops ui DONE 9/3/2024 +* boats leave trails * names don't appear when zoomed out -* don't join game for reason * make names bigger * send boat even if touching -* put number of troops ui -* boats leave trails +* directed expansion * more random names for game id & client id * Make fake humans -* directed expansion -* win condition & popup -* BUG: tiles get left behind during conquer; still problem? +* UI: win condition & popup +* UI: boats +* UI: current attacks +* UI: leader board * Load terrain dataImage in background -* BUG: shore tiles left behind during conquer * BUG: when sending boat to TerraNullius, only takes one tile * REFACTOR: give terranullius an ID, game.player() returns terranullius * REFACTOR: ocean is considered TerraNullius ? diff --git a/src/client/ClientGame.ts b/src/client/ClientGame.ts index 595d87458..29659271c 100644 --- a/src/client/ClientGame.ts +++ b/src/client/ClientGame.ts @@ -16,7 +16,7 @@ export function createClientGame(name: string, clientID: ClientID, ip: string | let eventBus = new EventBus() let game = createGame(terrainMap, eventBus, config) let terrainRenderer = new TerrainRenderer(game) - let gameRenderer = new GameRenderer(game, terrainRenderer) + let gameRenderer = new GameRenderer(game, clientID, terrainRenderer) return new ClientGame( name, diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index deca40b1d..2dea647de 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -5,6 +5,9 @@ import {DragEvent, ZoomEvent} from "../InputHandler"; import {NameRenderer} from "./NameRenderer"; import {TerrainRenderer} from "./TerrainRenderer"; import {TerritoryRenderer} from "./TerritoryRenderer"; +import {ClientID} from "../../core/Schemas"; +import {renderTroops} from "./Utils"; +import {UIRenderer} from "./UIRenderer"; export class GameRenderer { private territoryCanvas: HTMLCanvasElement @@ -20,16 +23,16 @@ export class GameRenderer { private nameRenderer: NameRenderer; private territoryRenderer: TerritoryRenderer; + private uiRenderer: UIRenderer; private theme: Theme - private exitButton: HTMLButtonElement; - - constructor(private gs: Game, private terrainRenderer: TerrainRenderer) { + constructor(private gs: Game, private clientID: ClientID, private terrainRenderer: TerrainRenderer) { this.theme = gs.config().theme() this.nameRenderer = new NameRenderer(gs, this.theme) this.territoryRenderer = new TerritoryRenderer(gs) + this.uiRenderer = new UIRenderer(gs, this.theme, clientID) } initialize() { @@ -46,6 +49,7 @@ export class GameRenderer { this.nameRenderer.initialize() this.terrainRenderer.init() this.territoryRenderer.init() + this.uiRenderer.init() document.body.appendChild(this.canvas); @@ -59,53 +63,9 @@ export class GameRenderer { this.territoryContext = this.territoryCanvas.getContext('2d') this.territoryContext.globalAlpha = 0.4; - this.createExitButton() - - requestAnimationFrame(() => this.renderGame()); } - - - createExitButton() { - this.exitButton = document.createElement('button'); - this.exitButton.innerHTML = '✕'; // HTML entity for "×" (multiplication sign) - this.exitButton.style.position = 'fixed'; - this.exitButton.style.top = '20px'; - this.exitButton.style.right = '20px'; - this.exitButton.style.zIndex = '1000'; - this.exitButton.style.width = '40px'; - this.exitButton.style.height = '40px'; - this.exitButton.style.fontSize = '20px'; - this.exitButton.style.fontWeight = 'bold'; - this.exitButton.style.backgroundColor = 'rgba(255, 0, 0, 0.4)'; // More translucent red - this.exitButton.style.color = 'white'; - this.exitButton.style.border = 'none'; - this.exitButton.style.borderRadius = '50%'; - this.exitButton.style.cursor = 'pointer'; - this.exitButton.style.display = 'flex'; - this.exitButton.style.justifyContent = 'center'; - this.exitButton.style.alignItems = 'center'; - this.exitButton.style.transition = 'background-color 0.3s'; - - this.exitButton.addEventListener('mouseover', () => { - this.exitButton.style.backgroundColor = 'rgba(255, 0, 0, 0.5)'; // Less translucent on hover - }); - - this.exitButton.addEventListener('mouseout', () => { - this.exitButton.style.backgroundColor = 'rgba(255, 0, 0, 0.3)'; // Back to more translucent - }); - - this.exitButton.addEventListener('click', () => this.onExitButtonClick()); - document.body.appendChild(this.exitButton); - } - - onExitButtonClick() { - console.log('Button clicked!'); - window.location.reload(); - // Add your button action here - } - resizeCanvas() { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; @@ -147,11 +107,13 @@ export class GameRenderer { this.context.restore() this.renderUIBar() + this.uiRenderer.render(this.context) requestAnimationFrame(() => this.renderGame()); } + // TODO: move to UIRenderer renderUIBar() { if (!this.gs.inSpawnPhase()) { return @@ -191,21 +153,6 @@ export class GameRenderer { this.canvas.height = Math.ceil(height / window.devicePixelRatio); } - // 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) { color = color.alpha(10) // Assign the result back to color this.territoryContext.fillStyle = color.toHslString() diff --git a/src/client/graphics/NameRenderer.ts b/src/client/graphics/NameRenderer.ts index 5c3ab2894..c0bdb8c66 100644 --- a/src/client/graphics/NameRenderer.ts +++ b/src/client/graphics/NameRenderer.ts @@ -3,6 +3,7 @@ import {PseudoRandom} from "../../core/PseudoRandom" import {calculateBoundingBox} from "../../core/Util" import {Theme} from "../../core/configuration/Config" import {placeName} from "./NameBoxCalculator" +import {renderTroops} from "./Utils" class RenderInfo { public isVisible = true @@ -123,19 +124,7 @@ export class NameRenderer { context.fillText(render.player.name(), nameCenterX, nameCenterY - render.fontSize / 2); context.font = `bold ${render.fontSize}px ${this.theme.font()}`; - let troopsStr: string = "" - let troops = render.player.troops() / 10 - - if (troops > 100000) { - troopsStr = String(Math.floor(troops / 1000)) + "K" - } else if (troops > 10000) { - troopsStr = String((troops / 1000).toFixed(1)) + "K" - } else if (troops > 1000) { - troopsStr = String((troops / 1000).toFixed(2)) + "K" - } - else { - troopsStr = String(Math.floor(troops)) - } - context.fillText(troopsStr, nameCenterX, nameCenterY + render.fontSize); + + context.fillText(renderTroops(render.player.troops()), nameCenterX, nameCenterY + render.fontSize); } } \ No newline at end of file diff --git a/src/client/graphics/UIRenderer.ts b/src/client/graphics/UIRenderer.ts new file mode 100644 index 000000000..f4ebc936d --- /dev/null +++ b/src/client/graphics/UIRenderer.ts @@ -0,0 +1,73 @@ +import {Theme} from "../../core/configuration/Config"; +import {Game} from "../../core/Game"; +import {ClientID} from "../../core/Schemas"; +import {renderTroops} from "./Utils"; + +export class UIRenderer { + private exitButton: HTMLButtonElement; + + constructor(private game: Game, private theme: Theme, private clientID: ClientID) { + + } + + init() { + this.createExitButton() + } + + createExitButton() { + this.exitButton = document.createElement('button'); + this.exitButton.innerHTML = '✕'; // HTML entity for "×" (multiplication sign) + this.exitButton.style.position = 'fixed'; + this.exitButton.style.top = '20px'; + this.exitButton.style.right = '20px'; + this.exitButton.style.zIndex = '1000'; + this.exitButton.style.width = '40px'; + this.exitButton.style.height = '40px'; + this.exitButton.style.fontSize = '20px'; + this.exitButton.style.fontWeight = 'bold'; + this.exitButton.style.backgroundColor = 'rgba(255, 0, 0, 0.4)'; // More translucent red + this.exitButton.style.color = 'white'; + this.exitButton.style.border = 'none'; + this.exitButton.style.borderRadius = '50%'; + this.exitButton.style.cursor = 'pointer'; + this.exitButton.style.display = 'flex'; + this.exitButton.style.justifyContent = 'center'; + this.exitButton.style.alignItems = 'center'; + this.exitButton.style.transition = 'background-color 0.3s'; + + this.exitButton.addEventListener('mouseover', () => { + this.exitButton.style.backgroundColor = 'rgba(255, 0, 0, 0.5)'; // Less translucent on hover + }); + + this.exitButton.addEventListener('mouseout', () => { + this.exitButton.style.backgroundColor = 'rgba(255, 0, 0, 0.3)'; // Back to more translucent + }); + + this.exitButton.addEventListener('click', () => this.onExitButtonClick()); + document.body.appendChild(this.exitButton); + } + + render(context) { + const p = this.game.players().find(p => p.clientID() == this.clientID); + let troopCount = p ? `${renderTroops(p.troops())}` : ''; + + context.save(); + context.fillStyle = 'black'; + context.textAlign = 'center'; + context.textBaseline = 'top'; + + const x = 65 + 18 * (troopCount.length - 2); // Right edge of the text area + const y = 40; // Distance from the top + + context.font = `bold ${60}px ${this.theme.font()}`; + context.fillText(troopCount, x, y); + context.restore(); + } + + onExitButtonClick() { + console.log('Button clicked!'); + window.location.reload(); + // Add your button action here + } + +} \ No newline at end of file diff --git a/src/client/graphics/Utils.ts b/src/client/graphics/Utils.ts new file mode 100644 index 000000000..ddabb5383 --- /dev/null +++ b/src/client/graphics/Utils.ts @@ -0,0 +1,17 @@ +export function renderTroops(troops: number): string { + let troopsStr = '' + + troops = troops / 10 + + if (troops > 100000) { + troopsStr = String(Math.floor(troops / 1000)) + "K" + } else if (troops > 10000) { + troopsStr = String((troops / 1000).toFixed(1)) + "K" + } else if (troops > 1000) { + troopsStr = String((troops / 1000).toFixed(2)) + "K" + } + else { + troopsStr = String(Math.floor(troops)) + } + return troopsStr +} \ No newline at end of file diff --git a/src/core/configuration/DevConfig.ts b/src/core/configuration/DevConfig.ts index 8137e9b78..1a59512eb 100644 --- a/src/core/configuration/DevConfig.ts +++ b/src/core/configuration/DevConfig.ts @@ -1,4 +1,4 @@ -import {PlayerInfo} from "../Game"; +import {Player, PlayerInfo} from "../Game"; import {DefaultConfig} from "./DefaultConfig"; export const devConfig = new class extends DefaultConfig { @@ -25,4 +25,13 @@ export const devConfig = new class extends DefaultConfig { } return 5000 } + + troopAdditionRate(player: Player): number { + let max = Math.sqrt(player.numTilesOwned()) * 2000 + 10000 + 10000 + max = Math.min(max, 1_000_000) + + let toAdd = 10 + (player.troops() + Math.sqrt(player.troops() * player.numTilesOwned())) / 200 * 100 + + return Math.min(player.troops() + toAdd, max) + } } \ No newline at end of file