Compare commits

...

5 commits

Author SHA1 Message Date
binaerverkehr
4449656a69 refactor: Typisierung in Hauptdateien und Skripten ergaenzt
- Felder und Methoden in Game und Editor typisiert
- Tilemap: Felder, render, tiles_around, physics_rects_around, autotile
- Animation: Konstruktor-Parameter, Felder, copy und update
- Clouds: Cloud und Clouds vollstaendig typisiert
- utils: load_image und load_images mit Rueckgabe-Typ
2026-05-11 10:37:37 +02:00
binaerverkehr
d796b47c46 feat: Collisions Dataclass eingefuehrt und in PhysicsEntity verwendet
- Neue @dataclass Collisions mit Feldern top/right/bottom/left
- collisions-Dict in PhysicsEntity durch Collisions-Instanz ersetzt
- Attribut-Zugriff (collisions.top) statt Dict-Indexierung
- Typisierung von Feldern und Methoden in PhysicsEntity und Player
2026-05-11 10:37:28 +02:00
binaerverkehr
da973dd968 fix: Save/Load Funktionen korrigiert. 2026-05-11 10:19:31 +02:00
binaerverkehr
dc5239aa4c fix: Falsch geschriebene Variable korrigiert 2026-05-11 10:11:52 +02:00
binaerverkehr
9e0068e926 - Lade map.json innerhalb von try-except
- test-Funktion entfernt
2026-05-11 10:11:09 +02:00
8 changed files with 142 additions and 131 deletions

View file

@ -3,7 +3,7 @@ import sys
from scripts.tilemap import Tilemap from scripts.tilemap import Tilemap
from scripts.utils import load_images from scripts.utils import load_images
RENDER_SCALE = 2.0 RENDER_SCALE: float = 2.0
class better_list(list): class better_list(list):
@ -16,45 +16,45 @@ class Editor:
pygame.init() pygame.init()
pygame.display.set_caption("Editor") pygame.display.set_caption("Editor")
self.screen = pygame.display.set_mode((640, 480)) self.screen: pygame.Surface = pygame.display.set_mode((640, 480))
self.display = pygame.Surface((320, 240)) self.display: pygame.Surface = pygame.Surface((320, 240))
self.clock = pygame.time.Clock() self.clock: pygame.time.Clock = pygame.time.Clock()
self.running = True self.running: bool = True
self.collision_area = pygame.Rect(50, 50, 300, 50) self.collision_area: pygame.Rect = pygame.Rect(50, 50, 300, 50)
self.movement = [False, False, False, False] self.movement: list[bool] = [False, False, False, False]
self.assets = { self.assets: dict[str, list[pygame.Surface]] = {
"decor": load_images("tiles/decor"), "decor": load_images("tiles/decor"),
"grass": load_images("tiles/grass"), "grass": load_images("tiles/grass"),
"large_decor": load_images("tiles/large_decor"), "large_decor": load_images("tiles/large_decor"),
"stone": load_images("tiles/stone"), "stone": load_images("tiles/stone"),
} }
self.tilemap = Tilemap(self, 16) self.tilemap: Tilemap = Tilemap(self, 16)
try: try:
self.tilemap.load("map.json") self.tilemap.load("map.json")
except FileNotFoundError: except FileNotFoundError:
pass pass
self.isJumping = False self.isJumping: bool = False
self.scroll = [0, 0] self.scroll: list[float] = [0, 0]
self.tile_list = better_list(self.assets) self.tile_list: better_list = better_list(self.assets)
self.tile_group = 0 self.tile_group: int = 0
self.tile_variant = 0 self.tile_variant: int = 0
self.clicking = False self.clicking: bool = False
self.right_clicking = False self.right_clicking: bool = False
self.shifting = False self.shifting: bool = False
self.sekunde = 60 self.sekunde: int = 60
self.zaehler = 0 self.zaehler: int = 0
self.on_grid = True self.on_grid: bool = True
def run(self): def run(self) -> None:
while self.running: while self.running:
self.display.fill((0, 0, 0)) self.display.fill((0, 0, 0))

36
game.py
View file

