Add funcionamiento del juego
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/../includes/conexionBD.php';
|
||||
require_once __DIR__ . '/ClaseRespuestaAlmacenada.php';
|
||||
|
||||
/**
|
||||
* Lógica de la dinámica de juego (la usa juego.php a través de ControladorJuego.php):
|
||||
* - deja preparadas las 10 preguntas de la partida
|
||||
* - se las devuelve al jugador con sus respuestas SIN decir cuál es la correcta
|
||||
* - guarda la respuesta de cada jugador y le dice si ha acertado
|
||||
*
|
||||
* Todos los métodos devuelven un array con la clave 'error' si algo falla,
|
||||
* igual que el resto de clases del proyecto.
|
||||
*/
|
||||
class Juego
|
||||
{
|
||||
const NUM_PREGUNTAS = 10;
|
||||
|
||||
/**
|
||||
* Comprueba que el usuario pertenece a alguno de los equipos de la partida.
|
||||
* Devuelve true/false (o el array de error si falla la BD).
|
||||
*/
|
||||
public static function usuarioEnPartida($idPartida, $idUsuario)
|
||||
{
|
||||
$conexion = conexionBD();
|
||||
if (is_array($conexion)) {
|
||||
return $conexion;
|
||||
}
|
||||
|
||||
try {
|
||||
$consulta = $conexion->prepare("
|
||||
SELECT COUNT(*) AS total
|
||||
FROM equipoxpartida exp
|
||||
INNER JOIN equipoxusuario exu
|
||||
ON exu.idEquipo = exp.idEquipo
|
||||
WHERE exp.idPartida = :idPartida
|
||||
AND exu.idUsuario = :idUsuario
|
||||
");
|
||||
$consulta->execute([
|
||||
':idPartida' => $idPartida,
|
||||
':idUsuario' => $idUsuario
|
||||
]);
|
||||
return (int)$consulta->fetchColumn() > 0;
|
||||
} catch (PDOException $error) {
|
||||
return [
|
||||
'error' => 'Ocurrió un error al comprobar si perteneces a la partida.',
|
||||
'error_tecnico' => $error->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Si la partida todavía no tiene preguntas, le asigna 10 al azar.
|
||||
* Se hace dentro de una transacción con la fila de la partida bloqueada
|
||||
* para que, si varios jugadores entran a la vez, las preguntas se
|
||||
* asignen una sola vez.
|
||||
* Devuelve null si todo va bien, o un array de error.
|
||||
*/
|
||||
private static function asegurarPreguntas($conexion, $idPartida)
|
||||
{
|
||||
try {
|
||||
$conexion->beginTransaction();
|
||||
|
||||
$bloqueo = $conexion->prepare("
|
||||
SELECT idPartida FROM partida
|
||||
WHERE idPartida = :idPartida
|
||||
FOR UPDATE
|
||||
");
|
||||
$bloqueo->execute([':idPartida' => $idPartida]);
|
||||
|
||||
if (!$bloqueo->fetch()) {
|
||||
$conexion->rollBack();
|
||||
return ['error' => 'La partida no existe.'];
|
||||
}
|
||||
|
||||
$cuenta = $conexion->prepare("
|
||||
SELECT COUNT(*) FROM partidaxpregunta
|
||||
WHERE idPartida = :idPartida
|
||||
");
|
||||
$cuenta->execute([':idPartida' => $idPartida]);
|
||||
|
||||
if ((int)$cuenta->fetchColumn() === 0) {
|
||||
|
||||
// Solo preguntas que tengan alguna respuesta marcada como correcta
|
||||
$elegidas = $conexion->query("
|
||||
SELECT p.idPregunta
|
||||
FROM pregunta p
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM respuesta r
|
||||
WHERE r.idPregunta = p.idPregunta
|
||||
AND r.correcta = 1
|
||||
)
|
||||
ORDER BY RAND()
|
||||
LIMIT " . self::NUM_PREGUNTAS
|
||||
)->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$insertar = $conexion->prepare("
|
||||
INSERT INTO partidaxpregunta (idPartida, idPregunta)
|
||||
VALUES (:idPartida, :idPregunta)
|
||||
");
|
||||
foreach ($elegidas as $idPregunta) {
|
||||
$insertar->execute([
|
||||
':idPartida' => $idPartida,
|
||||
':idPregunta' => $idPregunta
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$conexion->commit();
|
||||
return null;
|
||||
} catch (PDOException $error) {
|
||||
if ($conexion->inTransaction()) {
|
||||
$conexion->rollBack();
|
||||
}
|
||||
return [
|
||||
'error' => 'Ocurrió un error al preparar las preguntas de la partida.',
|
||||
'error_tecnico' => $error->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuelve las preguntas de la partida (máximo 10) con sus respuestas
|
||||
* mezcladas. NO incluye el campo 'correcta' para que no se pueda hacer
|
||||
* trampa mirando la respuesta del servidor.
|
||||
* Cada pregunta indica si este usuario ya la ha respondido, para poder
|
||||
* retomar la partida si recarga la página.
|
||||
*/
|
||||
public static function cargarPreguntas($idPartida, $idUsuario)
|
||||
{
|
||||
$conexion = conexionBD();
|
||||
if (is_array($conexion)) {
|
||||
return $conexion;
|
||||
}
|
||||
|
||||
$error = self::asegurarPreguntas($conexion, $idPartida);
|
||||
if ($error !== null) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Las preguntas, siempre en el mismo orden para todos los jugadores
|
||||
$consulta = $conexion->prepare("
|
||||
SELECT
|
||||
pxp.idPartidaxPregunta,
|
||||
p.idPregunta,
|
||||
p.nombrePregunta
|
||||
FROM partidaxpregunta pxp
|
||||
INNER JOIN pregunta p
|
||||
ON p.idPregunta = pxp.idPregunta
|
||||
WHERE pxp.idPartida = :idPartida
|
||||
ORDER BY pxp.idPartidaxPregunta
|
||||
LIMIT " . self::NUM_PREGUNTAS
|
||||
);
|
||||
$consulta->execute([':idPartida' => $idPartida]);
|
||||
$preguntas = $consulta->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// 2. Las respuestas de todas esas preguntas, agrupadas por pregunta
|
||||
$consulta = $conexion->prepare("
|
||||
SELECT DISTINCT
|
||||
r.idRespuesta,
|
||||
r.idPregunta,
|
||||
r.nombreRespuesta
|
||||
FROM respuesta r
|
||||
INNER JOIN partidaxpregunta pxp
|
||||
ON pxp.idPregunta = r.idPregunta
|
||||
WHERE pxp.idPartida = :idPartida
|
||||
");
|
||||
$consulta->execute([':idPartida' => $idPartida]);
|
||||
|
||||
$respuestasPorPregunta = [];
|
||||
foreach ($consulta->fetchAll(PDO::FETCH_ASSOC) as $fila) {
|
||||
$respuestasPorPregunta[(int)$fila['idPregunta']][] = [
|
||||
'idRespuesta' => (int)$fila['idRespuesta'],
|
||||
'nombreRespuesta' => $fila['nombreRespuesta']
|
||||
];
|
||||
}
|
||||
|
||||
// 3. Qué preguntas ha respondido ya este usuario
|
||||
$consulta = $conexion->prepare("
|
||||
SELECT ra.idPartidaxPregunta
|
||||
FROM respuestaalmacenada ra
|
||||
INNER JOIN partidaxpregunta pxp
|
||||
ON pxp.idPartidaxPregunta = ra.idPartidaxPregunta
|
||||
WHERE pxp.idPartida = :idPartida
|
||||
AND ra.idUsuario = :idUsuario
|
||||
");
|
||||
$consulta->execute([
|
||||
':idPartida' => $idPartida,
|
||||
':idUsuario' => $idUsuario
|
||||
]);
|
||||
$respondidas = array_flip($consulta->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
// 4. Montamos la respuesta final
|
||||
$resultado = [];
|
||||
foreach ($preguntas as $pregunta) {
|
||||
$idPregunta = (int)$pregunta['idPregunta'];
|
||||
$idPxp = (int)$pregunta['idPartidaxPregunta'];
|
||||
|
||||
$respuestas = $respuestasPorPregunta[$idPregunta] ?? [];
|
||||
shuffle($respuestas);
|
||||
|
||||
$resultado[] = [
|
||||
'idPartidaxPregunta' => $idPxp,
|
||||
'idPregunta' => $idPregunta,
|
||||
'nombrePregunta' => $pregunta['nombrePregunta'],
|
||||
'respondida' => isset($respondidas[$idPxp]),
|
||||
'respuestas' => $respuestas
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'total' => count($resultado),
|
||||
'respondidas' => count($respondidas),
|
||||
'preguntas' => $resultado
|
||||
];
|
||||
} catch (PDOException $error) {
|
||||
return [
|
||||
'error' => 'Ocurrió un error al cargar las preguntas.',
|
||||
'error_tecnico' => $error->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarda la respuesta que ha elegido el usuario en una pregunta y
|
||||
* devuelve si ha acertado y cuál era la correcta (para el feedback).
|
||||
* Comprueba en el servidor que:
|
||||
* - la pregunta pertenece a la partida
|
||||
* - la respuesta pertenece a esa pregunta
|
||||
* - el usuario no ha respondido ya esa pregunta
|
||||
*/
|
||||
public static function registrarRespuesta($idPartida, $idUsuario, $idPartidaxPregunta, $idRespuesta)
|
||||
{
|
||||
$conexion = conexionBD();
|
||||
if (is_array($conexion)) {
|
||||
return $conexion;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. La pregunta tiene que ser de esta partida
|
||||
$consulta = $conexion->prepare("
|
||||
SELECT idPregunta
|
||||
FROM partidaxpregunta
|
||||
WHERE idPartidaxPregunta = :idPxp
|
||||
AND idPartida = :idPartida
|
||||
");
|
||||
$consulta->execute([
|
||||
':idPxp' => $idPartidaxPregunta,
|
||||
':idPartida' => $idPartida
|
||||
]);
|
||||
$idPregunta = $consulta->fetchColumn();
|
||||
|
||||
if ($idPregunta === false) {
|
||||
return ['error' => 'Esa pregunta no pertenece a esta partida.'];
|
||||
}
|
||||
|
||||
// 2. La respuesta tiene que ser una de las de esa pregunta
|
||||
$consulta = $conexion->prepare("
|
||||
SELECT idRespuesta, correcta
|
||||
FROM respuesta
|
||||
WHERE idPregunta = :idPregunta
|
||||
");
|
||||
$consulta->execute([':idPregunta' => $idPregunta]);
|
||||
|
||||
$elegida = null;
|
||||
$primeraCorrecta = null;
|
||||
foreach ($consulta->fetchAll(PDO::FETCH_ASSOC) as $fila) {
|
||||
if ((int)$fila['correcta'] === 1 && $primeraCorrecta === null) {
|
||||
$primeraCorrecta = (int)$fila['idRespuesta'];
|
||||
}
|
||||
if ((int)$fila['idRespuesta'] === $idRespuesta) {
|
||||
$elegida = $fila;
|
||||
}
|
||||
}
|
||||
|
||||
if ($elegida === null) {
|
||||
return ['error' => 'Esa respuesta no pertenece a la pregunta.'];
|
||||
}
|
||||
|
||||
// 3. Solo se puede responder una vez a cada pregunta
|
||||
$consulta = $conexion->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM respuestaalmacenada
|
||||
WHERE idPartidaxPregunta = :idPxp
|
||||
AND idUsuario = :idUsuario
|
||||
");
|
||||
$consulta->execute([
|
||||
':idPxp' => $idPartidaxPregunta,
|
||||
':idUsuario' => $idUsuario
|
||||
]);
|
||||
|
||||
if ((int)$consulta->fetchColumn() > 0) {
|
||||
return ['error' => 'Ya has respondido a esta pregunta.'];
|
||||
}
|
||||
|
||||
// 4. Guardamos la respuesta con la clase que ya existía
|
||||
$almacenada = new RespuestaAlmacenada(
|
||||
null,
|
||||
$idPartidaxPregunta,
|
||||
$idUsuario,
|
||||
$idRespuesta
|
||||
);
|
||||
$guardado = $almacenada->guardarRespuesta();
|
||||
|
||||
if (isset($guardado['error'])) {
|
||||
return $guardado;
|
||||
}
|
||||
|
||||
// 5. Progreso del usuario en la partida
|
||||
$consulta = $conexion->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM respuestaalmacenada ra
|
||||
INNER JOIN partidaxpregunta pxp
|
||||
ON pxp.idPartidaxPregunta = ra.idPartidaxPregunta
|
||||
WHERE pxp.idPartida = :idPartida
|
||||
AND ra.idUsuario = :idUsuario
|
||||
");
|
||||
$consulta->execute([
|
||||
':idPartida' => $idPartida,
|
||||
':idUsuario' => $idUsuario
|
||||
]);
|
||||
$respondidas = (int)$consulta->fetchColumn();
|
||||
|
||||
$consulta = $conexion->prepare("
|
||||
SELECT COUNT(*) FROM partidaxpregunta
|
||||
WHERE idPartida = :idPartida
|
||||
");
|
||||
$consulta->execute([':idPartida' => $idPartida]);
|
||||
$total = min((int)$consulta->fetchColumn(), self::NUM_PREGUNTAS);
|
||||
|
||||
$acierto = (int)$elegida['correcta'] === 1;
|
||||
|
||||
return [
|
||||
'exito' => 'Respuesta guardada.',
|
||||
'acierto' => $acierto,
|
||||
'idRespuestaCorrecta' => $acierto ? $idRespuesta : $primeraCorrecta,
|
||||
'respondidas' => $respondidas,
|
||||
'total' => $total,
|
||||
'terminada' => $respondidas >= $total
|
||||
];
|
||||
} catch (PDOException $error) {
|
||||
return [
|
||||
'error' => 'Ocurrió un error al guardar la respuesta.',
|
||||
'error_tecnico' => $error->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user