Integración de Juegos en JavaScript

 

Ampliación del Proyecto: Integración de Juegos en JavaScript y Sistema de Proyectos Prácticos

Estructura de Base de Datos Ampliada

sql
-- Tabla de proyectos prácticos
CREATE TABLE proyectos (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tema_id INT NOT NULL,
  titulo VARCHAR(100) NOT NULL,
  descripcion TEXT NOT NULL,
  dificultad ENUM('fácil', 'medio', 'difícil') DEFAULT 'medio',
  requisitos TEXT, -- Conceptos previos necesarios
  duracion_estimada INT, -- En minutos
  ejemplo_solucion TEXT,
  FOREIGN KEY (tema_id) REFERENCES temas(id)
);

-- Tabla de recursos de estudio
CREATE TABLE recursos_estudio (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tema_id INT NOT NULL,
  tipo ENUM('texto', 'video', 'imagen', 'enlace', 'documento') NOT NULL,
  contenido TEXT NOT NULL,
  url VARCHAR(255),
  orden INT DEFAULT 0,
  FOREIGN KEY (tema_id) REFERENCES temas(id)
);

Implementación de Juegos en JavaScript

1. Memorama (Memory Game)

javascript
// memorama.js
class MemoryGame {
  constructor(parejas) {
    this.parejas = parejas;
    this.cards = [];
    this.flippedCards = [];
    this.matches = 0;
    this.initializeGame();
  }

  initializeGame() {
    // Duplicar y mezclar las parejas
    this.cards = [...this.parejas, ...this.parejas]
      .map((card, index) => ({ ...card, id: index }))
      .sort(() => Math.random() - 0.5);
    
    this.renderBoard();
  }

  renderBoard() {
    const gameContainer = document.getElementById('memory-game');
    gameContainer.innerHTML = '';
    
    this.cards.forEach(card => {
      const cardElement = document.createElement('div');
      cardElement.className = 'memory-card';
      cardElement.dataset.id = card.id;
      
      const frontFace = document.createElement('div');
      frontFace.className = 'front-face';
      frontFace.textContent = '?'; // Puede ser una imagen de reverso
      
      const backFace = document.createElement('div');
      backFace.className = 'back-face';
      
      if (card.imagen_url) {
        const img = document.createElement('img');
        img.src = card.imagen_url;
        img.alt = card.concepto;
        backFace.appendChild(img);
      } else {
        backFace.textContent = card.concepto || card.definicion;
      }
      
      cardElement.appendChild(frontFace);
      cardElement.appendChild(backFace);
      cardElement.addEventListener('click', () => this.flipCard(cardElement, card));
      gameContainer.appendChild(cardElement);
    });
  }

  flipCard(cardElement, card) {
    if (this.flippedCards.length < 2 && !card.matched && !this.flippedCards.includes(card)) {
      cardElement.classList.add('flipped');
      this.flippedCards.push(card);
      
      if (this.flippedCards.length === 2) {
        this.checkForMatch();
      }
    }
  }

  checkForMatch() {
    const [card1, card2] = this.flippedCards;
    const isMatch = card1.concepto === card2.definicion || 
                   card1.definicion === card2.concepto ||
                   (card1.id !== card2.id && card1.concepto === card2.concepto);
    
    if (isMatch) {
      this.matches++;
      this.flippedCards.forEach(card => card.matched = true);
      
      if (this.matches === this.parejas.length) {
        setTimeout(() => alert('¡Felicidades! Has completado el juego.'), 500);
      }
    } else {
      setTimeout(() => {
        document.querySelectorAll('.memory-card').forEach(el => {
          if (this.flippedCards.some(c => c.id == el.dataset.id)) {
            el.classList.remove('flipped');
          }
        });
      }, 1000);
    }
    
    this.flippedCards = [];
  }
}

// Uso desde PHP
<?php
$parejas = $db->query("SELECT concepto, definicion, imagen_url FROM parejas_memorama WHERE tema_id = $tema_id")->fetchAll(PDO::FETCH_ASSOC);
echo "<script>const game = new MemoryGame(" . json_encode($parejas) . ");</script>";
?>