@ -13,16 +13,16 @@ class Game:
pygame.init() pygame.init()
pygame.display.set_caption("Jump and run spiel") pygame.display.set_caption("Jump and run spiel")
self.screen = pygame.display.set_mode((640, 480)) self.screen: pygame.Surface = pygame.display.set_mode((640, 480))
self.display = pygame.Surface((320, 240)) self.display: pygame.Surface = pygame.Surface((320, 240))
self.clock = pygame.time.Clock() self.clock: pygame.time.Clock = pygame.time.Clock()
self.running = True self.running: bool = True
self.collision_area = pygame.Rect(50, 50, 300, 50) self.collision_area: pygame.Rect = pygame.Rect(50, 50, 300, 50)
self.movement = [False, False] self.movement: list[bool] = [False, False]
self.assets = { self.assets: dict = {
# Andere Dinge # Andere Dinge
"background": load_image("background.png"), "background": load_image("background.png"),
"decor": load_images("tiles/decor"), "decor": load_images("tiles/decor"),
@ -41,20 +41,18 @@ class Game:
"player/slide": Animation(load_images("entities/player/slide")), "player/slide": Animation(load_images("entities/player/slide")),
"player/wall_slide": Animation(load_images("entities/player/wall_slide")), "player/wall_slide": Animation(load_images("entities/player/wall_slide")),
} }
self.player = Player(self, (50, 50), (8, 15)) self.player: Player = Player(self, (50, 50), (8, 15))
self.tilemap = Tilemap(self, 16) self.tilemap: Tilemap = Tilemap(self, 16)
self.tilemap.load("map.json") try:
self.clouds = Clouds(self.assets["clouds"], count=16) self.tilemap.load("map.json")
self.isJumping = False except FileNotFoundError:
self.scroll = [0, 0] pass
self.test: dict = {"dsa"} self.clouds: Clouds = Clouds(self.assets["clouds"], count=16)
self.isJumping: bool = False
self.scroll: list[float] = [0, 0]
def testolino(self): def run(self) -> None:
print(self.test)
print(type(self.test))
def run(self):
while self.running: while self.running:
self.display.blit(self.assets["background"], (0, 0)) self.display.blit(self.assets["background"], (0, 0))

View file

@ -1,5 +1,5 @@
class Animation: class Animation:
def __init__(self, images, image_duration=5, loop=True): def __init__(self, images: list, image_duration: int = 5, loop: bool = True):
""" """
Initialisiert eine Animation mit einer Liste von Bildern. Initialisiert eine Animation mit einer Liste von Bildern.
@ -11,13 +11,13 @@ class Animation:
- done startet bei False - done startet bei False
""" """
# TODO: Implementieren # TODO: Implementieren
self.images = images self.images: list = images
self.image_duration = image_duration self.image_duration: int = image_duration
self.loop = loop self.loop: bool = loop
self.frame = 0 self.frame: int = 0
self.done = False self.done: bool = False
def copy(self): def copy(self) -> "Animation":
""" """
Erstellt eine Kopie der Animation. Erstellt eine Kopie der Animation.
@ -29,7 +29,8 @@ class Animation:
# TODO: Implementieren # TODO: Implementieren
copy_animation = Animation(self.images, self.image_duration, self.loop) copy_animation = Animation(self.images, self.image_duration, self.loop)
return copy_animation return copy_animation
def update(self):
def update(self) -> None:
""" """
Aktualisiert die Animation um einen Frame weiter. Aktualisiert die Animation um einen Frame weiter.
@ -50,6 +51,7 @@ class Animation:
self.frame = min(self.frame + 1, len(self.images) * self.image_duration -1) self.frame = min(self.frame + 1, len(self.images) * self.image_duration -1)
if self.frame >= self.image_duration * (len(self.images) - 1): if self.frame >= self.image_duration * (len(self.images) - 1):
self.done = True self.done = True
def img(self): def img(self):
""" """
Gibt das aktuelle Bild der Animation zurück. Gibt das aktuelle Bild der Animation zurück.

View file

