bonk-challanges 0.1.0 → 0.2.1

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 +122 -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,95 @@ 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) {
68
+ const quotient = randomInt(-12, 12);
69
+ const integerX = quotient * a;
70
+ return { text: `(x / ${a}) ${b >= 0 ? '+' : '-'} ${Math.abs(b)} = ${quotient + b}`, answer: String(integerX), label: 'x' };
71
+ }
72
+ return { text: `${a}x - ${b} = ${a * x - b}`, answer: String(x), label: 'x' };
73
+ }
74
+
75
+ function formatComplex(real, imag) {
76
+ if (imag === 0) return String(real);
77
+ if (real === 0) {
78
+ if (imag === 1) return 'i';
79
+ if (imag === -1) return '-i';
80
+ return `${imag}i`;
81
+ }
82
+ const sign = imag >= 0 ? '+' : '-';
83
+ const mag = Math.abs(imag);
84
+ return `${real}${sign}${mag === 1 ? '' : mag}i`;
85
+ }
86
+
87
+ function normalizeAnswer(value) {
88
+ return String(value)
89
+ .trim()
90
+ .toLowerCase()
91
+ .replace(/\s+/g, '')
92
+ .replace(/−/g, '-')
93
+ .replace(/\*/g, '')
94
+ .replace(/\+-/g, '-');
25
95
  }
26
96
 
27
97
  function boardSizeFromTileCount(tileCount) {
28
- return tileCount === 3 ? 2 : 3;
98
+ return tileCount === 15 ? 4 : 3;
29
99
  }
30
100
 
31
101
  function createSolvedBoard(tileCount) {
@@ -112,7 +182,7 @@ function useMathChallenge(difficulty) {
112
182
  (value, onSolved) => {
113
183
  const nextAttempts = attempts + 1;
114
184
  setAttempts(nextAttempts);
115
- if (Number(value) === problem.answer) {
185
+ if (normalizeAnswer(value) === normalizeAnswer(problem.answer)) {
116
186
  setMessage('Correct');
117
187
  onSolved({
118
188
  type: 'math',
@@ -135,13 +205,28 @@ function MathChallenge({ colors, stylesConfig, onSolved, difficulty = 'intermedi
135
205
  const [input, setInput] = React.useState('');
136
206
  const { problem, message, submit } = useMathChallenge(difficulty);
137
207
 
138
- const pad = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'C', '0', 'OK'];
208
+ React.useEffect(() => {
209
+ setInput('');
210
+ }, [problem.text]);
211
+
212
+ const pad = ['1', '2', '3', '-', '4', '5', '6', '+', '7', '8', '9', 'i', 'C', '0', 'OK'];
139
213
 
140
214
  return React.createElement(
141
215
  View,
142
216
  { 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'),
217
+ React.createElement(Text, { style: [styles.prompt, { color: colors.text }, stylesConfig?.prompt] }, problem.text),
218
+ React.createElement(
219
+ TextInput,
220
+ {
221
+ value: input,
222
+ onChangeText: setInput,
223
+ placeholder: problem.label || 'Answer',
224
+ placeholderTextColor: colors.subtleText,
225
+ autoCapitalize: 'none',
226
+ autoCorrect: false,
227
+ style: [styles.answerInput, { color: colors.text, borderColor: colors.border, backgroundColor: colors.cardMuted }, stylesConfig?.answer],
228
+ }
229
+ ),
145
230
  message
146
231
  ? React.createElement(Text, { style: [styles.feedback, { color: colors.subtleText }, stylesConfig?.feedback] }, message)
147
232
  : null,
@@ -162,7 +247,7 @@ function MathChallenge({ colors, stylesConfig, onSolved, difficulty = 'intermedi
162
247
  submit(input, onSolved);
163
248
  return;
164
249
  }
165
- if (input.length >= 3) return;
250
+ if (input.length >= 12) return;
166
251
  setInput((v) => `${v}${k}`);
167
252
  },
168
253
  style: ({ pressed }) => [
@@ -183,9 +268,9 @@ function MathChallenge({ colors, stylesConfig, onSolved, difficulty = 'intermedi
183
268
 
184
269
  function TileChallenge({ colors, stylesConfig, onSolved, tileCount = 8 }) {
185
270
  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));
271
+ const sizeLabel = size === 4 ? '4x4' : '3x3';
272
+ const tileSide = tileCount === 15 ? 62 : 80;
273
+ const [board, setBoard] = React.useState(() => shuffleBoard(tileCount, tileCount === 15 ? 160 : 80));
189
274
  const [moves, setMoves] = React.useState(0);
190
275
  const startedAtRef = React.useRef(Date.now());
191
276
 
@@ -256,7 +341,13 @@ function TileChallenge({ colors, stylesConfig, onSolved, tileCount = 8 }) {
256
341
  stylesConfig?.tile,
257
342
  ],
258
343
  },
259
- value === 0 ? null : React.createElement(Text, { style: [styles.tileText, { color: colors.text }, stylesConfig?.tileText] }, String(value))
344
+ value === 0
345
+ ? null
346
+ : React.createElement(
347
+ Text,
348
+ { style: [styles.tileText, { color: colors.text, fontSize: tileCount === 15 ? 24 : 30 }, stylesConfig?.tileText] },
349
+ String(value)
350
+ )
260
351
  );
261
352
  })
262
353
  )
@@ -282,6 +373,14 @@ const styles = StyleSheet.create({
282
373
  fontSize: 15,
283
374
  fontWeight: '600',
284
375
  },
376
+ answerInput: {
377
+ minHeight: 46,
378
+ borderWidth: 1,
379
+ borderRadius: 10,
380
+ paddingHorizontal: 12,
381
+ fontSize: 18,
382
+ fontWeight: '700',
383
+ },
285
384
  feedback: {
286
385
  fontSize: 13,
287
386
  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.1",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "peerDependencies": {