2. Rompecabezas de Comandos Linux

javascript
// puzzle.js
class CommandPuzzle {
  constructor(comando, partes) {
    this.comandoCompleto = comando;
    this.partes = partes;
    this.initializePuzzle();
  }

  initializePuzzle() {
    const puzzleContainer = document.getElementById('puzzle-container');
    const dropzone = document.getElementById('dropzone');
    
    // Mezclar las partes
    const shuffledParts = [...this.partes].sort(() => Math.random() - 0.5);
    
    // Crear elementos arrastrables
    shuffledParts.forEach((part, index) => {
      const draggable = document.createElement('div');
      draggable.className = 'draggable-part';
      draggable.draggable = true;
      draggable.textContent = part;
      draggable.dataset.index = index;
      draggable.addEventListener('dragstart', this.dragStart);
      puzzleContainer.appendChild(draggable);
    });
    
    // Configurar zona de destino
    dropzone.addEventListener('dragover', this.dragOver);
    dropzone.addEventListener('drop', (e) => this.drop(e));
    dropzone.addEventListener('dragleave', this.dragLeave);
  }

  dragStart(e) {
    e.dataTransfer.setData('text/plain', e.target.dataset.index);
    setTimeout(() => e.target.classList.add('dragging'), 0);
  }

  dragOver(e) {
    e.preventDefault();
    e.target.classList.add('dropzone-active');
  }

  dragLeave(e) {
    e.target.classList.remove('dropzone-active');
  }

  drop(e) {
    e.preventDefault();
    e.target.classList.remove('dropzone-active');
    
    const index = e.dataTransfer.getData('text/plain');
    const draggedItem = document.querySelector(`.draggable-part[data-index="${index}"]`);
    
    if (draggedItem) {
      e.target.appendChild(draggedItem);
      this.checkSolution();
    }
  }

  checkSolution() {
    const dropzone = document.getElementById('dropzone');
    const currentParts = Array.from(dropzone.children)
      .map(el => el.textContent.trim())
      .join(' ');
    
    if (currentParts === this.comandoCompleto) {
      dropzone.classList.add('correct');
      setTimeout(() => alert('¡Correcto! Comando armado: ' + currentParts), 500);
    }
  }
}

// Uso desde PHP
<?php
$comando = "docker run -d -p 8080:80 nginx";
$partes = explode(" ", $comando);
echo "<script>const puzzle = new CommandPuzzle('$comando', " . json_encode($partes) . ");</script>";
?>

3. Tarjetas de Memoria (Flashcards)

javascript
// flashcards.js
class FlashCards {
  constructor(cards) {
    this.cards = cards;
    this.currentIndex = 0;
    this.initializeFlashCards();
  }

  initializeFlashCards() {
    this.renderCard();
    
    document.getElementById('next-btn').addEventListener('click', () => {
      this.currentIndex = (this.currentIndex + 1) % this.cards.length;
      this.renderCard();
    });
    
    document.getElementById('prev-btn').addEventListener('click', () => {
      this.currentIndex = (this.currentIndex - 1 + this.cards.length) % this.cards.length;
      this.renderCard();
    });
    
    document.getElementById('flip-btn').addEventListener('click', () => {
      document.getElementById('flashcard').classList.toggle('flipped');
    });
  }

  renderCard() {
    const card = document.getElementById('flashcard');
    card.classList.remove('flipped');
    
    const front = card.querySelector('.front');
    const back = card.querySelector('.back');
    
    front.innerHTML = this.cards[this.currentIndex].pregunta;
    back.innerHTML = this.cards[this.currentIndex].explicacion || this.cards[this.currentIndex].correcta;
    
    document.getElementById('card-count').textContent = 
      `${this.currentIndex + 1}/${this.cards.length}`;
  }
}

