floating-games-sdk 1.0.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 (77) hide show
  1. package/package.json +44 -0
  2. package/src/FloatingGames.tsx +25 -0
  3. package/src/assets/fonts/Creepster-Regular.ttf +0 -0
  4. package/src/assets/images/android-icon-background.png +0 -0
  5. package/src/assets/images/android-icon-foreground.png +0 -0
  6. package/src/assets/images/android-icon-monochrome.png +0 -0
  7. package/src/assets/images/blocks-icon.png +0 -0
  8. package/src/assets/images/bright-rubber-puzzles-blue.png +0 -0
  9. package/src/assets/images/close.png +0 -0
  10. package/src/assets/images/eriri.png +0 -0
  11. package/src/assets/images/favicon.png +0 -0
  12. package/src/assets/images/first-play.png +0 -0
  13. package/src/assets/images/game-over.gif +0 -0
  14. package/src/assets/images/game-pad.png +0 -0
  15. package/src/assets/images/game-pad2.png +0 -0
  16. package/src/assets/images/game-page-bg1.png +0 -0
  17. package/src/assets/images/hands-down-bg.png +0 -0
  18. package/src/assets/images/icon.png +0 -0
  19. package/src/assets/images/lets-play.gif +0 -0
  20. package/src/assets/images/lucky-day.gif +0 -0
  21. package/src/assets/images/maths.png +0 -0
  22. package/src/assets/images/not-playing.png +0 -0
  23. package/src/assets/images/number-puzzle-select.png +0 -0
  24. package/src/assets/images/number-puzzle-selector.png +0 -0
  25. package/src/assets/images/pause-clock.png +0 -0
  26. package/src/assets/images/pause-icon.png +0 -0
  27. package/src/assets/images/play-icon.png +0 -0
  28. package/src/assets/images/play-now.gif +0 -0
  29. package/src/assets/images/play-play.png +0 -0
  30. package/src/assets/images/play.png +0 -0
  31. package/src/assets/images/quit-game.png +0 -0
  32. package/src/assets/images/quit-icon.png +0 -0
  33. package/src/assets/images/quit.png +0 -0
  34. package/src/assets/images/review-win-selector.png +0 -0
  35. package/src/assets/images/select-bg.png +0 -0
  36. package/src/assets/images/select-logo.png +0 -0
  37. package/src/assets/images/spin-wheel-select.png +0 -0
  38. package/src/assets/images/spin-wheel-selector.png +0 -0
  39. package/src/assets/images/splash-icon.png +0 -0
  40. package/src/assets/images/start-button.png +0 -0
  41. package/src/assets/images/start-game.png +0 -0
  42. package/src/assets/images/stop-quit.png +0 -0
  43. package/src/assets/images/waiting-for-you.gif +0 -0
  44. package/src/assets/sounds/selector-music.mp3 +0 -0
  45. package/src/assets/sounds/timeup-sound.wav +0 -0
  46. package/src/components/atoms/Amount.tsx +48 -0
  47. package/src/components/atoms/FloatingMenu.tsx +119 -0
  48. package/src/components/atoms/ShimmerHR.tsx +64 -0
  49. package/src/components/atoms/UseAnimatedCounter.tsx +31 -0
  50. package/src/components/games/Chain.tsx +26 -0
  51. package/src/components/games/GameItem.tsx +38 -0
  52. package/src/components/games/GameSelectorScreen.tsx +221 -0
  53. package/src/components/games/GameSplash.tsx +44 -0
  54. package/src/components/games/HangingSign.tsx +75 -0
  55. package/src/components/games/SplashScreen.tsx +69 -0
  56. package/src/components/games/number-puzzle/DragTile.tsx +65 -0
  57. package/src/components/games/number-puzzle/GameScreen.tsx +597 -0
  58. package/src/components/games/number-puzzle/PuzzleSplash.tsx +50 -0
  59. package/src/components/games/number-puzzle/TileComponent.tsx +47 -0
  60. package/src/components/games/review-and-win/EmojiTracker.tsx +96 -0
  61. package/src/components/games/review-and-win/GameScreen.tsx +135 -0
  62. package/src/components/games/review-and-win/Tile.tsx +60 -0
  63. package/src/components/games/spin-wheel/SpinAndWin.tsx +507 -0
  64. package/src/components/payment/CheckoutScreen.tsx +50 -0
  65. package/src/components/payment/FundAccount.tsx +281 -0
  66. package/src/components/payment/Payment.tsx +45 -0
  67. package/src/components/reusables/AppSelect.tsx +70 -0
  68. package/src/components/reusables/CountdownTimer.tsx +116 -0
  69. package/src/components/reusables/FloatingButton.tsx +71 -0
  70. package/src/components/reusables/SoundPlayer.ts +28 -0
  71. package/src/components/utils/shuffle.ts +48 -0
  72. package/src/context/GameContext.tsx +15 -0
  73. package/src/index.tsx +5 -0
  74. package/src/modals/BottomSheetModal.tsx +38 -0
  75. package/src/modals/GameOverModal.tsx +76 -0
  76. package/src/modals/PauseModal.tsx +95 -0
  77. package/src/navigation/GameNavigator.tsx +48 -0
