nizzad commited on
Commit
4562618
·
verified ·
1 Parent(s): a9910d8

Upload 8 files

Browse files
requirements.txt CHANGED
@@ -1,3 +1,2 @@
1
- altair
2
- pandas
3
- streamlit
 
1
+ venv\Scriptspygame>=2.1.0
2
+ pytest>=7.0.0
 
src/__pycache__/game.cpython-312.pyc ADDED
Binary file (15.6 kB). View file
 
src/__pycache__/levels.cpython-312.pyc ADDED
Binary file (3.16 kB). View file
 
src/game.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pygame
2
+ import sys
3
+ from levels import *
4
+
5
+ class Game:
6
+ def __init__(self, screen):
7
+ self.screen = screen
8
+ self.clock = pygame.time.Clock()
9
+
10
+ # Load fonts - try to use more playful fonts if available
11
+ try:
12
+ self.font = pygame.font.SysFont('Comic Sans MS', 32)
13
+ self.small_font = pygame.font.SysFont('Comic Sans MS', 24)
14
+ self.title_font = pygame.font.SysFont('Comic Sans MS', 48)
15
+ except:
16
+ self.font = pygame.font.SysFont('Arial', 32)
17
+ self.small_font = pygame.font.SysFont('Arial', 24)
18
+ self.title_font = pygame.font.SysFont('Arial', 48)
19
+
20
+ # Enhanced color palette
21
+ self.WHITE = (255, 255, 255)
22
+ self.BLACK = (0, 0, 0)
23
+ self.GREEN = (76, 175, 80)
24
+ self.RED = (244, 67, 54)
25
+ self.BLUE = (33, 150, 243)
26
+ self.PURPLE = (156, 39, 176)
27
+ self.BACKGROUND_TOP = (179, 229, 252) # Light blue
28
+ self.BACKGROUND_BOTTOM = (255, 255, 255)
29
+ self.GOLD = (255, 193, 7)
30
+
31
+ # Game state
32
+ self.current_grade = 1
33
+ self.score = 0
34
+ self.lives = 3
35
+ self.answer_input = ""
36
+ self.feedback = ""
37
+ self.feedback_color = self.BLACK
38
+ self.feedback_scale = 1.0 # For animation
39
+
40
+ # Load heart image for lives or create a heart shape
41
+ self.heart_points = [
42
+ (0, -4), (2, -2), (4, -4), (4, -2), (2, 4), (0, 2),
43
+ (-2, 4), (-4, -2), (-4, -4), (-2, -2)
44
+ ]
45
+ self.heart_points = [(x * 3 + 20, y * 3 + 80) for x, y in self.heart_points]
46
+
47
+ # Initialize first problem
48
+ self.new_problem()
49
+
50
+ def new_problem(self):
51
+ """Generate a new math problem based on current grade"""
52
+ self.problem = generate_problem(self.current_grade)
53
+ self.answer_input = ""
54
+ self.feedback = ""
55
+
56
+ def handle_events(self):
57
+ """Handle user input events"""
58
+ for event in pygame.event.get():
59
+ if event.type == pygame.QUIT:
60
+ pygame.quit()
61
+ sys.exit()
62
+
63
+ if event.type == pygame.KEYDOWN:
64
+ if event.key == pygame.K_RETURN and self.answer_input:
65
+ self.check_answer()
66
+ elif event.key == pygame.K_BACKSPACE:
67
+ self.answer_input = self.answer_input[:-1]
68
+ elif event.unicode.isnumeric() or event.unicode == '-':
69
+ self.answer_input += event.unicode
70
+
71
+ def check_answer(self):
72
+ """Check if the provided answer is correct"""
73
+ try:
74
+ user_answer = int(self.answer_input)
75
+ if user_answer == self.problem.answer:
76
+ self.score += 10 * self.current_grade
77
+ self.feedback = "Correct!"
78
+ self.feedback_color = self.GREEN
79
+ self.feedback_scale = 1.5 # Start larger for "pop" effect
80
+
81
+ # Increase grade if score threshold reached
82
+ if self.score >= self.current_grade * 100 and self.current_grade < 5:
83
+ self.current_grade += 1
84
+ self.feedback = f"Level Up! Now at Grade {self.current_grade}"
85
+ self.feedback_color = self.GOLD
86
+ self.feedback_scale = 2.0 # Even bigger for level up
87
+ else:
88
+ self.lives -= 1
89
+ self.feedback = "Wrong answer!"
90
+ self.feedback_color = self.RED
91
+ self.feedback_scale = 1.3 # Slightly larger for wrong answer
92
+
93
+ if self.lives <= 0:
94
+ self.game_over()
95
+
96
+ # Draw one frame with the current feedback
97
+ self.draw()
98
+ pygame.display.flip()
99
+ pygame.time.wait(500) # Show feedback for half a second
100
+
101
+ self.new_problem()
102
+
103
+ except ValueError:
104
+ self.feedback = "Please enter a valid number"
105
+ self.feedback_color = self.RED
106
+ self.feedback_scale = 1.2
107
+
108
+ def game_over(self):
109
+ """Handle game over state"""
110
+ waiting = True
111
+ alpha = 0
112
+ fade_surface = pygame.Surface((self.screen.get_width(), self.screen.get_height()))
113
+ fade_surface.fill(self.BLACK)
114
+
115
+ while alpha < 128: # Fade to semi-transparent black
116
+ self.draw() # Draw the game screen underneath
117
+ fade_surface.set_alpha(alpha)
118
+ self.screen.blit(fade_surface, (0, 0))
119
+ pygame.display.flip()
120
+ alpha += 2
121
+ pygame.time.wait(5)
122
+
123
+ # Create game over panel
124
+ panel_width = 400
125
+ panel_height = 300
126
+ panel_x = (self.screen.get_width() - panel_width) // 2
127
+ panel_y = (self.screen.get_height() - panel_height) // 2
128
+
129
+ while waiting:
130
+ # Draw the base game screen and fade overlay
131
+ self.draw()
132
+ fade_surface.set_alpha(128)
133
+ self.screen.blit(fade_surface, (0, 0))
134
+
135
+ # Draw game over panel
136
+ pygame.draw.rect(self.screen, self.WHITE,
137
+ (panel_x, panel_y, panel_width, panel_height))
138
+ pygame.draw.rect(self.screen, self.BLUE,
139
+ (panel_x, panel_y, panel_width, panel_height), 4)
140
+
141
+ # Draw game over text
142
+ game_over_text = self.title_font.render("Game Over!", True, self.RED)
143
+ score_text = self.font.render(f"Final Score: {self.score}", True, self.BLACK)
144
+ grade_text = self.font.render(f"Reached Grade: {self.current_grade}", True, self.BLUE)
145
+ restart_text = self.small_font.render("Press R to restart or Q to quit", True, self.PURPLE)
146
+
147
+ # Center all text in the panel
148
+ self.screen.blit(game_over_text,
149
+ game_over_text.get_rect(centerx=panel_x + panel_width // 2,
150
+ top=panel_y + 40))
151
+ self.screen.blit(score_text,
152
+ score_text.get_rect(centerx=panel_x + panel_width // 2,
153
+ top=panel_y + 120))
154
+ self.screen.blit(grade_text,
155
+ grade_text.get_rect(centerx=panel_x + panel_width // 2,
156
+ top=panel_y + 170))
157
+ self.screen.blit(restart_text,
158
+ restart_text.get_rect(centerx=panel_x + panel_width // 2,
159
+ top=panel_y + 220))
160
+
161
+ pygame.display.flip()
162
+
163
+ for event in pygame.event.get():
164
+ if event.type == pygame.QUIT:
165
+ pygame.quit()
166
+ sys.exit()
167
+ if event.type == pygame.KEYDOWN:
168
+ if event.key == pygame.K_r:
169
+ self.__init__(self.screen)
170
+ waiting = False
171
+ elif event.key == pygame.K_q:
172
+ pygame.quit()
173
+ sys.exit()
174
+
175
+ def update(self):
176
+ """Update game state"""
177
+ self.clock.tick(60)
178
+
179
+ def draw_gradient_background(self):
180
+ """Draw a gradient background"""
181
+ height = self.screen.get_height()
182
+ for i in range(height):
183
+ progress = i / height
184
+ color = [int(self.BACKGROUND_TOP[j] * (1 - progress) + self.BACKGROUND_BOTTOM[j] * progress)
185
+ for j in range(3)]
186
+ pygame.draw.line(self.screen, color, (0, i), (self.screen.get_width(), i))
187
+
188
+ def draw_heart(self, x, y, filled=True):
189
+ """Draw a heart shape at the specified position"""
190
+ points = [(px + x - 20, py - 80 + y) for px, py in self.heart_points]
191
+ if filled:
192
+ pygame.draw.polygon(self.screen, self.RED, points)
193
+ pygame.draw.polygon(self.screen, (200, 0, 0), points, 2)
194
+
195
+ def draw(self):
196
+ """Draw the game screen"""
197
+ # Draw gradient background
198
+ self.draw_gradient_background()
199
+
200
+ # Draw decorative header
201
+ pygame.draw.rect(self.screen, self.BLUE, (0, 0, self.screen.get_width(), 120))
202
+ pygame.draw.rect(self.screen, self.PURPLE, (0, 115, self.screen.get_width(), 10))
203
+
204
+ # Draw grade and score with enhanced styling
205
+ grade_text = self.small_font.render(f"Grade: {self.current_grade}", True, self.WHITE)
206
+ score_text = self.small_font.render(f"Score: {self.score}", True, self.WHITE)
207
+
208
+ self.screen.blit(grade_text, (20, 20))
209
+ self.screen.blit(score_text, (20, 50))
210
+
211
+ # Draw lives as hearts
212
+ for i in range(3):
213
+ self.draw_heart(60 + i * 30, 80, i < self.lives)
214
+
215
+ # Draw problem with a background panel
216
+ panel_rect = pygame.Rect(250, 150, 300, 250)
217
+ pygame.draw.rect(self.screen, self.WHITE, panel_rect)
218
+ pygame.draw.rect(self.screen, self.BLUE, panel_rect, 3)
219
+
220
+ # Draw problem text
221
+ problem_text = self.font.render(str(self.problem), True, self.BLACK)
222
+ problem_rect = problem_text.get_rect(centerx=panel_rect.centerx, top=panel_rect.top + 30)
223
+ self.screen.blit(problem_text, problem_rect)
224
+
225
+ # Draw answer input with a box
226
+ input_rect = pygame.Rect(panel_rect.left + 20, problem_rect.bottom + 20,
227
+ panel_rect.width - 40, 40)
228
+ pygame.draw.rect(self.screen, self.WHITE, input_rect)
229
+ pygame.draw.rect(self.screen, self.BLACK, input_rect, 2)
230
+
231
+ answer_text = self.font.render(f"Answer: {self.answer_input}", True, self.BLACK)
232
+ answer_rect = answer_text.get_rect(centerx=input_rect.centerx, centery=input_rect.centery)
233
+ self.screen.blit(answer_text, answer_rect)
234
+
235
+ # Draw feedback with animation
236
+ if self.feedback:
237
+ feedback_text = self.font.render(self.feedback, True, self.feedback_color)
238
+ feedback_rect = feedback_text.get_rect(centerx=panel_rect.centerx,
239
+ top=input_rect.bottom + 20)
240
+
241
+ # Apply scale animation
242
+ scaled_surface = pygame.transform.scale(
243
+ feedback_text,
244
+ (int(feedback_text.get_width() * self.feedback_scale),
245
+ int(feedback_text.get_height() * self.feedback_scale))
246
+ )
247
+ scaled_rect = scaled_surface.get_rect(center=feedback_rect.center)
248
+ self.screen.blit(scaled_surface, scaled_rect)
249
+
250
+ # Update scale for animation
251
+ target_scale = 1.0
252
+ self.feedback_scale += (target_scale - self.feedback_scale) * 0.1
253
+ if abs(self.feedback_scale - target_scale) < 0.01:
254
+ self.feedback_scale = target_scale
src/levels.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from dataclasses import dataclass
3
+
4
+ @dataclass
5
+ class Problem:
6
+ num1: int
7
+ num2: int
8
+ operator: str
9
+ answer: int
10
+
11
+ def __str__(self):
12
+ return f"{self.num1} {self.operator} {self.num2} = ?"
13
+
14
+ def generate_problem(grade: int) -> Problem:
15
+ """Generate a math problem appropriate for the given grade level"""
16
+
17
+ if grade == 1:
18
+ # Grade 1: Simple addition and subtraction with numbers 1-10
19
+ num1 = random.randint(1, 10)
20
+ num2 = random.randint(1, 10)
21
+ operator = random.choice(['+', '-'])
22
+
23
+ # Ensure subtraction doesn't result in negative numbers
24
+ if operator == '-' and num2 > num1:
25
+ num1, num2 = num2, num1
26
+
27
+ elif grade == 2:
28
+ # Grade 2: Addition and subtraction with numbers 1-20
29
+ num1 = random.randint(1, 20)
30
+ num2 = random.randint(1, 20)
31
+ operator = random.choice(['+', '-'])
32
+
33
+ if operator == '-' and num2 > num1:
34
+ num1, num2 = num2, num1
35
+
36
+ elif grade == 3:
37
+ # Grade 3: Addition, subtraction with numbers 1-50, and simple multiplication
38
+ num1 = random.randint(1, 50)
39
+ operator = random.choice(['+', '-', '*'])
40
+
41
+ if operator == '*':
42
+ num2 = random.randint(1, 10)
43
+ else:
44
+ num2 = random.randint(1, 50)
45
+ if operator == '-' and num2 > num1:
46
+ num1, num2 = num2, num1
47
+
48
+ elif grade == 4:
49
+ # Grade 4: All operations, multiplication with larger numbers
50
+ num1 = random.randint(1, 100)
51
+ operator = random.choice(['+', '-', '*', '/'])
52
+
53
+ if operator == '*':
54
+ num2 = random.randint(1, 12)
55
+ elif operator == '/':
56
+ # Generate division problems with whole number answers
57
+ num2 = random.randint(1, 12)
58
+ num1 = num2 * random.randint(1, 10)
59
+ else:
60
+ num2 = random.randint(1, 100)
61
+ if operator == '-' and num2 > num1:
62
+ num1, num2 = num2, num1
63
+
64
+ else: # grade 5
65
+ # Grade 5: Larger numbers and more complex operations
66
+ num1 = random.randint(1, 200)
67
+ operator = random.choice(['+', '-', '*', '/'])
68
+
69
+ if operator == '*':
70
+ num2 = random.randint(2, 15)
71
+ elif operator == '/':
72
+ num2 = random.randint(2, 15)
73
+ num1 = num2 * random.randint(1, 20)
74
+ else:
75
+ num2 = random.randint(1, 200)
76
+ if operator == '-' and num2 > num1:
77
+ num1, num2 = num2, num1
78
+
79
+ # Calculate answer
80
+ if operator == '+':
81
+ answer = num1 + num2
82
+ elif operator == '-':
83
+ answer = num1 - num2
84
+ elif operator == '*':
85
+ answer = num1 * num2
86
+ else: # operator == '/'
87
+ answer = num1 // num2
88
+
89
+ return Problem(num1, num2, operator, answer)
src/main.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pygame
2
+ import sys
3
+ from game import Game
4
+
5
+ def main():
6
+ # Initialize Pygame
7
+ pygame.init()
8
+ pygame.font.init()
9
+
10
+ # Set up the display
11
+ WINDOW_SIZE = (800, 600)
12
+ screen = pygame.display.set_mode(WINDOW_SIZE)
13
+ pygame.display.set_caption("Math Adventure - Developed by M.N.F. Zahra - Grade 05 - KM/KM/G.M.M. School")
14
+
15
+ # Create game instance
16
+ game = Game(screen)
17
+
18
+ # Main game loop
19
+ while True:
20
+ game.handle_events()
21
+ game.update()
22
+ game.draw()
23
+ pygame.display.flip()
24
+
25
+ if __name__ == "__main__":
26
+ main()
tests/test_game.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ import pygame
3
+ from src.game import Game
4
+
5
+ @pytest.fixture
6
+ def game():
7
+ pygame.init()
8
+ screen = pygame.display.set_mode((800, 600))
9
+ return Game(screen)
10
+
11
+ def test_game_initialization(game):
12
+ assert game.current_grade == 1
13
+ assert game.score == 0
14
+ assert game.lives == 3
15
+ assert game.answer_input == ""
16
+ assert game.feedback == ""
17
+ assert game.feedback_scale == 1.0 # Test new feedback scale initialization
18
+
19
+ def test_correct_answer(game):
20
+ initial_score = game.score
21
+ game.problem.answer = 42
22
+ game.answer_input = "42"
23
+ game.check_answer()
24
+ assert game.score > initial_score
25
+ assert game.feedback == "Correct!"
26
+ assert game.feedback_color == game.GREEN
27
+ assert game.feedback_scale == 1.5 # Test feedback animation scale
28
+
29
+ def test_wrong_answer(game):
30
+ initial_lives = game.lives
31
+ game.problem.answer = 42
32
+ game.answer_input = "24"
33
+ game.check_answer()
34
+ assert game.lives == initial_lives - 1
35
+ assert game.feedback == "Wrong answer!"
36
+ assert game.feedback_color == game.RED
37
+ assert game.feedback_scale == 1.3 # Test feedback animation scale
38
+
39
+ def test_invalid_answer(game):
40
+ game.answer_input = "abc"
41
+ game.check_answer()
42
+ assert game.feedback == "Please enter a valid number"
43
+ assert game.feedback_color == game.RED
44
+ assert game.feedback_scale == 1.2 # Test feedback animation scale
45
+
46
+ def test_grade_progression(game):
47
+ # Set score just below threshold for level up
48
+ game.score = 99
49
+ game.current_grade = 1
50
+ game.problem.answer = 42
51
+ game.answer_input = "42"
52
+ game.check_answer()
53
+ assert game.current_grade == 2
54
+ assert game.feedback == "Level Up! Now at Grade 2"
55
+ assert game.feedback_color == game.GOLD # Test gold color for level up
56
+ assert game.feedback_scale == 2.0 # Test larger scale for level up
57
+
58
+ def test_game_over(game):
59
+ game.lives = 1
60
+ game.problem.answer = 42
61
+ game.answer_input = "24"
62
+ game.check_answer()
63
+ assert game.lives == 0
64
+
65
+ def test_ui_colors(game):
66
+ # Test that all required colors are defined
67
+ assert hasattr(game, 'WHITE')
68
+ assert hasattr(game, 'BLACK')
69
+ assert hasattr(game, 'GREEN')
70
+ assert hasattr(game, 'RED')
71
+ assert hasattr(game, 'BLUE')
72
+ assert hasattr(game, 'PURPLE')
73
+ assert hasattr(game, 'GOLD')
74
+ assert hasattr(game, 'BACKGROUND_TOP')
75
+ assert hasattr(game, 'BACKGROUND_BOTTOM')
76
+
77
+ def test_heart_shape(game):
78
+ # Test that heart points are properly initialized
79
+ assert hasattr(game, 'heart_points')
80
+ assert len(game.heart_points) > 0
81
+ # Test that points are properly transformed
82
+ assert all(isinstance(point, tuple) and len(point) == 2 for point in game.heart_points)
tests/test_levels.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ from src.levels import generate_problem, Problem
3
+
4
+ def test_problem_string_representation():
5
+ problem = Problem(5, 3, '+', 8)
6
+ assert str(problem) == "5 + 3 = ?"
7
+
8
+ def test_grade_1_problem():
9
+ problem = generate_problem(1)
10
+ assert problem.num1 <= 10
11
+ assert problem.num2 <= 10
12
+ assert problem.operator in ['+', '-']
13
+ if problem.operator == '-':
14
+ assert problem.num1 >= problem.num2 # No negative results
15
+
16
+ def test_grade_2_problem():
17
+ problem = generate_problem(2)
18
+ assert problem.num1 <= 20
19
+ assert problem.num2 <= 20
20
+ assert problem.operator in ['+', '-']
21
+ if problem.operator == '-':
22
+ assert problem.num1 >= problem.num2
23
+
24
+ def test_grade_3_problem():
25
+ problem = generate_problem(3)
26
+ assert problem.num1 <= 50
27
+ assert problem.operator in ['+', '-', '*']
28
+ if problem.operator == '*':
29
+ assert problem.num2 <= 10
30
+ else:
31
+ assert problem.num2 <= 50
32
+ if problem.operator == '-':
33
+ assert problem.num1 >= problem.num2
34
+
35
+ def test_grade_4_problem():
36
+ problem = generate_problem(4)
37
+ assert problem.num1 <= 100
38
+ assert problem.operator in ['+', '-', '*', '/']
39
+ if problem.operator == '*':
40
+ assert problem.num2 <= 12
41
+ elif problem.operator == '/':
42
+ assert problem.num2 <= 12
43
+ assert problem.num1 % problem.num2 == 0 # Ensure clean division
44
+ else:
45
+ assert problem.num2 <= 100
46
+
47
+ def test_grade_5_problem():
48
+ problem = generate_problem(5)
49
+ assert problem.num1 <= 200
50
+ assert problem.operator in ['+', '-', '*', '/']
51
+ if problem.operator == '*':
52
+ assert problem.num2 <= 15
53
+ elif problem.operator == '/':
54
+ assert problem.num2 <= 15
55
+ assert problem.num1 % problem.num2 == 0
56
+ else:
57
+ assert problem.num2 <= 200
58
+
59
+ def test_problem_answers():
60
+ for grade in range(1, 6):
61
+ problem = generate_problem(grade)
62
+ if problem.operator == '+':
63
+ assert problem.answer == problem.num1 + problem.num2
64
+ elif problem.operator == '-':
65
+ assert problem.answer == problem.num1 - problem.num2
66
+ elif problem.operator == '*':
67
+ assert problem.answer == problem.num1 * problem.num2
68
+ elif problem.operator == '/':
69
+ assert problem.answer == problem.num1 // problem.num2