From 180769bf356992cd6bb6ee870bd01a2a4f192816 Mon Sep 17 00:00:00 2001 From: evanpelle Date: Fri, 11 Oct 2024 20:48:55 -0700 Subject: [PATCH] create leaderboard, max 18 char name --- src/client/graphics/GameRenderer.ts | 11 +- src/client/graphics/layers/Layer.ts | 3 +- src/client/graphics/layers/Leaderboard.ts | 179 +++++++++++++++++++ src/client/graphics/layers/NameLayer.ts | 2 +- src/client/graphics/layers/TerrainLayer.ts | 2 +- src/client/graphics/layers/TerritoryLayer.ts | 2 +- src/client/graphics/layers/UILayer.ts | 2 +- src/client/index.html | 11 +- src/client/index.ts | 12 -- src/core/execution/ExecutionManager.ts | 2 +- 10 files changed, 199 insertions(+), 27 deletions(-) create mode 100644 src/client/graphics/layers/Leaderboard.ts delete mode 100644 src/client/index.ts diff --git a/src/client/graphics/GameRenderer.ts b/src/client/graphics/GameRenderer.ts index 3b28f02fc..cf898cc0d 100644 --- a/src/client/graphics/GameRenderer.ts +++ b/src/client/graphics/GameRenderer.ts @@ -10,6 +10,7 @@ import {Layer} from "./layers/Layer"; import {EventsDisplay} from "./layers/EventsDisplay"; import {RadialMenu} from "./layers/radial/RadialMenu"; import {EmojiTable} from "./layers/radial/EmojiTable"; +import {Leaderboard} from "./layers/Leaderboard"; export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: EventBus, clientID: ClientID): GameRenderer { @@ -19,6 +20,13 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: if (!emojiTable || !(emojiTable instanceof EmojiTable)) { console.error('EmojiTable element not found in the DOM'); } + + const leaderboard = document.querySelector('leader-board') as Leaderboard; + if (!emojiTable || !(leaderboard instanceof Leaderboard)) { + console.error('EmojiTable element not found in the DOM'); + } + leaderboard.clientID = clientID + const layers: Layer[] = [ new TerrainLayer(game), new TerritoryLayer(game, eventBus), @@ -26,6 +34,7 @@ export function createRenderer(canvas: HTMLCanvasElement, game: Game, eventBus: new UILayer(eventBus, game, clientID, transformHandler), new EventsDisplay(eventBus, game, clientID), new RadialMenu(eventBus, game, transformHandler, clientID, emojiTable as EmojiTable), + leaderboard, ] return new GameRenderer(game, eventBus, canvas, transformHandler, layers) @@ -41,7 +50,7 @@ export class GameRenderer { } initialize() { - this.layers.forEach(l => l.init()) + this.layers.forEach(l => l.init(this.game)) document.body.appendChild(this.canvas); window.addEventListener('resize', () => this.resizeCanvas()); diff --git a/src/client/graphics/layers/Layer.ts b/src/client/graphics/layers/Layer.ts index 2c7d7ac7a..b2d9c8388 100644 --- a/src/client/graphics/layers/Layer.ts +++ b/src/client/graphics/layers/Layer.ts @@ -1,7 +1,8 @@ +import {Game} from "../../../core/game/Game" import {TransformHandler} from "../TransformHandler" export interface Layer { - init() + init(game: Game) tick() renderLayer(context: CanvasRenderingContext2D) shouldTransform(): boolean diff --git a/src/client/graphics/layers/Leaderboard.ts b/src/client/graphics/layers/Leaderboard.ts new file mode 100644 index 000000000..7929c5605 --- /dev/null +++ b/src/client/graphics/layers/Leaderboard.ts @@ -0,0 +1,179 @@ +import {LitElement, html, css} from 'lit'; +import {customElement, property, state} from 'lit/decorators.js'; +import {Layer} from './Layer'; +import {Game, Player} from '../../../core/game/Game'; +import {ClientID} from '../../../core/Schemas'; + +interface Entry { + name: string + position: number + score: number + isMyPlayer: boolean +} + +@customElement('leader-board') +export class Leaderboard extends LitElement implements Layer { + + private game: Game + public clientID: ClientID + + init(game: Game) { + this.game = game + } + + tick() { + if (this._hidden && !this.game.inSpawnPhase()) { + this.showLeaderboard() + this.updateLeaderboard() + } + if (this._hidden) { + return + } + + if (this.game.ticks() % 10 == 0) { + this.updateLeaderboard() + } + } + + private updateLeaderboard() { + if (this.clientID == null) { + return + } + const myPlayer = this.game.players().find(p => p.clientID() == this.clientID) + if (myPlayer == null) { + return + } + + const sorted = this.game.players() + .sort((a, b) => b.numTilesOwned() - a.numTilesOwned()) + + this.players = sorted + .slice(0, 5) + .map((player, index) => ({ + name: player.name(), + position: index + 1, + score: player.numTilesOwned(), + isMyPlayer: player == myPlayer + })); + + if (this.players.find(p => p.isMyPlayer) == null) { + let place = 0 + for (const p of sorted) { + place++ + if (p == myPlayer) { + break + } + } + + this.players.pop() + this.players.push({ + name: myPlayer.name(), + position: place, + score: myPlayer.numTilesOwned(), + isMyPlayer: true, + }) + } + + + this.requestUpdate() + } + + renderLayer(context: CanvasRenderingContext2D) { + } + shouldTransform(): boolean { + return false + } + + static styles = css` + :host { + display: block; + } + .leaderboard { + position: fixed; + top: 20px; + left: 20px; + z-index: 9999; + background-color: #1E1E1E; + padding: 15px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + border-radius: 10px; + max-width: 300px; + max-height: 80vh; + overflow-y: auto; + width: 300px; + } + table { + width: 100%; + border-collapse: collapse; + } + th, td { + padding: 8px; + text-align: left; + border-bottom: 1px solid #333; + color: white; + } + th { + background-color: #2C2C2C; + color: white; + } + .myPlayer { + font-weight: bold; + font-size: 1.2em; + } + tr:nth-child(even) { + background-color: #2C2C2C; + } + tr:hover { + background-color: #3A3A3A; + } + .hidden { + display: none !important; + } + `; + + @property({type: Array}) + players: Entry[] = []; + + @state() + private _hidden = true; + + render() { + return html` +
+ + + + + + + + + + ${this.players + .map((player, index) => html` + + + + + + `)} + +
RankPlayerScore
${player.position}${player.name.slice(0, 12)}${player.score}
+
+ `; + } + + hideLeaderboard() { + this._hidden = true; + this.requestUpdate(); + } + + showLeaderboard() { + this._hidden = false; + this.requestUpdate(); + } + + get isVisible() { + return !this._hidden; + } +} \ No newline at end of file diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts index b71f1874c..711310ede 100644 --- a/src/client/graphics/layers/NameLayer.ts +++ b/src/client/graphics/layers/NameLayer.ts @@ -60,7 +60,7 @@ export class NameLayer implements Layer { return true } - public init() { + public init(game: Game) { } diff --git a/src/client/graphics/layers/TerrainLayer.ts b/src/client/graphics/layers/TerrainLayer.ts index 69cffec3d..544ab4e0b 100644 --- a/src/client/graphics/layers/TerrainLayer.ts +++ b/src/client/graphics/layers/TerrainLayer.ts @@ -17,7 +17,7 @@ export class TerrainLayer implements Layer { tick() { } - init() { + init(game: Game) { this.canvas = document.createElement('canvas'); this.context = this.canvas.getContext("2d") diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts index 4b9dd496d..85d76d87a 100644 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ b/src/client/graphics/layers/TerritoryLayer.ts @@ -31,7 +31,7 @@ export class TerritoryLayer implements Layer { tick() { } - init() { + init(game: Game) { this.canvas = document.createElement('canvas'); this.context = this.canvas.getContext("2d") diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts index 7543c6168..d6efb4fe7 100644 --- a/src/client/graphics/layers/UILayer.ts +++ b/src/client/graphics/layers/UILayer.ts @@ -55,7 +55,7 @@ export class UILayer implements Layer { tick() { } - init() { + init(game: Game) { this.createExitButton() this.createWinModal() this.initRightClickMenu() diff --git a/src/client/index.html b/src/client/index.html index 1b79f0074..0a3d9335e 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -33,8 +33,8 @@
- + class="w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + maxlength="18">
@@ -80,13 +80,8 @@
+ -