@sudobility/sudojo_lib 0.0.199 → 0.0.201
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/useBoardEntry.d.ts +5 -1
- package/dist/hooks/useBoardEntry.d.ts.map +1 -1
- package/dist/hooks/useBoardEntry.js +4 -1
- package/dist/hooks/useBoardEntry.js.map +1 -1
- package/dist/types/currentGame.d.ts +7 -0
- package/dist/types/currentGame.d.ts.map +1 -1
- package/dist/types/currentGame.js.map +1 -1
- package/package.json +5 -5
|
@@ -7,6 +7,10 @@ import type { NetworkClient } from '@sudobility/types';
|
|
|
7
7
|
export interface ValidatedPuzzle {
|
|
8
8
|
puzzle: string;
|
|
9
9
|
solution: string;
|
|
10
|
+
/** Difficulty level (1-12) the solver rated the puzzle at */
|
|
11
|
+
level?: number;
|
|
12
|
+
/** Solver's difficulty_score for the puzzle, when the API sends it */
|
|
13
|
+
difficultyScore?: number | undefined;
|
|
10
14
|
}
|
|
11
15
|
export interface UseBoardEntryOptions {
|
|
12
16
|
networkClient: NetworkClient;
|
|
@@ -22,7 +26,7 @@ export interface UseBoardEntryReturn {
|
|
|
22
26
|
isValidating: boolean;
|
|
23
27
|
/** Validation error message key */
|
|
24
28
|
validationError: string | null;
|
|
25
|
-
/** Validated puzzle data (puzzle + solution) */
|
|
29
|
+
/** Validated puzzle data (puzzle + solution + solver rating) */
|
|
26
30
|
validatedPuzzle: ValidatedPuzzle | null;
|
|
27
31
|
/** Select a cell by index */
|
|
28
32
|
selectCell: (index: number) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBoardEntry.d.ts","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"useBoardEntry.d.ts","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACtC;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,oCAAoC;IACpC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wCAAwC;IACxC,YAAY,EAAE,OAAO,CAAC;IACtB,mCAAmC;IACnC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gEAAgE;IAChE,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,6BAA6B;IAC7B,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,MAAM,CAAC;IAC9B,yBAAyB;IACzB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAoED,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,OAAO,EACP,KAAK,GACN,EAAE,oBAAoB,GAAG,mBAAmB,CA2L5C"}
|
|
@@ -91,11 +91,14 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
|
|
|
91
91
|
return;
|
|
92
92
|
processedDataRef.current = validateData;
|
|
93
93
|
// ValidateData has board.solution
|
|
94
|
-
const
|
|
94
|
+
const board = validateData.data?.board;
|
|
95
|
+
const solution = board?.solution;
|
|
95
96
|
if (validateData.success && solution) {
|
|
96
97
|
setValidatedPuzzle({
|
|
97
98
|
puzzle: puzzleString,
|
|
98
99
|
solution,
|
|
100
|
+
level: board.level,
|
|
101
|
+
difficultyScore: board.difficulty_score,
|
|
99
102
|
});
|
|
100
103
|
setValidationError(null);
|
|
101
104
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBoardEntry.js","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing Sudoku board entry mode state\n * Allows users to enter given clues for a custom puzzle\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { SudokuCell } from '../types';\nimport { useSolverValidate } from '@sudobility/sudojo_client';\nimport type { NetworkClient } from '@sudobility/types';\n\nexport interface ValidatedPuzzle {\n puzzle: string;\n solution: string;\n}\n\nexport interface UseBoardEntryOptions {\n networkClient: NetworkClient;\n baseUrl: string;\n token: string;\n}\n\nexport interface UseBoardEntryReturn {\n /** 81 cells with given values only */\n cells: SudokuCell[];\n /** Currently selected cell index */\n selectedIndex: number | null;\n /** Whether validation is in progress */\n isValidating: boolean;\n /** Validation error message key */\n validationError: string | null;\n /** Validated puzzle data (puzzle + solution) */\n validatedPuzzle: ValidatedPuzzle | null;\n /** Select a cell by index */\n selectCell: (index: number) => void;\n /** Set a given value at the selected cell */\n setGiven: (value: number) => void;\n /** Erase the selected cell */\n erase: () => void;\n /** Get the puzzle as an 81-character string */\n getPuzzleString: () => string;\n /** Trigger validation */\n validate: () => void;\n /** Reset to empty board */\n reset: () => void;\n /** Number of clues entered */\n clueCount: number;\n /** Set cells from an 81-character puzzle string */\n setCellsFromPuzzle: (puzzle: string) => void;\n}\n\n/**\n * Create an empty 81-cell board for entry mode\n */\nfunction createEmptyBoard(): SudokuCell[] {\n return Array.from({ length: 81 }, (_, index) => ({\n index,\n solution: null,\n given: null,\n input: null,\n pencilmarks: null,\n }));\n}\n\n/**\n * Convert cells to 81-character puzzle string\n * '0' for empty, '1'-'9' for given values\n */\nfunction cellsToPuzzleString(cells: SudokuCell[]): string {\n return cells.map(cell => cell.given?.toString() ?? '0').join('');\n}\n\n/**\n * Count the number of non-empty cells (clues)\n */\nfunction countClues(cells: SudokuCell[]): number {\n return cells.filter(cell => cell.given !== null).length;\n}\n\n/**\n * Create a board from an 81-character puzzle string\n * '0' or '.' for empty, '1'-'9' for given values\n */\nfunction puzzleStringToCells(puzzle: string): SudokuCell[] {\n if (puzzle.length !== 81) {\n return createEmptyBoard();\n }\n\n return Array.from({ length: 81 }, (_, index) => {\n const char = puzzle.charAt(index);\n const value = parseInt(char, 10);\n const given = value >= 1 && value <= 9 ? value : null;\n\n return {\n index,\n solution: null,\n given,\n input: null,\n pencilmarks: null,\n };\n });\n}\n\n/**\n * Classify a validation error message into a translation key\n */\nfunction classifyValidationError(errorMsg: string): string {\n const lower = errorMsg.toLowerCase();\n if (lower.includes('multiple') || lower.includes('not unique')) {\n return 'enter.errors.multipleSolutions';\n }\n if (lower.includes('cannot solve') || lower.includes('no solution')) {\n return 'enter.errors.noSolution';\n }\n return 'enter.errors.validationFailed';\n}\n\nexport function useBoardEntry({\n networkClient,\n baseUrl,\n token,\n}: UseBoardEntryOptions): UseBoardEntryReturn {\n const [cells, setCells] = useState<SudokuCell[]>(createEmptyBoard);\n const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n const [validationError, setValidationError] = useState<string | null>(null);\n const [validatedPuzzle, setValidatedPuzzle] =\n useState<ValidatedPuzzle | null>(null);\n const [shouldValidate, setShouldValidate] = useState(false);\n\n // Compute puzzle string for validation\n const puzzleString = useMemo(() => cellsToPuzzleString(cells), [cells]);\n const clueCount = useMemo(() => countClues(cells), [cells]);\n\n // Use the solver validate hook\n const {\n data: validateData,\n error: validateError,\n isLoading: isValidating,\n } = useSolverValidate(\n networkClient,\n baseUrl,\n token,\n { original: puzzleString },\n {\n enabled: shouldValidate && clueCount >= 17,\n retry: false,\n }\n );\n\n // Track whether we've already processed this validation result\n const processedDataRef = useRef<typeof validateData | null>(null);\n const processedErrorRef = useRef<typeof validateError | null>(null);\n\n // Handle validation result\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateData) return;\n // Avoid re-processing the same result\n if (processedDataRef.current === validateData) return;\n processedDataRef.current = validateData;\n\n // ValidateData has board.solution\n const solution = validateData.data?.board?.solution;\n if (validateData.success && solution) {\n setValidatedPuzzle({\n puzzle: puzzleString,\n solution,\n });\n setValidationError(null);\n } else if (validateData.error) {\n // Validation failed with specific error\n setValidationError(classifyValidationError(validateData.error));\n setValidatedPuzzle(null);\n } else {\n // Unexpected response - success but no solution\n console.error('Unexpected validation response:', validateData);\n setValidationError('enter.errors.validationFailed');\n setValidatedPuzzle(null);\n }\n setShouldValidate(false);\n }, [validateData, shouldValidate, isValidating, puzzleString]);\n\n // Handle network/query errors (e.g. 400 responses throw NetworkError)\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateError) return;\n if (processedErrorRef.current === validateError) return;\n processedErrorRef.current = validateError;\n\n // Extract error message from NetworkError response body or message\n const networkErr = validateError as {\n response?: { error?: string };\n message?: string;\n };\n const errorMsg = networkErr.response?.error ?? networkErr.message ?? '';\n setValidationError(classifyValidationError(errorMsg));\n setValidatedPuzzle(null);\n setShouldValidate(false);\n }, [validateError, shouldValidate, isValidating]);\n\n // Select a cell\n const selectCell = useCallback((index: number) => {\n if (index >= 0 && index < 81) {\n setSelectedIndex(index);\n }\n }, []);\n\n // Set a given value at the selected cell\n const setGiven = useCallback(\n (value: number) => {\n if (selectedIndex === null || value < 1 || value > 9) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: value };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n },\n [selectedIndex]\n );\n\n // Erase the selected cell\n const erase = useCallback(() => {\n if (selectedIndex === null) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: null };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n }, [selectedIndex]);\n\n // Get puzzle string\n const getPuzzleString = useCallback(() => {\n return puzzleString;\n }, [puzzleString]);\n\n // Trigger validation\n const validate = useCallback(() => {\n // Check minimum clues\n if (clueCount < 17) {\n setValidationError('enter.errors.notEnoughClues');\n return;\n }\n\n // Clear previous state and trigger validation\n setValidationError(null);\n setValidatedPuzzle(null);\n // Clear processed refs so cached React Query results are re-processed\n processedDataRef.current = null;\n processedErrorRef.current = null;\n setShouldValidate(true);\n }, [clueCount]);\n\n // Reset to empty board\n const reset = useCallback(() => {\n setCells(createEmptyBoard());\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n processedDataRef.current = null;\n processedErrorRef.current = null;\n }, []);\n\n // Set cells from puzzle string\n const setCellsFromPuzzle = useCallback((puzzle: string) => {\n setCells(puzzleStringToCells(puzzle));\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n processedDataRef.current = null;\n processedErrorRef.current = null;\n }, []);\n\n return {\n cells,\n selectedIndex,\n isValidating,\n validationError,\n validatedPuzzle,\n selectCell,\n setGiven,\n erase,\n getPuzzleString,\n validate,\n reset,\n clueCount,\n setCellsFromPuzzle,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useBoardEntry.js","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing Sudoku board entry mode state\n * Allows users to enter given clues for a custom puzzle\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { SudokuCell } from '../types';\nimport { useSolverValidate } from '@sudobility/sudojo_client';\nimport type { NetworkClient } from '@sudobility/types';\n\nexport interface ValidatedPuzzle {\n puzzle: string;\n solution: string;\n /** Difficulty level (1-12) the solver rated the puzzle at */\n level?: number;\n /** Solver's difficulty_score for the puzzle, when the API sends it */\n difficultyScore?: number | undefined;\n}\n\nexport interface UseBoardEntryOptions {\n networkClient: NetworkClient;\n baseUrl: string;\n token: string;\n}\n\nexport interface UseBoardEntryReturn {\n /** 81 cells with given values only */\n cells: SudokuCell[];\n /** Currently selected cell index */\n selectedIndex: number | null;\n /** Whether validation is in progress */\n isValidating: boolean;\n /** Validation error message key */\n validationError: string | null;\n /** Validated puzzle data (puzzle + solution + solver rating) */\n validatedPuzzle: ValidatedPuzzle | null;\n /** Select a cell by index */\n selectCell: (index: number) => void;\n /** Set a given value at the selected cell */\n setGiven: (value: number) => void;\n /** Erase the selected cell */\n erase: () => void;\n /** Get the puzzle as an 81-character string */\n getPuzzleString: () => string;\n /** Trigger validation */\n validate: () => void;\n /** Reset to empty board */\n reset: () => void;\n /** Number of clues entered */\n clueCount: number;\n /** Set cells from an 81-character puzzle string */\n setCellsFromPuzzle: (puzzle: string) => void;\n}\n\n/**\n * Create an empty 81-cell board for entry mode\n */\nfunction createEmptyBoard(): SudokuCell[] {\n return Array.from({ length: 81 }, (_, index) => ({\n index,\n solution: null,\n given: null,\n input: null,\n pencilmarks: null,\n }));\n}\n\n/**\n * Convert cells to 81-character puzzle string\n * '0' for empty, '1'-'9' for given values\n */\nfunction cellsToPuzzleString(cells: SudokuCell[]): string {\n return cells.map(cell => cell.given?.toString() ?? '0').join('');\n}\n\n/**\n * Count the number of non-empty cells (clues)\n */\nfunction countClues(cells: SudokuCell[]): number {\n return cells.filter(cell => cell.given !== null).length;\n}\n\n/**\n * Create a board from an 81-character puzzle string\n * '0' or '.' for empty, '1'-'9' for given values\n */\nfunction puzzleStringToCells(puzzle: string): SudokuCell[] {\n if (puzzle.length !== 81) {\n return createEmptyBoard();\n }\n\n return Array.from({ length: 81 }, (_, index) => {\n const char = puzzle.charAt(index);\n const value = parseInt(char, 10);\n const given = value >= 1 && value <= 9 ? value : null;\n\n return {\n index,\n solution: null,\n given,\n input: null,\n pencilmarks: null,\n };\n });\n}\n\n/**\n * Classify a validation error message into a translation key\n */\nfunction classifyValidationError(errorMsg: string): string {\n const lower = errorMsg.toLowerCase();\n if (lower.includes('multiple') || lower.includes('not unique')) {\n return 'enter.errors.multipleSolutions';\n }\n if (lower.includes('cannot solve') || lower.includes('no solution')) {\n return 'enter.errors.noSolution';\n }\n return 'enter.errors.validationFailed';\n}\n\nexport function useBoardEntry({\n networkClient,\n baseUrl,\n token,\n}: UseBoardEntryOptions): UseBoardEntryReturn {\n const [cells, setCells] = useState<SudokuCell[]>(createEmptyBoard);\n const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n const [validationError, setValidationError] = useState<string | null>(null);\n const [validatedPuzzle, setValidatedPuzzle] =\n useState<ValidatedPuzzle | null>(null);\n const [shouldValidate, setShouldValidate] = useState(false);\n\n // Compute puzzle string for validation\n const puzzleString = useMemo(() => cellsToPuzzleString(cells), [cells]);\n const clueCount = useMemo(() => countClues(cells), [cells]);\n\n // Use the solver validate hook\n const {\n data: validateData,\n error: validateError,\n isLoading: isValidating,\n } = useSolverValidate(\n networkClient,\n baseUrl,\n token,\n { original: puzzleString },\n {\n enabled: shouldValidate && clueCount >= 17,\n retry: false,\n }\n );\n\n // Track whether we've already processed this validation result\n const processedDataRef = useRef<typeof validateData | null>(null);\n const processedErrorRef = useRef<typeof validateError | null>(null);\n\n // Handle validation result\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateData) return;\n // Avoid re-processing the same result\n if (processedDataRef.current === validateData) return;\n processedDataRef.current = validateData;\n\n // ValidateData has board.solution\n const board = validateData.data?.board;\n const solution = board?.solution;\n if (validateData.success && solution) {\n setValidatedPuzzle({\n puzzle: puzzleString,\n solution,\n level: board.level,\n difficultyScore: board.difficulty_score,\n });\n setValidationError(null);\n } else if (validateData.error) {\n // Validation failed with specific error\n setValidationError(classifyValidationError(validateData.error));\n setValidatedPuzzle(null);\n } else {\n // Unexpected response - success but no solution\n console.error('Unexpected validation response:', validateData);\n setValidationError('enter.errors.validationFailed');\n setValidatedPuzzle(null);\n }\n setShouldValidate(false);\n }, [validateData, shouldValidate, isValidating, puzzleString]);\n\n // Handle network/query errors (e.g. 400 responses throw NetworkError)\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateError) return;\n if (processedErrorRef.current === validateError) return;\n processedErrorRef.current = validateError;\n\n // Extract error message from NetworkError response body or message\n const networkErr = validateError as {\n response?: { error?: string };\n message?: string;\n };\n const errorMsg = networkErr.response?.error ?? networkErr.message ?? '';\n setValidationError(classifyValidationError(errorMsg));\n setValidatedPuzzle(null);\n setShouldValidate(false);\n }, [validateError, shouldValidate, isValidating]);\n\n // Select a cell\n const selectCell = useCallback((index: number) => {\n if (index >= 0 && index < 81) {\n setSelectedIndex(index);\n }\n }, []);\n\n // Set a given value at the selected cell\n const setGiven = useCallback(\n (value: number) => {\n if (selectedIndex === null || value < 1 || value > 9) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: value };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n },\n [selectedIndex]\n );\n\n // Erase the selected cell\n const erase = useCallback(() => {\n if (selectedIndex === null) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: null };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n }, [selectedIndex]);\n\n // Get puzzle string\n const getPuzzleString = useCallback(() => {\n return puzzleString;\n }, [puzzleString]);\n\n // Trigger validation\n const validate = useCallback(() => {\n // Check minimum clues\n if (clueCount < 17) {\n setValidationError('enter.errors.notEnoughClues');\n return;\n }\n\n // Clear previous state and trigger validation\n setValidationError(null);\n setValidatedPuzzle(null);\n // Clear processed refs so cached React Query results are re-processed\n processedDataRef.current = null;\n processedErrorRef.current = null;\n setShouldValidate(true);\n }, [clueCount]);\n\n // Reset to empty board\n const reset = useCallback(() => {\n setCells(createEmptyBoard());\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n processedDataRef.current = null;\n processedErrorRef.current = null;\n }, []);\n\n // Set cells from puzzle string\n const setCellsFromPuzzle = useCallback((puzzle: string) => {\n setCells(puzzleStringToCells(puzzle));\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n processedDataRef.current = null;\n processedErrorRef.current = null;\n }, []);\n\n return {\n cells,\n selectedIndex,\n isValidating,\n validationError,\n validatedPuzzle,\n selectCell,\n setGiven,\n erase,\n getPuzzleString,\n validate,\n reset,\n clueCount,\n setCellsFromPuzzle,\n };\n}\n"]}
|
|
@@ -17,6 +17,13 @@ export interface CurrentGameMeta {
|
|
|
17
17
|
boardUuid?: string;
|
|
18
18
|
/** Techniques bitset for the puzzle */
|
|
19
19
|
techniques?: number | null;
|
|
20
|
+
/**
|
|
21
|
+
* For entered puzzles - the level (1-12) the solver rated it at when it
|
|
22
|
+
* was validated, so a resumed game can still show it without re-validating.
|
|
23
|
+
*/
|
|
24
|
+
level?: number;
|
|
25
|
+
/** For entered puzzles - the solver's difficulty_score, kept for the same reason */
|
|
26
|
+
difficultyScore?: number;
|
|
20
27
|
}
|
|
21
28
|
/** Current game state stored in Zustand */
|
|
22
29
|
export interface CurrentGame {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"currentGame.d.ts","sourceRoot":"","sources":["../../src/types/currentGame.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,iCAAiC;AACjC,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvD,uCAAuC;AACvC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAExC,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"currentGame.d.ts","sourceRoot":"","sources":["../../src/types/currentGame.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,iCAAiC;AACjC,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvD,uCAAuC;AACvC,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAExC,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,IAAI,EAAE,eAAe,CAAC;IACtB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qCAAqC;IACrC,YAAY,EAAE,OAAO,CAAC;IACtB,4CAA4C;IAC5C,eAAe,EAAE,OAAO,CAAC;IACzB,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"currentGame.js","sourceRoot":"","sources":["../../src/types/currentGame.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * Types for the current game feature\n */\n\n/** Source of the current game */\nexport type GameSource = 'daily' | 'level' | 'entered';\n\n/** Which store slot a game lives in */\nexport type GameSlot = 'daily' | 'play';\n\n/** Metadata for navigation back to the game */\nexport interface CurrentGameMeta {\n /** For daily games - the date string (YYYY-MM-DD) */\n dailyDate?: string;\n /** For level games - the level UUID */\n levelId?: string;\n /** Level title for display */\n levelTitle?: string;\n /** Board UUID for the puzzle */\n boardUuid?: string;\n /** Techniques bitset for the puzzle */\n techniques?: number | null;\n}\n\n/** Current game state stored in Zustand */\nexport interface CurrentGame {\n /** Game source type */\n source: GameSource;\n /** Original puzzle string (81 chars) */\n puzzle: string;\n /** Solution string (81 chars) */\n solution: string;\n /** Navigation metadata */\n meta: CurrentGameMeta;\n /** User's current input (81 chars, '0' = empty) */\n inputString: string;\n /** User's pencilmarks (comma-separated format) */\n pencilmarksString: string;\n /** Whether pencil mode was active */\n isPencilMode: boolean;\n /** Whether auto pencilmarks were enabled */\n autoPencilmarks: boolean;\n /** Elapsed time in seconds */\n elapsedTime: number;\n /** When the game was started (ISO string) */\n startedAt: string;\n /** When last updated (ISO string) */\n updatedAt: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"currentGame.js","sourceRoot":"","sources":["../../src/types/currentGame.ts"],"names":[],"mappings":"AAAA;;GAEG","sourcesContent":["/**\n * Types for the current game feature\n */\n\n/** Source of the current game */\nexport type GameSource = 'daily' | 'level' | 'entered';\n\n/** Which store slot a game lives in */\nexport type GameSlot = 'daily' | 'play';\n\n/** Metadata for navigation back to the game */\nexport interface CurrentGameMeta {\n /** For daily games - the date string (YYYY-MM-DD) */\n dailyDate?: string;\n /** For level games - the level UUID */\n levelId?: string;\n /** Level title for display */\n levelTitle?: string;\n /** Board UUID for the puzzle */\n boardUuid?: string;\n /** Techniques bitset for the puzzle */\n techniques?: number | null;\n /**\n * For entered puzzles - the level (1-12) the solver rated it at when it\n * was validated, so a resumed game can still show it without re-validating.\n */\n level?: number;\n /** For entered puzzles - the solver's difficulty_score, kept for the same reason */\n difficultyScore?: number;\n}\n\n/** Current game state stored in Zustand */\nexport interface CurrentGame {\n /** Game source type */\n source: GameSource;\n /** Original puzzle string (81 chars) */\n puzzle: string;\n /** Solution string (81 chars) */\n solution: string;\n /** Navigation metadata */\n meta: CurrentGameMeta;\n /** User's current input (81 chars, '0' = empty) */\n inputString: string;\n /** User's pencilmarks (comma-separated format) */\n pencilmarksString: string;\n /** Whether pencil mode was active */\n isPencilMode: boolean;\n /** Whether auto pencilmarks were enabled */\n autoPencilmarks: boolean;\n /** Elapsed time in seconds */\n elapsedTime: number;\n /** When the game was started (ISO string) */\n startedAt: string;\n /** When last updated (ISO string) */\n updatedAt: string;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudobility/sudojo_lib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.201",
|
|
4
4
|
"description": "Business logic library for Sudojo app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"author": "Sudobility team",
|
|
47
47
|
"license": "BUSL-1.1",
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@sudobility/di": "^1.5.
|
|
50
|
-
"@sudobility/sudojo_client": "^0.0.
|
|
49
|
+
"@sudobility/di": "^1.5.68",
|
|
50
|
+
"@sudobility/sudojo_client": "^0.0.160",
|
|
51
51
|
"@sudobility/sudojo_types": "^1.2.71",
|
|
52
52
|
"@sudobility/types": "^1.9.68",
|
|
53
53
|
"@tanstack/react-query": "^5.90.11",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"zustand": "^5.0.9"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|
|
75
|
-
"@sudobility/di": "^1.5.
|
|
76
|
-
"@sudobility/sudojo_client": "^0.0.
|
|
75
|
+
"@sudobility/di": "^1.5.68",
|
|
76
|
+
"@sudobility/sudojo_client": "^0.0.160",
|
|
77
77
|
"@sudobility/sudojo_types": "^1.2.71",
|
|
78
78
|
"@sudobility/types": "^1.9.68",
|
|
79
79
|
"@tanstack/react-query": ">=5.0.0",
|