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` +
| Rank | +Player | +Score | +
|---|---|---|
| ${player.position} | +${player.name.slice(0, 12)} | +${player.score} | +