Compare commits
10
Commits
ba85e87473
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5976165238 | ||
|
|
e4d36ad733 | ||
|
|
87feb62121 | ||
|
|
41c5ca9107 | ||
|
|
27464f2528 | ||
|
|
1751290044 | ||
|
|
01eabb3a59 | ||
|
|
990896d3c2 | ||
|
|
88ff3d7a8a | ||
|
|
731b7c2a1d |
No files matched your search
@@ -1,4 +0,0 @@
|
||||
[project]
|
||||
name = "matrixmania"
|
||||
version = "0.1.0"
|
||||
dependencies = ["tabulate", "ruff"]
|
||||
Generated
+5
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
Generated
+1
@@ -0,0 +1 @@
|
||||
matrixmania
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.14 (PythonProject)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.14 (PythonProject)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14 (PythonProject)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/matrixmania.iml" filepath="$PROJECT_DIR$/.idea/matrixmania.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,4 +1,6 @@
|
||||
# Regeln für dieses Projekt
|
||||
- Code nach PEP 8, Zeilenlänge maximal 88 Zeichen.
|
||||
- Jede Funktion und Methode hat einen Docstring und Type Hints.
|
||||
- Vor jedem Commit: `ruff check` und `ruff format`.
|
||||
- Vor jedem Commit: `ruff check` und `ruff format`.
|
||||
- Quellcode liegt unter "src/matrixmania/", Tests unter "test/test_matrixmania/"
|
||||
- Testpakete, Testdateien und Testfunktionen beginnen mit "test_"
|
||||
@@ -0,0 +1,10 @@
|
||||
[project]
|
||||
name = "matrixmania"
|
||||
version = "0.1.0"
|
||||
dependencies = ["tabulate", "ruff", "pygame"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["pytest", "ruff", "pytest-cov"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = "src"
|
||||
@@ -0,0 +1,58 @@
|
||||
import pygame
|
||||
|
||||
from src.matrixmania.compute import matmul, rot_2D, transpose
|
||||
from src.matrixmania.game import Game
|
||||
|
||||
EARTH_RADIUS = 30
|
||||
MOON_RADIUS = 10
|
||||
BLACK = (0, 0, 0)
|
||||
WHITE = (255, 255, 255)
|
||||
BLUE = (0, 0, 255)
|
||||
|
||||
|
||||
class Moon(Game):
|
||||
"""Der Mond kreist um die Erde."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str = "Moon",
|
||||
fps: int = 60,
|
||||
size: tuple[int, int] = (640, 400),
|
||||
) -> None:
|
||||
"""Legt Startposition, Drehwinkel und Rotationsmatrix fest."""
|
||||
super().__init__(title, fps, size)
|
||||
self.centre = (size[0] // 2, size[1] // 2)
|
||||
|
||||
self.position_moon = [0.0, -150.0]
|
||||
|
||||
theta = 0.01
|
||||
self.rot_moon = rot_2D(theta)
|
||||
|
||||
def update_game(self) -> bool:
|
||||
"""Dreht den Mond ein Stück weiter."""
|
||||
self.update_moon_position()
|
||||
return True
|
||||
|
||||
def update_moon_position(self) -> None:
|
||||
point = transpose([self.position_moon]) # [x, y] wird zur Spalte [[x], [y]]
|
||||
result = matmul(self.rot_moon, point) # Ergebnis: [[x'], [y']]
|
||||
self.position_moon = [result[0][0], result[1][0]]
|
||||
|
||||
def draw_game(self) -> None:
|
||||
self.screen.fill(BLACK)
|
||||
self.draw_earth()
|
||||
self.draw_moon()
|
||||
pygame.display.flip()
|
||||
|
||||
def draw_earth(self) -> None:
|
||||
pygame.draw.circle(self.screen, BLUE, self.centre, EARTH_RADIUS)
|
||||
|
||||
def draw_moon(self) -> None:
|
||||
moon_x = int(self.centre[0] + self.position_moon[0])
|
||||
moon_y = int(self.centre[1] + self.position_moon[1])
|
||||
pygame.draw.circle(self.screen, WHITE, (moon_x, moon_y), MOON_RADIUS)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
game = Moon()
|
||||
game.run()
|
||||
@@ -0,0 +1 @@
|
||||
from .compute import matmul, transpose, rot_2D
|
||||
@@ -0,0 +1,50 @@
|
||||
import pygame
|
||||
|
||||
from src.matrixmania.compute import matmul, rot_2D, transpose
|
||||
from Moon import Moon
|
||||
|
||||
RED = (255, 0, 0)
|
||||
BLACK = (0, 0, 0)
|
||||
APOLLO_RADIUS = 3
|
||||
|
||||
|
||||
class Apollo(Moon):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: str = "Apollo",
|
||||
fps: int = 60,
|
||||
size: tuple[int, int] = (640, 400),
|
||||
) -> None:
|
||||
|
||||
super().__init__(title, fps, size) #Startpos. des Mondes wird hier übergeben
|
||||
|
||||
self.position_apollo = [0.0, -40.0] #Startposition von Apollo relativ zum Mond
|
||||
theta_apollo = -0.02 #doppelte Geschwindigkeit, umgekehrte Drehrichtung
|
||||
self.rot_apollo = rot_2D(theta_apollo)
|
||||
|
||||
def update_game(self) -> bool:
|
||||
super().update_game()
|
||||
|
||||
point = transpose([self.position_apollo]) #macht aus Koordinatenliste Spaltenvektor
|
||||
result = matmul(self.rot_apollo, point) #neue relative Pos. berechnen
|
||||
self.position_apollo = [result[0][0], result[1][0]] #Matrix: erste Zeile: erstes Element, zweite Zeile: erstes Element -> Koordinaten
|
||||
|
||||
return True
|
||||
|
||||
def draw_game(self) -> None:
|
||||
self.screen.fill(BLACK)
|
||||
self.draw_earth()
|
||||
self.draw_moon()
|
||||
self.draw_apollo()
|
||||
pygame.display.flip()
|
||||
|
||||
def draw_apollo(self) -> None:
|
||||
apollo_x = int(self.centre[0] + self.position_moon[0] + self.position_apollo[0]) #Absolute Position = Bildschirmmitte + Mond-Position + Apollo-Relativposition
|
||||
apollo_y = int(self.centre[1] + self.position_moon[1] + self.position_apollo[1])
|
||||
pygame.draw.circle(self.screen, RED, (apollo_x, apollo_y), APOLLO_RADIUS)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
game = Apollo()
|
||||
game.run()
|
||||
@@ -120,6 +120,64 @@ def matmul(A: Matrix, B: Matrix) -> Matrix:
|
||||
|
||||
return result
|
||||
|
||||
def rot_3D(angle: float, axis: str) -> list[list[float]]:
|
||||
"""Berechnet die 3D-Rotationsmatrix für einen gegebenen Winkel und eine Achse.
|
||||
|
||||
Die Rotationsmatrizen lauten:
|
||||
|
||||
Rotation um die x-Achse:
|
||||
R_x(θ) = [[1, 0, 0],
|
||||
[0, cos(θ), -sin(θ)],
|
||||
[0, sin(θ), cos(θ)]]
|
||||
|
||||
Rotation um die y-Achse:
|
||||
R_y(θ) = [[ cos(θ), 0, sin(θ)],
|
||||
[ 0, 1, 0],
|
||||
[-sin(θ), 0, cos(θ)]]
|
||||
|
||||
Rotation um die z-Achse:
|
||||
R_z(θ) = [[cos(θ), -sin(θ), 0],
|
||||
[sin(θ), cos(θ), 0],
|
||||
[ 0, 0, 1]]
|
||||
|
||||
Args:
|
||||
angle (float): Der Rotationswinkel in Bogenmaß (Radians).
|
||||
axis (str): Die Rotationsachse ('x', 'y' oder 'z').
|
||||
|
||||
Returns:
|
||||
list[list[float]]: Die 3x3-Rotationsmatrix als geschachtelte Liste.
|
||||
|
||||
Raises:
|
||||
ValueError: Wenn ein ungültiger Achsenwert übergeben wird.
|
||||
"""
|
||||
cos_a = math.cos(angle)
|
||||
sin_a = math.sin(angle)
|
||||
|
||||
if axis == "x":
|
||||
return [[1.0, 0.0, 0.0], [0.0, cos_a, -sin_a], [0.0, sin_a, cos_a]]
|
||||
elif axis == "y":
|
||||
return [[cos_a, 0.0, sin_a], [0.0, 1.0, 0.0], [-sin_a, 0.0, cos_a]]
|
||||
elif axis == "z":
|
||||
return [[cos_a, -sin_a, 0.0], [sin_a, cos_a, 0.0], [0.0, 0.0, 1.0]]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Ungültige Achse '{axis}'. Gültige Achsen sind 'x', 'y' oder 'z'."
|
||||
)
|
||||
|
||||
def project_ortho(point: tuple[float, float, float], scale: float = 1) -> tuple[float, float]:
|
||||
"""Projiziert einen 3D-Punkt (x, y, z) orthogonal auf eine 2D-Fläche (x, y),
|
||||
indem die z-Koordinate ignoriert und der Punkt mit scale multipliziert wird.
|
||||
|
||||
Args:
|
||||
point (tuple[float, float, float]): Der 3D-Punkt als (x, y, z).
|
||||
scale (float, optional): Skalierungsfaktor für die Ausgabe. Standard ist 1.
|
||||
|
||||
Returns:
|
||||
tuple[float, float]: Der 2D-Punkt als (x * scale, y * scale).
|
||||
"""
|
||||
x, y, _ = point
|
||||
return (x * scale, y * scale)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Test für rot_2D (z. B. Rotation um 90 Grad = pi / 2)
|
||||
theta = math.pi / 2
|
||||
@@ -0,0 +1,51 @@
|
||||
import pygame
|
||||
|
||||
class Game:
|
||||
|
||||
def __init__(self, title, fps=60, size=(640, 400)):
|
||||
self.title = title
|
||||
self.fps = fps
|
||||
self.size = size
|
||||
self.clock = pygame.time.Clock()
|
||||
self.dt = 0
|
||||
self.screen = None
|
||||
|
||||
def init_game(self):
|
||||
pygame.init()
|
||||
pygame.display.set_caption(self.title)
|
||||
self.screen = pygame.display.set_mode(self.size)
|
||||
|
||||
def game_loop(self):
|
||||
while True:
|
||||
# Berechnung der Zeitdifferenz seit dem letzten Frame
|
||||
self.dt = self.clock.tick(self.fps) / 1000
|
||||
if self.event_handling() == False:
|
||||
break
|
||||
if self.update_game() == False:
|
||||
break
|
||||
self.draw_game()
|
||||
|
||||
def exit_game(self):
|
||||
pygame.quit()
|
||||
|
||||
def event_handling(self): # bleibt in der Unterklasse unverändert
|
||||
for event in pygame.event.get():
|
||||
if not self.handle_event(event):
|
||||
return False
|
||||
return True
|
||||
|
||||
def handle_event(self, event): # wird in der Unterklasse überschrieben
|
||||
if event.type == pygame.QUIT:
|
||||
return False
|
||||
return True
|
||||
|
||||
def update_game(self):
|
||||
return True
|
||||
|
||||
def draw_game(self):
|
||||
pygame.display.flip()
|
||||
|
||||
def run(self):
|
||||
self.init_game()
|
||||
self.game_loop()
|
||||
self.exit_game()
|
||||
Whitespace-only changes.
@@ -0,0 +1,16 @@
|
||||
import pytest
|
||||
from matrixmania.compute import project_ortho
|
||||
|
||||
|
||||
def test_project_ortho_default_scale():
|
||||
# Test ohne Angabe von scale (Standardwert = 1)
|
||||
point = (1.0, 2.0, 3.0)
|
||||
result = project_ortho(point)
|
||||
assert result == (1.0, 2.0)
|
||||
|
||||
|
||||
def test_project_ortho_with_scale():
|
||||
# Test mit scale = 100
|
||||
point = (1.0, 2.0, 3.0)
|
||||
result = project_ortho(point, scale=100)
|
||||
assert result == (100.0, 200.0)
|
||||
@@ -0,0 +1,32 @@
|
||||
import math
|
||||
import pytest
|
||||
from matrixmania.compute import rot_3D
|
||||
|
||||
|
||||
def test_rot_3D_x_axis():
|
||||
# Test bei 0 Radian (Identitätsmatrix)
|
||||
res = rot_3D(0.0, "x")
|
||||
assert res == [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
|
||||
|
||||
# Test bei 90 Grad (PI / 2)
|
||||
res_90 = rot_3D(math.pi / 2, "x")
|
||||
assert pytest.approx(res_90[1][1]) == 0.0
|
||||
assert pytest.approx(res_90[1][2]) == -1.0
|
||||
assert pytest.approx(res_90[2][1]) == 1.0
|
||||
assert pytest.approx(res_90[2][2]) == 0.0
|
||||
|
||||
|
||||
def test_rot_3D_y_axis():
|
||||
res = rot_3D(0.0, "y")
|
||||
assert res == [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
|
||||
|
||||
|
||||
def test_rot_3D_z_axis():
|
||||
res = rot_3D(0.0, "z")
|
||||
assert res == [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
|
||||
|
||||
|
||||
def test_rot_3D_invalid_axis():
|
||||
# Prüft, ob bei ungültiger Achse ein ValueError geworfen wird
|
||||
with pytest.raises(ValueError):
|
||||
rot_3D(1.0, "w")
|
||||
Reference in new issue
Block a user