@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,187 @@
|
|
|
1
|
+
import type { PluginSurfaceProps } from "@getpaseo/plugin/client";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { Pressable, StyleSheet, Text, type TextStyle, View, type ViewStyle } from "react-native";
|
|
4
|
+
import { PUZZLE_DIFFICULTIES, PUZZLE_SIZES, type PuzzleDifficulty } from "../shared/puzzle-catalog";
|
|
5
|
+
|
|
6
|
+
export type PuzzleSelectorProps = {
|
|
7
|
+
readonly size: number;
|
|
8
|
+
readonly difficulty: PuzzleDifficulty;
|
|
9
|
+
readonly disabled: boolean;
|
|
10
|
+
readonly compact: boolean;
|
|
11
|
+
readonly theme: PluginSurfaceProps["theme"];
|
|
12
|
+
readonly onChange: (size: number, difficulty: PuzzleDifficulty) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function titleCase(value: string): string {
|
|
16
|
+
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function PuzzleSelector({
|
|
20
|
+
size,
|
|
21
|
+
difficulty,
|
|
22
|
+
disabled,
|
|
23
|
+
compact,
|
|
24
|
+
theme,
|
|
25
|
+
onChange,
|
|
26
|
+
}: PuzzleSelectorProps) {
|
|
27
|
+
const styles = useMemo(() => createStyles(theme, compact), [compact, theme]);
|
|
28
|
+
const sizeIndex = Math.max(0, PUZZLE_SIZES.indexOf(size as (typeof PUZZLE_SIZES)[number]));
|
|
29
|
+
const difficultyIndex = Math.max(0, PUZZLE_DIFFICULTIES.indexOf(difficulty));
|
|
30
|
+
|
|
31
|
+
const changeSize = (offset: number) => {
|
|
32
|
+
const nextIndex = (sizeIndex + offset + PUZZLE_SIZES.length) % PUZZLE_SIZES.length;
|
|
33
|
+
onChange(PUZZLE_SIZES[nextIndex], difficulty);
|
|
34
|
+
};
|
|
35
|
+
const changeDifficulty = (offset: number) => {
|
|
36
|
+
const nextIndex =
|
|
37
|
+
(difficultyIndex + offset + PUZZLE_DIFFICULTIES.length) % PUZZLE_DIFFICULTIES.length;
|
|
38
|
+
onChange(size, PUZZLE_DIFFICULTIES[nextIndex]);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<View style={styles.container}>
|
|
43
|
+
<SelectorStep
|
|
44
|
+
label="Board size"
|
|
45
|
+
value={`${size}×${size}`}
|
|
46
|
+
disabled={disabled}
|
|
47
|
+
styles={styles}
|
|
48
|
+
onPrevious={() => changeSize(-1)}
|
|
49
|
+
onNext={() => changeSize(1)}
|
|
50
|
+
/>
|
|
51
|
+
<SelectorStep
|
|
52
|
+
label="Difficulty"
|
|
53
|
+
value={titleCase(difficulty)}
|
|
54
|
+
disabled={disabled}
|
|
55
|
+
styles={styles}
|
|
56
|
+
onPrevious={() => changeDifficulty(-1)}
|
|
57
|
+
onNext={() => changeDifficulty(1)}
|
|
58
|
+
/>
|
|
59
|
+
</View>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type SelectorStyles = {
|
|
64
|
+
readonly container: ViewStyle;
|
|
65
|
+
readonly step: ViewStyle;
|
|
66
|
+
readonly arrow: ViewStyle;
|
|
67
|
+
readonly arrowText: TextStyle;
|
|
68
|
+
readonly valueBlock: ViewStyle;
|
|
69
|
+
readonly label: TextStyle;
|
|
70
|
+
readonly value: TextStyle;
|
|
71
|
+
readonly disabled: ViewStyle;
|
|
72
|
+
readonly pressed: ViewStyle;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function SelectorStep({
|
|
76
|
+
label,
|
|
77
|
+
value,
|
|
78
|
+
disabled,
|
|
79
|
+
styles,
|
|
80
|
+
onPrevious,
|
|
81
|
+
onNext,
|
|
82
|
+
}: {
|
|
83
|
+
readonly label: string;
|
|
84
|
+
readonly value: string;
|
|
85
|
+
readonly disabled: boolean;
|
|
86
|
+
readonly styles: SelectorStyles;
|
|
87
|
+
readonly onPrevious: () => void;
|
|
88
|
+
readonly onNext: () => void;
|
|
89
|
+
}) {
|
|
90
|
+
return (
|
|
91
|
+
<View
|
|
92
|
+
accessibilityRole="adjustable"
|
|
93
|
+
accessibilityLabel={`${label}, ${value}`}
|
|
94
|
+
style={styles.step}
|
|
95
|
+
>
|
|
96
|
+
<Pressable
|
|
97
|
+
accessibilityRole="button"
|
|
98
|
+
accessibilityLabel={`Previous ${label.toLowerCase()}`}
|
|
99
|
+
disabled={disabled}
|
|
100
|
+
onPress={onPrevious}
|
|
101
|
+
style={({ pressed }) => [
|
|
102
|
+
styles.arrow,
|
|
103
|
+
disabled && styles.disabled,
|
|
104
|
+
pressed && styles.pressed,
|
|
105
|
+
]}
|
|
106
|
+
>
|
|
107
|
+
<Text style={styles.arrowText}>‹</Text>
|
|
108
|
+
</Pressable>
|
|
109
|
+
<View style={styles.valueBlock}>
|
|
110
|
+
<Text style={styles.label}>{label.toUpperCase()}</Text>
|
|
111
|
+
<Text style={styles.value}>{value}</Text>
|
|
112
|
+
</View>
|
|
113
|
+
<Pressable
|
|
114
|
+
accessibilityRole="button"
|
|
115
|
+
accessibilityLabel={`Next ${label.toLowerCase()}`}
|
|
116
|
+
disabled={disabled}
|
|
117
|
+
onPress={onNext}
|
|
118
|
+
style={({ pressed }) => [
|
|
119
|
+
styles.arrow,
|
|
120
|
+
disabled && styles.disabled,
|
|
121
|
+
pressed && styles.pressed,
|
|
122
|
+
]}
|
|
123
|
+
>
|
|
124
|
+
<Text style={styles.arrowText}>›</Text>
|
|
125
|
+
</Pressable>
|
|
126
|
+
</View>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createStyles(theme: PluginSurfaceProps["theme"], compact: boolean): SelectorStyles {
|
|
131
|
+
return StyleSheet.create({
|
|
132
|
+
container: {
|
|
133
|
+
width: "100%",
|
|
134
|
+
flexDirection: "row",
|
|
135
|
+
flexWrap: "wrap",
|
|
136
|
+
justifyContent: "center",
|
|
137
|
+
gap: compact ? 8 : 12,
|
|
138
|
+
},
|
|
139
|
+
step: {
|
|
140
|
+
minWidth: compact ? 136 : 152,
|
|
141
|
+
minHeight: 44,
|
|
142
|
+
flexDirection: "row",
|
|
143
|
+
alignItems: "center",
|
|
144
|
+
justifyContent: "center",
|
|
145
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
146
|
+
borderColor: theme.colors.border,
|
|
147
|
+
borderRadius: 10,
|
|
148
|
+
backgroundColor: theme.colors.surface1,
|
|
149
|
+
overflow: "hidden",
|
|
150
|
+
},
|
|
151
|
+
arrow: {
|
|
152
|
+
width: 40,
|
|
153
|
+
minHeight: 44,
|
|
154
|
+
alignItems: "center",
|
|
155
|
+
justifyContent: "center",
|
|
156
|
+
},
|
|
157
|
+
arrowText: {
|
|
158
|
+
color: theme.colors.foreground,
|
|
159
|
+
fontSize: 24,
|
|
160
|
+
lineHeight: 28,
|
|
161
|
+
fontWeight: "500",
|
|
162
|
+
},
|
|
163
|
+
valueBlock: {
|
|
164
|
+
minWidth: compact ? 56 : 68,
|
|
165
|
+
flex: 1,
|
|
166
|
+
alignItems: "center",
|
|
167
|
+
gap: 1,
|
|
168
|
+
},
|
|
169
|
+
label: {
|
|
170
|
+
color: theme.colors.foregroundMuted,
|
|
171
|
+
fontSize: 8,
|
|
172
|
+
fontWeight: "700",
|
|
173
|
+
letterSpacing: 0.8,
|
|
174
|
+
},
|
|
175
|
+
value: {
|
|
176
|
+
color: theme.colors.foreground,
|
|
177
|
+
fontSize: compact ? 12 : 13,
|
|
178
|
+
fontWeight: "800",
|
|
179
|
+
},
|
|
180
|
+
disabled: {
|
|
181
|
+
opacity: 0.4,
|
|
182
|
+
},
|
|
183
|
+
pressed: {
|
|
184
|
+
backgroundColor: theme.colors.surface2,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|