Skip to main content
The Pica y Fija client maintains several critical state variables to track the game progression, player turns, and UI state. This guide explains how client-side state management works.

Core State Variables

The client maintains the following global state variables:
index.html:215-219
let countdown = null;
let timeLeft = 60;
let tiempoTurnoConfigurado = 60;
let miJugadorId = null;
let juegoTerminado = false;

State Variable Reference

countdown

Stores the interval timer reference for the turn countdown. Used to clear the timer when turns end.

timeLeft

Current seconds remaining in the active turn. Decrements every second during a player’s turn.

tiempoTurnoConfigurado

The configured turn duration in seconds. Set when joining a room and used to reset timeLeft.

miJugadorId

The current player’s ID (0 or 1). Used to distinguish “yo” (me) from “oponente” (opponent) in the results table.

juegoTerminado

Boolean flag indicating if the game has ended. Prevents further guesses after a winner is determined.

Additional State Tracking

index.html:212
const turnosMostrados = new Set();
The turnosMostrados Set prevents duplicate result rows from appearing in the table. Each turn is keyed by ${jugadorId}-${turno}.

Player ID Assignment

When a player joins a room, the server sends a salaLista event with player assignment:
index.html:231-237
socket.on('salaLista', data => {
  miJugadorId = data.jugador === 1 ? 0 : 1;
  tiempoTurnoConfigurado = data.tiempoTurno || 60;
  lobby.style.display = 'none';
  zonaJuego.style.display = 'block';
  log.innerText += `✅ Te uniste como Jugador ${data.jugador} a la sala ${data.sala}\n`;
});
The player ID mapping inverts the server’s player number:
  • Server Player 1 → miJugadorId = 0
  • Server Player 2 → miJugadorId = 1
This allows proper identification of “yo” vs “oponente” in result displays.

Turn Management

The client handles turn state through the turn event from the server:
index.html:253-268
socket.on('turn', data => {
  if (juegoTerminado) return;
  if (data.yourTurn) {
    guessInput.disabled = false;
    guessBtn.disabled = false;
    guessInput.value = '';
    startTimer();
    log.innerText += '🟢 ¡Es tu turno!\n';
    sndTurno.play();
  } else {
    guessInput.disabled = true;
    guessBtn.disabled = true;
    stopTimer();
    log.innerText += '🔴 Esperando turno del oponente...\n';
  }
});
1

Check if Game Ended

If juegoTerminado is true, the handler returns early to prevent turn processing.
2

Your Turn

When data.yourTurn is true:
  • Enable guess input and button
  • Clear previous guess value
  • Start the countdown timer
  • Play turn notification sound
  • Log turn notification
3

Opponent's Turn

When data.yourTurn is false:
  • Disable guess input and button
  • Stop the countdown timer
  • Log waiting message

Timer Implementation

The countdown timer provides visual feedback for remaining turn time:

Starting the Timer

index.html:314-328
function startTimer() {
  timeLeft = tiempoTurnoConfigurado;
  updateTimer();
  countdown = setInterval(() => {
    timeLeft--;
    updateTimer();
    if (timeLeft <= 0) {
      stopTimer();
      guessInput.disabled = true;
      guessBtn.disabled = true;
      log.innerText += '⏰ Se acabó el tiempo del turno. Pasando turno...\n';
      socket.emit('pasarTurnoPorTiempo');
    }
  }, 1000);
}
  1. Resets timeLeft to the configured turn duration
  2. Updates the timer display immediately
  3. Creates an interval that runs every 1000ms (1 second)
  4. Decrements timeLeft and updates display each second
  5. When time expires:
    • Stops the timer
    • Disables input controls
    • Logs timeout message
    • Emits pasarTurnoPorTiempo event to server

Stopping the Timer

index.html:330-333
function stopTimer() {
  clearInterval(countdown);
  timer.innerText = '';
}

Updating the Display

index.html:335-337
function updateTimer() {
  timer.innerText = `⏳ Tiempo restante: ${timeLeft}s`;
}
The timer display shows remaining seconds in a prominent yellow color:
index.html:153
<div id="turnTimer" style="margin-top:1rem; font-size: 1.2rem; color: #ffcc00;"></div>

Results Table Management

The results table displays all guesses made during the game:

Table Structure

index.html:157-169
<table id="resultTable" hidden>
  <thead class="table-warning text-dark">
    <tr>
      <th><i class="fas fa-user"></i></th> <!-- Jugador -->
      <th><i class="fas fa-hashtag"></i></th> <!-- Turno -->
      <th><i class="fas fa-keyboard"></i></th> <!-- Intento -->
      <th><i class="fas fa-bullseye"></i></th> <!-- Picas -->
      <th><i class="fas fa-check-circle"></i></th> <!-- Fijas -->
    </tr>
  </thead>
  <tbody></tbody>
