@@ -13,8 +13,8 @@ import type {
1313
1414const __dirname = path . dirname ( fileURLToPath ( import . meta. url ) ) ;
1515
16- /** Persisted quiz boards: round 1–3 match `AdeptsBoardId` in the game client. */
17- export type AdeptsPersistedBoardId = 1 | 2 | 3 ;
16+ /** Persisted quiz boards: ids match `AdeptsBoardId` in the game client. */
17+ export type AdeptsPersistedBoardId = 1 | 2 | 3 | 4 ;
1818
1919const QUESTION_KEYS : ( keyof AdeptsQuizQuestionPersisted ) [ ] = [
2020 "text" ,
@@ -31,7 +31,7 @@ const QUESTION_KEYS: (keyof AdeptsQuizQuestionPersisted)[] = [
3131
3232export function parsePersistedBoardId ( raw : unknown ) : AdeptsPersistedBoardId | null {
3333 const n = typeof raw === "string" ? Number ( raw ) : typeof raw === "number" ? raw : NaN ;
34- if ( n === 1 || n === 2 || n === 3 ) return n ;
34+ if ( n === 1 || n === 2 || n === 3 || n === 4 ) return n ;
3535 return null ;
3636}
3737
@@ -51,7 +51,7 @@ export function persistedDataFilePath(boardId: AdeptsPersistedBoardId): string {
5151 return path . resolve ( process . cwd ( ) , "data" , `adepts-quiz-board-${ boardId } .json` ) ;
5252}
5353
54- function parseBoardJson ( raw : string ) : AdeptsQuizBoardPersisted {
54+ function parseBoardJson ( raw : string , boardId : AdeptsPersistedBoardId ) : AdeptsQuizBoardPersisted {
5555 const data = JSON . parse ( raw ) as unknown ;
5656 if ( ! data || typeof data !== "object" ) throw new Error ( "Invalid board JSON" ) ;
5757 const o = data as Record < string , unknown > ;
@@ -64,11 +64,18 @@ function parseBoardJson(raw: string): AdeptsQuizBoardPersisted {
6464 return row . map ( ( cell ) => normalizeQuestion ( cell ) ) ;
6565 } ) ;
6666 if ( themes . length === 0 ) throw new Error ( "At least one theme required" ) ;
67- for ( const row of questions ) {
68- if ( row . length !== 5 ) throw new Error ( "Each theme must have exactly 5 questions" ) ;
69- }
70- if ( questions . length !== themes . length ) {
71- throw new Error ( "themes and questions row counts must match" ) ;
67+ if ( boardId === 4 ) {
68+ if ( themes . length !== 1 ) throw new Error ( "Board 4 must have exactly 1 theme row" ) ;
69+ const row = questions [ 0 ] ;
70+ if ( ! row || row . length !== 4 ) throw new Error ( "Board 4 must have exactly 4 question cells" ) ;
71+ if ( questions . length !== 1 ) throw new Error ( "themes and questions row counts must match" ) ;
72+ } else {
73+ for ( const row of questions ) {
74+ if ( row . length !== 5 ) throw new Error ( "Each theme must have exactly 5 questions" ) ;
75+ }
76+ if ( questions . length !== themes . length ) {
77+ throw new Error ( "themes and questions row counts must match" ) ;
78+ }
7279 }
7380 return { themes, questions } ;
7481}
@@ -144,7 +151,7 @@ function ensurePersistedFileFromSeed(boardId: AdeptsPersistedBoardId): void {
144151 const dir = path . dirname ( target ) ;
145152 mkdirSync ( dir , { recursive : true } ) ;
146153 const seed = readFileSync ( seedFilePath ( boardId ) , "utf8" ) ;
147- parseBoardJson ( seed ) ;
154+ parseBoardJson ( seed , boardId ) ;
148155 writeFileSync ( target , seed , "utf8" ) ;
149156}
150157
@@ -163,7 +170,7 @@ export function readAdeptsQuizBoard(boardId: AdeptsPersistedBoardId): AdeptsQuiz
163170 ensurePersistedFileFromSeed ( boardId ) ;
164171 const target = persistedDataFilePath ( boardId ) ;
165172 const raw = readFileSync ( target , "utf8" ) ;
166- return parseBoardJson ( raw ) ;
173+ return parseBoardJson ( raw , boardId ) ;
167174}
168175
169176export function writeAdeptsQuizBoard (
@@ -212,7 +219,8 @@ export function patchQuestionCell(
212219 ) {
213220 throw new Error ( "Invalid theme index" ) ;
214221 }
215- if ( ! Number . isInteger ( questionIndex ) || questionIndex < 0 || questionIndex >= 5 ) {
222+ const rowLen = board . questions [ themeIndex ] ! . length ;
223+ if ( ! Number . isInteger ( questionIndex ) || questionIndex < 0 || questionIndex >= rowLen ) {
216224 throw new Error ( "Invalid question index" ) ;
217225 }
218226 const row = [ ...board . questions [ themeIndex ] ! ] ;
0 commit comments