Browse Source

rematch if draw

drawing-pad
Stephanie Gredell 1 month ago
parent
commit
298318a0fd
  1. 29
      backend/src/services/game.service.ts
  2. 34
      backend/src/services/websocket.service.ts
  3. 26
      frontend/src/pages/TicTacToeApp.tsx

29
backend/src/services/game.service.ts

@ -53,7 +53,7 @@ function checkDraw(board: (string | null)[]): boolean {
return board.every(cell => cell !== null) && !checkWinner(board); return board.every(cell => cell !== null) && !checkWinner(board);
} }
function makeMove(roomId: string, playerId: string, position: number): { success: boolean; error?: string; autoStartNext?: boolean } { function makeMove(roomId: string, playerId: string, position: number): { success: boolean; error?: string; autoStartNext?: boolean; autoRematch?: boolean } {
const game = games.get(roomId); const game = games.get(roomId);
if (!game) { if (!game) {
return { success: false, error: 'Game not found' }; return { success: false, error: 'Game not found' };
@ -88,11 +88,13 @@ function makeMove(roomId: string, playerId: string, position: number): { success
// After broadcast, if there's a queue, we'll auto-start the next game // After broadcast, if there's a queue, we'll auto-start the next game
} else if (isDraw) { } else if (isDraw) {
game.isDraw = true; game.isDraw = true;
// Auto-rematch on draw - same two players play again
// Note: The websocket handler will reset the board and keep same players
} else { } else {
game.currentPlayer = game.currentPlayer === 'X' ? 'O' : 'X'; game.currentPlayer = game.currentPlayer === 'X' ? 'O' : 'X';
} }
return { success: true, autoStartNext: winner !== null && game.queue.length > 0 }; return { success: true, autoStartNext: winner !== null && game.queue.length > 0, autoRematch: isDraw };
} }
function addPlayer(roomId: string, playerId: string, ws: WS): { success: boolean; error?: string; symbol?: 'X' | 'O' | null } { function addPlayer(roomId: string, playerId: string, ws: WS): { success: boolean; error?: string; symbol?: 'X' | 'O' | null } {
@ -196,25 +198,10 @@ function resetGame(roomId: string, resettingPlayerId?: string): void {
nextPlayer.symbol = previousWinner === 'X' ? 'O' : 'X'; nextPlayer.symbol = previousWinner === 'X' ? 'O' : 'X';
} }
} }
} else if (wasDraw && hasQueue && activePlayers.length === 2) { } else if (wasDraw && activePlayers.length === 2) {
// If it was a draw and there's a queue, both players go to queue // If it was a draw, rematch - same two players play again
activePlayers.forEach(p => { // Players keep their symbols, board is already reset above
p.symbol = null; // No changes needed - just reset the board (already done above)
game.queue.push(p.id);
});
// Promote next 2 from queue
const newPlayer1 = game.queue.shift();
const newPlayer2 = game.queue.shift();
if (newPlayer1) {
const p1 = game.players.find(p => p.id === newPlayer1);
if (p1) p1.symbol = 'X';
}
if (newPlayer2) {
const p2 = game.players.find(p => p.id === newPlayer2);
if (p2) p2.symbol = 'O';
}
} else if (activePlayers.length > 2) { } else if (activePlayers.length > 2) {
// If no winner or no queue, rotate all players // If no winner or no queue, rotate all players
activePlayers.forEach(p => { activePlayers.forEach(p => {

34
backend/src/services/websocket.service.ts

@ -101,8 +101,40 @@ export function createWebSocketServer(server: any) {
} }
}); });
// Auto-rematch on draw - same two players play again
if (moveResult.autoRematch && updatedGame.isDraw) {
// Reset board, keep same players and symbols
updatedGame.board = Array(9).fill(null);
updatedGame.currentPlayer = 'X';
updatedGame.winner = null;
updatedGame.isDraw = false;
// Players keep their symbols, so no change needed
// Broadcast new game state immediately
setTimeout(() => {
const rematchGame = getGame(roomId);
if (rematchGame) {
rematchGame.players.forEach(player => {
if (player.ws.readyState === 1) {
player.ws.send(JSON.stringify({
type: 'gameState',
game: {
board: rematchGame.board,
currentPlayer: rematchGame.currentPlayer,
winner: rematchGame.winner,
isDraw: rematchGame.isDraw,
yourSymbol: player.symbol,
players: rematchGame.players.map(p => ({ id: p.id, symbol: p.symbol })),
queue: rematchGame.queue,
},
}));
}
});
}
}, 1500); // Small delay to show draw message before rematch
}
// Auto-start next game if there's a winner and queue // Auto-start next game if there's a winner and queue
if (moveResult.autoStartNext && updatedGame.winner && updatedGame.queue.length > 0) { else if (moveResult.autoStartNext && updatedGame.winner && updatedGame.queue.length > 0) {
const winner = updatedGame.winner; const winner = updatedGame.winner;
const loser = updatedGame.players.find(p => p.symbol !== winner && p.symbol !== null); const loser = updatedGame.players.find(p => p.symbol !== winner && p.symbol !== null);

26
frontend/src/pages/TicTacToeApp.tsx

@ -255,34 +255,14 @@ export function TicTacToeApp() {
} }
} }
// Draw: if there's a queue, show "Get in line", otherwise "Play Again" // Draw: rematch happens automatically, show message
if (gameState.isDraw) { if (gameState.isDraw) {
if (hasQueue && !isInQueue) {
return ( return (
<button <p className="text-sm text-primary font-semibold">
onClick={handleJoinQueue} It's a draw! Rematch starting...
className="px-6 py-3 bg-secondary text-primary-foreground rounded-full font-semibold hover:bg-secondary/90 transition-all active:scale-95 shadow-md"
>
Get in line to play again
</button>
);
} else if (!hasQueue) {
return (
<button
onClick={handleReset}
className="px-6 py-3 bg-primary text-primary-foreground rounded-full font-semibold hover:bg-primary/90 transition-all active:scale-95 shadow-md"
>
Play Again
</button>
);
} else {
return (
<p className="text-sm text-muted-foreground">
You're in line to play again (position {gameState.queue.indexOf(playerIdRef.current) + 1})
</p> </p>
); );
} }
}
return null; return null;
})()} })()}

Loading…
Cancel
Save