</table>

Adding Results

When the server sends a result event, a new row is added:
index.html:270-288
socket.on('result', data => {
  const key = `${data.jugadorId}-${data.turno}`;
  if (turnosMostrados.has(key)) return;
  turnosMostrados.add(key);

  const jugadorLabel = data.jugadorId === miJugadorId ? 'yo' : 'oponente';
  const jugadorColor = data.jugadorId === miJugadorId ? 'white' : 'orange';

  const row = document.createElement('tr');
  row.innerHTML = `
    <td style="color:${jugadorColor}">${jugadorLabel}</td>
    <td style="color:${jugadorColor}">${data.turno}</td>
    <td style="color:${jugadorColor}">${data.intento}</td>
    <td style="color:${jugadorColor}">${data.picas}</td>
    <td style="color:${jugadorColor}">${data.fijas}</td>
  `;
  table.insertBefore(row, table.firstChild);
  filtrarTurnos();
  // ... game end detection
});
Duplicate Prevention: The turnosMostrados Set ensures each unique turn is only displayed once, even if the server sends multiple result events.

Color Coding

PlayerLabelColor
Current player (miJugadorId)“yo”White
Opponent”oponente”Orange

Result Filtering System

Players can filter the results table to view different perspectives:
index.html:146-152
<label for="filtroTurnos">Ver:</label>
<select id="filtroTurnos" onchange="filtrarTurnos()" class="form-control">
  <option value="todos">Todos los turnos</option>
  <option value="yo">Solo mis turnos</option>
  <option value="oponente">Solo del oponente</option>
</select>
index.html:379-393
function filtrarTurnos() {
  const filtro = document.getElementById('filtroTurnos').value;
  const rows = table.querySelectorAll('tr');
  rows.forEach(row => {
    const jugador = row.children[0]?.textContent;
    if (filtro === 'todos') {
      row.style.display = '';
    } else if (filtro === 'yo') {
      row.style.display = jugador === 'yo' ? '' : 'none';
    } else if (filtro === 'oponente') {
      row.style.display = jugador === 'oponente' ? '' : 'none';
    }
  });
}
1

Get Filter Value

Reads the selected option from the dropdown (todos, yo, or oponente)
2

Iterate Rows

Loops through all table rows using querySelectorAll('tr')
3

Apply Filter

Shows or hides rows based on the player label in the first column:
  • todos: Show all rows
  • yo: Show only current player’s rows
  • oponente: Show only opponent’s rows

Game End Detection

The client detects game completion when a result contains 4 fijas (exact matches):
index.html:291-303
if (data.fijas === 4) {
  juegoTerminado = true;
  guessInput.disabled = true;
  guessBtn.disabled = true;
  sndJuego.pause(); 
  stopTimer();
  if (data.jugadorId === miJugadorId) {
    showResult('🎉 ¡Ganaste!', sndGanador);
  } else {
    showResult('💀 Perdiste', sndPerdedor);
  }
}
1

Set Game Ended Flag

Sets juegoTerminado = true to prevent further guesses
2

Disable Controls

Disables the guess input and submit button
3

Stop Background Audio

Pauses the looping game music
4

Clear Timer

Stops the countdown timer
5

Display Result

Shows win/loss overlay based on which player won:
  • Current player won: ”🎉 ¡Ganaste!” + winner sound
  • Opponent won: ”💀 Perdiste” + loser sound

Overlay Display System

The overlay provides full-screen feedback for game results:
index.html:339-343
function showResult(message, audio) {
  overlay.style.display = 'flex';
  overlay.innerText = message;
  audio.play();
}
The overlay element uses a semi-transparent black background with large centered text:
index.html:181
<div id="overlay" 
     style="display:none; 
            position:fixed; 
            top:0; 
            left:0; 
            width:100%; 
            height:100%; 
            background:rgba(0,0,0,0.8); 
            color:#fff; 
            font-size:3rem; 
            justify-content:center; 
            align-items:center; 
            z-index:9999;">
</div>
The overlay uses z-index:9999 to ensure it appears above all other UI elements when displayed.

Alternative State Management (main.js)

The main.js file shows a simplified state management approach:
main.js:26-29
let secretSent = false;
let countdown = null;
let timeLeft = 60;
let isReady = false;
Key differences from the main implementation:
  • Uses secretSent boolean instead of hiding UI elements
  • Includes isReady flag for player readiness
  • Fixed 60-second turn time (no tiempoTurnoConfigurado)
  • No miJugadorId tracking (simpler win/loss detection)
  • No duplicate turn prevention with turnosMostrados
The main.js implementation is an alternative/legacy version. The primary client implementation is in index.html with more robust state management.

Build docs developers (and LLMs) love