@connectorvol/chess-widgets 1.1.0 → 1.2.0
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/position-editor/EditMove.svelte +164 -158
- package/dist/puzzle/puzzleStepPreviewSolver.d.ts +1 -1
- package/dist/puzzle/puzzleStepPreviewSolver.js +4 -1
- package/dist/puzzle-creation/PuzzleCreationWizard.svelte +198 -200
- package/dist/puzzle-creation/PuzzlePgnBoardTreeEditor.svelte +66 -8
- package/dist/puzzle-creation/StepPosition.svelte +196 -190
- package/package.json +11 -8
|
@@ -1,214 +1,212 @@
|
|
|
1
1
|
<script lang="ts" module>
|
|
2
|
-
|
|
2
|
+
export type { TPuzzleCreationWizardProps } from "./types.js";
|
|
3
3
|
</script>
|
|
4
4
|
|
|
5
5
|
<script lang="ts">
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
children: [],
|
|
73
|
-
data: {
|
|
74
|
-
fen,
|
|
75
|
-
san,
|
|
76
|
-
ply: halfMoves,
|
|
77
|
-
fullMoves,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
return { move, fen, turn: chess.turn() };
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const actions: IChessBoardActions = {
|
|
84
|
-
game: {
|
|
85
|
-
possibleMovesOnSquare: (square: Square) => chess.moves(square),
|
|
86
|
-
beforePieceMoveSan(san: string) {
|
|
87
|
-
const result = addMoveToTree(san);
|
|
88
|
-
return result;
|
|
89
|
-
},
|
|
90
|
-
afterPieceMoveSan: () => {},
|
|
91
|
-
beforePieceMove: (from, to, promotion) => {
|
|
92
|
-
const san = chess.getSanForMove({ from, to, promotion });
|
|
93
|
-
chess.makeMove({ from, to, promotion });
|
|
6
|
+
import { untrack } from "svelte";
|
|
7
|
+
|
|
8
|
+
import StepMoves from "./StepMoves.svelte";
|
|
9
|
+
import StepPosition from "./StepPosition.svelte";
|
|
10
|
+
import StepPreview from "./StepPreview.svelte";
|
|
11
|
+
import type { TPuzzleCreationWizardProps } from "./types.js";
|
|
12
|
+
import {
|
|
13
|
+
createEmptyTreeFromFen,
|
|
14
|
+
createInitialPuzzleData,
|
|
15
|
+
type PuzzleData,
|
|
16
|
+
} from "../puzzle/puzzleData.js";
|
|
17
|
+
import { PgnOps } from "@connectorvol/chessops/pgnOps.svelte";
|
|
18
|
+
import { ChessTree, createPgnFromTree } from "@connectorvol/tree";
|
|
19
|
+
import {
|
|
20
|
+
CHESSBOARD_THEMES,
|
|
21
|
+
Draggable,
|
|
22
|
+
type IChessBoardActions,
|
|
23
|
+
} from "@connectorvol/chessboard";
|
|
24
|
+
import { calculatePly, Color, Square } from "@connectorvol/shared";
|
|
25
|
+
|
|
26
|
+
import { createPuzzleLineEditingBoardApi } from "./createPuzzleLineEditingBoard.js";
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
onPuzzleCreated,
|
|
30
|
+
fen: fenProp,
|
|
31
|
+
boardTheme,
|
|
32
|
+
boardAppearanceSettings,
|
|
33
|
+
}: TPuzzleCreationWizardProps = $props();
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Представляет нормализацию входного FEN из пропса (пустая строка трактуется как отсутствие значения).
|
|
37
|
+
*/
|
|
38
|
+
function wizardSeedFen(prop: string | undefined): string | undefined {
|
|
39
|
+
const t = prop?.trim();
|
|
40
|
+
return t ? t : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let step = $state<1 | 2 | 3>(1);
|
|
44
|
+
let puzzleData = $state<PuzzleData>(
|
|
45
|
+
createInitialPuzzleData(wizardSeedFen(fenProp)),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/** Возвращает PGN дерева решения для превью на шаге 3. */
|
|
49
|
+
let solutionPgnForPreview = $state("");
|
|
50
|
+
|
|
51
|
+
function handleMovesBack() {
|
|
52
|
+
step = 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleMovesNext(moves: string[]) {
|
|
56
|
+
puzzleData.moves = moves;
|
|
57
|
+
solutionPgnForPreview = createPgnFromTree(tree.rootNode);
|
|
58
|
+
step = 3;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handlePreviewBack() {
|
|
62
|
+
step = 2;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let chess = $derived(new PgnOps(puzzleData.initialFen, "chess"));
|
|
66
|
+
let tree = $derived(
|
|
67
|
+
new ChessTree(createEmptyTreeFromFen(puzzleData.initialFen)),
|
|
68
|
+
);
|
|
69
|
+
const addMoveToTree = (san: string) => {
|
|
70
|
+
const move = chess.makeSanMove(san);
|
|
71
|
+
if (!move) return;
|
|
94
72
|
const fen = chess.fen();
|
|
95
73
|
const { halfMoves, fullMoves } = calculatePly(fen);
|
|
96
74
|
tree.addNodeToCurrent({
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
75
|
+
id: "",
|
|
76
|
+
children: [],
|
|
77
|
+
data: {
|
|
78
|
+
fen,
|
|
79
|
+
san,
|
|
80
|
+
ply: halfMoves,
|
|
81
|
+
fullMoves,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
return { move, fen, turn: chess.turn() };
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const actions: IChessBoardActions = {
|
|
88
|
+
game: {
|
|
89
|
+
possibleMovesOnSquare: (square: Square) => chess.moves(square),
|
|
90
|
+
beforePieceMoveSan(san: string) {
|
|
91
|
+
const result = addMoveToTree(san);
|
|
92
|
+
return result;
|
|
93
|
+
},
|
|
94
|
+
afterPieceMoveSan: () => {},
|
|
95
|
+
beforePieceMove: (from, to, promotion) => {
|
|
96
|
+
const san = chess.getSanForMove({ from, to, promotion });
|
|
97
|
+
chess.makeMove({ from, to, promotion });
|
|
98
|
+
const fen = chess.fen();
|
|
99
|
+
const { halfMoves, fullMoves } = calculatePly(fen);
|
|
100
|
+
tree.addNodeToCurrent({
|
|
101
|
+
id: "",
|
|
102
|
+
children: [],
|
|
103
|
+
data: {
|
|
104
|
+
fen,
|
|
105
|
+
san,
|
|
106
|
+
ply: halfMoves,
|
|
107
|
+
fullMoves,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
return fen;
|
|
111
|
+
},
|
|
112
|
+
afterPieceMove: () => {
|
|
113
|
+
const side = chess.turn();
|
|
114
|
+
chessboard.draggable =
|
|
115
|
+
side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK;
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Представляет сборку доски для шага построения линии: позиция и ориентация по стороне хода в FEN.
|
|
122
|
+
*/
|
|
123
|
+
function createMainChessboard(fullFen: string) {
|
|
124
|
+
return createPuzzleLineEditingBoardApi(fullFen, actions, {
|
|
125
|
+
boardTheme: boardTheme ?? CHESSBOARD_THEMES.blue,
|
|
126
|
+
boardAppearanceSettings,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let chessboard = $derived(createMainChessboard(puzzleData.initialFen));
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Представляет сброс мастера при смене входного FEN извне (шаг 1 и черновик ходов обнуляются).
|
|
134
|
+
*/
|
|
135
|
+
$effect(() => {
|
|
136
|
+
const seed = wizardSeedFen(fenProp);
|
|
137
|
+
const next = createInitialPuzzleData(seed);
|
|
138
|
+
untrack(() => {
|
|
139
|
+
puzzleData = next;
|
|
140
|
+
step = 1;
|
|
141
|
+
solutionPgnForPreview = "";
|
|
105
142
|
});
|
|
106
|
-
return fen;
|
|
107
|
-
},
|
|
108
|
-
afterPieceMove: () => {
|
|
109
|
-
const side = chess.turn();
|
|
110
|
-
chessboard.draggable =
|
|
111
|
-
side === Color.WHITE ? Draggable.WHITE : Draggable.BLACK;
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Представляет сборку доски для шага построения линии: позиция и ориентация по стороне хода в FEN.
|
|
118
|
-
*/
|
|
119
|
-
function createMainChessboard(fullFen: string) {
|
|
120
|
-
return createPuzzleLineEditingBoardApi(fullFen, actions, {
|
|
121
|
-
boardTheme: boardTheme ?? CHESSBOARD_THEMES.blue,
|
|
122
|
-
boardAppearanceSettings,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let chessboard = $derived(createMainChessboard(puzzleData.initialFen));
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Представляет сброс мастера при смене входного FEN извне (шаг 1 и черновик ходов обнуляются).
|
|
130
|
-
*/
|
|
131
|
-
$effect(() => {
|
|
132
|
-
const seed = wizardSeedFen(fenProp);
|
|
133
|
-
const next = createInitialPuzzleData(seed);
|
|
134
|
-
untrack(() => {
|
|
135
|
-
puzzleData = next;
|
|
136
|
-
chess = new PgnOps(next.initialFen, "chess");
|
|
137
|
-
tree = new ChessTree(createEmptyTreeFromFen(next.initialFen));
|
|
138
|
-
chessboard = createMainChessboard(next.initialFen);
|
|
139
|
-
step = 1;
|
|
140
|
-
solutionPgnForPreview = "";
|
|
141
143
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
tree = new ChessTree(createEmptyTreeFromFen(fen));
|
|
148
|
-
chessboard = createMainChessboard(fen);
|
|
149
|
-
step = 2;
|
|
150
|
-
}
|
|
144
|
+
|
|
145
|
+
function handlePositionNext(fen: string) {
|
|
146
|
+
puzzleData.initialFen = fen;
|
|
147
|
+
step = 2;
|
|
148
|
+
}
|
|
151
149
|
</script>
|
|
152
150
|
|
|
153
151
|
<div class="mx-4 lg:container lg:mx-auto">
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
152
|
+
<div class="flex flex-col gap-4 pt-4 pb-0">
|
|
153
|
+
<div class="flex items-center gap-2">
|
|
154
|
+
<span
|
|
155
|
+
class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium"
|
|
156
|
+
class:bg-primary={step >= 1}
|
|
157
|
+
class:bg-muted={step < 1}
|
|
158
|
+
class:text-primary-foreground={step >= 1}
|
|
159
|
+
>
|
|
160
|
+
1
|
|
161
|
+
</span>
|
|
162
|
+
<span class="text-sm">Позиция</span>
|
|
163
|
+
<span class="h-px flex-1 bg-border"></span>
|
|
164
|
+
<span
|
|
165
|
+
class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium"
|
|
166
|
+
class:bg-primary={step >= 2}
|
|
167
|
+
class:bg-muted={step < 2}
|
|
168
|
+
class:text-primary-foreground={step >= 2}
|
|
169
|
+
>
|
|
170
|
+
2
|
|
171
|
+
</span>
|
|
172
|
+
<span class="text-sm">Ходы</span>
|
|
173
|
+
<span class="h-px flex-1 bg-border"></span>
|
|
174
|
+
<span
|
|
175
|
+
class="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium"
|
|
176
|
+
class:bg-primary={step >= 3}
|
|
177
|
+
class:bg-muted={step < 3}
|
|
178
|
+
class:text-primary-foreground={step >= 3}
|
|
179
|
+
>
|
|
180
|
+
3
|
|
181
|
+
</span>
|
|
182
|
+
<span class="text-sm">Превью</span>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{#if step === 1}
|
|
186
|
+
<StepPosition
|
|
187
|
+
initialFen={puzzleData.initialFen}
|
|
188
|
+
onNext={handlePositionNext}
|
|
189
|
+
{boardTheme}
|
|
190
|
+
{boardAppearanceSettings}
|
|
191
|
+
/>
|
|
192
|
+
{:else if step === 2}
|
|
193
|
+
<StepMoves
|
|
194
|
+
{puzzleData}
|
|
195
|
+
{chess}
|
|
196
|
+
{tree}
|
|
197
|
+
{chessboard}
|
|
198
|
+
onBack={handleMovesBack}
|
|
199
|
+
onNext={handleMovesNext}
|
|
200
|
+
/>
|
|
201
|
+
{:else}
|
|
202
|
+
<StepPreview
|
|
203
|
+
{puzzleData}
|
|
204
|
+
solutionPgn={solutionPgnForPreview}
|
|
205
|
+
onBack={handlePreviewBack}
|
|
206
|
+
{onPuzzleCreated}
|
|
207
|
+
{boardTheme}
|
|
208
|
+
{boardAppearanceSettings}
|
|
209
|
+
/>
|
|
210
|
+
{/if}
|
|
185
211
|
</div>
|
|
186
|
-
|
|
187
|
-
{#if step === 1}
|
|
188
|
-
<StepPosition
|
|
189
|
-
initialFen={puzzleData.initialFen}
|
|
190
|
-
onNext={handlePositionNext}
|
|
191
|
-
{boardTheme}
|
|
192
|
-
boardAppearanceSettings={boardAppearanceSettings}
|
|
193
|
-
/>
|
|
194
|
-
{:else if step === 2}
|
|
195
|
-
<StepMoves
|
|
196
|
-
{puzzleData}
|
|
197
|
-
{chess}
|
|
198
|
-
{tree}
|
|
199
|
-
{chessboard}
|
|
200
|
-
onBack={handleMovesBack}
|
|
201
|
-
onNext={handleMovesNext}
|
|
202
|
-
/>
|
|
203
|
-
{:else}
|
|
204
|
-
<StepPreview
|
|
205
|
-
{puzzleData}
|
|
206
|
-
solutionPgn={solutionPgnForPreview}
|
|
207
|
-
onBack={handlePreviewBack}
|
|
208
|
-
{onPuzzleCreated}
|
|
209
|
-
{boardTheme}
|
|
210
|
-
boardAppearanceSettings={boardAppearanceSettings}
|
|
211
|
-
/>
|
|
212
|
-
{/if}
|
|
213
|
-
</div>
|
|
214
212
|
</div>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
import { INITIAL_FEN } from "@connectorvol/chessops/fen";
|
|
26
26
|
import { PgnOps } from "@connectorvol/chessops/pgnOps.svelte";
|
|
27
|
-
import { Color, type Move } from "@connectorvol/shared";
|
|
27
|
+
import { Color, calculatePly, type Move } from "@connectorvol/shared";
|
|
28
28
|
|
|
29
29
|
import { mergePgnWithSetupFen } from "../puzzle/mergePgnWithSetupFen.js";
|
|
30
30
|
import { createEmptyTreeFromFen } from "../puzzle/puzzleData.js";
|
|
@@ -81,7 +81,9 @@
|
|
|
81
81
|
function syncBoardDragFromChessTurn(): void {
|
|
82
82
|
const solver = puzzleSolverColor();
|
|
83
83
|
chessboard.draggable = solved
|
|
84
|
-
?
|
|
84
|
+
? previewChess.turn() === Color.WHITE
|
|
85
|
+
? Draggable.WHITE
|
|
86
|
+
: Draggable.BLACK
|
|
85
87
|
: previewChess.turn() === solver
|
|
86
88
|
? solver === Color.WHITE
|
|
87
89
|
? Draggable.WHITE
|
|
@@ -101,6 +103,7 @@
|
|
|
101
103
|
let src = solution.rootNode.moves;
|
|
102
104
|
for (const idx of path) {
|
|
103
105
|
const next = src.children[idx];
|
|
106
|
+
if (!next) break;
|
|
104
107
|
studentPreviewTree.addNodeToCurrent({
|
|
105
108
|
id: "",
|
|
106
109
|
children: [],
|
|
@@ -152,7 +155,8 @@
|
|
|
152
155
|
previewChess.setFen(layoutInitialFen);
|
|
153
156
|
let node = solution.rootNode.moves;
|
|
154
157
|
for (const idx of path) {
|
|
155
|
-
const child = node.children[idx]
|
|
158
|
+
const child = node.children[idx];
|
|
159
|
+
if (!child) break;
|
|
156
160
|
previewChess.makeSanMove(child.data.san);
|
|
157
161
|
node = child;
|
|
158
162
|
}
|
|
@@ -178,7 +182,7 @@
|
|
|
178
182
|
solution.rootNode.moves,
|
|
179
183
|
pathAfterSolver,
|
|
180
184
|
);
|
|
181
|
-
if (node.children.length === 0) return pathAfterSolver;
|
|
185
|
+
if (!node || node.children.length === 0) return pathAfterSolver;
|
|
182
186
|
|
|
183
187
|
previewChess.makeSanMove(node.children[0]!.data.san);
|
|
184
188
|
return [...pathAfterSolver, 0];
|
|
@@ -207,7 +211,9 @@
|
|
|
207
211
|
}
|
|
208
212
|
|
|
209
213
|
if (depth === path.length) break;
|
|
210
|
-
|
|
214
|
+
const child = node.children[path[depth]!];
|
|
215
|
+
if (!child) break;
|
|
216
|
+
node = child;
|
|
211
217
|
}
|
|
212
218
|
|
|
213
219
|
node = solution.rootNode.moves;
|
|
@@ -223,7 +229,9 @@
|
|
|
223
229
|
bestNextIdx = chosenIdx + 1;
|
|
224
230
|
}
|
|
225
231
|
}
|
|
226
|
-
|
|
232
|
+
const child = node.children[path[depth]!];
|
|
233
|
+
if (!child) break;
|
|
234
|
+
node = child;
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
if (bestForkDepth === null) return null;
|
|
@@ -255,6 +263,7 @@
|
|
|
255
263
|
solution.rootNode.moves,
|
|
256
264
|
cursorPath,
|
|
257
265
|
);
|
|
266
|
+
if (!cursorNode) return undefined;
|
|
258
267
|
|
|
259
268
|
const outcome = puzzlePreviewClassifySolverMove(
|
|
260
269
|
cursorNode,
|
|
@@ -275,7 +284,7 @@
|
|
|
275
284
|
setStateFromPath(path);
|
|
276
285
|
|
|
277
286
|
const leaf = puzzlePreviewNodeAtPath(solution.rootNode.moves, cursorPath);
|
|
278
|
-
if (puzzlePreviewIsSolvedPosition(previewChess, leaf, puzzleSolverColor())) {
|
|
287
|
+
if (leaf && puzzlePreviewIsSolvedPosition(previewChess, leaf, puzzleSolverColor())) {
|
|
279
288
|
if (advanceLineTimer !== null) {
|
|
280
289
|
clearTimeout(advanceLineTimer);
|
|
281
290
|
advanceLineTimer = null;
|
|
@@ -306,6 +315,31 @@
|
|
|
306
315
|
game: {
|
|
307
316
|
possibleMovesOnSquare: (square: Square) => previewChess.moves(square),
|
|
308
317
|
beforePieceMoveSan(san: string) {
|
|
318
|
+
if (solved) {
|
|
319
|
+
try {
|
|
320
|
+
const move = previewChess.makeSanMove(san);
|
|
321
|
+
const fen = previewChess.fen();
|
|
322
|
+
const { halfMoves, fullMoves } = calculatePly(fen);
|
|
323
|
+
solutionPreviewTree?.addNodeToCurrent({
|
|
324
|
+
id: "",
|
|
325
|
+
children: [],
|
|
326
|
+
data: {
|
|
327
|
+
fen,
|
|
328
|
+
san,
|
|
329
|
+
ply: halfMoves,
|
|
330
|
+
fullMoves,
|
|
331
|
+
lastMove: move,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
return {
|
|
335
|
+
move,
|
|
336
|
+
fen,
|
|
337
|
+
turn: previewChess.turn(),
|
|
338
|
+
};
|
|
339
|
+
} catch {
|
|
340
|
+
return undefined;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
309
343
|
const move = tryApplySolverSan(san);
|
|
310
344
|
if (!move) return undefined;
|
|
311
345
|
return {
|
|
@@ -318,6 +352,28 @@
|
|
|
318
352
|
syncBoardDragFromChessTurn();
|
|
319
353
|
},
|
|
320
354
|
beforePieceMove: (from, to, promotion) => {
|
|
355
|
+
if (solved) {
|
|
356
|
+
try {
|
|
357
|
+
const san = previewChess.getSanForMove({ from, to, promotion });
|
|
358
|
+
const move = previewChess.makeSanMove(san);
|
|
359
|
+
const fen = previewChess.fen();
|
|
360
|
+
const { halfMoves, fullMoves } = calculatePly(fen);
|
|
361
|
+
solutionPreviewTree?.addNodeToCurrent({
|
|
362
|
+
id: "",
|
|
363
|
+
children: [],
|
|
364
|
+
data: {
|
|
365
|
+
fen,
|
|
366
|
+
san,
|
|
367
|
+
ply: halfMoves,
|
|
368
|
+
fullMoves,
|
|
369
|
+
lastMove: move,
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
return fen;
|
|
373
|
+
} catch {
|
|
374
|
+
return "";
|
|
375
|
+
}
|
|
376
|
+
}
|
|
321
377
|
const san = previewChess.getSanForMove({ from, to, promotion });
|
|
322
378
|
const move = tryApplySolverSan(san);
|
|
323
379
|
if (!move) return "";
|
|
@@ -357,7 +413,8 @@
|
|
|
357
413
|
if (!solved || !solutionPreviewTree) return;
|
|
358
414
|
const tree = solutionPreviewTree;
|
|
359
415
|
const path = cursorPath;
|
|
360
|
-
|
|
416
|
+
const target = puzzlePreviewNodeAtPath(tree.rootNode.moves, path);
|
|
417
|
+
tree.currentNode = target ?? tree.rootNode.moves;
|
|
361
418
|
});
|
|
362
419
|
|
|
363
420
|
$effect(() => {
|
|
@@ -430,6 +487,7 @@
|
|
|
430
487
|
chessboard.fen = previewChess.fen();
|
|
431
488
|
chessboard.addLastMove(tree.currentNode.data.lastMove ?? null);
|
|
432
489
|
syncMoveEvaluationNagBadge(chessboard, tree.currentNode.data);
|
|
490
|
+
syncBoardDragFromChessTurn();
|
|
433
491
|
}
|
|
434
492
|
|
|
435
493
|
/**
|