import { LitElement, html } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { repeat } from "lit/directives/repeat.js"; import { translateText } from "../../../client/Utils"; import { EventBus, GameEvent } from "../../../core/EventBus"; import { GameView, PlayerView, UnitView } from "../../../core/game/GameView"; import { renderNumber } from "../../Utils"; import { Layer } from "./Layer"; interface Entry { name: string; position: number; score: string; gold: string; troops: string; isMyPlayer: boolean; player: PlayerView; } export class GoToPlayerEvent implements GameEvent { constructor(public player: PlayerView) {} } export class GoToPositionEvent implements GameEvent { constructor( public x: number, public y: number, ) {} } export class GoToUnitEvent implements GameEvent { constructor(public unit: UnitView) {} } @customElement("leader-board") export class Leaderboard extends LitElement implements Layer { public game: GameView | null = null; public eventBus: EventBus | null = null; players: Entry[] = []; @property({ type: Boolean }) visible = false; private showTopFive = true; @state() private _sortKey: "tiles" | "gold" | "troops" = "tiles"; @state() private _sortOrder: "asc" | "desc" = "desc"; createRenderRoot() { return this; // use light DOM for Tailwind support } init() {} tick() { if (this.game === null) throw new Error("Not initialized"); if (!this.visible) return; if (this.game.ticks() % 10 === 0) { this.updateLeaderboard(); } } private setSort(key: "tiles" | "gold" | "troops") { if (this._sortKey === key) { this._sortOrder = this._sortOrder === "asc" ? "desc" : "asc"; } else { this._sortKey = key; this._sortOrder = "desc"; } this.updateLeaderboard(); } private updateLeaderboard() { if (this.game === null) throw new Error("Not initialized"); const myPlayer = this.game.myPlayer(); let sorted = this.game.playerViews(); const compare = (a: number, b: number) => this._sortOrder === "asc" ? a - b : b - a; switch (this._sortKey) { case "gold": sorted = sorted.sort((a, b) => compare(Number(a.gold()), Number(b.gold())), ); break; case "troops": sorted = sorted.sort((a, b) => compare(a.troops(), b.troops())); break; default: sorted = sorted.sort((a, b) => compare(a.numTilesOwned(), b.numTilesOwned()), ); } const numTilesWithoutFallout = this.game.numLandTiles() - this.game.numTilesWithFallout(); const alivePlayers = sorted.filter((player) => player.isAlive()); const playersToShow = this.showTopFive ? alivePlayers.slice(0, 5) : alivePlayers; this.players = playersToShow.map((player, index) => { const troops = player.troops() / 10; return { name: player.displayName(), position: index + 1, score: formatPercentage( player.numTilesOwned() / numTilesWithoutFallout, ), gold: renderNumber(player.gold()), troops: renderNumber(troops), isMyPlayer: player === myPlayer, player: player, }; }); if ( myPlayer !== null && this.players.find((p) => p.isMyPlayer) === undefined ) { let place = 0; for (const p of sorted) { place++; if (p === myPlayer) { break; } } if (myPlayer.isAlive()) { const myPlayerTroops = myPlayer.troops() / 10; this.players.pop(); this.players.push({ name: myPlayer.displayName(), position: place, score: formatPercentage( myPlayer.numTilesOwned() / this.game.numLandTiles(), ), gold: renderNumber(myPlayer.gold()), troops: renderNumber(myPlayerTroops), isMyPlayer: true, player: myPlayer, }); } } this.requestUpdate(); } private handleRowClickPlayer(player: PlayerView) { if (this.eventBus === null) return; this.eventBus.emit(new GoToPlayerEvent(player)); } renderLayer(context: CanvasRenderingContext2D) {} shouldTransform(): boolean { return false; } render() { if (!this.visible) { return html``; } return html`
e.preventDefault()} >
#
${translateText("leaderboard.player")}
this.setSort("tiles")} > ${translateText("leaderboard.owned")} ${this._sortKey === "tiles" ? this._sortOrder === "asc" ? "⬆️" : "⬇️" : ""}
this.setSort("gold")} > ${translateText("leaderboard.gold")} ${this._sortKey === "gold" ? this._sortOrder === "asc" ? "⬆️" : "⬇️" : ""}
this.setSort("troops")} > ${translateText("leaderboard.troops")} ${this._sortKey === "troops" ? this._sortOrder === "asc" ? "⬆️" : "⬇️" : ""}
${repeat( this.players, (p) => p.player.id(), (player) => html`
this.handleRowClickPlayer(player.player)} >
${player.position}
${player.name}
${player.score}
${player.gold}
${player.troops}
`, )}
`; } } function formatPercentage(value: number): string { const perc = value * 100; if (Number.isNaN(perc)) return "0%"; return perc.toFixed(1) + "%"; }