From ca977fbce318a55c4bd9e16432016d474f848d10 Mon Sep 17 00:00:00 2001 From: Jonas Tranberg Date: Wed, 22 May 2024 18:23:00 +0200 Subject: [PATCH] implement card drawing + some other fixes --- src/stores/game.ts | 80 ++++++++++++-------- src/stores/gamesPlayed.ts | 2 +- src/stores/metrics.ts | 26 +++---- src/stores/settings.ts | 2 +- src/utilities/deck.ts | 14 +++- src/views/Game/components/CardInventory.tsx | 1 + src/views/Game/components/DNFDialog.tsx | 8 +- src/views/Game/components/GameOverDialog.tsx | 5 +- src/views/Game/components/Header.tsx | 15 ++-- src/views/Game/components/PlayerItem.tsx | 9 ++- src/views/Game/components/Table.tsx | 1 + 11 files changed, 96 insertions(+), 67 deletions(-) diff --git a/src/stores/game.ts b/src/stores/game.ts index 17c338b..e1453cb 100644 --- a/src/stores/game.ts +++ b/src/stores/game.ts @@ -1,11 +1,14 @@ -import create from "zustand"; +import { create } from "zustand"; import { persist } from "zustand/middleware"; import * as GameAPI from "../api/endpoints/game"; -import { Card, CardSuits, CardValues } from "../models/card"; +import { Card, CardValues } from "../models/card"; import { Chug } from "../models/chug"; import { Player } from "../models/player"; -import { GenerateShuffleIndices } from "../utilities/deck"; -import { mapToRemote } from "./game.mapper"; +import { + GenerateDeck, + GenerateShuffleIndices, + GetCardN, +} from "../utilities/deck"; import useGamesPlayed from "./gamesPlayed"; import useSettings from "./settings"; /* @@ -25,13 +28,13 @@ interface GameState { numberOfRounds: number; gameStartTimestamp: number; + gameEndTimestamp: number; turnStartTimestamp: number; - draws: Card[]; - players: Player[]; chugs: Chug[]; + draws: Card[]; } interface GameActions { @@ -60,11 +63,12 @@ const initialState: GameState = { numberOfRounds: 13, gameStartTimestamp: 0, + gameEndTimestamp: 0, turnStartTimestamp: 0, players: [], - chugs: [], + chugs: [], draws: [], }; @@ -92,13 +96,15 @@ const useGame = create()( // Set up game state - let id = undefined; + let id; + let token; + let shuffleIndices; let gameStartTimestamp = Date.now(); let turnStartTimestamp = Date.now(); - let shuffleIndices = GenerateShuffleIndices(players.length); - let token = undefined; - if (!options.offline) { + if (options.offline) { + shuffleIndices = GenerateShuffleIndices(players.length); + } else { const playerTokens = players.map((player) => player.token as string); const resp = await GameAPI.postStart(playerTokens, true); @@ -109,6 +115,8 @@ const useGame = create()( shuffleIndices = resp.shuffle_indices; } + const deck = GenerateDeck(shuffleIndices, players.length); + set({ id: id, offline: options.offline, @@ -129,31 +137,37 @@ const useGame = create()( Draw: () => { console.debug("[Game]", "Drawing card"); - const suit = CardSuits[Math.floor(Math.random() * 4)]; - const value = CardValues[Math.floor(Math.random() * 13)]; + const state = useGame.getState(); + + const deck = GenerateDeck(state.shuffleIndices, state.players.length); + + if (deck.length === 0) { + throw new Error("Cannot draw from an empty deck!"); + } + + const card = GetCardN( + state.shuffleIndices, + state.players.length, + state.draws.length, + ); + + const draws = [...state.draws, card]; + const turnStartTimestamp = Date.now(); + + const done = + draws.length === (CardValues.length - 1) * state.players.length; - const card = { - suit: suit, - value: value, + const update: Partial = { + draws: draws, + turnStartTimestamp: turnStartTimestamp, }; - set((state) => { - const updates = { - turnStartTimestamp: Date.now(), - draws: [...state.draws, card], - }; - - if (!state.offline) { - GameAPI.postUpdate( - mapToRemote({ - ...state, - ...updates, - }), - ); - } - - return updates; - }); + if (done) { + useGamesPlayed.getState().incrementCompleted(); + update.gameEndTimestamp = Date.now(); + } + + set(update); return card; }, diff --git a/src/stores/gamesPlayed.ts b/src/stores/gamesPlayed.ts index 41a1d14..9a76d1e 100644 --- a/src/stores/gamesPlayed.ts +++ b/src/stores/gamesPlayed.ts @@ -1,4 +1,4 @@ -import create from "zustand"; +import { create } from "zustand"; import { persist } from "zustand/middleware"; interface ComputerGamesCountState { diff --git a/src/stores/metrics.ts b/src/stores/metrics.ts index a2208c3..cf90332 100644 --- a/src/stores/metrics.ts +++ b/src/stores/metrics.ts @@ -1,6 +1,4 @@ -import create from "zustand"; -import { Card } from "../models/card"; -import { GenerateDeck } from "../utilities/deck"; +import { create } from "zustand"; import useGame from "./game"; interface PlayerMetrics { @@ -72,16 +70,18 @@ const MetricsStore = create()((set, get) => ({ const draws = game.draws; /* - Calculate game metrics - */ + Calculate game metrics + */ const numberOfCards = game.players.length * game.numberOfRounds; const numberOfCardsDrawn = draws.length; const numberOfCardsRemaining = numberOfCards - numberOfCardsDrawn; - const currentRound = - Math.floor(numberOfCardsDrawn / game.players.length) + 1; + const currentRound = Math.min( + Math.floor(numberOfCardsDrawn / game.players.length) + 1, + 13, + ); const roundsRemaining = game.numberOfRounds - currentRound; const numberOfPlayers = game.players.length; @@ -90,8 +90,8 @@ const MetricsStore = create()((set, get) => ({ const done = numberOfCardsDrawn === numberOfCards; /* - Calculate player metrics - */ + Calculate player metrics + */ const currentPlayerMetrics = get().players; const currentPlayerIndex = draws.length % game.players.length; @@ -141,8 +141,8 @@ const MetricsStore = create()((set, get) => ({ })); /* - Update the metrics store state - */ + Update the metrics store state + */ set({ players: playerMetrics, @@ -192,8 +192,8 @@ const useGameMetrics = () => { export { MetricsStore, - usePlayerMetricsByIndex, useGameMetrics, usePlayerMetrics, + usePlayerMetricsByIndex, }; -export type { PlayerMetrics, GameMetrics, MetricsState, MetricsActions }; +export type { GameMetrics, MetricsActions, MetricsState, PlayerMetrics }; diff --git a/src/stores/settings.ts b/src/stores/settings.ts index d5c250c..cc66c06 100644 --- a/src/stores/settings.ts +++ b/src/stores/settings.ts @@ -1,4 +1,4 @@ -import create from "zustand"; +import { create } from "zustand"; import { persist } from "zustand/middleware"; type ThemeMode = "light" | "dark"; diff --git a/src/utilities/deck.ts b/src/utilities/deck.ts index 2254261..92cd4c7 100644 --- a/src/utilities/deck.ts +++ b/src/utilities/deck.ts @@ -6,7 +6,7 @@ const GenerateDeck = ( shuffleIndices: number[], numberOfPlayers: number, ): Card[] => { - if (shuffleIndices.length !== numberOfPlayers * CardValues.length) { + if (shuffleIndices.length !== numberOfPlayers * CardValues.length - 1) { throw new Error("Number of players and shuffle indices mismatch!"); } @@ -39,4 +39,14 @@ const GenerateShuffleIndices = (numberOfPlayers: number): number[] => { return shuffleIndices; }; -export { GenerateDeck, GenerateShuffleIndices }; +const GetCardN = ( + shuffleIndices: number[], + numberOfPlayers: number, + n: number, +): Card => { + const deck = GenerateDeck(shuffleIndices, numberOfPlayers); + + return deck[n]; +}; + +export { GenerateDeck, GenerateShuffleIndices, GetCardN }; diff --git a/src/views/Game/components/CardInventory.tsx b/src/views/Game/components/CardInventory.tsx index 38326f2..c483917 100644 --- a/src/views/Game/components/CardInventory.tsx +++ b/src/views/Game/components/CardInventory.tsx @@ -66,6 +66,7 @@ const CardInventoryCard: FunctionComponent = ( flexShrink: 0, textAlign: "center", position: "relative", + userSelect: "none", ...(props.value <= 0 && { opacity: 0.5, diff --git a/src/views/Game/components/DNFDialog.tsx b/src/views/Game/components/DNFDialog.tsx index b47a070..5b15e28 100644 --- a/src/views/Game/components/DNFDialog.tsx +++ b/src/views/Game/components/DNFDialog.tsx @@ -14,8 +14,8 @@ import { useTheme, } from "@mui/material"; import { FunctionComponent } from "react"; -import useGame from "../../../stores/game"; import { useSounds } from "../../../hooks/sounds"; +import useGame from "../../../stores/game"; interface DNFDialogProps extends DialogProps {} const DNFDialog: FunctionComponent = (props) => { @@ -28,8 +28,8 @@ const DNFDialog: FunctionComponent = (props) => { return ( - - Did not finish? + + Did not finish? = (props) => { {players.map((player, index) => { return ( = (props) => { return ( - - Game Over + + Game Over { const game = useGame((state) => ({ gameStartTimestamp: state.gameStartTimestamp, + gameEndTimestamp: state.gameEndTimestamp, turnStartTimestamp: state.turnStartTimestamp, numberOfRounds: state.numberOfRounds, ExitGame: state.Exit, @@ -56,15 +57,16 @@ const Header: FunctionComponent = () => { const [elapsedTurnTime, setElapsedTurnTime] = useState(0); const updateTimes = () => { - setElapsedGameTime(Date.now() - game.gameStartTimestamp); - setElapsedTurnTime(Date.now() - game.turnStartTimestamp); - }; - - useEffect(() => { if (gameMetrics.done) { - // TODO + setElapsedGameTime(game.gameEndTimestamp - game.gameStartTimestamp); + setElapsedTurnTime(0); + } else { + setElapsedGameTime(Date.now() - game.gameStartTimestamp); + setElapsedTurnTime(Date.now() - game.turnStartTimestamp); } + }; + useEffect(() => { updateTimes(); const interval = setInterval(updateTimes, 1); @@ -84,6 +86,7 @@ const Header: FunctionComponent = () => { paddingRight: 2, flexShrink: 0, display: "flex", + userSelect: "none", }} > = (props) => { display: "flex", backgroundColor: color(), color: "white", + userSelect: "none", }} onClick={() => settings.SetSimpleCardsMode(!settings.simpleCardsMode)} > diff --git a/src/views/Game/components/Table.tsx b/src/views/Game/components/Table.tsx index 8ffabed..6281b13 100644 --- a/src/views/Game/components/Table.tsx +++ b/src/views/Game/components/Table.tsx @@ -30,6 +30,7 @@ const GameTable: FunctionComponent = () => {