// Uso desde PHP
<?php
$flashcards = $db->query("SELECT pregunta, explicacion, correcta FROM preguntas WHERE tema_id = $tema_id")->fetchAll(PDO::FETCH_ASSOC);
echo "<script>const flashcards = new FlashCards(" . json_encode($flashcards) . ");</script>";
?>

4. Ahorcado Técnico

javascript
// hangman.js
class TechnicalHangman {
  constructor(words, maxAttempts = 6) {
    this.words = words;
    this.maxAttempts = maxAttempts;
    this.selectedWord = '';
    this.guessedLetters = [];
    this.wrongAttempts = 0;
    this.initializeGame();
  }

  initializeGame() {
    this.selectRandomWord();
    this.renderWord();
    this.renderKeyboard();
    this.updateHangmanImage();
  }

  selectRandomWord() {
    this.selectedWord = this.words[Math.floor(Math.random() * this.words.length)].toUpperCase();
  }

  renderWord() {
    const wordDisplay = document.getElementById('word-display');
    wordDisplay.innerHTML = '';
    
    this.selectedWord.split('').forEach(letter => {
      const letterSpan = document.createElement('span');
      letterSpan.className = 'letter';
      
      if (this.guessedLetters.includes(letter) || letter === ' ') {
        letterSpan.textContent = letter;
      } else {
        letterSpan.textContent = '_';
      }
      
      wordDisplay.appendChild(letterSpan);
    });
    
    if (!wordDisplay.textContent.includes('_')) {
      this.endGame(true);
    }
  }

  renderKeyboard() {
    const keyboard = document.getElementById('keyboard');
    keyboard.innerHTML = '';
    
    'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').forEach(letter => {
      const key = document.createElement('button');
      key.className = 'key';
      key.textContent = letter;
      key.disabled = this.guessedLetters.includes(letter);
      key.addEventListener('click', () => this.guessLetter(letter));
      keyboard.appendChild(key);
    });
  }

  guessLetter(letter) {
    if (!this.guessedLetters.includes(letter)) {
      this.guessedLetters.push(letter);
      
      if (!this.selectedWord.includes(letter)) {
        this.wrongAttempts++;
        this.updateHangmanImage();
      }
      
      this.renderWord();
      this.renderKeyboard();
      
      if (this.wrongAttempts >= this.maxAttempts) {
        this.endGame(false);
      }
    }
  }

  updateHangmanImage() {
    const hangmanImg = document.getElementById('hangman-img');
    hangmanImg.src = `images/hangman-${this.wrongAttempts}.png`;
  }

  endGame(isWin) {
    const message = isWin 
      ? `¡Felicidades! Adivinaste: ${this.selectedWord}` 
      : `¡Game Over! La palabra era: ${this.selectedWord}`;
    
    alert(message);
    document.getElementById('keyboard').querySelectorAll('button').forEach(btn => {
      btn.disabled = true;
    });
    
    // Botón para reiniciar
    const restartBtn = document.createElement('button');
    restartBtn.textContent = 'Jugar de nuevo';
    restartBtn.addEventListener('click', () => {
      this.guessedLetters = [];
      this.wrongAttempts = 0;
      this.initializeGame();
    });
    
    document.getElementById('game-controls').appendChild(restartBtn);
  }
}

// Uso desde PHP
<?php
$terminos = $db->query("SELECT DISTINCT concepto FROM parejas_memorama WHERE tema_id = $tema_id")->fetchAll(PDO::FETCH_COLUMN);
echo "<script>const hangman = new TechnicalHangman(" . json_encode($terminos) . ");</script>";
?>

5. Simulador de Terminal Linux

javascript
// terminal.js
class LinuxTerminalSimulator {
  constructor(commands) {
    this.commands = commands;
    this.history = [];
    this.currentDir = '~';
    this.initializeTerminal();
  }

