diff --git a/src/pages/game/GameController.tsx b/src/pages/game/GameController.tsx index 08ae002..3b52cd5 100644 --- a/src/pages/game/GameController.tsx +++ b/src/pages/game/GameController.tsx @@ -3,24 +3,28 @@ import { LobbySettings } from "./LobbySettings" import { LobbyPlayers } from "./LobbyPlayers" import { useNavigate, useParams } from "react-router-dom" import { useEffect, useState } from "react" -import { addPlayerToGame, listenGame } from "../../services/games-store" +import { addPlayerToGame, listenGame, updateGameUserAnswers } from "../../services/games-store" import { getSnapshotData } from "../../services/store" -import { Game, GameSchema, StoreResponse } from "../../utils/types" +import { Game, GameSchema, GameState, Question, StoreResponse } from "../../utils/types" import { GameQuestion } from "./GameQuestion" import { getUserInfo } from "../../services/authentication" import { isEqual } from "lodash" +import { GameReview } from "./GameReview" export const GameController = () => { - const [isSetup, setIsSetup] = useState(false) + const [gameState, setGameState] = useState(GameState.WAITING) const [questionIndex, setQuestionIndex] = useState(0) - const [usernames, setUsernames] = useState([] as string[]) + const [usernames, setUsernames] = useState({}) + const [questions, setQuestions] = useState([] as Question[]) + const [answers, setAnswers] = useState({} as Record) + const [game, setGame] = useState({} as Game) const { gameId } = useParams() const navigate = useNavigate() console.log("Rendering app") - // Listen to game changes, called once + // Game state contoller useEffect(() => { if (gameId === undefined) { console.error("Undefined game id") @@ -31,6 +35,7 @@ export const GameController = () => { // Add user to game addPlayerToGame(gameId, getUserInfo()) + // TODO: Handle stop listening game listenGame(gameId, (snapshot) => { console.log("Snapshot listener") const response: StoreResponse = getSnapshotData(snapshot, GameSchema) @@ -50,24 +55,48 @@ export const GameController = () => { // Get this game const game = response.data.shift()! + // Set game as state + setGame(game) + // Update users if needed if (!isEqual(usernames, game.users)) { setUsernames(Object.values(game.users)) } - // Control game state - if (isSetup !== game.isSetup) { - setIsSetup(game.isSetup) - } - - // Set next question - if (questionIndex !== game.questionIndex) { - setQuestionIndex(game.questionIndex) + // Start the game play + if (game.isSetup) { + setQuestions(game.questions) + setGameState(GameState.PLAYING) } }) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) + // Game play controller + const handleAnswer = (answer: string) => { + // Add answer to answers + answers[questions[questionIndex].id] = answer + setAnswers(answers) + + // Next question + if (questionIndex < questions.length - 1) { + setQuestionIndex(questionIndex + 1) + } + + // Reviwing state + else { + // TODO: reviwing state + updateGameUserAnswers(game.id, getUserInfo().id, answers).then((response) => { + if (!response.success) { + console.error(response.error) + navigate("/error/") + } else { + setGameState(GameState.REVIEWING) + } + }) + } + } + if (gameId === undefined) { console.error("Undefined game id") navigate("/error/") @@ -77,14 +106,18 @@ export const GameController = () => { return (
- {isSetup ? ( - - ) : ( + {gameState === GameState.WAITING ? (

Nouvelle partie

+ ) : gameState === GameState.PLAYING ? ( + + ) : gameState === GameState.REVIEWING ? ( + + ) : ( +
Jeu fini
)}
) diff --git a/src/pages/game/GameQuestion.tsx b/src/pages/game/GameQuestion.tsx index 18c1932..2068599 100644 --- a/src/pages/game/GameQuestion.tsx +++ b/src/pages/game/GameQuestion.tsx @@ -1,7 +1,20 @@ -export const GameQuestion = () => { +import { useState } from "react" +import { Question } from "../../utils/types" + +type GameQuestionPorps = { + question: Question + sendAnswer: (answer: string) => void +} + +export const GameQuestion = (props: GameQuestionPorps) => { + const [answer, setAnswer] = useState("") + return (

Partie!

+
{JSON.stringify(props.question)}
+ setAnswer(e.target.value)} /> +
) } diff --git a/src/pages/game/GameReview.tsx b/src/pages/game/GameReview.tsx new file mode 100644 index 0000000..ce6ecaa --- /dev/null +++ b/src/pages/game/GameReview.tsx @@ -0,0 +1,14 @@ +import { Game } from "../../utils/types" + +type GameReviewProps = { + game: Game +} + +export const GameReview = (props: GameReviewProps) => { + return ( +
+

Question Review

+
{JSON.stringify(props.game.users)}
+
+ ) +} diff --git a/src/pages/game/LobbyPlayers.tsx b/src/pages/game/LobbyPlayers.tsx index 9c5595d..76a545a 100644 --- a/src/pages/game/LobbyPlayers.tsx +++ b/src/pages/game/LobbyPlayers.tsx @@ -1,14 +1,18 @@ +import { GameUser } from "../../utils/types" + type LobbyPlayersProps = { - usernames: string[] + usernames: Record } export const LobbyPlayers = (props: LobbyPlayersProps) => { + const userIds = Object.keys(props.usernames) + return (

Joueurs

    - {props.usernames.map((username) => ( -
  • {username}
  • + {userIds.map((id) => ( +
  • {props.usernames[id].name}
  • ))}
diff --git a/src/services/games-store.ts b/src/services/games-store.ts index 0ff6dbe..c5f60ba 100644 --- a/src/services/games-store.ts +++ b/src/services/games-store.ts @@ -11,7 +11,12 @@ import { where, } from "firebase/firestore" import { db } from "../config/firebase" -import { getErrorStoreResponse, getSuccessStoreResponse, initializeEmptyGameData } from "../utils/utils" +import { + getErrorStoreResponse, + getSuccessStoreResponse, + initializeEmptyGameData, + initializeGameUser, +} from "../utils/utils" import { findDataByQuery } from "./store" import { Game, GameSchema, StoreResponse, UserInfo } from "../utils/types" import { validateStoreResponseLength } from "./validation" @@ -97,11 +102,17 @@ export async function startGame(id: string, tags: string[], nbQuestions: number) } export async function addPlayerToGame(gameId: string, userInfo: UserInfo) { - const response = await updateGame(gameId, { ["users." + userInfo.id]: userInfo.name }) + const gameUser = initializeGameUser(userInfo.name) + const response = await updateGame(gameId, { ["users." + userInfo.id]: gameUser }) + return response +} + +export async function updateGameUserAnswers(gameId: string, userId: string, answers: Record) { + const response = await updateGame(gameId, { ["users." + userId + ".answers"]: answers }) return response } -export async function listenGame(id: string, callback: (snapshot: QuerySnapshot) => void) { +export function listenGame(id: string, callback: (snapshot: QuerySnapshot) => void) { const q = query(gamesRef, where(documentId(), "==", id)) - onSnapshot(q, callback) + return onSnapshot(q, callback) } diff --git a/src/utils/types.ts b/src/utils/types.ts index 9953d2e..478990a 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,5 +1,22 @@ import { z } from "zod" +/* User types */ + +export const UserInfoSchema = z.object({ + id: z.string(), + name: z.string(), + isAuth: z.boolean(), +}) + +export const GameUserSchema = z.object({ + name: z.string(), + answers: z.record(z.string(), z.string()), + reviews: z.record(z.string(), z.record(z.string(), z.boolean())), +}) + +export type UserInfo = z.infer +export type GameUser = z.infer + /* Questions Types */ export enum QuestionType { @@ -32,11 +49,19 @@ export const QuestionSchema = QuestionDataSchema.extend({ export type QuestionData = z.infer export type Question = z.infer -// Games types +/* Game types */ + +export enum GameState { + WAITING, + PLAYING, + REVIEWING, + END, +} + export const GameDataSchema = z.object({ name: z.string().toUpperCase().length(4), isSetup: z.boolean(), - users: z.record(z.string(), z.string()), + users: z.record(z.string(), GameUserSchema), tags: z.array(z.string()), questions: z.array(QuestionSchema), questionIndex: z.number(), @@ -49,19 +74,11 @@ export const GameSchema = GameDataSchema.extend({ export type GameData = z.infer export type Game = z.infer -// Store types +/* Store types */ + export const StoreResponseSchema = z.discriminatedUnion("success", [ z.object({ success: z.literal(true), data: z.object({}) }), z.object({ success: z.literal(false), error: z.any() }), ]) export type StoreResponse = { success: true; data: DataType[] } | { success: false; error: unknown } - -// User types -export const UserInfoSchema = z.object({ - id: z.string(), - name: z.string(), - isAuth: z.boolean(), -}) - -export type UserInfo = z.infer diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 92b2e97..07767fb 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,4 @@ -import { GameData, StoreResponse, UserInfo } from "./types" +import { GameData, GameUser, StoreResponse, UserInfo } from "./types" /** * Get an anonymous userInfo @@ -30,6 +30,14 @@ export function initializeEmptyGameData(): GameData { } } +export function initializeGameUser(name: string): GameUser { + return { + name, + answers: {}, + reviews: {}, + } +} + export function initializeEmptyQuestionData() {} export function getSuccessStoreResponse(data: DataType[]): StoreResponse {