@ -1,45 +1,40 @@
import pygame import pygame
import random import random
class Cloud: class Cloud:
def __init__(self, pos:tuple, img, speed:float, depth): def __init__(self, pos: tuple[float, float], img: pygame.Surface, speed: float, depth: float):
self.pos = list(pos) self.pos: list[float] = list(pos)
self.image = img self.image: pygame.Surface = img
self.speed = speed self.speed: float = speed
self.depth = depth self.depth: float = depth
def update(self) -> None:
def update(self):
self.pos[0] += self.speed self.pos[0] += self.speed
def render(self, surface:pygame.Surface,offset:tuple=(0,0)):
def render(self, surface: pygame.Surface, offset: tuple[int, int] = (0, 0)) -> None:
render_pos = (self.pos[0] - offset[0] * self.depth, self.pos[1] - offset[1] * self.depth) render_pos = (self.pos[0] - offset[0] * self.depth, self.pos[1] - offset[1] * self.depth)
surface.blit(self.image, surface.blit(self.image,
(render_pos[0] % (surface.get_width() + self.image.get_width()) - self.image.get_width(), (render_pos[0] % (surface.get_width() + self.image.get_width()) - self.image.get_width(),
render_pos[1] % (surface.get_height() + self.image.get_height()) - self.image.get_height()) render_pos[1] % (surface.get_height() + self.image.get_height()) - self.image.get_height())
) )
class Clouds: class Clouds:
def __init__(self, cloud_images, count=16): def __init__(self, cloud_images: list[pygame.Surface], count: int = 16):
self.clouds = [] self.clouds: list[Cloud] = []
for _ in range(count): for _ in range(count):
self.clouds.append(Cloud((random.random() * 99999, random.random() * 99999), self.clouds.append(Cloud((random.random() * 99999, random.random() * 99999),
random.choice(cloud_images), random.choice(cloud_images),
(random.random() * 0.05 + 0.05) * 1, (random.random() * 0.05 + 0.05) * 1,
random.random() * 0.6 + 0.2)) random.random() * 0.6 + 0.2))
self.clouds.sort(key=lambda x: x.depth) self.clouds.sort(key=lambda x: x.depth)
"""
self.clouds = []
# Erstelle die Wolken mit zufälligen Eigenschaften def update(self) -> None:
for i in range(count):
self.clouds.append(Cloud((random.random() * 99999, random.random() * 99999),
random.choice(cloud_images),
random.random() * 0.05 + 0.05,
random.random() * 0.6 + 0.2)
)
self.clouds.sort(key=lambda x: x.depth)"""
def update(self):
for cloud in self.clouds: for cloud in self.clouds:
cloud.update() cloud.update()
def render(self, surface:pygame.Surface, offset:tuple=(0,0)):
def render(self, surface: pygame.Surface, offset: tuple[int, int] = (0, 0)) -> None:
for cloud in self.clouds: for cloud in self.clouds:
cloud.render(surface, offset) cloud.render(surface, offset)

View file

