diff --git a/TODO.txt b/TODO.txt index 8bdd0421d..0c008be98 100644 --- a/TODO.txt +++ b/TODO.txt @@ -103,16 +103,17 @@ * Update landing page image DONE 9/9/2024 * BUG: fix tile per turn pacing issues DONE 9/9/2024 * Improve expansion directions (more uniform) 9/9/2024 +* store username DONE 9/11/2024 +* give anon unique number after Anon name DONE 9/11/2024 +* make color a hash of name DONE 9/11/2024 +* Make boats more intuitive (larger area to click off coast) +* FakeHumans retaliate when attacked --- v3 Release -* store username + * store cookies -* Make boats more intuitive (larger area to click off coast) -* give anon unique number after Anon name -* make color a hash of name -* FakeHumans retaliate when attacked * BUG: when clicking on enemy sometimes boat goes all the way around * names dissappear too much (maybe screen size) * names dissapear on bottom of screen diff --git a/src/client/Client.ts b/src/client/Client.ts index 5cc970374..b8c9c031d 100644 --- a/src/client/Client.ts +++ b/src/client/Client.ts @@ -11,8 +11,12 @@ import './styles.css'; import {simpleHash} from "../core/Util"; import {PseudoRandom} from "../core/PseudoRandom"; +const usernameKey: string = 'username'; + class Client { + + private terrainMap: Promise private game: ClientGame private lobbiesInterval: NodeJS.Timeout | null = null; @@ -26,6 +30,13 @@ class Client { } initialize(): void { + const storedUsername = localStorage.getItem(usernameKey) + if (storedUsername) { + const usernameInput = document.getElementById('username') as HTMLInputElement | null; + if (usernameInput) { + usernameInput.value = storedUsername + } + } this.config = getConfig() setFavicon() this.terrainMap = loadTerrainMap() @@ -125,7 +136,7 @@ class Client { ]); console.log(`got ip ${clientIP}`) this.game = createClientGame( - getUsername(), + refreshUsername(), uuidv4(), uuidv4(), clientIP, @@ -164,20 +175,32 @@ class Client { } } -function getUsername(): string { +function refreshUsername(): string { const usernameInput = document.getElementById('username') as HTMLInputElement | null; - if (usernameInput) { - const trimmedValue = usernameInput.value.trim(); - return trimmedValue || 'Anon'; // Return 'Anon' if the trimmed value is empty + if (usernameInput == null) { + console.warn('username element not found') + return "Anon" + } + if (usernameInput && usernameInput.value.trim()) { + const trimmedValue = usernameInput.value.trim(); + localStorage.setItem(usernameKey, trimmedValue) + return trimmedValue + } else { + const storedUsername = localStorage.getItem(usernameKey); + if (storedUsername) { + return storedUsername + } + const newUsername = "Anon" + uuidToThreeDigits() + localStorage.setItem(usernameKey, newUsername) + return newUsername } - return 'Anon'; // Return 'Anon' if the input element is not found } function setupUsernameCallback(callback: (username: string) => void): void { const usernameInput = document.getElementById('username') as HTMLInputElement | null; if (usernameInput) { usernameInput.addEventListener('input', () => { - const username = getUsername(); + const username = refreshUsername(); callback(username); }); } else { @@ -229,4 +252,19 @@ function setFavicon(): void { link.rel = 'shortcut icon'; link.href = favicon; document.head.appendChild(link); +} + +function uuidToThreeDigits(): string { + const uuid = uuidv4() + // Remove hyphens and convert to lowercase + const cleanUuid = uuid.replace(/-/g, '').toLowerCase(); + + // Convert hex string to decimal + const decimal = BigInt(`0x${cleanUuid}`); + + // Get last 3 digits + const threeDigits = decimal % 1000n; + + // Pad with leading zeros if necessary + return threeDigits.toString().padStart(3, '0'); } \ No newline at end of file diff --git a/src/client/ClientGame.ts b/src/client/ClientGame.ts index 4ed0a7bf9..50b4ffdfd 100644 --- a/src/client/ClientGame.ts +++ b/src/client/ClientGame.ts @@ -11,6 +11,7 @@ import {and, bfs, dist, manhattanDist} from "../core/Util"; import {TerrainRenderer} from "./graphics/TerrainRenderer"; + export function createClientGame(name: string, clientID: ClientID, playerID: PlayerID, ip: string | null, gameID: GameID, config: Config, terrainMap: TerrainMap): ClientGame { let eventBus = new EventBus() let game = createGame(terrainMap, eventBus, config) @@ -32,7 +33,6 @@ export function createClientGame(name: string, clientID: ClientID, playerID: Pla } export class ClientGame { - private myPlayer: Player private turns: Turn[] = [] private socket: WebSocket @@ -119,6 +119,8 @@ export class ClientGame { public start() { console.log('version 3') + + this.isActive = true // TODO: make each class do this, or maybe have client intercept all requests? //this.eventBus.on(TickEvent, (e) => this.tick(e)) diff --git a/src/client/graphics/TerritoryRenderer.ts b/src/client/graphics/TerritoryRenderer.ts index a77b985a8..5321564e9 100644 --- a/src/client/graphics/TerritoryRenderer.ts +++ b/src/client/graphics/TerritoryRenderer.ts @@ -1,5 +1,5 @@ import {PriorityQueue} from "@datastructures-js/priority-queue"; -import {Boat, BoatEvent, Cell, Game, Tile, TileEvent} from "../../core/Game"; +import {Boat, BoatEvent, Cell, Game, Player, Tile, TileEvent} from "../../core/Game"; import {PseudoRandom} from "../../core/PseudoRandom"; import {Colord} from "colord"; import {bfs, dist} from "../../core/Util"; @@ -65,12 +65,12 @@ export class TerritoryRenderer { bfs(event.boat.tile(), dist(event.boat.tile(), 4)).forEach( t => { if (trail.has(t)) { - this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().id()), 150) + this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().info()), 150) } } ) - 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()), 180)) + bfs(event.boat.tile(), dist(event.boat.tile(), 2)).forEach(t => this.paintCell(t.cell(), this.theme.borderColor(event.boat.owner().info()), 255)) + bfs(event.boat.tile(), dist(event.boat.tile(), 1)).forEach(t => this.paintCell(t.cell(), this.theme.territoryColor(event.boat.owner().info()), 180)) } else { trail.forEach(t => this.paintTerritory(t)) this.boatToTrail.delete(event.boat) @@ -96,16 +96,17 @@ export class TerritoryRenderer { this.clearCell(tile.cell()) return } + const owner = tile.owner() as Player if (tile.isBorder()) { this.paintCell( tile.cell(), - this.theme.borderColor(tile.owner().id()), + this.theme.borderColor(owner.info()), 255 ) } else { this.paintCell( tile.cell(), - this.theme.territoryColor(tile.owner().id()), + this.theme.territoryColor(owner.info()), 110 ) } diff --git a/src/client/styles.css b/src/client/styles.css index 1e4079844..e67b62174 100644 --- a/src/client/styles.css +++ b/src/client/styles.css @@ -58,14 +58,15 @@ h3 { } #username { - width: 100%; + width: 60%; padding: 15px; - font-size: 30px; + font-size: 40px; text-align: center; border: 2px solid #007bff; border-radius: 5px; background-color: rgba(255, 255, 255, 0.8); margin: 30px auto; + display: block; } #lobbies-container { diff --git a/src/core/Game.ts b/src/core/Game.ts index ceafddfc6..c3960b51d 100644 --- a/src/core/Game.ts +++ b/src/core/Game.ts @@ -1,3 +1,4 @@ +import {info} from "console" import {Config} from "./configuration/Config" import {GameEvent} from "./EventBus" import {ClientID, GameID} from "./Schemas" diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts index 4e39784e4..ec3f32229 100644 --- a/src/core/configuration/Config.ts +++ b/src/core/configuration/Config.ts @@ -50,8 +50,8 @@ export interface Config { export interface Theme { playerInfoColor(id: PlayerID): Colord; - territoryColor(id: PlayerID): Colord; - borderColor(id: PlayerID): Colord; + territoryColor(playerInfo: PlayerInfo): Colord; + borderColor(playerInfo: PlayerInfo): Colord; terrainColor(tile: Tile): Colord; backgroundColor(): Colord; font(): string; diff --git a/src/core/configuration/PastelTheme.ts b/src/core/configuration/PastelTheme.ts index a143ccb97..7ed4da2e1 100644 --- a/src/core/configuration/PastelTheme.ts +++ b/src/core/configuration/PastelTheme.ts @@ -1,5 +1,5 @@ import {Colord, colord, random} from "colord"; -import {PlayerID, TerrainType, Tile} from "../Game"; +import {PlayerID, PlayerInfo, TerrainType, Tile} from "../Game"; import {Theme} from "./Config"; import {time} from "console"; import {PseudoRandom} from "../PseudoRandom"; @@ -119,12 +119,12 @@ export const pastelTheme = new class implements Theme { return colord({r: 50, g: 50, b: 50}) } - territoryColor(id: PlayerID): Colord { - return this.territoryColors[simpleHash(id) % this.territoryColors.length] + territoryColor(playerInfo: PlayerInfo): Colord { + return this.territoryColors[simpleHash(playerInfo.name) % this.territoryColors.length] } - borderColor(id: PlayerID): Colord { - const tc = this.territoryColor(id).rgba; + borderColor(playerInfo: PlayerInfo): Colord { + const tc = this.territoryColor(playerInfo).rgba; return colord({ r: Math.max(tc.r - 40, 0), g: Math.max(tc.g - 40, 0),