import React, { useState, useCallback } from 'react';
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle,
AlertDialogDescription, AlertDialogFooter, AlertDialogAction } from
'@/components/ui/alert-dialog';
import { Flag, Mouse, ArrowLeft } from 'lucide-react';
const DIFFICULTY_LEVELS = {
beginner: {
rows: 9,
cols: 9,
mines: 10,
name: 'Principiante',
cellSize: 'w-7 h-7'
},
intermediate: {
rows: 16,
cols: 16,
mines: 40,
name: 'Intermedio',
cellSize: 'w-5 h-5'
},
expert: {
rows: 16,
cols: 30,
mines: 99,
name: 'Experto',
cellSize: 'w-4 h-4'
}
};
const WelcomeScreen = ({ onStartGame }) => {
return (
<div className="flex flex-col items-center gap-8 p-8">
<h1 className="text-4xl font-bold text-blue-600">Buscaminas</h1>
<div className="text-xl text-gray-600 mb-4">
Selecciona un nivel de dificultad
</div>
<div className="flex flex-col gap-4 w-64">
{Object.entries(DIFFICULTY_LEVELS).map(([level, config]) => (
<button
key={level}
className="px-6 py-4 bg-blue-500 text-white rounded-lg hover:bg-blue-
600 transition-colors flex flex-col items-center"
onClick={() => onStartGame(level)}
>
<span className="text-lg font-bold">{config.name}</span>
<span className="text-sm">
{config.rows}x{config.cols}, {config.mines} minas
</span>
</button>
))}
</div>
</div>
);
};
const Cell = ({ value, revealed, flagged, onClick, cellSize }) => {
const getCellContent = () => {
if (!revealed && !flagged) return '';
if (flagged) return '🚩';
if (value === -1) return '💣';
return value === 0 ? '' : value;
};
const getBackgroundColor = () => {
if (!revealed) return 'bg-gray-300';
if (value === -1) return 'bg-red-500';
return 'bg-gray-100';
};
const getFontSize = () => {
if (cellSize.includes('w-4')) return 'text-xs';
if (cellSize.includes('w-5')) return 'text-sm';
return 'text-base';
};
return (
<button
className={`${cellSize} border border-gray-400 flex items-center justify-
center font-bold ${getBackgroundColor()} hover:bg-gray-200 ${getFontSize()}`}
onClick={onClick}
>
{getCellContent()}
</button>
);
};
const Minesweeper = () => {
const [difficulty, setDifficulty] = useState(null);
const [grid, setGrid] = useState([]);
const [revealed, setRevealed] = useState([]);
const [flagged, setFlagged] = useState([]);
const [gameOver, setGameOver] = useState(false);
const [gameWon, setGameWon] = useState(false);
const [firstClick, setFirstClick] = useState(true);
const [flagMode, setFlagMode] = useState(false);
const initializeGame = useCallback((selectedDifficulty) => {
const config = DIFFICULTY_LEVELS[selectedDifficulty];
const newGrid = Array(config.rows).fill().map(() =>
Array(config.cols).fill(0));
const newRevealed = Array(config.rows).fill().map(() =>
Array(config.cols).fill(false));
const newFlagged = Array(config.rows).fill().map(() =>
Array(config.cols).fill(false));
setDifficulty(selectedDifficulty);
setGrid(newGrid);
setRevealed(newRevealed);
setFlagged(newFlagged);
setGameOver(false);
setGameWon(false);
setFirstClick(true);
setFlagMode(false);
}, []);
const returnToMenu = () => {
setDifficulty(null);
};
const placeMines = (firstRow, firstCol) => {
const config = DIFFICULTY_LEVELS[difficulty];
const newGrid = Array(config.rows).fill().map(() =>
Array(config.cols).fill(0));
let minesPlaced = 0;
const safeZone = [];
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
if (firstRow + i >= 0 && firstRow + i < config.rows && firstCol + j >= 0 &&
firstCol + j < config.cols) {
safeZone.push([firstRow + i, firstCol + j]);
}
}
}
while (minesPlaced < config.mines) {
const row = Math.floor(Math.random() * config.rows);
const col = Math.floor(Math.random() * config.cols);
const isSafe = safeZone.some(([r, c]) => r === row && c === col);
if (!isSafe && newGrid[row][col] !== -1) {
newGrid[row][col] = -1;
minesPlaced++;
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
if (row + i >= 0 && row + i < config.rows && col + j >= 0 && col + j <
config.cols) {
if (newGrid[row + i][col + j] !== -1) {
newGrid[row + i][col + j]++;
}
}
}
}
}
}
setGrid(newGrid);
const newRevealed = [...revealed];
safeZone.forEach(([row, col]) => {
revealCellAndNeighbors(newGrid, newRevealed, row, col);
});
setRevealed(newRevealed);
};
const revealCellAndNeighbors = (grid, revealed, row, col) => {
const config = DIFFICULTY_LEVELS[difficulty];
if (row < 0 || row >= config.rows || col < 0 || col >= config.cols ||
revealed[row][col]) {
return;
}
revealed[row][col] = true;
if (grid[row][col] === 0) {
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
revealCellAndNeighbors(grid, revealed, row + i, col + j);
}
}
}
};
const handleCellClick = (row, col) => {
if (gameOver || gameWon) return;
if (flagMode) {
if (!revealed[row][col]) {
const newFlagged = [...flagged];
newFlagged[row][col] = !newFlagged[row][col];
setFlagged(newFlagged);
}
} else {
if (!flagged[row][col]) {
if (firstClick) {
setFirstClick(false);
placeMines(row, col);
} else {
revealCell(row, col);
}
}
}
};
const revealCell = (row, col) => {
if (revealed[row][col] || flagged[row][col] || gameOver || gameWon) return;
const newRevealed = [...revealed];
newRevealed[row][col] = true;
setRevealed(newRevealed);
if (grid[row][col] === -1) {
setGameOver(true);
return;
}
const config = DIFFICULTY_LEVELS[difficulty];
if (grid[row][col] === 0) {
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
if (row + i >= 0 && row + i < config.rows && col + j >= 0 && col + j <
config.cols) {
if (!revealed[row + i][col + j] && !flagged[row + i][col + j]) {
revealCell(row + i, col + j);
}
}
}
}
}
const totalRevealed = newRevealed.flat().filter(cell => cell).length;
if (totalRevealed === (config.rows * config.cols) - config.mines) {
setGameWon(true);
}
};
React.useEffect(() => {
if (difficulty) {
initializeGame(difficulty);
}
}, [difficulty, initializeGame]);
if (!difficulty) {
return <WelcomeScreen onStartGame={initializeGame} />;
}
const config = DIFFICULTY_LEVELS[difficulty];
return (
<div className="flex flex-col items-center gap-4">
<div className="flex items-center w-full justify-between px-4">
<button
className="flex items-center gap-2 text-blue-500 hover:text-blue-600"
onClick={returnToMenu}
>
<ArrowLeft size={20} />
Volver al menú
</button>
<div className="text-2xl font-bold">
{config.name}
</div>
</div>
<div className="flex gap-4 mb-4">
<button
className={`px-4 py-2 rounded flex items-center gap-2 ${
!flagMode ? 'bg-blue-500 text-white' : 'bg-gray-200'
}`}
onClick={() => setFlagMode(false)}
>
<Mouse size={20} /> Revelar
</button>
<button
className={`px-4 py-2 rounded flex items-center gap-2 ${
flagMode ? 'bg-blue-500 text-white' : 'bg-gray-200'
}`}
onClick={() => setFlagMode(true)}
>
<Flag size={20} /> Colocar Bandera
</button>
</div>
<div className="bg-gray-100 p-4 rounded-lg shadow-lg overflow-auto max-h-
[70vh]">
<div className="flex flex-col">
{grid.map((row, rowIndex) => (
<div key={rowIndex} className="flex">
{row.map((cell, colIndex) => (
<Cell
key={`${rowIndex}-${colIndex}`}
value={cell}
revealed={revealed[rowIndex][colIndex]}
flagged={flagged[rowIndex][colIndex]}
onClick={() => handleCellClick(rowIndex, colIndex)}
cellSize={config.cellSize}
/>
))}
</div>
))}
</div>
</div>
<button
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
onClick={() => initializeGame(difficulty)}
>
Nuevo Juego
</button>
<AlertDialog open={gameOver || gameWon}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{gameOver ? '¡Game Over!' : '¡Felicidades!'}
</AlertDialogTitle>
<AlertDialogDescription>
{gameOver
? '¿Quieres intentarlo de nuevo?'
: '¡Has ganado! ¿Quieres jugar otra partida?'}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogAction onClick={() => initializeGame(difficulty)}>
Nuevo Juego
</AlertDialogAction>
<AlertDialogAction onClick={returnToMenu}>
Volver al Menú
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
);
};
export default Minesweeper;