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.
Files changed (3) hide show
  1. package/README.md +50 -0
  2. package/index.js +118 -23
  3. 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 x = randomInt(1, 9);
11
- const a = randomInt(1, 4);
12
- const b = randomInt(0, 10);
13
- return { text: `${a}x + ${b} = ${a * x + b}`, answer: x };
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 x = randomInt(-12, 12);
17
- const a = randomInt(3, 12);
18
- const b = randomInt(-24, 24);
19
- return { text: `${a}x ${b >= 0 ? '+' : '-'} ${Math.abs(b)} = ${a * x + b}`, answer: x };
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
- const x = randomInt(2, 12);
22
- const a = randomInt(2, 9);
23
- const b = randomInt(1, 25);
24
- return { text: `${a}x + ${b} = ${a * x + b}`, answer: x };
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 === 3 ? 2 : 3;
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 (Number(value) === problem.answer) {
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
- const pad = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '0', 'OK'];
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] }, `Solve for x: ${problem.text}`),
144
- React.createElement(Text, { style: [styles.answer, { color: colors.subtleText }, stylesConfig?.answer] }, input ? `x = ${input}` : 'Enter x'),
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 >= 3) return;
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 === 2 ? '2x2' : '3x3';
187
- const tileSide = tileCount === 3 ? 96 : 80;
188
- const [board, setBoard] = React.useState(() => shuffleBoard(tileCount, tileCount === 3 ? 28 : 80));
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 ? null : React.createElement(Text, { style: [styles.tileText, { color: colors.text }, stylesConfig?.tileText] }, String(value))
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bonk-challanges",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "peerDependencies": {