@sudobility/sudojo_lib 0.0.201 → 0.0.203
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/admin/adminApi.d.ts +34 -0
- package/dist/admin/adminApi.d.ts.map +1 -0
- package/dist/admin/adminApi.js +59 -0
- package/dist/admin/adminApi.js.map +1 -0
- package/dist/admin/boardRules.d.ts +75 -0
- package/dist/admin/boardRules.d.ts.map +1 -0
- package/dist/admin/boardRules.js +127 -0
- package/dist/admin/boardRules.js.map +1 -0
- package/dist/admin/boardStrings.d.ts +55 -0
- package/dist/admin/boardStrings.d.ts.map +1 -0
- package/dist/admin/boardStrings.js +99 -0
- package/dist/admin/boardStrings.js.map +1 -0
- package/dist/admin/examples.d.ts +58 -0
- package/dist/admin/examples.d.ts.map +1 -0
- package/dist/admin/examples.js +104 -0
- package/dist/admin/examples.js.map +1 -0
- package/dist/admin/extraction.d.ts +87 -0
- package/dist/admin/extraction.d.ts.map +1 -0
- package/dist/admin/extraction.js +141 -0
- package/dist/admin/extraction.js.map +1 -0
- package/dist/admin/index.d.ts +32 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +22 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/jobs.d.ts +108 -0
- package/dist/admin/jobs.d.ts.map +1 -0
- package/dist/admin/jobs.js +435 -0
- package/dist/admin/jobs.js.map +1 -0
- package/dist/admin/regenerateHints.d.ts +16 -0
- package/dist/admin/regenerateHints.d.ts.map +1 -0
- package/dist/admin/regenerateHints.js +43 -0
- package/dist/admin/regenerateHints.js.map +1 -0
- package/dist/admin/types.d.ts +42 -0
- package/dist/admin/types.d.ts.map +1 -0
- package/dist/admin/types.js +21 -0
- package/dist/admin/types.js.map +1 -0
- package/dist/admin/useAdminJob.d.ts +28 -0
- package/dist/admin/useAdminJob.d.ts.map +1 -0
- package/dist/admin/useAdminJob.js +99 -0
- package/dist/admin/useAdminJob.js.map +1 -0
- package/dist/admin/useAdminStats.d.ts +46 -0
- package/dist/admin/useAdminStats.d.ts.map +1 -0
- package/dist/admin/useAdminStats.js +91 -0
- package/dist/admin/useAdminStats.js.map +1 -0
- package/dist/admin/useBoardGenerator.d.ts +29 -0
- package/dist/admin/useBoardGenerator.d.ts.map +1 -0
- package/dist/admin/useBoardGenerator.js +38 -0
- package/dist/admin/useBoardGenerator.js.map +1 -0
- package/dist/admin/useBoardTechniquesUpdater.d.ts +14 -0
- package/dist/admin/useBoardTechniquesUpdater.d.ts.map +1 -0
- package/dist/admin/useBoardTechniquesUpdater.js +28 -0
- package/dist/admin/useBoardTechniquesUpdater.js.map +1 -0
- package/dist/admin/useExampleCreator.d.ts +29 -0
- package/dist/admin/useExampleCreator.d.ts.map +1 -0
- package/dist/admin/useExampleCreator.js +58 -0
- package/dist/admin/useExampleCreator.js.map +1 -0
- package/dist/admin/useSingleBoardExtractor.d.ts +18 -0
- package/dist/admin/useSingleBoardExtractor.d.ts.map +1 -0
- package/dist/admin/useSingleBoardExtractor.js +19 -0
- package/dist/admin/useSingleBoardExtractor.js.map +1 -0
- package/dist/admin/useTechniqueExtractor.d.ts +26 -0
- package/dist/admin/useTechniqueExtractor.d.ts.map +1 -0
- package/dist/admin/useTechniqueExtractor.js +37 -0
- package/dist/admin/useTechniqueExtractor.js.map +1 -0
- package/dist/context/SudojoApiContext.d.ts +58 -0
- package/dist/context/SudojoApiContext.d.ts.map +1 -0
- package/dist/context/SudojoApiContext.js +52 -0
- package/dist/context/SudojoApiContext.js.map +1 -0
- package/dist/context/SudojoApiProvider.d.ts +21 -0
- package/dist/context/SudojoApiProvider.d.ts.map +1 -0
- package/dist/context/SudojoApiProvider.js +15 -0
- package/dist/context/SudojoApiProvider.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +2 -0
- package/dist/context/index.js.map +1 -1
- package/dist/hooks/index.d.ts +24 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +22 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useBoardEntry.d.ts +65 -5
- package/dist/hooks/useBoardEntry.d.ts.map +1 -1
- package/dist/hooks/useBoardEntry.js +124 -27
- package/dist/hooks/useBoardEntry.js.map +1 -1
- package/dist/hooks/useBoardScan.d.ts +27 -0
- package/dist/hooks/useBoardScan.d.ts.map +1 -0
- package/dist/hooks/useBoardScan.js +27 -0
- package/dist/hooks/useBoardScan.js.map +1 -0
- package/dist/hooks/useCommunities.d.ts +6 -4
- package/dist/hooks/useCommunities.d.ts.map +1 -1
- package/dist/hooks/useCommunities.js +6 -1
- package/dist/hooks/useCommunities.js.map +1 -1
- package/dist/hooks/useCompletionTrigger.d.ts +23 -0
- package/dist/hooks/useCompletionTrigger.d.ts.map +1 -0
- package/dist/hooks/useCompletionTrigger.js +39 -0
- package/dist/hooks/useCompletionTrigger.js.map +1 -0
- package/dist/hooks/useDailyGame.d.ts +11 -6
- package/dist/hooks/useDailyGame.d.ts.map +1 -1
- package/dist/hooks/useDailyGame.js +20 -61
- package/dist/hooks/useDailyGame.js.map +1 -1
- package/dist/hooks/useDeleteAccount.d.ts +72 -0
- package/dist/hooks/useDeleteAccount.d.ts.map +1 -0
- package/dist/hooks/useDeleteAccount.js +71 -0
- package/dist/hooks/useDeleteAccount.js.map +1 -0
- package/dist/hooks/useEnteredGameSession.d.ts +73 -0
- package/dist/hooks/useEnteredGameSession.d.ts.map +1 -0
- package/dist/hooks/useEnteredGameSession.js +155 -0
- package/dist/hooks/useEnteredGameSession.js.map +1 -0
- package/dist/hooks/useGameSession.d.ts +8 -4
- package/dist/hooks/useGameSession.d.ts.map +1 -1
- package/dist/hooks/useGameSession.js +4 -1
- package/dist/hooks/useGameSession.js.map +1 -1
- package/dist/hooks/useGameTeaching.d.ts.map +1 -1
- package/dist/hooks/useGameTeaching.js +11 -13
- package/dist/hooks/useGameTeaching.js.map +1 -1
- package/dist/hooks/useHint.d.ts +7 -7
- package/dist/hooks/useHint.d.ts.map +1 -1
- package/dist/hooks/useHint.js +18 -9
- package/dist/hooks/useHint.js.map +1 -1
- package/dist/hooks/useIsSiteAdmin.d.ts +39 -0
- package/dist/hooks/useIsSiteAdmin.d.ts.map +1 -0
- package/dist/hooks/useIsSiteAdmin.js +31 -0
- package/dist/hooks/useIsSiteAdmin.js.map +1 -0
- package/dist/hooks/useLearning.d.ts +8 -8
- package/dist/hooks/useLearning.d.ts.map +1 -1
- package/dist/hooks/useLearning.js +5 -2
- package/dist/hooks/useLearning.js.map +1 -1
- package/dist/hooks/useLevelGame.d.ts +7 -7
- package/dist/hooks/useLevelGame.d.ts.map +1 -1
- package/dist/hooks/useLevelGame.js +13 -49
- package/dist/hooks/useLevelGame.js.map +1 -1
- package/dist/hooks/useLevels.d.ts +8 -8
- package/dist/hooks/useLevels.d.ts.map +1 -1
- package/dist/hooks/useLevels.js +7 -3
- package/dist/hooks/useLevels.js.map +1 -1
- package/dist/hooks/usePracticeGame.d.ts +53 -0
- package/dist/hooks/usePracticeGame.d.ts.map +1 -0
- package/dist/hooks/usePracticeGame.js +72 -0
- package/dist/hooks/usePracticeGame.js.map +1 -0
- package/dist/hooks/usePuzzleDistribution.d.ts +2 -0
- package/dist/hooks/usePuzzleDistribution.d.ts.map +1 -1
- package/dist/hooks/usePuzzleDistribution.js +4 -14
- package/dist/hooks/usePuzzleDistribution.js.map +1 -1
- package/dist/hooks/usePuzzleProgress.d.ts +37 -0
- package/dist/hooks/usePuzzleProgress.d.ts.map +1 -0
- package/dist/hooks/usePuzzleProgress.js +48 -0
- package/dist/hooks/usePuzzleProgress.js.map +1 -0
- package/dist/hooks/usePuzzleSession.d.ts +215 -0
- package/dist/hooks/usePuzzleSession.d.ts.map +1 -0
- package/dist/hooks/usePuzzleSession.js +358 -0
- package/dist/hooks/usePuzzleSession.js.map +1 -0
- package/dist/hooks/useResumeGame.d.ts +70 -0
- package/dist/hooks/useResumeGame.d.ts.map +1 -0
- package/dist/hooks/useResumeGame.js +123 -0
- package/dist/hooks/useResumeGame.js.map +1 -0
- package/dist/hooks/useStrategies.d.ts +39 -0
- package/dist/hooks/useStrategies.d.ts.map +1 -0
- package/dist/hooks/useStrategies.js +34 -0
- package/dist/hooks/useStrategies.js.map +1 -0
- package/dist/hooks/useSudoku.d.ts +5 -0
- package/dist/hooks/useSudoku.d.ts.map +1 -1
- package/dist/hooks/useSudoku.js +7 -1
- package/dist/hooks/useSudoku.js.map +1 -1
- package/dist/hooks/useTechniqueByPath.d.ts +46 -0
- package/dist/hooks/useTechniqueByPath.d.ts.map +1 -0
- package/dist/hooks/useTechniqueByPath.js +47 -0
- package/dist/hooks/useTechniqueByPath.js.map +1 -0
- package/dist/hooks/useTechniqueExample.d.ts +60 -0
- package/dist/hooks/useTechniqueExample.d.ts.map +1 -0
- package/dist/hooks/useTechniqueExample.js +63 -0
- package/dist/hooks/useTechniqueExample.js.map +1 -0
- package/dist/hooks/useTechniques.d.ts +8 -8
- package/dist/hooks/useTechniques.d.ts.map +1 -1
- package/dist/hooks/useTechniques.js +9 -21
- package/dist/hooks/useTechniques.js.map +1 -1
- package/dist/index.d.ts +418 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +276 -10
- package/dist/index.js.map +1 -1
- package/dist/types/currentGame.d.ts +9 -1
- package/dist/types/currentGame.d.ts.map +1 -1
- package/dist/types/currentGame.js.map +1 -1
- package/dist/utils/auth.d.ts +6 -0
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +8 -0
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/community.d.ts +64 -0
- package/dist/utils/community.d.ts.map +1 -0
- package/dist/utils/community.js +66 -0
- package/dist/utils/community.js.map +1 -0
- package/dist/utils/date.d.ts +37 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +68 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/entityTranslate.d.ts +120 -0
- package/dist/utils/entityTranslate.d.ts.map +1 -0
- package/dist/utils/entityTranslate.js +146 -0
- package/dist/utils/entityTranslate.js.map +1 -0
- package/dist/utils/gameFetchStatus.d.ts +74 -0
- package/dist/utils/gameFetchStatus.d.ts.map +1 -0
- package/dist/utils/gameFetchStatus.js +118 -0
- package/dist/utils/gameFetchStatus.js.map +1 -0
- package/dist/utils/gamification.d.ts +36 -0
- package/dist/utils/gamification.d.ts.map +1 -0
- package/dist/utils/gamification.js +33 -0
- package/dist/utils/gamification.js.map +1 -0
- package/dist/utils/hintAccess.d.ts +64 -0
- package/dist/utils/hintAccess.d.ts.map +1 -0
- package/dist/utils/hintAccess.js +57 -0
- package/dist/utils/hintAccess.js.map +1 -0
- package/dist/utils/index.d.ts +31 -10
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +20 -9
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/language.d.ts +123 -0
- package/dist/utils/language.d.ts.map +1 -0
- package/dist/utils/language.js +95 -0
- package/dist/utils/language.js.map +1 -0
- package/dist/utils/localizedHint.d.ts +60 -0
- package/dist/utils/localizedHint.d.ts.map +1 -1
- package/dist/utils/localizedHint.js +90 -0
- package/dist/utils/localizedHint.js.map +1 -1
- package/dist/utils/progress.d.ts +10 -1
- package/dist/utils/progress.d.ts.map +1 -1
- package/dist/utils/progress.js +24 -0
- package/dist/utils/progress.js.map +1 -1
- package/dist/utils/scannedBoard.d.ts +79 -0
- package/dist/utils/scannedBoard.d.ts.map +1 -0
- package/dist/utils/scannedBoard.js +96 -0
- package/dist/utils/scannedBoard.js.map +1 -0
- package/dist/utils/shareUrl.d.ts +38 -15
- package/dist/utils/shareUrl.d.ts.map +1 -1
- package/dist/utils/shareUrl.js +75 -26
- package/dist/utils/shareUrl.js.map +1 -1
- package/dist/utils/subscription.d.ts +118 -0
- package/dist/utils/subscription.d.ts.map +1 -1
- package/dist/utils/subscription.js +212 -0
- package/dist/utils/subscription.js.map +1 -1
- package/dist/utils/sudokuPresenter.d.ts +42 -0
- package/dist/utils/sudokuPresenter.d.ts.map +1 -1
- package/dist/utils/sudokuPresenter.js +114 -1
- package/dist/utils/sudokuPresenter.js.map +1 -1
- package/dist/utils/sudokuScrambler.d.ts +8 -0
- package/dist/utils/sudokuScrambler.d.ts.map +1 -1
- package/dist/utils/sudokuScrambler.js +12 -0
- package/dist/utils/sudokuScrambler.js.map +1 -1
- package/dist/utils/technique.d.ts +112 -1
- package/dist/utils/technique.d.ts.map +1 -1
- package/dist/utils/technique.js +293 -1
- package/dist/utils/technique.js.map +1 -1
- package/dist/utils/techniqueBitmask.d.ts +31 -0
- package/dist/utils/techniqueBitmask.d.ts.map +1 -0
- package/dist/utils/techniqueBitmask.js +53 -0
- package/dist/utils/techniqueBitmask.js.map +1 -0
- package/dist/utils/techniqueExample.d.ts +42 -0
- package/dist/utils/techniqueExample.d.ts.map +1 -0
- package/dist/utils/techniqueExample.js +57 -0
- package/dist/utils/techniqueExample.js.map +1 -0
- package/dist/utils/theme.d.ts +2 -0
- package/dist/utils/theme.d.ts.map +1 -1
- package/dist/utils/theme.js +2 -0
- package/dist/utils/theme.js.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imperative network surface for the admin jobs, built only from
|
|
3
|
+
* `@sudobility/sudojo_client` hooks (never a hand-made SudojoClient).
|
|
4
|
+
*
|
|
5
|
+
* - Writes, /solve, /validate, /generate and the boards list use mutation
|
|
6
|
+
* hooks' `mutateAsync` (per-call arguments).
|
|
7
|
+
* - Fixed-key reads (levels, techniques) use a disabled query hook's
|
|
8
|
+
* `refetch()`, which fetches regardless of `enabled`/`staleTime`.
|
|
9
|
+
*
|
|
10
|
+
* The jobs depend on the {@link AdminApi} interface, not on the hooks, so
|
|
11
|
+
* they can be driven by a fake in tests.
|
|
12
|
+
*/
|
|
13
|
+
import type { BaseResponse, Board, BoardCreateRequest, BoardQueryParams, BoardUpdateRequest, GenerateData, GenerateOptions, Level, SolveData, SolveOptions, Technique, TechniqueExample, TechniqueExampleCreateRequest, TechniquePractice, TechniquePracticeCreateRequest, ValidateData, ValidateOptions } from '@sudobility/sudojo_types';
|
|
14
|
+
import type { NetworkClient } from '@sudobility/types';
|
|
15
|
+
/** Network calls the admin jobs make. `token` is the admin ID token. */
|
|
16
|
+
export interface AdminApi {
|
|
17
|
+
solve(token: string, options: SolveOptions): Promise<BaseResponse<SolveData>>;
|
|
18
|
+
validate(token: string, options: ValidateOptions): Promise<BaseResponse<ValidateData>>;
|
|
19
|
+
generate(token: string, options: GenerateOptions): Promise<BaseResponse<GenerateData>>;
|
|
20
|
+
getBoards(token: string, queryParams: BoardQueryParams): Promise<BaseResponse<Board[]>>;
|
|
21
|
+
getLevels(): Promise<BaseResponse<Level[]>>;
|
|
22
|
+
getTechniques(): Promise<BaseResponse<Technique[]>>;
|
|
23
|
+
createBoard(token: string, data: BoardCreateRequest): Promise<BaseResponse<Board>>;
|
|
24
|
+
updateBoard(token: string, uuid: string, data: BoardUpdateRequest): Promise<BaseResponse<Board>>;
|
|
25
|
+
createExample(token: string, data: TechniqueExampleCreateRequest): Promise<BaseResponse<TechniqueExample>>;
|
|
26
|
+
createPractice(token: string, data: TechniquePracticeCreateRequest): Promise<BaseResponse<TechniquePractice>>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build an {@link AdminApi} from sudojo_client hooks. The returned object is
|
|
30
|
+
* stable for the component's lifetime and always calls the latest hook
|
|
31
|
+
* results, so a long-running job can hold on to it.
|
|
32
|
+
*/
|
|
33
|
+
export declare function useAdminApi(networkClient: NetworkClient, baseUrl: string): AdminApi;
|
|
34
|
+
//# sourceMappingURL=adminApi.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adminApi.d.ts","sourceRoot":"","sources":["../../src/admin/adminApi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAcH,OAAO,KAAK,EACV,YAAY,EACZ,KAAK,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,KAAK,EACL,SAAS,EACT,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,6BAA6B,EAC7B,iBAAiB,EACjB,8BAA8B,EAC9B,YAAY,EACZ,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,wEAAwE;AACxE,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IAC9E,QAAQ,CACN,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC;IACvC,QAAQ,CACN,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC;IACvC,SAAS,CACP,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,gBAAgB,GAC5B,OAAO,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAClC,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5C,aAAa,IAAI,OAAO,CAAC,YAAY,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IACpD,WAAW,CACT,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAChC,WAAW,CACT,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAChC,aAAa,CACX,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,6BAA6B,GAClC,OAAO,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC3C,cAAc,CACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC,CAAC;CAC7C;AAWD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,aAAa,EAAE,aAAa,EAC5B,OAAO,EAAE,MAAM,GACd,QAAQ,CA2CV"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imperative network surface for the admin jobs, built only from
|
|
3
|
+
* `@sudobility/sudojo_client` hooks (never a hand-made SudojoClient).
|
|
4
|
+
*
|
|
5
|
+
* - Writes, /solve, /validate, /generate and the boards list use mutation
|
|
6
|
+
* hooks' `mutateAsync` (per-call arguments).
|
|
7
|
+
* - Fixed-key reads (levels, techniques) use a disabled query hook's
|
|
8
|
+
* `refetch()`, which fetches regardless of `enabled`/`staleTime`.
|
|
9
|
+
*
|
|
10
|
+
* The jobs depend on the {@link AdminApi} interface, not on the hooks, so
|
|
11
|
+
* they can be driven by a fake in tests.
|
|
12
|
+
*/
|
|
13
|
+
import { useSolverGenerateMutation, useSolverSolveMutation, useSolverValidateMutation, useSudojoCreateBoard, useSudojoCreateExample, useSudojoCreatePractice, useSudojoFetchBoards, useSudojoLevels, useSudojoTechniques, useSudojoUpdateBoard, } from '@sudobility/sudojo_client';
|
|
14
|
+
import { useMemo, useRef } from 'react';
|
|
15
|
+
async function dataOrThrow(refetch) {
|
|
16
|
+
const result = await refetch();
|
|
17
|
+
if (result.error)
|
|
18
|
+
throw result.error;
|
|
19
|
+
if (result.data === undefined)
|
|
20
|
+
throw new Error('No response');
|
|
21
|
+
return result.data;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build an {@link AdminApi} from sudojo_client hooks. The returned object is
|
|
25
|
+
* stable for the component's lifetime and always calls the latest hook
|
|
26
|
+
* results, so a long-running job can hold on to it.
|
|
27
|
+
*/
|
|
28
|
+
export function useAdminApi(networkClient, baseUrl) {
|
|
29
|
+
const hooks = {
|
|
30
|
+
solve: useSolverSolveMutation(networkClient, baseUrl),
|
|
31
|
+
validate: useSolverValidateMutation(networkClient, baseUrl),
|
|
32
|
+
generate: useSolverGenerateMutation(networkClient, baseUrl),
|
|
33
|
+
fetchBoards: useSudojoFetchBoards(networkClient, baseUrl),
|
|
34
|
+
createBoard: useSudojoCreateBoard(networkClient, baseUrl),
|
|
35
|
+
updateBoard: useSudojoUpdateBoard(networkClient, baseUrl),
|
|
36
|
+
createExample: useSudojoCreateExample(networkClient, baseUrl),
|
|
37
|
+
createPractice: useSudojoCreatePractice(networkClient, baseUrl),
|
|
38
|
+
// Public GETs: no token needed. Disabled; read with refetch().
|
|
39
|
+
levels: useSudojoLevels(networkClient, baseUrl, '', { enabled: false }),
|
|
40
|
+
techniques: useSudojoTechniques(networkClient, baseUrl, '', undefined, {
|
|
41
|
+
enabled: false,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
const latest = useRef(hooks);
|
|
45
|
+
latest.current = hooks;
|
|
46
|
+
return useMemo(() => ({
|
|
47
|
+
solve: (token, options) => latest.current.solve.mutateAsync({ token, options }),
|
|
48
|
+
validate: (token, options) => latest.current.validate.mutateAsync({ token, options }),
|
|
49
|
+
generate: (token, options) => latest.current.generate.mutateAsync({ token, options }),
|
|
50
|
+
getBoards: (token, queryParams) => latest.current.fetchBoards.mutateAsync({ token, queryParams }),
|
|
51
|
+
getLevels: () => dataOrThrow(() => latest.current.levels.refetch()),
|
|
52
|
+
getTechniques: () => dataOrThrow(() => latest.current.techniques.refetch()),
|
|
53
|
+
createBoard: (token, data) => latest.current.createBoard.mutateAsync({ token, data }),
|
|
54
|
+
updateBoard: (token, uuid, data) => latest.current.updateBoard.mutateAsync({ token, uuid, data }),
|
|
55
|
+
createExample: (token, data) => latest.current.createExample.mutateAsync({ token, data }),
|
|
56
|
+
createPractice: (token, data) => latest.current.createPractice.mutateAsync({ token, data }),
|
|
57
|
+
}), []);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=adminApi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adminApi.js","sourceRoot":"","sources":["../../src/admin/adminApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Imperative network surface for the admin jobs, built only from\n * `@sudobility/sudojo_client` hooks (never a hand-made SudojoClient).\n *\n * - Writes, /solve, /validate, /generate and the boards list use mutation\n * hooks' `mutateAsync` (per-call arguments).\n * - Fixed-key reads (levels, techniques) use a disabled query hook's\n * `refetch()`, which fetches regardless of `enabled`/`staleTime`.\n *\n * The jobs depend on the {@link AdminApi} interface, not on the hooks, so\n * they can be driven by a fake in tests.\n */\n\nimport {\n useSolverGenerateMutation,\n useSolverSolveMutation,\n useSolverValidateMutation,\n useSudojoCreateBoard,\n useSudojoCreateExample,\n useSudojoCreatePractice,\n useSudojoFetchBoards,\n useSudojoLevels,\n useSudojoTechniques,\n useSudojoUpdateBoard,\n} from '@sudobility/sudojo_client';\nimport type {\n BaseResponse,\n Board,\n BoardCreateRequest,\n BoardQueryParams,\n BoardUpdateRequest,\n GenerateData,\n GenerateOptions,\n Level,\n SolveData,\n SolveOptions,\n Technique,\n TechniqueExample,\n TechniqueExampleCreateRequest,\n TechniquePractice,\n TechniquePracticeCreateRequest,\n ValidateData,\n ValidateOptions,\n} from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useMemo, useRef } from 'react';\n\n/** Network calls the admin jobs make. `token` is the admin ID token. */\nexport interface AdminApi {\n solve(token: string, options: SolveOptions): Promise<BaseResponse<SolveData>>;\n validate(\n token: string,\n options: ValidateOptions\n ): Promise<BaseResponse<ValidateData>>;\n generate(\n token: string,\n options: GenerateOptions\n ): Promise<BaseResponse<GenerateData>>;\n getBoards(\n token: string,\n queryParams: BoardQueryParams\n ): Promise<BaseResponse<Board[]>>;\n getLevels(): Promise<BaseResponse<Level[]>>;\n getTechniques(): Promise<BaseResponse<Technique[]>>;\n createBoard(\n token: string,\n data: BoardCreateRequest\n ): Promise<BaseResponse<Board>>;\n updateBoard(\n token: string,\n uuid: string,\n data: BoardUpdateRequest\n ): Promise<BaseResponse<Board>>;\n createExample(\n token: string,\n data: TechniqueExampleCreateRequest\n ): Promise<BaseResponse<TechniqueExample>>;\n createPractice(\n token: string,\n data: TechniquePracticeCreateRequest\n ): Promise<BaseResponse<TechniquePractice>>;\n}\n\nasync function dataOrThrow<T>(\n refetch: () => Promise<{ data?: T | undefined; error?: Error | null }>\n): Promise<T> {\n const result = await refetch();\n if (result.error) throw result.error;\n if (result.data === undefined) throw new Error('No response');\n return result.data;\n}\n\n/**\n * Build an {@link AdminApi} from sudojo_client hooks. The returned object is\n * stable for the component's lifetime and always calls the latest hook\n * results, so a long-running job can hold on to it.\n */\nexport function useAdminApi(\n networkClient: NetworkClient,\n baseUrl: string\n): AdminApi {\n const hooks = {\n solve: useSolverSolveMutation(networkClient, baseUrl),\n validate: useSolverValidateMutation(networkClient, baseUrl),\n generate: useSolverGenerateMutation(networkClient, baseUrl),\n fetchBoards: useSudojoFetchBoards(networkClient, baseUrl),\n createBoard: useSudojoCreateBoard(networkClient, baseUrl),\n updateBoard: useSudojoUpdateBoard(networkClient, baseUrl),\n createExample: useSudojoCreateExample(networkClient, baseUrl),\n createPractice: useSudojoCreatePractice(networkClient, baseUrl),\n // Public GETs: no token needed. Disabled; read with refetch().\n levels: useSudojoLevels(networkClient, baseUrl, '', { enabled: false }),\n techniques: useSudojoTechniques(networkClient, baseUrl, '', undefined, {\n enabled: false,\n }),\n };\n const latest = useRef(hooks);\n latest.current = hooks;\n\n return useMemo<AdminApi>(\n () => ({\n solve: (token, options) =>\n latest.current.solve.mutateAsync({ token, options }),\n validate: (token, options) =>\n latest.current.validate.mutateAsync({ token, options }),\n generate: (token, options) =>\n latest.current.generate.mutateAsync({ token, options }),\n getBoards: (token, queryParams) =>\n latest.current.fetchBoards.mutateAsync({ token, queryParams }),\n getLevels: () => dataOrThrow(() => latest.current.levels.refetch()),\n getTechniques: () =>\n dataOrThrow(() => latest.current.techniques.refetch()),\n createBoard: (token, data) =>\n latest.current.createBoard.mutateAsync({ token, data }),\n updateBoard: (token, uuid, data) =>\n latest.current.updateBoard.mutateAsync({ token, uuid, data }),\n createExample: (token, data) =>\n latest.current.createExample.mutateAsync({ token, data }),\n createPractice: (token, data) =>\n latest.current.createPractice.mutateAsync({ token, data }),\n }),\n []\n );\n}\n"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure rules for the board jobs: reading a /generate response, turning a
|
|
3
|
+
* /validate result into a create request, the board query filters, and the
|
|
4
|
+
* frontend-vs-solver comparison of the technique extractor.
|
|
5
|
+
*
|
|
6
|
+
* Masks are exact `bigint`s here and go on the wire as base-10 strings
|
|
7
|
+
* (`formatTechniqueBitmask`): a JSON number drops the low bits once any
|
|
8
|
+
* technique id >= 54 is set.
|
|
9
|
+
*
|
|
10
|
+
* Ported from `sudojo_app/src/utils/admin/BoardGenerator.ts`,
|
|
11
|
+
* `TechniqueExtractor.ts` and `boardTechniquesApi.ts`.
|
|
12
|
+
*/
|
|
13
|
+
import { type BoardCreateRequest, type BoardQueryParams, type BoardUpdateRequest, type TechniqueId, type ValidateData } from '@sudobility/sudojo_types';
|
|
14
|
+
import type { BoardExtractionResult } from './extraction';
|
|
15
|
+
/** Puzzle read from a /generate response. */
|
|
16
|
+
export interface GeneratedPuzzle {
|
|
17
|
+
level: number;
|
|
18
|
+
original: string;
|
|
19
|
+
solution: string;
|
|
20
|
+
}
|
|
21
|
+
export type ParseGeneratedBoardResult = ({
|
|
22
|
+
ok: true;
|
|
23
|
+
} & GeneratedPuzzle) | {
|
|
24
|
+
ok: false;
|
|
25
|
+
reason: 'invalid' | 'missing-puzzle';
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Read level/original/solution from a /generate `data`, tolerating the older
|
|
29
|
+
* shapes the web app accepted: `{ board: { level, board: {original, …} } }`,
|
|
30
|
+
* `{ board: { level, original, … } }` and `{ level, original, … }`.
|
|
31
|
+
* `invalid` = no numeric level; `missing-puzzle` = no original.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseGeneratedBoard(data: unknown): ParseGeneratedBoardResult;
|
|
34
|
+
/** A board ready to save, with the values reported back to the UI. */
|
|
35
|
+
export interface ValidatedBoard {
|
|
36
|
+
request: BoardCreateRequest;
|
|
37
|
+
level: number;
|
|
38
|
+
/** Exact bitmask (bit N = technique N) */
|
|
39
|
+
techniques: bigint;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create request for a generated board. The /validate result is the source of
|
|
43
|
+
* truth for level, techniques and solution; the /generate values are only a
|
|
44
|
+
* fallback when /validate omits them.
|
|
45
|
+
*/
|
|
46
|
+
export declare function buildValidatedBoard(generated: GeneratedPuzzle, validateData: ValidateData | undefined | null, symmetrical: boolean): ValidatedBoard;
|
|
47
|
+
/** Update request that sets only a board's techniques bitmask and level. */
|
|
48
|
+
export declare function buildBoardTechniquesUpdate(techniques: bigint, level: number | null): BoardUpdateRequest;
|
|
49
|
+
/** Filter for the next board whose techniques were never extracted. */
|
|
50
|
+
export declare function boardsWithoutTechniquesQuery(): BoardQueryParams;
|
|
51
|
+
/**
|
|
52
|
+
* Filter for boards with the technique's bit set (API `technique_bit`, sent
|
|
53
|
+
* as an exact string). Not filtered by level: boards with the technique may
|
|
54
|
+
* sit at higher levels.
|
|
55
|
+
*/
|
|
56
|
+
export declare function boardsWithTechniqueQuery(techniqueId: TechniqueId, limit: number): BoardQueryParams;
|
|
57
|
+
/** Filter for boards at one level. */
|
|
58
|
+
export declare function boardsAtLevelQuery(level: number, limit: number): BoardQueryParams;
|
|
59
|
+
/** Frontend extraction disagreed with /validate. */
|
|
60
|
+
export interface ExtractionMismatch {
|
|
61
|
+
solverTechniques: bigint;
|
|
62
|
+
solverLevel: number;
|
|
63
|
+
message: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Compare a frontend extraction with /validate. Exact bigint comparison: the
|
|
67
|
+
* numeric `techniques` drops low bits above 2^53, which would hide mismatches.
|
|
68
|
+
* Returns null when techniques and level both match.
|
|
69
|
+
*/
|
|
70
|
+
export declare function compareExtractionWithSolver(boardUuid: string, frontend: BoardExtractionResult, solver: {
|
|
71
|
+
level: number;
|
|
72
|
+
techniques: number;
|
|
73
|
+
techniques_bitmask?: string;
|
|
74
|
+
}): ExtractionMismatch | null;
|
|
75
|
+
//# sourceMappingURL=boardRules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boardRules.d.ts","sourceRoot":"","sources":["../../src/admin/boardRules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EAGvB,KAAK,WAAW,EAEhB,KAAK,YAAY,EAClB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,6CAA6C;AAC7C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,yBAAyB,GACjC,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,eAAe,CAAC,GAChC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,SAAS,GAAG,gBAAgB,CAAA;CAAE,CAAC;AAExD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,GAAG,yBAAyB,CAe5E;AAeD,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,eAAe,EAC1B,YAAY,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,EAC7C,WAAW,EAAE,OAAO,GACnB,cAAc,CAgBhB;AAED,4EAA4E;AAC5E,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GAAG,IAAI,GACnB,kBAAkB,CAQpB;AAWD,uEAAuE;AACvE,wBAAgB,4BAA4B,IAAI,gBAAgB,CAE/D;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,MAAM,GACZ,gBAAgB,CAMlB;AAED,sCAAsC;AACtC,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,gBAAgB,CAElB;AAED,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,qBAAqB,EAC/B,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAAE,GACzE,kBAAkB,GAAG,IAAI,CAgB3B"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure rules for the board jobs: reading a /generate response, turning a
|
|
3
|
+
* /validate result into a create request, the board query filters, and the
|
|
4
|
+
* frontend-vs-solver comparison of the technique extractor.
|
|
5
|
+
*
|
|
6
|
+
* Masks are exact `bigint`s here and go on the wire as base-10 strings
|
|
7
|
+
* (`formatTechniqueBitmask`): a JSON number drops the low bits once any
|
|
8
|
+
* technique id >= 54 is set.
|
|
9
|
+
*
|
|
10
|
+
* Ported from `sudojo_app/src/utils/admin/BoardGenerator.ts`,
|
|
11
|
+
* `TechniqueExtractor.ts` and `boardTechniquesApi.ts`.
|
|
12
|
+
*/
|
|
13
|
+
import { formatTechniqueBitmask, techniqueBitmaskOf, techniqueIdsToBitmask, } from '@sudobility/sudojo_types';
|
|
14
|
+
import { formatTechniqueMaskHex, shortUuid } from './boardStrings';
|
|
15
|
+
/**
|
|
16
|
+
* Read level/original/solution from a /generate `data`, tolerating the older
|
|
17
|
+
* shapes the web app accepted: `{ board: { level, board: {original, …} } }`,
|
|
18
|
+
* `{ board: { level, original, … } }` and `{ level, original, … }`.
|
|
19
|
+
* `invalid` = no numeric level; `missing-puzzle` = no original.
|
|
20
|
+
*/
|
|
21
|
+
export function parseGeneratedBoard(data) {
|
|
22
|
+
const root = asRecord(data);
|
|
23
|
+
const nested = asRecord(root?.board);
|
|
24
|
+
const boardData = nested?.level !== undefined ? nested : root;
|
|
25
|
+
if (!boardData || typeof boardData.level !== 'number') {
|
|
26
|
+
return { ok: false, reason: 'invalid' };
|
|
27
|
+
}
|
|
28
|
+
const puzzleData = asRecord(boardData.board) ?? boardData;
|
|
29
|
+
const inner = asRecord(puzzleData.board);
|
|
30
|
+
const original = firstString(puzzleData.original, inner?.original);
|
|
31
|
+
const solution = firstString(puzzleData.solution, inner?.solution);
|
|
32
|
+
if (!original)
|
|
33
|
+
return { ok: false, reason: 'missing-puzzle' };
|
|
34
|
+
return { ok: true, level: boardData.level, original, solution };
|
|
35
|
+
}
|
|
36
|
+
function asRecord(value) {
|
|
37
|
+
return typeof value === 'object' && value !== null
|
|
38
|
+
? value
|
|
39
|
+
: null;
|
|
40
|
+
}
|
|
41
|
+
function firstString(...values) {
|
|
42
|
+
for (const value of values) {
|
|
43
|
+
if (typeof value === 'string' && value)
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create request for a generated board. The /validate result is the source of
|
|
50
|
+
* truth for level, techniques and solution; the /generate values are only a
|
|
51
|
+
* fallback when /validate omits them.
|
|
52
|
+
*/
|
|
53
|
+
export function buildValidatedBoard(generated, validateData, symmetrical) {
|
|
54
|
+
const board = validateData?.board;
|
|
55
|
+
const level = board?.level ?? generated.level;
|
|
56
|
+
// Prefers the exact `techniques_bitmask` string over the lossy number.
|
|
57
|
+
const techniques = board ? techniqueBitmaskOf(board) : 0n;
|
|
58
|
+
return {
|
|
59
|
+
request: {
|
|
60
|
+
board: generated.original,
|
|
61
|
+
solution: board?.solution ?? generated.solution,
|
|
62
|
+
level,
|
|
63
|
+
symmetrical,
|
|
64
|
+
techniques: formatTechniqueBitmask(techniques),
|
|
65
|
+
},
|
|
66
|
+
level,
|
|
67
|
+
techniques,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Update request that sets only a board's techniques bitmask and level. */
|
|
71
|
+
export function buildBoardTechniquesUpdate(techniques, level) {
|
|
72
|
+
return {
|
|
73
|
+
techniques: formatTechniqueBitmask(techniques),
|
|
74
|
+
level,
|
|
75
|
+
symmetrical: undefined,
|
|
76
|
+
board: undefined,
|
|
77
|
+
solution: undefined,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const NO_BOARD_FILTERS = {
|
|
81
|
+
level: undefined,
|
|
82
|
+
symmetrical: undefined,
|
|
83
|
+
limit: undefined,
|
|
84
|
+
offset: undefined,
|
|
85
|
+
techniques: undefined,
|
|
86
|
+
technique_bit: undefined,
|
|
87
|
+
};
|
|
88
|
+
/** Filter for the next board whose techniques were never extracted. */
|
|
89
|
+
export function boardsWithoutTechniquesQuery() {
|
|
90
|
+
return { ...NO_BOARD_FILTERS, techniques: 0, limit: 1 };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Filter for boards with the technique's bit set (API `technique_bit`, sent
|
|
94
|
+
* as an exact string). Not filtered by level: boards with the technique may
|
|
95
|
+
* sit at higher levels.
|
|
96
|
+
*/
|
|
97
|
+
export function boardsWithTechniqueQuery(techniqueId, limit) {
|
|
98
|
+
return {
|
|
99
|
+
...NO_BOARD_FILTERS,
|
|
100
|
+
limit,
|
|
101
|
+
technique_bit: formatTechniqueBitmask(techniqueIdsToBitmask([techniqueId])),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Filter for boards at one level. */
|
|
105
|
+
export function boardsAtLevelQuery(level, limit) {
|
|
106
|
+
return { ...NO_BOARD_FILTERS, level, limit };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Compare a frontend extraction with /validate. Exact bigint comparison: the
|
|
110
|
+
* numeric `techniques` drops low bits above 2^53, which would hide mismatches.
|
|
111
|
+
* Returns null when techniques and level both match.
|
|
112
|
+
*/
|
|
113
|
+
export function compareExtractionWithSolver(boardUuid, frontend, solver) {
|
|
114
|
+
const solverTechniques = techniqueBitmaskOf(solver);
|
|
115
|
+
if (solverTechniques === frontend.techniques &&
|
|
116
|
+
solver.level === frontend.level) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
solverTechniques,
|
|
121
|
+
solverLevel: solver.level,
|
|
122
|
+
message: `MISMATCH: Board ${shortUuid(boardUuid)} - ` +
|
|
123
|
+
`FE techniques ${formatTechniqueMaskHex(frontend.techniques)} level ${frontend.level} vs ` +
|
|
124
|
+
`Solver techniques ${formatTechniqueMaskHex(solverTechniques)} level ${solver.level}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=boardRules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boardRules.js","sourceRoot":"","sources":["../../src/admin/boardRules.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure rules for the board jobs: reading a /generate response, turning a\n * /validate result into a create request, the board query filters, and the\n * frontend-vs-solver comparison of the technique extractor.\n *\n * Masks are exact `bigint`s here and go on the wire as base-10 strings\n * (`formatTechniqueBitmask`): a JSON number drops the low bits once any\n * technique id >= 54 is set.\n *\n * Ported from `sudojo_app/src/utils/admin/BoardGenerator.ts`,\n * `TechniqueExtractor.ts` and `boardTechniquesApi.ts`.\n */\n\nimport {\n type BoardCreateRequest,\n type BoardQueryParams,\n type BoardUpdateRequest,\n formatTechniqueBitmask,\n techniqueBitmaskOf,\n type TechniqueId,\n techniqueIdsToBitmask,\n type ValidateData,\n} from '@sudobility/sudojo_types';\nimport { formatTechniqueMaskHex, shortUuid } from './boardStrings';\nimport type { BoardExtractionResult } from './extraction';\n\n/** Puzzle read from a /generate response. */\nexport interface GeneratedPuzzle {\n level: number;\n original: string;\n solution: string;\n}\n\nexport type ParseGeneratedBoardResult =\n | ({ ok: true } & GeneratedPuzzle)\n | { ok: false; reason: 'invalid' | 'missing-puzzle' };\n\n/**\n * Read level/original/solution from a /generate `data`, tolerating the older\n * shapes the web app accepted: `{ board: { level, board: {original, …} } }`,\n * `{ board: { level, original, … } }` and `{ level, original, … }`.\n * `invalid` = no numeric level; `missing-puzzle` = no original.\n */\nexport function parseGeneratedBoard(data: unknown): ParseGeneratedBoardResult {\n const root = asRecord(data);\n const nested = asRecord(root?.board);\n const boardData = nested?.level !== undefined ? nested : root;\n if (!boardData || typeof boardData.level !== 'number') {\n return { ok: false, reason: 'invalid' };\n }\n\n const puzzleData = asRecord(boardData.board) ?? boardData;\n const inner = asRecord(puzzleData.board);\n const original = firstString(puzzleData.original, inner?.original);\n const solution = firstString(puzzleData.solution, inner?.solution);\n if (!original) return { ok: false, reason: 'missing-puzzle' };\n\n return { ok: true, level: boardData.level, original, solution };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return typeof value === 'object' && value !== null\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction firstString(...values: unknown[]): string {\n for (const value of values) {\n if (typeof value === 'string' && value) return value;\n }\n return '';\n}\n\n/** A board ready to save, with the values reported back to the UI. */\nexport interface ValidatedBoard {\n request: BoardCreateRequest;\n level: number;\n /** Exact bitmask (bit N = technique N) */\n techniques: bigint;\n}\n\n/**\n * Create request for a generated board. The /validate result is the source of\n * truth for level, techniques and solution; the /generate values are only a\n * fallback when /validate omits them.\n */\nexport function buildValidatedBoard(\n generated: GeneratedPuzzle,\n validateData: ValidateData | undefined | null,\n symmetrical: boolean\n): ValidatedBoard {\n const board = validateData?.board;\n const level = board?.level ?? generated.level;\n // Prefers the exact `techniques_bitmask` string over the lossy number.\n const techniques = board ? techniqueBitmaskOf(board) : 0n;\n return {\n request: {\n board: generated.original,\n solution: board?.solution ?? generated.solution,\n level,\n symmetrical,\n techniques: formatTechniqueBitmask(techniques),\n },\n level,\n techniques,\n };\n}\n\n/** Update request that sets only a board's techniques bitmask and level. */\nexport function buildBoardTechniquesUpdate(\n techniques: bigint,\n level: number | null\n): BoardUpdateRequest {\n return {\n techniques: formatTechniqueBitmask(techniques),\n level,\n symmetrical: undefined,\n board: undefined,\n solution: undefined,\n };\n}\n\nconst NO_BOARD_FILTERS: BoardQueryParams = {\n level: undefined,\n symmetrical: undefined,\n limit: undefined,\n offset: undefined,\n techniques: undefined,\n technique_bit: undefined,\n};\n\n/** Filter for the next board whose techniques were never extracted. */\nexport function boardsWithoutTechniquesQuery(): BoardQueryParams {\n return { ...NO_BOARD_FILTERS, techniques: 0, limit: 1 };\n}\n\n/**\n * Filter for boards with the technique's bit set (API `technique_bit`, sent\n * as an exact string). Not filtered by level: boards with the technique may\n * sit at higher levels.\n */\nexport function boardsWithTechniqueQuery(\n techniqueId: TechniqueId,\n limit: number\n): BoardQueryParams {\n return {\n ...NO_BOARD_FILTERS,\n limit,\n technique_bit: formatTechniqueBitmask(techniqueIdsToBitmask([techniqueId])),\n };\n}\n\n/** Filter for boards at one level. */\nexport function boardsAtLevelQuery(\n level: number,\n limit: number\n): BoardQueryParams {\n return { ...NO_BOARD_FILTERS, level, limit };\n}\n\n/** Frontend extraction disagreed with /validate. */\nexport interface ExtractionMismatch {\n solverTechniques: bigint;\n solverLevel: number;\n message: string;\n}\n\n/**\n * Compare a frontend extraction with /validate. Exact bigint comparison: the\n * numeric `techniques` drops low bits above 2^53, which would hide mismatches.\n * Returns null when techniques and level both match.\n */\nexport function compareExtractionWithSolver(\n boardUuid: string,\n frontend: BoardExtractionResult,\n solver: { level: number; techniques: number; techniques_bitmask?: string }\n): ExtractionMismatch | null {\n const solverTechniques = techniqueBitmaskOf(solver);\n if (\n solverTechniques === frontend.techniques &&\n solver.level === frontend.level\n ) {\n return null;\n }\n return {\n solverTechniques,\n solverLevel: solver.level,\n message:\n `MISMATCH: Board ${shortUuid(boardUuid)} - ` +\n `FE techniques ${formatTechniqueMaskHex(frontend.techniques)} level ${frontend.level} vs ` +\n `Solver techniques ${formatTechniqueMaskHex(solverTechniques)} level ${solver.level}`,\n };\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers on 81-char board strings and technique bitmasks used by the
|
|
3
|
+
* admin jobs.
|
|
4
|
+
*
|
|
5
|
+
* Ported from `sudojo_app/src/utils/admin/utils.ts` and the private helpers
|
|
6
|
+
* of `ExampleCreator.ts` / `TechniqueExtractor.ts`.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* True when every cell has a digit, taking the user's digit over the given.
|
|
10
|
+
*
|
|
11
|
+
* @param original - 81-char original puzzle (0 = empty)
|
|
12
|
+
* @param user - 81-char user input (0 = no input)
|
|
13
|
+
*/
|
|
14
|
+
export declare function isPuzzleFilled(original: string, user: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* True when every cell has a digit AND matches the solution.
|
|
17
|
+
*
|
|
18
|
+
* @param original - 81-char original puzzle (0 = empty)
|
|
19
|
+
* @param user - 81-char user input (0 = no input)
|
|
20
|
+
* @param solution - 81-char full solution
|
|
21
|
+
*/
|
|
22
|
+
export declare function isPuzzleSolved(original: string, user: string, solution: string): boolean;
|
|
23
|
+
/** True when any solver hint step is the "Invalid Pencilmarks" correction. */
|
|
24
|
+
export declare function hasInvalidPencilmarksStep(steps: ReadonlyArray<{
|
|
25
|
+
title?: string;
|
|
26
|
+
}> | undefined | null): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Overlay user input on the original board: each cell takes the user's digit
|
|
29
|
+
* when it is not '0', else the original's. Always returns 81 chars.
|
|
30
|
+
*
|
|
31
|
+
* The solver's `user` string never contains the givens, so a technique example
|
|
32
|
+
* must be stored as `mergeBoardWithUserInput(board.board, found.userInput)` or
|
|
33
|
+
* it comes out near-empty.
|
|
34
|
+
*/
|
|
35
|
+
export declare function mergeBoardWithUserInput(original: string, userInput: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Solution for a practice built from `mergedBoard`: '0' wherever the merged
|
|
38
|
+
* board already has a digit, else the solution's digit. Always 81 chars.
|
|
39
|
+
*/
|
|
40
|
+
export declare function adjustPracticeSolution(mergedBoard: string, solution: string): string;
|
|
41
|
+
/** `0x…` form of an exact technique bitmask, for progress lines. */
|
|
42
|
+
export declare function formatTechniqueMaskHex(mask: bigint): string;
|
|
43
|
+
/** Log-friendly forms of an exact bitmask (decimal, binary, hex). */
|
|
44
|
+
export declare function describeTechniqueMask(mask: bigint): {
|
|
45
|
+
techniques: string;
|
|
46
|
+
techniquesBinary: string;
|
|
47
|
+
techniquesHex: string;
|
|
48
|
+
};
|
|
49
|
+
/** Level to store for a board: positive levels as-is, 0 or less as null. */
|
|
50
|
+
export declare function levelToSave(level: number): number | null;
|
|
51
|
+
/** First 8 chars of a UUID, as used in every admin progress line. */
|
|
52
|
+
export declare function shortUuid(uuid: string): string;
|
|
53
|
+
/** Message of an unknown thrown value. */
|
|
54
|
+
export declare function errorMessage(err: unknown): string;
|
|
55
|
+
//# sourceMappingURL=boardStrings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boardStrings.d.ts","sourceRoot":"","sources":["../../src/admin/boardStrings.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAOT;AAED,8EAA8E;AAC9E,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,aAAa,CAAC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,GAC1D,OAAO,CAGT;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,MAAM,CAOR;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,MAAM,CAMR;AAED,oEAAoE;AACpE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,qEAAqE;AACrE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;CACvB,CAMA;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAExD;AAED,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,0CAA0C;AAC1C,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAEjD"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers on 81-char board strings and technique bitmasks used by the
|
|
3
|
+
* admin jobs.
|
|
4
|
+
*
|
|
5
|
+
* Ported from `sudojo_app/src/utils/admin/utils.ts` and the private helpers
|
|
6
|
+
* of `ExampleCreator.ts` / `TechniqueExtractor.ts`.
|
|
7
|
+
*/
|
|
8
|
+
import { formatTechniqueBitmask, TOTAL_CELLS } from '@sudobility/sudojo_types';
|
|
9
|
+
/** Digit at `index`, or '0' when the string is short. */
|
|
10
|
+
const cellAt = (board, index) => board[index] || '0';
|
|
11
|
+
/**
|
|
12
|
+
* True when every cell has a digit, taking the user's digit over the given.
|
|
13
|
+
*
|
|
14
|
+
* @param original - 81-char original puzzle (0 = empty)
|
|
15
|
+
* @param user - 81-char user input (0 = no input)
|
|
16
|
+
*/
|
|
17
|
+
export function isPuzzleFilled(original, user) {
|
|
18
|
+
for (let i = 0; i < TOTAL_CELLS; i++) {
|
|
19
|
+
const userChar = cellAt(user, i);
|
|
20
|
+
const actual = userChar !== '0' ? userChar : cellAt(original, i);
|
|
21
|
+
if (actual === '0')
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* True when every cell has a digit AND matches the solution.
|
|
28
|
+
*
|
|
29
|
+
* @param original - 81-char original puzzle (0 = empty)
|
|
30
|
+
* @param user - 81-char user input (0 = no input)
|
|
31
|
+
* @param solution - 81-char full solution
|
|
32
|
+
*/
|
|
33
|
+
export function isPuzzleSolved(original, user, solution) {
|
|
34
|
+
for (let i = 0; i < TOTAL_CELLS; i++) {
|
|
35
|
+
const userChar = cellAt(user, i);
|
|
36
|
+
const actual = userChar !== '0' ? userChar : cellAt(original, i);
|
|
37
|
+
if (actual === '0' || actual !== cellAt(solution, i))
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
/** True when any solver hint step is the "Invalid Pencilmarks" correction. */
|
|
43
|
+
export function hasInvalidPencilmarksStep(steps) {
|
|
44
|
+
if (!steps)
|
|
45
|
+
return false;
|
|
46
|
+
return steps.some(step => step.title === 'Invalid Pencilmarks');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Overlay user input on the original board: each cell takes the user's digit
|
|
50
|
+
* when it is not '0', else the original's. Always returns 81 chars.
|
|
51
|
+
*
|
|
52
|
+
* The solver's `user` string never contains the givens, so a technique example
|
|
53
|
+
* must be stored as `mergeBoardWithUserInput(board.board, found.userInput)` or
|
|
54
|
+
* it comes out near-empty.
|
|
55
|
+
*/
|
|
56
|
+
export function mergeBoardWithUserInput(original, userInput) {
|
|
57
|
+
let result = '';
|
|
58
|
+
for (let i = 0; i < TOTAL_CELLS; i++) {
|
|
59
|
+
const userChar = cellAt(userInput, i);
|
|
60
|
+
result += userChar !== '0' ? userChar : cellAt(original, i);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Solution for a practice built from `mergedBoard`: '0' wherever the merged
|
|
66
|
+
* board already has a digit, else the solution's digit. Always 81 chars.
|
|
67
|
+
*/
|
|
68
|
+
export function adjustPracticeSolution(mergedBoard, solution) {
|
|
69
|
+
let result = '';
|
|
70
|
+
for (let i = 0; i < TOTAL_CELLS; i++) {
|
|
71
|
+
result += cellAt(mergedBoard, i) !== '0' ? '0' : cellAt(solution, i);
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
/** `0x…` form of an exact technique bitmask, for progress lines. */
|
|
76
|
+
export function formatTechniqueMaskHex(mask) {
|
|
77
|
+
return `0x${mask.toString(16)}`;
|
|
78
|
+
}
|
|
79
|
+
/** Log-friendly forms of an exact bitmask (decimal, binary, hex). */
|
|
80
|
+
export function describeTechniqueMask(mask) {
|
|
81
|
+
return {
|
|
82
|
+
techniques: formatTechniqueBitmask(mask),
|
|
83
|
+
techniquesBinary: `0b${mask.toString(2)}`,
|
|
84
|
+
techniquesHex: formatTechniqueMaskHex(mask),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Level to store for a board: positive levels as-is, 0 or less as null. */
|
|
88
|
+
export function levelToSave(level) {
|
|
89
|
+
return level > 0 ? level : null;
|
|
90
|
+
}
|
|
91
|
+
/** First 8 chars of a UUID, as used in every admin progress line. */
|
|
92
|
+
export function shortUuid(uuid) {
|
|
93
|
+
return uuid.slice(0, 8);
|
|
94
|
+
}
|
|
95
|
+
/** Message of an unknown thrown value. */
|
|
96
|
+
export function errorMessage(err) {
|
|
97
|
+
return err instanceof Error ? err.message : String(err);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=boardStrings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boardStrings.js","sourceRoot":"","sources":["../../src/admin/boardStrings.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pure helpers on 81-char board strings and technique bitmasks used by the\n * admin jobs.\n *\n * Ported from `sudojo_app/src/utils/admin/utils.ts` and the private helpers\n * of `ExampleCreator.ts` / `TechniqueExtractor.ts`.\n */\n\nimport { formatTechniqueBitmask, TOTAL_CELLS } from '@sudobility/sudojo_types';\n\n/** Digit at `index`, or '0' when the string is short. */\nconst cellAt = (board: string, index: number): string => board[index] || '0';\n\n/**\n * True when every cell has a digit, taking the user's digit over the given.\n *\n * @param original - 81-char original puzzle (0 = empty)\n * @param user - 81-char user input (0 = no input)\n */\nexport function isPuzzleFilled(original: string, user: string): boolean {\n for (let i = 0; i < TOTAL_CELLS; i++) {\n const userChar = cellAt(user, i);\n const actual = userChar !== '0' ? userChar : cellAt(original, i);\n if (actual === '0') return false;\n }\n return true;\n}\n\n/**\n * True when every cell has a digit AND matches the solution.\n *\n * @param original - 81-char original puzzle (0 = empty)\n * @param user - 81-char user input (0 = no input)\n * @param solution - 81-char full solution\n */\nexport function isPuzzleSolved(\n original: string,\n user: string,\n solution: string\n): boolean {\n for (let i = 0; i < TOTAL_CELLS; i++) {\n const userChar = cellAt(user, i);\n const actual = userChar !== '0' ? userChar : cellAt(original, i);\n if (actual === '0' || actual !== cellAt(solution, i)) return false;\n }\n return true;\n}\n\n/** True when any solver hint step is the \"Invalid Pencilmarks\" correction. */\nexport function hasInvalidPencilmarksStep(\n steps: ReadonlyArray<{ title?: string }> | undefined | null\n): boolean {\n if (!steps) return false;\n return steps.some(step => step.title === 'Invalid Pencilmarks');\n}\n\n/**\n * Overlay user input on the original board: each cell takes the user's digit\n * when it is not '0', else the original's. Always returns 81 chars.\n *\n * The solver's `user` string never contains the givens, so a technique example\n * must be stored as `mergeBoardWithUserInput(board.board, found.userInput)` or\n * it comes out near-empty.\n */\nexport function mergeBoardWithUserInput(\n original: string,\n userInput: string\n): string {\n let result = '';\n for (let i = 0; i < TOTAL_CELLS; i++) {\n const userChar = cellAt(userInput, i);\n result += userChar !== '0' ? userChar : cellAt(original, i);\n }\n return result;\n}\n\n/**\n * Solution for a practice built from `mergedBoard`: '0' wherever the merged\n * board already has a digit, else the solution's digit. Always 81 chars.\n */\nexport function adjustPracticeSolution(\n mergedBoard: string,\n solution: string\n): string {\n let result = '';\n for (let i = 0; i < TOTAL_CELLS; i++) {\n result += cellAt(mergedBoard, i) !== '0' ? '0' : cellAt(solution, i);\n }\n return result;\n}\n\n/** `0x…` form of an exact technique bitmask, for progress lines. */\nexport function formatTechniqueMaskHex(mask: bigint): string {\n return `0x${mask.toString(16)}`;\n}\n\n/** Log-friendly forms of an exact bitmask (decimal, binary, hex). */\nexport function describeTechniqueMask(mask: bigint): {\n techniques: string;\n techniquesBinary: string;\n techniquesHex: string;\n} {\n return {\n techniques: formatTechniqueBitmask(mask),\n techniquesBinary: `0b${mask.toString(2)}`,\n techniquesHex: formatTechniqueMaskHex(mask),\n };\n}\n\n/** Level to store for a board: positive levels as-is, 0 or less as null. */\nexport function levelToSave(level: number): number | null {\n return level > 0 ? level : null;\n}\n\n/** First 8 chars of a UUID, as used in every admin progress line. */\nexport function shortUuid(uuid: string): string {\n return uuid.slice(0, 8);\n}\n\n/** Message of an unknown thrown value. */\nexport function errorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure rules for the technique-example creator: which techniques still need
|
|
3
|
+
* examples, where to search for boards, and the example + linked practice
|
|
4
|
+
* requests built from a find.
|
|
5
|
+
*
|
|
6
|
+
* Ported from `sudojo_app/src/utils/admin/ExampleCreator.ts` and the
|
|
7
|
+
* constants of `sudojo_app/src/pages/AdminPage.tsx`.
|
|
8
|
+
*/
|
|
9
|
+
import { type Board, type Technique, type TechniqueExampleCreateRequest, type TechniqueId, type TechniquePracticeCreateRequest } from '@sudobility/sudojo_types';
|
|
10
|
+
import type { TechniqueExampleFound } from './extraction';
|
|
11
|
+
/** Examples wanted per technique. */
|
|
12
|
+
export declare const ADMIN_EXAMPLE_TARGET_PER_TECHNIQUE = 1;
|
|
13
|
+
/** Every technique id, easiest (lowest id) first: the creator's work order. */
|
|
14
|
+
export declare const ADMIN_TECHNIQUE_ORDER: readonly TechniqueId[];
|
|
15
|
+
/** Boards fetched per technique by bit. */
|
|
16
|
+
export declare const BOARDS_WITH_TECHNIQUE_LIMIT = 50;
|
|
17
|
+
/** Boards fetched per level in the level-based search. */
|
|
18
|
+
export declare const BOARDS_PER_LEVEL_LIMIT = 100;
|
|
19
|
+
/** Example counts keyed by technique id. */
|
|
20
|
+
export type TechniqueCounts = Readonly<Record<number, number>>;
|
|
21
|
+
/**
|
|
22
|
+
* Normalize an API counts map (string keys, e.g. `/examples/counts`) to
|
|
23
|
+
* numeric technique-id keys. null/undefined → {}.
|
|
24
|
+
*/
|
|
25
|
+
export declare function normalizeTechniqueCounts(data: Readonly<Record<string, number>> | Readonly<Record<number, number>> | null | undefined): Record<number, number>;
|
|
26
|
+
/** Count for a technique (0 when absent). */
|
|
27
|
+
export declare function countForTechnique(counts: TechniqueCounts, techniqueId: number): number;
|
|
28
|
+
/** True when the technique already has `target` examples. */
|
|
29
|
+
export declare function hasReachedTarget(counts: TechniqueCounts, techniqueId: number, target: number): boolean;
|
|
30
|
+
/** Techniques in `order` that still need examples. */
|
|
31
|
+
export declare function techniquesNeedingExamples(order: readonly TechniqueId[], counts: TechniqueCounts, target: number): TechniqueId[];
|
|
32
|
+
/** Sum of all counts. */
|
|
33
|
+
export declare function totalTechniqueCount(counts: TechniqueCounts): number;
|
|
34
|
+
/** technique id → level (null when the technique has no level). */
|
|
35
|
+
export declare function buildTechniqueLevelMap(techniques: readonly Technique[] | null | undefined): Map<number, number | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Levels to search, in order, for a technique at `techniqueLevel`: one below,
|
|
38
|
+
* its own, then two above (a technique often first appears on harder
|
|
39
|
+
* boards). Clamped to MIN_LEVEL..MAX_LEVEL.
|
|
40
|
+
*/
|
|
41
|
+
export declare function levelsToSearchForTechnique(techniqueLevel: number): number[];
|
|
42
|
+
/** Requests to save a find: the example, then (after it) the linked practice. */
|
|
43
|
+
export interface ExampleSaveRequests {
|
|
44
|
+
/** Source givens + the cells the solver filled before reaching the technique */
|
|
45
|
+
mergedBoard: string;
|
|
46
|
+
example: TechniqueExampleCreateRequest;
|
|
47
|
+
/** Practice request without `source_example_uuid` (set it from the example) */
|
|
48
|
+
practice: TechniquePracticeCreateRequest;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the example and practice requests for a find on `board`.
|
|
52
|
+
*
|
|
53
|
+
* `found.userInput` holds only the solver's cells (never the givens), so the
|
|
54
|
+
* stored board is `mergeBoardWithUserInput(board.board, found.userInput)`.
|
|
55
|
+
* The practice's solution is blanked ('0') wherever that board is filled.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildExampleSaveRequests(board: Pick<Board, 'uuid' | 'board' | 'solution'>, found: TechniqueExampleFound): ExampleSaveRequests;
|
|
58
|
+
//# sourceMappingURL=examples.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"examples.d.ts","sourceRoot":"","sources":["../../src/admin/examples.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAEL,KAAK,KAAK,EAIV,KAAK,SAAS,EACd,KAAK,6BAA6B,EAClC,KAAK,WAAW,EAEhB,KAAK,8BAA8B,EACpC,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE1D,qCAAqC;AACrC,eAAO,MAAM,kCAAkC,IAAI,CAAC;AAEpD,+EAA+E;AAC/E,eAAO,MAAM,qBAAqB,EAAE,SAAS,WAAW,EAEjC,CAAC;AAExB,2CAA2C;AAC3C,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,0DAA0D;AAC1D,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,4CAA4C;AAC5C,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE/D;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EACA,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAChC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAChC,IAAI,GACJ,SAAS,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB;AAED,6CAA6C;AAC7C,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,MAAM,GAClB,MAAM,CAER;AAED,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAET;AAED,sDAAsD;AACtD,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,MAAM,GACb,WAAW,EAAE,CAEf;AAED,yBAAyB;AACzB,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAEnE;AAED,mEAAmE;AACnE,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,SAAS,SAAS,EAAE,GAAG,IAAI,GAAG,SAAS,GAClD,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAI5B;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAO3E;AAED,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,gFAAgF;IAChF,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,6BAA6B,CAAC;IACvC,+EAA+E;IAC/E,QAAQ,EAAE,8BAA8B,CAAC;CAC1C;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC,EACjD,KAAK,EAAE,qBAAqB,GAC3B,mBAAmB,CA0BrB"}
|