diff --git a/truthinquiry/logic/game_logic.py b/truthinquiry/logic/game_logic.py index 2504b21..34bcf7e 100644 --- a/truthinquiry/logic/game_logic.py +++ b/truthinquiry/logic/game_logic.py @@ -226,6 +226,26 @@ def check_username(username: str) -> bool: return True +def check_game_id(game_id: str) -> bool: + """ + Check if a game_id is valid using a set of rules + + :param game_id: the game_id to check + :return: True or False depending on if the rules are respected + """ + + if not game_id: + return False + if not game_id.isalnum(): + return False + if not game_id == game_id.strip(): + return False + if not len(game_id) == 6: + return False + + return True + + def generate_npc_text(npc: Npc, lang: str) -> dict: """ diff --git a/truthinquiry/routes/routes_api.py b/truthinquiry/routes/routes_api.py index 9b7282f..49a7b1f 100644 --- a/truthinquiry/routes/routes_api.py +++ b/truthinquiry/routes/routes_api.py @@ -55,6 +55,8 @@ def create_game(): @routes_api.route("/getGameMembers", methods=["GET", "POST"]) def get_members(): game_id = flask.request.values.get("game_id") + if not game_logic.check_game_id(game_id): + return {"error": 1, "msg": "invalid game_id"} game = game_logic.get_game(game_id) if game is None: return {"error": 1, "msg": "this game doesn't exist"} @@ -66,6 +68,8 @@ def get_members(): @routes_api.route("/joinGame", methods=["GET", "POST"]) def join_game(): game_id = flask.request.values.get("game_id") + if not game_logic.check_game_id(game_id): + return {"error": 1, "msg": "invalid game_id"} username = flask.request.values.get("username") if game_id is None or username is None: return {"error": 1, "msg": "username or game id not set"} @@ -91,7 +95,10 @@ def join_game(): def is_owner(): if not flask.session: return {"error": 0, "owner": False} - game = game_logic.get_game(flask.session["game_id"]) + game_id = flask.session["game_id"] + if not game_logic.check_game_id(game_id): + return {"error": 1, "msg": "invalid game_id"} + game = game_logic.get_game(game_id) if game is None: return {"error": 0, "owner": False} @@ -104,7 +111,10 @@ def is_owner(): def has_joined(): if not flask.session: return {"error": 0, "joined": False} - game = game_logic.get_game(flask.session["game_id"]) + game_id = flask.session["game_id"] + if not game_logic.check_game_id(game_id): + return {"error": 1, "msg": "invalid game_id"} + game = game_logic.get_game(game_id) if game is None: return {"error": 0, "joined": False} return {"error": 0, "joined": True} @@ -115,7 +125,10 @@ def start_game(): return {"error": 1, "msg": "No session"} if not flask.session["is_owner"]: return {"error": 1, "msg": "you are not the owner of this game"} - game = game_logic.get_game(flask.session["game_id"]) + game_id = flask.session["game_id"] + if not game_logic.check_game_id(game_id): + return {"error": 1, "msg": "invalid game_id"} + game = game_logic.get_game(game_id) if game is None: return {"error": 1, "msg": "this game doesn't exist"} if game.has_started: @@ -129,7 +142,10 @@ def start_game(): def get_data(): if not flask.session: return {"error": 1, "msg": "No session"} - game = game_logic.get_game(flask.session["game_id"]) + game_id = flask.session["game_id"] + if not game_logic.check_game_id(game_id): + return {"error": 1, "msg": "invalid game_id"} + game = game_logic.get_game(game_id) if game is None: return {"error": 1, "msg": "this game doesn't exist"} @@ -159,7 +175,10 @@ def get_npc_reaction(): if not flask.session: return {"error": 1, "msg": "No session"} - game = game_logic.get_game(flask.session["game_id"]) + game_id = flask.session["game_id"] + if not game_logic.check_game_id(game_id): + return {"error": 1, "msg": "invalid game_id"} + game = game_logic.get_game(game_id) if game is None: return {"error": 1, "msg": "this game doesn't exist"} npc_id = flask.request.values.get("npcid") @@ -193,7 +212,10 @@ def get_reaction(): def game_progress(): if not flask.session: return {"error": 1, "msg": "No session"} - game = game_logic.get_game(flask.session["game_id"]) + game_id = flask.session["game_id"] + if not game_logic.check_game_id(game_id): + return {"error": 1, "msg": "invalid game_id"} + game = game_logic.get_game(game_id) if game is None: return {"error": 1, "msg": "this game doesn't exist"} @@ -210,7 +232,10 @@ def game_progress(): def check_anwser(): if not flask.session: return {"error": 1, "msg": "No session"} - game = game_logic.get_game(flask.session["game_id"]) + game_id = flask.session["game_id"] + if not game_logic.check_game_id(game_id): + return {"error": 1, "msg": "invalid game_id"} + game = game_logic.get_game(game_id) if game is None: return {"error": 1, "msg": "this game doesn't exist"} diff --git a/truthinquiry/static/css/game_ui.css b/truthinquiry/static/css/game_ui.css index fd41bad..c951d4a 100644 --- a/truthinquiry/static/css/game_ui.css +++ b/truthinquiry/static/css/game_ui.css @@ -31,10 +31,9 @@ /* Colors */ color-scheme: dark; --alert-dialog-background-color: #000000DF; - --button-links-gray-color: #939393; --dark-theme-background-color: #213C40; --game-black: #000000; - --game-blue: #0096FF; + --game-blue: #7DDCFF; --game-green: #008000; --game-grey: #5A5656; --game-red: #BD1E1E; @@ -54,6 +53,10 @@ body { margin: var(--body-margin); } +noscript .alert_dialog_background, noscript .alert_dialog_msg, noscript .alert_dialog_title { + display: block; +} + .action_button { background-color: var(--game-red); border-color: var(--game-black); @@ -100,21 +103,23 @@ body { /* Footer */ .footer_link { color: var(--game-white); - font-family: sans-serif; + font-family: "Roboto Mono", sans-serif; font-size: 1em; font-weight: bold; margin: 0.5em; + text-align: center; transition: color 0.25s; } .footer_link:hover { - color: var(--button-links-gray-color); + color: var(--game-black); } .footer_links { + align-items: center; + align-self: flex-end; display: flex; flex-direction: row; - flex-wrap: wrap; height: var(--footer-links-height); justify-content: flex-end; } @@ -130,6 +135,8 @@ body { /* Alert dialogs */ .alert_dialog { + background-color: var(--game-grey); + border-color: var(--game-white); border-radius: var(--button-and-dialog-border-radius); display: none; font-family: "Spicy Rice", serif; @@ -137,6 +144,7 @@ body { flex-wrap: nowrap; justify-content: center; left: 50%; + overflow: auto; padding: 2em; position: absolute; top: 50%; @@ -149,6 +157,8 @@ body { } .alert_dialog_title { + margin-bottom: 0.5em; + margin-top: 0.5em; font-size: 2em; font-weight: normal; text-align: center; diff --git a/truthinquiry/static/css/game_ui_game.css b/truthinquiry/static/css/game_ui_game.css index 53d33ba..f0182a6 100644 --- a/truthinquiry/static/css/game_ui_game.css +++ b/truthinquiry/static/css/game_ui_game.css @@ -81,6 +81,10 @@ html { margin: 0.25em; } +.home_button, .next_btn, .suspects { + padding: 0; +} + .home_button, .next_btn, .suspect_emotion_chooser { border: none; } @@ -101,6 +105,10 @@ html { margin: 1em; } +.suspect { + list-style-type: none; +} + .suspect_picture { height: 15em; width: 15em; @@ -118,7 +126,6 @@ html { .home_button, .next_btn { fill: var(--game-red); height: 5em; - padding: 0; width: 5em; } @@ -192,13 +199,8 @@ html { .culprit_icon { fill: var(--game-white); height: 1em; - width: 1em; -} - -.culprit_btn_text { - margin: 0; - margin-left: 0.25em; margin-right: 0.25em; + width: 1em; } .culprit_btn_checked { diff --git a/truthinquiry/static/css/game_ui_legal.css b/truthinquiry/static/css/game_ui_legal.css index b21d620..d32c47c 100644 --- a/truthinquiry/static/css/game_ui_legal.css +++ b/truthinquiry/static/css/game_ui_legal.css @@ -1,10 +1,21 @@ /* Common properties */ html { - background-color: var(--game-black); + background-color: var(--game-grey); color: var(--game-white); font-family: "Roboto Mono", "sans-serif"; } +body { + display: flex; + flex-direction: column; + height: calc(100vh - var(--body-margin) * 2); + width: calc(100vw - var(--body-margin) * 2); +} + +footer { + margin-top: auto; +} + /* Legal links and texts */ .legal_text_description, .legal_text_last_update, .legal_text_title { margin: 0; diff --git a/truthinquiry/static/css/game_ui_start.css b/truthinquiry/static/css/game_ui_start.css index 18fe960..3c8799c 100644 --- a/truthinquiry/static/css/game_ui_start.css +++ b/truthinquiry/static/css/game_ui_start.css @@ -1,13 +1,18 @@ /* Common properties */ :root { --game-begin-margin: 2em; - --header-actions-height: 3em; + --header-actions-side: 4em; } input, .action_button, .game_mode_item_title { font-family: "Titan One", sans-serif; } +.back_button, .theme_switcher_btn { + height: var(--header-actions-side); + width: var(--header-actions-side); +} + .game_begin, .game_mode_item, .game_mode_item_input_text_single_line, .game_mode_items, .game_mode_selection { align-items: center; display: flex; @@ -61,7 +66,7 @@ input::placeholder { */ height: calc(100vh - var(--body-margin) * 2 - var(--game-begin-margin) * 2 - - var(--header-actions-height) + - var(--header-actions-side) - var(--footer-links-height)); justify-content: center; margin: var(--game-begin-margin); @@ -80,10 +85,16 @@ input::placeholder { margin: 0; } +.header_action { + list-style-type: none; +} + .header_actions { display: flex; - height: var(--header-actions-height); + height: var(--header-actions-side); justify-content: flex-end; + margin: 0; + padding: 0; } .action_button { diff --git a/truthinquiry/static/js/game.js b/truthinquiry/static/js/game.js index 7aa3339..0ee12fa 100644 --- a/truthinquiry/static/js/game.js +++ b/truthinquiry/static/js/game.js @@ -74,9 +74,12 @@ function showEmotionAndCulpritChoicesView() { } /** - * Parse the gamedata object to retreive the room in which the npc passed as parameter is - * located and the second npc located in the same room. When the passed npc is alone in the - * room, a npc is choosen at random as the returned partener + * Parse the gamedata object to retreive the room in which the npc passed as parameter is located + * and the second npc located in the same room. + * + *

+ * When the passed npc is alone in the room, a npc is chosen at random as the returned partener. + *

*/ function getNpcLocationAndPartner(npcid) { const data = {}; @@ -102,9 +105,12 @@ function getNpcLocationAndPartner(npcid) { } /** - * Parse the gamedata object to retreive the room in which the npc passed as parameter is - * located and the second npc located in the same room. When the passed npc is alone in the - * room, a npc is choosen at random as the returned partener + * Parse the gamedata object to retreive the room in which the npc passed as parameter is located + * and the second npc located in the same room. + * + *

+ * When the passed npc is alone in the room, a npc is chosen at random as the returned partner. + *

*/ function disableCulpritButtons(culprit_choices_element, selected_suspect) { let childrenCulpritChoicesElement = culprit_choices_element.children; @@ -123,8 +129,8 @@ function disableCulpritButtons(culprit_choices_element, selected_suspect) { } /** - * Return the npc designed as the "culprit" of the crime, the culprit - * is determined by being the only npc alone in a room. + * Return the npc designed as the "culprit" of the crime, the culprit is determined by being the + * only npc alone in a room. */ function getCulprit() { let culprit = null; @@ -140,8 +146,8 @@ function getCulprit() { } /** - * handler for the function call "askQuestion" for a type_zero question - * also known as "Where were you ?" + * Handler for the function call {@link askQuestion} for a type_zero question also known as + * "Where were you ?". */ async function askTypeZeroQuestion() { askQuestion(npcLocationAndPartner => gameData["npcs"][currentNpc]["QA_0"].replace( @@ -149,8 +155,8 @@ async function askTypeZeroQuestion() { } /** - * handler for the function call "askQuestion" for a type_one question - * also known as "With who were you with ?" + * Handler for the function call {@link askQuestion} for a type_one question also known as + * "With who were you with ?". */ async function askTypeOneQuestion() { askQuestion(npcLocationAndPartner => gameData["npcs"][currentNpc]["QA_1"].replace( @@ -158,11 +164,14 @@ async function askTypeOneQuestion() { } /** - * This function primary goal is to display the answer to the question the player - * asked to a npc. - * It parses the gamedata object to retreive the answer of the npc - * and fill the variables left in the string accordingly to the type of the question. - * Then it fetches the reacion of the npc and diplays it all. + * This function's primary goal is to display the answer to the question the player + * asked to a npc. + * + *

+ * It parses the gamedata object to retreive the answer of the npc and fill the variables left in + * the string accordingly to the type of the question; then it fetches the reacion of the npc and + * diplays it all. + *

*/ async function askQuestion(buildAnswer) { unsetQuestionButtonsListeners(); @@ -187,7 +196,7 @@ async function askQuestion(buildAnswer) { } /** - * This function sends the player's answers to the server + * Send the player's answers to the server. */ async function sendAnswers() { const selections = document.getElementsByClassName("suspect_emotion_chooser"); @@ -209,8 +218,10 @@ async function sendAnswers() { * then decide on which npc is the culprit. */ function renderAnswerSelectionPanel() { + const culpritChoices = document.getElementById("culprits_choices"); + npcsIds.forEach(element => { - const suspect = document.createElement("div"); + const suspect = document.createElement("li"); suspect.classList.add("suspect"); const suspectEmotionChooser = document.createElement("select"); @@ -228,14 +239,20 @@ function renderAnswerSelectionPanel() { const img = document.createElement('img'); img.classList.add("suspect_picture"); + img.setAttribute("alt", "Image d'un suspect"); img.src = NPC_IMAGE_PATH + element; suspect.appendChild(img); const button = document.createElement("button"); button.classList.add("culprit_btn", "action_button"); - button.innerHTML = '

Couplable

'; - const culpritChoices = document.getElementById("culprits_choices"); + button.appendChild(createCulpritSvgElement("culprit_checked_icon", + "M18.9 36.75 6.65 24.5l3.3-3.3 8.95 9L38 11.1l3.3 3.25Z", true)); + button.appendChild(createCulpritSvgElement("culprit_unchecked_icon", + "M12.45 38.7 9.3 35.55 20.85 24 9.3 12.5l3.15-3.2L24 20.8 35.55 9.3l3.15 3.2L27.2 24l11.5 11.55-3.15 3.15L24 27.2Z", + false)); + + button.appendChild(document.createTextNode("Couplable")); button.addEventListener("click", () => { disableCulpritButtons(culpritChoices, suspect); @@ -248,7 +265,30 @@ function renderAnswerSelectionPanel() { } /** - * Show the screen in which the player asks auestions to the npcs + * Create a culprit SVG {@link Element}. + * + * @param {String} buttonCssClass the specific CSS class to add to the culprit button + * @param {String} pathAttributeValue the value of the path attribute of the SVG element generated + * @returns a svg {@link Element} with a culprit button depending of the custom CSS class, path + * attribute value and isHidden values + */ +function createCulpritSvgElement(buttonCssClass, pathAttributeValue, isHidden) { + const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svgElement.classList.add(buttonCssClass, "culprit_icon"); + if (isHidden) { + svgElement.classList.add("hidden"); + } + svgElement.setAttribute("viewBox", "0 0 48 48"); + + const pathElement = document.createElementNS("http://www.w3.org/2000/svg", "path"); + pathElement.setAttribute("d", pathAttributeValue); + + svgElement.appendChild(pathElement); + return svgElement; +} + +/** + * Show the screen in which the player asks questions to the npcs. */ function renderInterrogation() { document.getElementById("QA_0").textContent = gameData["questions"]["QA_0"]; @@ -257,11 +297,12 @@ function renderInterrogation() { const interrogationSuspects = document.getElementById("interrogation_suspects"); npcsIds.forEach(element => { - const suspect = document.createElement("div"); + const suspect = document.createElement("li"); suspect.classList.add("suspect"); const img = document.createElement('img'); img.classList.add("suspect_picture"); + img.setAttribute("alt", "Image d'un suspect"); img.src = NPC_IMAGE_PATH + element; suspect.appendChild(img); @@ -281,12 +322,14 @@ function renderInterrogation() { }); } - /** - * Initialize the websocket for this page, its primary use is to - * show the final page once it receive the event that all player have finished - * it parses the payload send by the server containing the other players + * Initialize the websocket for this page, its primary use is to show the final page once it + * receives the event that all players have finished. + * + *

+ * It parses the payload send by the server containing the other players * nicknames and scores. + *

*/ function initSock() { const socket = io({ @@ -306,9 +349,12 @@ function initSock() { socket.on("gamefinished", finalResults => { hideFirstClassElement("emotion_and_culprit_choices"); - document.querySelector(".reveal_score").textContent = - Object.values(finalResults["player"][username]) - .filter(x => x == true).length + " / 5"; + const revealScoreElement = document.createElement("h2"); + revealScoreElement.classList.add("reveal_score"); + revealScoreElement.textContent = Object.values(finalResults["player"][username]) + .filter(x => x == true).length + " / 5"; + + document.querySelector(".player_score").appendChild(revealScoreElement); const playerListElement = document.querySelector(".players_list"); @@ -331,9 +377,14 @@ function initSock() { const culpritName = gameData["npcs"][culprit]["name"]; document.querySelector(".reveal_culprit_title").textContent += " " + culpritName; - const culpritElement = document.getElementById("culprit"); - culpritElement.src = NPC_IMAGE_PATH + culprit; + const culpritElement = document.createElement("img"); + culpritElement.classList.add("suspect_picture"); culpritElement.setAttribute("alt", "Image du ou de la coupable, " + culpritName); + culpritElement.src = NPC_IMAGE_PATH + culprit; + culpritElement.setAttribute("draggable", "false"); + + document.querySelector(".reveal_culprit") + .appendChild(culpritElement); showFirstClassElement("results_game"); setGameBackground(RESULTS_IMAGE_PATH); @@ -346,6 +397,7 @@ function initSock() { suspect.classList.add("summary_suspect"); const img = document.createElement("img"); + img.setAttribute("alt", "Image d'un suspect"); img.src = NPC_IMAGE_PATH + npcid; suspect.appendChild(img); @@ -366,12 +418,11 @@ function initSock() { }); }); } -/** - * This function retreive the initial gamedata of the game - * containing all of the needed textual ressources to make - * the game playable -*/ +/** + * Retreive the initial gamedata of the game containing all of the needed textual ressources to + * make the game playable. + */ async function setGameData() { const response = await makeAPIRequest("getGameData"); gameData = response["gamedata"]; @@ -380,7 +431,7 @@ async function setGameData() { } /** - * Initialize the game, by setting the game data, initializing the socket, rendering the answer + * Initialize the game by setting the game data, initializing the socket, rendering the answer * selection panel, rendering the interrogation view, setting questions buttons listeners, * setting introduction and interrogation listeners, showing the introduction view and setting the * introduction image as the game background. diff --git a/truthinquiry/templates/errorhandler.html b/truthinquiry/templates/errorhandler.html index 843124c..0ca40e4 100644 --- a/truthinquiry/templates/errorhandler.html +++ b/truthinquiry/templates/errorhandler.html @@ -1,2 +1,20 @@ - -

{{desc}}

+ + + + + Truth Inquiry - Error + + + + + + + + + + + + Image describing a HTTP error +

{{desc}}

+ + diff --git a/truthinquiry/templates/game.html b/truthinquiry/templates/game.html index 86cf93e..c25ecfe 100644 --- a/truthinquiry/templates/game.html +++ b/truthinquiry/templates/game.html @@ -1,6 +1,7 @@ + Truth Inquiry @@ -13,92 +14,83 @@ -
- - - - - +
-
-
+
+

Navigateur non supporté

-
-
+ + diff --git a/truthinquiry/templates/index.html b/truthinquiry/templates/index.html index 124de84..82243e4 100644 --- a/truthinquiry/templates/index.html +++ b/truthinquiry/templates/index.html @@ -1,6 +1,7 @@ + Truth Inquiry @@ -13,26 +14,26 @@ - -
-
-
- -
+
+ +
  • + +
  • +

    Truth Inquiry

    -
    - - -
    -
    + +
    +
    +

    Navigateur non supporté

    -
    -
    + +