From 1e883746dc8bf6fbccb07e7299fd459fce03aa9e Mon Sep 17 00:00:00 2001 From: uo293758 Date: Sun, 9 Mar 2025 21:00:36 +0100 Subject: [PATCH 1/2] =?UTF-8?q?Implementaci=C3=B3n=20de=20guardado=20de=20?= =?UTF-8?q?configuraci=C3=B3n=20de=20partida=20en=20componente=20GameConfi?= =?UTF-8?q?gProvider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/game/GameConfigProvider.js | 39 +++++++++++++++ webapp/src/components/home/Configuration.js | 48 ++++++++++++++++--- webapp/src/components/home/Home.js | 8 +++- webapp/src/components/routers/AppRouter.js | 1 + 4 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 webapp/src/components/game/GameConfigProvider.js diff --git a/webapp/src/components/game/GameConfigProvider.js b/webapp/src/components/game/GameConfigProvider.js new file mode 100644 index 00000000..87c4208c --- /dev/null +++ b/webapp/src/components/game/GameConfigProvider.js @@ -0,0 +1,39 @@ +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]); + + const resetConfig = () => { + setConfig(defaultConfig); + localStorage.setItem("gameConfig", JSON.stringify(defaultConfig)); + } + + return ( + + {children} + + ); +}; + +export const useConfig = () => 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..f39ca2d6 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,63 @@ 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 handleClose = () => { + resetConfig(); onClose(); }; + // 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 navigate = useNavigate(); 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 })); }; + useEffect(() => { + console.log(config); + }, [config]); + const startGame = () => { - navigate('/game', { state: { questionTime: time }}) + navigate('/game', { state: { questionTime: time } }) } return ( @@ -39,7 +73,7 @@ const Configuration = ({ onClose }) => {

{t("title-configuration")}

- setQuestions(Number(value))}> + handleQuestionsChange(Number(value))}> {questions} 10 @@ -50,7 +84,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..0f797219 100644 --- a/webapp/src/components/routers/AppRouter.js +++ b/webapp/src/components/routers/AppRouter.js @@ -6,6 +6,7 @@ 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 = [ { From 1a7c1fd97aaf2e6636ac440e926f8b3e02e8a81b Mon Sep 17 00:00:00 2001 From: uo293758 Date: Sun, 9 Mar 2025 21:14:05 +0100 Subject: [PATCH 2/2] =?UTF-8?q?Partida=20recibe=20configuraci=C3=B3n=20des?= =?UTF-8?q?de=20el=20componente=20Configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gracias al componente wrapper GameConfigProvider, el componente Configuration puede enviar la configuración de la partida al componente Game. Para ello, el componente Configuration debe enviar la configuración de la partida al componente wrapper GameConfigProvider, que a su vez la enviará al componente Game. --- webapp/src/components/game/Game.js | 17 +- .../src/components/game/GameConfigProvider.js | 6 +- webapp/src/components/home/Configuration.js | 8 +- webapp/src/components/routers/AppRouter.js | 236 +++++++++--------- 4 files changed, 137 insertions(+), 130 deletions(-) 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 index 87c4208c..43daa857 100644 --- a/webapp/src/components/game/GameConfigProvider.js +++ b/webapp/src/components/game/GameConfigProvider.js @@ -12,6 +12,7 @@ const defaultConfig = { questions: 30, timePerRound: 120, topics: [] }; * @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(() => { @@ -24,6 +25,7 @@ export const GameConfigProvider = ({ children }) => { 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)); @@ -36,4 +38,6 @@ export const GameConfigProvider = ({ children }) => { ); }; -export const useConfig = () => useContext(ConfigContext); \ No newline at end of file +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 f39ca2d6..5f2b5e6a 100644 --- a/webapp/src/components/home/Configuration.js +++ b/webapp/src/components/home/Configuration.js @@ -22,6 +22,8 @@ const Configuration = ({ onClose }) => { const topicList = ["history", "science", "art", "sport", "geography"]; + const navigate = useNavigate(); + const handleClose = () => { resetConfig(); onClose(); @@ -39,8 +41,6 @@ const Configuration = ({ onClose }) => { setConfig((prevConfig) => ({ ...prevConfig, timePerRound: value })); }; - const navigate = useNavigate(); - const handleButtonClick = (value) => { setSelectedButtons((prevSelected) => prevSelected.includes(value) @@ -58,10 +58,6 @@ const Configuration = ({ onClose }) => { setConfig((prevConfig) => ({ ...prevConfig, topics: updatedTopics })); }; - useEffect(() => { - console.log(config); - }, [config]); - const startGame = () => { navigate('/game', { state: { questionTime: time } }) } diff --git a/webapp/src/components/routers/AppRouter.js b/webapp/src/components/routers/AppRouter.js index 0f797219..e22092a3 100644 --- a/webapp/src/components/routers/AppRouter.js +++ b/webapp/src/components/routers/AppRouter.js @@ -9,126 +9,126 @@ 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