@@ -0,0 +1,597 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import {
3
+ Alert,
4
+ Dimensions,
5
+ Image,
6
+ ImageBackground,
7
+ Platform,
8
+ StyleSheet,
9
+ Text,
10
+ TouchableOpacity,
11
+ Vibration,
12
+ View
13
+ } from 'react-native';
14
+ import { SharedValue, useSharedValue, withTiming } from 'react-native-reanimated';
15
+ import Tile from './TileComponent';
16
+ import { Gesture, GestureDetector } from 'react-native-gesture-handler';
17
+ import { NativeStackScreenProps } from '@react-navigation/native-stack';
18
+ import { RootStackParamList } from '@/src/navigation/GameNavigator';
19
+ import CountdownTimer, { CountdownTimerRef } from '../../../components/reusables/CountdownTimer';
20
+ // import {amount} from "@/components/atoms/amount";
21
+ import { useSoundPlayer } from '@/src/components/reusables/SoundPlayer';
22
+ import * as Haptics from 'expo-haptics';
23
+ import Modal from 'react-native-modal';
24
+ import AppSelect, { Option } from '../../../components/reusables/AppSelect';
25
+ import { Amount } from '@/src/components/atoms/Amount';
26
+ import FloatingButton from '@/src/components/reusables/FloatingButton';
27
+ import GameOverModal from '../../../modals/GameOverModal';
28
+ import PauseModal from '../../../modals/PauseModal';
29
+ // import myLuckyDay from '../../../assets/images/lucky-day.gif'
30
+ // import startButton from '../../../assets/images/start-button.png'
31
+
32
+ const GRID = 4;
33
+ const { width } = Dimensions.get('window');
34
+ const BOARD = width - 100;
35
+ const SIZE = BOARD / GRID;
36
+ const TILE_COUNT = GRID * GRID - 1;
37
+
38
+ // ------------------
39
+ // Colors
40
+ // ------------------
41
+ const COLORS = {
42
+ background: '#072e60',
43
+ resetBtn: '#27ae60',
44
+ quitBtn: '#f67373',
45
+ timer: '#00bfff',
46
+ instruction: '#95cca8',
47
+ goal: '#E3F8D980',
48
+ sheet: '#c5daf4',
49
+ sheetBtn: '#4c88d6',
50
+ textWhite: '#fff',
51
+ sheetText: '#0f6e0c',
52
+ };
53
+
54
+ interface TileType {
55
+ value: number;
56
+ row: number;
57
+ col: number;
58
+ x: SharedValue<number>;
59
+ y: SharedValue<number>;
60
+ }
61
+
62
+ type GameScreenProps = NativeStackScreenProps<RootStackParamList, 'Game1'> & {
63
+ onWin?: () => void;
64
+ };
65
+
66
+ function GameScreen({ navigation, onWin }: GameScreenProps) {
67
+ const [tiles, setTiles] = useState<TileType[]>([]);
68
+ const [empty, setEmpty] = useState({ row: GRID - 1, col: GRID - 1 });
69
+ const [hasPlayed, setHasPlayed] = useState(false);
70
+ const [hasResetGameBtn, setHasResetGameBtn] = useState(true);
71
+
72
+ const [modalVisible, setModalVisible] = useState(false);
73
+ const [gameEnded, setGameEnded] = useState(true);
74
+ const [isPp, setIsPp] = useState(false);
75
+
76
+ const [isPlaying, setIsPlaying] = useState(false);
77
+ const [stakedAmt, setStakedAmt] = useState(0);
78
+ const [possibleWinAmt, setPossibleWinAmt] = useState(0);
79
+ const [walletAmt, setWalletAmt] = useState(3500);
80
+ const [remainingTime, setRemainingTime] = useState(0);
81
+
82
+ const [isAmountOpen, setIsAmountOpen] = useState(false);
83
+ const [isTimeOpen, setIsTimeOpen] = useState(false);
84
+
85
+ const [amountValue, setAmountValue] = useState(0);
86
+ const [possibleAmountValue, setPossibleAmountValue] = useState(0);
87
+ const [timeValue, setTimeValue] = useState(0);
88
+
89
+ const images = [
90
+ require('../../../assets/images/lets-play.gif'),
91
+ require('../../../assets/images/start-button.png'),
92
+ require('../../../assets/images/quit-icon.png'),
93
+ require('../../../assets/images/pause-icon.png'),
94
+ require('../../../assets/images/blocks-icon.png'),
95
+ require('../../../assets/images/close.png'),
96
+ ];
97
+
98
+ // images.forEach(img => Image.resolveAssetSource(img));
99
+ if (Platform.OS !== 'web') {
100
+ images.forEach(img => Image.resolveAssetSource(img));
101
+ }
102
+
103
+ const [items, setItems] = useState<Option[]>(
104
+ Array.from({ length: 10 }, (_, i) => {
105
+ const val = ((i + 1) * 100).toString();
106
+ return { label: val, value: val };
107
+ })
108
+ );
109
+
110
+ const [timeList, setTimeList] = useState<Option[]>([
111
+ { label: 'Set 1 Minute', value: 60 },
112
+ { label: 'Set 3 Minutes', value: 180 },
113
+ { label: 'Set 4 Minutes', value: 240 },
114
+ { label: 'Set 5 Minutes', value: 300 },
115
+ ]);
116
+
117
+ const timerRef = useRef<CountdownTimerRef>(null);
118
+
119
+ const shared = useRef(
120
+ Array.from({ length: TILE_COUNT }, () => ({ x: useSharedValue(0), y: useSharedValue(0) }))
121
+ ).current;
122
+
123
+ useEffect(() => {
124
+ initialize();
125
+ setTimeout(() => setHasResetGameBtn(false), 60000);
126
+ }, []);
127
+
128
+ useEffect(() => {
129
+ if (!amountValue || !timeValue) return;
130
+
131
+ let finalAmount = amountValue;
132
+
133
+ if (timeValue === 180) finalAmount *= 6;
134
+ else if (timeValue === 240) finalAmount *= 4;
135
+ else if (timeValue === 300) finalAmount *= 3;
136
+ else if (timeValue === 60) finalAmount *= 10;
137
+
138
+ setPossibleAmountValue(finalAmount);
139
+ }, [timeValue, amountValue]);
140
+
141
+ useEffect(() => {
142
+ if (!modalVisible) return;
143
+
144
+ const timer = setTimeout(() => {
145
+ setModalVisible(false);
146
+ setGameEnded(true);
147
+ }, 6000);
148
+
149
+ return () => clearTimeout(timer);
150
+ }, [modalVisible]);
151
+
152
+ // useEffect(() => {
153
+ // Image.prefetch(Image.resolveAssetSource(require('../../../assets/images/lucky-day.gif')).uri);
154
+ // Image.prefetch(Image.resolveAssetSource(require('../../../assets/images/game-page-bg1.png')).uri);
155
+ // Image.prefetch(Image.resolveAssetSource(require('../../../assets/images/maths.png')).uri);
156
+ // Image.prefetch(Image.resolveAssetSource(require('../../../assets/images/start-button.png')).uri);
157
+ // }, []);
158
+
159
+ const handleSwipe = async (direction: 'up' | 'down' | 'left' | 'right') => {
160
+ let targetRow = empty.row;
161
+ let targetCol = empty.col;
162
+
163
+ switch (direction) {
164
+ case 'up':
165
+ targetRow += 1;
166
+ break;
167
+ case 'down':
168
+ targetRow -= 1;
169
+ break;
170
+ case 'left':
171
+ targetCol += 1;
172
+ break;
173
+ case 'right':
174
+ targetCol -= 1;
175
+ break;
176
+ }
177
+
178
+ if (targetRow >= 0 && targetRow < GRID && targetCol >= 0 && targetCol < GRID) {
179
+ await vibrateSoft()
180
+ // Vibration.vibrate(40);
181
+ move(targetRow, targetCol);
182
+ }
183
+ };
184
+
185
+ const panGesture = Gesture.Pan()
186
+ .onFinalize(e => {
187
+ const { translationX, translationY } = e;
188
+ if (Math.abs(translationX) > Math.abs(translationY)) {
189
+ translationX > 30 ? handleSwipe('right') : translationX < -30 ? handleSwipe('left') : null;
190
+ } else {
191
+ translationY > 30 ? handleSwipe('down') : translationY < -30 ? handleSwipe('up') : null;
192
+ }
193
+ })
194
+ .runOnJS(true);
195
+
196
+ const shuffleArray = (arr: number[]) => {
197
+ const shuffled = [...arr];
198
+ for (let i = shuffled.length - 1; i > 0; i--) {
199
+ const j = Math.floor(Math.random() * (i + 1));
200
+ [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
201
+ }
202
+ return shuffled;
203
+ };
204
+
205
+ const initialize = () => {
206
+ let count = 1;
207
+ const newTiles: TileType[] = [];
208
+ const numbers = shuffleArray(Array.from({ length: TILE_COUNT }, (_, i) => i + 1));
209
+
210
+ for (let r = 0; r < GRID; r++) {
211
+ for (let c = 0; c < GRID; c++) {
212
+ if (r === GRID - 1 && c === GRID - 1) continue;
213
+ const i = count - 1;
214
+ shared[i].x.value = c * SIZE;
215
+ shared[i].y.value = r * SIZE;
216
+ newTiles.push({ value: numbers[i], row: r, col: c, x: shared[i].x, y: shared[i].y });
217
+ count++;
218
+ }
219
+ }
220
+ timerRef.current?.reset();
221
+ setTiles(newTiles);
222
+ setEmpty({ row: GRID - 1, col: GRID - 1 });
223
+ // setResetCount(resetCount + 1);
224
+ setHasResetGameBtn(true);
225
+ };
226
+
227
+ const pause = () => {
228
+ timerRef.current?.pause();
229
+ setIsPp(true)
230
+ }
231
+
232
+ const play = () => {
233
+ timerRef.current?.resume();
234
+ setIsPp(false)
235
+ }
236
+
237
+ const move = (row: number, col: number) => {
238
+ if (!isPlaying) {
239
+ return
240
+ }
241
+ const dx = Math.abs(row - empty.row);
242
+ const dy = Math.abs(col - empty.col);
243
+ if (dx + dy !== 1) return;
244
+
245
+ const index = tiles.findIndex(t => t.row === row && t.col === col);
246
+ if (index === -1) return;
247
+
248
+ const tile = tiles[index];
249
+ tile.x.value = withTiming(empty.col * SIZE);
250
+ tile.y.value = withTiming(empty.row * SIZE);
251
+
252
+ const updated = tiles.map((t, i) => i === index ? { ...t, row: empty.row, col: empty.col } : t);
253
+ setTiles(updated);
254
+ setEmpty({ row, col });
255
+
256
+ checkWin(updated);
257
+ };
258
+
259
+ const checkWin = (tilesToCheck: TileType[]) => {
260
+ const sorted = [...tilesToCheck].sort((a, b) => a.row * GRID + a.col - (b.row * GRID + b.col));
261
+ for (let i = 0; i < sorted.length; i++) if (sorted[i].value !== i + 1) return;
262
+ if (onWin) Alert.alert('Hurray, You won!', 'Your account has been credited...?');
263
+ };
264
+
265
+ const { playSound } = useSoundPlayer(require('../../../assets/sounds/timeup-sound.wav'));
266
+
267
+ const handleTimeUp = async () => {
268
+ playSound();
269
+ setIsPlaying(false);
270
+ setAmountValue(0);
271
+ };
272
+
273
+ const startGame = () => {
274
+
275
+ if (!timeValue || !amountValue) {
276
+ Alert.alert('Information!', 'Select time / amount to stake.');
277
+ return;
278
+ }
279
+
280
+ if (amountValue > walletAmt) {
281
+ Alert.alert('Insufficient Fund!', 'Add more coins to your virtual coin.');
282
+ return;
283
+ }
284
+
285
+ setIsPlaying(true);
286
+ setGameEnded(false);
287
+ setRemainingTime(timeValue);
288
+ setStakedAmt(amountValue);
289
+ setPossibleWinAmt(possibleAmountValue);
290
+ setWalletAmt(walletAmt - amountValue);
291
+ setHasPlayed(true)
292
+ }
293
+
294
+ const endGame = () => {
295
+ setModalVisible(true);
296
+ setIsPlaying(false);
297
+ setRemainingTime(0);
298
+ setStakedAmt(0);
299
+ setPossibleWinAmt(0);
300
+ setAmountValue(0);
301
+ setTimeValue(0);
302
+ setPossibleAmountValue(0);
303
+ };
304
+
305
+ const vibrateSoft = async () => {
306
+ Platform.OS === 'ios'
307
+ ? await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
308
+ : Vibration.vibrate(30);
309
+ };
310
+
311
+ const confirmQuit = () => {
312
+ Alert.alert(
313
+ 'Exit Game',
314
+ 'Are you sure?',
315
+ [
316
+ { text: 'No', style: 'cancel' },
317
+ { text: 'Yes', onPress: () => navigation.replace('GameSelector') },
318
+ ],
319
+ { cancelable: true }
320
+ );
321
+ };
322
+
323
+ const showConfirm = () => {
324
+ Alert.alert(
325
+ 'Confirm Action',
326
+ 'Amount paid, will be lost.\n\nAre you sure you want to quit this game?',
327
+ [
328
+ { text: 'No', style: 'cancel' },
329
+ // {text: 'Yes', onPress: () => navigation.replace('GameSelector')},
330
+ { text: 'Yes', onPress: () => endGame() },
331
+ ],
332
+ { cancelable: true }
333
+ );
334
+ };
335
+
336
+ const cpw = (valOrFn: (arg0: number) => any) => {
337
+ // if (!amountValue) {
338
+ // Alert.alert('Information!', 'Select stake amount first.');
339
+ // return;
340
+ // }
341
+ const val = typeof valOrFn === 'function' ? valOrFn(timeValue) : valOrFn;
342
+ let finalAmount = amountValue
343
+ if (timeValue === 180) finalAmount *= 6;
344
+ else if (timeValue === 240) finalAmount *= 4;
345
+ else if (timeValue === 300) finalAmount *= 3;
346
+ setPossibleAmountValue(finalAmount);
347
+ setTimeValue(val);
348
+ }
349
+
350
+ return (
351
+ <ImageBackground
352
+ source={ require('../../../assets/images/game-page-bg1.png') }
353
+ style={ { flex: 1 } }
354
+ >
355
+
356
+ <View style={ styles.container }>
357
+ <View style={ styles.headerContainer }>
358
+ <Text style={ styles.timerIntro }>Solve The Puzzle{ '\n' }Before Countdown</Text>
359
+ <View style={ styles.timerWrapper }>
360
+ <CountdownTimer
361
+ key={ remainingTime }
362
+ ref={ timerRef }
363
+ initialSeconds={ remainingTime }
364
+ warningThreshold={ 10 }
365
+ onComplete={ endGame }
366
+ color={ COLORS.timer } // ✅ Use string directly
367
+ />
368
+ </View>
369
+
370
+ <View style={ styles.amountContainer }>
371
+ <View style={ styles.amountBlock }>
372
+ <View>
373
+ <Amount value={ stakedAmt } color="#4CAF50" fontSize={ 25 }/>
374
+ </View>
375
+ <Text style={ styles.amountLabel }>Staked Amount 💰💵</Text>
376
+ </View>
377
+ <View style={ styles.amountBlock }>
378
+ <View>
379
+ <Amount value={ possibleWinAmt } color="#4CAF50" fontSize={ 25 }/>
380
+ </View>
381
+ <Text style={ styles.amountLabel }>Possible Winning🔥</Text>
382
+ </View>
383
+ </View>
384
+
385
+ <Text style={ styles.instructionText }>
386
+ Arrange the scrambled numbers on the board in ascending order to complete the puzzle. The empty
387
+ space allows you to move tiles, one at a time, until all numbers are in order.
388
+ </Text>
389
+ <Text style={ styles.goalText }>
390
+ ✅ Goal: Arrange numbers 1 → 2 → 3 … { '\n' }until the puzzle is solved.
391
+ </Text>
392
+ </View>
393
+ <View style={ { alignItems: 'center' } }>
394
+ {
395
+ isPlaying ? <GestureDetector gesture={ panGesture }>
396
+ <View style={ styles.board }>
397
+ { tiles.map(tile => (
398
+ <Tile
399
+ key={ tile.value }
400
+ value={ tile.value }
401
+ size={ SIZE }
402
+ x={ tile.x }
403
+ y={ tile.y }
404
+ onPress={ async () => {
405
+ await vibrateSoft();
406
+ move(tile.row, tile.col);
407
+ } }
408
+ />
409
+ )) }
410
+ </View>
411
+ </GestureDetector> : <Image
412
+ source={ images[4] }
413
+ style={ { width: 300, height: 300, marginBottom: 100 } }
414
+ resizeMode="contain"
415
+ />
416
+ }
417
+ </View>
418
+ {
419
+ !modalVisible && !gameEnded && <View style={ styles.buttonContainer }>
420
+ <FloatingButton preset="left">
421
+ <View style={ { alignItems: 'center' } }>
422
+ <TouchableOpacity style={ styles.button } onPress={ pause }>
423
+ <Image
424
+ source={ images[3] }
425
+ style={ { width: 50, height: 50 } }
426
+ resizeMode="contain"
427
+ />
428
+ </TouchableOpacity>
429
+ <Text style={ styles.pText }>Pause</Text>
430
+ </View>
431
+ </FloatingButton>
432
+
433
+ <FloatingButton preset="right">
434
+ <View style={ { alignItems: 'center' } }>
435
+ <TouchableOpacity style={ styles.button } onPress={ showConfirm }>
436
+ <Image
437
+ source={ images[2] }
438
+ style={ { width: 50, height: 50 } }
439
+ resizeMode="contain"
440
+ />
441
+ </TouchableOpacity>
442
+ <Text style={ styles.pText }>Quit</Text>
443
+ </View>
444
+ </FloatingButton>
445
+
446
+ </View>
447
+ }
448
+
449
+ </View>
450
+
451
+ <Modal
452
+ isVisible={ gameEnded } // gameEnded
453
+ style={ styles.modal }>
454
+ <View style={ styles.sheet }>
455
+ <View style={ { flexDirection: 'row', justifyContent: 'space-between' } }>
456
+ <View style={ { borderLeftWidth: 3, borderColor: '#0f6e0c', marginBottom: 12, paddingStart: 6 } }>
457
+ <Text style={ styles.sheetText }>Total Virtual Coins.</Text>
458
+ <Amount value={ walletAmt } color="#0f6e0c" fontSize={ 25 }/>
459
+ </View>
460
+ <View>
461
+ <Image
462
+ // source={require('../../../assets/images/lucky-day.gif')}
463
+ source={ images[0] }
464
+ style={ { width: 100, height: 100, position: 'absolute', right: 0, top: -10 } }
465
+ resizeMode="contain"
466
+ />
467
+ </View>
468
+ </View>
469
+
470
+ <Text style={ { fontSize: 13, marginBottom: 6 } }>Setting different time, affects the odds</Text>
471
+ <View style={ { alignItems: 'center', flexDirection: 'row', marginBottom: 10 } }>
472
+ <Text style={ { fontSize: 15, fontWeight: '800', color: '#0c53ac' } }>Possible Winning
473
+ Amount: </Text>
474
+ <Amount value={ possibleAmountValue } color={ '#0c53ac' } fontSize={ 17 }/>
475
+ </View>
476
+
477
+ <View style={ { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 20 } }>
478
+
479
+ <View style={ { width: '49%' } }>
480
+ <AppSelect
481
+ open={ isAmountOpen }
482
+ value={ amountValue }
483
+ items={ items }
484
+ setOpen={ setIsAmountOpen }
485
+ setValue={ setAmountValue }
486
+ setItems={ setItems }
487
+ placeholder="Stake Amount"
488
+ fontSize={ 15 }
489
+ />
490
+ </View>
491
+
492
+ <View style={ { width: '49%' } }>
493
+ <AppSelect
494
+ open={ isTimeOpen }
495
+ value={ timeValue }
496
+ items={ timeList }
497
+ setOpen={ setIsTimeOpen }
498
+ // setValue={setTimeValue}
499
+ setValue={ cpw }
500
+ setItems={ setTimeList }
501
+ placeholder="Select Time"
502
+ fontSize={ 15 }
503
+ />
504
+ </View>
505
+
506
+ </View>
507
+ <View
508
+ style={ { flexDirection: 'row', justifyContent: 'space-evenly' } }>
509
+
510
+ <TouchableOpacity onPress={ confirmQuit }>
511
+ <Image
512
+ source={ images[5] }
513
+ style={ { width: 50, height: 50 } }
514
+ resizeMode="contain"
515
+ />
516
+ </TouchableOpacity>
517
+
518
+ <FloatingButton preset="left">
519
+ <View style={ { alignItems: 'center', marginTop: 14 } }>
520
+ <TouchableOpacity onPress={ startGame }>
521
+ <Image
522
+ source={ images[1] }
523
+ style={ { width: 100, height: 40 } }
524
+ resizeMode={ 'cover' }
525
+ />
526
+ </TouchableOpacity>
527
+ </View>
528
+ </FloatingButton>
529
+
530
+ </View>
531
+ </View>
532
+ </Modal>
533
+
534
+ <GameOverModal
535
+ visible={ modalVisible && hasPlayed } //modalVisible
536
+ onClose={ () => setModalVisible(false) }
537
+ title={ 'Game Over' }
538
+ />
539
+
540
+ <PauseModal visible={ isPp } onClose={ play }/>
541
+
542
+ </ImageBackground>
543
+ );
544
+ }
545
+
546
+ const styles = StyleSheet.create({
547
+ container: { flex: 1, backgroundColor: 'rgba(7,46,96,0.92)', justifyContent: 'center' },
548
+ headerContainer: { marginHorizontal: 15 },
549
+ timerIntro: { color: COLORS.instruction, alignSelf: 'center', marginBottom: 16, textAlign: 'center' },
550
+ timerWrapper: { height: 30, alignSelf: 'center', alignItems: 'center', marginBottom: 16 },
551
+ amountContainer: {
552
+ flexDirection: 'row',
553
+ justifyContent: 'space-evenly',
554
+ marginBottom: 20,
555
+ backgroundColor: '#E496914F',
556
+ padding: 12,
557
+ borderRadius: 6
558
+ },
559
+ amountBlock: { flexDirection: 'column', textAlign: 'center', alignItems: 'center', alignSelf: 'center' },
560
+ amountLabel: { alignSelf: 'center', color: COLORS.goal, alignItems: 'center' },
561
+ instructionText: { textAlign: 'center', marginBottom: 15, color: COLORS.instruction },
562
+ goalText: { textAlign: 'center', fontWeight: 'bold', color: COLORS.goal },
563
+ board: { width: BOARD, height: BOARD, backgroundColor: COLORS.background, borderRadius: 16, marginVertical: 30 },
564
+ buttonContainer: { flexDirection: 'row', justifyContent: 'space-evenly', marginTop: 50 },
565
+ button: { borderRadius: 50, backgroundColor: '#eee' },
566
+ resetBtn: { backgroundColor: COLORS.resetBtn, marginEnd: 20 },
567
+ quitBtn: { backgroundColor: COLORS.quitBtn },
568
+ buttonText: { color: '#0c53ae', fontSize: 16, fontWeight: '600', marginTop: 2 },
569
+ pText: { color: '#effae7', fontSize: 16, fontWeight: '600', marginTop: 4 },
570
+ modal: { justifyContent: 'flex-end', margin: 0 },
571
+ sheet: {
572
+ backgroundColor: COLORS.sheet,
573
+ height: '30%',
574
+ padding: 10, /*justifyContent: 'center', alignItems: 'center'*/
575
+ },
576
+ sheetText: { fontSize: 15, color: COLORS.sheetText, fontWeight: 'bold' },
577
+ sheetBtn: {
578
+ padding: 10,
579
+ backgroundColor: COLORS.sheetBtn,
580
+ // marginVertical: 5,
581
+ width: 150,
582
+ alignItems: 'center',
583
+ borderRadius: 25
584
+ },
585
+ startGame: {
586
+ padding: 10,
587
+ backgroundColor: '#0a3e81',
588
+ marginVertical: 5,
589
+ width: 150,
590
+ alignItems: 'center',
591
+ borderRadius: 25
592
+ },
593
+ sheetBtnText: { color: COLORS.textWhite, fontWeight: '500' },
594
+
595
+ });
596
+
597
+ export default React.memo(GameScreen);
@@ -0,0 +1,50 @@
1
+ import React, {useEffect, useRef} from 'react';
2
+ import {Animated, StyleSheet, Text, View} from 'react-native';
3
+ import {NativeStackScreenProps} from '@react-navigation/native-stack';
4
+ import { RootStackParamList } from '@/src/navigation/GameNavigator';
5
+
6
+ type Props = NativeStackScreenProps<RootStackParamList, 'Splash'>;
7
+
8
+ export default function SplashScreen({navigation}: Props) {
9
+ const opacity = useRef(new Animated.Value(0)).current;
10
+
11
+ useEffect(() => {
12
+ Animated.sequence([
13
+ Animated.timing(opacity, {
14
+ toValue: 1,
15
+ duration: 2000, // fade in 2 sec
16
+ useNativeDriver: true,
17
+ }),
18
+ Animated.delay(1000), // stay visible 1 sec
19
+ Animated.timing(opacity, {
20
+ toValue: 0,
21
+ duration: 2000, // fade out 2 sec
22
+ useNativeDriver: true,
23
+ }),
24
+ ]).start(() => {
25
+ navigation.replace('GameSelector'); // go to selector after splash
26
+ });
27
+ }, []);
28
+
29
+ return (
30
+ <View style={styles.container}>
31
+ <Animated.View style={{opacity}}>
32
+ <Text style={styles.logo}>🎮 My Game</Text>
33
+ </Animated.View>
34
+ </View>
35
+ );
36
+ }
37
+
38
+ const styles = StyleSheet.create({
39
+ container: {
40
+ flex: 1,
41
+ backgroundColor: '#000',
42
+ justifyContent: 'center',
43
+ alignItems: 'center',
44
+ },
45
+ logo: {
46
+ fontSize: 36,
47
+ fontWeight: 'bold',
48
+ color: '#fff',
49
+ },
50
+ });
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { Text, TouchableOpacity, StyleSheet } from 'react-native';
3
+ import Animated, { useAnimatedStyle, withTiming, SharedValue } from 'react-native-reanimated';
4
+
5
+ interface TileProps {
6
+ value: number;
7
+ size: number;
8
+ x: SharedValue<number>;
9
+ y: SharedValue<number>;
10
+ onPress: () => void;
11
+ }
12
+
13
+ export default function Tile({ value, size, x, y, onPress }: TileProps) {
14
+ const style = useAnimatedStyle(() => ({
15
+ transform: [{ translateX: x.value }, { translateY: y.value }],
16
+ }));
17
+
18
+ return (
19
+ <Animated.View style={[styles.tile, { width: size, height: size }, style]}>
20
+ <TouchableOpacity style={styles.touch} onPress={onPress} activeOpacity={0.8}>
21
+ <Text style={styles.text}>{value}</Text>
22
+ </TouchableOpacity>
23
+ </Animated.View>
24
+ );
25
+ }
26
+
27
+ const styles = StyleSheet.create({
28
+ tile: {
29
+ position: 'absolute',
30
+ backgroundColor: '#3498db',
31
+ borderRadius: 12,
32
+ overflow: 'hidden',
33
+ borderWidth: 4,
34
+ borderColor: '#072e60'
35
+ },
36
+ touch: {
37
+ flex: 1,
38
+ justifyContent: 'center',
39
+ alignItems: 'center',
40
+ margin: 6
41
+ },
42
+ text: {
43
+ color: '#fff',
44
+ fontSize: 22,
45
+ fontWeight: 'bold',
46
+ },
47
+ });