<!DOCTYPE html>
<html>
<head>
<base target="_top">
<!-- Carga de Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Fuente Inter */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #f4f7f6; /* Un gris muy claro */
}
/* Estilos para el cartón de bingo */
.bingo-card {
display: grid;
grid-template-columns: repeat(5, 1fr);
border: 2px solid #4a5568; /* a darker gray */
border-radius: 8px;
overflow: hidden;
width: 280px; /* Ancho fijo para consistencia */
margin: auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.bingo-card.is-winner {
box-shadow: 0 0 20px 5px #48bb78; /* Resplandor verde */
border-color: #48bb78;
transform: scale(1.03);
}
.bingo-header {
background-color: #2c5282; /* Azul oscuro */
color: white;
font-size: 1.5rem;
font-weight: 700;
text-align: center;
padding: 8px 0;
letter-spacing: 0.5em; /* Espaciado B I N G O */
}
.bingo-cell {
height: 50px;
width: 56px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.25rem;
font-weight: 600;
color: #2d3748; /* gris oscuro */
background-color: #ffffff;
border: 1px solid #e2e8f0; /* gris claro */
transition: all 0.2s ease-in-out;
position: relative;
}
.bingo-cell.is-free {
background-color: #edf2f7; /* gris un poco más oscuro */
color: #4a5568;
font-size: 0.9rem;
}
.bingo-cell.is-marked::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
height: 80%;
background-color: #fbd38d; /* Naranja/amarillo */
border-radius: 50%;
opacity: 0.7;
z-index: 1;
}
.bingo-cell span {
position: relative;
z-index: 2; /* Asegura que el número esté sobre el marcador */
}
/* Estilos para el modal de patrones */
.pattern-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 4px;
border: 2px solid #cbd5e0;
border-radius: 8px;
padding: 8px;
background-color: #f7fafc;
}
.pattern-cell {
width: 40px;
height: 40px;
background-color: #e2e8f0;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.pattern-cell.is-selected {
background-color: #4299e1; /* Azul */
}
/* Pequeña animación para los números cantados */
.called-number-item {
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
</head>
<body class="p-4 md:p-8">
<!-- ! NUEVO: Vista de Configuración -->
<div id="setup-view" class="max-w-3xl mx-auto bg-white p-8 rounded-lg shadow-xl">
<h1 class="text-3xl font-bold text-gray-900 mb-4">Configuración del Juego de Bingo</h1>
<p class="text-gray-700 mb-6">Pega aquí los datos de tus cartones. Cada cartón debe estar en una nueva línea con el formato: <br>
<code class="text-sm bg-gray-100 p-1 rounded">#ID: 1,2,3,4,5, 6,7,8,9,10, 11,12,Free,14,15, 16,17,18,19,20, 21,22,23,24,25</code>
</p>
<label for="card-data-input" class="block text-sm font-medium text-gray-700 mb-2">Datos de los Cartones</label>
<textarea id="card-data-input" rows="10"
class="w-full p-3 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500 font-mono text-sm"
placeholder="#0336409: 14,7,15,3,10, 20,25,28,27,22, 40,42,Free,32,37, 47,51,54,57,59, 72,70,71,63,64
#0336410: 4,6,5,1,15, 19,17,27,25,29, 45,42,Free,31,41, 58,53,59,56,54, 71,62,68,73,63"></textarea>
<button id="start-game-btn"
class="w-full mt-6 px-6 py-3 bg-blue-600 text-white font-semibold text-lg rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Cargar Cartones e Iniciar Juego
</button>
</div>
<!-- ! MODIFICADO: Vista del Juego (oculta por defecto) -->
<div id="game-view" class="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-4 gap-6 hidden">
<!-- Columna de Controles (Izquierda) -->
<div class="lg:col-span-1 bg-white p-5 rounded-lg shadow-lg h-full lg:sticky lg:top-8">
<h2 class="text-2xl font-bold text-gray-800 mb-4">Controles del Juego</h2>
<!-- Ingreso de Números -->
<div class="mb-6">
<label for="number-input" class="block text-sm font-medium text-gray-700 mb-1">Número Cantado</label>
<div class="flex space-x-2">
<input type="number" id="number-input"
class="flex-grow p-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
placeholder="Ej: 14">
<button id="add-number-btn"
class="px-4 py-2 bg-blue-600 text-white font-semibold rounded-md shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">
Agregar
</button>
</div>
</div>
<!-- Números Cantados -->
<div class="mb-6">
<div class="flex justify-between items-center mb-2">
<h3 class="text-lg font-semibold text-gray-800">Números Cantados</h3>
<button id="reset-game-btn" class="text-sm text-red-500 hover:text-red-700 font-medium">Reiniciar</button>
</div>
<div id="called-numbers-list"
class="h-48 bg-gray-50 rounded-md border border-gray-200 p-3 overflow-y-auto flex flex-wrap gap-2 content-start">
<!-- Los números se agregarán aquí -->
</div>
</div>
<!-- Patrones a Jugar (Checkboxes) -->
<div>
<h3 class="text-lg font-semibold text-gray-800 mb-3">Patrones a Jugar</h3>
<div id="pattern-checkboxes" class="space-y-2">
<div>
<input type="checkbox" id="cb-carton-lleno" value="carton-lleno" class="mr-2 pattern-cb h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
<label for="cb-carton-lleno" class="text-gray-700">Cartón Lleno</label>
</div>
<div>
<input type="checkbox" id="cb-letra-x" value="letra-x" class="mr-2 pattern-cb h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
<label for="cb-letra-x" class="text-gray-700">Letra X</label>
</div>
<div>
<input type="checkbox" id="cb-letra-l" value="letra-l" class="mr-2 pattern-cb h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
<label for="cb-letra-l" class="text-gray-700">Letra L</label>
</div>
<div>
<input type="checkbox" id="cb-esquinas" value="esquinas" class="mr-2 pattern-cb h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
<label for="cb-esquinas" class="text-gray-700">4 Esquinas</label>
</div>
<div>
<input type="checkbox" id="cb-custom" value="custom" class="mr-2 pattern-cb h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500">
<label for="cb-custom" class="text-gray-700">Personalizado...</label>
</div>
</div>
<button id="custom-pattern-btn"
class="w-full p-2 bg-gray-200 text-gray-700 font-semibold rounded-md hover:bg-gray-300 mt-3">
Definir Patrón Personalizado
</button>
</div>
</div>
<!-- Área de Cartones (Derecha) -->
<div class="lg:col-span-3">
<h1 class="text-3xl font-bold text-gray-900 mb-5">Mis Cartones de Bingo</h1>
<div id="cards-container" class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
<!-- Los cartones se generarán aquí -->
</div>
</div>
</div>
<!-- Modal para Patrón Personalizado -->
<div id="pattern-modal" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center p-4 hidden z-50">
<div class="bg-white rounded-lg shadow-2xl p-6 w-full max-w-sm">
<h3 class="text-xl font-semibold mb-4">Define tu Patrón</h3>
<p class="text-sm text-gray-600 mb-4">Haz clic en las casillas para crear el patrón ganador.</p>
<div class="pattern-grid mx-auto mb-5" id="modal-pattern-grid">
<!-- 25 celdas se generarán aquí -->
</div>
<div class="flex justify-end space-x-3">
<button id="modal-cancel-btn"
class="px-4 py-2 bg-gray-200 text-gray-700 font-medium rounded-md hover:bg-gray-300">Cancelar</button>
<button id="modal-save-btn"
class="px-4 py-2 bg-blue-600 text-white font-semibold rounded-md shadow-md hover:bg-blue-700">Guardar</button>
</div>
</div>
</div>
<!-- Modal de Ganador -->
<div id="winner-modal" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center p-4 hidden z-50">
<div class="bg-white rounded-lg shadow-2xl p-8 w-full max-w-md text-center">
<div class="text-6xl mb-4">🎉</div>
<h2 class="text-3xl font-bold text-gray-900 mb-2">¡BINGO!</h2>
<p class="text-lg text-gray-700 mb-6">El cartón <strong id="winner-card-id" class="text-blue-600"></strong> ha ganado
con el patrón <strong id="winner-pattern-name" class="text-blue-600"></strong>.</p>
<button id="winner-close-btn"
class="w-full px-6 py-3 bg-blue-600 text-white font-semibold rounded-md shadow-md hover:bg-blue-700 text-lg">
Cerrar
</button>
</div>
</div>
<!-- Modal de Alerta -->
<div id="alert-modal" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center p-4 hidden z-50">
<div class="bg-white rounded-lg shadow-2xl p-8 w-full max-w-md text-center">
<h3 class="text-xl font-semibold mb-4" id="alert-title">Aviso</h3>
<p class="text-gray-700 mb-6" id="alert-message">Este es un mensaje de alerta.</p>
<button id="alert-close-btn"
class="w-full px-6 py-2 bg-blue-600 text-white font-semibold rounded-md shadow-md hover:bg-blue-700">
Entendido
</button>
</div>
</div>
<!-- Modal de Confirmación -->
<div id="confirm-modal" class="fixed inset-0 bg-gray-800 bg-opacity-75 flex items-center justify-center p-4 hidden z-50">
<div class="bg-white rounded-lg shadow-2xl p-8 w-full max-w-md text-center">
<h3 class="text-xl font-semibold mb-4" id="confirm-title">Confirmación</h3>
<p class="text-gray-700 mb-6" id="confirm-message">¿Estás seguro?</p>
<div class="flex justify-center space-x-4">
<button id="confirm-cancel-btn"
class="w-1/2 px-6 py-2 bg-gray-200 text-gray-700 font-medium rounded-md hover:bg-gray-300">
Cancelar
</button>
<button id="confirm-ok-btn"
class="w-1/2 px-6 py-2 bg-red-600 text-white font-semibold rounded-md shadow-md hover:bg-red-700">
Aceptar
</button>
</div>
</div>
</div>
<script>
// --- 1. DATOS INICIALES ---
// ¡Eliminados! Ahora se cargarán desde el textarea.
// --- 2. PATRONES GANADORES ---
const patterns = {
"carton-lleno": {
id: "carton-lleno",
name: "Cartón Lleno",
grid: Array(5).fill(Array(5).fill(true))
},
"letra-x": {
id: "letra-x",
name: "Letra X",
grid: [
[true, false, false, false, true],
[false, true, false, true, false],
[false, false, true, false, false],
[false, true, false, true, false],
[true, false, false, false, true]
]
},
"letra-l": {
id: "letra-l",
name: "Letra L",
grid: [
[true, false, false, false, false],
[true, false, false, false, false],
[true, false, false, false, false],
[true, false, false, false, false],
[true, true, true, true, true]
]
},
"esquinas": {
id: "esquinas",
name: "4 Esquinas",
grid: [
[true, false, false, false, true],
[false, false, false, false, false],
[false, false, false, false, false],
[false, false, false, false, false],
[true, false, false, false, true]
]
},
"custom": {
id: "custom",
name: "Personalizado",
grid: Array(5).fill(Array(5).fill(false)) // Se llenará desde el modal
}
};
// --- 3. ESTADO DE LA APLICACIÓN ---
let calledNumbers = new Set();
let bingoCards = [];
let activePatterns = [];
// --- 4. ELEMENTOS DEL DOM ---
const setupView = document.getElementById('setup-view');
const gameView = document.getElementById('game-view');
const cardDataInput = document.getElementById('card-data-input');
const startGameBtn = document.getElementById('start-game-btn');
const numberInput = document.getElementById('number-input');
const addNumberBtn = document.getElementById('add-number-btn');
const calledNumbersList = document.getElementById('called-numbers-list');
const cardsContainer = document.getElementById('cards-container');
const patternCheckboxes = document.getElementById('pattern-checkboxes');
const customPatternBtn = document.getElementById('custom-pattern-btn');
const resetGameBtn = document.getElementById('reset-game-btn');
const patternModal = document.getElementById('pattern-modal');
const modalPatternGrid = document.getElementById('modal-pattern-grid');
const modalCancelBtn = document.getElementById('modal-cancel-btn');
const modalSaveBtn = document.getElementById('modal-save-btn');
const winnerModal = document.getElementById('winner-modal');
const winnerCardId = document.getElementById('winner-card-id');
const winnerPatternName = document.getElementById('winner-pattern-name');
const winnerCloseBtn = document.getElementById('winner-close-btn');
const alertModal = document.getElementById('alert-modal');
const alertMessage = document.getElementById('alert-message');
const alertCloseBtn = document.getElementById('alert-close-btn');
const confirmModal = document.getElementById('confirm-modal');
const confirmMessage = document.getElementById('confirm-message');
const confirmCancelBtn = document.getElementById('confirm-cancel-btn');
const confirmOkBtn = document.getElementById('confirm-ok-btn');
let confirmCallback = null;
// --- 5. LÓGICA DEL JUEGO ---
/**
* Parsea el texto del textarea y lo convierte en datos de cartones.
*/
function parseCardData(text) {
const lines = text.split('\n').filter(line => line.trim().length > 0);
const newCardsData = [];
for (const line of lines) {
try {
const parts = line.split(':');
if (parts.length !== 2) {
throw new Error(`Formato inválido (falta ':'). Formato esperado: #ID: num,num,...`);
}
const id = parts[0].trim();
if (!id.startsWith('#')) {
throw new Error(`ID debe empezar con '#'.`);
}
const numbers = parts[1].split(',').map(n => n.trim());
if (numbers.length !== 25) {
throw new Error(`No tiene 25 números. Tiene ${numbers.length}.`);
}
const rows = [];
for (let i = 0; i < 5; i++) {
const row = [];
for (let j = 0; j < 5; j++) {
const numStr = numbers[i * 5 + j];
if (numStr.toLowerCase() === 'free') {
row.push('Free');
} else {
const num = parseInt(numStr, 10);
if (isNaN(num)) throw new Error(`Valor no numérico encontrado: '${numStr}'`);
row.push(num);
}
}
rows.push(row);
}
if (rows[2][2] !== 'Free') {
showAlert(`Advertencia: Cartón ${id} no tiene "Free" en el centro. Se marcará como "Free" automáticamente.`);
rows[2][2] = 'Free';
}
newCardsData.push({ id, rows });
} catch (error) {
showAlert(`Línea inválida (ignorada): ${line.substring(0, 30)}... | Error: ${error.message}`);
}
}
return newCardsData;
}
/**
* Inicia el juego desde la pantalla de setup.
*/
function startGame() {
const textData = cardDataInput.value;
const parsedData = parseCardData(textData);
if (parsedData.length === 0) {
showAlert("No se cargaron cartones válidos. Por favor revisa el formato e inténtalo de nuevo.");
return;
}
initGame(parsedData);
setupView.classList.add('hidden');
gameView.classList.remove('hidden');
}
/**
* Inicializa el juego: resetea el estado y dibuja los cartones.
*/
function initGame(cardData) {
calledNumbers.clear();
bingoCards = [];
cardData.forEach(cardData => {
const markedGrid = Array(5).fill(null).map(() => Array(5).fill(false));
markedGrid[2][2] = true; // Marcar "Free"
bingoCards.push({
id: cardData.id,
rows: cardData.rows,
marked: markedGrid,
wonPatterns: new Set() // Para rastrear patrones ganados
});
});
renderAllCards();
renderCalledNumbers();
updateActivePatterns();
winnerModal.classList.add('hidden');
document.querySelectorAll('.bingo-card.is-winner').forEach(c => c.classList.remove('is-winner'));
}
/**
* Dibuja todos los cartones en el contenedor.
*/
function renderAllCards() {
cardsContainer.innerHTML = '';
bingoCards.forEach(card => {
cardsContainer.appendChild(createCardElement(card));
});
}
/**
* Crea el elemento HTML para un solo cartón.
* ! MODIFICADO: Añade la lista de patrones ganados.
*/
function createCardElement(card) {
const cardEl = document.createElement('div');
// Contenedor principal del cartón
cardEl.className = 'bg-white rounded-lg shadow-md overflow-hidden';
cardEl.dataset.cardId = card.id;
// Cabecera B-I-N-G-O
let headerHtml = '<div class="grid grid-cols-5 text-center bg-blue-800 text-white font-bold text-xl py-2">';
['B', 'I', 'N', 'G', 'O'].forEach(letter => headerHtml += `<div>${letter}</div>`);
headerHtml += '</div>';
// Grilla de números
let gridHtml = `<div class="p-2 bingo-card" id="card-${card.id}">`;
for (let r = 0; r < 5; r++) {
for (let c = 0; c < 5; c++) {
const number = card.rows[r][c];
const isFree = (number === 'Free');
const isMarked = card.marked[r][c];
let cellClasses = 'bingo-cell';
if (isFree) cellClasses += ' is-free';
if (isMarked) cellClasses += ' is-marked';
gridHtml += `<div class="${cellClasses}"><span>${number}</span></div>`;
}
}
gridHtml += '</div>';
// ! --- INICIO DE LA MODIFICACIÓN ---
// Sección de Patrones Ganados (solo si hay alguno)
let wonPatternsHtml = '';
if (card.wonPatterns.size > 0) {
wonPatternsHtml = `
<div class="p-2 bg-gray-50 border-t border-gray-200">
<h5 class="text-xs font-semibold text-gray-600 mb-1">Patrones Ganados:</h5>
<div class="flex flex-wrap gap-1">
`;
for (const patternKey of card.wonPatterns) {
const patternName = patterns[patternKey]?.name || patternKey;
// Añade una "insignia" (badge) por cada patrón ganado
wonPatternsHtml += `
<span class="text-xs font-medium bg-green-100 text-green-800 px-2 py-0.5 rounded-full">
${patternName}
</span>
`;
}
wonPatternsHtml += '</div></div>'; // Cierra los divs
}
// ! --- FIN DE LA MODIFICACIÓN ---
// Ensambla el cartón completo
cardEl.innerHTML = `
<h4 class="text-lg font-semibold text-center py-2 bg-gray-100 text-gray-700">${card.id}</h4>
${headerHtml}
${gridHtml}
${wonPatternsHtml} <!-- ! AÑADIDO: Muestra los patrones ganados -->
`;
return cardEl;
}
/**
* Dibuja la lista de números cantados.
*/
function renderCalledNumbers() {
calledNumbersList.innerHTML = '';
const sortedNumbers = Array.from(calledNumbers).sort((a, b) => a - b);
sortedNumbers.forEach(num => {
const numEl = document.createElement('div');
numEl.className = 'called-number-item w-10 h-10 flex items-center justify-center bg-white border-2 border-blue-500 text-blue-700 font-bold rounded-full shadow-sm';
numEl.textContent = num;
calledNumbersList.appendChild(numEl);
});
}
/**
* Maneja el ingreso de un nuevo número.
*/
function handleAddNumber() {
const num = parseInt(numberInput.value, 10);
if (isNaN(num) || num < 1 || num > 75) {
showAlert("Por favor ingresa un número válido entre 1 y 75.");
return;
}
if (calledNumbers.has(num)) {
showAlert("Ese número ya fue cantado.");
return;
}
calledNumbers.add(num);
numberInput.value = '';
renderCalledNumbers();
updateCardsWithNumber(num);
checkAllCardsForWin();
}
/**
* Actualiza el estado 'marked' de todos los cartones con el nuevo número.
*/
function updateCardsWithNumber(num) {
bingoCards.forEach(card => {
let
numberFound = false;
for (let r = 0; r < 5; r++) {
for (let c = 0; c < 5; c++) {
if (card.rows[r][c] === num) {
card.marked[r][c] = true;
numberFound = true;
break;
}
}
if (numberFound) break;
}
if (numberFound) {
const cardEl = document.getElementById(`card-${card.id}`);
if (cardEl) {
// Reemplaza el contenedor 'div.bg-white...' (el padre del grid)
const parent = cardEl.parentElement;
if (parent) {
// Vuelve a crear el cartón. La nueva función 'createCardElement'
// incluirá la lista de patrones ganados actualizada.
parent.replaceWith(createCardElement(card));
}
}
}
});
}
/**
* Revisa todos los cartones para ver si alguno ganó.
*/
function checkAllCardsForWin() {
for (const card of bingoCards) {
for (const patternKey of activePatterns) {
const pattern = patterns[patternKey];
// Revisa si este patrón AÚN NO HA SIDO GANADO por este cartón
if (!card.wonPatterns.has(patternKey) && isWinner(card.marked, pattern.grid)) {
card.wonPatterns.add(patternKey); // Marcar este patrón como ganado
announceWinner(card, pattern);
// ! IMPORTANTE: Vuelve a renderizar el cartón para mostrar la nueva insignia
const cardEl = document.getElementById(`card-${card.id}`);
if (cardEl) {
const parent = cardEl.parentElement;
if (parent) {
parent.replaceWith(createCardElement(card));
}
}
}
}
}
}
/**
* Compara el grid marcado de un cartón con un patrón ganador.
*/
function isWinner(markedGrid, patternGrid) {
for (let r = 0; r < 5; r++) {
for (let c = 0; c < 5; c++) {
if (patternGrid[r][c] && !markedGrid[r][c]) {
return false;
}
}
}
return true;
}
/**
* Muestra el modal de ganador.
*/
function announceWinner(card, pattern) {
winnerCardId.textContent = card.id;
winnerPatternName.textContent = pattern.name;
winnerModal.classList.remove('hidden');
const cardEl = document.getElementById(`card-${card.id}`);
if (cardEl) {
cardEl.classList.add('is-winner'); // El resaltado persiste
cardEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
/**
* Actualiza el array 'activePatterns' basado en los checkboxes.
*/
function updateActivePatterns() {
activePatterns = [];
const checkboxes = document.querySelectorAll('.pattern-cb');
checkboxes.forEach(cb => {
if (cb.checked) {
activePatterns.push(cb.value);
}
});
// Re-revisa por si un nuevo patrón seleccionado ya es un ganador
checkAllCardsForWin();
}
// --- 6. LÓGICA DEL MODAL DE PATRONES ---
/**
* Crea el grid de 5x5 para el modal.
*/
function createModalGrid() {
modalPatternGrid.innerHTML = '';
for (let r = 0; r < 5; r++) {
for (let c = 0; c < 5; c++) {
const cell = document.createElement('div');
cell.className = 'pattern-cell';
cell.dataset.row = r;
cell.dataset.col = c;
if (patterns.custom.grid[r][c]) {
cell.classList.add('is-selected');
}
cell.addEventListener('click', () => {
cell.classList.toggle('is-selected');
});
modalPatternGrid.appendChild(cell);
}
}
modalPatternGrid.children[12].classList.add('is-selected', 'opacity-50', 'cursor-not-allowed');
modalPatternGrid.children[12].style.pointerEvents = 'none';
}
/**
* Guarda el patrón personalizado del modal.
*/
function saveCustomPattern() {
const newPatternGrid = Array(5).fill(null).map(() => Array(5).fill(false));
const cells = modalPatternGrid.children;
for (let i = 0; i < cells.length; i++) {
const r = cells[i].dataset.row;
const c = cells[i].dataset.col;
if (cells[i].classList.contains('is-selected')) {
newPatternGrid[r][c] = true;
}
}
patterns.custom.grid = newPatternGrid;
patternModal.classList.add('hidden');
document.getElementById('cb-custom').checked = true;
updateActivePatterns();
}
// --- Funciones para mostrar modales ---
function showAlert(message) {
alertMessage.textContent = message;
alertModal.classList.remove('hidden');
}
function showConfirm(message, onOk) {
confirmMessage.textContent = message;
confirmCallback = onOk;
confirmModal.classList.remove('hidden');
}
// --- 7. EVENT LISTENERS ---
startGameBtn.addEventListener('click', startGame);
addNumberBtn.addEventListener('click', handleAddNumber);
numberInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleAddNumber();
}
});
resetGameBtn.addEventListener('click', () => {
showConfirm('¿Estás seguro de que quieres reiniciar? Volverás a la pantalla de carga de cartones.', () => {
gameView.classList.add('hidden');
setupView.classList.remove('hidden');
cardDataInput.value = ''; // Limpiar cartones antiguos
document.querySelectorAll('.pattern-cb').forEach(cb => cb.checked = false);
initGame([]); // Limpiar estado del juego
});
});
document.querySelectorAll('.pattern-cb').forEach(cb => {
cb.addEventListener('change', updateActivePatterns);
});
customPatternBtn.addEventListener('click', () => {
createModalGrid();
patternModal.classList.remove('hidden');
});
modalCancelBtn.addEventListener('click', () => {
patternModal.classList.add('hidden');
document.getElementById('cb-custom').checked = false;
updateActivePatterns();
});
modalSaveBtn.addEventListener('click', saveCustomPattern);
winnerCloseBtn.addEventListener('click', () => {
winnerModal.classList.add('hidden');
});
alertCloseBtn.addEventListener('click', () => {
alertModal.classList.add('hidden');
});
confirmCancelBtn.addEventListener('click', () => {
confirmModal.classList.add('hidden');
confirmCallback = null;
});
confirmOkBtn.addEventListener('click', () => {
confirmModal.classList.add('hidden');
if (confirmCallback) {
confirmCallback();
}
confirmCallback = null;
});
</script>
</body>
</html>
0 Comentarios