  initializeTerminal() {
    const terminal = document.getElementById('terminal');
    const input = document.getElementById('terminal-input');
    const output = document.getElementById('terminal-output');
    
    input.addEventListener('keydown', (e) => {
      if (e.key === 'Enter') {
        const command = input.value.trim();
        this.executeCommand(command);
        input.value = '';
      }
    });
    
    // Mostrar mensaje inicial
    this.printMessage('Bienvenido al simulador de terminal Linux. Escribe "help" para ver los comandos disponibles.');
  }

  executeCommand(command) {
    if (!command) return;
    
    this.history.push(command);
    this.printMessage(`$ ${command}`, 'command');
    
    const [cmd, ...args] = command.split(' ');
    const matchedCommand = this.commands.find(c => c.comando === cmd);
    
    if (matchedCommand) {
      if (matchedCommand.validacion) {
        // Validar argumentos si es necesario
        const isValid = this.validateArgs(matchedCommand, args);
        if (!isValid) {
          this.printMessage(matchedCommand.uso, 'error');
          return;
        }
      }
      
      this.printMessage(matchedCommand.descripcion, 'output');
      
      if (matchedCommand.accion === 'cd') {
        this.currentDir = args[0] || '~';
      }
    } else if (cmd === 'help') {
      this.showHelp();
    } else if (cmd === 'clear') {
      document.getElementById('terminal-output').innerHTML = '';
    } else if (cmd === 'history') {
      this.printMessage(this.history.join('\n'), 'output');
    } else {
      this.printMessage(`Comando no encontrado: ${cmd}. Escribe "help" para ver los comandos disponibles.`, 'error');
    }
  }

  validateArgs(command, args) {
    if (command.args_requeridos && args.length < command.args_requeridos) {
      return false;
    }
    return true;
  }

  showHelp() {
    let helpText = 'Comandos disponibles:\n\n';
    this.commands.forEach(cmd => {
      helpText += `${cmd.comando}: ${cmd.descripcion}\n`;
      helpText += `Uso: ${cmd.uso}\n\n`;
    });
    helpText += 'Comandos especiales:\n';
    helpText += 'help: Muestra esta ayuda\n';
    helpText += 'clear: Limpia la terminal\n';
    helpText += 'history: Muestra el historial de comandos\n';
    
    this.printMessage(helpText, 'output');
  }

  printMessage(message, type = 'output') {
    const output = document.getElementById('terminal-output');
    const messageElement = document.createElement('div');
    messageElement.className = `terminal-${type}`;
    messageElement.textContent = message;
    output.appendChild(messageElement);
    output.scrollTop = output.scrollHeight;
  }
}

