Skip to content

Commit 2d71190

Browse files
committed
feat: add turn timer
1 parent 0f347f2 commit 2d71190

File tree

23 files changed

+434
-223
lines changed

23 files changed

+434
-223
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,15 @@
1616
"clsx": "2.0.0",
1717
"immer": "^10.0.3",
1818
"ky": "^1.0.1",
19-
"lodash": "^4.17.21",
2019
"react": "18.2.0",
2120
"react-dom": "18.2.0",
2221
"react-router-dom": "6.16.0",
22+
"remeda": "^1.29.0",
2323
"zustand": "4.4.2"
2424
},
2525
"devDependencies": {
2626
"@iconify-json/lucide": "1.1.131",
2727
"@testing-library/react": "^14.0.0",
28-
"@types/lodash": "^4.14.199",
2928
"@types/node": "^20.8.2",
3029
"@types/react": "18.2.24",
3130
"@types/react-dom": "18.2.8",

pnpm-lock.yaml

Lines changed: 7 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/modules/game/model/hooks.game.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useShallow } from 'zustand/shallow'
2+
3+
import { selectCollectedCards, selectCurrentPlayer } from './selectors'
4+
import { useGameState } from './state'
5+
import { GameCard } from './types'
6+
import { isCardCollected } from '../utils'
7+
8+
export const useGameActions = () =>
9+
useGameState(useShallow((state) => state.actions))
10+
11+
export const usePlayers = () => useGameState((state) => state.players)
12+
13+
export const useCurrentPlayer = () => useGameState(selectCurrentPlayer)
14+
15+
export const useCards = () => useGameState((state) => state.cards)
16+
17+
export const useAreCardsLoading = () =>
18+
useGameState((state) => state.areCardsLoading)
19+
20+
export const useCollectedCards = () => useGameState(selectCollectedCards)
21+
22+
export const useIsCardCollected = () => {
23+
const collectedCards = useCollectedCards()
24+
return (card: GameCard) => isCardCollected(collectedCards, card)
25+
}
26+
27+
export const useCardRefs = () => useGameState((state) => state.refs.cards)
28+
29+
export const useAllCardsRegistered = () =>
30+
useGameState((state) => state.cards.length === state.refs.cards.length)

src/modules/game/model/hooks.ts

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,2 @@
1-
import { selectCollectedCards } from './selectors'
2-
import { useGameState } from './state'
3-
import { GameCard } from './types'
4-
import { isCardCollected, isCardRevealed } from '../utils'
5-
6-
export const useTurn = () => useGameState((state) => state.turn)
7-
8-
export const usePlayers = () => useGameState((state) => state.players)
9-
10-
export const useCurrentPlayer = () =>
11-
useGameState((state) => state.players[state.turn.playerIndex])
12-
13-
export const useCards = () => useGameState((state) => state.cards)
14-
15-
export const useAreCardsLoading = () =>
16-
useGameState((state) => state.areCardsLoading)
17-
18-
export const useCollectedCards = () => useGameState(selectCollectedCards)
19-
20-
export const useSelectedCards = () =>
21-
useGameState((state) => state.turn.selectedCards)
22-
23-
export const useIsCardCollected = () => {
24-
const collectedCards = useCollectedCards()
25-
return (card: GameCard) => isCardCollected(collectedCards, card)
26-
}
27-
28-
export const useIsCardRevealed = () => {
29-
const selectedCards = useSelectedCards()
30-
return (card: GameCard) => isCardRevealed(selectedCards, card)
31-
}
32-
33-
export const useCardRefs = () => useGameState((state) => state.refs.cards)
34-
35-
export const useAllCardsRegistered = () =>
36-
useGameState((state) => state.cards.length === state.refs.cards.length)
37-
38-
export const useGameActions = () => {
39-
const selectCard = useGameState((state) => state.selectCard)
40-
const startGame = useGameState((state) => state.startGame)
41-
const endGame = useGameState((state) => state.endGame)
42-
const endTurn = useGameState((state) => state.endTurn)
43-
const registerCardRef = useGameState((state) => state.registerCardRef)
44-
45-
return {
46-
startGame,
47-
endGame,
48-
selectCard,
49-
endTurn,
50-
registerCardRef,
51-
}
52-
}
1+
export * from './hooks.game'
2+
export * from './hooks.turn'

src/modules/game/model/hooks.turn.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { omit } from 'remeda'
2+
import { useShallow } from 'zustand/shallow'
3+
4+
import { useGameState } from './state'
5+
import { GameCard } from './types'
6+
import { isCardRevealed } from '../utils'
7+
8+
export const useTurn = () =>
9+
useGameState(useShallow((state) => omit(state.turn, ['actions'])))
10+
11+
export const useTurnActions = () =>
12+
useGameState(useShallow((state) => state.turn.actions))
13+
14+
export const useSelectedCards = () =>
15+
useGameState((state) => state.turn.selectedCards)
16+
17+
export const useIsCardRevealed = () => {
18+
const selectedCards = useSelectedCards()
19+
return (card: GameCard) => isCardRevealed(selectedCards, card)
20+
}

src/modules/game/model/selectors.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import { GameCard } from '.'
2-
import { GameState } from './state'
1+
import { Draft } from 'immer'
2+
3+
import { GameCard, GameState } from '.'
34

45
export const selectCollectedCards = (state: GameState) =>
56
state.players.reduce<GameCard[]>(
67
(collectedCards, player) => [...collectedCards, ...player.cards],
78
[],
89
)
10+
11+
export const selectCurrentPlayer = (state: GameState | Draft<GameState>) =>
12+
state.players[state.turn.playerIndex]

src/modules/game/model/state.game.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { castDraft } from 'immer'
2+
3+
import { GameSettings } from '@/modules/game-settings'
4+
import { ImmerStateCreator } from '@/shared/types/state'
5+
6+
import { GameState, GameBaseStateSlice } from './types'
7+
import { GameService } from '../services/GameService'
8+
9+
export const createGameStateSlice: ImmerStateCreator<
10+
GameState,
11+
GameBaseStateSlice
12+
> = (set, get) => ({
13+
players: [],
14+
cards: [],
15+
areCardsLoading: true,
16+
isFinished: false,
17+
18+
refs: {
19+
cards: [],
20+
},
21+
22+
actions: {
23+
startGame: async (settings: GameSettings) => {
24+
get().actions.resetGame()
25+
26+
const gameService = new GameService(settings)
27+
const players = gameService.initializePlayers()
28+
const cards = await gameService.initializeCards()
29+
30+
set({ players, cards, areCardsLoading: false })
31+
},
32+
33+
resetGame: () => {
34+
get().turn.actions.resetTurn()
35+
36+
set((state) => {
37+
state.isFinished = false
38+
state.areCardsLoading = true
39+
})
40+
},
41+
42+
endGame: () => {
43+
set({ isFinished: true })
44+
},
45+
46+
registerCardRef: (element: HTMLElement) => {
47+
set((state) => {
48+
state.refs.cards.push(castDraft(element))
49+
})
50+
51+
return () => {
52+
const elementIndex = get().refs.cards.indexOf(element)
53+
54+
set((state) => {
55+
state.refs.cards.splice(elementIndex, 1)
56+
})
57+
}
58+
},
59+
},
60+
})

0 commit comments

Comments
 (0)