349 lines
12 KiB
PHP
349 lines
12 KiB
PHP
<?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()
|
|
];
|
|
}
|
|
}
|
|
}
|