// Uso desde PHP
<?php
$comandos = $db->query("
  SELECT 
    p.pregunta as comando, 
    r.contenido as descripcion, 
    r2.contenido as uso,
    r3.contenido as args_requeridos,
    r4.contenido as validacion
  FROM preguntas p
  JOIN respuestas r ON p.id = r.pregunta_id AND r.es_correcta = 1
  JOIN respuestas r2 ON p.id = r2.pregunta_id AND r2.contenido LIKE 'Uso:%'
  JOIN respuestas r3 ON p.id = r3.pregunta_id AND r3.contenido LIKE 'Args:%'
  JOIN respuestas r4 ON p.id = r4.pregunta_id AND r4.contenido LIKE 'Validación:%'
  WHERE p.tema_id = $tema_id AND p.tipo = 'comando_linux'
")->fetchAll(PDO::FETCH_ASSOC);

echo "<script>const terminal = new LinuxTerminalSimulator(" . json_encode($comandos) . ");</script>";
?>

Página de Estudio de Temas

php
<?php
// study_page.php
function renderStudyPage($tema_id) {
  global $db;
  
  // Obtener información del tema
  $tema = $db->query("SELECT * FROM temas WHERE id = $tema_id")->fetch(PDO::FETCH_ASSOC);
  
  // Obtener recursos de estudio
  $recursos = $db->query("SELECT * FROM recursos_estudio WHERE tema_id = $tema_id ORDER BY orden")->fetchAll(PDO::FETCH_ASSOC);
  
  // Obtener proyectos relacionados
  $proyectos = $db->query("SELECT * FROM proyectos WHERE tema_id = $tema_id")->fetchAll(PDO::FETCH_ASSOC);
  
  echo "<div class='study-container'>";
  echo "<h1>{$tema['nombre']}</h1>";
  echo "<p class='description'>{$tema['descripcion']}</p>";
  
  // Mostrar recursos de estudio
  echo "<div class='resources-section'>";
  echo "<h2>Recursos de Estudio</h2>";
  
  foreach ($recursos as $recurso) {
    echo "<div class='resource {$recurso['tipo']}'>";
    
    switch ($recurso['tipo']) {
      case 'texto':
        echo "<div class='text-content'>{$recurso['contenido']}</div>";
        break;
      case 'video':
        echo "<div class='video-container'><iframe src='{$recurso['url']}' allowfullscreen></iframe></div>";
        break;
      case 'imagen':
        echo "<img src='{$recurso['url']}' alt='{$recurso['contenido']}'>";
        break;
      case 'enlace':
        echo "<a href='{$recurso['url']}' target='_blank'>{$recurso['contenido']}</a>";
        break;
      case 'documento':
        echo "<a href='{$recurso['url']}' class='document-link' download>{$recurso['contenido']}</a>";
        break;
    }
    
    echo "</div>";
  }
  
  echo "</div>";
  
  // Mostrar proyecto sugerido
  if (!empty($proyectos)) {
    echo "<div class='project-section'>";
    echo "<h2>Proyecto Práctico</h2>";
    
    $proyecto = $proyectos[0]; // Tomar el primer proyecto
    echo "<div class='project-card'>";
    echo "<h3>{$proyecto['titulo']}</h3>";
    echo "<p>{$proyecto['descripcion']}</p>";
    
    if ($proyecto['requisitos']) {
      echo "<div class='requirements'><strong>Requisitos:</strong> {$proyecto['requisitos']}</div>";
    }
    
    if ($proyecto['duracion_estimada']) {
      echo "<div class='duration'><strong>Duración estimada:</strong> {$proyecto['duracion_estimada']} minutos</div>";
    }
    
    echo "<button class='start-project' data-project-id='{$proyecto['id']}'>Comenzar Proyecto</button>";
    
    // Ejemplo de solución (oculto inicialmente)
    if ($proyecto['ejemplo_solucion']) {
      echo "<div class='solution-toggle'>Mostrar solución ejemplo</div>";
      echo "<div class='solution' style='display:none;'>{$proyecto['ejemplo_solucion']}</div>";
    }
    
    echo "</div>";
    echo "</div>";
    
    // JavaScript para manejar el proyecto
    echo "<script>
      document.querySelector('.start-project').addEventListener('click', function() {
        const projectId = this.dataset.projectId;
        // Aquí podrías redirigir a una página específica del proyecto
        // o mostrar un modal con las instrucciones detalladas
        alert('Iniciando proyecto: ' + projectId);
      });
      
      document.querySelector('.solution-toggle').addEventListener('click', function() {
        const solution = this.nextElementSibling;
        solution.style.display = solution.style.display === 'none' ? 'block' : 'none';
        this.textContent = solution.style.display === 'none' ? 'Mostrar solución ejemplo' : 'Ocultar solución ejemplo';
      });
    </script>";
  }
  
  // Botones de navegación
  echo "<div class='study-navigation'>";
  echo "<button class='btn-practice' onclick=\"window.location.href='practice.php?tema_id={$tema_id}'\">Practicar con Ejercicios</button>";
  echo "<button class='btn-games' onclick=\"window.location.href='games.php?tema_id={$tema_id}'\">Aprender con Juegos</button>";
  echo "</div>";
  
  echo "</div>";
}
?>

Sistema de Proyectos Prácticos

Para implementar el sistema que sugiere proyectos después de estudiar un tema:

php
<?php
// project_system.php

// Función para sugerir proyecto basado en el tema estudiado
function suggestProject($usuario_id, $tema_id) {
  global $db;
  
  // Verificar si el usuario ha completado el estudio del tema
  $progreso = $db->query("SELECT * FROM progreso_usuarios 
                          WHERE usuario_id = $usuario_id AND tema_id = $tema_id
                          AND porcentaje_completado >= 70")->fetch(PDO::FETCH_ASSOC);
  
  if ($progreso) {
    // Obtener proyecto no completado para este tema
    $proyecto = $db->query("
      SELECT p.* 
      FROM proyectos p
      LEFT JOIN user_projects up ON p.id = up.project_id AND up.user_id = $usuario_id
      WHERE p.tema_id = $tema_id AND (up.completed IS NULL OR up.completed = 0)
      ORDER BY p.dificultad
      LIMIT 1
    ")->fetch(PDO::FETCH_ASSOC);
    
    if ($proyecto) {
      return [
        'suggestion' => true,
        'project' => $proyecto,
        'message' => '¡Has completado el estudio de este tema! Te sugerimos el siguiente proyecto práctico:'
      ];
    }
  }
  
  return ['suggestion' => false];
}

// Función para marcar proyecto como completado
function completeProject($usuario_id, $proyecto_id) {
  global $db;
  
  // Verificar si ya existe registro
  $existente = $db->query("SELECT * FROM user_projects 
                          WHERE user_id = $usuario_id AND project_id = $proyecto_id")
                  ->fetch(PDO::FETCH_ASSOC);
  
  if ($existente) {
    $db->query("UPDATE user_projects SET completed = 1, completed_at = NOW() 
                WHERE user_id = $usuario_id AND project_id = $proyecto_id");
  } else {
    $db->query("INSERT INTO user_projects (user_id, project_id, completed, completed_at)
                VALUES ($usuario_id, $proyecto_id, 1, NOW())");
  }
  
  // Actualizar logros del usuario si es necesario
  checkAchievements($usuario_id);
}

// Tabla adicional necesaria para el sistema de proyectos
CREATE TABLE user_projects (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  project_id INT NOT NULL,
  completed BOOLEAN DEFAULT FALSE,
  completed_at TIMESTAMP NULL,
  feedback TEXT,
  FOREIGN KEY (user_id) REFERENCES usuarios(id),
  FOREIGN KEY (project_id) REFERENCES proyectos(id),
  UNIQUE KEY (user_id, project_id)
);
?>

Integración Completa del Sistema

Para unir todos estos componentes en un flujo coherente:

  1. Estudio del Tema:

    • El usuario accede a la página de estudio con los recursos organizados

    • Al finalizar, el sistema verifica comprensión con preguntas clave

  2. Sugerencia de Proyecto:

    • Si el usuario demuestra comprensión (≥70% en evaluación), se sugiere un proyecto

    • El proyecto se selecciona basado en:

      • Dificultad apropiada

      • No completado previamente

      • Relevancia para el tema

  3. Ejecución del Proyecto:

    • El usuario recibe instrucciones paso a paso

    • Puede consultar la solución ejemplo si se atasca

    • Al completarlo, recibe retroalimentación y se registra su progreso

  4. Refuerzo con Juegos:

    • Después del proyecto, se sugieren juegos relacionados para reforzar aprendizaje

    • Los juegos usan los mismos conceptos en formatos interactivos

  5. Seguimiento:

    • El sistema registra:

      • Tiempo dedicado a cada tema/proyecto

      • Puntuaciones en ejercicios y juegos

      • Proyectos completados

    • Genera recomendaciones personalizadas para próximos pasos

Recomendaciones Finales

  1. Diseño Responsivo: Asegúrate que todos los juegos y la página de estudio funcionen bien en móviles

  2. Progresión Gradual: Organiza los temas y proyectos por dificultad creciente

  3. Retroalimentación: Proporciona explicaciones cuando el usuario cometa errores

  4. Gamificación: Añade logros, puntos y recompensas para mantener la motivación

  5. Personalización: Permite a los usuarios agregar sus propias preguntas/proyectos

Comentarios