Improve title box with turn indicator

This commit is contained in:
sepia 2025-07-20 19:50:33 -05:00
parent 7cbeef6482
commit 6bb62b9c87
4 changed files with 78 additions and 28 deletions

View File

@ -97,6 +97,7 @@ body {
} }
#title-box { #title-box {
font-size: 1.25em;
padding-top: 0.25em; padding-top: 0.25em;
padding-bottom: 0.25em; padding-bottom: 0.25em;
} }
@ -184,6 +185,29 @@ body {
background-color: var(--color-on-primary); background-color: var(--color-on-primary);
} }
.player-name {
font-weight: bold;
}
.player-name.player-black {
color: var(--color-primary-light);
}
.player-name.player-white {
color: var(--color-on-primary);
text-shadow:
-1px -1px 0 var(--color-neutral-900),
1px -1px 0 var(--color-neutral-900),
-1px 1px 0 var(--color-neutral-900),
1px 1px 0 var(--color-neutral-900);
}
.player-name.player-to-play {
border: 2px solid var(--color-focus-ring);
border-radius: 10px;
padding: 2px;
}
.last-move { .last-move {
box-shadow: 0 0 5px 3px var(--color-warning-light); box-shadow: 0 0 5px 3px var(--color-warning-light);
} }

View File

@ -7,7 +7,7 @@ type BoardCell = null | 'black' | 'white';
export class GameInstance { export class GameInstance {
public readonly id: string; public readonly id: string;
public readonly board: BoardCell[][]; public readonly board: BoardCell[][];
public currentPlayer: PlayerColor | null; public currentPlayerColor: PlayerColor | null;
public status: GameStatus; public status: GameStatus;
public winner: null | PlayerColor | 'draw'; public winner: null | PlayerColor | 'draw';
public players: { black?: string; white?: string }; public players: { black?: string; white?: string };
@ -20,12 +20,20 @@ export class GameInstance {
this.board = Array.from({ length: this.boardSize }, () => this.board = Array.from({ length: this.boardSize }, () =>
Array(this.boardSize).fill(null), Array(this.boardSize).fill(null),
); );
this.currentPlayer = null; this.currentPlayerColor = null;
this.status = 'waiting'; this.status = 'waiting';
this.winner = null; this.winner = null;
this.players = {}; this.players = {};
} }
public getCurrentPlayerId(): string | undefined {
if (this.currentPlayerColor === 'black') {
return this.players.black;
} else {
return this.players.white;
}
}
public getPlayerCount(): number { public getPlayerCount(): number {
return Object.values(this.players).filter(Boolean).length; return Object.values(this.players).filter(Boolean).length;
} }
@ -52,7 +60,7 @@ export class GameInstance {
// If both players have joined, start the game. // If both players have joined, start the game.
if (this.players.black && this.players.white) { if (this.players.black && this.players.white) {
this.currentPlayer = 'black'; this.currentPlayerColor = 'black';
this.status = 'playing'; this.status = 'playing';
} }
return true; return true;
@ -77,7 +85,7 @@ export class GameInstance {
} }
// Validate it's the player's turn // Validate it's the player's turn
if (this.currentPlayer !== playerColor) { if (this.currentPlayerColor !== playerColor) {
return { success: false, error: 'Not your turn' }; return { success: false, error: 'Not your turn' };
} }
@ -99,7 +107,7 @@ export class GameInstance {
if (this.checkWin(row, col, playerColor)) { if (this.checkWin(row, col, playerColor)) {
this.winner = playerColor; this.winner = playerColor;
this.status = 'finished'; this.status = 'finished';
this.currentPlayer = null; this.currentPlayerColor = null;
return { success: true }; return { success: true };
} }
@ -107,12 +115,12 @@ export class GameInstance {
if (this.moveCount === this.boardSize * this.boardSize) { if (this.moveCount === this.boardSize * this.boardSize) {
this.winner = 'draw'; this.winner = 'draw';
this.status = 'finished'; this.status = 'finished';
this.currentPlayer = null; this.currentPlayerColor = null;
return { success: true }; return { success: true };
} }
// Switch turns // Switch turns
this.currentPlayer = playerColor === 'black' ? 'white' : 'black'; this.currentPlayerColor = playerColor === 'black' ? 'white' : 'black';
return { success: true }; return { success: true };
} }

View File

@ -1,26 +1,20 @@
import { GameInstance } from '../game/game-instance'; import { GameInstance } from '../game/game-instance';
export type GameStateType = Pick<
GameInstance,
'id' | 'board' | 'currentPlayer' | 'status' | 'winner' | 'players'
>;
export function renderGameBoardHtml( export function renderGameBoardHtml(
gameState: GameStateType, game: GameInstance,
playerId: string, playerId: string,
): string { ): string {
let boardHtml = '<div id="game-board" class="game-board-grid">'; let boardHtml = '<div id="game-board" class="game-board-grid">';
const currentPlayerColor = const currentPlayerColor =
Object.entries(gameState.players).find(([_, id]) => id === playerId)?.[0] || Object.entries(game.players).find(([_, id]) => id === playerId)?.[0] ||
null; null;
const isPlayersTurn = const isPlayersTurn =
gameState.status === 'playing' && game.status === 'playing' && game.currentPlayerColor === currentPlayerColor;
gameState.currentPlayer === currentPlayerColor;
for (let row = 0; row < gameState.board.length; row++) { for (let row = 0; row < game.board.length; row++) {
for (let col = 0; col < gameState.board[row].length; col++) { for (let col = 0; col < game.board[row].length; col++) {
const stone = gameState.board[row][col]; const stone = game.board[row][col];
const intersectionId = `intersection-${row}-${col}`; const intersectionId = `intersection-${row}-${col}`;
let stoneHtml = ''; let stoneHtml = '';
if (stone) { if (stone) {

View File

@ -74,7 +74,7 @@ export class WebSocketHandler {
return; return;
} }
if (game.currentPlayer !== playerColor) { if (game.currentPlayerColor !== playerColor) {
this.sendMessage(ws, "Error: It's not your turn"); this.sendMessage(ws, "Error: It's not your turn");
return; return;
} }
@ -142,22 +142,28 @@ export class WebSocketHandler {
ws.send(updatedBoardHtml); ws.send(updatedBoardHtml);
if (game.status === 'finished') { if (game.status === 'finished') {
if (game.winner === 'draw') { if (game.winnerColor === 'draw') {
this.sendMessageToGame(gameId, 'Game ended in draw.'); this.sendMessageToGame(gameId, 'Game ended in draw.');
} else if (game.winner) { } else if (game.winnerColor) {
this.sendMessageToGame( this.sendMessageToGame(
gameId, gameId,
`${game.winner.toUpperCase()} wins!`, `${game.winnerColor.toUpperCase()} wins!`,
); );
} }
} else if (game.status === 'playing') { } else if (game.status === 'playing') {
const clientPlayerColor = Object.entries(game.players).find( const clientPlayerColor = Object.entries(game.players).find(
([_, id]) => id === playerId, ([_, id]) => id === playerId,
)?.[0] as ('black' | 'white') | undefined; )?.[0] as ('black' | 'white') | undefined;
if (game.currentPlayer && clientPlayerColor === game.currentPlayer) { if (
game.currentPlayerColor &&
clientPlayerColor === game.currentPlayerColor
) {
this.sendMessage(ws, "It's your turn!"); this.sendMessage(ws, "It's your turn!");
} else if (game.currentPlayer) { } else if (game.currentPlayerColor) {
this.sendMessage(ws, `Waiting for ${game.currentPlayer}'s move.`); this.sendMessage(
ws,
`Waiting for ${game.currentPlayerColor}'s move.`,
);
} }
} else if (game.status === 'waiting') { } else if (game.status === 'waiting') {
this.sendMessage(ws, 'Waiting for another player...'); this.sendMessage(ws, 'Waiting for another player...');
@ -220,7 +226,7 @@ export class WebSocketHandler {
break; break;
} }
case 'finished': { case 'finished': {
switch (game.winner) { switch (game.winnerColor) {
case 'draw': { case 'draw': {
message = 'Game ended in draw.'; message = 'Game ended in draw.';
break; break;
@ -244,6 +250,7 @@ export class WebSocketHandler {
} }
private playerTag(gameId: string, playerId: string) { private playerTag(gameId: string, playerId: string) {
// Determine whether the player is disconnected
var connectionIcon = `<img src="/icons/disconnected.svg" alt="Disconnected" class="icon" />`; var connectionIcon = `<img src="/icons/disconnected.svg" alt="Disconnected" class="icon" />`;
const connections = this.connections.get(gameId); const connections = this.connections.get(gameId);
if (connections) { if (connections) {
@ -254,7 +261,24 @@ export class WebSocketHandler {
} }
}); });
} }
return `<span>${playerId}${connectionIcon}</span>`;
// Set the correct name color for the player
var colorClass = '';
var turnClass = '';
const game = this.games.get(gameId);
if (game) {
if (game.players.white === playerId) {
colorClass = 'player-white';
} else if (game.players.black === playerId) {
colorClass = 'player-black';
}
if (game.getCurrentPlayerId() === playerId) {
turnClass = 'player-to-play';
}
}
const classes = `player-name ${colorClass} ${turnClass}`.trim();
return `<span class="${classes}">${playerId}${connectionIcon}</span>`;
} }
public getGame(gameId: string): GameInstance | undefined { public getGame(gameId: string): GameInstance | undefined {