Practice and reinforce the concepts from Lesson 9
Create engaging math learning experiences by:
Time Limit: 5 minutes
# Create math adventure app
npx create-expo-app MathAdventure --template blank
cd MathAdventure
# Install dependencies
npx expo install expo-haptics
npx expo install @react-native-async-storage/async-storage
npx expo install react-native-svg
npx expo install expo-av
npx expo install react-native-reanimated
Time Limit: 5 minutes
Set up math problems and game data:
// Math topics and difficulty levels
const MATH_TOPICS = {
ADDITION: {
name: 'Addition',
emoji: 'โ',
color: '#3498db',
levels: [
{ range: [1, 10], name: 'Beginner' },
{ range: [10, 50], name: 'Intermediate' },
{ range: [50, 100], name: 'Advanced' },
],
},
SUBTRACTION: {
name: 'Subtraction',
emoji: 'โ',
color: '#e74c3c',
levels: [
{ range: [1, 10], name: 'Beginner' },
{ range: [10, 50], name: 'Intermediate' },
{ range: [50, 100], name: 'Advanced' },
],
},
MULTIPLICATION: {
name: 'Multiplication',
emoji: 'โ๏ธ',
color: '#27ae60',
levels: [
{ range: [1, 5], name: 'Times Tables' },
{ range: [6, 10], name: 'Intermediate' },
{ range: [11, 20], name: 'Advanced' },
],
},
DIVISION: {
name: 'Division',
emoji: 'โ',
color: '#f39c12',
levels: [
{ range: [1, 50], name: 'Basic' },
{ range: [50, 200], name: 'Intermediate' },
{ range: [200, 1000], name: 'Advanced' },
],
},
};
// Game modes
const GAME_MODES = {
QUICK_FIRE: {
name: 'Quick Fire',
description: 'Answer as many as you can in 60 seconds',
timeLimit: 60,
emoji: 'โก',
},
ADVENTURE: {
name: 'Math Adventure',
description: 'Journey through math challenges',
timeLimit: null,
emoji: '๐ญ',
},
VISUAL_SOLVER: {
name: 'Visual Solver',
description: 'See math problems come to life',
timeLimit: null,
emoji: '๐๏ธ',
},
};
โ Checkpoint: Math game structure and data ready!
Replace App.js
with the math game foundation:
import React, { useState, useEffect, useRef } from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Dimensions,
Alert,
TextInput,
Animated,
} from 'react-native';
import * as Haptics from 'expo-haptics';
import AsyncStorage from '@react-native-async-storage/async-storage';
const { width, height } = Dimensions.get('window');
// Math problem generator
const generateProblem = (topic, level) => {
const { range } = MATH_TOPICS[topic].levels[level];
let num1, num2, answer, operation, display;
switch (topic) {
case 'ADDITION':
num1 = Math.floor(Math.random() * range[1]) + range[0];
num2 = Math.floor(Math.random() * range[1]) + range[0];
answer = num1 + num2;
operation = '+';
display = `${num1} + ${num2}`;
break;
case 'SUBTRACTION':
num1 = Math.floor(Math.random() * range[1]) + range[0];
num2 = Math.floor(Math.random() * num1) + 1; // Ensure positive result
answer = num1 - num2;
operation = '-';
display = `${num1} - ${num2}`;
break;
case 'MULTIPLICATION':
num1 = Math.floor(Math.random() * range[1]) + range[0];
num2 = Math.floor(Math.random() * 12) + 1; // Times tables up to 12
answer = num1 * num2;
operation = 'ร';
display = `${num1} ร ${num2}`;
break;
case 'DIVISION':
num2 = Math.floor(Math.random() * 12) + 1;
answer = Math.floor(Math.random() * 20) + 1;
num1 = num2 * answer; // Ensure clean division
operation = 'รท';
display = `${num1} รท ${num2}`;
break;
default:
return null;
}
return {
id: Date.now(),
display,
num1,
num2,
operation,
answer,
topic,
level,
};
};
export default function App() {
// Game state
const [currentScreen, setCurrentScreen] = useState('home');
const [selectedTopic, setSelectedTopic] = useState(null);
const [currentLevel, setCurrentLevel] = useState(0);
const [currentProblem, setCurrentProblem] = useState(null);
const [userAnswer, setUserAnswer] = useState('');
const [score, setScore] = useState(0);
const [streak, setStreak] = useState(0);
const [timeLeft, setTimeLeft] = useState(60);
const [gameMode, setGameMode] = useState('QUICK_FIRE');
const [isGameActive, setIsGameActive] = useState(false);
const [feedback, setFeedback] = useState('');
const [totalProblems, setTotalProblems] = useState(0);
const [correctAnswers, setCorrectAnswers] = useState(0);
// Animations
const fadeAnimation = useRef(new Animated.Value(1)).current;
const scaleAnimation = useRef(new Animated.Value(1)).current;
// Game timer
useEffect(() => {
let timer;
if (isGameActive && gameMode === 'QUICK_FIRE' && timeLeft > 0) {
timer = setTimeout(() => {
setTimeLeft(timeLeft - 1);
}, 1000);
} else if (timeLeft === 0 && isGameActive) {
endGame();
}
return () => clearTimeout(timer);
}, [timeLeft, isGameActive, gameMode]);
// Generate new problem
const generateNewProblem = () => {
if (!selectedTopic) return;
const problem = generateProblem(selectedTopic, currentLevel);
setCurrentProblem(problem);
setUserAnswer('');
setTotalProblems(prev => prev + 1);
};
// Start game
const startGame = (topic, mode) => {
setSelectedTopic(topic);
setGameMode(mode);
setCurrentScreen('game');
setScore(0);
setStreak(0);
setCorrectAnswers(0);
setTotalProblems(0);
setCurrentLevel(0);
setIsGameActive(true);
if (mode === 'QUICK_FIRE') {
setTimeLeft(60);
}
generateNewProblem();
};
// Submit answer
const submitAnswer = () => {
if (!currentProblem || userAnswer === '') return;
const userNum = parseInt(userAnswer);
const isCorrect = userNum === currentProblem.answer;
if (isCorrect) {
setScore(prev => prev + (10 + streak * 2));
setStreak(prev => prev + 1);
setCorrectAnswers(prev => prev + 1);
setFeedback('โ
Correct!');
// Success animation
Animated.sequence([
Animated.timing(scaleAnimation, {
toValue: 1.2,
duration: 200,
useNativeDriver: true,
}),
Animated.timing(scaleAnimation, {
toValue: 1,
duration: 200,
useNativeDriver: true,
}),
]).start();
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
// Level up check
if (streak > 0 && streak % 5 === 0 && currentLevel < 2) {
setCurrentLevel(prev => prev + 1);
setFeedback('๐ Level Up!');
}
} else {
setStreak(0);
setFeedback(`โ Wrong! Answer: ${currentProblem.answer}`);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
}
// Clear feedback and generate new problem
setTimeout(() => {
setFeedback('');
generateNewProblem();
}, 1500);
};
// End game
const endGame = () => {
setIsGameActive(false);
const accuracy = totalProblems > 0 ? Math.round((correctAnswers / totalProblems) * 100) : 0;
Alert.alert(
'Game Over!',
`Score: ${score}\nAccuracy: ${accuracy}%\nStreak: ${streak}`,
[
{ text: 'Play Again', onPress: () => startGame(selectedTopic, gameMode) },
{ text: 'Home', onPress: () => setCurrentScreen('home') },
]
);
};
// Render functions
const renderHomeScreen = () => (
<View style={styles.container}>
<Text style={styles.title}>Math Adventure</Text>
<Text style={styles.subtitle}>Make math fun and engaging!</Text>
<View style={styles.topicSelection}>
<Text style={styles.sectionTitle}>Choose a Topic:</Text>
{Object.entries(MATH_TOPICS).map(([key, topic]) => (
<TouchableOpacity
key={key}
style={[styles.topicButton, { backgroundColor: topic.color }]}
onPress={() => showGameModes(key)}
>
<Text style={styles.topicEmoji}>{topic.emoji}</Text>
<Text style={styles.topicName}>{topic.name}</Text>
</TouchableOpacity>
))}
</View>
<View style={styles.statsContainer}>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{score}</Text>
<Text style={styles.statLabel}>Best Score</Text>
</View>
<View style={styles.statItem}>
<Text style={styles.statNumber}>{streak}</Text>
<Text style={styles.statLabel}>Best Streak</Text>
</View>
</View>
</View>
);
const showGameModes = (topic) => {
Alert.alert(
'Choose Game Mode',
'Select how you want to practice:',
[
{
text: 'โก Quick Fire (60s)',
onPress: () => startGame(topic, 'QUICK_FIRE'),
},
{
text: '๐ญ Adventure Mode',
onPress: () => startGame(topic, 'ADVENTURE'),
},
{
text: '๐๏ธ Visual Mode',
onPress: () => startVisualMode(topic),
},
{ text: 'Cancel', style: 'cancel' },
]
);
};
const renderGameScreen = () => {
if (!currentProblem) return null;
return (
<View style={styles.container}>
{/* Game Header */}
<View style={styles.gameHeader}>
<TouchableOpacity
style={styles.backButton}
onPress={() => setCurrentScreen('home')}
>
<Text style={styles.backButtonText}>โ Back</Text>
</TouchableOpacity>
<View style={styles.gameStats}>
<Text style={styles.scoreText}>Score: {score}</Text>
<Text style={styles.streakText}>Streak: {streak} ๐ฅ</Text>
{gameMode === 'QUICK_FIRE' && (
<Text style={styles.timeText}>Time: {timeLeft}s</Text>
)}
</View>
</View>
{/* Level Indicator */}
<View style={styles.levelIndicator}>
<Text style={styles.levelText}>
{MATH_TOPICS[selectedTopic].name} - Level {currentLevel + 1}
</Text>
</View>
{/* Problem Display */}
<Animated.View
style={[
styles.problemContainer,
{ transform: [{ scale: scaleAnimation }] }
]}
>
<Text style={styles.problemText}>{currentProblem.display}</Text>
<Text style={styles.equalsSign}>=</Text>
<Text style={styles.questionMark}>?</Text>
</Animated.View>
{/* Answer Input */}
<View style={styles.answerContainer}>
<TextInput
style={styles.answerInput}
value={userAnswer}
onChangeText={setUserAnswer}
keyboardType="numeric"
placeholder="Your answer"
onSubmitEditing={submitAnswer}
autoFocus
/>
<TouchableOpacity
style={styles.submitButton}
onPress={submitAnswer}
>
<Text style={styles.submitButtonText}>Submit</Text>
</TouchableOpacity>
</View>
{/* Feedback */}
{feedback ? (
<Text style={[
styles.feedback,
feedback.includes('โ
') ? styles.correctFeedback : styles.incorrectFeedback
]}>
{feedback}
</Text>
) : null}
{/* Number Pad */}
<View style={styles.numberPad}>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 0].map(number => (
<TouchableOpacity
key={number}
style={styles.numberButton}
onPress={() => setUserAnswer(prev => prev + number.toString())}
>
<Text style={styles.numberButtonText}>{number}</Text>
</TouchableOpacity>
))}
<TouchableOpacity
style={[styles.numberButton, styles.clearButton]}
onPress={() => setUserAnswer('')}
>
<Text style={styles.numberButtonText}>Clear</Text>
</TouchableOpacity>
</View>
</View>
);
};
return (
<View style={styles.container}>
{currentScreen === 'home' && renderHomeScreen()}
{currentScreen === 'game' && renderGameScreen()}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8f9fa',
paddingTop: 50,
},
title: {
fontSize: 32,
fontWeight: 'bold',
color: '#2c3e50',
textAlign: 'center',
marginBottom: 10,
},
subtitle: {
fontSize: 18,
color: '#7f8c8d',
textAlign: 'center',
marginBottom: 30,
},
sectionTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#2c3e50',
textAlign: 'center',
marginBottom: 20,
},
topicSelection: {
paddingHorizontal: 20,
marginBottom: 30,
},
topicButton: {
flexDirection: 'row',
alignItems: 'center',
padding: 20,
borderRadius: 15,
marginVertical: 8,
elevation: 3,
},
topicEmoji: {
fontSize: 30,
marginRight: 15,
},
topicName: {
fontSize: 20,
fontWeight: 'bold',
color: 'white',
},
statsContainer: {
flexDirection: 'row',
justifyContent: 'center',
paddingHorizontal: 20,
},
statItem: {
alignItems: 'center',
marginHorizontal: 30,
},
statNumber: {
fontSize: 28,
fontWeight: 'bold',
color: '#3498db',
},
statLabel: {
fontSize: 16,
color: '#7f8c8d',
marginTop: 5,
},
gameHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 20,
paddingVertical: 10,
},
backButton: {
padding: 10,
},
backButtonText: {
fontSize: 16,
color: '#3498db',
fontWeight: 'bold',
},
gameStats: {
alignItems: 'flex-end',
},
scoreText: {
fontSize: 16,
color: '#27ae60',
fontWeight: 'bold',
},
streakText: {
fontSize: 16,
color: '#e74c3c',
fontWeight: 'bold',
},
timeText: {
fontSize: 16,
color: '#f39c12',
fontWeight: 'bold',
},
levelIndicator: {
alignItems: 'center',
marginVertical: 20,
},
levelText: {
fontSize: 18,
color: '#8e44ad',
fontWeight: 'bold',
},
problemContainer: {
alignItems: 'center',
marginVertical: 40,
},
problemText: {
fontSize: 48,
fontWeight: 'bold',
color: '#2c3e50',
textAlign: 'center',
},
equalsSign: {
fontSize: 40,
color: '#3498db',
marginVertical: 10,
},
questionMark: {
fontSize: 60,
color: '#e74c3c',
fontWeight: 'bold',
},
answerContainer: {
flexDirection: 'row',
paddingHorizontal: 20,
marginBottom: 20,
alignItems: 'center',
justifyContent: 'center',
},
answerInput: {
backgroundColor: 'white',
borderRadius: 25,
paddingHorizontal: 20,
paddingVertical: 15,
fontSize: 24,
textAlign: 'center',
minWidth: 150,
marginRight: 15,
elevation: 2,
},
submitButton: {
backgroundColor: '#27ae60',
paddingHorizontal: 25,
paddingVertical: 15,
borderRadius: 25,
},
submitButtonText: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
feedback: {
fontSize: 20,
textAlign: 'center',
fontWeight: 'bold',
marginBottom: 20,
},
correctFeedback: {
color: '#27ae60',
},
incorrectFeedback: {
color: '#e74c3c',
},
numberPad: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
paddingHorizontal: 20,
},
numberButton: {
backgroundColor: 'white',
width: width * 0.25,
height: 60,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
margin: 5,
elevation: 2,
},
clearButton: {
backgroundColor: '#e74c3c',
width: width * 0.5,
},
numberButtonText: {
fontSize: 24,
fontWeight: 'bold',
color: '#2c3e50',
},
});
Add visual representations for math problems:
// Add visual mode component
const startVisualMode = (topic) => {
setSelectedTopic(topic);
setCurrentScreen('visual');
generateNewProblem();
};
// Visual representations
const VisualMath = ({ problem }) => {
if (!problem) return null;
const renderVisualAddition = () => {
const { num1, num2 } = problem;
const items1 = Array(num1).fill(0).map((_, i) => i);
const items2 = Array(num2).fill(0).map((_, i) => i + num1);
return (
<View style={styles.visualContainer}>
<Text style={styles.visualTitle}>Visual Addition</Text>
<View style={styles.visualGroup}>
<Text style={styles.visualGroupLabel}>{num1} items:</Text>
<View style={styles.itemsContainer}>
{items1.map(i => (
<View key={i} style={[styles.visualItem, { backgroundColor: '#3498db' }]} />
))}
</View>
</View>
<Text style={styles.operatorText}>+</Text>
<View style={styles.visualGroup}>
<Text style={styles.visualGroupLabel}>{num2} items:</Text>
<View style={styles.itemsContainer}>
{items2.map(i => (
<View key={i} style={[styles.visualItem, { backgroundColor: '#e74c3c' }]} />
))}
</View>
</View>
<Text style={styles.operatorText}>=</Text>
<View style={styles.visualGroup}>
<Text style={styles.visualGroupLabel}>Total: {num1 + num2} items</Text>
<View style={styles.itemsContainer}>
{[...items1, ...items2].map(i => (
<View
key={i}
style={[
styles.visualItem,
{ backgroundColor: i < num1 ? '#3498db' : '#e74c3c' }
]}
/>
))}
</View>
</View>
</View>
);
};
const renderVisualMultiplication = () => {
const { num1, num2 } = problem;
const totalItems = num1 * num2;
return (
<View style={styles.visualContainer}>
<Text style={styles.visualTitle}>Visual Multiplication</Text>
<Text style={styles.visualExplanation}>
{num1} groups of {num2} items each
</Text>
<View style={styles.multiplicationGrid}>
{Array(num1).fill(0).map((_, groupIndex) => (
<View key={groupIndex} style={styles.multiplicationGroup}>
<Text style={styles.groupLabel}>Group {groupIndex + 1}:</Text>
<View style={styles.groupItems}>
{Array(num2).fill(0).map((_, itemIndex) => (
<View
key={`${groupIndex}-${itemIndex}`}
style={[styles.visualItem, { backgroundColor: '#27ae60' }]}
/>
))}
</View>
</View>
))}
</View>
<Text style={styles.visualResult}>
Total: {num1} ร {num2} = {totalItems} items
</Text>
</View>
);
};
switch (problem.topic) {
case 'ADDITION':
return renderVisualAddition();
case 'MULTIPLICATION':
return renderVisualMultiplication();
default:
return (
<View style={styles.visualContainer}>
<Text style={styles.visualTitle}>Visual representation coming soon!</Text>
<Text style={styles.problemText}>{problem.display} = {problem.answer}</Text>
</View>
);
}
};
// Visual mode screen
const renderVisualScreen = () => {
return (
<View style={styles.container}>
<View style={styles.gameHeader}>
<TouchableOpacity
style={styles.backButton}
onPress={() => setCurrentScreen('home')}
>
<Text style={styles.backButtonText}>โ Back</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.nextButton}
onPress={generateNewProblem}
>
<Text style={styles.nextButtonText}>Next Problem</Text>
</TouchableOpacity>
</View>
<VisualMath problem={currentProblem} />
</View>
);
};
// Add to render return
{currentScreen === 'visual' && renderVisualScreen()}
// Visual styles
visualContainer: {
flex: 1,
paddingHorizontal: 20,
alignItems: 'center',
},
visualTitle: {
fontSize: 24,
fontWeight: 'bold',
color: '#2c3e50',
marginBottom: 20,
},
visualGroup: {
alignItems: 'center',
marginVertical: 15,
},
visualGroupLabel: {
fontSize: 18,
color: '#34495e',
marginBottom: 10,
},
itemsContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
maxWidth: width * 0.8,
},
visualItem: {
width: 20,
height: 20,
borderRadius: 10,
margin: 2,
},
operatorText: {
fontSize: 32,
color: '#8e44ad',
fontWeight: 'bold',
marginVertical: 10,
},
visualExplanation: {
fontSize: 16,
color: '#7f8c8d',
textAlign: 'center',
marginBottom: 20,
},
multiplicationGrid: {
alignItems: 'center',
},
multiplicationGroup: {
alignItems: 'center',
marginVertical: 10,
padding: 10,
backgroundColor: 'rgba(46, 204, 113, 0.1)',
borderRadius: 10,
},
groupLabel: {
fontSize: 14,
color: '#27ae60',
fontWeight: 'bold',
marginBottom: 5,
},
groupItems: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
},
visualResult: {
fontSize: 20,
fontWeight: 'bold',
color: '#2c3e50',
marginTop: 20,
textAlign: 'center',
},
nextButton: {
backgroundColor: '#3498db',
paddingHorizontal: 15,
paddingVertical: 8,
borderRadius: 15,
},
nextButtonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
Speed-based competitive math:
const MathRacer = () => {
const [racePosition, setRacePosition] = useState(0);
const [opponentPosition, setOpponentPosition] = useState(0);
const [raceProblems, setRaceProblems] = useState([]);
const [currentRaceIndex, setCurrentRaceIndex] = useState(0);
const startRace = () => {
const problems = Array(10).fill(0).map(() => generateProblem(selectedTopic, currentLevel));
setRaceProblems(problems);
setRacePosition(0);
setOpponentPosition(0);
setCurrentRaceIndex(0);
setCurrentScreen('race');
// Simulate opponent progress
const opponentInterval = setInterval(() => {
setOpponentPosition(prev => {
const newPos = prev + Math.random() * 2;
if (newPos >= 100) {
clearInterval(opponentInterval);
Alert.alert('Race Over', 'You lost! The opponent finished first.');
}
return Math.min(newPos, 100);
});
}, 2000);
};
const solveRaceProblem = (isCorrect) => {
if (isCorrect) {
setRacePosition(prev => {
const newPos = prev + 10;
if (newPos >= 100) {
Alert.alert('Victory!', 'You won the race!');
return 100;
}
return newPos;
});
}
if (currentRaceIndex < raceProblems.length - 1) {
setCurrentRaceIndex(prev => prev + 1);
}
};
return (
<View style={styles.raceContainer}>
{/* Race Track */}
<View style={styles.raceTrack}>
<Text style={styles.raceTitle}>Math Race!</Text>
{/* Player Lane */}
<View style={styles.raceLane}>
<Text style={styles.laneLabel}>You</Text>
<View style={styles.trackLine}>
<View
style={[
styles.racer,
styles.playerRacer,
{ left: `${racePosition}%` }
]}
>
<Text style={styles.racerEmoji}>๐</Text>
</View>
</View>
<Text style={styles.positionText}>{racePosition.toFixed(0)}%</Text>
</View>
{/* Opponent Lane */}
<View style={styles.raceLane}>
<Text style={styles.laneLabel}>Opponent</Text>
<View style={styles.trackLine}>
<View
style={[
styles.racer,
styles.opponentRacer,
{ left: `${opponentPosition}%` }
]}
>
<Text style={styles.racerEmoji}>๐ค</Text>
</View>
</View>
<Text style={styles.positionText}>{opponentPosition.toFixed(0)}%</Text>
</View>
</View>
{/* Current Problem */}
{raceProblems[currentRaceIndex] && (
<View style={styles.raceProblem}>
<Text style={styles.problemText}>
{raceProblems[currentRaceIndex].display} = ?
</Text>
{/* Quick Answer Buttons */}
<View style={styles.quickAnswers}>
{generateQuickAnswers(raceProblems[currentRaceIndex]).map((answer, index) => (
<TouchableOpacity
key={index}
style={styles.quickAnswerButton}
onPress={() => solveRaceProblem(answer === raceProblems[currentRaceIndex].answer)}
>
<Text style={styles.quickAnswerText}>{answer}</Text>
</TouchableOpacity>
))}
</View>
</View>
)}
</View>
);
};
// Generate multiple choice answers for racing
const generateQuickAnswers = (problem) => {
const correct = problem.answer;
const wrong1 = correct + Math.floor(Math.random() * 10) + 1;
const wrong2 = Math.max(0, correct - Math.floor(Math.random() * 10) - 1);
const wrong3 = correct + Math.floor(Math.random() * 20) + 10;
return [correct, wrong1, wrong2, wrong3].sort(() => Math.random() - 0.5);
};
// Race styles
raceContainer: {
flex: 1,
padding: 20,
},
raceTitle: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 20,
color: '#2c3e50',
},
raceTrack: {
backgroundColor: '#ecf0f1',
borderRadius: 15,
padding: 20,
marginBottom: 30,
},
raceLane: {
marginVertical: 15,
},
laneLabel: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
color: '#34495e',
},
trackLine: {
height: 40,
backgroundColor: '#bdc3c7',
borderRadius: 20,
position: 'relative',
marginBottom: 5,
},
racer: {
position: 'absolute',
top: 5,
width: 30,
height: 30,
borderRadius: 15,
alignItems: 'center',
justifyContent: 'center',
},
playerRacer: {
backgroundColor: '#3498db',
},
opponentRacer: {
backgroundColor: '#e74c3c',
},
racerEmoji: {
fontSize: 20,
},
positionText: {
textAlign: 'right',
fontSize: 14,
color: '#7f8c8d',
},
raceProblem: {
alignItems: 'center',
},
quickAnswers: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
marginTop: 20,
},
quickAnswerButton: {
backgroundColor: '#3498db',
paddingHorizontal: 20,
paddingVertical: 15,
borderRadius: 10,
margin: 5,
},
quickAnswerText: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
Story-based math progression:
const MathAdventure = () => {
const [adventureLevel, setAdventureLevel] = useState(1);
const [adventureScore, setAdventureScore] = useState(0);
const [currentChallenge, setCurrentChallenge] = useState(null);
const ADVENTURE_LEVELS = {
1: {
title: 'The Number Kingdom',
story: 'Help the king count his treasure!',
challenges: 5,
topic: 'ADDITION',
reward: 100,
},
2: {
title: 'The Subtraction Caves',
story: 'Escape the caves by solving puzzles!',
challenges: 7,
topic: 'SUBTRACTION',
reward: 150,
},
3: {
title: 'Multiplication Mountain',
story: 'Climb the mountain with math power!',
challenges: 10,
topic: 'MULTIPLICATION',
reward: 200,
},
};
const startAdventure = () => {
const level = ADVENTURE_LEVELS[adventureLevel];
setCurrentChallenge({
...level,
currentProblem: 1,
problemsCompleted: 0,
});
setCurrentScreen('adventure');
};
const solveAdventureProblem = (isCorrect) => {
if (isCorrect) {
setAdventureScore(prev => prev + 25);
setCurrentChallenge(prev => ({
...prev,
problemsCompleted: prev.problemsCompleted + 1,
}));
if (currentChallenge.problemsCompleted + 1 >= currentChallenge.challenges) {
// Level complete!
Alert.alert(
'Level Complete!',
`You earned ${currentChallenge.reward} bonus points!`,
[{
text: 'Continue',
onPress: () => {
setAdventureScore(prev => prev + currentChallenge.reward);
if (adventureLevel < Object.keys(ADVENTURE_LEVELS).length) {
setAdventureLevel(prev => prev + 1);
}
setCurrentScreen('home');
}
}]
);
}
}
generateNewProblem();
};
return (
<View style={styles.adventureContainer}>
{/* Adventure Header */}
<View style={styles.adventureHeader}>
<Text style={styles.adventureTitle}>{currentChallenge?.title}</Text>
<Text style={styles.adventureStory}>{currentChallenge?.story}</Text>
<View style={styles.adventureProgress}>
<Text style={styles.progressText}>
Challenge {currentChallenge?.problemsCompleted + 1} / {currentChallenge?.challenges}
</Text>
<View style={styles.progressBar}>
<View
style={[
styles.progressFill,
{
width: `${((currentChallenge?.problemsCompleted || 0) / (currentChallenge?.challenges || 1)) * 100}%`
}
]}
/>
</View>
</View>
</View>
{/* Adventure Problem */}
{currentProblem && (
<View style={styles.adventureProblem}>
<Text style={styles.problemText}>{currentProblem.display} = ?</Text>
<TextInput
style={styles.adventureInput}
value={userAnswer}
onChangeText={setUserAnswer}
keyboardType="numeric"
placeholder="Your answer"
onSubmitEditing={() => {
const isCorrect = parseInt(userAnswer) === currentProblem.answer;
solveAdventureProblem(isCorrect);
}}
/>
<TouchableOpacity
style={styles.adventureSubmit}
onPress={() => {
const isCorrect = parseInt(userAnswer) === currentProblem.answer;
solveAdventureProblem(isCorrect);
}}
>
<Text style={styles.adventureSubmitText}>Solve Challenge</Text>
</TouchableOpacity>
</View>
)}
</View>
);
};
Math Learning App Successfully Built If:
Time Investment: 60 minutes total Difficulty Level: Intermediate Prerequisites: React Native basics, understanding of educational game design Tools Needed: Device for testing, understanding of math concepts