bonk-challanges 0.2.2 → 0.4.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/index.js +578 -28
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const React = require('react');
|
|
2
|
-
const { PanResponder, Pressable, StyleSheet, Text, TextInput, View } = require('react-native');
|
|
2
|
+
const { Animated, 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;
|
|
@@ -58,6 +58,37 @@ function createMathProblem(difficulty = 'intermediate') {
|
|
|
58
58
|
label: 'Answer',
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
+
if (difficulty === 'marc') {
|
|
62
|
+
const kind = randomInt(0, 2);
|
|
63
|
+
if (kind === 0) {
|
|
64
|
+
const slope = randomInt(-5, 5) * 2;
|
|
65
|
+
const intercept = randomInt(-8, 8);
|
|
66
|
+
const upper = randomInt(2, 8);
|
|
67
|
+
const answer = (slope / 2) * upper * upper + intercept * upper;
|
|
68
|
+
return {
|
|
69
|
+
text: `Integral from 0 to ${upper} of (${slope}x ${intercept >= 0 ? '+' : '-'} ${Math.abs(intercept)}) dx = ?`,
|
|
70
|
+
answer: String(answer),
|
|
71
|
+
label: 'Answer',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (kind === 1) {
|
|
75
|
+
const matrix = Array.from({ length: 3 }, () => Array.from({ length: 3 }, () => randomInt(-4, 5)));
|
|
76
|
+
return {
|
|
77
|
+
text: `det(\n${formatMatrix(matrix)}\n) = ?`,
|
|
78
|
+
answer: String(determinant3(matrix)),
|
|
79
|
+
label: 'det',
|
|
80
|
+
monospace: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const a = [randomInt(-5, 5), randomInt(-5, 5), randomInt(-5, 5)];
|
|
84
|
+
const b = [randomInt(-5, 5), randomInt(-5, 5), randomInt(-5, 5)];
|
|
85
|
+
return {
|
|
86
|
+
text: `a x b\n\na = ${formatVerticalVector(a)}\nb = ${formatVerticalVector(b)}\n\nAnswer as x,y,z`,
|
|
87
|
+
answer: formatVector(crossProduct(a, b)),
|
|
88
|
+
label: 'x,y,z',
|
|
89
|
+
monospace: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
61
92
|
const x = randomInt(-12, 12);
|
|
62
93
|
const a = randomInt(2, 10);
|
|
63
94
|
const b = randomInt(-24, 24);
|
|
@@ -72,6 +103,33 @@ function createMathProblem(difficulty = 'intermediate') {
|
|
|
72
103
|
return { text: `${a}x - ${b} = ${a * x - b}`, answer: String(x), label: 'x' };
|
|
73
104
|
}
|
|
74
105
|
|
|
106
|
+
function determinant3(matrix) {
|
|
107
|
+
const [[a, b, c], [d, e, f], [g, h, i]] = matrix;
|
|
108
|
+
return a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function crossProduct(a, b) {
|
|
112
|
+
return [
|
|
113
|
+
a[1] * b[2] - a[2] * b[1],
|
|
114
|
+
a[2] * b[0] - a[0] * b[2],
|
|
115
|
+
a[0] * b[1] - a[1] * b[0],
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function formatVector(vector) {
|
|
120
|
+
return vector.join(',');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function formatMatrix(matrix) {
|
|
124
|
+
const width = Math.max(...matrix.flat().map((value) => String(value).length));
|
|
125
|
+
return matrix.map((row) => `[ ${row.map((value) => String(value).padStart(width, ' ')).join(' ')} ]`).join('\n');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function formatVerticalVector(vector) {
|
|
129
|
+
const width = Math.max(...vector.map((value) => String(value).length));
|
|
130
|
+
return `[ ${vector.map((value) => String(value).padStart(width, ' ')).join(' ')} ]`;
|
|
131
|
+
}
|
|
132
|
+
|
|
75
133
|
function formatComplex(real, imag) {
|
|
76
134
|
if (imag === 0) return String(real);
|
|
77
135
|
if (real === 0) {
|
|
@@ -90,6 +148,7 @@ function normalizeAnswer(value) {
|
|
|
90
148
|
.toLowerCase()
|
|
91
149
|
.replace(/\s+/g, '')
|
|
92
150
|
.replace(/−/g, '-')
|
|
151
|
+
.replace(/[()[\]]/g, '')
|
|
93
152
|
.replace(/\*/g, '')
|
|
94
153
|
.replace(/\+-/g, '-');
|
|
95
154
|
}
|
|
@@ -165,6 +224,129 @@ function swipeTargetFromGesture(index, dx, dy, tileCount) {
|
|
|
165
224
|
return null;
|
|
166
225
|
}
|
|
167
226
|
|
|
227
|
+
function createDeck() {
|
|
228
|
+
const suits = ['S', 'H', 'D', 'C'];
|
|
229
|
+
const ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
|
|
230
|
+
const deck = [];
|
|
231
|
+
suits.forEach((suit) => ranks.forEach((rank) => deck.push({ rank, suit, id: `${rank}${suit}` })));
|
|
232
|
+
for (let i = deck.length - 1; i > 0; i -= 1) {
|
|
233
|
+
const j = randomInt(0, i);
|
|
234
|
+
const tmp = deck[i];
|
|
235
|
+
deck[i] = deck[j];
|
|
236
|
+
deck[j] = tmp;
|
|
237
|
+
}
|
|
238
|
+
return deck;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function drawCard(deck) {
|
|
242
|
+
return deck.pop();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function cardLabel(card) {
|
|
246
|
+
return `${card.rank}${suitSymbol(card.suit)}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function suitSymbol(suit) {
|
|
250
|
+
if (suit === 'S') return '♠';
|
|
251
|
+
if (suit === 'H') return '♥';
|
|
252
|
+
if (suit === 'D') return '♦';
|
|
253
|
+
return '♣';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function isRedSuit(suit) {
|
|
257
|
+
return suit === 'H' || suit === 'D';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function handValue(cards) {
|
|
261
|
+
let total = 0;
|
|
262
|
+
let aces = 0;
|
|
263
|
+
cards.forEach((card) => {
|
|
264
|
+
if (card.rank === 'A') {
|
|
265
|
+
aces += 1;
|
|
266
|
+
total += 11;
|
|
267
|
+
} else if (['K', 'Q', 'J'].includes(card.rank)) {
|
|
268
|
+
total += 10;
|
|
269
|
+
} else {
|
|
270
|
+
total += Number(card.rank);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
while (total > 21 && aces > 0) {
|
|
274
|
+
total -= 10;
|
|
275
|
+
aces -= 1;
|
|
276
|
+
}
|
|
277
|
+
return total;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function canSplitHand(hand) {
|
|
281
|
+
return hand.cards.length === 2 && hand.cards[0].rank === hand.cards[1].rank;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function createBlackjackRound(rounds = 0, message = 'Beat the dealer to pass.') {
|
|
285
|
+
const deck = createDeck();
|
|
286
|
+
const playerCards = [drawCard(deck), drawCard(deck)];
|
|
287
|
+
const dealerCards = [drawCard(deck), drawCard(deck)];
|
|
288
|
+
return {
|
|
289
|
+
deck,
|
|
290
|
+
dealerCards,
|
|
291
|
+
hands: [{ cards: playerCards, bet: 1, done: false }],
|
|
292
|
+
activeIndex: 0,
|
|
293
|
+
rounds,
|
|
294
|
+
message,
|
|
295
|
+
finished: false,
|
|
296
|
+
startedAt: Date.now(),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function activeBlackjackHand(game) {
|
|
301
|
+
return game.hands[game.activeIndex];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function nextBlackjackHandOrResolve(game) {
|
|
305
|
+
const nextIndex = game.hands.findIndex((hand, index) => index > game.activeIndex && !hand.done);
|
|
306
|
+
if (nextIndex >= 0) return { ...game, activeIndex: nextIndex };
|
|
307
|
+
return resolveBlackjackRound(game);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function dealerPlay(deck, dealerCards) {
|
|
311
|
+
const nextDeck = [...deck];
|
|
312
|
+
const nextDealer = [...dealerCards];
|
|
313
|
+
while (handValue(nextDealer) < 17) {
|
|
314
|
+
nextDealer.push(drawCard(nextDeck));
|
|
315
|
+
}
|
|
316
|
+
return { deck: nextDeck, dealerCards: nextDealer };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function resolveBlackjackRound(game) {
|
|
320
|
+
const { deck, dealerCards } = dealerPlay(game.deck, game.dealerCards);
|
|
321
|
+
const dealerTotal = handValue(dealerCards);
|
|
322
|
+
let won = false;
|
|
323
|
+
const resultLines = game.hands.map((hand, index) => {
|
|
324
|
+
const total = handValue(hand.cards);
|
|
325
|
+
let result = 'Push';
|
|
326
|
+
if (total > 21) result = 'Bust';
|
|
327
|
+
else if (dealerTotal > 21 || total > dealerTotal) {
|
|
328
|
+
result = 'Win';
|
|
329
|
+
won = true;
|
|
330
|
+
} else if (total < dealerTotal) result = 'Lose';
|
|
331
|
+
return `Hand ${index + 1}: ${result} (${total})`;
|
|
332
|
+
});
|
|
333
|
+
if (won) {
|
|
334
|
+
return {
|
|
335
|
+
...game,
|
|
336
|
+
deck,
|
|
337
|
+
dealerCards,
|
|
338
|
+
message: `You won. ${resultLines.join(' ')}`,
|
|
339
|
+
finished: true,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return createBlackjackRound(game.rounds + 1, `No win yet. ${resultLines.join(' ')} New deal.`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function updateBlackjackHand(game, updater) {
|
|
346
|
+
const hands = game.hands.map((hand, index) => (index === game.activeIndex ? updater(hand) : hand));
|
|
347
|
+
return { ...game, hands };
|
|
348
|
+
}
|
|
349
|
+
|
|
168
350
|
function useMathChallenge(difficulty) {
|
|
169
351
|
const [problem, setProblem] = React.useState(() => createMathProblem(difficulty));
|
|
170
352
|
const [message, setMessage] = React.useState('');
|
|
@@ -209,12 +391,23 @@ function MathChallenge({ colors, stylesConfig, onSolved, difficulty = 'intermedi
|
|
|
209
391
|
setInput('');
|
|
210
392
|
}, [problem.text]);
|
|
211
393
|
|
|
212
|
-
const
|
|
394
|
+
const padRows = [['-', '+', 'i'], ['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9'], [',', 'C', '0', 'OK']];
|
|
213
395
|
|
|
214
396
|
return React.createElement(
|
|
215
397
|
View,
|
|
216
398
|
{ style: [styles.block, stylesConfig?.block] },
|
|
217
|
-
React.createElement(
|
|
399
|
+
React.createElement(
|
|
400
|
+
Text,
|
|
401
|
+
{
|
|
402
|
+
style: [
|
|
403
|
+
styles.prompt,
|
|
404
|
+
problem.monospace ? styles.monospacePrompt : null,
|
|
405
|
+
{ color: colors.text },
|
|
406
|
+
stylesConfig?.prompt,
|
|
407
|
+
],
|
|
408
|
+
},
|
|
409
|
+
problem.text
|
|
410
|
+
),
|
|
218
411
|
React.createElement(
|
|
219
412
|
TextInput,
|
|
220
413
|
{
|
|
@@ -233,33 +426,42 @@ function MathChallenge({ colors, stylesConfig, onSolved, difficulty = 'intermedi
|
|
|
233
426
|
React.createElement(
|
|
234
427
|
View,
|
|
235
428
|
{ style: styles.padGrid },
|
|
236
|
-
|
|
429
|
+
padRows.map((row) =>
|
|
237
430
|
React.createElement(
|
|
238
|
-
|
|
431
|
+
View,
|
|
239
432
|
{
|
|
240
|
-
key:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (k === 'OK') {
|
|
247
|
-
submit(input, onSolved);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
if (input.length >= 12) return;
|
|
251
|
-
setInput((v) => `${v}${k}`);
|
|
252
|
-
},
|
|
253
|
-
style: ({ pressed }) => [
|
|
254
|
-
styles.key,
|
|
433
|
+
key: row.join(''),
|
|
434
|
+
style: styles.padRow,
|
|
435
|
+
},
|
|
436
|
+
row.map((k) =>
|
|
437
|
+
React.createElement(
|
|
438
|
+
Pressable,
|
|
255
439
|
{
|
|
256
|
-
|
|
257
|
-
|
|
440
|
+
key: k,
|
|
441
|
+
onPress: () => {
|
|
442
|
+
if (k === 'C') {
|
|
443
|
+
setInput('');
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (k === 'OK') {
|
|
447
|
+
submit(input, onSolved);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (input.length >= 18) return;
|
|
451
|
+
setInput((v) => `${v}${k}`);
|
|
452
|
+
},
|
|
453
|
+
style: ({ pressed }) => [
|
|
454
|
+
styles.key,
|
|
455
|
+
{
|
|
456
|
+
borderColor: colors.border,
|
|
457
|
+
backgroundColor: pressed ? colors.primaryPressed : colors.cardMuted,
|
|
458
|
+
},
|
|
459
|
+
stylesConfig?.key,
|
|
460
|
+
],
|
|
258
461
|
},
|
|
259
|
-
stylesConfig?.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
React.createElement(Text, { style: [styles.keyText, { color: colors.text }, stylesConfig?.keyText] }, k)
|
|
462
|
+
React.createElement(Text, { style: [styles.keyText, { color: colors.text }, stylesConfig?.keyText] }, k)
|
|
463
|
+
)
|
|
464
|
+
)
|
|
263
465
|
)
|
|
264
466
|
)
|
|
265
467
|
)
|
|
@@ -354,10 +556,255 @@ function TileChallenge({ colors, stylesConfig, onSolved, tileCount = 8 }) {
|
|
|
354
556
|
);
|
|
355
557
|
}
|
|
356
558
|
|
|
559
|
+
function BlackjackChallenge({ colors, stylesConfig, onSolved }) {
|
|
560
|
+
const [game, setGame] = React.useState(() => createBlackjackRound());
|
|
561
|
+
const activeHand = activeBlackjackHand(game);
|
|
562
|
+
const dealerShownCards = game.finished ? game.dealerCards : [game.dealerCards[0]];
|
|
563
|
+
const canAct = Boolean(activeHand) && !game.finished;
|
|
564
|
+
const canDouble = canAct && activeHand.cards.length === 2;
|
|
565
|
+
const canSplit = canAct && game.hands.length < 4 && canSplitHand(activeHand);
|
|
566
|
+
|
|
567
|
+
const completeIfWon = React.useCallback(
|
|
568
|
+
(nextGame) => {
|
|
569
|
+
setGame(nextGame);
|
|
570
|
+
if (nextGame.finished) {
|
|
571
|
+
onSolved({
|
|
572
|
+
type: 'blackjack',
|
|
573
|
+
attempts: nextGame.rounds + 1,
|
|
574
|
+
durationMs: Date.now() - nextGame.startedAt,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
[onSolved]
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
const hit = React.useCallback(() => {
|
|
582
|
+
if (!canAct) return;
|
|
583
|
+
const deck = [...game.deck];
|
|
584
|
+
let next = updateBlackjackHand({ ...game, deck }, (hand) => ({
|
|
585
|
+
...hand,
|
|
586
|
+
cards: [...hand.cards, drawCard(deck)],
|
|
587
|
+
}));
|
|
588
|
+
next = { ...next, deck };
|
|
589
|
+
if (handValue(activeBlackjackHand(next).cards) > 21) {
|
|
590
|
+
next = updateBlackjackHand(next, (hand) => ({ ...hand, done: true }));
|
|
591
|
+
next = nextBlackjackHandOrResolve(next);
|
|
592
|
+
}
|
|
593
|
+
completeIfWon(next);
|
|
594
|
+
}, [canAct, completeIfWon, game]);
|
|
595
|
+
|
|
596
|
+
const stand = React.useCallback(() => {
|
|
597
|
+
if (!canAct) return;
|
|
598
|
+
let next = updateBlackjackHand(game, (hand) => ({ ...hand, done: true }));
|
|
599
|
+
next = nextBlackjackHandOrResolve(next);
|
|
600
|
+
completeIfWon(next);
|
|
601
|
+
}, [canAct, completeIfWon, game]);
|
|
602
|
+
|
|
603
|
+
const doubleDown = React.useCallback(() => {
|
|
604
|
+
if (!canDouble) return;
|
|
605
|
+
const deck = [...game.deck];
|
|
606
|
+
let next = updateBlackjackHand({ ...game, deck }, (hand) => ({
|
|
607
|
+
...hand,
|
|
608
|
+
bet: hand.bet * 2,
|
|
609
|
+
cards: [...hand.cards, drawCard(deck)],
|
|
610
|
+
done: true,
|
|
611
|
+
}));
|
|
612
|
+
next = nextBlackjackHandOrResolve({ ...next, deck });
|
|
613
|
+
completeIfWon(next);
|
|
614
|
+
}, [canDouble, completeIfWon, game]);
|
|
615
|
+
|
|
616
|
+
const split = React.useCallback(() => {
|
|
617
|
+
if (!canSplit) return;
|
|
618
|
+
const deck = [...game.deck];
|
|
619
|
+
const hands = [...game.hands];
|
|
620
|
+
const [first, second] = activeHand.cards;
|
|
621
|
+
hands.splice(
|
|
622
|
+
game.activeIndex,
|
|
623
|
+
1,
|
|
624
|
+
{ cards: [first, drawCard(deck)], bet: activeHand.bet, done: false },
|
|
625
|
+
{ cards: [second, drawCard(deck)], bet: activeHand.bet, done: false }
|
|
626
|
+
);
|
|
627
|
+
setGame({ ...game, deck, hands });
|
|
628
|
+
}, [activeHand, canSplit, game]);
|
|
629
|
+
|
|
630
|
+
const renderHand = (hand, index) =>
|
|
631
|
+
React.createElement(
|
|
632
|
+
View,
|
|
633
|
+
{
|
|
634
|
+
key: index,
|
|
635
|
+
style: [
|
|
636
|
+
styles.blackjackHand,
|
|
637
|
+
{
|
|
638
|
+
borderColor: index === game.activeIndex && !game.finished ? colors.primary : colors.border,
|
|
639
|
+
backgroundColor: colors.cardMuted,
|
|
640
|
+
},
|
|
641
|
+
],
|
|
642
|
+
},
|
|
643
|
+
React.createElement(
|
|
644
|
+
Text,
|
|
645
|
+
{ style: [styles.blackjackLabel, { color: colors.subtleText }] },
|
|
646
|
+
`Hand ${index + 1}${hand.bet > 1 ? ' x2' : ''}`
|
|
647
|
+
),
|
|
648
|
+
React.createElement(
|
|
649
|
+
View,
|
|
650
|
+
{ style: styles.cardRow },
|
|
651
|
+
hand.cards.map((card, cardIndex) =>
|
|
652
|
+
React.createElement(AnimatedCard, {
|
|
653
|
+
key: `${game.rounds}-${index}-${card.id}-${cardIndex}-${hand.cards.length}`,
|
|
654
|
+
card,
|
|
655
|
+
delay: 120 + index * 120 + cardIndex * 95,
|
|
656
|
+
})
|
|
657
|
+
)
|
|
658
|
+
),
|
|
659
|
+
React.createElement(
|
|
660
|
+
Text,
|
|
661
|
+
{ style: [styles.blackjackValue, { color: handValue(hand.cards) > 21 ? '#D65A5A' : colors.subtleText }] },
|
|
662
|
+
`Value: ${handValue(hand.cards)}${hand.done ? ' Done' : ''}`
|
|
663
|
+
)
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
return React.createElement(
|
|
667
|
+
View,
|
|
668
|
+
{ style: [styles.block, stylesConfig?.block] },
|
|
669
|
+
React.createElement(Text, { style: [styles.prompt, { color: colors.text }, stylesConfig?.prompt] }, 'Blackjack: beat the dealer'),
|
|
670
|
+
React.createElement(
|
|
671
|
+
View,
|
|
672
|
+
{ style: [styles.blackjackDealer, { borderColor: colors.border, backgroundColor: colors.cardMuted }] },
|
|
673
|
+
React.createElement(Text, { style: [styles.blackjackLabel, { color: colors.subtleText }] }, 'Dealer'),
|
|
674
|
+
React.createElement(
|
|
675
|
+
View,
|
|
676
|
+
{ style: styles.cardRow },
|
|
677
|
+
dealerShownCards.map((card, index) =>
|
|
678
|
+
React.createElement(AnimatedCard, {
|
|
679
|
+
key: `${game.rounds}-dealer-${card.id}-${index}-${game.finished ? 'show' : 'peek'}`,
|
|
680
|
+
card,
|
|
681
|
+
delay: index * 95,
|
|
682
|
+
})
|
|
683
|
+
).concat(
|
|
684
|
+
game.finished
|
|
685
|
+
? []
|
|
686
|
+
: [
|
|
687
|
+
React.createElement(AnimatedCard, {
|
|
688
|
+
key: `${game.rounds}-dealer-hidden`,
|
|
689
|
+
hidden: true,
|
|
690
|
+
delay: 95,
|
|
691
|
+
}),
|
|
692
|
+
]
|
|
693
|
+
)
|
|
694
|
+
),
|
|
695
|
+
React.createElement(
|
|
696
|
+
Text,
|
|
697
|
+
{ style: [styles.blackjackValue, { color: colors.subtleText }] },
|
|
698
|
+
game.finished ? `Value: ${handValue(game.dealerCards)}` : `Showing: ${handValue(dealerShownCards)}`
|
|
699
|
+
)
|
|
700
|
+
),
|
|
701
|
+
React.createElement(View, { style: styles.blackjackHands }, game.hands.map(renderHand)),
|
|
702
|
+
React.createElement(Text, { style: [styles.feedback, { color: colors.subtleText }, stylesConfig?.feedback] }, game.message),
|
|
703
|
+
React.createElement(
|
|
704
|
+
View,
|
|
705
|
+
{ style: styles.blackjackActions },
|
|
706
|
+
React.createElement(BlackjackAction, { label: 'Hit', colors, disabled: !canAct, onPress: hit }),
|
|
707
|
+
React.createElement(BlackjackAction, { label: 'Stand', colors, disabled: !canAct, onPress: stand }),
|
|
708
|
+
React.createElement(BlackjackAction, { label: 'Double', colors, disabled: !canDouble, onPress: doubleDown }),
|
|
709
|
+
React.createElement(BlackjackAction, { label: 'Split', colors, disabled: !canSplit, onPress: split })
|
|
710
|
+
)
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function BlackjackAction({ label, colors, disabled, onPress }) {
|
|
715
|
+
return React.createElement(
|
|
716
|
+
Pressable,
|
|
717
|
+
{
|
|
718
|
+
disabled,
|
|
719
|
+
onPress,
|
|
720
|
+
style: ({ pressed }) => [
|
|
721
|
+
styles.blackjackAction,
|
|
722
|
+
{
|
|
723
|
+
borderColor: colors.border,
|
|
724
|
+
backgroundColor: pressed ? colors.primaryPressed : colors.cardMuted,
|
|
725
|
+
opacity: disabled ? 0.45 : 1,
|
|
726
|
+
},
|
|
727
|
+
],
|
|
728
|
+
},
|
|
729
|
+
React.createElement(Text, { style: [styles.blackjackActionText, { color: colors.text }] }, label)
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function AnimatedCard({ card, hidden = false, delay = 0 }) {
|
|
734
|
+
const progress = React.useRef(new Animated.Value(0)).current;
|
|
735
|
+
|
|
736
|
+
React.useEffect(() => {
|
|
737
|
+
progress.setValue(0);
|
|
738
|
+
Animated.spring(progress, {
|
|
739
|
+
toValue: 1,
|
|
740
|
+
delay,
|
|
741
|
+
speed: 14,
|
|
742
|
+
bounciness: 8,
|
|
743
|
+
useNativeDriver: true,
|
|
744
|
+
}).start();
|
|
745
|
+
}, [delay, progress]);
|
|
746
|
+
|
|
747
|
+
return React.createElement(
|
|
748
|
+
Animated.View,
|
|
749
|
+
{
|
|
750
|
+
style: [
|
|
751
|
+
styles.playingCardAnim,
|
|
752
|
+
{
|
|
753
|
+
opacity: progress,
|
|
754
|
+
transform: [
|
|
755
|
+
{
|
|
756
|
+
translateY: progress.interpolate({
|
|
757
|
+
inputRange: [0, 1],
|
|
758
|
+
outputRange: [-26, 0],
|
|
759
|
+
}),
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
translateX: progress.interpolate({
|
|
763
|
+
inputRange: [0, 1],
|
|
764
|
+
outputRange: [22, 0],
|
|
765
|
+
}),
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
rotate: progress.interpolate({
|
|
769
|
+
inputRange: [0, 1],
|
|
770
|
+
outputRange: ['-7deg', '0deg'],
|
|
771
|
+
}),
|
|
772
|
+
},
|
|
773
|
+
],
|
|
774
|
+
},
|
|
775
|
+
],
|
|
776
|
+
},
|
|
777
|
+
React.createElement(PlayingCard, { card, hidden })
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function PlayingCard({ card, hidden = false }) {
|
|
782
|
+
if (hidden) {
|
|
783
|
+
return React.createElement(
|
|
784
|
+
View,
|
|
785
|
+
{ style: [styles.playingCard, styles.playingCardBack] },
|
|
786
|
+
React.createElement(Text, { style: styles.cardBackMark }, 'BONK')
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
const red = isRedSuit(card.suit);
|
|
790
|
+
const color = red ? '#D65A5A' : '#1F2328';
|
|
791
|
+
const symbol = suitSymbol(card.suit);
|
|
792
|
+
return React.createElement(
|
|
793
|
+
View,
|
|
794
|
+
{ style: styles.playingCard },
|
|
795
|
+
React.createElement(Text, { style: [styles.cardCorner, { color }] }, card.rank),
|
|
796
|
+
React.createElement(Text, { style: [styles.cardSuit, { color }] }, symbol),
|
|
797
|
+
React.createElement(Text, { style: [styles.cardCornerBottom, { color }] }, card.rank)
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
357
801
|
function BonkChallengeRunner({ type, tileCount = 8, difficulty = 'intermediate', colors, stylesConfig, onSolved }) {
|
|
358
802
|
if (type === 'tile') {
|
|
359
803
|
return React.createElement(TileChallenge, { colors, stylesConfig, onSolved, tileCount });
|
|
360
804
|
}
|
|
805
|
+
if (type === 'blackjack') {
|
|
806
|
+
return React.createElement(BlackjackChallenge, { colors, stylesConfig, onSolved });
|
|
807
|
+
}
|
|
361
808
|
return React.createElement(MathChallenge, { colors, stylesConfig, onSolved, difficulty });
|
|
362
809
|
}
|
|
363
810
|
|
|
@@ -369,6 +816,11 @@ const styles = StyleSheet.create({
|
|
|
369
816
|
fontSize: 20,
|
|
370
817
|
fontWeight: '700',
|
|
371
818
|
},
|
|
819
|
+
monospacePrompt: {
|
|
820
|
+
fontFamily: 'Courier',
|
|
821
|
+
fontSize: 18,
|
|
822
|
+
lineHeight: 24,
|
|
823
|
+
},
|
|
372
824
|
answer: {
|
|
373
825
|
fontSize: 15,
|
|
374
826
|
fontWeight: '600',
|
|
@@ -387,12 +839,14 @@ const styles = StyleSheet.create({
|
|
|
387
839
|
},
|
|
388
840
|
padGrid: {
|
|
389
841
|
marginTop: 2,
|
|
842
|
+
gap: 8,
|
|
843
|
+
},
|
|
844
|
+
padRow: {
|
|
390
845
|
flexDirection: 'row',
|
|
391
|
-
flexWrap: 'wrap',
|
|
392
846
|
gap: 8,
|
|
393
847
|
},
|
|
394
848
|
key: {
|
|
395
|
-
|
|
849
|
+
flex: 1,
|
|
396
850
|
borderRadius: 10,
|
|
397
851
|
borderWidth: 1,
|
|
398
852
|
alignItems: 'center',
|
|
@@ -403,6 +857,102 @@ const styles = StyleSheet.create({
|
|
|
403
857
|
fontSize: 17,
|
|
404
858
|
fontWeight: '700',
|
|
405
859
|
},
|
|
860
|
+
blackjackDealer: {
|
|
861
|
+
borderWidth: 1,
|
|
862
|
+
borderRadius: 10,
|
|
863
|
+
padding: 12,
|
|
864
|
+
gap: 8,
|
|
865
|
+
},
|
|
866
|
+
blackjackHands: {
|
|
867
|
+
gap: 8,
|
|
868
|
+
},
|
|
869
|
+
blackjackHand: {
|
|
870
|
+
borderWidth: 1,
|
|
871
|
+
borderRadius: 10,
|
|
872
|
+
padding: 12,
|
|
873
|
+
gap: 8,
|
|
874
|
+
},
|
|
875
|
+
blackjackLabel: {
|
|
876
|
+
fontSize: 12,
|
|
877
|
+
fontWeight: '800',
|
|
878
|
+
letterSpacing: 0.6,
|
|
879
|
+
},
|
|
880
|
+
blackjackValue: {
|
|
881
|
+
fontSize: 13,
|
|
882
|
+
fontWeight: '700',
|
|
883
|
+
},
|
|
884
|
+
cardRow: {
|
|
885
|
+
flexDirection: 'row',
|
|
886
|
+
flexWrap: 'wrap',
|
|
887
|
+
gap: 8,
|
|
888
|
+
},
|
|
889
|
+
playingCardAnim: {
|
|
890
|
+
width: 54,
|
|
891
|
+
height: 76,
|
|
892
|
+
},
|
|
893
|
+
playingCard: {
|
|
894
|
+
width: 54,
|
|
895
|
+
height: 76,
|
|
896
|
+
borderRadius: 8,
|
|
897
|
+
backgroundColor: '#F8FAFC',
|
|
898
|
+
borderWidth: 1,
|
|
899
|
+
borderColor: '#D6DEE8',
|
|
900
|
+
padding: 5,
|
|
901
|
+
justifyContent: 'space-between',
|
|
902
|
+
shadowColor: '#000',
|
|
903
|
+
shadowOpacity: 0.18,
|
|
904
|
+
shadowRadius: 4,
|
|
905
|
+
shadowOffset: { width: 0, height: 2 },
|
|
906
|
+
elevation: 2,
|
|
907
|
+
},
|
|
908
|
+
playingCardBack: {
|
|
909
|
+
backgroundColor: '#E9A866',
|
|
910
|
+
borderColor: '#F2C083',
|
|
911
|
+
alignItems: 'center',
|
|
912
|
+
justifyContent: 'center',
|
|
913
|
+
},
|
|
914
|
+
cardBackMark: {
|
|
915
|
+
color: '#1F2328',
|
|
916
|
+
fontSize: 10,
|
|
917
|
+
fontWeight: '900',
|
|
918
|
+
letterSpacing: 0.8,
|
|
919
|
+
},
|
|
920
|
+
cardCorner: {
|
|
921
|
+
fontSize: 14,
|
|
922
|
+
fontWeight: '900',
|
|
923
|
+
lineHeight: 15,
|
|
924
|
+
},
|
|
925
|
+
cardSuit: {
|
|
926
|
+
alignSelf: 'center',
|
|
927
|
+
fontSize: 28,
|
|
928
|
+
fontWeight: '900',
|
|
929
|
+
lineHeight: 30,
|
|
930
|
+
},
|
|
931
|
+
cardCornerBottom: {
|
|
932
|
+
alignSelf: 'flex-end',
|
|
933
|
+
fontSize: 14,
|
|
934
|
+
fontWeight: '900',
|
|
935
|
+
lineHeight: 15,
|
|
936
|
+
transform: [{ rotate: '180deg' }],
|
|
937
|
+
},
|
|
938
|
+
blackjackActions: {
|
|
939
|
+
flexDirection: 'row',
|
|
940
|
+
flexWrap: 'wrap',
|
|
941
|
+
gap: 8,
|
|
942
|
+
},
|
|
943
|
+
blackjackAction: {
|
|
944
|
+
flexGrow: 1,
|
|
945
|
+
flexBasis: '45%',
|
|
946
|
+
minHeight: 42,
|
|
947
|
+
borderRadius: 10,
|
|
948
|
+
borderWidth: 1,
|
|
949
|
+
alignItems: 'center',
|
|
950
|
+
justifyContent: 'center',
|
|
951
|
+
},
|
|
952
|
+
blackjackActionText: {
|
|
953
|
+
fontSize: 15,
|
|
954
|
+
fontWeight: '800',
|
|
955
|
+
},
|
|
406
956
|
tileGrid: {
|
|
407
957
|
marginTop: 6,
|
|
408
958
|
flexDirection: 'row',
|