@omercnet/paseo-queens 0.1.0-next.126.2
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/LICENSE +21 -0
- package/README.md +122 -0
- package/client/completion-feedback.tsx +55 -0
- package/client/composer-pill.tsx +74 -0
- package/client/contribute.tsx +34 -0
- package/client/game/curated.ts +91 -0
- package/client/game/engine.ts +199 -0
- package/client/game/index.ts +5 -0
- package/client/game/puzzles.ts +100 -0
- package/client/game/state.ts +348 -0
- package/client/game/types.ts +7 -0
- package/client/game-controls.tsx +199 -0
- package/client/game-mark.tsx +54 -0
- package/client/puzzle-selector.tsx +187 -0
- package/client/queens-board.tsx +592 -0
- package/client/queens-popover.tsx +349 -0
- package/client/queens-surface.tsx +761 -0
- package/client/use-persisted-game.ts +769 -0
- package/client/use-puzzle-catalog.ts +112 -0
- package/index.client.tsx +6 -0
- package/index.server.ts +10 -0
- package/package.json +58 -0
- package/paseo-plugin.json +4 -0
- package/scripts/import-curated-puzzles.mjs +231 -0
- package/server/curated-manifest.ts +334 -0
- package/server/puzzle-catalog.ts +70 -0
- package/shared/game-settings.ts +114 -0
- package/shared/puzzle-catalog.ts +29 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import type { PuzzleDifficulty } from "../shared/puzzle-catalog";
|
|
2
|
+
|
|
3
|
+
/** Pinned, integrity-checked files in an unlisted GitHub Gist. */
|
|
4
|
+
export type RemotePuzzleGroup = {
|
|
5
|
+
readonly size: number;
|
|
6
|
+
readonly difficulty: PuzzleDifficulty;
|
|
7
|
+
readonly count: number;
|
|
8
|
+
readonly recordBytes: number;
|
|
9
|
+
readonly sha256: string;
|
|
10
|
+
readonly url: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const REMOTE_PUZZLE_GROUPS: Readonly<Record<string, RemotePuzzleGroup>> = {
|
|
14
|
+
"5-beginner": {
|
|
15
|
+
size: 5,
|
|
16
|
+
difficulty: "beginner",
|
|
17
|
+
count: 237,
|
|
18
|
+
recordBytes: 20,
|
|
19
|
+
sha256: "a0eb4398895869f50cf81562b96db1703b5886e6c98c0926296eb8a0a01be101",
|
|
20
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/5-beginner.b64",
|
|
21
|
+
},
|
|
22
|
+
"5-easy": {
|
|
23
|
+
size: 5,
|
|
24
|
+
difficulty: "easy",
|
|
25
|
+
count: 4661,
|
|
26
|
+
recordBytes: 20,
|
|
27
|
+
sha256: "98a26f23e42bb1587cb660f4a11adeccb26e8142fd33b6c5c2a7a8d39a7185e4",
|
|
28
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/5-easy.b64",
|
|
29
|
+
},
|
|
30
|
+
"5-medium": {
|
|
31
|
+
size: 5,
|
|
32
|
+
difficulty: "medium",
|
|
33
|
+
count: 1044,
|
|
34
|
+
recordBytes: 20,
|
|
35
|
+
sha256: "2b37f37fbeff1fc91feb9219efb4b86fe7935260b003bc996c807c734cb2a28c",
|
|
36
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/5-medium.b64",
|
|
37
|
+
},
|
|
38
|
+
"5-hard": {
|
|
39
|
+
size: 5,
|
|
40
|
+
difficulty: "hard",
|
|
41
|
+
count: 178,
|
|
42
|
+
recordBytes: 20,
|
|
43
|
+
sha256: "e620330449d1592893950b54afa341d77a4fb6b5028575e110640cf5f31a9a74",
|
|
44
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/5-hard.b64",
|
|
45
|
+
},
|
|
46
|
+
"6-beginner": {
|
|
47
|
+
size: 6,
|
|
48
|
+
difficulty: "beginner",
|
|
49
|
+
count: 311,
|
|
50
|
+
recordBytes: 26,
|
|
51
|
+
sha256: "88423258dd66d3d88a7813c9665a26ce39a14b2357c94551009bd51e007e314e",
|
|
52
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/6-beginner.b64",
|
|
53
|
+
},
|
|
54
|
+
"6-easy": {
|
|
55
|
+
size: 6,
|
|
56
|
+
difficulty: "easy",
|
|
57
|
+
count: 6172,
|
|
58
|
+
recordBytes: 26,
|
|
59
|
+
sha256: "c1ddd59328108831787195f9c218b3d35c0497bead6fc61d37bc4a6cb5810191",
|
|
60
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/6-easy.b64",
|
|
61
|
+
},
|
|
62
|
+
"6-medium": {
|
|
63
|
+
size: 6,
|
|
64
|
+
difficulty: "medium",
|
|
65
|
+
count: 2059,
|
|
66
|
+
recordBytes: 26,
|
|
67
|
+
sha256: "e07837d97570709f6e32a0e4a3b38b496a1f819fe43134168ab9c01af2ba8e7c",
|
|
68
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/6-medium.b64",
|
|
69
|
+
},
|
|
70
|
+
"6-hard": {
|
|
71
|
+
size: 6,
|
|
72
|
+
difficulty: "hard",
|
|
73
|
+
count: 181,
|
|
74
|
+
recordBytes: 26,
|
|
75
|
+
sha256: "ae71e185be0e5732b4351c1c042484731c4b7e0443b07cba4748f78f4e988859",
|
|
76
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/6-hard.b64",
|
|
77
|
+
},
|
|
78
|
+
"7-beginner": {
|
|
79
|
+
size: 7,
|
|
80
|
+
difficulty: "beginner",
|
|
81
|
+
count: 253,
|
|
82
|
+
recordBytes: 34,
|
|
83
|
+
sha256: "83171ec81b3a347023aa1577a771627af98794b6e938857729a45e8ea1643b3c",
|
|
84
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/7-beginner.b64",
|
|
85
|
+
},
|
|
86
|
+
"7-easy": {
|
|
87
|
+
size: 7,
|
|
88
|
+
difficulty: "easy",
|
|
89
|
+
count: 5899,
|
|
90
|
+
recordBytes: 34,
|
|
91
|
+
sha256: "d95beb7dca32b83b5639afe7e673ca10575d6499100bd6a02beaff012d291713",
|
|
92
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/7-easy.b64",
|
|
93
|
+
},
|
|
94
|
+
"7-medium": {
|
|
95
|
+
size: 7,
|
|
96
|
+
difficulty: "medium",
|
|
97
|
+
count: 3034,
|
|
98
|
+
recordBytes: 34,
|
|
99
|
+
sha256: "dd93b0c0d4a3bfd7d2401b2aefbea81c10153adc69dd9cc512d944a876501815",
|
|
100
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/7-medium.b64",
|
|
101
|
+
},
|
|
102
|
+
"7-hard": {
|
|
103
|
+
size: 7,
|
|
104
|
+
difficulty: "hard",
|
|
105
|
+
count: 310,
|
|
106
|
+
recordBytes: 34,
|
|
107
|
+
sha256: "c7f6e137741e6b03f2fa8626a73f63b1f1fea7c5de8f35121a388dde9c87f937",
|
|
108
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/7-hard.b64",
|
|
109
|
+
},
|
|
110
|
+
"8-beginner": {
|
|
111
|
+
size: 8,
|
|
112
|
+
difficulty: "beginner",
|
|
113
|
+
count: 164,
|
|
114
|
+
recordBytes: 42,
|
|
115
|
+
sha256: "e8a8790dd97b54e0d0baac0c2e16b7ad01c0ba3b40575e9283a0797284dd0a8e",
|
|
116
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/8-beginner.b64",
|
|
117
|
+
},
|
|
118
|
+
"8-easy": {
|
|
119
|
+
size: 8,
|
|
120
|
+
difficulty: "easy",
|
|
121
|
+
count: 5187,
|
|
122
|
+
recordBytes: 42,
|
|
123
|
+
sha256: "55f53bb48eb0f42d1251abe747bf720cbcf50256db98a81b015ef4f4d3de536b",
|
|
124
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/8-easy.b64",
|
|
125
|
+
},
|
|
126
|
+
"8-medium": {
|
|
127
|
+
size: 8,
|
|
128
|
+
difficulty: "medium",
|
|
129
|
+
count: 4203,
|
|
130
|
+
recordBytes: 42,
|
|
131
|
+
sha256: "882f9f58b6b90723252c34c7e4b5c4adffe8537e637649f6705d43ed60d4a149",
|
|
132
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/8-medium.b64",
|
|
133
|
+
},
|
|
134
|
+
"8-hard": {
|
|
135
|
+
size: 8,
|
|
136
|
+
difficulty: "hard",
|
|
137
|
+
count: 456,
|
|
138
|
+
recordBytes: 42,
|
|
139
|
+
sha256: "cf89a88a7e3aff16a311cad357982bddc21bb851c83b38d5657da1a6a90d49e1",
|
|
140
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/8-hard.b64",
|
|
141
|
+
},
|
|
142
|
+
"9-beginner": {
|
|
143
|
+
size: 9,
|
|
144
|
+
difficulty: "beginner",
|
|
145
|
+
count: 119,
|
|
146
|
+
recordBytes: 52,
|
|
147
|
+
sha256: "c84d59f243ecef3db80b49bf629803814e47bfa16db6a53e601d234f04932d41",
|
|
148
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/9-beginner.b64",
|
|
149
|
+
},
|
|
150
|
+
"9-easy": {
|
|
151
|
+
size: 9,
|
|
152
|
+
difficulty: "easy",
|
|
153
|
+
count: 3989,
|
|
154
|
+
recordBytes: 52,
|
|
155
|
+
sha256: "30a6832ac47bd5fe02f272e34bde499bfdcaad6830058de50f2ad137fb4780d7",
|
|
156
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/9-easy.b64",
|
|
157
|
+
},
|
|
158
|
+
"9-medium": {
|
|
159
|
+
size: 9,
|
|
160
|
+
difficulty: "medium",
|
|
161
|
+
count: 5002,
|
|
162
|
+
recordBytes: 52,
|
|
163
|
+
sha256: "dca2c411656446d3bfd741b942cd4fded86da1438e15689549c19fa14f0d2c27",
|
|
164
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/9-medium.b64",
|
|
165
|
+
},
|
|
166
|
+
"9-hard": {
|
|
167
|
+
size: 9,
|
|
168
|
+
difficulty: "hard",
|
|
169
|
+
count: 721,
|
|
170
|
+
recordBytes: 52,
|
|
171
|
+
sha256: "b3782fb6a4858c38f6060ca3c0e74ab62bb5c9e826c3c6bf31288c73a99506b6",
|
|
172
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/9-hard.b64",
|
|
173
|
+
},
|
|
174
|
+
"10-beginner": {
|
|
175
|
+
size: 10,
|
|
176
|
+
difficulty: "beginner",
|
|
177
|
+
count: 61,
|
|
178
|
+
recordBytes: 62,
|
|
179
|
+
sha256: "d0b030ae157690fd8306666cd75610d9f724c6fdbf847dc3252de55c4ad97c52",
|
|
180
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/10-beginner.b64",
|
|
181
|
+
},
|
|
182
|
+
"10-easy": {
|
|
183
|
+
size: 10,
|
|
184
|
+
difficulty: "easy",
|
|
185
|
+
count: 2974,
|
|
186
|
+
recordBytes: 62,
|
|
187
|
+
sha256: "51e3b5bd38f91cce2ae9e13942e0b1284f1a8f6489006033cf6917a908691124",
|
|
188
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/10-easy.b64",
|
|
189
|
+
},
|
|
190
|
+
"10-medium": {
|
|
191
|
+
size: 10,
|
|
192
|
+
difficulty: "medium",
|
|
193
|
+
count: 5842,
|
|
194
|
+
recordBytes: 62,
|
|
195
|
+
sha256: "51df6bee224ea44dc58757f960a5e418f01241caddcdd23fd603fee8fb72dbd5",
|
|
196
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/10-medium.b64",
|
|
197
|
+
},
|
|
198
|
+
"10-hard": {
|
|
199
|
+
size: 10,
|
|
200
|
+
difficulty: "hard",
|
|
201
|
+
count: 1044,
|
|
202
|
+
recordBytes: 62,
|
|
203
|
+
sha256: "2481a379ccb608e3f3e21e8068b616f63523c5d70152bb50034cb0041dc46faa",
|
|
204
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/10-hard.b64",
|
|
205
|
+
},
|
|
206
|
+
"11-beginner": {
|
|
207
|
+
size: 11,
|
|
208
|
+
difficulty: "beginner",
|
|
209
|
+
count: 55,
|
|
210
|
+
recordBytes: 74,
|
|
211
|
+
sha256: "3b4aa704d937035969580ff8b8270e3ce672287027d590a99a9a67457b384c4a",
|
|
212
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/11-beginner.b64",
|
|
213
|
+
},
|
|
214
|
+
"11-easy": {
|
|
215
|
+
size: 11,
|
|
216
|
+
difficulty: "easy",
|
|
217
|
+
count: 2257,
|
|
218
|
+
recordBytes: 74,
|
|
219
|
+
sha256: "1c4c5ac5a6d262babac70144024fd5829ebbca5d657cbd4534a6da9b7377e046",
|
|
220
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/11-easy.b64",
|
|
221
|
+
},
|
|
222
|
+
"11-medium": {
|
|
223
|
+
size: 11,
|
|
224
|
+
difficulty: "medium",
|
|
225
|
+
count: 6592,
|
|
226
|
+
recordBytes: 74,
|
|
227
|
+
sha256: "6dea1159d6159ded26743accf88eee714f0ee1dc98bda8a5d998a282f2879ed0",
|
|
228
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/11-medium.b64",
|
|
229
|
+
},
|
|
230
|
+
"11-hard": {
|
|
231
|
+
size: 11,
|
|
232
|
+
difficulty: "hard",
|
|
233
|
+
count: 1750,
|
|
234
|
+
recordBytes: 74,
|
|
235
|
+
sha256: "30c04ec600b0ae57e90d049f8194af67bcb1dfbae29df57c1b2a06a82995c356",
|
|
236
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/11-hard.b64",
|
|
237
|
+
},
|
|
238
|
+
"12-beginner": {
|
|
239
|
+
size: 12,
|
|
240
|
+
difficulty: "beginner",
|
|
241
|
+
count: 20,
|
|
242
|
+
recordBytes: 86,
|
|
243
|
+
sha256: "e59449cc42c78b25e2a82e1fd22cf7a0e021fe9d08517e92ed24b74a7c99eb63",
|
|
244
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/12-beginner.b64",
|
|
245
|
+
},
|
|
246
|
+
"12-easy": {
|
|
247
|
+
size: 12,
|
|
248
|
+
difficulty: "easy",
|
|
249
|
+
count: 1552,
|
|
250
|
+
recordBytes: 86,
|
|
251
|
+
sha256: "05e24ded88bd1f2ba66d2d5bae31b96659a1173e222d067e8afaf22679c38186",
|
|
252
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/12-easy.b64",
|
|
253
|
+
},
|
|
254
|
+
"12-medium": {
|
|
255
|
+
size: 12,
|
|
256
|
+
difficulty: "medium",
|
|
257
|
+
count: 6145,
|
|
258
|
+
recordBytes: 86,
|
|
259
|
+
sha256: "ffe7226b9ed146a91696966ec3d5ef92244a3287baa3081ff8f732f1fe5ad027",
|
|
260
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/12-medium.b64",
|
|
261
|
+
},
|
|
262
|
+
"12-hard": {
|
|
263
|
+
size: 12,
|
|
264
|
+
difficulty: "hard",
|
|
265
|
+
count: 2278,
|
|
266
|
+
recordBytes: 86,
|
|
267
|
+
sha256: "dfe7f957e895dd9f9d3a10bef34615ad1a106f9a2bfa8c54c5db5c75e5e8c679",
|
|
268
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/12-hard.b64",
|
|
269
|
+
},
|
|
270
|
+
"13-beginner": {
|
|
271
|
+
size: 13,
|
|
272
|
+
difficulty: "beginner",
|
|
273
|
+
count: 53,
|
|
274
|
+
recordBytes: 100,
|
|
275
|
+
sha256: "d7bcc65265743fd276da25a2fa1768f38ace124930d3c44d51b54c1f7315eb47",
|
|
276
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/13-beginner.b64",
|
|
277
|
+
},
|
|
278
|
+
"13-easy": {
|
|
279
|
+
size: 13,
|
|
280
|
+
difficulty: "easy",
|
|
281
|
+
count: 2646,
|
|
282
|
+
recordBytes: 100,
|
|
283
|
+
sha256: "8ab626bc8b7bf3362403839b2a3127a3a3eea09f484e20a931fd6515ebf4c56c",
|
|
284
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/13-easy.b64",
|
|
285
|
+
},
|
|
286
|
+
"13-medium": {
|
|
287
|
+
size: 13,
|
|
288
|
+
difficulty: "medium",
|
|
289
|
+
count: 2949,
|
|
290
|
+
recordBytes: 100,
|
|
291
|
+
sha256: "5fd11c7100d513dd1e4a31254d1f857ca27403337b4b24d4989f3f35dffbc4b6",
|
|
292
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/13-medium.b64",
|
|
293
|
+
},
|
|
294
|
+
"13-hard": {
|
|
295
|
+
size: 13,
|
|
296
|
+
difficulty: "hard",
|
|
297
|
+
count: 4582,
|
|
298
|
+
recordBytes: 100,
|
|
299
|
+
sha256: "928d08e0b2d272781adae11692c500629d91db89583778640618f8e969c09e0f",
|
|
300
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/13-hard.b64",
|
|
301
|
+
},
|
|
302
|
+
"14-beginner": {
|
|
303
|
+
size: 14,
|
|
304
|
+
difficulty: "beginner",
|
|
305
|
+
count: 99,
|
|
306
|
+
recordBytes: 114,
|
|
307
|
+
sha256: "53a77886ed0156726e178aed64e558a4777801243875b3347eda87b6fea9e944",
|
|
308
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/14-beginner.b64",
|
|
309
|
+
},
|
|
310
|
+
"14-easy": {
|
|
311
|
+
size: 14,
|
|
312
|
+
difficulty: "easy",
|
|
313
|
+
count: 3338,
|
|
314
|
+
recordBytes: 114,
|
|
315
|
+
sha256: "9959ea694895cc76dbd171c585e03ac5ef913373fb583b038387f7937e55dd9e",
|
|
316
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/14-easy.b64",
|
|
317
|
+
},
|
|
318
|
+
"14-medium": {
|
|
319
|
+
size: 14,
|
|
320
|
+
difficulty: "medium",
|
|
321
|
+
count: 2960,
|
|
322
|
+
recordBytes: 114,
|
|
323
|
+
sha256: "c1e50bb2acd25cbbcdf45fc78dc65f6d292e6ec9af743f207ac4c9930b786090",
|
|
324
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/14-medium.b64",
|
|
325
|
+
},
|
|
326
|
+
"14-hard": {
|
|
327
|
+
size: 14,
|
|
328
|
+
difficulty: "hard",
|
|
329
|
+
count: 5807,
|
|
330
|
+
recordBytes: 114,
|
|
331
|
+
sha256: "c01f62423bf0f8940f26e98f2fab5e276a845194b01ae3e6c5337cdff29493c5",
|
|
332
|
+
url: "https://gist.githubusercontent.com/omercnet/652af12e2640979ba7fb5dc5adddde42/raw/4f3e565c2805a709a5121c5617bdd6f327a2ecd8/14-hard.b64",
|
|
333
|
+
},
|
|
334
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import type { loadPuzzleDeck } from "../shared/puzzle-catalog";
|
|
4
|
+
import { REMOTE_PUZZLE_GROUPS, type RemotePuzzleGroup } from "./curated-manifest";
|
|
5
|
+
|
|
6
|
+
const MAX_REMOTE_BYTES = 2_000_000;
|
|
7
|
+
const FETCH_TIMEOUT_MS = 15_000;
|
|
8
|
+
|
|
9
|
+
type PuzzleDeckInput = z.output<typeof loadPuzzleDeck.input>;
|
|
10
|
+
type PuzzleGroupManifest = Readonly<Record<string, RemotePuzzleGroup>>;
|
|
11
|
+
|
|
12
|
+
export function createPuzzleDeckLoader(
|
|
13
|
+
fetcher: typeof fetch = fetch,
|
|
14
|
+
groups: PuzzleGroupManifest = REMOTE_PUZZLE_GROUPS,
|
|
15
|
+
) {
|
|
16
|
+
const cache = new Map<string, Promise<string>>();
|
|
17
|
+
|
|
18
|
+
const fetchGroup = (key: string, group: RemotePuzzleGroup) => {
|
|
19
|
+
const existing = cache.get(key);
|
|
20
|
+
if (existing) return existing;
|
|
21
|
+
|
|
22
|
+
const request = (async () => {
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetcher(group.url, { signal: controller.signal });
|
|
27
|
+
if (!response.ok) throw new Error(`Curated puzzle host returned HTTP ${response.status}.`);
|
|
28
|
+
const declaredLength = Number(response.headers.get("content-length"));
|
|
29
|
+
if (Number.isFinite(declaredLength) && declaredLength > MAX_REMOTE_BYTES) {
|
|
30
|
+
throw new RangeError("Curated puzzle payload exceeds the download limit.");
|
|
31
|
+
}
|
|
32
|
+
const data = (await response.text()).trim();
|
|
33
|
+
if (data.length === 0 || data.length > MAX_REMOTE_BYTES) {
|
|
34
|
+
throw new RangeError("Curated puzzle payload has an invalid size.");
|
|
35
|
+
}
|
|
36
|
+
const digest = createHash("sha256").update(data).digest("hex");
|
|
37
|
+
if (digest !== group.sha256) {
|
|
38
|
+
throw new Error("Curated puzzle payload failed its integrity check.");
|
|
39
|
+
}
|
|
40
|
+
return data;
|
|
41
|
+
} finally {
|
|
42
|
+
clearTimeout(timeout);
|
|
43
|
+
}
|
|
44
|
+
})().catch((error: unknown) => {
|
|
45
|
+
cache.delete(key);
|
|
46
|
+
throw error;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
cache.set(key, request);
|
|
50
|
+
return request;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return async (input: PuzzleDeckInput) => {
|
|
54
|
+
const key = `${input.size}-${input.difficulty}`;
|
|
55
|
+
const group = groups[key];
|
|
56
|
+
if (!group) {
|
|
57
|
+
throw new RangeError(`No curated ${input.size}×${input.size} ${input.difficulty} deck.`);
|
|
58
|
+
}
|
|
59
|
+
const data = await fetchGroup(key, group);
|
|
60
|
+
return {
|
|
61
|
+
size: group.size,
|
|
62
|
+
difficulty: group.difficulty,
|
|
63
|
+
count: group.count,
|
|
64
|
+
recordBytes: group.recordBytes,
|
|
65
|
+
data,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const getPuzzleDeck = createPuzzleDeckLoader();
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { defineSettings } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { PuzzleDifficultySchema, PuzzleSizeSchema } from "./puzzle-catalog";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_PUZZLE_ID = "paseo-queens-01";
|
|
6
|
+
export const DEFAULT_BOARD_SIZE = 6;
|
|
7
|
+
export const DEFAULT_DIFFICULTY = "easy" as const;
|
|
8
|
+
|
|
9
|
+
export const GAME_SETTINGS_LIMITS = {
|
|
10
|
+
puzzleIdLength: 128,
|
|
11
|
+
puzzles: 256,
|
|
12
|
+
cellsPerPuzzle: 400,
|
|
13
|
+
completionsPerPuzzle: 1_000_000,
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export const PersistedCellStateSchema = z.enum(["empty", "excluded", "marked"]);
|
|
17
|
+
|
|
18
|
+
export const PuzzleIdSchema = z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.max(GAME_SETTINGS_LIMITS.puzzleIdLength)
|
|
22
|
+
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
|
|
23
|
+
|
|
24
|
+
const TimestampMsSchema = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
25
|
+
|
|
26
|
+
export const PuzzleStateSchema = z
|
|
27
|
+
.object({
|
|
28
|
+
cells: z.array(PersistedCellStateSchema).min(1).max(GAME_SETTINGS_LIMITS.cellsPerPuzzle),
|
|
29
|
+
startedAtMs: TimestampMsSchema.nullable(),
|
|
30
|
+
completedAtMs: TimestampMsSchema.nullable(),
|
|
31
|
+
hintUsed: z.boolean(),
|
|
32
|
+
})
|
|
33
|
+
.strict()
|
|
34
|
+
.superRefine((state, context) => {
|
|
35
|
+
if (state.completedAtMs !== null && state.startedAtMs === null) {
|
|
36
|
+
context.addIssue({
|
|
37
|
+
code: "custom",
|
|
38
|
+
path: ["completedAtMs"],
|
|
39
|
+
message: "A completed puzzle must have a start timestamp",
|
|
40
|
+
});
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
state.completedAtMs !== null &&
|
|
46
|
+
state.startedAtMs !== null &&
|
|
47
|
+
state.completedAtMs < state.startedAtMs
|
|
48
|
+
) {
|
|
49
|
+
context.addIssue({
|
|
50
|
+
code: "custom",
|
|
51
|
+
path: ["completedAtMs"],
|
|
52
|
+
message: "Completion timestamp cannot precede start timestamp",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const CompletionRecordSchema = z
|
|
58
|
+
.object({
|
|
59
|
+
completions: z.number().int().min(1).max(GAME_SETTINGS_LIMITS.completionsPerPuzzle),
|
|
60
|
+
bestTimeMs: TimestampMsSchema,
|
|
61
|
+
})
|
|
62
|
+
.strict();
|
|
63
|
+
|
|
64
|
+
export const PuzzleStatesSchema = z
|
|
65
|
+
.record(PuzzleIdSchema, PuzzleStateSchema)
|
|
66
|
+
.superRefine((states, context) => {
|
|
67
|
+
if (Object.keys(states).length > GAME_SETTINGS_LIMITS.puzzles) {
|
|
68
|
+
context.addIssue({
|
|
69
|
+
code: "custom",
|
|
70
|
+
message: `Puzzle state collection cannot exceed ${GAME_SETTINGS_LIMITS.puzzles} entries`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export const CompletionRecordsSchema = z
|
|
76
|
+
.record(PuzzleIdSchema, CompletionRecordSchema)
|
|
77
|
+
.superRefine((records, context) => {
|
|
78
|
+
if (Object.keys(records).length > GAME_SETTINGS_LIMITS.puzzles) {
|
|
79
|
+
context.addIssue({
|
|
80
|
+
code: "custom",
|
|
81
|
+
message: `Completion record collection cannot exceed ${GAME_SETTINGS_LIMITS.puzzles} entries`,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const GameSettingsSchema = z
|
|
87
|
+
.object({
|
|
88
|
+
boardSize: PuzzleSizeSchema.default(DEFAULT_BOARD_SIZE),
|
|
89
|
+
difficulty: PuzzleDifficultySchema.default(DEFAULT_DIFFICULTY),
|
|
90
|
+
currentPuzzleId: PuzzleIdSchema.default(DEFAULT_PUZZLE_ID),
|
|
91
|
+
puzzleStates: PuzzleStatesSchema.default({}),
|
|
92
|
+
records: CompletionRecordsSchema.default({}),
|
|
93
|
+
})
|
|
94
|
+
.strict();
|
|
95
|
+
|
|
96
|
+
export type PersistedCellState = z.infer<typeof PersistedCellStateSchema>;
|
|
97
|
+
export type PuzzleState = z.infer<typeof PuzzleStateSchema>;
|
|
98
|
+
export type CompletionRecord = z.infer<typeof CompletionRecordSchema>;
|
|
99
|
+
export type GameSettings = z.infer<typeof GameSettingsSchema>;
|
|
100
|
+
|
|
101
|
+
export const DEFAULT_GAME_SETTINGS = {
|
|
102
|
+
boardSize: DEFAULT_BOARD_SIZE,
|
|
103
|
+
difficulty: DEFAULT_DIFFICULTY,
|
|
104
|
+
currentPuzzleId: DEFAULT_PUZZLE_ID,
|
|
105
|
+
puzzleStates: {},
|
|
106
|
+
records: {},
|
|
107
|
+
} satisfies GameSettings;
|
|
108
|
+
|
|
109
|
+
export const gameSettings = defineSettings({
|
|
110
|
+
id: "paseo-queens-game",
|
|
111
|
+
scope: "host",
|
|
112
|
+
version: 1,
|
|
113
|
+
schema: GameSettingsSchema,
|
|
114
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineRpc } from "@getpaseo/plugin";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const PUZZLE_SIZES = [5, 6, 7, 8, 9, 10, 11, 12, 13, 14] as const;
|
|
5
|
+
export const PUZZLE_DIFFICULTIES = ["beginner", "easy", "medium", "hard"] as const;
|
|
6
|
+
|
|
7
|
+
export const PuzzleSizeSchema = z.number().int().min(5).max(14);
|
|
8
|
+
export const PuzzleDifficultySchema = z.enum(PUZZLE_DIFFICULTIES);
|
|
9
|
+
|
|
10
|
+
export type PuzzleDifficulty = z.infer<typeof PuzzleDifficultySchema>;
|
|
11
|
+
|
|
12
|
+
export const loadPuzzleDeck = defineRpc({
|
|
13
|
+
name: "queens.load-puzzle-deck",
|
|
14
|
+
input: z
|
|
15
|
+
.object({
|
|
16
|
+
size: PuzzleSizeSchema,
|
|
17
|
+
difficulty: PuzzleDifficultySchema,
|
|
18
|
+
})
|
|
19
|
+
.strict(),
|
|
20
|
+
output: z
|
|
21
|
+
.object({
|
|
22
|
+
size: PuzzleSizeSchema,
|
|
23
|
+
difficulty: PuzzleDifficultySchema,
|
|
24
|
+
count: z.number().int().positive().max(20_000),
|
|
25
|
+
recordBytes: z.number().int().positive().max(256),
|
|
26
|
+
data: z.string().min(1).max(2_000_000),
|
|
27
|
+
})
|
|
28
|
+
.strict(),
|
|
29
|
+
});
|