decla-red/frontend/src/scripts/scoreboard.ts
2026-06-12 21:46:47 +01:00

71 lines
2.6 KiB
TypeScript

import { CharacterTeam, UpdateGameState, clamp, settings } from 'shared';
// Centred tug-of-war territory bar. The two fills meet at the bar's centre and
// grow outward by each team's share of the score limit, so the meeting point
// reads as the lead at a glance. Numeric scores are overlaid (the leader's is
// emphasised) and a "YOU" tick marks the local team's half. Owns its own DOM so
// the Game just appends `element` to the overlay and feeds it `update()`.
export class Scoreboard {
public readonly element = document.createElement('div');
private readonly blueFill = document.createElement('div');
private readonly redFill = document.createElement('div');
private readonly blueScore = document.createElement('span');
private readonly redScore = document.createElement('span');
private readonly youMarker = document.createElement('div');
constructor() {
this.element.className = 'planet-progress';
this.blueFill.className = 'fill blue';
this.redFill.className = 'fill red';
this.blueScore.className = 'score blue';
this.redScore.className = 'score red';
this.youMarker.className = 'you-marker';
this.youMarker.innerText = 'YOU';
this.element.append(
this.blueFill,
this.redFill,
this.blueScore,
this.redScore,
this.youMarker,
);
}
public update(
{ blueCount, redCount, limit }: UpdateGameState,
localTeam: CharacterTeam | undefined,
) {
const { scoreboardHalfWidthPercent, scoreboardMinFillPercent } = settings;
const fraction = (count: number) =>
Math.max(
count > 0 ? scoreboardMinFillPercent : 0,
clamp(count / limit, 0, 1) * scoreboardHalfWidthPercent,
);
this.blueFill.style.width = fraction(blueCount) + '%';
this.redFill.style.width = fraction(redCount) + '%';
const isMatchPoint = (count: number) =>
count / limit >= settings.matchPointScoreRatio;
this.blueFill.classList.toggle('match-point', isMatchPoint(blueCount));
this.redFill.classList.toggle('match-point', isMatchPoint(redCount));
this.blueScore.innerText = String(Math.round(blueCount));
this.redScore.innerText = String(Math.round(redCount));
this.blueScore.classList.toggle('leading', blueCount > redCount);
this.redScore.classList.toggle('leading', redCount > blueCount);
if (localTeam === CharacterTeam.blue || localTeam === CharacterTeam.red) {
this.youMarker.style.display = '';
this.youMarker.classList.toggle('blue', localTeam === CharacterTeam.blue);
this.youMarker.classList.toggle('red', localTeam === CharacterTeam.red);
} else {
this.youMarker.style.display = 'none';
}
}
}