diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index b0b2da86..0760d423 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -7,27 +7,28 @@ import Modal from "react-bootstrap/Modal"; import Button from "react-bootstrap/Button"; import Spinner from 'react-bootstrap/Spinner'; import { useNavigate } from 'react-router-dom'; -import { useLocation } from 'react-router-dom'; import './game.css'; import "bootstrap/dist/css/bootstrap.min.css"; import { getNextQuestion } from '../../services/GameService'; +import { useConfig } from './GameConfigProvider'; /** * React component that represents a wichat game with his timer, question, image, * answers and chat with the LLM to ask for clues. * - * @param {Number} questionTime - The initial time in seconds to answer the question. - * @param {Array} answers - The array of answers with the text and if it is the correct answer. * @returns the hole game screen with the timer, question, image, answers and chat with the LLM. */ export const Game = () => { + // Game configuration + const { config } = useConfig(); const { t } = useTranslation(); const navigate = useNavigate(); - const location = useLocation(); - const questionTime = location.state?.questionTime || 120; // Get the question time from the location state or set it to 120 seconds by default + const questionTime = config.timePerRound; // Get the question time from the configuration + const numberOfQuestions = config.questions; // Get the number of questions from tge configuration + const topics = config.topics; // Get the topics from the configuration // State that stores the answers of the current question with the text and if it is the correct answer const [answers, setAnswers] = useState([]); @@ -47,6 +48,7 @@ export const Game = () => { // with attributes: topic, imageUrl, wasUserCorrect, selectedAnswer (text of the answer selected) // and answers (an array of objects with text and isCorrect) const [questionResults, setQuestionResults] = useState([]); + const [numberOfQuestionsAnswered, setNumberOfQuestionsAnswered] = useState(0); const onTimeUp = () => { blockAnswerButtons(); @@ -57,7 +59,12 @@ export const Game = () => { const askForNextQuestion = () => { prepareUIForNextQuestion(); + setNumberOfQuestionsAnswered(numberOfQuestionsAnswered + 1); + if (numberOfQuestionsAnswered === numberOfQuestions) { + navigate('/game-results'); // TODO: Send game info to the results page + return; + } getNextQuestion().then((questionInfo) => { setIsLoading(false); setQuestion(questionInfo.question); diff --git a/webapp/src/components/game/GameConfigProvider.js b/webapp/src/components/game/GameConfigProvider.js new file mode 100644 index 00000000..43daa857 --- /dev/null +++ b/webapp/src/components/game/GameConfigProvider.js @@ -0,0 +1,43 @@ +import { Component } from "react"; +import { createContext, useContext, useState, useEffect } from "react"; + +const ConfigContext = createContext(); + +const defaultConfig = { questions: 30, timePerRound: 120, topics: [] }; + +/** + * Provider for the game configuration, it will store the configuration in the local storage + * + * @param {Component} children Component that will be wrapped by the provider + * @returns + */ +export const GameConfigProvider = ({ children }) => { + + // State to store the configuration + // its initial value is the value stored in the local storage + const [config, setConfig] = useState(() => { + const savedConfig = localStorage.getItem("gameConfig"); + return savedConfig ? JSON.parse(savedConfig) : defaultConfig; + }); + + // Save the configuration in the local storage when it changes + useEffect(() => { + localStorage.setItem("gameConfig", JSON.stringify(config)); + }, [config]); + + // Function to reset the configuration to the default values + const resetConfig = () => { + setConfig(defaultConfig); + localStorage.setItem("gameConfig", JSON.stringify(defaultConfig)); + } + + return ( + + {children} + + ); +}; + +export const useConfig = () => { + return useContext(ConfigContext); +}; \ No newline at end of file diff --git a/webapp/src/components/home/Configuration.js b/webapp/src/components/home/Configuration.js index eea0a33d..5f2b5e6a 100644 --- a/webapp/src/components/home/Configuration.js +++ b/webapp/src/components/home/Configuration.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { Button, Dropdown } from "react-bootstrap"; import "bootstrap/dist/css/bootstrap.min.css"; import { GoXCircle } from "react-icons/go"; @@ -7,29 +7,59 @@ import "./configuration.css"; import ToggleButton from "react-bootstrap/ToggleButton"; import ToggleButtonGroup from "react-bootstrap/ToggleButtonGroup"; import { useNavigate } from "react-router-dom"; +import { useConfig } from "../game/GameConfigProvider"; const Configuration = ({ onClose }) => { + + // Constant to store the configuration of the game + const { config, setConfig, resetConfig } = useConfig(); + const [questions, setQuestions] = useState(30); const [time, setTime] = useState(120); const { t } = useTranslation(); - const [selectedButtons, setSelectedButtons] = useState([]); + const [selectedButtons, setSelectedButtons] = useState([]); + const [topics, setTopics] = useState([]); + + const topicList = ["history", "science", "art", "sport", "geography"]; + + const navigate = useNavigate(); const handleClose = () => { + resetConfig(); onClose(); }; - const navigate = useNavigate(); + // Function to update the numberOfQuestionsSelected + const handleQuestionsChange = (value) => { + setQuestions(value); + setConfig((prevConfig) => ({ ...prevConfig, questions: value })); + }; + + // Function to update the timePerRound + const handleTimeChange = (value) => { + setTime(value); + setConfig((prevConfig) => ({ ...prevConfig, timePerRound: value })); + }; const handleButtonClick = (value) => { setSelectedButtons((prevSelected) => prevSelected.includes(value) - ? prevSelected.filter((item) => item !== value) - : [...prevSelected, value] + ? prevSelected.filter((item) => item !== value) + : [...prevSelected, value] ); + const topic = topicList[value - 1]; + + // Adds or removes the topic from the list of topics + const updatedTopics = selectedButtons.includes(value) + ? topics.filter((item) => item !== topic) // If value is in topics, remove it + : [...topics, topic]; // If value is not in topics, add it + + setTopics(updatedTopics); + setConfig((prevConfig) => ({ ...prevConfig, topics: updatedTopics })); }; const startGame = () => { - navigate('/game', { state: { questionTime: time }}) + navigate('/game', { state: { questionTime: time } }) } return ( @@ -39,7 +69,7 @@ const Configuration = ({ onClose }) => {

{t("title-configuration")}

- setQuestions(Number(value))}> + handleQuestionsChange(Number(value))}> {questions} 10 @@ -50,7 +80,7 @@ const Configuration = ({ onClose }) => {
- setTime(Number(value))}> + handleTimeChange(Number(value))}> {time}s 60s diff --git a/webapp/src/components/home/Home.js b/webapp/src/components/home/Home.js index 54d6a224..e0dce542 100644 --- a/webapp/src/components/home/Home.js +++ b/webapp/src/components/home/Home.js @@ -3,12 +3,14 @@ import Button from "react-bootstrap/Button"; import Configuration from "./Configuration"; import { useTranslation } from "react-i18next"; import NavBar from "../NavBar"; +import { GameConfigProvider } from '../game/GameConfigProvider'; import './home.css'; export const Home = () => { const [showConfig, setShowConfig] = useState(false); const { t } = useTranslation(); + return (
{/* NavBar */} @@ -28,7 +30,11 @@ export const Home = () => {
- {showConfig && setShowConfig(false)} />} + {showConfig && + {/* Key to force re-render and configurate again */} + setShowConfig(false)} /> + + }
); }; \ No newline at end of file diff --git a/webapp/src/components/routers/AppRouter.js b/webapp/src/components/routers/AppRouter.js index 1a409970..e22092a3 100644 --- a/webapp/src/components/routers/AppRouter.js +++ b/webapp/src/components/routers/AppRouter.js @@ -6,128 +6,129 @@ import { Login } from '../Login'; import { AddUser } from '../AddUser'; import { Navigate, createBrowserRouter } from 'react-router-dom'; import { AuthRoute } from './AuthRoute'; +import { GameConfigProvider } from '../game/GameConfigProvider'; const answers = [ - { - text: "React", - isCorrect: true - }, - { - text: "Angular", - isCorrect: false - }, - { - text: "Vue", - isCorrect: false - }, - { - text: "Svelte", - isCorrect: false - } - ]; - - const question = { - text: "¿Qué librería web es esta?", - image: "/logo512.png" - }; - - const gameHistory = [ - { - "points": 1450, - "correctAnswers": 18, - "totalQuestions": 30, - "date": "1/02/25", //Ojo con el formato de la fecha - "questions": [ - { - "topic": "Tecnología", - "imageUrl": "/logo512.png", - "answers": [ - { "text": "Respuesta 1", "isCorrect": false }, - { "text": "Respuesta 2", "isCorrect": true }, - { "text": "Respuesta 3", "isCorrect": false }, - { "text": "Respuesta 4", "isCorrect": false } - ] - }, - { - "topic": "Tecnología", - "imageUrl": "/logo512.png", - "answers": [ - { "text": "Respuesta A", "isCorrect": false }, - { "text": "Respuesta B", "isCorrect": false }, - { "text": "Respuesta C", "isCorrect": true }, - { "text": "Respuesta D", "isCorrect": false } - ] - } - ] - }, - { - "points": 1250, - "correctAnswers": 14, - "totalQuestions": 25, - "date": "12/02/25", - "questions": [ - { - "topic": "Tecnología", - "imageUrl": "/logo512.png", - "answers": [ - { "text": "Opción 1", "isCorrect": true }, - { "text": "Opción 2", "isCorrect": false }, - { "text": "Opción 3", "isCorrect": false }, - { "text": "Opción 4", "isCorrect": false } - ] - } - ] - } - ]; - - const questions = [ - { - "topic": "Tecnología", - "imageUrl": "/logo512.png", - "answers": [ - { "text": "Respuesta 1", "isCorrect": false }, - { "text": "Respuesta 2", "isCorrect": true }, - { "text": "Respuesta 3", "isCorrect": false }, - { "text": "Respuesta 4", "isCorrect": false } - ] - }, - { - "topic": "Tecnología", - "imageUrl": "/logo512.png", - "answers": [ - { "text": "Respuesta A", "isCorrect": false }, - { "text": "Respuesta B", "isCorrect": false }, - { "text": "Respuesta C", "isCorrect": true }, - { "text": "Respuesta D", "isCorrect": false } - ] - } - ] + { + text: "React", + isCorrect: true + }, + { + text: "Angular", + isCorrect: false + }, + { + text: "Vue", + isCorrect: false + }, + { + text: "Svelte", + isCorrect: false + } +]; + +const question = { + text: "¿Qué librería web es esta?", + image: "/logo512.png" +}; + +const gameHistory = [ + { + "points": 1450, + "correctAnswers": 18, + "totalQuestions": 30, + "date": "1/02/25", //Ojo con el formato de la fecha + "questions": [ + { + "topic": "Tecnología", + "imageUrl": "/logo512.png", + "answers": [ + { "text": "Respuesta 1", "isCorrect": false }, + { "text": "Respuesta 2", "isCorrect": true }, + { "text": "Respuesta 3", "isCorrect": false }, + { "text": "Respuesta 4", "isCorrect": false } + ] + }, + { + "topic": "Tecnología", + "imageUrl": "/logo512.png", + "answers": [ + { "text": "Respuesta A", "isCorrect": false }, + { "text": "Respuesta B", "isCorrect": false }, + { "text": "Respuesta C", "isCorrect": true }, + { "text": "Respuesta D", "isCorrect": false } + ] + } + ] + }, + { + "points": 1250, + "correctAnswers": 14, + "totalQuestions": 25, + "date": "12/02/25", + "questions": [ + { + "topic": "Tecnología", + "imageUrl": "/logo512.png", + "answers": [ + { "text": "Opción 1", "isCorrect": true }, + { "text": "Opción 2", "isCorrect": false }, + { "text": "Opción 3", "isCorrect": false }, + { "text": "Opción 4", "isCorrect": false } + ] + } + ] + } +]; + +const questions = [ + { + "topic": "Tecnología", + "imageUrl": "/logo512.png", + "answers": [ + { "text": "Respuesta 1", "isCorrect": false }, + { "text": "Respuesta 2", "isCorrect": true }, + { "text": "Respuesta 3", "isCorrect": false }, + { "text": "Respuesta 4", "isCorrect": false } + ] + }, + { + "topic": "Tecnología", + "imageUrl": "/logo512.png", + "answers": [ + { "text": "Respuesta A", "isCorrect": false }, + { "text": "Respuesta B", "isCorrect": false }, + { "text": "Respuesta C", "isCorrect": true }, + { "text": "Respuesta D", "isCorrect": false } + ] + } +] const router = createBrowserRouter([ - { - path: '/', - element: - }, - { - path: '/game', - element: - }, - { - path: '/user', - element: - }, - { - path: '/game/results', - element: - }, - { - path: '/login', - element: - }, - { - path: '/addUser', - element: - } + { + path: '/', + element: + }, + { + path: '/game', + element: + }, + { + path: '/user', + element: + }, + { + path: '/game/results', + element: + }, + { + path: '/login', + element: + }, + { + path: '/addUser', + element: + } ]); export default router; \ No newline at end of file