cdec/water-game.py

162 lines
6.9 KiB
Python
Raw Normal View History

2025-10-11 16:26:27 +02:00
# Simple top-down shooter with WASD player, arrow-key enemy, health bar, minimap, and raster background
import pygame, sys, numpy as np, rasterio
2025-10-11 14:12:58 +02:00
pygame.init()
2025-10-11 15:40:28 +02:00
WIDTH, HEIGHT = 800, 600
2025-10-11 14:12:58 +02:00
WIN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Water Bottle Shooter")
2025-10-11 16:26:27 +02:00
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"
MAP_PATH = "results/s2_2025.tif"
2025-10-11 14:12:58 +02:00
2025-10-11 16:26:27 +02:00
# 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))
# Load raster map
def load_map(path):
2025-10-11 14:12:58 +02:00
try:
2025-10-11 16:26:27 +02:00
arr = rasterio.open(path).read(1)
arr = np.nan_to_num(arr - 272.15, nan=0)
2025-10-11 14:12:58 +02:00
arr = np.clip(arr, np.percentile(arr, 5), np.percentile(arr, 95))
2025-10-11 16:26:27 +02:00
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)
2025-10-11 14:12:58 +02:00
surf = pygame.surfarray.make_surface(arr)
2025-10-11 16:26:27 +02:00
return surf, arr.shape[1], arr.shape[0]
2025-10-11 14:12:58 +02:00
except Exception as e:
2025-10-11 16:26:27 +02:00
print(f"Map load error: {e}"); return None, WIDTH, HEIGHT
map_surface, MAP_W, MAP_H = load_map(MAP_PATH)
2025-10-11 14:12:58 +02:00
class Player:
def __init__(self, x, y):
2025-10-11 16:26:27 +02:00
self.x, self.y, self.alive = x, y, True
self.rect = pygame.Rect(x, y, PLAYER_SIZE, PLAYER_SIZE)
self.dir = "up"
2025-10-11 14:12:58 +02:00
def move(self, keys):
2025-10-11 16:26:27 +02:00
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))
2025-10-11 14:12:58 +02:00
self.rect.topleft = (self.x, self.y)
2025-10-11 16:26:27 +02:00
def draw(self, win, ox, oy):
if not self.alive: return
img = PLAYER_IMAGE or None
if img: win.blit(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))
2025-10-11 14:12:58 +02:00
2025-10-11 15:40:28 +02:00
class Enemy:
def __init__(self, x, y):
2025-10-11 16:26:27 +02:00
self.x, self.y = x, y
self.rect = pygame.Rect(x, y, PLAYER_SIZE//2, PLAYER_SIZE//2)
self.dir = "up"
self.max_health, self.health = 100, 100
2025-10-11 15:40:28 +02:00
def move(self, keys):
2025-10-11 16:26:27 +02:00
dx = dy = 0
if keys[pygame.K_UP]: dy -= PLAYER_SPEED; self.dir = "up"
if keys[pygame.K_DOWN]: dy += PLAYER_SPEED; self.dir = "down"
if keys[pygame.K_LEFT]: dx -= PLAYER_SPEED; self.dir = "left"
if keys[pygame.K_RIGHT]: dx += PLAYER_SPEED; 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))
2025-10-11 15:40:28 +02:00
self.rect.topleft = (self.x, self.y)
2025-10-11 16:26:27 +02:00
def draw(self, win, ox, oy):
img = ENEMY_IMAGE or None
if img: win.blit(img, (self.x-ox, self.y-oy))
else: pygame.draw.rect(win, (200,200,0), (self.x-ox, self.y-oy, PLAYER_SIZE//2, PLAYER_SIZE//2))
# Health bar
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):
self.health = max(0, self.health-amt)
def check_kill_player(self, player):
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
2025-10-11 14:12:58 +02:00
class WaterBottle:
2025-10-11 16:26:27 +02:00
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
2025-10-11 14:12:58 +02:00
self.rect = pygame.Rect(self.x, self.y, WATER_SIZE, WATER_SIZE)
def move(self):
2025-10-11 16:26:27 +02:00
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
2025-10-11 14:12:58 +02:00
self.rect.topleft = (self.x, self.y)
2025-10-11 16:26:27 +02:00
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, enemy, 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)
pygame.draw.circle(win, (255,0,0), map2mini(enemy.x+PLAYER_SIZE//4, enemy.y+PLAYER_SIZE//4), 5)
for b in bottles:
pygame.draw.circle(win, (0,180,255), map2mini(b.x, b.y), 3)
2025-10-11 14:12:58 +02:00
def main():
2025-10-11 16:26:27 +02:00
player = Player(MAP_W//2, MAP_H//2)
enemy = Enemy(MAP_W//3, MAP_H//3)
bottles = []
minimap_visible = True
clock = pygame.time.Clock()
2025-10-11 14:12:58 +02:00
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
2025-10-11 16:26:27 +02:00
if event.type==pygame.QUIT: running=False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_SPACE: bottles.append(WaterBottle(player.x, player.y, player.dir))
if event.key==pygame.K_m: minimap_visible = not minimap_visible
2025-10-11 14:12:58 +02:00
keys = pygame.key.get_pressed()
player.move(keys)
2025-10-11 15:40:28 +02:00
enemy.move(keys)
2025-10-11 16:26:27 +02:00
enemy.check_kill_player(player)
for b in bottles[:]:
b.move()
if enemy.rect.colliderect(b.rect): enemy.take_damage(20); bottles.remove(b)
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)
enemy.draw(WIN, ox, oy)
if minimap_visible: draw_minimap(WIN, map_surface, player, enemy, bottles)
2025-10-11 14:12:58 +02:00
pygame.display.update()
2025-10-11 16:26:27 +02:00
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()