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
-- 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)
// 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
// 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)
// 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
// 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
// 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
// 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
// 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:
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
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
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
Refuerzo con Juegos:
Después del proyecto, se sugieren juegos relacionados para reforzar aprendizaje
Los juegos usan los mismos conceptos en formatos interactivos
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
Diseño Responsivo: Asegúrate que todos los juegos y la página de estudio funcionen bien en móviles
Progresión Gradual: Organiza los temas y proyectos por dificultad creciente
Retroalimentación: Proporciona explicaciones cuando el usuario cometa errores
Gamificación: Añade logros, puntos y recompensas para mantener la motivación
Personalización: Permite a los usuarios agregar sus propias preguntas/proyectos
Comentarios
Publicar un comentario