@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,180 +1,186 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type { BoardApi } from "@connectorvol/chessboard";
|
|
3
|
+
import type { Castling, Fen } from "./fen.svelte.js";
|
|
4
|
+
import { cn } from "../utils.js";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
fen: Fen;
|
|
13
|
-
api: BoardApi;
|
|
14
|
-
}
|
|
6
|
+
interface Props {
|
|
7
|
+
fen: Fen;
|
|
8
|
+
api: BoardApi;
|
|
9
|
+
}
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
const { fen, api: _api }: Props = $props();
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
isPossibleLongCastleBlack = fen.castling.includes("q");
|
|
23
|
-
});
|
|
13
|
+
const isPossibleShortCastleWhite = $derived(fen.castling.includes("K"));
|
|
14
|
+
const isPossibleShortCastleBlack = $derived(fen.castling.includes("k"));
|
|
15
|
+
const isPossibleLongCastleWhite = $derived(fen.castling.includes("Q"));
|
|
16
|
+
const isPossibleLongCastleBlack = $derived(fen.castling.includes("q"));
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
fen.castling = "-";
|
|
36
|
-
} else {
|
|
37
|
-
fen.castling = castling as Castling;
|
|
18
|
+
function buildCastling(
|
|
19
|
+
shortWhite: boolean,
|
|
20
|
+
longWhite: boolean,
|
|
21
|
+
shortBlack: boolean,
|
|
22
|
+
longBlack: boolean,
|
|
23
|
+
): Castling {
|
|
24
|
+
const castling = [shortWhite, longWhite, shortBlack, longBlack]
|
|
25
|
+
.map((c, i) => (c ? "KQkq"[i] : ""))
|
|
26
|
+
.join("");
|
|
27
|
+
return (castling || "-") as Castling;
|
|
38
28
|
}
|
|
39
|
-
});
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
function toggleCastling(flagIndex: number, value: boolean): void {
|
|
31
|
+
const flags = [
|
|
32
|
+
isPossibleShortCastleWhite,
|
|
33
|
+
isPossibleLongCastleWhite,
|
|
34
|
+
isPossibleShortCastleBlack,
|
|
35
|
+
isPossibleLongCastleBlack,
|
|
36
|
+
];
|
|
37
|
+
flags[flagIndex] = value;
|
|
38
|
+
fen.castling = buildCastling(
|
|
39
|
+
flags[0]!,
|
|
40
|
+
flags[1]!,
|
|
41
|
+
flags[2]!,
|
|
42
|
+
flags[3]!,
|
|
43
|
+
);
|
|
45
44
|
}
|
|
46
|
-
|
|
45
|
+
|
|
46
|
+
const enPassantMoves = $derived(fen.genPossibleEnPassantMoves());
|
|
47
47
|
</script>
|
|
48
48
|
|
|
49
49
|
<div class="space-y-3 md:space-y-4">
|
|
50
|
-
|
|
51
|
-
<label
|
|
52
|
-
for="move-select"
|
|
53
|
-
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
54
|
-
>
|
|
55
|
-
Установить ход
|
|
56
|
-
</label>
|
|
57
|
-
<select
|
|
58
|
-
id="move-select"
|
|
59
|
-
bind:value={fen.move}
|
|
60
|
-
class={cn(
|
|
61
|
-
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
62
|
-
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
63
|
-
)}
|
|
64
|
-
>
|
|
65
|
-
<option value="w">w</option>
|
|
66
|
-
<option value="b">b</option>
|
|
67
|
-
</select>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<div class="space-y-2">
|
|
71
|
-
<label
|
|
72
|
-
for="enpassant-select"
|
|
73
|
-
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
74
|
-
>
|
|
75
|
-
Enpassant
|
|
76
|
-
</label>
|
|
77
|
-
<select
|
|
78
|
-
id="enpassant-select"
|
|
79
|
-
bind:value={fen.enPassant}
|
|
80
|
-
class={cn(
|
|
81
|
-
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
82
|
-
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
83
|
-
)}
|
|
84
|
-
>
|
|
85
|
-
<option value="-">-</option>
|
|
86
|
-
{#each enPassantMoves as move}
|
|
87
|
-
<option value={move}>{move}</option>
|
|
88
|
-
{/each}
|
|
89
|
-
</select>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<div class="space-y-2 md:space-y-3">
|
|
93
|
-
<div class="text-sm font-medium leading-none">Возможность рокировки</div>
|
|
94
|
-
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-4">
|
|
95
|
-
<div class="flex items-center space-x-2">
|
|
96
|
-
<input
|
|
97
|
-
type="checkbox"
|
|
98
|
-
id="isPossibleShortCastleWhite"
|
|
99
|
-
bind:checked={isPossibleShortCastleWhite}
|
|
100
|
-
class={cn(
|
|
101
|
-
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
102
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
103
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
104
|
-
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
105
|
-
"accent-primary",
|
|
106
|
-
)}
|
|
107
|
-
/>
|
|
50
|
+
<div class="space-y-2">
|
|
108
51
|
<label
|
|
109
|
-
|
|
110
|
-
|
|
52
|
+
for="move-select"
|
|
53
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
111
54
|
>
|
|
112
|
-
|
|
55
|
+
Установить ход
|
|
113
56
|
</label>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class={cn(
|
|
122
|
-
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
123
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
124
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
125
|
-
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
126
|
-
"accent-primary",
|
|
127
|
-
)}
|
|
128
|
-
/>
|
|
129
|
-
<label
|
|
130
|
-
for="isPossibleShortCastleBlack"
|
|
131
|
-
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
57
|
+
<select
|
|
58
|
+
id="move-select"
|
|
59
|
+
bind:value={fen.move}
|
|
60
|
+
class={cn(
|
|
61
|
+
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
62
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
63
|
+
)}
|
|
132
64
|
>
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
65
|
+
<option value="w">w</option>
|
|
66
|
+
<option value="b">b</option>
|
|
67
|
+
</select>
|
|
68
|
+
</div>
|
|
136
69
|
|
|
137
|
-
|
|
138
|
-
<input
|
|
139
|
-
type="checkbox"
|
|
140
|
-
id="isPossibleLongCastleWhite"
|
|
141
|
-
bind:checked={isPossibleLongCastleWhite}
|
|
142
|
-
class={cn(
|
|
143
|
-
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
144
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
145
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
146
|
-
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
147
|
-
"accent-primary",
|
|
148
|
-
)}
|
|
149
|
-
/>
|
|
70
|
+
<div class="space-y-2">
|
|
150
71
|
<label
|
|
151
|
-
|
|
152
|
-
|
|
72
|
+
for="enpassant-select"
|
|
73
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
153
74
|
>
|
|
154
|
-
|
|
75
|
+
Enpassant
|
|
155
76
|
</label>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class={cn(
|
|
164
|
-
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
165
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
166
|
-
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
167
|
-
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
168
|
-
"accent-primary",
|
|
169
|
-
)}
|
|
170
|
-
/>
|
|
171
|
-
<label
|
|
172
|
-
for="isPossibleLongCastleBlack"
|
|
173
|
-
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
77
|
+
<select
|
|
78
|
+
id="enpassant-select"
|
|
79
|
+
bind:value={fen.enPassant}
|
|
80
|
+
class={cn(
|
|
81
|
+
"border-input bg-background ring-offset-background shadow-xs flex h-9 w-full rounded-md border px-3 py-1 text-base outline-none transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
82
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
83
|
+
)}
|
|
174
84
|
>
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
85
|
+
<option value="-">-</option>
|
|
86
|
+
{#each enPassantMoves as move}
|
|
87
|
+
<option value={move}>{move}</option>
|
|
88
|
+
{/each}
|
|
89
|
+
</select>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="space-y-2 md:space-y-3">
|
|
93
|
+
<div class="text-sm font-medium leading-none">
|
|
94
|
+
Возможность рокировки
|
|
95
|
+
</div>
|
|
96
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-4">
|
|
97
|
+
<div class="flex items-center space-x-2">
|
|
98
|
+
<input
|
|
99
|
+
type="checkbox"
|
|
100
|
+
id="isPossibleShortCastleWhite"
|
|
101
|
+
checked={isPossibleShortCastleWhite}
|
|
102
|
+
onchange={(e) => toggleCastling(0, e.currentTarget.checked)}
|
|
103
|
+
class={cn(
|
|
104
|
+
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
105
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
106
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
107
|
+
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
108
|
+
"accent-primary",
|
|
109
|
+
)}
|
|
110
|
+
/>
|
|
111
|
+
<label
|
|
112
|
+
for="isPossibleShortCastleWhite"
|
|
113
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
114
|
+
>
|
|
115
|
+
Белые 0-0
|
|
116
|
+
</label>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div class="flex items-center space-x-2">
|
|
120
|
+
<input
|
|
121
|
+
type="checkbox"
|
|
122
|
+
id="isPossibleShortCastleBlack"
|
|
123
|
+
checked={isPossibleShortCastleBlack}
|
|
124
|
+
onchange={(e) => toggleCastling(2, e.currentTarget.checked)}
|
|
125
|
+
class={cn(
|
|
126
|
+
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
127
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
128
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
129
|
+
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
130
|
+
"accent-primary",
|
|
131
|
+
)}
|
|
132
|
+
/>
|
|
133
|
+
<label
|
|
134
|
+
for="isPossibleShortCastleBlack"
|
|
135
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
136
|
+
>
|
|
137
|
+
Черные 0-0
|
|
138
|
+
</label>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div class="flex items-center space-x-2">
|
|
142
|
+
<input
|
|
143
|
+
type="checkbox"
|
|
144
|
+
id="isPossibleLongCastleWhite"
|
|
145
|
+
checked={isPossibleLongCastleWhite}
|
|
146
|
+
onchange={(e) => toggleCastling(1, e.currentTarget.checked)}
|
|
147
|
+
class={cn(
|
|
148
|
+
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
149
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
150
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
151
|
+
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
152
|
+
"accent-primary",
|
|
153
|
+
)}
|
|
154
|
+
/>
|
|
155
|
+
<label
|
|
156
|
+
for="isPossibleLongCastleWhite"
|
|
157
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
158
|
+
>
|
|
159
|
+
Белые 0-0-0
|
|
160
|
+
</label>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div class="flex items-center space-x-2">
|
|
164
|
+
<input
|
|
165
|
+
type="checkbox"
|
|
166
|
+
id="isPossibleLongCastleBlack"
|
|
167
|
+
checked={isPossibleLongCastleBlack}
|
|
168
|
+
onchange={(e) => toggleCastling(3, e.currentTarget.checked)}
|
|
169
|
+
class={cn(
|
|
170
|
+
"peer h-4 w-4 shrink-0 rounded-sm border border-input ring-offset-background transition-colors",
|
|
171
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
172
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
173
|
+
"data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
|
174
|
+
"accent-primary",
|
|
175
|
+
)}
|
|
176
|
+
/>
|
|
177
|
+
<label
|
|
178
|
+
for="isPossibleLongCastleBlack"
|
|
179
|
+
class="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
180
|
+
>
|
|
181
|
+
Черные 0-0-0
|
|
182
|
+
</label>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
178
185
|
</div>
|
|
179
|
-
</div>
|
|
180
186
|
</div>
|
|
@@ -20,7 +20,7 @@ export declare function puzzlePreviewSideToMoveFromFen(fullFen: string): Color;
|
|
|
20
20
|
/**
|
|
21
21
|
* Представляет получение узла дерева по пути индексов детей от корня партии.
|
|
22
22
|
*/
|
|
23
|
-
export declare function puzzlePreviewNodeAtPath(rootMoves: ChessTreeNode, path: number[]): ChessTreeNode;
|
|
23
|
+
export declare function puzzlePreviewNodeAtPath(rootMoves: ChessTreeNode, path: number[]): ChessTreeNode | null;
|
|
24
24
|
/**
|
|
25
25
|
* Представляет поиск индекса ребёнка, чей SAN даёт ту же результирующую позицию, что и сыгранный SAN.
|
|
26
26
|
*/
|
|
@@ -13,7 +13,10 @@ export function puzzlePreviewSideToMoveFromFen(fullFen) {
|
|
|
13
13
|
export function puzzlePreviewNodeAtPath(rootMoves, path) {
|
|
14
14
|
let node = rootMoves;
|
|
15
15
|
for (const idx of path) {
|
|
16
|
-
|
|
16
|
+
const child = node.children[idx];
|
|
17
|
+
if (!child)
|
|
18
|
+
return null;
|
|
19
|
+
node = child;
|
|
17
20
|
}
|
|
18
21
|
return node;
|
|
19
22
|
}
|