@ -1,26 +1,37 @@
import pygame import pygame
from dataclasses import dataclass
from scripts.animation import Animation
from scripts.tilemap import Tilemap from scripts.tilemap import Tilemap
@dataclass
class Collisions:
top: bool = False
right: bool = False
bottom: bool = False
left: bool = False
class PhysicsEntity: class PhysicsEntity:
def __init__(self, game, e_type, pos: tuple, size): def __init__(self, game, e_type: str, pos: tuple[float, float], size: tuple[int, int]):
self.game = game self.game = game
self.e_type = e_type self.e_type: str = e_type
self.pos = list(pos) self.pos: list[float] = list(pos)
self.size = size self.size: tuple[int, int] = size
self.velocity = [0, 0] self.velocity: list[float] = [0, 0]
self.collisions = {"top": False, "bottom": False, "left": False, "right": False} self.collisions: Collisions = Collisions()
self.speed = 3 self.speed: int = 3
# Animationen # Animationen
self.action = "" self.action: str = ""
self.flip = False self.flip: bool = False
self.animation_offset = (-3, -3) self.animation_offset: tuple[int, int] = (-3, -3)
self.animation: Animation
self.set_action("idle") self.set_action("idle")
def update(self, tilemap: Tilemap, movement: tuple = (0, 0)): def update(self, tilemap: Tilemap, movement: tuple[float, float] = (0, 0)) -> None:
self.collisions = {"top": False, "bottom": False, "left": False, "right": False} self.collisions = Collisions()
frame_movement = ( frame_movement: tuple[float, float] = (
(movement[0] + self.velocity[0]) * self.speed, (movement[0] + self.velocity[0]) * self.speed,
movement[1] + self.velocity[1], movement[1] + self.velocity[1],
) )
@ -31,12 +42,12 @@ class PhysicsEntity:
if entity_rect.colliderect(recto): if entity_rect.colliderect(recto):
if frame_movement[0] > 0: if frame_movement[0] > 0:
entity_rect.right = recto.left entity_rect.right = recto.left
self.collisions["right"] = True self.collisions.right = True
self.game.isJumping = False self.game.isJumping = False
if frame_movement[0] < 0: if frame_movement[0] < 0:
entity_rect.left = recto.right entity_rect.left = recto.right
self.collisions["left"] = True self.collisions.left = True
self.game.isJumping = False self.game.isJumping = False
self.pos[0] = entity_rect.x self.pos[0] = entity_rect.x
@ -48,12 +59,12 @@ class PhysicsEntity:
if entity_rect.colliderect(rectolino): if entity_rect.colliderect(rectolino):
if frame_movement[1] > 0: if frame_movement[1] > 0:
entity_rect.bottom = rectolino.top entity_rect.bottom = rectolino.top
self.collisions["bottom"] = True self.collisions.bottom = True
self.game.isJumping = False self.game.isJumping = False
if frame_movement[1] < 0: if frame_movement[1] < 0:
entity_rect.top = rectolino.bottom entity_rect.top = rectolino.bottom
self.collisions["top"] = True self.collisions.top = True
self.pos[1] = entity_rect.y self.pos[1] = entity_rect.y
@ -62,14 +73,14 @@ class PhysicsEntity:
if movement[0] < 0: if movement[0] < 0:
self.flip = True self.flip = True
if self.collisions["bottom"] or self.collisions["top"]: if self.collisions.bottom or self.collisions.top:
self.velocity[1] = 0 self.velocity[1] = 0
self.velocity[1] = min(5, self.velocity[1] + 0.1) self.velocity[1] = min(5, self.velocity[1] + 0.1)
self.animation.update() self.animation.update()
def render(self, surface: pygame.Surface, offset: tuple = (0, 0)): def render(self, surface: pygame.Surface, offset: tuple[int, int] = (0, 0)) -> None:
surface.blit( surface.blit(
pygame.transform.flip(self.animation.img(), self.flip, False), pygame.transform.flip(self.animation.img(), self.flip, False),
( (
@ -78,10 +89,10 @@ class PhysicsEntity:
), ),
) )
def rect(self): def rect(self) -> pygame.Rect:
return pygame.Rect(self.pos[0], self.pos[1], self.size[0], self.size[1]) return pygame.Rect(self.pos[0], self.pos[1], self.size[0], self.size[1])
def set_action(self, action): def set_action(self, action: str) -> None:
if action != self.action: if action != self.action:
self.action = action self.action = action
image = self.e_type + "/" + self.action image = self.e_type + "/" + self.action
@ -89,16 +100,16 @@ class PhysicsEntity:
class Player(PhysicsEntity): class Player(PhysicsEntity):
def __init__(self, game, pos, size): def __init__(self, game, pos: tuple[float, float], size: tuple[int, int]):
super().__init__(game, "player", pos, size) super().__init__(game, "player", pos, size)
self.air_time = 0 # Neu! Zählt, wie lange wir fallen/springe self.air_time: int = 0 # Neu! Zählt, wie lange wir fallen/springe
def update(self, tilemap, movement=(0, 0)): def update(self, tilemap: Tilemap, movement: tuple[float, float] = (0, 0)) -> None:
super().update(tilemap, movement) super().update(tilemap, movement)
self.air_time += 1 self.air_time += 1
if self.collisions["bottom"]: if self.collisions.bottom:
self.air_time = 0 self.air_time = 0
# Wenn wir länger als 4 Frames in der Luft sind -> Springen! # Wenn wir länger als 4 Frames in der Luft sind -> Springen!
if self.air_time > 4: if self.air_time > 4:

View file

@ -9,14 +9,14 @@ class Tile:
def grid_key(self) -> str: def grid_key(self) -> str:
"""Erzeugt den String-Key für die Tilemap.""" """Erzeugt den String-Key für die Tilemap."""
return f"{self.pos[0]};{self.post[1]}" return f"{self.pos[0]};{self.pos[1]}"
def to_dict(self) -> dict: def to_dict(self) -> dict:
"""Konvertiert zurück in ein Dict (für JSON-Speicherung).""" """Konvertiert zurück in ein Dict (für JSON-Speicherung)."""
return { return {
'type': self.type, 'type': self.type,
'variant': self.variant, 'variant': self.variant,
'pos': list(self.post) 'pos': list(self.pos)
} }
@staticmethod @staticmethod

View file

