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.
- package/package.json +44 -0
- package/src/FloatingGames.tsx +25 -0
- package/src/assets/fonts/Creepster-Regular.ttf +0 -0
- package/src/assets/images/android-icon-background.png +0 -0
- package/src/assets/images/android-icon-foreground.png +0 -0
- package/src/assets/images/android-icon-monochrome.png +0 -0
- package/src/assets/images/blocks-icon.png +0 -0
- package/src/assets/images/bright-rubber-puzzles-blue.png +0 -0
- package/src/assets/images/close.png +0 -0
- package/src/assets/images/eriri.png +0 -0
- package/src/assets/images/favicon.png +0 -0
- package/src/assets/images/first-play.png +0 -0
- package/src/assets/images/game-over.gif +0 -0
- package/src/assets/images/game-pad.png +0 -0
- package/src/assets/images/game-pad2.png +0 -0
- package/src/assets/images/game-page-bg1.png +0 -0
- package/src/assets/images/hands-down-bg.png +0 -0
- package/src/assets/images/icon.png +0 -0
- package/src/assets/images/lets-play.gif +0 -0
- package/src/assets/images/lucky-day.gif +0 -0
- package/src/assets/images/maths.png +0 -0
- package/src/assets/images/not-playing.png +0 -0
- package/src/assets/images/number-puzzle-select.png +0 -0
- package/src/assets/images/number-puzzle-selector.png +0 -0
- package/src/assets/images/pause-clock.png +0 -0
- package/src/assets/images/pause-icon.png +0 -0
- package/src/assets/images/play-icon.png +0 -0
- package/src/assets/images/play-now.gif +0 -0
- package/src/assets/images/play-play.png +0 -0
- package/src/assets/images/play.png +0 -0
- package/src/assets/images/quit-game.png +0 -0
- package/src/assets/images/quit-icon.png +0 -0
- package/src/assets/images/quit.png +0 -0
- package/src/assets/images/review-win-selector.png +0 -0
- package/src/assets/images/select-bg.png +0 -0
- package/src/assets/images/select-logo.png +0 -0
- package/src/assets/images/spin-wheel-select.png +0 -0
- package/src/assets/images/spin-wheel-selector.png +0 -0
- package/src/assets/images/splash-icon.png +0 -0
- package/src/assets/images/start-button.png +0 -0
- package/src/assets/images/start-game.png +0 -0
- package/src/assets/images/stop-quit.png +0 -0
- package/src/assets/images/waiting-for-you.gif +0 -0
- package/src/assets/sounds/selector-music.mp3 +0 -0
- package/src/assets/sounds/timeup-sound.wav +0 -0
- package/src/components/atoms/Amount.tsx +48 -0
- package/src/components/atoms/FloatingMenu.tsx +119 -0
- package/src/components/atoms/ShimmerHR.tsx +64 -0
- package/src/components/atoms/UseAnimatedCounter.tsx +31 -0
- package/src/components/games/Chain.tsx +26 -0
- package/src/components/games/GameItem.tsx +38 -0
- package/src/components/games/GameSelectorScreen.tsx +221 -0
- package/src/components/games/GameSplash.tsx +44 -0
- package/src/components/games/HangingSign.tsx +75 -0
- package/src/components/games/SplashScreen.tsx +69 -0
- package/src/components/games/number-puzzle/DragTile.tsx +65 -0
- package/src/components/games/number-puzzle/GameScreen.tsx +597 -0
- package/src/components/games/number-puzzle/PuzzleSplash.tsx +50 -0
- package/src/components/games/number-puzzle/TileComponent.tsx +47 -0
- package/src/components/games/review-and-win/EmojiTracker.tsx +96 -0
- package/src/components/games/review-and-win/GameScreen.tsx +135 -0
- package/src/components/games/review-and-win/Tile.tsx +60 -0
- package/src/components/games/spin-wheel/SpinAndWin.tsx +507 -0
- package/src/components/payment/CheckoutScreen.tsx +50 -0
- package/src/components/payment/FundAccount.tsx +281 -0
- package/src/components/payment/Payment.tsx +45 -0
- package/src/components/reusables/AppSelect.tsx +70 -0
- package/src/components/reusables/CountdownTimer.tsx +116 -0
- package/src/components/reusables/FloatingButton.tsx +71 -0
- package/src/components/reusables/SoundPlayer.ts +28 -0
- package/src/components/utils/shuffle.ts +48 -0
- package/src/context/GameContext.tsx +15 -0
- package/src/index.tsx +5 -0
- package/src/modals/BottomSheetModal.tsx +38 -0
- package/src/modals/GameOverModal.tsx +76 -0
- package/src/modals/PauseModal.tsx +95 -0
- package/src/navigation/GameNavigator.tsx +48 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Image, ImageBackground, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
3
|
+
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
4
|
+
import { RootStackParamList } from '@/src/navigation/GameNavigator';
|
|
5
|
+
import FloatingButton from '../../components/reusables/FloatingButton';
|
|
6
|
+
import { LinearGradient } from 'expo-linear-gradient';
|
|
7
|
+
import { useSoundPlayer } from '../reusables/SoundPlayer';
|
|
8
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
9
|
+
import FloatingMenu from '../../components/atoms/FloatingMenu';
|
|
10
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
11
|
+
import ShimmerHR from '../../components/atoms/ShimmerHR';
|
|
12
|
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
13
|
+
|
|
14
|
+
type Props = NativeStackScreenProps<RootStackParamList, 'GameSelector'>;
|
|
15
|
+
|
|
16
|
+
type Game = {
|
|
17
|
+
icon: string;
|
|
18
|
+
title: string;
|
|
19
|
+
backgroundColor: string;
|
|
20
|
+
navigateTo: string | any;
|
|
21
|
+
howTo: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const GAMES: Game[] = [
|
|
25
|
+
{
|
|
26
|
+
icon: '🧩',
|
|
27
|
+
title: 'Number Puzzle',
|
|
28
|
+
backgroundColor: '#022759',
|
|
29
|
+
navigateTo: 'Game1',
|
|
30
|
+
howTo: 'Slide the numbered tiles until arranged 1–15 using the empty space. Complete within the time limit to win a cash prize.',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
icon: '🎡',
|
|
34
|
+
title: 'Spin Wheel',
|
|
35
|
+
backgroundColor: '#02594a',
|
|
36
|
+
navigateTo: 'Game2',
|
|
37
|
+
howTo: 'Spin the wheel and watch it land on a random segment to reveal your prize instantly.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
icon: '⭐',
|
|
41
|
+
title: 'Review And Win',
|
|
42
|
+
backgroundColor: '#5a0217',
|
|
43
|
+
navigateTo: 'Game3',
|
|
44
|
+
howTo: 'Tap to reveal hidden tiles and match three identical items to win the prize shown.',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const GAME_IMAGES = [
|
|
49
|
+
require('../../assets/images/bright-rubber-puzzles-blue.png'),
|
|
50
|
+
require('../../assets/images/spin-wheel-selector.png'),
|
|
51
|
+
require('../../assets/images/review-win-selector.png'),
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const PLAY_NOW_GIF = require('../../assets/images/play-now.gif');
|
|
55
|
+
const BG_IMAGE = require('../../assets/images/select-bg.png');
|
|
56
|
+
const SELECTOR_MUSIC = require('../../assets/sounds/selector-music.mp3');
|
|
57
|
+
|
|
58
|
+
export default function GameSelectorScreen({ navigation }: Props) {
|
|
59
|
+
const insets = useSafeAreaInsets();
|
|
60
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
61
|
+
const { playSound, stopSound } = useSoundPlayer(SELECTOR_MUSIC, 0.9);
|
|
62
|
+
|
|
63
|
+
const handleMusic = async () => {
|
|
64
|
+
const value = await AsyncStorage.getItem('hasMusic');
|
|
65
|
+
const enabled = value === 'true';
|
|
66
|
+
setIsPlaying(enabled);
|
|
67
|
+
enabled ? playSound() : stopSound();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const musicCtrl = async () => {
|
|
71
|
+
try {
|
|
72
|
+
const value = await AsyncStorage.getItem('hasMusic');
|
|
73
|
+
const next = value !== 'true';
|
|
74
|
+
await AsyncStorage.setItem('hasMusic', String(next));
|
|
75
|
+
setIsPlaying(next);
|
|
76
|
+
next ? playSound() : stopSound();
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.log('musicCtrl error:', err);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
handleMusic();
|
|
84
|
+
return () => stopSound();
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
const onSelection = (navigateTo: string) => {
|
|
88
|
+
const game = GAMES.find(g => g.navigateTo === navigateTo);
|
|
89
|
+
if (game) {
|
|
90
|
+
navigation.navigate('GameSplash', {
|
|
91
|
+
icon: game.icon,
|
|
92
|
+
title: game.title,
|
|
93
|
+
backgroundColor: game.backgroundColor,
|
|
94
|
+
navigateTo: game.navigateTo,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<ImageBackground source={BG_IMAGE} style={styles.bg} resizeMode="contain">
|
|
101
|
+
<LinearGradient colors={['rgba(198,198,198,0.5)', '#4c68af', '#597e79']} style={styles.container}>
|
|
102
|
+
|
|
103
|
+
<SafeAreaView style={{ paddingTop: insets.top }}>
|
|
104
|
+
<Text style={styles.brand}>Floating Games</Text>
|
|
105
|
+
<Text style={styles.ageNotice}>
|
|
106
|
+
This game contains content intended for players aged 18 and above.{'\n'}
|
|
107
|
+
By tapping 'Play Button', you confirm that you meet the age requirement.
|
|
108
|
+
</Text>
|
|
109
|
+
<Text style={styles.title}>Select Game</Text>
|
|
110
|
+
|
|
111
|
+
<ShimmerHR />
|
|
112
|
+
|
|
113
|
+
<ScrollView
|
|
114
|
+
style={styles.scroll}
|
|
115
|
+
contentContainerStyle={styles.scrollContent}
|
|
116
|
+
showsVerticalScrollIndicator={false}
|
|
117
|
+
>
|
|
118
|
+
{GAMES.map((game, index) => (
|
|
119
|
+
<View key={game.navigateTo} style={styles.cardWrapper}>
|
|
120
|
+
<FloatingButton preset="normal">
|
|
121
|
+
<Image
|
|
122
|
+
source={GAME_IMAGES[index]}
|
|
123
|
+
style={[styles.gameImage, index === 1 && styles.gameImageLarge]}
|
|
124
|
+
/>
|
|
125
|
+
<TouchableOpacity onPress={() => onSelection(game.navigateTo)}>
|
|
126
|
+
<Text style={styles.gameTitle}>{game.title} {game.icon}</Text>
|
|
127
|
+
<Image source={PLAY_NOW_GIF} style={styles.playNow} />
|
|
128
|
+
</TouchableOpacity>
|
|
129
|
+
<Text style={styles.howTo}>{game.howTo}</Text>
|
|
130
|
+
</FloatingButton>
|
|
131
|
+
</View>
|
|
132
|
+
))}
|
|
133
|
+
</ScrollView>
|
|
134
|
+
|
|
135
|
+
<FloatingMenu
|
|
136
|
+
isPlaying={isPlaying}
|
|
137
|
+
soundCtrl={musicCtrl}
|
|
138
|
+
paymentCtrl={() => {
|
|
139
|
+
navigation.navigate('Payment');
|
|
140
|
+
stopSound();
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
|
|
144
|
+
</SafeAreaView>
|
|
145
|
+
|
|
146
|
+
</LinearGradient>
|
|
147
|
+
</ImageBackground>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const styles = StyleSheet.create({
|
|
152
|
+
bg: {
|
|
153
|
+
flex: 1,
|
|
154
|
+
backgroundColor: 'rgba(247,247,248,0.76)',
|
|
155
|
+
},
|
|
156
|
+
container: {
|
|
157
|
+
flex: 1,
|
|
158
|
+
alignItems: 'center',
|
|
159
|
+
},
|
|
160
|
+
brand: {
|
|
161
|
+
alignSelf: 'center',
|
|
162
|
+
fontWeight: '600',
|
|
163
|
+
},
|
|
164
|
+
ageNotice: {
|
|
165
|
+
textAlign: 'center',
|
|
166
|
+
marginBottom: 10,
|
|
167
|
+
marginHorizontal: 40,
|
|
168
|
+
color: '#8e5656',
|
|
169
|
+
fontSize: 10,
|
|
170
|
+
},
|
|
171
|
+
title: {
|
|
172
|
+
fontSize: 13,
|
|
173
|
+
fontWeight: '500',
|
|
174
|
+
color: '#4788dc',
|
|
175
|
+
textAlign: 'center',
|
|
176
|
+
marginBottom: 10,
|
|
177
|
+
alignSelf: 'center',
|
|
178
|
+
},
|
|
179
|
+
scroll: {
|
|
180
|
+
width: '100%',
|
|
181
|
+
},
|
|
182
|
+
scrollContent: {
|
|
183
|
+
paddingVertical: 20,
|
|
184
|
+
alignItems: 'center',
|
|
185
|
+
marginBottom: 30,
|
|
186
|
+
},
|
|
187
|
+
cardWrapper: {
|
|
188
|
+
marginVertical: 20,
|
|
189
|
+
},
|
|
190
|
+
gameImage: {
|
|
191
|
+
height: 80,
|
|
192
|
+
width: 150,
|
|
193
|
+
alignSelf: 'center',
|
|
194
|
+
},
|
|
195
|
+
gameImageLarge: {
|
|
196
|
+
height: 100,
|
|
197
|
+
width: 100,
|
|
198
|
+
},
|
|
199
|
+
gameTitle: {
|
|
200
|
+
fontFamily: 'Creepster_400Regular',
|
|
201
|
+
letterSpacing: 2,
|
|
202
|
+
fontSize: 15,
|
|
203
|
+
fontWeight: '800',
|
|
204
|
+
color: '#5a0217',
|
|
205
|
+
alignSelf: 'center',
|
|
206
|
+
marginTop: 5,
|
|
207
|
+
},
|
|
208
|
+
playNow: {
|
|
209
|
+
height: 15,
|
|
210
|
+
width: 150,
|
|
211
|
+
alignSelf: 'center',
|
|
212
|
+
borderRadius: 10,
|
|
213
|
+
marginVertical: 10,
|
|
214
|
+
},
|
|
215
|
+
howTo: {
|
|
216
|
+
fontSize: 10,
|
|
217
|
+
textAlign: 'center',
|
|
218
|
+
marginHorizontal: 35,
|
|
219
|
+
color: '#2b2727',
|
|
220
|
+
},
|
|
221
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react';
|
|
2
|
+
import { Animated, Image, StyleSheet, Text, View } from 'react-native';
|
|
3
|
+
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
4
|
+
import { RootStackParamList } from '@/src/navigation/GameNavigator';
|
|
5
|
+
|
|
6
|
+
type GameSplashProps = NativeStackScreenProps<RootStackParamList, 'GameSplash'>;
|
|
7
|
+
|
|
8
|
+
export default function GameSplash({ navigation, route }: GameSplashProps) {
|
|
9
|
+
const { icon = '🎮', title = 'Game', backgroundColor = '#000', backgroundImage, navigateTo } = route.params;
|
|
10
|
+
const opacity = useRef(new Animated.Value(0)).current;
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!navigateTo) {
|
|
14
|
+
console.warn('GameSplash: navigateTo is undefined!');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Animated.sequence([
|
|
19
|
+
Animated.timing(opacity, { toValue: 1, duration: 1000, useNativeDriver: true }),
|
|
20
|
+
Animated.delay(1000),
|
|
21
|
+
Animated.timing(opacity, { toValue: 0, duration: 1000, useNativeDriver: true }),
|
|
22
|
+
]).start(() => {
|
|
23
|
+
navigation.replace(navigateTo); // Navigate to the target screen
|
|
24
|
+
});
|
|
25
|
+
}, [navigateTo]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View style={ [styles.container, { backgroundColor }] }>
|
|
29
|
+
{ backgroundImage && (
|
|
30
|
+
<Image source={ backgroundImage } style={ StyleSheet.absoluteFillObject } resizeMode="cover"/>
|
|
31
|
+
) }
|
|
32
|
+
<Animated.View style={ { opacity, alignItems: 'center' } }>
|
|
33
|
+
<Text style={ styles.icon }>{ icon }</Text>
|
|
34
|
+
<Text style={ styles.title }>{ title }</Text>
|
|
35
|
+
</Animated.View>
|
|
36
|
+
</View>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const styles = StyleSheet.create({
|
|
41
|
+
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
|
|
42
|
+
icon: { fontSize: 50, marginBottom: 20 },
|
|
43
|
+
title: { fontSize: 25, fontWeight: 'bold', color: '#fff' },
|
|
44
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React, {useEffect} from 'react';
|
|
2
|
+
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
|
3
|
+
import Animated, {Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming} from 'react-native-reanimated';
|
|
4
|
+
import Chain from './Chain';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
title: string;
|
|
8
|
+
onPress: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function HangingSign({title, onPress}: Props) {
|
|
12
|
+
|
|
13
|
+
const rotate = useSharedValue(0);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
rotate.value = withRepeat(
|
|
17
|
+
withTiming(2, {
|
|
18
|
+
duration: 2000,
|
|
19
|
+
easing: Easing.inOut(Easing.ease)
|
|
20
|
+
}),
|
|
21
|
+
-1,
|
|
22
|
+
true
|
|
23
|
+
);
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
const animatedBoard = useAnimatedStyle(() => ({
|
|
27
|
+
transform: [
|
|
28
|
+
{rotate: `${rotate.value}deg`}
|
|
29
|
+
]
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Pressable style={styles.container} onPress={onPress}>
|
|
34
|
+
|
|
35
|
+
<View style={styles.chainRow}>
|
|
36
|
+
<Chain/>
|
|
37
|
+
<Chain/>
|
|
38
|
+
</View>
|
|
39
|
+
|
|
40
|
+
<Animated.View style={[styles.board, animatedBoard]}>
|
|
41
|
+
<Text style={styles.text}>{title}</Text>
|
|
42
|
+
</Animated.View>
|
|
43
|
+
|
|
44
|
+
</Pressable>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const styles = StyleSheet.create({
|
|
49
|
+
container: {
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
// marginTop: 20,
|
|
52
|
+
},
|
|
53
|
+
chainRow: {
|
|
54
|
+
flexDirection: 'row',
|
|
55
|
+
justifyContent: 'space-between',
|
|
56
|
+
width: 160,
|
|
57
|
+
zIndex: 0
|
|
58
|
+
},
|
|
59
|
+
board: {
|
|
60
|
+
width: 210,
|
|
61
|
+
height: 30,
|
|
62
|
+
backgroundColor: '#8B5A2B',
|
|
63
|
+
borderRadius: 12,
|
|
64
|
+
justifyContent: 'center',
|
|
65
|
+
alignItems: 'center',
|
|
66
|
+
elevation: 6,
|
|
67
|
+
zIndex: 10
|
|
68
|
+
},
|
|
69
|
+
text: {
|
|
70
|
+
color: 'white',
|
|
71
|
+
fontSize: 17,
|
|
72
|
+
// fontWeight: "bold"
|
|
73
|
+
fontFamily: 'Creepster_400Regular'
|
|
74
|
+
}
|
|
75
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
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 10 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
|
+
navigation.replace('Payment'); // go to selector after splash
|
|
27
|
+
});
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<View style={ styles.container }>
|
|
32
|
+
<Animated.View style={ { opacity } }>
|
|
33
|
+
<View style={ styles.divContainer }>
|
|
34
|
+
<Text style={ styles.logo }>🎮 Floating Games</Text>
|
|
35
|
+
<Text style={ styles.disclaimer }>
|
|
36
|
+
<Text style={ { fontWeight:'bold',fontSize:13 } }>Disclaimer:</Text> { '\n' }This game is intended strictly for users aged 18 years and older.
|
|
37
|
+
By accessing or playing, you confirm that you are of legal age in your jurisdiction.
|
|
38
|
+
</Text>
|
|
39
|
+
</View>
|
|
40
|
+
</Animated.View>
|
|
41
|
+
</View>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const styles = StyleSheet.create({
|
|
46
|
+
container: {
|
|
47
|
+
flex: 1,
|
|
48
|
+
backgroundColor: '#000',
|
|
49
|
+
justifyContent: 'center',
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
},
|
|
52
|
+
divContainer: {
|
|
53
|
+
flex: 1, // take full screen
|
|
54
|
+
justifyContent: 'center', // vertical center
|
|
55
|
+
alignItems: 'center', // horizontal center
|
|
56
|
+
},
|
|
57
|
+
logo: {
|
|
58
|
+
fontSize: 25,
|
|
59
|
+
fontWeight: 'bold',
|
|
60
|
+
color: '#fff',
|
|
61
|
+
marginBottom: 20
|
|
62
|
+
},
|
|
63
|
+
disclaimer: {
|
|
64
|
+
fontSize: 11,
|
|
65
|
+
color: '#aaa',
|
|
66
|
+
marginHorizontal: 25,
|
|
67
|
+
textAlign: 'center'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {Text} from 'react-native';
|
|
3
|
+
import Animated, {useAnimatedStyle, SharedValue} from 'react-native-reanimated';
|
|
4
|
+
import {Gesture, GestureDetector, Directions} from 'react-native-gesture-handler';
|
|
5
|
+
|
|
6
|
+
interface Tile {
|
|
7
|
+
value: number;
|
|
8
|
+
row: number;
|
|
9
|
+
col: number;
|
|
10
|
+
x: SharedValue<number>;
|
|
11
|
+
y: SharedValue<number>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
tile: Tile;
|
|
16
|
+
size: number;
|
|
17
|
+
emptyTile: { row: number; col: number };
|
|
18
|
+
onMove: (row: number, col: number) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function DragTile({
|
|
22
|
+
tile,
|
|
23
|
+
size,
|
|
24
|
+
emptyTile,
|
|
25
|
+
onMove,
|
|
26
|
+
}: Props) {
|
|
27
|
+
|
|
28
|
+
const swipe = Gesture.Fling()
|
|
29
|
+
.direction(
|
|
30
|
+
Directions.UP |
|
|
31
|
+
Directions.DOWN |
|
|
32
|
+
Directions.LEFT |
|
|
33
|
+
Directions.RIGHT
|
|
34
|
+
)
|
|
35
|
+
.onEnd(() => {
|
|
36
|
+
const dx = Math.abs(tile.row - emptyTile.row);
|
|
37
|
+
const dy = Math.abs(tile.col - emptyTile.col);
|
|
38
|
+
if (dx + dy === 1) {
|
|
39
|
+
onMove(tile.row, tile.col);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const style = useAnimatedStyle(() => ({
|
|
44
|
+
position: 'absolute',
|
|
45
|
+
width: size,
|
|
46
|
+
height: size,
|
|
47
|
+
top: tile.y.value,
|
|
48
|
+
left: tile.x.value,
|
|
49
|
+
justifyContent: 'center',
|
|
50
|
+
alignItems: 'center',
|
|
51
|
+
backgroundColor: '#3498db',
|
|
52
|
+
borderRadius: 12,
|
|
53
|
+
margin: 20
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<GestureDetector gesture={swipe}>
|
|
58
|
+
<Animated.View style={style}>
|
|
59
|
+
<Text style={{color: '#fff', fontSize: 22, fontWeight: 'bold'}}>
|
|
60
|
+
{tile.value}
|
|
61
|
+
</Text>
|
|
62
|
+
</Animated.View>
|
|
63
|
+
</GestureDetector>
|
|
64
|
+
);
|
|
65
|
+
}
|