add tip list

This commit is contained in:
Aron Petau 2025-10-12 08:54:30 +02:00
parent 7b9d7734ab
commit c733cf64e1
12 changed files with 598 additions and 187 deletions

61
.gitignore vendored Normal file
View file

@ -0,0 +1,61 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*.so
*.pyd
*.pyo
# Virtual environments
.venv/
venv/
ENV/
# Jupyter Notebook checkpoints
.ipynb_checkpoints/
# Raster and geodata outputs
*.tif
*.npy
*.png
trinkbrunnen_pixel_positions.npy
trinkbrunnen_karte.png
# Matplotlib figures
*.pdf
*.svg
# OS files
.DS_Store
Thumbs.db
# Editor files
.vscode/
.idea/
# Logs
*.log
# Test and coverage
.coverage
htmlcov/
# Misc
*.bak
*.tmp
# Ignore results folder except source data
results/
!results/s2_2025.tif
# Ignore geodata except source files
geodata/
!geodata/Trinkbrunnen_Berlin.geojson
# PyInstaller build outputs
build/
dist/
*.spec
# Executables
*.exe
*.app

View file

@ -0,0 +1,30 @@
# Trinkbrunnen Berlin Top-Down Spiel
Dieses Projekt ist ein einfaches Top-Down-Spiel mit einer echten Satellitenkarte von Berlin als Hintergrund. Die Trinkbrunnen-Standorte werden aus einer GeoJSON-Datei geladen und auf der Karte angezeigt. Du kannst die Brunnen im Spiel nutzen und mit ihnen interagieren.
## Schnellstart
1. Python 3 installieren
2. Abhängigkeiten installieren:
```
pip install -r requirements.txt
```
3. Spiel starten:
```
python water-game.py
```
## Features
- Berlin-Satellitenkarte als Spielfeld
- Trinkbrunnen werden korrekt angezeigt
- Interaktion mit Trinkbrunnen möglich
- Minimap und Gegner
## Datenquellen
- `geodata/s2_2025.tif`: Satellitenbild Berlin
- `geodata/Trinkbrunnen_Berlin.geojson`: Trinkbrunnen-Standorte

174
README_TIPS.md Normal file
View file

