From 4c10201a62da8c714fceaaa1b533aa4bfc09af66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Fern=C3=A1ndez=20Noriega?= Date: Sat, 30 Mar 2024 13:48:46 +0100 Subject: [PATCH] Refactored question service and added new category: 'Guess the city' --- gatewayservice/gateway-service.js | 18 ++++- questionservice/question-service.js | 109 +++++++++++++++++----------- webapp/src/components/Game.jsx | 27 +++++-- webapp/src/components/Question.jsx | 14 ++-- 4 files changed, 106 insertions(+), 62 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 7ed1e6c..0a1dfe5 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -43,21 +43,31 @@ app.post('/adduser', async (req, res) => { } }); -app.get('/flags/question', async (req, res) => { +app.get('/imgs/flags/question', async (req, res) => { try { // Forward the request to the question service - const questionResponse = await axios.get(questionServiceUrl+'/flags/question', req.body); + const questionResponse = await axios.get(questionServiceUrl+'/imgs/flags/question', req.body); res.json(questionResponse.data); } catch (error) { res.status(error.response.status).json({ error: error.response.data.error }); } }); -app.post('/flags/answer', async (req, res) => { +app.get('/imgs/cities/question', async (req, res) => { + try { + // Forward the request to the question service + const questionResponse = await axios.get(questionServiceUrl+'/imgs/cities/question', req.body); + res.json(questionResponse.data); + } catch (error) { + res.status(error.response.status).json({ error: error.response.data.error }); + } +}); + +app.post('/imgs/answer', async (req, res) => { try { const answer = req.body.answer; // Forward the request to the question service - const questionResponse = await axios.post(questionServiceUrl+'/flags/answer', answer, { headers: {'Content-Type': 'text/plain'} }); + const questionResponse = await axios.post(questionServiceUrl+'/imgs/answer', answer, { headers: {'Content-Type': 'text/plain'} }); res.json(questionResponse.data); } catch (error) { res.status(error.response.status).json({ error: error.response.data.error }); diff --git a/questionservice/question-service.js b/questionservice/question-service.js index ec9357f..49a7f41 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -10,37 +10,36 @@ const port = 8010; app.use(express.static('public')); app.use(express.text()); -//Correct image -var correctAnswerFlag -//Associates flags with their countries -var flagToCountryMap = new Map() +var correctImg +var imgToAssociatedMap = new Map() class WIQ_API{ /** + * Extracts from wikidata images and their associates, then selects 4 images and one of + * their associates for the question so the question is constructed with it as the target + * for the answer. * - * @returns JSON with the question and the flags + * @param {string} query - SPARQL query for wikidata that has to use + * an 'image' variable and an 'itemLabel' variable, respectively containing + * the image urls and the name of the associated entities (For example, flags and countries) + * @param {string} imgTypeName - Name of what the images represent + * @param {string} relation - Relation of the images with the question associated element + * @returns - A JSON with the question (question) and the images (images) */ - async getQuestionAndCountryFlags() { + async getQuestionAndImages(query, imgTypeName, relation) { //Reset the map for the new question - flagToCountryMap = new Map() + imgToAssociatedMap = new Map() - //Num of fetched countries - const countriesNum = 100 + //Num of fetched items + const itemsNum = 100 //Required by wikidata to accept the request const headers = new Headers(); headers.append('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' +' AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'); - const sparql = `SELECT ?país ?paísLabel ?imagen_de_la_bandera WHERE { - SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". } - ?país wdt:P31 wd:Q6256. - OPTIONAL { ?país wdt:P41 ?imagen_de_la_bandera. } - } - LIMIT ${countriesNum}` - //Constructing the url for the wikidata request - var url = wbk.sparqlQuery(sparql); + var url = wbk.sparqlQuery(query); const response = await fetch(url, { headers }); const data = await response.json() @@ -49,33 +48,29 @@ class WIQ_API{ const numOfChosen = 4 // Generate n random numbers for (let i = 0; i < numOfChosen; i++) { - this.#getRandomNumNotInSetAndUpdate(countriesNum, chosenNums) + this.#getRandomNumNotInSetAndUpdate(itemsNum, chosenNums) } - const countries = [] + const associates = [] const imgs = [] for(var i=0;i { + //Gets flag images and their associated country names + const query = `SELECT ?item ?itemLabel ?image WHERE + { + SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". } + ?item wdt:P31 wd:Q6256; + wdt:P41 ?image. + FILTER NOT EXISTS { ?item wdt:P41 wd:Q3024110 } + }` + const question = JSON.parse(await wiq.getQuestionAndImages(query,"flags","belongs to")); + res.json(question); +}); + +/** + * Returns the needed information to construct a question of the form + * "Which of the following images corresponds to xCity?" with 4 options * @param {} req - Not used - * @param {Object} res - Contains the question (question) and the images of the flags (flags) + * @param {Object} res - Contains the question (question) and the cities (images) */ -app.get('/flags/question', async (req, res) => { - const question = JSON.parse(await wiq.getQuestionAndCountryFlags()); +app.get('/imgs/cities/question', async (req, res) => { + //Gets city images and their associated names + const query = `SELECT ?item ?itemLabel ?image WHERE + { + SERVICE wikibase:label { bd:serviceParam wikibase:language "[AUTO_LANGUAGE],en". } + ?item wdt:P31 wd:Q515; + wdt:P18 ?image. + FILTER NOT EXISTS { ?item wdt:P41 wd:Q3024110. } + } + LIMIT 100` + const question = JSON.parse(await wiq.getQuestionAndImages(query,"images","corresponds to")); res.json(question); }); /** - * Gets a response indicating if the chosen flag img was correct or not - * @param {string} req - Flag img url selected by the player + * Gets a response indicating if the chosen img was correct or not + * @param {string} req - img url selected by the player * @param {Object} res - JSON containing whether the answer was correct "true" * or not "false". In case it was incorrect, the chosen - * country will be returned as well + * associate will be returned as well */ -app.post('/flags/answer', (req, res) => { +app.post('/imgs/answer', (req, res) => { const answer = req.body; - if(correctAnswerFlag==answer){ + if(correctImg==answer){ res.json({ correct: "true" }) } else { res.json({ correct: "false", - country: `${flagToCountryMap.get(answer)}` + country: `${imgToAssociatedMap.get(answer)}` }) } }); diff --git a/webapp/src/components/Game.jsx b/webapp/src/components/Game.jsx index e19e95b..c39df49 100644 --- a/webapp/src/components/Game.jsx +++ b/webapp/src/components/Game.jsx @@ -5,12 +5,16 @@ import useAuthUser from "react-auth-kit/hooks/useAuthUser"; import Question from "./Question"; const Game = () => { - const [gameStarted, setGameStarted] = useState(false); + const [flagGameStarted, setFlagGameStarted] = useState(false); + const [cityGameStarted, setCityGameStarted] = useState(false); const isAuthenticated = useIsAuthenticated(); const navigate = useNavigate(); const auth = useAuthUser(); - const startGame = () => { - setGameStarted(!gameStarted); + const startFlagsGame = () => { + setFlagGameStarted(!flagGameStarted); + }; + const startCitiesGame = () => { + setCityGameStarted(!cityGameStarted); }; useEffect(() => { if (!isAuthenticated()) { @@ -20,17 +24,24 @@ const Game = () => { return (
- {isAuthenticated()?gameStarted ? ( - + {isAuthenticated() && (flagGameStarted || cityGameStarted) ?( +
+ {flagGameStarted && } + {cityGameStarted && } +
) : (

{auth.username}, Let's Play!

- +
- ):""} + )}
) }; diff --git a/webapp/src/components/Question.jsx b/webapp/src/components/Question.jsx index 7eb579f..022f264 100644 --- a/webapp/src/components/Question.jsx +++ b/webapp/src/components/Question.jsx @@ -1,14 +1,14 @@ import React, { useState, useEffect } from "react"; import axios from "axios"; -const Question = () => { +const Question = (props) => { const apiEndpoint = process.env.REACT_APP_API_ENDPOINT ||'http://localhost:8000'; const [question, setQuestion] = useState([]); const [loading, setLoading] = useState(true); const fetchQuestion = async () => { try { - const res = await axios.get(`${apiEndpoint}/flags/question`); + const res = await axios.get(`${apiEndpoint}/${props.type}/${props.category}/question`); setQuestion(res.data); setLoading(false); } catch (error) { @@ -19,8 +19,8 @@ const Question = () => { const answerQuestion = async (answer) => { try { setLoading(true); - const result = await axios.post(`${apiEndpoint}/flags/answer`, {answer}); - const res = await axios.get(`${apiEndpoint}/flags/question`); + const result = await axios.post(`${apiEndpoint}/${props.type}/answer`, {answer}); + const res = await axios.get(`${apiEndpoint}/${props.type}/${props.category}/question`); setQuestion(res.data); setLoading(false); @@ -41,11 +41,11 @@ const Question = () => { <>

{question.question}

- {question.flags.map( flag => ( + {question.images.map( image => (
))}