bonk-challanges 0.1.0 → 0.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/README.md +50 -0
- package/index.js +118 -23
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# bonk-challanges
|
|
2
|
+
|
|
3
|
+
Reusable React Native challenge components for Bonk wake-up flows.
|
|
4
|
+
|
|
5
|
+
## Exports
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const { BonkChallengeRunner } = require('bonk-challanges');
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
`BonkChallengeRunner` renders one challenge at a time and calls `onSolved(stats)` when the current challenge is complete.
|
|
12
|
+
|
|
13
|
+
## Challenge Types
|
|
14
|
+
|
|
15
|
+
### Math
|
|
16
|
+
|
|
17
|
+
```jsx
|
|
18
|
+
<BonkChallengeRunner type="math" difficulty="advanced" colors={colors} onSolved={onSolved} />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Difficulties:
|
|
22
|
+
|
|
23
|
+
- `easy`
|
|
24
|
+
- `intermediate`
|
|
25
|
+
- `advanced`
|
|
26
|
+
|
|
27
|
+
Stats include `type`, `attempts`, `durationMs`, and `difficulty`.
|
|
28
|
+
|
|
29
|
+
### Tile Puzzle
|
|
30
|
+
|
|
31
|
+
```jsx
|
|
32
|
+
<BonkChallengeRunner type="tile" tileCount={8} colors={colors} onSolved={onSolved} />
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Tile sizes:
|
|
36
|
+
|
|
37
|
+
- `8` = 3x3 puzzle with 8 numbered tiles and one blank space.
|
|
38
|
+
- `15` = 4x4 puzzle with 15 numbered tiles and one blank space.
|
|
39
|
+
|
|
40
|
+
The old 2x2 puzzle has been removed. Stats include `type`, `moves`, `durationMs`, and `tileCount`.
|
|
41
|
+
|
|
42
|
+
## Packaging
|
|
43
|
+
|
|
44
|
+
This package is consumed by the mobile app as a local packed tarball. After editing:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm pack
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Then update the mobile app dependency/lockfile or reinstall so `mobile/node_modules/bonk-challanges` receives the new package contents.
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const React = require('react');
|
|
2
|
-
const { PanResponder, Pressable, StyleSheet, Text, View } = require('react-native');
|
|
2
|
+
const { PanResponder, Pressable, StyleSheet, Text, TextInput, View } = require('react-native');
|
|
3
3
|
|
|
4
4
|
function randomInt(min, max) {
|
|
5
5
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
@@ -7,25 +7,91 @@ function randomInt(min, max) {
|
|
|
7
7
|
|
|
8
8
|
function createMathProblem(difficulty = 'intermediate') {
|
|
9
9
|
if (difficulty === 'easy') {
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
return { text: `${a}
|
|
10
|
+
const a = randomInt(-12, 18);
|
|
11
|
+
const b = randomInt(-10, 14);
|
|
12
|
+
const op = ['+', '-', 'x'][randomInt(0, 2)];
|
|
13
|
+
if (op === '+') return { text: `${a} + ${b} = ?`, answer: String(a + b), label: 'Answer' };
|
|
14
|
+
if (op === '-') return { text: `${a} - ${b} = ?`, answer: String(a - b), label: 'Answer' };
|
|
15
|
+
return { text: `${a} x ${b} = ?`, answer: String(a * b), label: 'Answer' };
|
|
14
16
|
}
|
|
15
17
|
if (difficulty === 'advanced') {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const kind = randomInt(0, 2);
|
|
19
|
+
if (kind === 0) {
|
|
20
|
+
const x = randomInt(-9, 9);
|
|
21
|
+
const a = randomInt(2, 8);
|
|
22
|
+
const b = randomInt(-18, 18);
|
|
23
|
+
return { text: `${a}x^2 ${b >= 0 ? '+' : '-'} ${Math.abs(b)} = ${a * x * x + b}`, answer: String(Math.abs(x)), label: '|x|' };
|
|
24
|
+
}
|
|
25
|
+
if (kind === 1) {
|
|
26
|
+
const x = randomInt(2, 12);
|
|
27
|
+
const a = randomInt(2, 5);
|
|
28
|
+
return { text: `sqrt(${a * x * x}) / sqrt(${a}) = ?`, answer: String(x), label: 'Answer' };
|
|
29
|
+
}
|
|
30
|
+
const x = randomInt(-6, 6);
|
|
31
|
+
const a = randomInt(2, 5);
|
|
32
|
+
const b = randomInt(2, 4);
|
|
33
|
+
return { text: `${a}^${b} + x = ${a ** b + x}`, answer: String(x), label: 'x' };
|
|
20
34
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
35
|
+
if (difficulty === 'professional') {
|
|
36
|
+
const ar = randomInt(-7, 7);
|
|
37
|
+
const ai = randomInt(-7, 7);
|
|
38
|
+
const br = randomInt(-7, 7);
|
|
39
|
+
const bi = randomInt(-7, 7);
|
|
40
|
+
const kind = randomInt(0, 2);
|
|
41
|
+
if (kind === 0) {
|
|
42
|
+
return {
|
|
43
|
+
text: `(${formatComplex(ar, ai)}) + (${formatComplex(br, bi)}) = ?`,
|
|
44
|
+
answer: formatComplex(ar + br, ai + bi),
|
|
45
|
+
label: 'Answer',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (kind === 1) {
|
|
49
|
+
return {
|
|
50
|
+
text: `(${formatComplex(ar, ai)}) - (${formatComplex(br, bi)}) = ?`,
|
|
51
|
+
answer: formatComplex(ar - br, ai - bi),
|
|
52
|
+
label: 'Answer',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
text: `i x (${formatComplex(ar, ai)}) = ?`,
|
|
57
|
+
answer: formatComplex(-ai, ar),
|
|
58
|
+
label: 'Answer',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const x = randomInt(-12, 12);
|
|
62
|
+
const a = randomInt(2, 10);
|
|
63
|
+
const b = randomInt(-24, 24);
|
|
64
|
+
const kind = randomInt(0, 3);
|
|
65
|
+
if (kind === 0) return { text: `${a}x ${b >= 0 ? '+' : '-'} ${Math.abs(b)} = ${a * x + b}`, answer: String(x), label: 'x' };
|
|
66
|
+
if (kind === 1) return { text: `${a}(x ${b >= 0 ? '+' : '-'} ${Math.abs(b)}) = ${a * (x + b)}`, answer: String(x), label: 'x' };
|
|
67
|
+
if (kind === 2) return { text: `(x / ${a}) ${b >= 0 ? '+' : '-'} ${Math.abs(b)} = ${x / a + b}`, answer: String(x), label: 'x' };
|
|
68
|
+
return { text: `${a}x - ${b} = ${a * x - b}`, answer: String(x), label: 'x' };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatComplex(real, imag) {
|
|
72
|
+
if (imag === 0) return String(real);
|
|
73
|
+
if (real === 0) {
|
|
74
|
+
if (imag === 1) return 'i';
|
|
75
|
+
if (imag === -1) return '-i';
|
|
76
|
+
return `${imag}i`;
|
|
77
|
+
}
|
|
78
|
+
const sign = imag >= 0 ? '+' : '-';
|
|
79
|
+
const mag = Math.abs(imag);
|
|
80
|
+
return `${real}${sign}${mag === 1 ? '' : mag}i`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeAnswer(value) {
|
|
84
|
+
return String(value)
|
|
85
|
+
.trim()
|
|
86
|
+
.toLowerCase()
|
|
87
|
+
.replace(/\s+/g, '')
|
|
88
|
+
.replace(/−/g, '-')
|
|
89
|
+
.replace(/\*/g, '')
|
|
90
|
+
.replace(/\+-/g, '-');
|
|
25
91
|
}
|
|
26
92
|
|
|
27
93
|
function boardSizeFromTileCount(tileCount) {
|
|
28
|
-
return tileCount ===
|
|
94
|
+
return tileCount === 15 ? 4 : 3;
|
|
29
95
|
}
|
|
30
96
|
|
|
31
97
|
function createSolvedBoard(tileCount) {
|
|
@@ -112,7 +178,7 @@ function useMathChallenge(difficulty) {
|
|
|
112
178
|
(value, onSolved) => {
|
|
113
179
|
const nextAttempts = attempts + 1;
|
|
114
180
|
setAttempts(nextAttempts);
|
|
115
|
-
if (
|
|
181
|
+
if (normalizeAnswer(value) === normalizeAnswer(problem.answer)) {
|
|
116
182
|
setMessage('Correct');
|
|
117
183
|
onSolved({
|
|
118
184
|
type: 'math',
|
|
@@ -135,13 +201,28 @@ function MathChallenge({ colors, stylesConfig, onSolved, difficulty = 'intermedi
|
|
|
135
201
|
const [input, setInput] = React.useState('');
|
|
136
202
|
const { problem, message, submit } = useMathChallenge(difficulty);
|
|
137
203
|
|
|
138
|
-
|
|
204
|
+
React.useEffect(() => {
|
|
205
|
+
setInput('');
|
|
206
|
+
}, [problem.text]);
|
|
207
|
+
|
|
208
|
+
const pad = ['1', '2', '3', '-', '4', '5', '6', '+', '7', '8', '9', 'i', 'C', '0', 'OK'];
|
|
139
209
|
|
|
140
210
|
return React.createElement(
|
|
141
211
|
View,
|
|
142
212
|
{ style: [styles.block, stylesConfig?.block] },
|
|
143
|
-
React.createElement(Text, { style: [styles.prompt, { color: colors.text }, stylesConfig?.prompt] },
|
|
144
|
-
React.createElement(
|
|
213
|
+
React.createElement(Text, { style: [styles.prompt, { color: colors.text }, stylesConfig?.prompt] }, problem.text),
|
|
214
|
+
React.createElement(
|
|
215
|
+
TextInput,
|
|
216
|
+
{
|
|
217
|
+
value: input,
|
|
218
|
+
onChangeText: setInput,
|
|
219
|
+
placeholder: problem.label || 'Answer',
|
|
220
|
+
placeholderTextColor: colors.subtleText,
|
|
221
|
+
autoCapitalize: 'none',
|
|
222
|
+
autoCorrect: false,
|
|
223
|
+
style: [styles.answerInput, { color: colors.text, borderColor: colors.border, backgroundColor: colors.cardMuted }, stylesConfig?.answer],
|
|
224
|
+
}
|
|
225
|
+
),
|
|
145
226
|
message
|
|
146
227
|
? React.createElement(Text, { style: [styles.feedback, { color: colors.subtleText }, stylesConfig?.feedback] }, message)
|
|
147
228
|
: null,
|
|
@@ -162,7 +243,7 @@ function MathChallenge({ colors, stylesConfig, onSolved, difficulty = 'intermedi
|
|
|
162
243
|
submit(input, onSolved);
|
|
163
244
|
return;
|
|
164
245
|
}
|
|
165
|
-
if (input.length >=
|
|
246
|
+
if (input.length >= 12) return;
|
|
166
247
|
setInput((v) => `${v}${k}`);
|
|
167
248
|
},
|
|
168
249
|
style: ({ pressed }) => [
|
|
@@ -183,9 +264,9 @@ function MathChallenge({ colors, stylesConfig, onSolved, difficulty = 'intermedi
|
|
|
183
264
|
|
|
184
265
|
function TileChallenge({ colors, stylesConfig, onSolved, tileCount = 8 }) {
|
|
185
266
|
const size = boardSizeFromTileCount(tileCount);
|
|
186
|
-
const sizeLabel = size ===
|
|
187
|
-
const tileSide = tileCount ===
|
|
188
|
-
const [board, setBoard] = React.useState(() => shuffleBoard(tileCount, tileCount ===
|
|
267
|
+
const sizeLabel = size === 4 ? '4x4' : '3x3';
|
|
268
|
+
const tileSide = tileCount === 15 ? 62 : 80;
|
|
269
|
+
const [board, setBoard] = React.useState(() => shuffleBoard(tileCount, tileCount === 15 ? 160 : 80));
|
|
189
270
|
const [moves, setMoves] = React.useState(0);
|
|
190
271
|
const startedAtRef = React.useRef(Date.now());
|
|
191
272
|
|
|
@@ -256,7 +337,13 @@ function TileChallenge({ colors, stylesConfig, onSolved, tileCount = 8 }) {
|
|
|
256
337
|
stylesConfig?.tile,
|
|
257
338
|
],
|
|
258
339
|
},
|
|
259
|
-
value === 0
|
|
340
|
+
value === 0
|
|
341
|
+
? null
|
|
342
|
+
: React.createElement(
|
|
343
|
+
Text,
|
|
344
|
+
{ style: [styles.tileText, { color: colors.text, fontSize: tileCount === 15 ? 24 : 30 }, stylesConfig?.tileText] },
|
|
345
|
+
String(value)
|
|
346
|
+
)
|
|
260
347
|
);
|
|
261
348
|
})
|
|
262
349
|
)
|
|
@@ -282,6 +369,14 @@ const styles = StyleSheet.create({
|
|
|
282
369
|
fontSize: 15,
|
|
283
370
|
fontWeight: '600',
|
|
284
371
|
},
|
|
372
|
+
answerInput: {
|
|
373
|
+
minHeight: 46,
|
|
374
|
+
borderWidth: 1,
|
|
375
|
+
borderRadius: 10,
|
|
376
|
+
paddingHorizontal: 12,
|
|
377
|
+
fontSize: 18,
|
|
378
|
+
fontWeight: '700',
|
|
379
|
+
},
|
|
285
380
|
feedback: {
|
|
286
381
|
fontSize: 13,
|
|
287
382
|
fontWeight: '600',
|