<!DOCTYPE html>
<html lang=”es”>
<head>
<meta charset=”UTF-8″ />
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″ />
<title>Dulce Code Challenge</title>
<style>
* { box-sizing: border-box; }
:root {
–bg1: #0f172a;
–bg2: #1e293b;
–card: rgba(15, 23, 42, 0.78);
–accent: #38bdf8;
–accent2: #a78bfa;
–text: #e2e8f0;
–muted: #94a3b8;
–ok: #22c55e;
–bad: #ef4444;
–warn: #f59e0b;
}
body {
margin: 0;
min-height: 100vh;
font-family: Inter, Arial, sans-serif;
color: var(–text);
background:
radial-gradient(circle at top left, rgba(56, 189, 248, 0.18), transparent 22%),
radial-gradient(circle at top right, rgba(167, 139, 250, 0.16), transparent 24%),
linear-gradient(135deg, var(–bg1), var(–bg2));
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.app {
width: 100%;
max-width: 1100px;
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 18px;
}
.game-wrap, .side {
background: var(–card);
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 24px;
box-shadow: 0 20px 45px rgba(0,0,0,0.28);
backdrop-filter: blur(12px);
}
.game-wrap {
padding: 18px;
}
.topbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 12px;
}
.title {
display: flex;
flex-direction: column;
gap: 4px;
}
.title h1 {
margin: 0;
font-size: 2rem;
letter-spacing: 0.5px;
}
.title p {
margin: 0;
color: var(–muted);
font-size: 0.95rem;
}
.stats {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.stat {
background: rgba(30, 41, 59, 0.75);
border: 1px solid rgba(148, 163, 184, 0.14);
border-radius: 16px;
padding: 10px 14px;
min-width: 108px;
}
.stat .label {
font-size: 0.75rem;
color: var(–muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.stat .value {
font-size: 1.3rem;
font-weight: 700;
margin-top: 2px;
}
canvas {
width: 100%;
max-width: 100%;
border-radius: 20px;
border: 1px solid rgba(148, 163, 184, 0.14);
background: linear-gradient(180deg, #0b1120, #111827);
display: block;
}
.controls {
display: flex;
gap: 10px;
align-items: center;
justify-content: space-between;
margin-top: 12px;
flex-wrap: wrap;
}
button {
border: 0;
border-radius: 14px;
padding: 12px 18px;
font-weight: 700;
font-size: 0.95rem;
cursor: pointer;
color: #08111f;
background: linear-gradient(135deg, var(–accent), var(–accent2));
box-shadow: 0 10px 25px rgba(56, 189, 248, 0.22);
}
.hint {
color: var(–muted);
font-size: 0.93rem;
}
.side {
padding: 18px;
display: flex;
flex-direction: column;
gap: 14px;
}
.card {
background: rgba(15, 23, 42, 0.64);
border: 1px solid rgba(148, 163, 184, 0.14);
border-radius: 18px;
padding: 16px;
}
.card h2, .card h3 {
margin: 0 0 10px;
}
.question {
font-size: 1.05rem;
line-height: 1.45;
margin-bottom: 12px;
}
.answers {
display: grid;
gap: 10px;
}
.answer-btn {
width: 100%;
text-align: left;
color: var(–text);
background: rgba(30, 41, 59, 0.9);
border: 1px solid rgba(148, 163, 184, 0.14);
box-shadow: none;
}
.answer-btn:hover { transform: translateY(-1px); }
.feedback {
min-height: 24px;
font-weight: 700;
margin-top: 10px;
}
.feedback.ok { color: var(–ok); }
.feedback.bad { color: var(–bad); }
.small {
color: var(–muted);
font-size: 0.9rem;
line-height: 1.5;
}
.tag {
display: inline-block;
padding: 6px 10px;
border-radius: 999px;
background: rgba(56, 189, 248, 0.12);
color: #7dd3fc;
font-size: 0.8rem;
margin-right: 6px;
margin-bottom: 6px;
}
@media (max-width: 930px) {
.app { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class=”app”>
<section class=”game-wrap”>
<div class=”topbar”>
<div class=”title”>
<h1>🍬 Dulce Code Challenge</h1>
<p>Atrapa dulces, responde preguntas de Sistemas y sube de nivel.</p>
</div>
<div class=”stats”>
<div class=”stat”><div class=”label”>Puntos</div><div class=”value” id=”score”>0</div></div>
<div class=”stat”><div class=”label”>Vidas</div><div class=”value” id=”lives”>3</div></div>
<div class=”stat”><div class=”label”>Nivel</div><div class=”value” id=”level”>1</div></div>
<div class=”stat”><div class=”label”>Racha</div><div class=”value” id=”streak”>0</div></div>
</div>
</div>
<canvas id=”game” width=”760″ height=”460″></canvas>
<div class=”controls”>
<button id=”startBtn”>Iniciar / Reiniciar</button>
<div class=”hint” id=”message”>Mueve la bandeja con el mouse. Cada cierto puntaje aparecerá una pregunta.</div>
</div>
</section>
<aside class=”side”>
<div class=”card”>
<h2>Modo clase 11°</h2>
<span class=”tag”>Sistemas</span>
<span class=”tag”>Lúdico</span>
<span class=”tag”>Navegador</span>
<p class=”small”>Este juego mezcla reflejos con conceptos básicos de informática: hardware, software, redes, seguridad digital, algoritmos y hojas de cálculo.</p>
</div>
<div class=”card” id=”questionCard”>
<h3>Pregunta de Sistemas</h3>
<div class=”question” id=”questionText”>Presiona iniciar para comenzar.</div>
<div class=”answers” id=”answers”></div>
<div class=”feedback” id=”feedback”></div>
</div>
<div class=”card”>
<h3>Cómo se juega</h3>
<p class=”small”>Atrapa los dulces buenos 🍬 para sumar puntos. Evita los malos 💀. Cuando desbloquees una pregunta, el juego se pausa y debes responder para continuar.</p>
</div>
</aside>
</div>
<script>
const canvas = document.getElementById(‘game’);
const ctx = canvas.getContext(‘2d’);
const scoreEl = document.getElementById(‘score’);
const livesEl = document.getElementById(‘lives’);
const levelEl = document.getElementById(‘level’);
const streakEl = document.getElementById(‘streak’);
const messageEl = document.getElementById(‘message’);
const feedbackEl = document.getElementById(‘feedback’);
const questionTextEl = document.getElementById(‘questionText’);
const answersEl = document.getElementById(‘answers’);
const startBtn = document.getElementById(‘startBtn’);
const questions = [
{
question: ‘¿Qué componente se encarga de procesar instrucciones en un computador?’,
answers: [‘Monitor’, ‘CPU’, ‘Teclado’, ‘Memoria USB’],
correct: 1
},
{
question: ‘¿Cuál de estos es un sistema operativo?’,
answers: [‘Excel’, ‘Windows’, ‘Chrome’, ‘Canva’],
correct: 1
},
{
question: ‘¿Qué significa la sigla URL?’,
answers: [‘Uniform Resource Locator’, ‘Universal Red Link’, ‘User Route Login’, ‘Unit Resource Load’],
correct: 0
},
{
question: ‘¿Cuál es una buena práctica de ciberseguridad?’,
answers: [‘Usar la misma clave siempre’, ‘Compartir contraseñas’, ‘Activar verificación en dos pasos’, ‘Abrir cualquier enlace’],
correct: 2
},
{
question: ‘En hojas de cálculo, ¿para qué sirve una fórmula?’,
answers: [‘Cambiar el fondo’, ‘Hacer cálculos automáticos’, ‘Borrar columnas’, ‘Insertar imágenes’],
correct: 1
},
{
question: ‘¿Qué describe mejor a un algoritmo?’,
answers: [‘Una red social’, ‘Un virus informático’, ‘Una secuencia de pasos para resolver un problema’, ‘Un tipo de hardware’],
correct: 2
},
{
question: ‘¿Qué dispositivo permite almacenar información?’,
answers: [‘Disco duro’, ‘Parlante’, ‘Mouse pad’, ‘Micrófono’],
correct: 0
},
{
question: ‘¿Qué es una red informática?’,
answers: [‘Un dibujo digital’, ‘Un conjunto de equipos conectados para compartir información’, ‘Un videojuego’, ‘Un tipo de impresora’],
correct: 1
}
];
let score = 0;
let lives = 3;
let level = 1;
let streak = 0;
let running = false;
let pausedForQuestion = false;
let animationId = null;
let nextQuestionAt = 3;
let currentQuestion = null;
let usedQuestions = [];
const tray = { x: 330, y: 408, width: 120, height: 16 };
const candy = { x: 200, y: -30, size: 30, speed: 3.6 };
const badCandy = { x: 520, y: -200, size: 28, speed: 4.6, active: false };
function updatePanel() {
scoreEl.textContent = score;
livesEl.textContent = lives;
levelEl.textContent = level;
streakEl.textContent = streak;
}
function randomX(margin = 30) {
return Math.random() * (canvas.width – margin * 2) + margin;
}
function resetCandy() {
candy.x = randomX();
candy.y = -20;
}
function resetBadCandy() {
badCandy.x = randomX();
badCandy.y = -Math.random() * 300 – 60;
badCandy.active = level >= 2;
}
function updateDifficulty() {
level = Math.floor(score / 5) + 1;
candy.speed = 3.6 + (level – 1) * 0.45;
badCandy.speed = 4.6 + (level – 1) * 0.5;
badCandy.active = level >= 2;
}
function resetGame() {
score = 0;
lives = 3;
level = 1;
streak = 0;
nextQuestionAt = 3;
usedQuestions = [];
pausedForQuestion = false;
running = true;
tray.x = canvas.width / 2 – tray.width / 2;
updatePanel();
resetCandy();
resetBadCandy();
feedbackEl.textContent = ”;
feedbackEl.className = ‘feedback’;
questionTextEl.textContent = ‘Atrapa 3 dulces para desbloquear la primera pregunta.’;
answersEl.innerHTML = ”;
messageEl.textContent = ‘Juego iniciado. Mantén la concentración.’;
cancelAnimationFrame(animationId);
loop();
}
function drawBackground() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
grad.addColorStop(0, ‘#09111f’);
grad.addColorStop(1, ‘#111827’);
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 0.12;
for (let i = 0; i < 14; i++) {
ctx.beginPath();
ctx.arc(50 + i * 55, 55 + (i % 3) * 18, 18, 0, Math.PI * 2);
ctx.fillStyle = i % 2 === 0 ? ‘#38bdf8’ : ‘#a78bfa’;
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.fillStyle = ‘rgba(148,163,184,0.08)’;
for (let x = 0; x <= canvas.width; x += 38) ctx.fillRect(x, 0, 1, canvas.height);
for (let y = 0; y <= canvas.height; y += 38) ctx.fillRect(0, y, canvas.width, 1);
}
function drawTray() {
ctx.fillStyle = ‘#334155’;
ctx.fillRect(tray.x, tray.y, tray.width, tray.height);
ctx.fillStyle = ‘#38bdf8’;
ctx.fillRect(tray.x + 8, tray.y – 8, tray.width – 16, 8);
ctx.strokeStyle = ‘rgba(255,255,255,0.3)’;
ctx.strokeRect(tray.x, tray.y, tray.width, tray.height);
}
function drawItem(item, emoji) {
ctx.font = `${item.size}px Arial`;
ctx.fillText(emoji, item.x, item.y);
}
function collide(item) {
const itemLeft = item.x – item.size * 0.25;
const itemRight = item.x + item.size * 0.55;
const itemTop = item.y – item.size;
const itemBottom = item.y;
return itemRight > tray.x && itemLeft < tray.x + tray.width && itemBottom > tray.y && itemTop < tray.y + tray.height;
}
function chooseQuestion() {
if (usedQuestions.length === questions.length) usedQuestions = [];
let idx;
do {
idx = Math.floor(Math.random() * questions.length);
} while (usedQuestions.includes(idx));
usedQuestions.push(idx);
return questions[idx];
}
function showQuestion() {
pausedForQuestion = true;
running = false;
currentQuestion = chooseQuestion();
questionTextEl.textContent = currentQuestion.question;
answersEl.innerHTML = ”;
feedbackEl.textContent = ”;
feedbackEl.className = ‘feedback’;
currentQuestion.answers.forEach((answer, index) => {
const btn = document.createElement(‘button’);
btn.className = ‘answer-btn’;
btn.textContent = `${String.fromCharCode(65 + index)}. ${answer}`;
btn.onclick = () => answerQuestion(index);
answersEl.appendChild(btn);
});
messageEl.textContent = ‘Pregunta desbloqueada. Responde para continuar.’;
drawPauseOverlay(‘Pregunta de Sistemas’);
}
function answerQuestion(index) {
if (!currentQuestion) return;
if (index === currentQuestion.correct) {
feedbackEl.textContent = ‘Correcto. +2 puntos extra.’;
feedbackEl.className = ‘feedback ok’;
score += 2;
streak += 1;
nextQuestionAt += 4;
} else {
const right = currentQuestion.answers[currentQuestion.correct];
feedbackEl.textContent = `Incorrecto. Respuesta: ${right}.`;
feedbackEl.className = ‘feedback bad’;
lives -= 1;
streak = 0;
}
updateDifficulty();
updatePanel();
currentQuestion = null;
setTimeout(() => {
if (lives <= 0) {
gameOver();
return;
}
pausedForQuestion = false;
running = true;
messageEl.textContent = ‘Sigue jugando.’;
loop();
}, 900);
}
function drawPauseOverlay(text) {
ctx.fillStyle = ‘rgba(2,6,23,0.72)’;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = ‘#e2e8f0’;
ctx.font = ‘bold 34px Inter, Arial’;
ctx.fillText(text, 235, 200);
ctx.font = ’20px Inter, Arial’;
ctx.fillText(‘Responde en el panel derecho.’, 240, 240);
}
function gameOver() {
running = false;
pausedForQuestion = false;
cancelAnimationFrame(animationId);
messageEl.textContent = `Fin del juego. Puntaje final: ${score}`;
drawBackground();
drawTray();
ctx.fillStyle = ‘rgba(2,6,23,0.74)’;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = ‘#f8fafc’;
ctx.font = ‘bold 40px Inter, Arial’;
ctx.fillText(‘Game Over’, 275, 190);
ctx.font = ’22px Inter, Arial’;
ctx.fillText(`Puntaje final: ${score}`, 295, 230);
ctx.fillText(`Nivel alcanzado: ${level}`, 286, 264);
}
function loop() {
if (!running) return;
drawBackground();
drawTray();
candy.y += candy.speed;
drawItem(candy, ‘🍬’);
if (badCandy.active) {
badCandy.y += badCandy.speed;
drawItem(badCandy, ‘💀’);
}
if (collide(candy)) {
score += 1;
streak += 1;
updateDifficulty();
updatePanel();
messageEl.textContent = ‘Buen reflejo. +1 punto.’;
resetCandy();
} else if (candy.y > canvas.height + 20) {
lives -= 1;
streak = 0;
updatePanel();
messageEl.textContent = ‘Se perdió un dulce.’;
resetCandy();
}
if (badCandy.active) {
if (collide(badCandy)) {
lives -= 1;
streak = 0;
updatePanel();
messageEl.textContent = ‘Atrapaste un dulce tóxico.’;
resetBadCandy();
} else if (badCandy.y > canvas.height + 30) {
resetBadCandy();
}
}
if (lives <= 0) {
gameOver();
return;
}
if (score >= nextQuestionAt && !pausedForQuestion) {
showQuestion();
return;
}
animationId = requestAnimationFrame(loop);
}
canvas.addEventListener(‘mousemove’, (e) => {
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX – rect.left;
tray.x = mouseX – tray.width / 2;
if (tray.x < 0) tray.x = 0;
if (tray.x + tray.width > canvas.width) tray.x = canvas.width – tray.width;
});
startBtn.addEventListener(‘click’, resetGame);
drawBackground();
drawTray();
drawItem({ x: 362, y: 120, size: 46 }, ‘🍬’);
ctx.fillStyle = ‘#f8fafc’;
ctx.font = ‘bold 36px Inter, Arial’;
ctx.fillText(‘Dulce Code Challenge’, 205, 205);
ctx.font = ’20px Inter, Arial’;
ctx.fillStyle = ‘#94a3b8’;
ctx.fillText(‘Haz clic en Iniciar para jugar en el navegador.’, 190, 240);
</script>
</body>
</html>