Signed-off-by: ale <ale@manalejandro.com>
Este commit está contenido en:
ale
2025-12-11 02:56:49 +01:00
padre dcd0aecd54
commit f5df98c544
Se han modificado 8 ficheros con 148 adiciones y 12 borrados

Ver fichero

@@ -26,6 +26,7 @@ function createGameState(roomId: string): GameState {
isGameOver: false,
turnsPassed: 0,
gameMode: 'waiting',
rematchRequests: [],
};
}

Ver fichero

@@ -27,6 +27,7 @@ export default function Home() {
leaveRoom,
startAIGame,
setError,
requestRematch,
} = useGameStore();
const [showRules, setShowRules] = useState(false);
@@ -362,8 +363,12 @@ export default function Home() {
<GameOver
winner={gameState.players.find(p => p.id === gameState.winner) || null}
players={gameState.players}
currentPlayerId={currentPlayerId}
rematchRequests={gameState.rematchRequests || []}
onPlayAgain={handlePlayAgain}
onRequestRematch={requestRematch}
onLeave={leaveRoom}
isAIGame={roomId?.startsWith('AI-') || false}
/>
)}
</div>

Ver fichero

@@ -7,11 +7,19 @@ import { Player } from '@/lib/types';
interface GameOverProps {
winner: Player | null;
players: Player[];
currentPlayerId: string | null;
rematchRequests: string[];
onPlayAgain: () => void;
onRequestRematch: () => void;
onLeave: () => void;
isAIGame: boolean;
}
export function GameOver({ winner, players, onPlayAgain, onLeave }: GameOverProps) {
export function GameOver({ winner, players, currentPlayerId, rematchRequests, onPlayAgain, onRequestRematch, onLeave, isAIGame }: GameOverProps) {
const hasRequestedRematch = currentPlayerId ? rematchRequests.includes(currentPlayerId) : false;
const otherPlayersRequested = rematchRequests.filter(id => id !== currentPlayerId);
const waitingForOthers = hasRequestedRematch && otherPlayersRequested.length < players.length - 1;
return (
<motion.div
initial={{ opacity: 0 }}
@@ -57,6 +65,7 @@ export function GameOver({ winner, players, onPlayAgain, onLeave }: GameOverProp
.map((player, index) => {
const score = player.tiles.reduce((sum, t) => sum + t.left + t.right, 0);
const isWinner = player.id === winner?.id;
const hasRequestedRematchPlayer = rematchRequests.includes(player.id);
return (
<motion.div
@@ -71,7 +80,14 @@ export function GameOver({ winner, players, onPlayAgain, onLeave }: GameOverProp
<div className="flex items-center gap-3">
{isWinner && <span className="text-2xl">👑</span>}
<div>
<div className="font-semibold text-gray-800">{player.name}</div>
<div className="font-semibold text-gray-800 flex items-center gap-2">
{player.name}
{hasRequestedRematchPlayer && !isAIGame && (
<span className="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full">
Wants rematch
</span>
)}
</div>
<div className="text-xs text-gray-500">
{player.tiles.length} tiles remaining
</div>
@@ -89,15 +105,35 @@ export function GameOver({ winner, players, onPlayAgain, onLeave }: GameOverProp
</div>
<div className="space-y-3">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={onPlayAgain}
className="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-3 rounded-lg font-semibold shadow-lg hover:shadow-xl transition-shadow"
aria-label="Start a new game"
>
Play Again
</motion.button>
{isAIGame ? (
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={onPlayAgain}
className="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-3 rounded-lg font-semibold shadow-lg hover:shadow-xl transition-shadow"
aria-label="Play again against AI"
>
Play Again
</motion.button>
) : (
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={onRequestRematch}
disabled={hasRequestedRematch}
className={`w-full py-3 rounded-lg font-semibold shadow-lg transition-shadow ${
hasRequestedRematch
? 'bg-gray-400 text-white cursor-not-allowed'
: 'bg-gradient-to-r from-green-500 to-green-600 text-white hover:shadow-xl'
}`}
aria-label={hasRequestedRematch ? 'Waiting for others to accept rematch' : 'Request rematch'}
>
{hasRequestedRematch
? (waitingForOthers ? 'Waiting for others...' : 'Starting rematch...')
: 'Request Rematch'
}
</motion.button>
)}
<motion.button
whileHover={{ scale: 1.02 }}

Ver fichero

@@ -33,6 +33,7 @@ function createGameState(roomId: string): GameState {
isGameOver: false,
turnsPassed: 0,
gameMode: 'waiting',
rematchRequests: [],
};
}

Ver fichero

@@ -26,6 +26,7 @@ interface GameStore {
selectTile: (tile: DominoTile | null) => void;
leaveRoom: () => void;
setError: (error: string | null) => void;
requestRematch: () => void;
// AI actions
startAIGame: (playerName: string) => void;
@@ -134,6 +135,18 @@ export const useGameStore = create<GameStore>((set, get) => ({
setTimeout(() => set({ error: null }), 3000);
});
socket.on('rematch-started', (gameState: GameState) => {
set({ gameState, selectedTile: null });
// Check if AI starts
const currentPlayer = gameState.players[gameState.currentPlayerIndex];
if (currentPlayer?.isAI) {
setTimeout(() => {
get().executeAITurn();
}, 1500);
}
});
set({ socket });
},
@@ -246,6 +259,25 @@ export const useGameStore = create<GameStore>((set, get) => ({
});
},
requestRematch: () => {
const { socket, roomId, gameState, currentPlayerId } = get();
// AI mode - start new game immediately
if (roomId?.startsWith('AI-') && gameState && currentPlayerId) {
const humanPlayer = gameState.players.find(p => !p.isAI);
if (humanPlayer) {
// Just restart with same settings
get().startAIGame(humanPlayer.name);
}
return;
}
// Online mode - send rematch request to server
if (socket && roomId) {
socket.emit('request-rematch', roomId);
}
},
setError: (error: string | null) => {
set({ error });
if (error) {
@@ -297,6 +329,7 @@ export const useGameStore = create<GameStore>((set, get) => ({
isGameOver: false,
turnsPassed: 0,
gameMode: 'playing',
rematchRequests: [],
};
set({ gameState, currentPlayerId: 'human', roomId });

Ver fichero

@@ -47,6 +47,7 @@ export type GameState = {
isGameOver: boolean;
turnsPassed: number;
gameMode: 'waiting' | 'playing' | 'finished';
rematchRequests: string[]; // Player IDs who requested rematch
};
export type GameMove = {
@@ -64,6 +65,7 @@ export type SocketEvents = {
'make-move': (roomId: string, move: GameMove) => void;
'draw-tile': (roomId: string) => void;
'leave-room': (roomId: string) => void;
'request-rematch': (roomId: string) => void;
// Server to Client
'room-created': (roomId: string) => void;
@@ -74,4 +76,6 @@ export type SocketEvents = {
'player-joined': (player: Player) => void;
'player-left': (playerId: string) => void;
'error': (message: string) => void;
'rematch-requested': (playerId: string) => void;
'rematch-started': (gameState: GameState) => void;
};

Ver fichero

@@ -32,6 +32,7 @@ function createGameState(roomId) {
isGameOver: false,
turnsPassed: 0,
gameMode: 'waiting',
rematchRequests: [],
};
}
@@ -115,12 +116,41 @@ function startGame(roomId) {
currentPlayerIndex: startingPlayerIndex,
boneyard,
gameMode: 'playing',
rematchRequests: [],
};
gameRooms.set(roomId, newGameState);
return newGameState;
}
// Start rematch with same players
function startRematch(roomId, oldGameState) {
const { playerTiles, boneyard } = dealTiles(oldGameState.players.length);
const updatedPlayers = oldGameState.players.map((player, index) => ({
...player,
tiles: playerTiles[index],
score: 0,
isReady: true,
}));
const startingPlayerIndex = findStartingPlayer(updatedPlayers);
return {
id: roomId,
players: updatedPlayers,
currentPlayerIndex: startingPlayerIndex,
board: [],
boneyard,
boardEnds: [],
winner: null,
isGameOver: false,
turnsPassed: 0,
gameMode: 'playing',
rematchRequests: [],
};
}
app.prepare().then(() => {
const server = createServer(async (req, res) => {
try {
@@ -437,6 +467,32 @@ app.prepare().then(() => {
}
});
socket.on('request-rematch', (roomId) => {
const gameState = gameRooms.get(roomId);
if (!gameState || !gameState.isGameOver) return;
// Add player to rematch requests if not already there
if (!gameState.rematchRequests.includes(socket.id)) {
gameState.rematchRequests.push(socket.id);
}
// Check if all players have requested rematch
const allPlayersRequested = gameState.players.every(p =>
gameState.rematchRequests.includes(p.id)
);
if (allPlayersRequested) {
// Start a new game with the same players
const newGameState = startRematch(roomId, gameState);
gameRooms.set(roomId, newGameState);
io.to(roomId).emit('rematch-started', newGameState);
} else {
// Update state to show who has requested
gameRooms.set(roomId, gameState);
io.to(roomId).emit('game-state-updated', gameState);
}
});
socket.on('disconnect', () => {
const roomId = playerRooms.get(socket.id);
if (roomId) {

Ver fichero

@@ -15,7 +15,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{