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'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { EventBus, GameEvent } from '../../../core/EventBus'; interface Entry { name: string position: number score: string isMyPlayer: boolean player: Player } export class GoToPlayerEvent implements GameEvent { constructor(public player: Player) { } } @customElement('leader-board') export class Leaderboard extends LitElement implements Layer { private game: Game public clientID: ClientID public eventBus: EventBus 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) const sorted = this.game.players() .sort((a, b) => b.numTilesOwned() - a.numTilesOwned()) this.players = sorted .slice(0, 5) .map((player, index) => ({ name: player.displayName(), position: index + 1, score: formatPercentage(player.numTilesOwned() / this.game.numLandTiles()), isMyPlayer: player == myPlayer, player: player })); if (myPlayer != null && 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.displayName(), position: place, score: formatPercentage(myPlayer.numTilesOwned() / this.game.numLandTiles()), isMyPlayer: true, player: myPlayer }) } this.requestUpdate() } private handleRowClick(player: Player) { this.eventBus.emit(new GoToPlayerEvent(player)) } renderLayer(context: CanvasRenderingContext2D) { } shouldTransform(): boolean { return false } static styles = css` :host { display: block; } img.emoji { height: 1em; width: auto; } .leaderboard { position: fixed; top: 10px; left: 10px; z-index: 9999; background-color: rgba(30, 30, 30, 0.7); padding: 10px; 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; backdrop-filter: blur(5px); } table { width: 100%; border-collapse: collapse; } th, td { padding: 5px; text-align: left; border-bottom: 1px solid rgba(51, 51, 51, 0.2); color: white; } th { background-color: rgba(44, 44, 44, 0.5); color: white; } .myPlayer { font-weight: bold; font-size: 1.2em; } .otherPlayer { font-size: 1.0em; } tr:nth-child(even) { background-color: rgba(44, 44, 44, 0.5); } tbody tr { cursor: pointer; transition: background-color 0.2s; } tbody tr:hover { background-color: rgba(78, 78, 78, 0.8); } .hidden { display: none !important; } @media (max-width: 1000px) { .leaderboard { display: none !important; } } `; players: Entry[] = []; @state() private _hidden = true; render() { return html`
| Rank | Player | Owned |
|---|---|---|
| ${player.position} | ${unsafeHTML(player.name)} | ${player.score} |