# Simple top-down shooter with WASD player, arrow-key enemy, health bar, minimap, and raster background import pygame, sys, numpy as np, rasterio, random pygame.init() WIDTH, HEIGHT = 800, 600 WIN = pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption("Water Bottle Shooter") PLAYER_SIZE, PLAYER_SPEED = 40, 5 WATER_SIZE, WATER_SPEED = 10, 10 KILL_RADIUS = 30 MINIMAP_SIZE, MINIMAP_MARGIN = 200, 20 PLAYER_IMG_PATH = "images/player.png" ENEMY_IMG_PATH = "images/enemy.png" HUMAN_IMG_PATH = "images/human.png" MAP_PATH = "results/s2_2025.tif" # Load images def load_img(path, size): try: img = pygame.image.load(path).convert_alpha() return pygame.transform.scale(img, size) except: return None PLAYER_IMAGE = load_img(PLAYER_IMG_PATH, (PLAYER_SIZE, PLAYER_SIZE)) ENEMY_IMAGE = load_img(ENEMY_IMG_PATH, (PLAYER_SIZE, PLAYER_SIZE)) HUMAN_IMAGE = load_img(HUMAN_IMG_PATH, (PLAYER_SIZE, PLAYER_SIZE)) # Load raster map def load_map(path): try: arr = rasterio.open(path).read(1) arr = np.nan_to_num(arr - 272.15, nan=0) arr = np.clip(arr, np.percentile(arr, 5), np.percentile(arr, 95)) arr = ((arr - arr.min()) / (arr.max() - arr.min()) * 255).astype(np.uint8) arr = np.stack([arr, arr, arr], -1) arr = np.rot90(arr, 3) arr = np.fliplr(arr) surf = pygame.surfarray.make_surface(arr) return surf, arr.shape[1], arr.shape[0] except Exception as e: print(f"Map load error: {e}"); return None, WIDTH, HEIGHT map_surface, MAP_W, MAP_H = load_map(MAP_PATH) class Player: def __init__(self, x, y): self.x, self.y, self.alive = x, y, True self.rect = pygame.Rect(x, y, PLAYER_SIZE, PLAYER_SIZE) self.dir = "up" def move(self, keys): if not self.alive: return dx = dy = 0 if keys[pygame.K_w]: dy -= PLAYER_SPEED; self.dir = "up" if keys[pygame.K_s]: dy += PLAYER_SPEED; self.dir = "down" if keys[pygame.K_a]: dx -= PLAYER_SPEED; self.dir = "left" if keys[pygame.K_d]: dx += PLAYER_SPEED; self.dir = "right" self.x = max(0, min(MAP_W-PLAYER_SIZE, self.x+dx)) self.y = max(0, min(MAP_H-PLAYER_SIZE, self.y+dy)) self.rect.topleft = (self.x, self.y) def draw(self, win, ox, oy): if not self.alive: return img = PLAYER_IMAGE or None angle = {"up": 90, "right": 0, "down": -90, "left": 180}[self.dir] if img: rotated_img = pygame.transform.rotate(img, angle) win.blit(rotated_img, (self.x-ox, self.y-oy)) else: pygame.draw.rect(win, (200,200,0), (self.x-ox, self.y-oy, PLAYER_SIZE, PLAYER_SIZE)) class Enemy: def __init__(self, x, y,controlled=False): self.x, self.y = x, y self.rect = pygame.Rect(x, y, PLAYER_SIZE, PLAYER_SIZE) self.dir = "up" self.max_health, self.health = 100, 100 self.is_human = False self.controlled = controlled def move(self, keys, player=None): if self.is_human: # Move randomly if random.random() < 0.02: self.dir = random.choice(["up", "down", "left", "right"]) dx = dy = 0 if self.dir == "up": dy -= PLAYER_SPEED//1.5 if self.dir == "down": dy += PLAYER_SPEED//2 if self.dir == "left": dx -= PLAYER_SPEED//2 if self.dir == "right": dx += PLAYER_SPEED//2 self.x = max(0, min(MAP_W-PLAYER_SIZE//2, self.x+dx)) self.y = max(0, min(MAP_H-PLAYER_SIZE//2, self.y+dy)) self.rect.topleft = (self.x, self.y) elif self.controlled: dx = dy = 0 if keys[pygame.K_UP]: dy -= PLAYER_SPEED//1.5; self.dir = "up" if keys[pygame.K_DOWN]: dy += PLAYER_SPEED//1.5; self.dir = "down" if keys[pygame.K_LEFT]: dx -= PLAYER_SPEED//1.5; self.dir = "left" if keys[pygame.K_RIGHT]: dx += PLAYER_SPEED//1.5; self.dir = "right" self.x = max(0, min(MAP_W-PLAYER_SIZE//2, self.x+dx)) self.y = max(0, min(MAP_H-PLAYER_SIZE//2, self.y+dy)) self.rect.topleft = (self.x, self.y) else: # Chase the player if player and player.alive: dx = player.x - self.x dy = player.y - self.y dist = max(1, (dx**2 + dy**2)**0.5) speed = PLAYER_SPEED//2 self.x += int(speed * dx / dist) self.y += int(speed * dy / dist) self.x = max(0, min(MAP_W-PLAYER_SIZE//2, self.x)) self.y = max(0, min(MAP_H-PLAYER_SIZE//2, self.y)) self.rect.topleft = (self.x, self.y) def draw(self, win, ox, oy): enemy_img = ENEMY_IMAGE or None human_img = HUMAN_IMAGE or None color = (0, 128, 255) if self.is_human else (200, 200, 0) if enemy_img and not self.is_human: win.blit(enemy_img, (self.x-ox, self.y-oy)) elif human_img and self.is_human: win.blit(human_img, (self.x-ox, self.y-oy)) else: pygame.draw.rect(win, color, (self.x-ox, self.y-oy, PLAYER_SIZE//2, PLAYER_SIZE//2)) # Health bar (only for zombie) if not self.is_human: bw, bh = PLAYER_SIZE//2, 6 ratio = self.health/self.max_health bx, by = self.x-ox, self.y-oy-bh-2 pygame.draw.rect(win, (255,0,0), (bx,by,bw,bh)) pygame.draw.rect(win, (0,255,0), (bx,by,int(bw*ratio),bh)) def take_damage(self, amt): if not self.is_human: self.health = max(0, self.health-amt) if self.health == 0: self.is_human = True def check_kill_player(self, player): if not self.is_human: dx = (self.x+PLAYER_SIZE//4)-(player.x+PLAYER_SIZE//2) dy = (self.y+PLAYER_SIZE//4)-(player.y+PLAYER_SIZE//2) if (dx**2+dy**2)**0.5 < KILL_RADIUS: player.alive = False class TapWater: def __init__(self, x, y, dir): self.x = x+PLAYER_SIZE//2-WATER_SIZE//2 self.y = y+PLAYER_SIZE//2-WATER_SIZE//2 self.dir = dir self.rect = pygame.Rect(self.x, self.y, WATER_SIZE, WATER_SIZE) def move(self): if self.dir=="up": self.y -= WATER_SPEED elif self.dir=="down": self.y += WATER_SPEED elif self.dir=="left": self.x -= WATER_SPEED elif self.dir=="right": self.x += WATER_SPEED self.rect.topleft = (self.x, self.y) def draw(self, win, ox, oy): pygame.draw.rect(win, (0,180,255), (self.x-ox, self.y-oy, WATER_SIZE, WATER_SIZE)) def draw_minimap(win, map_surface, player, enemies, bottles): mini = pygame.transform.smoothscale(map_surface, (MINIMAP_SIZE, MINIMAP_SIZE)) win.blit(mini, (WIDTH-MINIMAP_SIZE-MINIMAP_MARGIN, MINIMAP_MARGIN)) def map2mini(x,y): mx = int(x/MAP_W*MINIMAP_SIZE) my = int(y/MAP_H*MINIMAP_SIZE) return WIDTH-MINIMAP_SIZE-MINIMAP_MARGIN+mx, MINIMAP_MARGIN+my pygame.draw.circle(win, (0,255,0), map2mini(player.x+PLAYER_SIZE//2, player.y+PLAYER_SIZE//2), 5) for enemy in enemies: color = (0,128,255) if enemy.is_human else (255,0,0) if enemy.controlled else (200,200,0) ex, ey = map2mini(enemy.x+PLAYER_SIZE//4, enemy.y+PLAYER_SIZE//4) pygame.draw.circle(win, color, (ex, ey), 5) for b in bottles: pygame.draw.circle(win, (0,180,255), map2mini(b.x, b.y), 3) def main(): player = Player(MAP_W//2, MAP_H//2) # Spawn 20 zombies, only the first is controlled enemies = [] for i in range(20): x = random.randint(0, MAP_W-PLAYER_SIZE) y = random.randint(0, MAP_H-PLAYER_SIZE) controlled = (i == 0) enemies.append(Enemy(x, y, controlled=controlled)) bottles = [] minimap_visible = True clock = pygame.time.Clock() running = True while running: clock.tick(60) for event in pygame.event.get(): if event.type==pygame.QUIT: running=False if event.type==pygame.KEYDOWN: if event.key==pygame.K_SPACE: bottles.append(TapWater(player.x, player.y, player.dir)) if event.key==pygame.K_m: minimap_visible = not minimap_visible keys = pygame.key.get_pressed() player.move(keys) for enemy in enemies: enemy.move(keys, player) enemy.check_kill_player(player) for b in bottles[:]: for enemy in enemies: if enemy.rect.colliderect(b.rect): enemy.take_damage(20); bottles.remove(b); break bottles = [b for b in bottles if 0<=b.x<=MAP_W and 0<=b.y<=MAP_H] ox = max(0, min(player.x+PLAYER_SIZE//2-WIDTH//2, MAP_W-WIDTH)) oy = max(0, min(player.y+PLAYER_SIZE//2-HEIGHT//2, MAP_H-HEIGHT)) if map_surface: WIN.blit(map_surface, (0,0), area=pygame.Rect(ox,oy,WIDTH,HEIGHT)) else: WIN.fill((255,255,255)) player.draw(WIN, ox, oy) for b in bottles: b.draw(WIN, ox, oy) for enemy in enemies: enemy.draw(WIN, ox, oy) if minimap_visible: # Draw all enemies on minimap draw_minimap(WIN, map_surface, player, enemies, bottles) pygame.display.update() if not player.alive: font = pygame.font.SysFont(None, 48) text = font.render("Game Over!", True, (255,0,0)) WIN.blit(text, (WIDTH//2-text.get_width()//2, HEIGHT//2-text.get_height()//2)) pygame.display.update(); pygame.time.wait(2000); running=False pygame.quit(); sys.exit() if __name__=="__main__": main()