SAMPLE CODE :
# top_down_shooter_advanced.py
"""
Top-Down Shooter — Advanced (Pygame)
Features:
- Multiple levels with waves
- Boss every N levels with patterns
- Enemy behaviours: seeker, zigzag, ranged
- Weapons: single, spread, rapid-fire (switch with Q)
- Powerups (health, weapon upgrade)
- Persistent high-score saved to [Link]
- No external assets required (shapes only)
Author: ChatGPT (for Griesh)
"""
import pygame
import random
import math
import sys
import json
import os
from collections import deque
# ---------------- CONFIG ----------------
WIDTH, HEIGHT = 900, 650
FPS = 60
PLAYER_COLOR = (50, 200, 255)
PLAYER_RADIUS = 16
PLAYER_SPEED = 4.2
PLAYER_MAX_HEALTH = 6
BULLET_COLOR = (255, 220, 80)
BULLET_SPEED = 12
BULLET_RADIUS = 4
BULLET_COOLDOWN = 160 # base ms between shots (modified by weapon)
ENEMY_COLOR = (220, 80, 80)
ENEMY_RADIUS = 14
ENEMY_BASE_SPEED = 1.2
POWERUP_COLOR = (80, 220, 120)
POWERUP_RADIUS = 10
SCORES_FILE = "[Link]"
MAX_SCORES = 10
# Level/wave config
WAVES_PER_LEVEL = 4
ENEMIES_BASE_PER_WAVE = 5
BOSS_EVERY = 3 # boss appears every 3 levels
# Weapons config
WEAPONS = ["Single", "Spread", "Rapid"]
WEAPON_SETTINGS = {
"Single": {"cooldown": 160, "bullets": 1, "spread_deg": 0},
"Spread": {"cooldown": 380, "bullets": 5, "spread_deg": 40},
"Rapid": {"cooldown": 80, "bullets": 1, "spread_deg": 0},
# UI
FONT_NAME = None
# ----------------------------------------
[Link]()
screen = [Link].set_mode((WIDTH, HEIGHT))
[Link].set_caption("Top-Down Shooter — Advanced")
clock = [Link]()
font = [Link](FONT_NAME, 20)
big_font = [Link](FONT_NAME, 48)
# ---------------- Helpers ----------------
def clamp(v, a, b):
return max(a, min(b, v))
def dist(a, b):
return [Link](a[0]-b[0], a[1]-b[1])
def normalize(dx, dy):
d = [Link](dx, dy)
if d == 0:
return 0,0
return dx/d, dy/d
def load_scores():
if not [Link](SCORES_FILE):
return []
try:
with open(SCORES_FILE, "r") as f:
return [Link](f)
except Exception:
return []
def save_scores(scores):
scores = sorted(scores, key=lambda e: (-e["score"], e["time"]))[:MAX_SCORES]
with open(SCORES_FILE, "w") as f:
[Link](scores, f, indent=2)
def add_score(name, score, time_seconds):
s = load_scores()
[Link]({"name": name, "score": score, "time": time_seconds, "date": [Link].get_ticks()})
save_scores(s)
# --------------- Game classes ---------------
class Player:
def __init__(self, x, y):
self.x = x; self.y = y
[Link] = PLAYER_RADIUS
[Link] = PLAYER_SPEED
self.max_health = PLAYER_MAX_HEALTH
[Link] = self.max_health
self.last_shot = 0
self.auto_fire = False
self.weapon_index = 0 # index in WEAPONS
@property
def weapon(self):
return WEAPONS[self.weapon_index]
def switch_weapon(self):
self.weapon_index = (self.weapon_index + 1) % len(WEAPONS)
def can_shoot(self):
w = [Link]
cooldown = WEAPON_SETTINGS[w]["cooldown"]
return [Link].get_ticks() - self.last_shot >= cooldown
def shoot(self, target_pos):
if not self.can_shoot():
return []
self.last_shot = [Link].get_ticks()
w = [Link]
cfg = WEAPON_SETTINGS[w]
bullets = []
dx = target_pos[0] - self.x
dy = target_pos[1] - self.y
base_angle = [Link](math.atan2(dy, dx))
count = cfg["bullets"]
spread = cfg["spread_deg"]
if count == 1:
angle = [Link](base_angle)
nx, ny = [Link](angle), [Link](angle)
[Link](Bullet(self.x + nx*([Link]+6), self.y + ny*([Link]+6), nx*BULLET_SPEED,
ny*BULLET_SPEED))
else:
# spread centered at base_angle
start = base_angle - spread/2
for i in range(count):
ang = [Link](start + (spread/(count-1))*i)
nx, ny = [Link](ang), [Link](ang)
[Link](Bullet(self.x + nx*([Link]+6), self.y + ny*([Link]+6), nx*BULLET_SPEED,
ny*BULLET_SPEED))
return bullets
def move(self, keys):
dx = dy = 0
if keys[pygame.K_w] or keys[pygame.K_UP]:
dy -= 1
if keys[pygame.K_s] or keys[pygame.K_DOWN]:
dy += 1
if keys[pygame.K_a] or keys[pygame.K_LEFT]:
dx -= 1
if keys[pygame.K_d] or keys[pygame.K_RIGHT]:
dx += 1
if dx != 0 or dy != 0:
nx, ny = normalize(dx, dy)
self.x += nx * [Link]
self.y += ny * [Link]
# clamp
self.x = clamp(self.x, [Link], WIDTH - [Link])
self.y = clamp(self.y, [Link], HEIGHT - [Link])
def draw(self, surf):
[Link](surf, PLAYER_COLOR, (int(self.x), int(self.y)), [Link])
# health bar
for i in range(self.max_health):
hx = 10 + i*20
rect = (hx, 10, 16, 12)
if i < [Link]:
[Link](surf, (200,30,30), rect)
else:
[Link](surf, (60,60,60), rect, 2)
# weapon label
[Link]([Link](f"Weapon: {[Link]}", True, (230,230,230)), (10, 30))
class Bullet:
def __init__(self, x, y, vx, vy, owner="player"):
self.x = x; self.y = y
[Link] = vx; [Link] = vy
[Link] = BULLET_RADIUS
[Link] = False
[Link] = owner
[Link] = 1 if owner == "player" else 1
def update(self):
self.x += [Link]
self.y += [Link]
if self.x < -30 or self.x > WIDTH + 30 or self.y < -30 or self.y > HEIGHT + 30:
[Link] = True
def draw(self, surf):
[Link](surf, BULLET_COLOR, (int(self.x), int(self.y)), [Link])
class Enemy:
"""
behaviors:
- seeker: always move toward player
- zigzag: move toward player but with oscillation
- ranged: keeps distance and shoots at player
- boss: big, multiple attacks & phases
"""
def __init__(self, x, y, kind="seeker", level=1):
self.x = x; self.y = y
[Link] = kind
[Link] = level
[Link] = ENEMY_BASE_SPEED + 0.08*level
[Link] = ENEMY_RADIUS + (0 if kind!="boss" else 10)
# HP depends on kind
base_hp = {"seeker":1, "zigzag":1, "ranged":1, "boss":10 + level*3}
[Link] = base_hp.get(kind, 1)
[Link] = False
self.last_shot = 0
self.shoot_interval = max(700 - level*20, 400) # for ranged
# zigzag params
self.zig_t = [Link]()*1000
self.zig_amp = 40 + level*2
self.zig_freq = 0.02 + level*0.005
def update(self, player, bullets):
if [Link] == "seeker":
dx = player.x - self.x; dy = player.y - self.y
nx, ny = normalize(dx, dy)
self.x += nx * [Link]
self.y += ny * [Link]
elif [Link] == "zigzag":
dx = player.x - self.x; dy = player.y - self.y
nx, ny = normalize(dx, dy)
# perpendicular oscillation
perp_x, perp_y = -ny, nx
t = self.zig_t
osc = [Link](t * self.zig_freq) * (self.zig_amp/(1 + [Link]*0.1))
self.x += nx * [Link] + perp_x * osc * 0.01
self.y += ny * [Link] + perp_y * osc * 0.01
self.zig_t += 1
elif [Link] == "ranged":
# try to maintain some distance
dx = player.x - self.x; dy = player.y - self.y
d = [Link](dx, dy)
if d > 260:
nx, ny = normalize(dx, dy)
self.x += nx * [Link]
self.y += ny * [Link]
elif d < 180:
nx, ny = normalize(dx, dy)
self.x -= nx * [Link] # back off
self.y -= ny * [Link]
# shoot if possible
now = [Link].get_ticks()
if now - self.last_shot >= self.shoot_interval:
self.last_shot = now
# shoot a bullet toward player
nx, ny = normalize(dx, dy)
bx = self.x + nx*([Link]+6); by = self.y + ny*([Link]+6)
[Link](Bullet(bx, by, nx*6, ny*6, owner="enemy"))
elif [Link] == "boss":
# more complex boss: moves slowly, large HP; occasionally fires many shots
dx = player.x - self.x; dy = player.y - self.y
nx, ny = normalize(dx, dy)
# slow chase plus small random motion
self.x += nx * ([Link]*0.4) + [Link](-0.8,0.8)
self.y += ny * ([Link]*0.4) + [Link](-0.8,0.8)
# boss attacks: bursts + radial
now = [Link].get_ticks()
if now % 3500 < 60: # radial burst occasionally
for a_deg in range(0,360,30):
a = [Link](a_deg + [Link](-6,6))
[Link](Bullet(self.x + [Link](a)*([Link]+6), self.y + [Link](a)*([Link]+6),
[Link](a)*5, [Link](a)*5, owner="enemy"))
if now - self.last_shot >= 900:
self.last_shot = now
# aimed triple shot
dx = player.x - self.x; dy = player.y - self.y
base = [Link](math.atan2(dy, dx))
for deg in (-8, 0, 8):
ang = [Link](base + deg)
nx, ny = [Link](ang), [Link](ang)
[Link](Bullet(self.x + nx*([Link]+6), self.y + ny*([Link]+6), nx*6, ny*6,
owner="enemy"))
def draw(self, surf):
col = ENEMY_COLOR if [Link] != "boss" else (200, 80, 200)
[Link](surf, col, (int(self.x), int(self.y)), [Link])
# small HP bar for boss
if [Link] == "boss":
w = 80
pct = clamp([Link] / (10 + [Link]*3), 0, 1)
[Link](surf, (60,60,60), (int(self.x - w/2), int(self.y - [Link] - 12), w, 8))
[Link](surf, (200,40,40), (int(self.x - w/2), int(self.y - [Link] - 12), int(w*pct), 8))
class Powerup:
def __init__(self, x, y, kind="health"):
self.x = x; self.y = y
[Link] = kind # 'health' or 'weapon'
[Link] = POWERUP_RADIUS
[Link] = False
self.spawn_time = [Link].get_ticks()
def draw(self, surf):
if [Link] == "health":
[Link](surf, POWERUP_COLOR, (int(self.x)-8, int(self.y)-8, 16, 16))
[Link](surf, (255,255,255), (self.x-3, self.y), (self.x+3, self.y), 2)
[Link](surf, (255,255,255), (self.x, self.y-3), (self.x, self.y+3), 2)
else:
# weapon
[Link](surf, (255,200,60), (int(self.x), int(self.y)), 10)
[Link]([Link]("W", True, (20,20,20)), (int(self.x)-6, int(self.y)-8))
# --------------- Game flow & spawn ---------------
def spawn_enemy(level):
kind = [Link](["seeker","zigzag","ranged"], weights=[0.5,0.25,0.25])[0]
side = [Link](["top","bottom","left","right"])
if side == "top":
x = [Link](0, WIDTH); y = -20
elif side == "bottom":
x = [Link](0, WIDTH); y = HEIGHT + 20
elif side == "left":
x = -20; y = [Link](0, HEIGHT)
else:
x = WIDTH + 20; y = [Link](0, HEIGHT)
return Enemy(x, y, kind=kind, level=level)
def spawn_boss(level):
# boss spawn at random near edge
x = WIDTH//2 + [Link](-100,100)
y = -120
return Enemy(x, y, kind="boss", level=level)
# ---------------- Main game ----------------
def draw_text_center(surf, text, y, size=36, color=(240,240,240)):
f = [Link](FONT_NAME, size)
[Link]([Link](text, True, color), [Link](text, True, color).get_rect(center=(WIDTH//2, y)))
def main_game():
player = Player(WIDTH//2, HEIGHT//2)
bullets = []
enemies = []
powerups = []
score = 0
level = 1
wave = 1
spawn_timer = 0
enemies_spawned_this_wave = 0
enemies_to_spawn = ENEMIES_BASE_PER_WAVE + (level-1)*2
wave_in_progress = True
game_over = False
start_time = [Link].get_ticks()
while True:
dt = [Link](FPS)
now = [Link].get_ticks()
for event in [Link]():
if [Link] == [Link]:
[Link](); [Link]()
if [Link] == [Link]:
if [Link] == pygame.K_ESCAPE:
return ("menu", None)
if [Link] == pygame.K_SPACE:
player.auto_fire = not player.auto_fire
if [Link] == pygame.K_q:
player.switch_weapon()
if [Link] == pygame.K_r and game_over:
return ("restart", None)
if [Link] == [Link]:
if [Link] == 1 and not game_over:
new = [Link]([Link].get_pos())
[Link](new)
keys = [Link].get_pressed()
if not game_over:
[Link](keys)
# auto fire
if player.auto_fire:
new = [Link]([Link].get_pos())
[Link](new)
# spawn logic
if BOSS_EVERY and level % BOSS_EVERY == 0 and wave == WAVES_PER_LEVEL + 1 and not
any([Link] == "boss" for e in enemies):
# spawn boss
[Link](spawn_boss(level))
wave_in_progress = True
else:
# normal waves
spawn_interval = max(280, 1000 - level*40)
if enemies_spawned_this_wave < enemies_to_spawn and now - spawn_timer > spawn_interval:
[Link](spawn_enemy(level))
enemies_spawned_this_wave += 1
spawn_timer = now
# update bullets
for b in bullets:
[Link]()
bullets = [b for b in bullets if not [Link]]
# update enemies
for e in enemies:
[Link](player, bullets)
# collisions: bullets vs enemies
for b in bullets:
if [Link] == "player":
for e in enemies:
if not [Link] and dist((b.x,b.y),(e.x,e.y)) < [Link] + [Link]:
[Link] -= [Link]
[Link] = True
if [Link] <= 0:
[Link] = True
# score depends on kind
score += 10 if [Link] != "boss" else 200 + level*30
# chance to drop powerup
if [Link]() < 0.18:
kind = "health" if [Link]() < 0.6 else "weapon"
[Link](Powerup(e.x, e.y, kind=kind))
enemies = [e for e in enemies if not [Link]]
# bullets from enemy hitting player
for b in bullets:
if [Link] == "enemy" and dist((b.x,b.y),(player.x, player.y)) < [Link] + [Link]:
[Link] = True
[Link] -= 1
if [Link] <= 0:
game_over = True
end_time = [Link].get_ticks()
total_time = (end_time - start_time) // 1000
# enemies colliding melee with player
for e in enemies:
if dist((e.x,e.y),(player.x,player.y)) < [Link] + [Link] - 6:
# damage player and destroy enemy (or hurt boss)
[Link] -= 1
if [Link] != "boss":
[Link] = True
score += 5
else:
[Link] -= 2
if [Link] <= 0:
[Link] = True
score += 200 + level*30
if [Link] <= 0:
game_over = True
end_time = [Link].get_ticks()
total_time = (end_time - start_time) // 1000
# pickups
for p in powerups:
if dist((p.x,p.y),(player.x,player.y)) < [Link] + [Link]:
if [Link] == "health":
[Link] = min(player.max_health, [Link] + 2)
else:
# swap to next weapon and give a brief cooldown reset
player.switch_weapon()
player.last_shot = 0
[Link] = True
powerups = [p for p in powerups if not [Link] and [Link].get_ticks() - p.spawn_time < 20_000]
# wave completion
if not enemies and enemies_spawned_this_wave >= enemies_to_spawn:
if wave < WAVES_PER_LEVEL:
wave += 1
enemies_spawned_this_wave = 0
enemies_to_spawn = ENEMIES_BASE_PER_WAVE + (level-1)*2 + (wave-1)*2
else:
# level complete
level += 1
wave = 1
enemies_spawned_this_wave = 0
enemies_to_spawn = ENEMIES_BASE_PER_WAVE + (level-1)*2
# small heal between levels
[Link] = min(player.max_health, [Link] + 1)
# DRAW
[Link]((12,12,20))
# background grid
for gx in range(0, WIDTH, 48):
[Link](screen, (16,16,26), (gx,0), (gx, HEIGHT))
for gy in range(0, HEIGHT, 48):
[Link](screen, (16,16,26), (0,gy), (WIDTH, gy))
# draw entities
for b in bullets:
[Link](screen)
for e in enemies:
[Link](screen)
for p in powerups:
[Link](screen)
[Link](screen)
# HUD
[Link]([Link](f"Score: {score}", True, (230,230,230)), (WIDTH-170, 10))
[Link]([Link](f"Level: {level} Wave: {wave}/{WAVES_PER_LEVEL}", True, (230,230,230)),
(WIDTH-320, 10))
[Link]([Link](f"Weapon: {[Link]} AutoFire: {'ON' if player.auto_fire else 'OFF'}", True,
(210,210,210)), (10, HEIGHT-28))
# show remaining enemies count
[Link]([Link](f"Enemies: {len(enemies)}", True, (210,210,210)), (WIDTH-320, 34))
if game_over:
overlay = [Link]((WIDTH, HEIGHT), [Link])
[Link]((0,0,0,180))
[Link](overlay, (0,0))
draw_text_center(screen, "GAME OVER", HEIGHT//2 - 80, size=64, color=(255,100,100))
draw_text_center(screen, f"Score: {score}", HEIGHT//2 - 20, size=36)
draw_text_center(screen, "Press R to Restart or ESC to Menu", HEIGHT//2 + 40, size=22)
[Link]()
# wait for player entry of name to save score
# simple keyboard loop to type name, only if new high score
entries = load_scores()
best_scores = sorted(entries, key=lambda e: -e["score"])
is_high = len(entries) < MAX_SCORES or score > best_scores[-1]["score"] if entries else True
if is_high:
name = ""
entry_done = False
while not entry_done:
for ev in [Link]():
if [Link] == [Link]:
[Link](); [Link]()
if [Link] == [Link]:
if [Link] == pygame.K_RETURN:
entry_done = True
elif [Link] == pygame.K_BACKSPACE:
name = name[:-1]
elif [Link] == pygame.K_ESCAPE:
entry_done = True
else:
ch = [Link]
if [Link]() and len(name) < 12:
name += ch
# show name entry overlay
overlay = [Link]((WIDTH, HEIGHT), [Link])
[Link]((0,0,0,220))
[Link](overlay, (0,0))
draw_text_center(screen, "NEW HIGH SCORE! Enter name and press Enter", HEIGHT//2 - 40,
size=28)
[Link]([Link](name + ("|" if int([Link].get_ticks()/400)%2==0 else ""), True,
(255,255,255)), (WIDTH//2 - 100, HEIGHT//2 + 10))
[Link]()
[Link](FPS)
if [Link]():
add_score([Link](), score, ([Link].get_ticks() - start_time)//1000)
# wait until R or ESC pressed
waiting = True
while waiting:
for ev in [Link]():
if [Link] == [Link]:
[Link](); [Link]()
if [Link] == [Link]:
if [Link] == pygame.K_r:
return ("restart", None)
if [Link] == pygame.K_ESCAPE:
return ("menu", None)
[Link](FPS)
[Link]()
# ---------------- Title & Scoreboard ----------------
def title_screen():
while True:
[Link]((8,8,18))
draw_text_center(screen, "TOP-DOWN SHOOTER - ADVANCED", HEIGHT//2 - 100, size=48)
draw_text_center(screen, "WASD to move Mouse to aim/fire Q to switch weapon Space toggle autofire",
HEIGHT//2 - 20, size=18)
draw_text_center(screen, "Press ENTER to Play | H to view High Scores | Q to Quit", HEIGHT//2 +
40, size=20)
[Link]()
for ev in [Link]():
if [Link] == [Link]:
[Link](); [Link]()
if [Link] == [Link]:
if [Link] == pygame.K_RETURN:
res = main_game()
if res and res[0] == "restart":
continue
if [Link] == pygame.K_h:
show_scores()
if [Link] == pygame.K_q or [Link] == pygame.K_ESCAPE:
[Link](); [Link]()
[Link](FPS)
def show_scores():
entries = load_scores()
entries = sorted(entries, key=lambda e: -e["score"])
showing = True
while showing:
[Link]((6,6,20))
draw_text_center(screen, "HIGH SCORES", 60, size=48)
y = 120
for i, e in enumerate(entries[:MAX_SCORES], start=1):
line = f"{i:2}. {e['name'][:12]:12} Score:{e['score']:6} Time:{[Link]('time','?')}s"
[Link]([Link](line, True, (220,220,220)), (80, y))
y += 30
draw_text_center(screen, "Press any key to return", HEIGHT - 40, size=18)
[Link]()
for ev in [Link]():
if [Link] == [Link]:
[Link](); [Link]()
if [Link] == [Link] or [Link] == [Link]:
showing = False
[Link](FPS)
# ---------- Run ----------
if __name__ == "__main__":
title_screen()