-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchasing_monster.py
More file actions
430 lines (369 loc) · 16.1 KB
/
chasing_monster.py
File metadata and controls
430 lines (369 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
import pygame
import sys
import math
import time
import random
# Initialize Pygame
pygame.init()
# Constants
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
PLAYER_RADIUS = 25
ENEMY_RADIUS = 30
SWORD_LENGTH = 40
ITEM_RADIUS = 15
OBSTACLE_WIDTH = 60
OBSTACLE_HEIGHT = 60
PLAYER_SPEED = 5
ENEMY_SPEED = 3.2
SPAWN_INTERVAL = 2000 # Milliseconds between item spawns
DESPAWN_TIME = 15 # Seconds for items to despawn
OBSTACLE_LIFETIME = 10 # Seconds for obstacles to despawn
MAX_OBSTACLES = 8
OBSTACLE_SPAWN_MIN_MS = 1500 # Min time between obstacle spawns
OBSTACLE_SPAWN_MAX_MS = 3000 # Max time between obstacle spawns
SURVIVAL_SCORE_MULTIPLIER = 1 # Points per second survived
BLACK = (10, 10, 20)
DARK_GRAY = (30, 30, 40)
LIGHT_GRAY = (200, 200, 220)
RED = (200, 50, 50)
WHITE = (240, 240, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)
FONT_SIZE = 36
# Set up display
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Sword Chase - Dynamic Obstacles & Items")
clock = pygame.time.Clock()
font = pygame.font.Font(None, FONT_SIZE)
small_font = pygame.font.Font(None, 24)
# Generate player character with a simple drawn appearance
def draw_player(surface, x, y):
# Body
pygame.draw.circle(surface, LIGHT_GRAY, (int(x), int(y)), PLAYER_RADIUS)
# Eyes (showing fear)
pygame.draw.circle(surface, BLACK, (int(x-8), int(y-5)), 5)
pygame.draw.circle(surface, BLACK, (int(x+8), int(y-5)), 5)
# Mouth (open in fear)
pygame.draw.arc(surface, BLACK, (int(x-10), int(y), 20, 15), 0, math.pi, 2)
# Generate enemy with sword
def draw_enemy(surface, x, y, angle):
# Enemy body
pygame.draw.circle(surface, DARK_GRAY, (int(x), int(y)), ENEMY_RADIUS)
# Evil eyes
pygame.draw.circle(surface, RED, (int(x-10), int(y-8)), 6)
pygame.draw.circle(surface, RED, (int(x+10), int(y-8)), 6)
pygame.draw.circle(surface, BLACK, (int(x-10), int(y-8)), 3)
pygame.draw.circle(surface, BLACK, (int(x+10), int(y-8)), 3)
# Angry mouth
pygame.draw.arc(surface, RED, (int(x-12), int(y+2), 24, 15), math.pi, 2*math.pi, 2)
# Sword
sword_end_x = x + math.cos(angle) * SWORD_LENGTH
sword_end_y = y + math.sin(angle) * SWORD_LENGTH
# Sword handle
pygame.draw.line(surface, (90, 90, 100), (x, y),
(x + math.cos(angle) * 15, y + math.sin(angle) * 15), 6)
# Sword blade
pygame.draw.line(surface, LIGHT_GRAY,
(x + math.cos(angle) * 15, y + math.sin(angle) * 15),
(sword_end_x, sword_end_y), 4)
# Draw coin
def draw_coin(surface, x, y):
pygame.draw.circle(surface, YELLOW, (int(x), int(y)), ITEM_RADIUS)
pygame.draw.circle(surface, ORANGE, (int(x), int(y)), ITEM_RADIUS, 2)
# Simple $ symbol
pygame.draw.line(surface, BLACK, (int(x-5), int(y-5)), (int(x+5), int(y-5)), 2)
pygame.draw.line(surface, BLACK, (int(x-5), int(y+5)), (int(x+5), int(y+5)), 2)
# Draw bomb
def draw_bomb(surface, x, y):
pygame.draw.circle(surface, RED, (int(x), int(y)), ITEM_RADIUS)
pygame.draw.circle(surface, BLACK, (int(x), int(y)), ITEM_RADIUS, 2)
# Simple ! symbol
pygame.draw.line(surface, BLACK, (int(x), int(y-5)), (int(x), int(y+5)), 3)
pygame.draw.circle(surface, BLACK, (int(x), int(y+8)), 3)
# Player class
class Player:
def __init__(self, x, y):
self.x = x
self.y = y
self.radius = PLAYER_RADIUS
def move(self, dx, dy, obstacles):
if dx == 0 and dy == 0:
return
new_x = self.x + dx * PLAYER_SPEED
new_y = self.y + dy * PLAYER_SPEED
# Check collision with obstacles
if not collides_with_obstacles(new_x, new_y, self.radius, obstacles):
self.x = new_x
self.y = new_y
# Keep player on screen
self.x = max(self.radius, min(SCREEN_WIDTH - self.radius, self.x))
self.y = max(self.radius, min(SCREEN_HEIGHT - self.radius, self.y))
def draw(self, screen):
draw_player(screen, self.x, self.y)
# Enemy class
class Enemy:
def __init__(self, x, y):
self.x = x
self.y = y
self.radius = ENEMY_RADIUS
self.sword_angle = 0
def update(self, player_x, player_y, obstacles):
# Calculate direction to player
dx = player_x - self.x
dy = player_y - self.y
distance = math.sqrt(dx**2 + dy**2)
if distance > 0:
# Normalize
dx /= distance
dy /= distance
# Calculate intended new position
new_x = self.x + dx * ENEMY_SPEED
new_y = self.y + dy * ENEMY_SPEED
# Check if move is blocked by obstacles
if not collides_with_obstacles(new_x, new_y, self.radius, obstacles):
self.x = new_x
self.y = new_y
else:
# If blocked, try to move slightly around (simple avoidance: reduce speed)
self.x += dx * ENEMY_SPEED * 0.5
self.y += dy * ENEMY_SPEED * 0.5
# Further clamp to avoid clipping
self.x = max(self.radius, min(SCREEN_WIDTH - self.radius, self.x))
self.y = max(self.radius, min(SCREEN_HEIGHT - self.radius, self.y))
# Calculate angle towards player for sword direction
self.sword_angle = math.atan2(player_y - self.y, player_x - self.x)
# Keep enemy on screen
self.x = max(self.radius, min(SCREEN_WIDTH - self.radius, self.x))
self.y = max(self.radius, min(SCREEN_HEIGHT - self.radius, self.y))
def draw(self, screen):
draw_enemy(screen, self.x, self.y, self.sword_angle)
# Item class for coins and bombs
class Item:
def __init__(self, x, y, item_type):
self.x = x
self.y = y
self.type = item_type # 'coin' or 'bomb'
self.radius = ITEM_RADIUS
self.spawn_time = time.time()
def should_despawn(self, current_time):
return current_time - self.spawn_time > DESPAWN_TIME
def draw(self, screen):
if self.type == 'coin':
draw_coin(screen, self.x, self.y)
elif self.type == 'bomb':
draw_bomb(screen, self.x, self.y)
# Obstacle class (simple rect with lifetime)
class Obstacle:
def __init__(self, x, y):
self.rect = pygame.Rect(x, y, OBSTACLE_WIDTH, OBSTACLE_HEIGHT)
self.spawn_time = time.time()
def should_despawn(self, current_time):
return current_time - self.spawn_time > OBSTACLE_LIFETIME
# Collision detection with obstacles
def collides_with_obstacles(x, y, radius, obstacles):
player_rect = pygame.Rect(x - radius, y - radius, radius * 2, radius * 2)
for obs in obstacles:
if player_rect.colliderect(obs.rect):
return True
return False
# Collision detection between player and enemy
def check_collision(player, enemy):
dx = player.x - enemy.x
dy = player.y - enemy.y
distance = math.sqrt(dx**2 + dy**2)
return distance < (player.radius + enemy.radius)
# Check if position overlaps with obstacles or screen edges (for spawning items/obstacles)
def is_valid_spawn_position(x, y, obstacles, player=None, is_obstacle=False):
if is_obstacle:
# For obstacles: larger buffer
if x < OBSTACLE_WIDTH or x > SCREEN_WIDTH - OBSTACLE_WIDTH or y < OBSTACLE_HEIGHT or y > SCREEN_HEIGHT - OBSTACLE_HEIGHT:
return False
new_rect = pygame.Rect(x, y, OBSTACLE_WIDTH, OBSTACLE_HEIGHT)
else:
# For items
if x < 50 or x > SCREEN_WIDTH - 50 or y < 50 or y > SCREEN_HEIGHT - 50:
return False
new_rect = pygame.Rect(x - ITEM_RADIUS, y - ITEM_RADIUS, ITEM_RADIUS * 2, ITEM_RADIUS * 2)
# Check overlap with existing obstacles
for obs in obstacles:
inflate_amount = 20 if is_obstacle else 0
if new_rect.colliderect(obs.rect.inflate(inflate_amount, inflate_amount)):
return False
# Additional check: Avoid spawning near player (for both items and obstacles)
if player:
player_rect = pygame.Rect(player.x - PLAYER_RADIUS, player.y - PLAYER_RADIUS, PLAYER_RADIUS * 2, PLAYER_RADIUS * 2)
player_rect.inflate_ip(40, 40) # Buffer around player
if new_rect.colliderect(player_rect):
return False
return True
# Check item collection
def check_item_collection(player, items):
collected = []
for item in items:
dx = player.x - item.x
dy = player.y - item.y
distance = math.sqrt(dx**2 + dy**2)
if distance < (player.radius + item.radius):
collected.append(item)
return collected
# Draw a pulsating effect for game over text
def pulsating_color(base_color, time):
r = min(255, base_color[0] + int(40 * math.sin(time * 5)))
g = max(0, base_color[1] - int(40 * math.sin(time * 5)))
b = base_color[2]
return (r, g, b)
# Spawn a single obstacle at random valid position (avoids player)
def spawn_obstacle(obstacles, player):
attempts = 0
while attempts < 100: # Prevent infinite loop
x = random.randint(0, SCREEN_WIDTH - OBSTACLE_WIDTH)
y = random.randint(0, SCREEN_HEIGHT - OBSTACLE_HEIGHT)
if is_valid_spawn_position(x, y, obstacles, player, is_obstacle=True):
new_obs = Obstacle(x, y)
obstacles.append(new_obs)
return True
attempts += 1
return False # Failed to spawn
# Initial obstacles (fewer to start, let system build up; avoids player)
def generate_initial_obstacles(obstacles, player):
for _ in range(4):
spawn_obstacle(obstacles, player)
return obstacles
# Main game function
def main():
# Start time
start_time = time.time()
last_time = start_time
last_item_spawn_time = start_time * 1000 # In milliseconds
last_obstacle_spawn_time = start_time * 1000
next_obstacle_spawn_interval = random.randint(OBSTACLE_SPAWN_MIN_MS, OBSTACLE_SPAWN_MAX_MS)
# Item score (from coins/bombs)
item_score = 0
# Create player first for initial obstacle generation
player = Player(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
# Generate initial obstacles (avoids player)
obstacles = []
generate_initial_obstacles(obstacles, player)
# Create enemy
enemy = Enemy(100, 100) # Start enemy in top-left corner
# Items list
items = []
running = True
game_over = False
while running:
current_time = time.time()
current_ms = current_time * 1000
# Handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
if game_over and event.key == pygame.K_SPACE:
# Restart game
start_time = time.time()
last_time = start_time
last_item_spawn_time = start_time * 1000
last_obstacle_spawn_time = start_time * 1000
next_obstacle_spawn_interval = random.randint(OBSTACLE_SPAWN_MIN_MS, OBSTACLE_SPAWN_MAX_MS)
item_score = 0
player = Player(SCREEN_WIDTH // 2, SCREEN_HEIGHT // 2)
enemy = Enemy(100, 100)
items = []
obstacles = []
generate_initial_obstacles(obstacles, player)
game_over = False
if not game_over:
# Update timer
last_time = current_time
# Manage obstacles: Despawn old ones
obstacles = [obs for obs in obstacles if not obs.should_despawn(current_time)]
# Spawn new obstacles randomly if below max (avoids player)
if len(obstacles) < MAX_OBSTACLES and (current_ms - last_obstacle_spawn_time > next_obstacle_spawn_interval):
if spawn_obstacle(obstacles, player):
last_obstacle_spawn_time = current_ms
next_obstacle_spawn_interval = random.randint(OBSTACLE_SPAWN_MIN_MS, OBSTACLE_SPAWN_MAX_MS)
# Spawn items (coins and bombs) randomly every SPAWN_INTERVAL ms (avoids player and obstacles)
if current_ms - last_item_spawn_time > SPAWN_INTERVAL:
attempts = 0
spawned = False
while attempts < 50 and not spawned:
x = random.randint(ITEM_RADIUS + 20, SCREEN_WIDTH - ITEM_RADIUS - 20)
y = random.randint(ITEM_RADIUS + 20, SCREEN_HEIGHT - ITEM_RADIUS - 20)
if is_valid_spawn_position(x, y, obstacles, player):
# 60% chance for coin, 40% for bomb (favoring coins as requested)
item_type = 'coin' if random.random() < 0.6 else 'bomb'
items.append(Item(x, y, item_type))
spawned = True
attempts += 1
last_item_spawn_time = current_ms
# Player movement with arrow keys
keys = pygame.key.get_pressed()
dx = 0
dy = 0
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
dx = -1
if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
dx = 1
if keys[pygame.K_UP] or keys[pygame.K_w]:
dy = -1
if keys[pygame.K_DOWN] or keys[pygame.K_s]:
dy = 1
player.move(dx, dy, obstacles)
# Update enemy
enemy.update(player.x, player.y, obstacles)
# Update and check items
# Despawn old items
items = [item for item in items if not item.should_despawn(current_time)]
# Check collections
collected = check_item_collection(player, items)
for item in collected:
if item.type == 'coin':
item_score += 5
elif item.type == 'bomb':
item_score -= 5
if item in items: # Safe removal
items.remove(item)
# Check collision with enemy
if check_collision(player, enemy):
game_over = True
# Calculate survival time
if not game_over:
survival_time = int(current_time - start_time)
else:
survival_time = int(last_time - start_time)
# Calculate total score: item_score + (survival_time * multiplier)
total_score = item_score + (survival_time * SURVIVAL_SCORE_MULTIPLIER)
# Draw everything
screen.fill(BLACK)
# Draw subtle grid background for depth
for x in range(0, SCREEN_WIDTH, 40):
pygame.draw.line(screen, (20, 20, 30), (x, 0), (x, SCREEN_HEIGHT), 1)
for y in range(0, SCREEN_HEIGHT, 40):
pygame.draw.line(screen, (20, 20, 30), (0, y), (SCREEN_WIDTH, y), 1)
# Draw obstacles
for obs in obstacles:
pygame.draw.rect(screen, DARK_GRAY, obs.rect)
pygame.draw.rect(screen, LIGHT_GRAY, obs.rect, 2)
# Draw items (coins and bombs)
for item in items:
item.draw(screen)
# Draw player and enemy
player.draw(screen)
enemy.draw(screen)
# Draw survival time, item score, and total score
time_text = font.render(f"Time: {survival_time}s", True, WHITE)
item_score_text = font.render(f"Item Score: {item_score}", True, WHITE)
total_score_text = font.render(f"Total Score: {total_score}", True, WHITE)
obstacles_count = small_font.render(f"Obstacles: {len(obstacles)}", True, WHITE)
screen.blit(time_text, (10, 10))
screen.blit(item_score_text, (10, 50))
screen.blit(total_score_text, (10, 90))
screen.blit(obstacles_count, (10, 130))
if game_over:
# Game over screen with pulsating text
pulse_color = pulsating_color(RED, current_time)
game_over_text = font.render("GAME OVER", True, pulse_color)
survived_text = font.render(f"You survived for {survival_time} seconds", True, WHITE)