first commit
This commit is contained in:
commit
c3223d211e
12 changed files with 570 additions and 0 deletions
16
.dockerignore
Normal file
16
.dockerignore
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
run.sh
|
||||||
|
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
ranking.db
|
||||||
|
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
|
||||||
34
Dockerfile
Normal file
34
Dockerfile
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
ENV FLASK_APP=app.py
|
||||||
|
ENV FLASK_ENV=production
|
||||||
|
ENV FLASK_RUN_HOST=0.0.0.0
|
||||||
|
|
||||||
|
# ---------------------------------------------
|
||||||
|
# 9) Antes de arrancar, creamos la tabla si no existe
|
||||||
|
# (llamamos a la función crear_tabla() definida en app.py).
|
||||||
|
# Esto hará que, cuando se construya la imagen, se garantice
|
||||||
|
# que la BD ya tiene la estructura mínima.
|
||||||
|
# ---------------------------------------------
|
||||||
|
RUN python - << 'EOF'
|
||||||
|
from app import crear_tabla
|
||||||
|
crear_tabla()
|
||||||
|
EOF
|
||||||
|
|
||||||
|
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
|
||||||
|
|
||||||
3
README.md
Normal file
3
README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# MULTI-GAME
|
||||||
|
|
||||||
|
Tienes 1 minuto para resolver todas las multiplicaciones de 2 cifras que seas capaz!
|
||||||
149
app.py
Normal file
149
app.py
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
import sqlite3
|
||||||
|
from flask import Flask, render_template, request, jsonify, g
|
||||||
|
|
||||||
|
DATABASE = "ranking.db"
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
"""
|
||||||
|
Abre una conexión a la base de datos SQLite y la asocia a 'g'
|
||||||
|
para reutilizarla en cada petición.
|
||||||
|
"""
|
||||||
|
db = getattr(g, "_database", None)
|
||||||
|
if db is None:
|
||||||
|
db = g._database = sqlite3.connect(DATABASE)
|
||||||
|
return db
|
||||||
|
|
||||||
|
|
||||||
|
@app.teardown_appcontext
|
||||||
|
def close_connection(exception):
|
||||||
|
"""
|
||||||
|
Al terminar el contexto de la petición, cierra la conexión si existe.
|
||||||
|
"""
|
||||||
|
db = getattr(g, "_database", None)
|
||||||
|
if db is not None:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def crear_tabla():
|
||||||
|
"""
|
||||||
|
Crea la tabla ranking si no existe.
|
||||||
|
"""
|
||||||
|
conn = sqlite3.connect(DATABASE)
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS ranking (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
nombre TEXT NOT NULL,
|
||||||
|
puntuacion INTEGER NOT NULL
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def obtener_top10():
|
||||||
|
"""
|
||||||
|
Devuelve una lista de tuplas [(nombre, puntuacion), ...] con el top 10 ordenado descendente.
|
||||||
|
"""
|
||||||
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("SELECT nombre, puntuacion FROM ranking ORDER BY puntuacion DESC, id ASC LIMIT 10")
|
||||||
|
resultados = c.fetchall()
|
||||||
|
return resultados
|
||||||
|
|
||||||
|
|
||||||
|
def puntuacion_entra_en_top(puntos):
|
||||||
|
"""
|
||||||
|
Comprueba si 'puntos' supera la última posición del top 10.
|
||||||
|
Si hay menos de 10 filas, siempre devuelve True para guardar.
|
||||||
|
"""
|
||||||
|
top = obtener_top10()
|
||||||
|
if len(top) < 10:
|
||||||
|
return True
|
||||||
|
# La décima posición es top[-1][1] (puntuacion más baja en top10)
|
||||||
|
return puntos > top[-1][1]
|
||||||
|
|
||||||
|
|
||||||
|
def guardar_puntuacion(nombre, puntos):
|
||||||
|
"""
|
||||||
|
Inserta la nueva puntuación en la tabla ranking.
|
||||||
|
"""
|
||||||
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
c.execute("INSERT INTO ranking (nombre, puntuacion) VALUES (?, ?)", (nombre, puntos))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
"""
|
||||||
|
Página principal con instrucciones y botón para iniciar el juego.
|
||||||
|
"""
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/juego")
|
||||||
|
def juego():
|
||||||
|
"""
|
||||||
|
Página donde se juega: se carga el HTML y el JS se encarga de todo lo demás.
|
||||||
|
"""
|
||||||
|
return render_template("game.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/ranking")
|
||||||
|
def ranking():
|
||||||
|
"""
|
||||||
|
Página que muestra el top 10.
|
||||||
|
"""
|
||||||
|
top10 = obtener_top10()
|
||||||
|
return render_template("ranking.html", top10=top10)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/score", methods=["POST"])
|
||||||
|
def api_score():
|
||||||
|
"""
|
||||||
|
Recibe JSON: { "puntuacion": <int> }
|
||||||
|
Comprueba si entra en top10 y devuelve JSON: { "entra_en_top": true/false, "top10": [...] }
|
||||||
|
"""
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or "puntuacion" not in data:
|
||||||
|
return jsonify({"error": "No se recibió puntuación"}), 400
|
||||||
|
|
||||||
|
puntos = int(data["puntuacion"])
|
||||||
|
entra = puntuacion_entra_en_top(puntos)
|
||||||
|
top10 = obtener_top10()
|
||||||
|
# Devolvemos el top10 actual (ANTES de guardar la nueva puntuación)
|
||||||
|
return jsonify({
|
||||||
|
"entra_en_top": entra,
|
||||||
|
"top10": top10
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/score/save", methods=["POST"])
|
||||||
|
def api_score_save():
|
||||||
|
"""
|
||||||
|
Recibe JSON: { "nombre": <string>, "puntuacion": <int> }
|
||||||
|
Guarda en la BD y devuelve el ranking actualizado.
|
||||||
|
"""
|
||||||
|
data = request.get_json()
|
||||||
|
if not data or "nombre" not in data or "puntuacion" not in data:
|
||||||
|
return jsonify({"error": "Faltan parámetros"}), 400
|
||||||
|
|
||||||
|
nombre = data["nombre"].strip()
|
||||||
|
puntos = int(data["puntuacion"])
|
||||||
|
# Sólo guardamos si efectivamente entra en top10 (por seguridad)
|
||||||
|
if puntuacion_entra_en_top(puntos):
|
||||||
|
guardar_puntuacion(nombre, puntos)
|
||||||
|
top10 = obtener_top10()
|
||||||
|
return jsonify({
|
||||||
|
"guardado": True,
|
||||||
|
"top10": top10
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
crear_tabla()
|
||||||
|
app.run(debug=True)
|
||||||
|
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
Flask==2.3.2
|
||||||
|
gunicorn==20.1.0
|
||||||
|
|
||||||
3
run.sh
Executable file
3
run.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
docker rm -f multiplicat-contenedor
|
||||||
|
docker build -t multiplicat-app .
|
||||||
|
docker run -d --name multiplicat-contenedor -p 5000:5000 multiplicat-app
|
||||||
10
static/css/styles.css
Normal file
10
static/css/styles.css
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
body {
|
||||||
|
background-color: #f8f9fa; /* gris claro */
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
#enunciado {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
186
static/js/game.js
Normal file
186
static/js/game.js
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
// static/js/game.js
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const TIEMPO_TOTAL = 60; // segundos
|
||||||
|
let segundosRestantes = TIEMPO_TOTAL;
|
||||||
|
let puntuacion = 0;
|
||||||
|
|
||||||
|
const barraTiempo = document.getElementById("barra-tiempo");
|
||||||
|
const tiempoTexto = document.getElementById("tiempo-texto");
|
||||||
|
const contadorAciertos = document.getElementById("contador-aciertos");
|
||||||
|
const enunciado = document.getElementById("enunciado");
|
||||||
|
const inputRespuesta = document.getElementById("input-respuesta");
|
||||||
|
const feedback = document.getElementById("feedback");
|
||||||
|
const botonFinal = document.getElementById("boton-final");
|
||||||
|
const verRankingBtn = document.getElementById("ver-ranking");
|
||||||
|
const jugarOtraBtn = document.getElementById("jugar-otra");
|
||||||
|
|
||||||
|
const formNombre = document.getElementById("form-nombre");
|
||||||
|
const inputNombre = document.getElementById("input-nombre");
|
||||||
|
const guardarNombreBtn = document.getElementById("guardar-nombre");
|
||||||
|
const mensajeGuardar = document.getElementById("mensaje-guardar");
|
||||||
|
|
||||||
|
let numeroA = 0;
|
||||||
|
let numeroB = 0;
|
||||||
|
let temporizadorInterval = null;
|
||||||
|
|
||||||
|
// Genera un par aleatorio (dos números entre 10 y 99)
|
||||||
|
function generarPregunta() {
|
||||||
|
numeroA = Math.floor(Math.random() * 90) + 10; // 10…99
|
||||||
|
numeroB = Math.floor(Math.random() * 90) + 10;
|
||||||
|
enunciado.textContent = `¿Cuánto es ${numeroA} × ${numeroB}?`;
|
||||||
|
inputRespuesta.value = "";
|
||||||
|
inputRespuesta.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicia el cronómetro y actualiza cada segundo
|
||||||
|
function iniciarTemporizador() {
|
||||||
|
barraTiempo.style.width = "100%";
|
||||||
|
tiempoTexto.textContent = `${segundosRestantes} segundos restantes`;
|
||||||
|
temporizadorInterval = setInterval(() => {
|
||||||
|
segundosRestantes--;
|
||||||
|
if (segundosRestantes < 0) {
|
||||||
|
clearInterval(temporizadorInterval);
|
||||||
|
terminarJuego();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Actualiza texto y barra de progreso
|
||||||
|
tiempoTexto.textContent = `${segundosRestantes} segundos restantes`;
|
||||||
|
const porcentaje = (segundosRestantes / TIEMPO_TOTAL) * 100;
|
||||||
|
barraTiempo.style.width = `${porcentaje}%`;
|
||||||
|
|
||||||
|
// Cambiar color de barra si queda poco tiempo
|
||||||
|
if (segundosRestantes <= 10) {
|
||||||
|
barraTiempo.classList.remove("bg-success");
|
||||||
|
barraTiempo.classList.add("bg-danger");
|
||||||
|
} else if (segundosRestantes <= 30) {
|
||||||
|
barraTiempo.classList.remove("bg-success");
|
||||||
|
barraTiempo.classList.add("bg-warning");
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Función que maneja el envío de la respuesta al presionar Enter
|
||||||
|
inputRespuesta.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter" && segundosRestantes > 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
validarRespuesta();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function validarRespuesta() {
|
||||||
|
const valor = parseInt(inputRespuesta.value);
|
||||||
|
const correcta = numeroA * numeroB;
|
||||||
|
if (!isNaN(valor)) {
|
||||||
|
if (valor === correcta) {
|
||||||
|
puntuacion++;
|
||||||
|
contadorAciertos.textContent = puntuacion;
|
||||||
|
feedback.innerHTML = `<span class="text-success">Correcto!</span>`;
|
||||||
|
} else {
|
||||||
|
feedback.innerHTML = `<span class="text-danger">Incorrecto. Era ${correcta}.</span>`;
|
||||||
|
}
|
||||||
|
// Mostrar feedback por 800ms y luego limpiarlo
|
||||||
|
setTimeout(() => {
|
||||||
|
feedback.textContent = "";
|
||||||
|
}, 800);
|
||||||
|
} else {
|
||||||
|
feedback.innerHTML = `<span class="text-warning">Ingresa un número válido.</span>`;
|
||||||
|
setTimeout(() => {
|
||||||
|
feedback.textContent = "";
|
||||||
|
}, 800);
|
||||||
|
}
|
||||||
|
generarPregunta();
|
||||||
|
}
|
||||||
|
|
||||||
|
function terminarJuego() {
|
||||||
|
// Deshabilitar input
|
||||||
|
inputRespuesta.disabled = true;
|
||||||
|
feedback.innerHTML = `
|
||||||
|
<div class="alert alert-info p-2">
|
||||||
|
¡Tiempo terminado! Tu puntuación es: <strong>${puntuacion}</strong>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Enviar puntuación al servidor
|
||||||
|
fetch('/api/score', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ puntuacion })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) {
|
||||||
|
feedback.innerHTML += `<div class="text-danger">Error: ${data.error}</div>`;
|
||||||
|
botonFinal.classList.remove("d-none");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.entra_en_top) {
|
||||||
|
// Muestra formulario para nombre
|
||||||
|
formNombre.classList.remove("d-none");
|
||||||
|
} else {
|
||||||
|
// No entra en top10: mostrar botón para ver ranking
|
||||||
|
botonFinal.classList.remove("d-none");
|
||||||
|
}
|
||||||
|
// Guarda el top10 actual en data.top10 para mostrar más tarde si hace falta
|
||||||
|
window.top10_actual = data.top10;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
feedback.innerHTML += `<div class="text-danger">Error conectando al servidor.</div>`;
|
||||||
|
botonFinal.classList.remove("d-none");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Botón “Ver Ranking” (si no entró en top 10)
|
||||||
|
verRankingBtn.addEventListener("click", () => {
|
||||||
|
// Redirigir a la página /ranking
|
||||||
|
window.location.href = "/ranking";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Botón “Jugar otra vez”
|
||||||
|
jugarOtraBtn.addEventListener("click", () => {
|
||||||
|
window.location.href = "/juego";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Al hacer clic en “Guardar” nombre (si entró en top 10)
|
||||||
|
guardarNombreBtn.addEventListener("click", () => {
|
||||||
|
const nombre = inputNombre.value.trim();
|
||||||
|
if (nombre.length === 0) {
|
||||||
|
mensajeGuardar.innerHTML = `<small class="text-danger">El nombre no puede estar vacío.</small>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Enviar nombre + puntuación al servidor
|
||||||
|
fetch('/api/score/save', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ nombre, puntuacion })
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.error) {
|
||||||
|
mensajeGuardar.innerHTML = `<small class="text-danger">Error: ${data.error}</small>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mensajeGuardar.innerHTML = `<div class="alert alert-success p-2">
|
||||||
|
¡Puntuación guardada! Puedes ver el ranking actualizado.</div>`;
|
||||||
|
// Opcional: mostrar tabla de top10 recibida en data.top10
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "/ranking";
|
||||||
|
}, 1500);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
mensajeGuardar.innerHTML = `<small class="text-danger">Error guardando el nombre.</small>`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Iniciar todo
|
||||||
|
function iniciarJuego() {
|
||||||
|
generarPregunta();
|
||||||
|
iniciarTemporizador();
|
||||||
|
inputRespuesta.disabled = false;
|
||||||
|
inputRespuesta.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Esperar 300ms para que el usuario vea la primera pregunta
|
||||||
|
setTimeout(iniciarJuego, 300);
|
||||||
|
});
|
||||||
|
|
||||||
52
templates/base.html
Normal file
52
templates/base.html
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="es">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
{% block head %}
|
||||||
|
<title>{% block title %}Juego de Multiplicaciones{% endblock %}</title>
|
||||||
|
<!-- Bootstrap 5 desde CDN -->
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
integrity="sha384-ENjdO4Dr2bkBIFxQpeo4Gaw416pGxYhbr2VdF+4E7RjzvRQZAZo+N91UZ7Jb8M+"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<!-- (Opcional) tu CSS personalizado -->
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
<!-- Barra de navegación sencilla -->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('index') }}">Multiplic-a-t</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||||
|
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav ms-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('juego') }}">Jugar</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('ranking') }}">Top 10</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="container">
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Scripts de Bootstrap 5 (Popper + JS) -->
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-q2gy50CFdE7mU2MDoKvxo6oREjY4FKRDvKoiL+KD8l1zsR4OrFvCDZXGuXtn0pjg"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
{% block scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
60
templates/game.html
Normal file
60
templates/game.html
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Jugar - Multiplic-a-t{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title mb-4 text-center">¡Pon a prueba tus multiplicaciones!</h3>
|
||||||
|
|
||||||
|
<!-- Temporizador: texto + barra de progreso -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<span id="tiempo-texto" class="fs-5">60 segundos restantes</span>
|
||||||
|
<div class="progress mt-1">
|
||||||
|
<div id="barra-tiempo" class="progress-bar bg-success" role="progressbar"
|
||||||
|
style="width: 100%" aria-valuenow="60" aria-valuemin="0" aria-valuemax="60">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contador de aciertos -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<span class="fs-6">Aciertos: <span id="contador-aciertos">0</span></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Enunciado de la multiplicación -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="input-respuesta" class="form-label fs-5" id="enunciado">¿Cuánto es 0 × 0?</label>
|
||||||
|
<input type="number" id="input-respuesta" class="form-control" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mensaje de feedback (correcto/incorrecto) -->
|
||||||
|
<div id="feedback" class="mb-3" style="min-height: 1.5em;"></div>
|
||||||
|
|
||||||
|
<!-- Botón oculto inicialmente, para reiniciar o ver ranking -->
|
||||||
|
<div id="boton-final" class="text-center mt-4 d-none">
|
||||||
|
<button id="ver-ranking" class="btn btn-primary">Ver Ranking</button>
|
||||||
|
<button id="jugar-otra" class="btn btn-secondary ms-2">Jugar otra vez</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formulario para que el usuario escriba nombre si entra en top 10 -->
|
||||||
|
<div id="form-nombre" class="mt-4 d-none">
|
||||||
|
<p class="mb-2">¡Felicidades! Entraste en el Top 10. Escribe tu nombre:</p>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="input-nombre" class="form-control" maxlength="20" />
|
||||||
|
<button id="guardar-nombre" class="btn btn-success">Guardar</button>
|
||||||
|
</div>
|
||||||
|
<div id="mensaje-guardar" class="mt-2"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<!-- Importar el archivo JS que contiene la lógica del juego -->
|
||||||
|
<script src="{{ url_for('static', filename='js/game.js') }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
16
templates/index.html
Normal file
16
templates/index.html
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Inicio - Multiplic-a-t{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="text-center py-5">
|
||||||
|
<h1 class="mb-3">Bienvenido a Multiplic-a-t</h1>
|
||||||
|
<p class="lead">
|
||||||
|
Tienes 1 minuto para resolver tantas multiplicaciones de dos cifras como puedas.
|
||||||
|
Escribe la respuesta en el campo de texto y presiona Enter.
|
||||||
|
Al terminar el minuto, podrás ver tu puntuación y, si entras en el top 10, registrar tu nombre.
|
||||||
|
</p>
|
||||||
|
<a href="{{ url_for('juego') }}" class="btn btn-lg btn-primary mt-4">Comenzar a jugar</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
38
templates/ranking.html
Normal file
38
templates/ranking.html
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}Top 10 - Multiplic-a-t{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h2>Top 10 de Puntuaciones</h2>
|
||||||
|
<a href="{{ url_for('juego') }}" class="btn btn-sm btn-primary mt-2">Jugar de nuevo</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% if top10 %}
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Puntuación</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{# Recorremos top10 sin enumerate; loop.index comienza en 1 #}
|
||||||
|
{% for par in top10 %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ loop.index }}</td>
|
||||||
|
<td>{{ par[0] }}</td> {# par[0] = nombre #}
|
||||||
|
<td>{{ par[1] }}</td> {# par[1] = puntuación #}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-center">Aún no hay puntuaciones.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
Loading…
Reference in a new issue