Skip to content

Commit

Permalink
Single player game play implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
david-vct committed Mar 30, 2024
1 parent ccb403c commit a3b2330
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 37 deletions.
65 changes: 49 additions & 16 deletions src/pages/game/GameController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>)
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")
Expand All @@ -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<Game> = getSnapshotData(snapshot, GameSchema)
Expand All @@ -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/")
Expand All @@ -77,14 +106,18 @@ export const GameController = () => {
return (
<div>
<Navbar />
{isSetup ? (
<GameQuestion />
) : (
{gameState === GameState.WAITING ? (
<div>
<h1>Nouvelle partie</h1>
<LobbySettings gameId={gameId} />
<LobbyPlayers usernames={usernames} />
</div>
) : gameState === GameState.PLAYING ? (
<GameQuestion question={questions[questionIndex]} sendAnswer={handleAnswer} />
) : gameState === GameState.REVIEWING ? (
<GameReview game={game} />
) : (
<div>Jeu fini</div>
)}
</div>
)
Expand Down
15 changes: 14 additions & 1 deletion src/pages/game/GameQuestion.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<h2>Partie!</h2>
<div>{JSON.stringify(props.question)}</div>
<input placeholder="Réponse" onChange={(e) => setAnswer(e.target.value)} />
<button onClick={() => props.sendAnswer(answer)}>Repondre</button>
</div>
)
}
14 changes: 14 additions & 0 deletions src/pages/game/GameReview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Game } from "../../utils/types"

type GameReviewProps = {
game: Game
}

export const GameReview = (props: GameReviewProps) => {
return (
<div>
<h2>Question Review</h2>
<div>{JSON.stringify(props.game.users)}</div>
</div>
)
}
10 changes: 7 additions & 3 deletions src/pages/game/LobbyPlayers.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { GameUser } from "../../utils/types"

type LobbyPlayersProps = {
usernames: string[]
usernames: Record<string, GameUser>
}

export const LobbyPlayers = (props: LobbyPlayersProps) => {
const userIds = Object.keys(props.usernames)

return (
<div>
<h2>Joueurs</h2>
<ul>
{props.usernames.map((username) => (
<li key={username}>{username}</li>
{userIds.map((id) => (
<li key={id}>{props.usernames[id].name}</li>
))}
</ul>
</div>
Expand Down
19 changes: 15 additions & 4 deletions src/services/games-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<string, string>) {
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)
}
41 changes: 29 additions & 12 deletions src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -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<typeof UserInfoSchema>
export type GameUser = z.infer<typeof GameUserSchema>

/* Questions Types */

export enum QuestionType {
Expand Down Expand Up @@ -32,11 +49,19 @@ export const QuestionSchema = QuestionDataSchema.extend({
export type QuestionData = z.infer<typeof QuestionDataSchema>
export type Question = z.infer<typeof QuestionSchema>

// 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(),
Expand All @@ -49,19 +74,11 @@ export const GameSchema = GameDataSchema.extend({
export type GameData = z.infer<typeof GameDataSchema>
export type Game = z.infer<typeof GameSchema>

// 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<DataType> = { 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<typeof UserInfoSchema>
10 changes: 9 additions & 1 deletion src/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GameData, StoreResponse, UserInfo } from "./types"
import { GameData, GameUser, StoreResponse, UserInfo } from "./types"

/**
* Get an anonymous userInfo
Expand Down Expand Up @@ -30,6 +30,14 @@ export function initializeEmptyGameData(): GameData {
}
}

export function initializeGameUser(name: string): GameUser {
return {
name,
answers: {},
reviews: {},
}
}

export function initializeEmptyQuestionData() {}

export function getSuccessStoreResponse<DataType>(data: DataType[]): StoreResponse<DataType> {
Expand Down

0 comments on commit a3b2330

Please sign in to comment.