@ -0,0 +1,174 @@
# Tipps & Hinweise
## Titelbild und Logo einfügen
- Ein Logo macht das Spiel persönlicher! Die Funktion zum Laden eines Logos ist im Code vorbereitet, aber aktuell deaktiviert.
- Die Kids können später ein eigenes Logo als `images/logo.png` erstellen und die folgende Zeile in `water-game.py` aktivieren:
```python
# logo_img = pygame.image.load("images/logo.png")
# pygame.display.set_icon(logo_img)
```
- Tipp: Das Logo sollte quadratisch und als PNG gespeichert sein.
- So kann jeder sein eigenes Spiel-Icon gestalten!
## Titelbildschirm, Gewinn- und Verlustbildschirm
- Implementiere einen Titelbildschirm und zeige bei Spielende einen Gewinn- oder Verlustbildschirm (siehe Codebeispiel unten).
**Beispiel für Titelbildschirm und Gewinn/Verlust:**
```python
# Titelbildschirm anzeigen
show_title_screen(WIN)
# ...
if win_condition:
show_win_screen(WIN)
elif lose_condition:
show_lose_screen(WIN)
```
**So kannst du Gewinn- und Verlustbildschirme umsetzen:**
1. Schreibe eine Funktion, die den Bildschirm einfärbt und einen Text anzeigt:
```python
def show_win_screen(win):
win.fill((40, 180, 80))
font = pygame.font.SysFont(None, 72)
text = font.render("Gewonnen!", True, (255, 255, 255))
win.blit(text, (win.get_width() // 2 - text.get_width() // 2, win.get_height() // 2 - text.get_height() // 2))
pygame.display.update()
pygame.time.wait(2500)
def show_lose_screen(win):
win.fill((180, 40, 40))
font = pygame.font.SysFont(None, 72)
text = font.render("Verloren!", True, (255, 255, 255))
win.blit(text, (win.get_width() // 2 - text.get_width() // 2, win.get_height() // 2 - text.get_height() // 2))
pygame.display.update()
pygame.time.wait(2500)
```
2. Rufe die jeweilige Funktion auf, wenn die Gewinn- oder Verlustbedingung erfüllt ist:
```python
if win_condition:
show_win_screen(WIN)
elif lose_condition:
show_lose_screen(WIN)
```
- Die Bedingungen kannst du selbst festlegen, z.B. alle Gegner besiegt = Gewinn, Spieler tot = Verlust.
- Nach dem Bildschirm kannst du das Spiel beenden oder neu starten.
## Gameplay-Tipp: Begrenzte Wasserflaschen & Auffüllen an Trinkbrunnen
- Implementiere eine Variable für die Anzahl der Wasserflaschen (Munition), z.B. `player.ammo`.
- Verringere `player.ammo` bei jedem Schuss (`SPACE`).
- Erlaube das Auffüllen der Munition, wenn der Spieler einen Trinkbrunnen-Pixel berührt (z.B. durch Kollisionsabfrage mit Trinkbrunnen-Positionen).
- Zeige die aktuelle Munition als Zahl oder Symbol im HUD an.
**Beispiel (Pseudo-Code):**
```python
# Beim Schießen:
if player.ammo > 0:
bottles.append(TapWater(player.x, player.y, player.dir))
player.ammo -= 1
# Beim Berühren eines Trinkbrunnens:
for brunnen in trinkbrunnen_list:
if player.rect.colliderect(brunnen.rect):
player.ammo = MAX_AMMO
```
- Die Trinkbrunnen-Positionen kannst du aus der GeoJSON oder aus einer Liste von Pixelkoordinaten laden.
- So wird das Sammeln und Nachfüllen von Wasser spielerisch relevant!
## .exe erstellen (Windows)
**Kurzanleitung auf Deutsch:**
1. Installiere PyInstaller:
```sh
pip install pyinstaller
```
2. Konvertiere dein Logo zu einer `.ico`-Datei (z.B. mit [favicon.io](https://favicon.io/)).
3. Erstelle die .exe:
```sh
pyinstaller --onefile --windowed --icon=images/logo.ico water-game.py
```
4. Die ausführbare Datei findest du im `dist`-Ordner.
## Mac: App als ausführbare Datei erstellen
**Kurzanleitung auf Deutsch:**
1. Installiere PyInstaller:
```sh
pip install pyinstaller
```
2. Erstelle die Mac-App:
```sh
pyinstaller --onefile --windowed --icon=images/logo.png water-game.py
```
- Das Icon kann als PNG verwendet werden.
- Die App wird im `dist`-Ordner als ausführbare Datei erscheinen.
3. (Optional) Um eine echte Mac-App zu erzeugen, nutze das Flag `--name` und `--osx-bundle-identifier`:
```sh
pyinstaller --onefile --windowed --icon=images/logo.png --name=WasserGame --osx-bundle-identifier=com.deinname.wassergame water-game.py
```
**Hinweis:**
- Die App kann per Doppelklick gestartet werden.
- Für die Verteilung an andere Macs kann eine Code-Signierung und Notarisierung nötig sein (siehe [Apple Doku](https://developer.apple.com/documentation/xcode/notarizing_macos_software_before_distribution)).
## Tipp: Intro-Video vor Spielstart abspielen
- Du kannst ein Video (z.B. MP4) als Intro vor dem eigentlichen Spiel abspielen.
- Nutze dazu z.B. die Bibliothek [`pygame-vlc`](https://github.com/pygame/pygame-vlc) oder [`opencv-python`](https://pypi.org/project/opencv-python/) zum Abspielen von Videos im Pygame-Fenster.
**Beispiel mit pygame-vlc:**
```python
import vlc
import pygame
def play_intro_video(win, video_path):
instance = vlc.Instance()
player = instance.media_player_new()
media = instance.media_new(video_path)
player.set_media(media)
player.set_xwindow(win.get_window_id()) # Für Linux, für Windows/Mac ggf. anpassen
player.play()
# Warte bis das Video fertig ist oder eine Taste gedrückt wird
playing = True
while playing:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN or event.type == pygame.QUIT:
player.stop()
playing = False
pygame.time.wait(100)
```
- Das Video kann z.B. als `intro.mp4` im Projektordner liegen.
- Nach dem Intro kannst du wie gewohnt den Titelbildschirm anzeigen.
- Für Windows/Mac kann die Fenster-ID Methode abweichen, siehe Doku von pygame-vlc.
## Zusätzliche Hinweise
- Stelle sicher, dass alle Bilder und Daten im richtigen Pfad liegen.
- Für komplexe Projekte nutze eine [PyInstaller spec file](https://pyinstaller.org/en/stable/spec-files.html).

48
align_trinkbrunnen.py Normal file
View file

@ -0,0 +1,48 @@
import rasterio
import geopandas as gpd
import numpy as np
import matplotlib.pyplot as plt
# --- CONFIG ---
raster_path = 'geodata/s2_2025.tif'
geojson_path = 'geodata/Trinkbrunnen_Berlin.geojson'
# --- LOAD RASTER ---
with rasterio.open(raster_path) as src:
map_img = src.read([1, 2, 3])
map_img = np.transpose(map_img, (1, 2, 0))
map_transform = src.transform
map_crs = src.crs
map_bounds = src.bounds
map_width = src.width
map_height = src.height
# --- LOAD GEOJSON ---
gdf = gpd.read_file(geojson_path)
if gdf.crs != map_crs:
gdf = gdf.to_crs(map_crs)
# --- PROJECT POINTS TO PIXELS ---
pixel_positions = []
for geom in gdf.geometry:
if geom and geom.type == 'Point':
x, y = geom.x, geom.y
px, py = ~map_transform * (x, y)
pixel_positions.append((int(px), int(py)))
# --- VISUALIZE ---
plt.figure(figsize=(10, 10))
plt.imshow(map_img)
for px, py in pixel_positions:
plt.scatter(px, py, c='deepskyblue', s=40, edgecolors='black', label='Trinkbrunnen')
plt.title('Trinkbrunnen auf der Berlin-Karte')
plt.axis('off')
plt.savefig('datenvisualisierung/trinkbrunnen_karte.png', bbox_inches='tight', dpi=200)
plt.show()
# --- EXPORT PIXEL POSITIONS FOR GAME ---
# Save as npy for easy loading in pygame
np.save('trinkbrunnen_pixel_positions.npy', np.array(pixel_positions))
print(f"{len(pixel_positions)} Trinkbrunnen-Pixelpositionen wurden in trinkbrunnen_pixel_positions.npy gespeichert.")

25
check_tif.py Normal file
View file

@ -0,0 +1,25 @@
import rasterio
import numpy as np
import matplotlib.pyplot as plt
import sys
# --- Konfiguration ---
tif_path = 'geodata/s2_2025.tif'
try:
with rasterio.open(tif_path) as src:
print(f"Datei: {tif_path}")
print(f"CRS: {src.crs}")
print(f"Breite x Höhe: {src.width} x {src.height}")
print(f"Bänder: {src.count}")
print(f"Datentyp: {src.dtypes}")
arr = src.read(1)
print(f"Min: {np.nanmin(arr)}, Max: {np.nanmax(arr)}")
plt.imshow(arr, cmap='gray')
plt.title('Erstes Band der TIFF-Datei')
plt.axis('off')
plt.savefig('datenvisualisierung/tif_check.png', bbox_inches='tight', dpi=200)
plt.show()
except Exception as e:
print(f"Fehler beim Laden der TIFF-Datei: {e}")
sys.exit(1)

94
enemy.py Normal file
View file

@ -0,0 +1,94 @@
import pygame
import random
from settings import PLAYER_SIZE, PLAYER_SPEED, KILL_RADIUS
class Enemy:
def __init__(self, x, y, enemy_image, human_image, controlled=False):
self.x = x
self.y = y
self.rect = pygame.Rect(x, y, PLAYER_SIZE, PLAYER_SIZE)
self.dir = "up"
self.max_health = 100
self.health = 100
self.is_human = False
self.enemy_image = enemy_image
self.human_image = human_image
self.controlled = controlled
self.alive = True
def move(self, keys, player, map_w, map_h):
if self.is_human:
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:
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):
img = self.human_image if self.is_human else self.enemy_image
angle = {"up": 90, "right": 0, "down": -90, "left": 180}[self.dir]
color = (0, 128, 255) if self.is_human else (200, 200, 0)
if img:
rotated_img = pygame.transform.rotate(img, angle)
win.blit(rotated_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))
if not self.is_human:
bw = PLAYER_SIZE // 2
bh = 6
ratio = self.health / self.max_health
bx = self.x - ox
by = 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.alive = False
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

42
player.py Normal file
View file

@ -0,0 +1,42 @@
import pygame
from settings import PLAYER_SIZE
class Player:
def __init__(self, x, y, image):
self.x = x
self.y = y
self.alive = True
self.rect = pygame.Rect(x, y, PLAYER_SIZE, PLAYER_SIZE)
self.dir = "up"
self.image = image
def move(self, keys, map_w, map_h):
if not self.alive:
return
dx = dy = 0
if keys[pygame.K_w]:
dy -= 5
self.dir = "up"
if keys[pygame.K_s]:
dy += 5
self.dir = "down"
if keys[pygame.K_a]:
dx -= 5
self.dir = "left"
if keys[pygame.K_d]:
dx += 5
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 = self.image
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))

Binary file not shown.

Binary file not shown.

26
settings.py Normal file
View file

@ -0,0 +1,26 @@
import pygame
# Globale Einstellungen für das Spiel
MAP_PATH = "geodata/s2_2025.tif"
WIDTH = 800
HEIGHT = 600
MINIMAP_MARGIN = 20
PLAYER_SIZE = 40
PLAYER_SPEED = 5
WATER_SIZE = 10
WATER_SPEED = 10
KILL_RADIUS = 30
PLAYER_IMG_PATH = "images/player.png"
ENEMY_IMG_PATH = "images/enemy.png"
HUMAN_IMG_PATH = "images/human.png"
def load_img(path, size):
try:
img = pygame.image.load(path).convert_alpha()
return pygame.transform.scale(img, size)
except Exception as e:
print(f"Error loading image '{path}': {e}")
return None

23
tapwater.py Normal file
View file

@ -0,0 +1,23 @@
import pygame
from settings import PLAYER_SIZE, WATER_SIZE, WATER_SPEED
class TapWater:
def __init__(self, x, y, direction):
self.x = x + PLAYER_SIZE // 2 - WATER_SIZE // 2
self.y = y + PLAYER_SIZE // 2 - WATER_SIZE // 2
self.direction = direction
self.rect = pygame.Rect(self.x, self.y, WATER_SIZE, WATER_SIZE)
def move(self):
if self.direction == "up":
self.y -= WATER_SPEED
elif self.direction == "down":
self.y += WATER_SPEED
elif self.direction == "left":
self.x -= WATER_SPEED
elif self.direction == "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))

View file

@ -1,32 +1,22 @@
# Simple top-down shooter with WASD player, arrow-key enemy, health bar, minimap, and raster background
import pygame, sys, numpy as np, rasterio, random
import sys
import random
import pygame
import numpy as np
import rasterio
from player import Player
from enemy import Enemy
from tapwater import TapWater
from settings import PLAYER_SIZE, MAP_PATH, WIDTH, HEIGHT, MINIMAP_MARGIN
DEBUG = False
pygame.init()
WIDTH, HEIGHT = 800, 600
# logo_img = pygame.image.load("images/logo.png")
# pygame.display.set_icon(logo_img)
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)
@ -34,156 +24,41 @@ def load_map(path):
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
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))
MINIMAP_WIDTH = 200
MINIMAP_HEIGHT = int(MAP_H / MAP_W * MINIMAP_WIDTH)
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)
mini = pygame.transform.smoothscale(map_surface, (MINIMAP_WIDTH, MINIMAP_HEIGHT))
win.blit(mini, (WIDTH - MINIMAP_WIDTH - MINIMAP_MARGIN, MINIMAP_MARGIN))
def map2mini(x, y):
mx = int(x / MAP_W * MINIMAP_WIDTH)
my = int(y / MAP_H * MINIMAP_HEIGHT)
return WIDTH - MINIMAP_WIDTH - 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)
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)
for bottle in bottles:
pygame.draw.circle(win, (0, 180, 255), map2mini(bottle.x, bottle.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))
from settings import PLAYER_IMG_PATH, ENEMY_IMG_PATH, HUMAN_IMG_PATH, PLAYER_SIZE, load_img
player_img = load_img(PLAYER_IMG_PATH, (PLAYER_SIZE, PLAYER_SIZE))
enemy_img = load_img(ENEMY_IMG_PATH, (PLAYER_SIZE, PLAYER_SIZE))
human_img = load_img(HUMAN_IMG_PATH, (PLAYER_SIZE, PLAYER_SIZE))
player = Player(MAP_W // 2, MAP_H // 2, player_img)
enemies = [Enemy(random.randint(0, MAP_W - PLAYER_SIZE), random.randint(0, MAP_H - PLAYER_SIZE), enemy_img, human_img, i == 0) for i in range(20)]
bottles = []
minimap_visible = True
clock = pygame.time.Clock()
@ -191,35 +66,48 @@ def main():
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
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)
player.move(keys, MAP_W, MAP_H)
for enemy in enemies:
enemy.move(keys, player)
enemy.move(keys, player, MAP_W, MAP_H)
enemy.check_kill_player(player)
for b in bottles[:]:
for bottle 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))
if enemy.rect.colliderect(bottle.rect):
enemy.take_damage(20)
bottles.remove(bottle)
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)
for bottle in bottles:
bottle.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()
# Remove win/lose condition checks
# if not player.alive:
# show_lose_screen(WIN)
# running = False
# elif all(not e.alive or e.health <= 0 for e in enemies):
# show_win_screen(WIN)
# running = False
pygame.quit()
sys.exit()
if __name__=="__main__": main()
if __name__ == "__main__":
main()