@@ -10,6 +10,12 @@ export type Round = Readonly<{
1010 status ?: "guessed" | "skipped" ;
1111} > ;
1212
13+ export type WordPack = Readonly < {
14+ id : string ;
15+ title : string ;
16+ words : readonly string [ ] ;
17+ } > ;
18+
1319function getNewRound (
1420 getNextWord : ( ) => string ,
1521 bannedWords : readonly string [ ] ,
@@ -31,7 +37,7 @@ function getNewRound(
3137type PreGameState = Readonly < {
3238 phase : "pre-game" ;
3339 bannedWords : readonly string [ ] | null ;
34- wordPack : readonly string [ ] | null ;
40+ wordPacks : Record < string , WordPack > ;
3541} > ;
3642
3743type InGameState = Readonly < {
@@ -40,15 +46,15 @@ type InGameState = Readonly<{
4046 finishedRounds : readonly Round [ ] ;
4147 guess : string ;
4248 bannedWords : readonly string [ ] ;
43- wordPack : readonly string [ ] ;
49+ wordPacks : Record < string , WordPack > ;
4450 getNextWord : ( ) => string ;
4551} > ;
4652
4753type PostGameState = {
4854 phase : "post-game" ;
4955 finishedRounds : readonly Round [ ] ;
5056 bannedWords : readonly string [ ] ;
51- wordPack : readonly string [ ] ;
57+ wordPacks : Record < string , WordPack > ;
5258} ;
5359
5460export type State = PreGameState | InGameState | PostGameState ;
@@ -66,19 +72,38 @@ function getNewRoundState(state: InGameState, didGuess: boolean): InGameState {
6672}
6773
6874export function getInitialState ( ) : State {
69- return { phase : "pre-game" , bannedWords : null , wordPack : null } ;
75+ return {
76+ phase : "pre-game" ,
77+ bannedWords : null ,
78+ wordPacks : { } ,
79+ } ;
7080}
7181
7282export type Action =
83+ | { type : "change-guess" ; newGuess : string }
7384 | { type : "end-game" }
7485 | { type : "load-banned-words" ; bannedWords : readonly string [ ] }
75- | { type : "load-word-pack" ; wordPack : readonly string [ ] }
86+ | { type : "load-word-pack" ; wordPack : WordPack }
7687 | { type : "skip-word" }
77- | { type : "start-game" }
78- | { type : "update-guess" ; newGuess : string } ;
88+ | { type : "start-game" ; selectedWordPackId : string } ;
7989
8090export function reducer ( state : State , action : Action ) : State {
8191 switch ( action . type ) {
92+ case "change-guess" : {
93+ // No-op if not in a game.
94+ if ( state . phase !== "in-game" ) {
95+ return state ;
96+ }
97+
98+ if (
99+ normalizeString ( action . newGuess ) === state . currentRound . wordUnscrambled
100+ ) {
101+ return getNewRoundState ( state , true ) ;
102+ }
103+
104+ return { ...state , guess : action . newGuess } ;
105+ }
106+
82107 case "end-game" : {
83108 // No-op if not in a game.
84109 if ( state . phase !== "in-game" ) {
@@ -89,7 +114,7 @@ export function reducer(state: State, action: Action): State {
89114 phase : "post-game" ,
90115 finishedRounds : [ ...state . finishedRounds , state . currentRound ] ,
91116 bannedWords : state . bannedWords ,
92- wordPack : state . wordPack ,
117+ wordPacks : state . wordPacks ,
93118 } ;
94119 }
95120
@@ -103,12 +128,18 @@ export function reducer(state: State, action: Action): State {
103128 }
104129
105130 case "load-word-pack" : {
106- // No-op if not in pre-game phase, or if we already have a word pack .
107- if ( state . phase !== "pre-game" || state . wordPack ) {
131+ // No-op if not in pre-game phase.
132+ if ( state . phase !== "pre-game" ) {
108133 return state ;
109134 }
110135
111- return { ...state , wordPack : action . wordPack } ;
136+ return {
137+ ...state ,
138+ wordPacks : {
139+ ...state . wordPacks ,
140+ [ action . wordPack . id ] : action . wordPack ,
141+ } ,
142+ } ;
112143 }
113144
114145 case "skip-word" : {
@@ -126,38 +157,25 @@ export function reducer(state: State, action: Action): State {
126157 return state ;
127158 }
128159
129- // No-op if data is not loaded.
130- const { bannedWords, wordPack } = state ;
131- if ( bannedWords == null || wordPack == null ) {
160+ const { wordPacks, bannedWords } = state ;
161+
162+ // No-op if word pack or banned words aren't loaded.
163+ const wordPack = wordPacks [ action . selectedWordPackId ] ;
164+ if ( wordPack == null || bannedWords == null ) {
132165 return state ;
133166 }
134167
135- const getNextWord = shuffleInfinitely ( wordPack ) ;
168+ const getNextWord = shuffleInfinitely ( wordPack . words ) ;
136169 return {
137170 phase : "in-game" ,
138171 currentRound : getNewRound ( getNextWord , bannedWords ) ,
139172 finishedRounds : [ ] ,
140173 guess : "" ,
141174 bannedWords,
142- wordPack,
143175 getNextWord,
176+ wordPacks,
144177 } ;
145178 }
146-
147- case "update-guess" : {
148- // No-op if not in a game.
149- if ( state . phase !== "in-game" ) {
150- return state ;
151- }
152-
153- if (
154- normalizeString ( action . newGuess ) === state . currentRound . wordUnscrambled
155- ) {
156- return getNewRoundState ( state , true ) ;
157- }
158-
159- return { ...state , guess : action . newGuess } ;
160- }
161179 }
162180
163181 // This should never happen!
0 commit comments