@ -1,6 +1,5 @@
import pygame as pg import pygame as pg
import json import json
from scripts.models import Tile
NEIGHBOR_OFFSETS = [ NEIGHBOR_OFFSETS = [
(0, 0), (0, 0),
@ -33,14 +32,22 @@ class Tilemap:
def __init__(self, game, tile_size: int = 16): def __init__(self, game, tile_size: int = 16):
self.game = game self.game = game
self.tile_size = tile_size self.tile_size: int = tile_size
self.tilemap = {} self.tilemap: dict[str, dict] = {}
self.offgrid_tiles = [] self.offgrid_tiles: list[dict] = []
for i in range(10): for i in range(10):
self.tilemap[str(3 + i) + ";10"] = Tile("grass", 1, (3 + i, 10)) self.tilemap[str(3 + i) + ";10"] = {
self.tilemap["10;" + str(5 + i)] = Tile("stone", 1, (10, i + 5)) "type": "grass",
"variant": 1,
"pos": (3 + i, 10),
}
self.tilemap["10;" + str(5 + i)] = {
"type": "stone",
"variant": 1,
"pos": (10, i + 5),
}
def render(self, surface: pg.Surface, offset: tuple = (0, 0)): def render(self, surface: pg.Surface, offset: tuple[int, int] = (0, 0)) -> None:
for tile in self.offgrid_tiles: for tile in self.offgrid_tiles:
surface.blit( surface.blit(
self.game.assets[tile["type"]][tile["variant"]], self.game.assets[tile["type"]][tile["variant"]],
@ -65,8 +72,8 @@ class Tilemap:
), ),
) )
def tiles_around(self, pos: list): def tiles_around(self, pos: list[float]) -> list[dict]:
tiles = [] tiles: list[dict] = []
tile_location = (int(pos[0] // self.tile_size), int(pos[1] // self.tile_size)) tile_location = (int(pos[0] // self.tile_size), int(pos[1] // self.tile_size))
for offset in NEIGHBOR_OFFSETS: for offset in NEIGHBOR_OFFSETS:
check_location = ( check_location = (
@ -79,9 +86,9 @@ class Tilemap:
return tiles return tiles
def physics_rects_around(self, pos: list) -> list: def physics_rects_around(self, pos: list[float]) -> list[pg.Rect]:
# Erzeuge eine leere Liste für die Rechtecke # Erzeuge eine leere Liste für die Rechtecke
rects = [] rects: list[pg.Rect] = []
# Durchlaufe alle Tiles aus der Umgebung (tiles_around) # Durchlaufe alle Tiles aus der Umgebung (tiles_around)
for tile in self.tiles_around(pos): for tile in self.tiles_around(pos):
# Prüfe, ob der Tile-Typ in PHYSICS_TILES enthalten ist # Prüfe, ob der Tile-Typ in PHYSICS_TILES enthalten ist
@ -103,7 +110,7 @@ class Tilemap:
# Gib die Liste der Rechtecke zurück # Gib die Liste der Rechtecke zurück
return rects return rects
def autotile(self): def autotile(self) -> None:
for loc in self.tilemap: for loc in self.tilemap:
tile = self.tilemap[loc] tile = self.tilemap[loc]
@ -127,27 +134,21 @@ class Tilemap:
tile["variant"] = AUTOTILE_MAP[neighbors] tile["variant"] = AUTOTILE_MAP[neighbors]
def save(self, path: str) -> None: def save(self, path: str) -> None:
data = { data = {
"tilemap": {k: tile.to_dict() for k, tile in self.tilemap.items()}, "tilemap": self.tilemap,
"tile_size": self.tile_size, "tile_size": self.tile_size,
"offgrid": [tile.to_dict() for tile in self.offgrid_tiles], "offgrid": self.offgrid_tiles,
} }
with open(path, "w") as f: with open(path, "w") as f:
json.dump(data, f) json.dump(data, f)
def load(self, path: str): def load(self, path: str) -> None:
"""Lädt die gespeicherte Tilemap.""" """Lädt die gespeicherte Tilemap."""
with open(path, "r") as f: with open(path, "r") as f:
map_data: dict = json.load(f) map_data: dict = json.load(f)
self.tilemap: dict[str:Tile] = {} self.tilemap: dict[str, dict] = map_data["tilemap"]
self.offgrid_tiles: list[Tile] = [] self.tile_size: int = map_data["tile_size"]
self.offgrid_tiles: list[dict] = map_data["offgrid"]
for key, data in map_data.items():
self.tilemap[key] = Tile.from_dict(data)
for tile in map_data["offgrid"]:
self.offgrid_tiles.append(Tile.from_dict(tile))

View file

@ -1,13 +1,17 @@
import pygame import pygame
import os import os
BASE_IMG_PATH = 'data/images/'
def load_image(path): BASE_IMG_PATH: str = 'data/images/'
def load_image(path: str) -> pygame.Surface:
img = pygame.image.load(BASE_IMG_PATH + path).convert() img = pygame.image.load(BASE_IMG_PATH + path).convert()
img.set_colorkey((0, 0, 0)) img.set_colorkey((0, 0, 0))
return img return img
def load_images(path):
images = [] def load_images(path: str) -> list[pygame.Surface]:
images: list[pygame.Surface] = []
for img_name in sorted(os.listdir(BASE_IMG_PATH + path)): for img_name in sorted(os.listdir(BASE_IMG_PATH + path)):
images.append(load_image(path + '/' + img_name)) images.append(load_image(path + '/' + img_name))
return images return images