Initialisiere didaktische Python OOP-Übungsreihe für Nachhilfe (Kapitel 01 bis 06)

This commit is contained in:
Alex Mueller 2026-08-29 12:46:08 +02:00
commit ce35e7d479
28 changed files with 2166 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
__pycache__/
*.py[cod]
*$py.class
.pytest_cache/
.coverage
htmlcov/
.env
venv/
.venv/
.DS_Store

View file

@ -0,0 +1,86 @@
# Kapitel 01: Die erste Klasse – Was sind Klassen & Objekte?
Herzlich willkommen in der Welt der objektorientierten Programmierung (kurz: **OOP**)! 🐶🐱
Bisher hast du in Python mit Variablen, Listen, Dictionaries und Funktionen gearbeitet. Das hat super funktioniert! Doch wenn Programme größer werden, wird es schnell unübersichtlich.
Hier kommen **Klassen** ins Spiel.
---
## 🏗️ Die wichtigste Analogie: Bauplan vs. Objekt
Stell dir vor, du bist Architektin oder Bäckermeisterin:
- Eine **Klasse** ist wie ein **Bauplan** für ein Haus (oder eine **Plätzchen-Ausstechform**). Der Bauplan selbst ist noch kein Haus, in dem man wohnen kann. Er beschreibt nur: *Wie sieht ein Haus aus? Welche Eigenschaften hat es?*
- Ein **Objekt** (auch **Instanz** genannt) ist das **echte Haus**, das nach diesem Bauplan gebaut wurde (oder das gebackene Plätzchen). Aus *einem* Bauplan kannst du beliebig viele verschiedene Häuser bauen!
```
[ KLASSE / BAUPLAN ]
class Haustier:
│
─────────┴─────────
│ │
[ OBJEKT 1 ] [ OBJEKT 2 ]
Bello, Hund, 3 Mimi, Katze, 5
```
---
## 🔍 Warum nicht einfach Dictionaries?
Bisher hättest du Tiere vielleicht so gespeichert:
```python
tier1 = {"name": "Bello", "tierart": "Hund", "alter": 3}
tier2 = {"name": "Mimi", "art": "Katze", "alter": 5} # Ups! Hier heißt der Key "art" statt "tierart"!
```
Beim Dictionary kann man sich schnell bei Schlüsseln vertippen. Eine Klasse gibt uns eine feste Struktur vor.
---
## 💻 Wie sieht eine Klasse in Python aus?
Eine einfache Klasse definierst du mit dem Schlüsselwort `class`:
```python
class Auto:
pass # 'pass' bedeutet: "Hier passiert erst mal nichts"
```
Aus dieser Klasse kannst du nun ein Objekt erzeugen (**Instanziierung**):
```python
# Objekt erstellen:
mein_auto = Auto()
# Eigenschaften (Attribute) zuweisen mit dem Punkt (.):
mein_auto.farbe = "Rot"
mein_auto.marke = "VW"
mein_auto.ps = 110
print(mein_auto.farbe) # Gibt "Rot" aus
```
Du kannst ganz einfach ein zweites Auto bauen:
```python
dein_auto = Auto()
dein_auto.farbe = "Blau"
dein_auto.marke = "BMW"
dein_auto.ps = 180
```
`mein_auto` und `dein_auto` sind zwei völlig eigenständige Objekte, die aus demselben Bauplan `Auto` entstanden sind!
---
## 🎯 Deine Aufgabe
Öffne jetzt die Datei `aufgabe.py` und bearbeite die Aufgaben:
1. Erstelle die Klasse `Haustier`.
2. Erzeuge zwei Haustier-Objekte `bello` und `mimi` mit Eigenschaften.
3. Schreibe zwei kleine Hilfsfunktionen, die mit deinen Haustier-Objekten arbeiten.
### Testen:
Führe deine Tests aus mit:
```bash
python3 test_aufgabe.py
```
Sobald alle Tests bestanden sind, kannst du mit Kapitel 02 weitermachen!

View file

@ -0,0 +1,94 @@
"""
Kapitel 01: Die erste Klasse – Aufgabenblatt
============================================
Bearbeite die Aufgaben Schritt für Schritt von TODO 1 bis TODO 5.
Wenn du fertig bist, überprüfe deine Lösung mit:
python3 test_aufgabe.py
"""
# ==============================================================================
# TODO 1: Definiere eine leere Klasse namens "Haustier".
# Tipp: Verwende das Schlüsselwort 'class' und vorerst 'pass' im Klassenrumpf.
# ==============================================================================
# Schreibe hier deinen Code für TODO 1:
# ==============================================================================
# TODO 2: Erstelle eine Funktion namens "erstelle_bello()".
# Diese Funktion soll:
# 1. Ein neues Haustier-Objekt erzeugen.
# 2. Dem Objekt folgende Attribute zuweisen:
# - name: "Bello"
# - tierart: "Hund"
# - alter: 3
# 3. Das fertige Objekt zurückgeben (return).
# ==============================================================================
def erstelle_bello():
# Schreibe hier deinen Code für TODO 2:
pass
# ==============================================================================
# TODO 3: Erstelle eine Funktion namens "erstelle_mimi()".
# Diese Funktion soll:
# 1. Ein neues Haustier-Objekt erzeugen.
# 2. Dem Objekt folgende Attribute zuweisen:
# - name: "Mimi"
# - tierart: "Katze"
# - alter: 5
# 3. Das fertige Objekt zurückgeben (return).
# ==============================================================================
def erstelle_mimi():
# Schreibe hier deinen Code für TODO 3:
pass
# ==============================================================================
# TODO 4: Schreibe eine Funktion "steckbrief_text(haustier)".
# Die Funktion bekommt ein Haustier-Objekt als Parameter übergeben.
# Sie soll einen formatierten Text im folgenden Format zurückgeben:
# "{name} ist ein(e) {tierart} und ist {alter} Jahre alt."
#
# Beispiel:
# Wenn bello übergeben wird, soll zurückgegeben werden:
# "Bello ist ein(e) Hund und ist 3 Jahre alt."
# ==============================================================================
def steckbrief_text(haustier):
# Schreibe hier deinen Code für TODO 4:
pass
# ==============================================================================
# TODO 5: Schreibe eine Funktion "aelteres_tier(tier1, tier2)".
# Die Funktion bekommt zwei Haustier-Objekte übergeben.
# Sie soll dasjenige Haustier-Objekt zurückgeben, dessen 'alter' größer ist.
# Wenn beide Tiere gleich alt sind, soll tier1 zurückgegeben werden.
# ==============================================================================
def aelteres_tier(tier1, tier2):
# Schreibe hier deinen Code für TODO 5:
pass
# ==============================================================================
# Hauptprogramm zum Ausprobieren:
# (Du kannst diese Datei direkt mit 'python3 aufgabe.py' ausführen)
# ==============================================================================
if __name__ == "__main__":
print("--- Teste deine Haustiere ---")
bello = erstelle_bello()
mimi = erstelle_mimi()
if bello and mimi:
print(steckbrief_text(bello))
print(steckbrief_text(mimi))
aelter = aelteres_tier(bello, mimi)
print(f"Das ältere Tier ist: {aelter.name}")
else:
print("Hinweis: Implementiere erst die TODOs, um die Ausgabe zu sehen!")

View file

@ -0,0 +1,49 @@
"""
Kapitel 01: Die erste Klasse – Musterlösung
===========================================
Hier findest du die vollständige Lösung mit Erklärungen.
"""
# TODO 1: Die Klasse Haustier definieren
class Haustier:
pass
# TODO 2: Ein Haustier-Objekt namens Bello erstellen und Attribute zuweisen
def erstelle_bello():
tier = Haustier()
tier.name = "Bello"
tier.tierart = "Hund"
tier.alter = 3
return tier
# TODO 3: Ein Haustier-Objekt namens Mimi erstellen
def erstelle_mimi():
tier = Haustier()
tier.name = "Mimi"
tier.tierart = "Katze"
tier.alter = 5
return tier
# TODO 4: Steckbrief generieren
def steckbrief_text(haustier):
return f"{haustier.name} ist ein(e) {haustier.tierart} und ist {haustier.alter} Jahre alt."
# TODO 5: Das ältere Tier ermitteln
def aelteres_tier(tier1, tier2):
if tier2.alter > tier1.alter:
return tier2
else:
return tier1
if __name__ == "__main__":
bello = erstelle_bello()
mimi = erstelle_mimi()
print(steckbrief_text(bello))
print(steckbrief_text(mimi))
aelter = aelteres_tier(bello, mimi)
print(f"Das ältere Tier ist: {aelter.name}")

View file

@ -0,0 +1,84 @@
import unittest
import sys
from pathlib import Path
# Sicherstellen, dass das aktuelle Verzeichnis im Modulpfad ist
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel01(unittest.TestCase):
def test_01_klasse_existiert(self):
"""Prüft, ob die Klasse Haustier definiert wurde."""
self.assertTrue(
hasattr(aufgabe, "Haustier"),
"Fehler: Die Klasse 'Haustier' existiert noch nicht. Nutze: class Haustier:"
)
self.assertTrue(
isinstance(aufgabe.Haustier, type),
"Fehler: 'Haustier' sollte eine Klasse sein!"
)
def test_02_erstelle_bello(self):
"""Prüft, ob erstelle_bello() ein korrektes Haustier-Objekt liefert."""
self.assertTrue(hasattr(aufgabe, "erstelle_bello"), "Fehler: Funktion 'erstelle_bello' fehlt!")
bello = aufgabe.erstelle_bello()
self.assertIsNotNone(bello, "Fehler: 'erstelle_bello()' gibt None zurück. Vergiss nicht das 'return'!")
self.assertIsInstance(bello, aufgabe.Haustier, "Fehler: 'bello' muss eine Instanz der Klasse 'Haustier' sein!")
self.assertEqual(getattr(bello, "name", None), "Bello", "Fehler: 'bello.name' muss 'Bello' sein!")
self.assertEqual(getattr(bello, "tierart", None), "Hund", "Fehler: 'bello.tierart' muss 'Hund' sein!")
self.assertEqual(getattr(bello, "alter", None), 3, "Fehler: 'bello.alter' muss 3 sein!")
def test_03_erstelle_mimi(self):
"""Prüft, ob erstelle_mimi() ein korrektes Haustier-Objekt liefert."""
self.assertTrue(hasattr(aufgabe, "erstelle_mimi"), "Fehler: Funktion 'erstelle_mimi' fehlt!")
mimi = aufgabe.erstelle_mimi()
self.assertIsNotNone(mimi, "Fehler: 'erstelle_mimi()' gibt None zurück. Vergiss nicht das 'return'!")
self.assertIsInstance(mimi, aufgabe.Haustier, "Fehler: 'mimi' muss eine Instanz der Klasse 'Haustier' sein!")
self.assertEqual(getattr(mimi, "name", None), "Mimi", "Fehler: 'mimi.name' muss 'Mimi' sein!")
self.assertEqual(getattr(mimi, "tierart", None), "Katze", "Fehler: 'mimi.tierart' muss 'Katze' sein!")
self.assertEqual(getattr(mimi, "alter", None), 5, "Fehler: 'mimi.alter' muss 5 sein!")
def test_04_steckbrief_text(self):
"""Prüft, ob steckbrief_text() den richtigen String formatiert."""
self.assertTrue(hasattr(aufgabe, "steckbrief_text"), "Fehler: Funktion 'steckbrief_text' fehlt!")
tier = aufgabe.Haustier()
tier.name = "Rocky"
tier.tierart = "Papagei"
tier.alter = 2
ergebnis = aufgabe.steckbrief_text(tier)
erwartet = "Rocky ist ein(e) Papagei und ist 2 Jahre alt."
self.assertEqual(
ergebnis,
erwartet,
f"Fehler: Textausgabe weicht ab!\nErwartet: '{erwartet}'\nErhalten: '{ergebnis}'"
)
def test_05_aelteres_tier(self):
"""Prüft die Altersvergleich-Funktion."""
self.assertTrue(hasattr(aufgabe, "aelteres_tier"), "Fehler: Funktion 'aelteres_tier' fehlt!")
t1 = aufgabe.Haustier()
t1.name = "Tier A"
t1.alter = 4
t2 = aufgabe.Haustier()
t2.name = "Tier B"
t2.alter = 7
t3 = aufgabe.Haustier()
t3.name = "Tier C"
t3.alter = 4
self.assertIs(aufgabe.aelteres_tier(t1, t2), t2, "Fehler: Tier B (7) ist älter als Tier A (4)!")
self.assertIs(aufgabe.aelteres_tier(t2, t1), t2, "Fehler: Tier B (7) ist älter als Tier A (4)!")
self.assertIs(aufgabe.aelteres_tier(t1, t3), t1, "Fehler: Bei gleichem Alter soll tier1 zurückgegeben werden!")
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -0,0 +1,90 @@
# Kapitel 02: Der Konstruktor `__init__` und das geheimnisvolle `self` 💳
Im ersten Kapitel haben wir Attribute noch manuell nach dem Erstellen zugewiesen:
```python
bello = Haustier()
bello.name = "Bello"
bello.alter = 3
```
Das ist mühsam und fehleranfällig: Was passiert, wenn man vergisst, `.alter` zu setzen? Richtig: Das Programm stürzt ab, sobald jemand darauf zugreift!
Deshalb gibt es in Python den **Konstruktor**: `__init__`.
---
## 🛠️ Was ist `__init__`?
`__init__` steht für *initialisieren* (vorbereiten/startklar machen).
Es ist eine **spezielle Methode**, die Python **automatisch aufruft**, sobald du ein neues Objekt erstellst:
```python
class Haustier:
def __init__(self, name, tierart, alter):
self.name = name
self.tierart = tierart
self.alter = alter
# Jetzt können wir die Werte direkt in die Klammern schreiben!
bello = Haustier("Bello", "Hund", 3)
mimi = Haustier("Mimi", "Katze", 5)
print(bello.name) # Gibt sofort "Bello" aus
```
Die doppelten Unterstriche `__` nennt man in Python auch **Dunder** (Double Underscore). Sie signalisieren besondere Python-Funktionen.
---
## 🪞 Was bedeutet eigentlich `self`?
`self` verwirrt am Anfang fast jeden – ist aber ganz einfach:
- `self` bedeutet auf Deutsch **"ich selbst"** oder **"dieses Objekt hier"**.
- Wenn du `bello = Haustier("Bello", "Hund", 3)` aufrufst, übergibt Python das neu entstandene `bello`-Objekt automatisch als erstes Argument `self`.
- `self.name = name` bedeutet also: *"Speichere den übergebenen Namen in MEINEM EIGENEN Attribut `.name` ab!"*
```
Haustier("Bello", "Hund", 3)
│
▼
def __init__(self, name, tierart, alter):
▲ │ │ │
│ ▼ ▼ ▼
bello "Bello" "Hund" 3
│
├── bello.name = "Bello"
├── bello.tierart = "Hund"
└── bello.alter = 3
```
---
## ⚙️ Standardwerte (Default Parameters)
Manchmal hat ein Attribut einen typischen Startwert (z.B. ein neues Bankkonto startet meist bei `0.0` Euro):
```python
class Bankkonto:
def __init__(self, inhaber, kontostand=0.0):
self.inhaber = inhaber
self.kontostand = kontostand
# Konto mit 0.0 Euro Startguthaben:
k1 = Bankkonto("Anna")
# Konto mit speziellem Startguthaben:
k2 = Bankkonto("Ben", 50.0)
```
---
## 🎯 Deine Aufgabe: Das Bankkonto
Öffne `aufgabe.py`. Du programmierst eine Klasse `Bankkonto`:
1. Schreibe den Konstruktor `__init__`.
2. Ergänze Methoden zum Einzahlen (`einzahlen`) und Auszahlen (`auszahlen`), die darauf achten, dass man nicht ins Minus rutscht.
3. Ergänze eine Methode `info_text()`.
### Testen:
```bash
python3 test_aufgabe.py
```

View file

@ -0,0 +1,77 @@
"""
Kapitel 02: Der Konstruktor __init__ und self
==============================================
In dieser Aufgabe erstellst du eine Klasse "Bankkonto" für eine Taschengeld-App.
"""
class Bankkonto:
# ==========================================================================
# TODO 1: Schreibe die __init__-Methode.
# Parameter: self, inhaber (String), kontostand (Float, Standardwert 0.0)
#
# Speichere die Parameter als Attribute an 'self':
# - self.inhaber
# - self.kontostand
# ==========================================================================
def __init__(self, inhaber, kontostand=0.0):
# Schreibe hier deinen Code für TODO 1:
pass
# ==========================================================================
# TODO 2: Schreibe die Methode "einzahlen(self, betrag)".
# Regeln:
# 1. Wenn 'betrag' größer als 0 ist:
# - Erhöhe 'self.kontostand' um 'betrag'.
# - Gib True zurück (Einzahlung erfolgreich).
# 2. Wenn 'betrag' kleiner oder gleich 0 ist:
# - Verändere den Kontostand nicht.
# - Gib False zurück (ungültiger Betrag).
# ==========================================================================
def einzahlen(self, betrag):
# Schreibe hier deinen Code für TODO 2:
pass
# ==========================================================================
# TODO 3: Schreibe die Methode "auszahlen(self, betrag)".
# Regeln:
# 1. Der Betrag muss größer als 0 sein UND es muss genug Geld auf dem Konto
# sein (self.kontostand >= betrag).
# - Ziehe 'betrag' von 'self.kontostand' ab.
# - Gib True zurück (Auszahlung erfolgreich).
# 2. Andernfalls (nicht genug Geld oder ungültiger Betrag):
# - Verändere den Kontostand nicht.
# - Gib False zurück.
# ==========================================================================
def auszahlen(self, betrag):
# Schreibe hier deinen Code für TODO 3:
pass
# ==========================================================================
# TODO 4: Schreibe die Methode "info_text(self)".
# Sie soll einen String im folgenden Format zurückgeben:
# "Konto von {inhaber}: {kontostand:.2f} Euro"
#
# Beispiel: Bei inhaber="Mia" und kontostand=25.5 -> "Konto von Mia: 25.50 Euro"
# ==========================================================================
def info_text(self):
# Schreibe hier deinen Code für TODO 4:
pass
# ==============================================================================
# Hauptprogramm zum Ausprobieren:
# ==============================================================================
if __name__ == "__main__":
print("--- Teste dein Bankkonto ---")
konto = Bankkonto("Mia", 20.0)
print(konto.info_text())
erfolg = konto.einzahlen(15.5)
print(f"15.50€ eingezahlt? {erfolg} -> Neuer Stand: {konto.kontostand}€")
erfolg = konto.auszahlen(50.0)
print(f"50.00€ ausgezahlt? {erfolg} (sollte False sein) -> Stand: {konto.kontostand}€")
erfolg = konto.auszahlen(10.0)
print(f"10.00€ ausgezahlt? {erfolg} -> Stand: {konto.kontostand}€")

View file

@ -0,0 +1,37 @@
"""
Kapitel 02: Der Konstruktor __init__ und self – Musterlösung
=============================================================
"""
class Bankkonto:
# TODO 1: Konstruktor
def __init__(self, inhaber, kontostand=0.0):
self.inhaber = inhaber
self.kontostand = float(kontostand)
# TODO 2: Einzahlen mit Betragsüberprüfung
def einzahlen(self, betrag):
if betrag > 0:
self.kontostand += betrag
return True
return False
# TODO 3: Auszahlen mit Guthabenüberprüfung
def auszahlen(self, betrag):
if betrag > 0 and self.kontostand >= betrag:
self.kontostand -= betrag
return True
return False
# TODO 4: Formatierter Info-Text
def info_text(self):
return f"Konto von {self.inhaber}: {self.kontostand:.2f} Euro"
if __name__ == "__main__":
konto = Bankkonto("Mia", 20.0)
print(konto.info_text())
konto.einzahlen(15.5)
print(konto.info_text())
konto.auszahlen(10.0)
print(konto.info_text())

View file

@ -0,0 +1,85 @@
import unittest
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel02(unittest.TestCase):
def test_01_init_standardwert(self):
"""Prüft __init__ mit Standardwert für kontostand."""
konto = aufgabe.Bankkonto("Lukas")
self.assertEqual(konto.inhaber, "Lukas", "Fehler: self.inhaber wurde nicht korrekt gesetzt!")
self.assertEqual(konto.kontostand, 0.0, "Fehler: Standardwert für kontostand sollte 0.0 sein!")
def test_02_init_mit_startguthaben(self):
"""Prüft __init__ mit übergebenem Startguthaben."""
konto = aufgabe.Bankkonto("Sarah", 100.50)
self.assertEqual(konto.inhaber, "Sarah")
self.assertEqual(konto.kontostand, 100.50)
def test_03_einzahlen_gueltig(self):
"""Prüft erfolgreiches Einzahlen."""
konto = aufgabe.Bankkonto("Tim", 50.0)
erfolg = konto.einzahlen(25.0)
self.assertTrue(erfolg, "Fehler: einzahlen(25.0) sollte True zurückgeben!")
self.assertEqual(konto.kontostand, 75.0, "Fehler: Kontostand sollte nach 25€ Einzahlung auf 50€ bei 75.0€ liegen!")
def test_04_einzahlen_ungueltig(self):
"""Prüft Einzahlen von negativen Beträgen oder 0."""
konto = aufgabe.Bankkonto("Tim", 50.0)
erfolg_null = konto.einzahlen(0)
self.assertFalse(erfolg_null, "Fehler: Einzahlen von 0 sollte False zurückgeben!")
self.assertEqual(konto.kontostand, 50.0, "Fehler: Kontostand darf sich bei 0€ nicht ändern!")
erfolg_neg = konto.einzahlen(-10.0)
self.assertFalse(erfolg_neg, "Fehler: Einzahlen von negativen Beträgen sollte False zurückgeben!")
self.assertEqual(konto.kontostand, 50.0, "Fehler: Kontostand darf sich bei negativem Betrag nicht ändern!")
def test_05_auszahlen_erfolgreich(self):
"""Prüft erfolgreiches Auszahlen."""
konto = aufgabe.Bankkonto("Emma", 80.0)
erfolg = konto.auszahlen(30.0)
self.assertTrue(erfolg, "Fehler: auszahlen(30.0) bei 80€ Guthaben sollte True zurückgeben!")
self.assertEqual(konto.kontostand, 50.0, "Fehler: Kontostand sollte nach Auszahlung 50.0€ sein!")
# Gesamtes Guthaben abheben
erfolg_alles = konto.auszahlen(50.0)
self.assertTrue(erfolg_alles, "Fehler: Gesamtes Guthaben abheben sollte möglich sein!")
self.assertEqual(konto.kontostand, 0.0)
def test_06_auszahlen_zu_viel_oder_ungueltig(self):
"""Prüft Auszahlen bei unzureichendem Guthaben oder negativem Betrag."""
konto = aufgabe.Bankkonto("Emma", 20.0)
# Zu viel abheben
erfolg = konto.auszahlen(50.0)
self.assertFalse(erfolg, "Fehler: Auszahlen von mehr Geld als vorhanden ist muss False liefern!")
self.assertEqual(konto.kontostand, 20.0, "Fehler: Kontostand darf sich nicht ändern, wenn Auszahlung fehlschlägt!")
# Negativer Betrag
erfolg_neg = konto.auszahlen(-5.0)
self.assertFalse(erfolg_neg, "Fehler: Auszahlen von negativen Beträgen muss False liefern!")
self.assertEqual(konto.kontostand, 20.0)
def test_07_info_text(self):
"""Prüft die Formatierung der info_text-Methode."""
konto = aufgabe.Bankkonto("Mia", 25.5)
text = konto.info_text()
erwartet = "Konto von Mia: 25.50 Euro"
self.assertEqual(
text,
erwartet,
f"Fehler: Text weicht ab!\nErwartet: '{erwartet}'\nErhalten: '{text}'"
)
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -0,0 +1,69 @@
# Kapitel 03: Methoden & Verhalten – Wenn Objekte lebendig werden ⚔️
Bisher haben unsere Objekte hauptsächlich Daten gespeichert (wie Name oder Kontostand). Aber Objekte können noch viel mehr: **Sie können Aktionen ausführen!**
Funktionen, die innerhalb einer Klasse definiert sind, nennt man **Methoden**.
---
## 🏃‍♀️ Was unterscheidet eine Methode von einer normalen Funktion?
1. Eine Methode steht **eingerückt in der Klasse**.
2. Das **erste Argument ist immer `self`**.
3. Über `self` hat die Methode direkten Zugriff auf alle Eigenschaften des Objekts und kann diese verändern.
```python
class Zauberer:
def __init__(self, name, mana=50):
self.name = name
self.mana = mana
# Das hier ist eine Methode:
def zauberspruch_wirken(self, mana_kosten):
if self.mana >= mana_kosten:
self.mana -= mana_kosten
print(f"{self.name} wirkt einen Zauber! Verbleibendes Mana: {self.mana}")
return True
else:
print(f"{self.name} hat nicht genug Mana!")
return False
merlin = Zauberer("Merlin", 30)
merlin.zauberspruch_wirken(10) # self wird automatisch merlin!
```
---
## 🤝 Wenn Objekte miteinander interagieren!
Das Spannendste an der objektorientierten Programmierung ist, wenn **ein Objekt mit einem anderen Objekt interagiert**.
Du kannst ein ganzes Objekt als Parameter an die Methode eines anderen Objekts übergeben:
```python
class Spieler:
def __init__(self, name, geld):
self.name = name
self.geld = geld
def bezahle_an(self, empfaenger, betrag):
if self.geld >= betrag:
self.geld -= betrag
empfaenger.geld += betrag # Wir greifen direkt auf das andere Objekt zu!
return True
return False
```
---
## 🎯 Deine Aufgabe: Das RPG-Duell
In `aufgabe.py` erstellst du eine Klasse `Held` für ein Rollenspiel:
- Ein Held hat `name`, `leben`, `max_leben` und `angriffskraft`.
- Ein Held kann Schaden erleiden (`schaden_erleiden`), geheilt werden (`heilen`) und einen anderen Helden angreifen (`angreifen(gegner)`).
- Du sorgst mit Bedingungen dafür, dass die Lebenspunkte nicht unter `0` fallen und nicht über `max_leben` steigen!
### Testen:
```bash
python3 test_aufgabe.py
```

View file

@ -0,0 +1,101 @@
"""
Kapitel 03: Methoden & Verhalten – Helden-Duell
==============================================
In dieser Aufgabe programmierst du eine Klasse 'Held' für ein Rollenspiel.
"""
class Held:
# ==========================================================================
# TODO 1: Konstruktor __init__(self, name, leben=100, angriffskraft=15)
# Speichere:
# - self.name = name
# - self.leben = leben
# - self.max_leben = leben (das maximale Leben entspricht dem Startleben)
# - self.angriffskraft = angriffskraft
# - self.ist_am_leben = True
# ==========================================================================
def __init__(self, name, leben=100, angriffskraft=15):
# Schreibe hier deinen Code für TODO 1:
pass
# ==========================================================================
# TODO 2: Methode "schaden_erleiden(self, schaden)"
# Regeln:
# 1. Wenn der Held bereits besiegt ist (not self.ist_am_leben) oder schaden <= 0:
# - Es passiert nichts, gib 0 zurück.
# 2. Ziehe 'schaden' von 'self.leben' ab.
# 3. Wenn 'self.leben' <= 0 wird:
# - Setze 'self.leben' genau auf 0.
# - Setze 'self.ist_am_leben' auf False.
# 4. Gib den erlittenen Schaden (int/float) zurück.
# ==========================================================================
def schaden_erleiden(self, schaden):
# Schreibe hier deinen Code für TODO 2:
pass
# ==========================================================================
# TODO 3: Methode "heilen(self, heilung)"
# Regeln:
# 1. Wenn der Held besiegt ist (not self.ist_am_leben) oder heilung <= 0:
# - Tote Helden können nicht geheilt werden! Gib 0 zurück.
# 2. Erhöhe das Leben um 'heilung', aber ACHTUNG:
# - 'self.leben' darf niemals größer als 'self.max_leben' werden!
# - Beispiel: Wenn leben=90, max_leben=100 und heilung=20:
# Das Leben wird 100 (nicht 110) und die tatsächliche Heilung war 10.
# 3. Gib die tatsächlich geheilte Menge an Lebenspunkten zurück.
# ==========================================================================
def heilen(self, heilung):
# Schreibe hier deinen Code für TODO 3:
pass
# ==========================================================================
# TODO 4: Methode "angreifen(self, gegner)"
# Parameter 'gegner' ist ein anderes Held-Objekt!
# Regeln:
# 1. Prüfe, ob BEIDE Helden noch am Leben sind (self.ist_am_leben und gegner.ist_am_leben).
# 2. Wenn ja:
# - Rufe auf dem 'gegner' die Methode 'schaden_erleiden' auf mit self.angriffskraft als Wert.
# - Gib True zurück.
# 3. Wenn einer von beiden (oder beide) nicht mehr am Leben ist:
# - Greife nicht an und gib False zurück.
# ==========================================================================
def angreifen(self, gegner):
# Schreibe hier deinen Code für TODO 4:
pass
# ==========================================================================
# TODO 5: Methode "status_text(self)"
# Gibt einen formatierten String zurück:
# Wenn am Leben:
# "[{self.name}] HP: {self.leben}/{self.max_leben} | Kraft: {self.angriffskraft} | Status: Lebendig"
# Wenn besiegt:
# "[{self.name}] HP: 0/{self.max_leben} | Kraft: {self.angriffskraft} | Status: Besiegt"
# ==========================================================================
def status_text(self):
# Schreibe hier deinen Code für TODO 5:
pass
# ==============================================================================
# Kampfsimulator zum Ausprobieren:
# ==============================================================================
if __name__ == "__main__":
print("⚔️ --- ARENA DER HELDEN --- ⚔️")
ritter = Held("Arthur", leben=80, angriffskraft=20)
drache = Held("Feuerspei", leben=120, angriffskraft=25)
print(ritter.status_text())
print(drache.status_text())
print("\n--- Runde 1 ---")
ritter.angreifen(drache)
print(drache.status_text())
drache.angreifen(ritter)
print(ritter.status_text())
print("\n--- Arthur trinkt einen Heiltrank ---")
geheilt = ritter.heilen(15)
print(f"Arthur heilt sich um {geheilt} HP!")
print(ritter.status_text())

View file

@ -0,0 +1,51 @@
"""
Kapitel 03: Methoden & Verhalten – Helden-Duell – Musterlösung
=============================================================
"""
class Held:
def __init__(self, name, leben=100, angriffskraft=15):
self.name = name
self.leben = leben
self.max_leben = leben
self.angriffskraft = angriffskraft
self.ist_am_leben = True
def schaden_erleiden(self, schaden):
if not self.ist_am_leben or schaden <= 0:
return 0
tatsaechlicher_schaden = min(schaden, self.leben)
self.leben -= schaden
if self.leben <= 0:
self.leben = 0
self.ist_am_leben = False
return tatsaechlicher_schaden
def heilen(self, heilung):
if not self.ist_am_leben or heilung <= 0:
return 0
alter_wert = self.leben
self.leben = min(self.max_leben, self.leben + heilung)
tatsaechlich_geheilt = self.leben - alter_wert
return tatsaechlich_geheilt
def angreifen(self, gegner):
if self.ist_am_leben and gegner.ist_am_leben:
gegner.schaden_erleiden(self.angriffskraft)
return True
return False
def status_text(self):
status_str = "Lebendig" if self.ist_am_leben else "Besiegt"
return f"[{self.name}] HP: {self.leben}/{self.max_leben} | Kraft: {self.angriffskraft} | Status: {status_str}"
if __name__ == "__main__":
ritter = Held("Arthur", 80, 20)
drache = Held("Feuerspei", 120, 25)
print(ritter.status_text())
print(drache.status_text())
ritter.angreifen(drache)
print(drache.status_text())

View file

@ -0,0 +1,101 @@
import unittest
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel03(unittest.TestCase):
def test_01_init(self):
"""Prüft die Initialisierung des Helden."""
h = aufgabe.Held("Galahad", 90, 18)
self.assertEqual(h.name, "Galahad")
self.assertEqual(h.leben, 90)
self.assertEqual(h.max_leben, 90)
self.assertEqual(h.angriffskraft, 18)
self.assertTrue(h.ist_am_leben)
def test_02_schaden_erleiden(self):
"""Prüft Schadensberechnung und K.O.-Logik."""
h = aufgabe.Held("Krieger", 50, 10)
# Normaler Schaden
schaden = h.schaden_erleiden(20)
self.assertEqual(schaden, 20)
self.assertEqual(h.leben, 30)
self.assertTrue(h.ist_am_leben)
# Schaden, der das Leben auf 0 oder darunter bringt
h.schaden_erleiden(40)
self.assertEqual(h.leben, 0, "Leben darf nach Über-Schaden nicht negativ werden, sondern muss 0 sein!")
self.assertFalse(h.ist_am_leben, "Held muss nach 0 HP als besiegt (ist_am_leben=False) markiert werden!")
# Schaden an bereits besiegtem Helden
schaden_tot = h.schaden_erleiden(10)
self.assertEqual(schaden_tot, 0)
self.assertEqual(h.leben, 0)
def test_03_heilen(self):
"""Prüft die Heilungsfunktion und max_leben Begrenzung."""
h = aufgabe.Held("Priester", 100, 5)
h.leben = 70
# Teilweise Heilung
geheilt = h.heilen(15)
self.assertEqual(geheilt, 15)
self.assertEqual(h.leben, 85)
# Heilung über max_leben hinaus
geheilt_cap = h.heilen(30)
self.assertEqual(geheilt_cap, 15, "Es sollten nur 15 HP geheilt werden, da max_leben=100!")
self.assertEqual(h.leben, 100)
# Heilung eines toten Helden
h.leben = 0
h.ist_am_leben = False
geheilt_tot = h.heilen(50)
self.assertEqual(geheilt_tot, 0, "Ein besiegter Held kann nicht geheilt werden!")
self.assertEqual(h.leben, 0)
def test_04_angreifen(self):
"""Prüft die Interaktion beim Angriff."""
held1 = aufgabe.Held("Held", 50, 20)
held2 = aufgabe.Held("Monster", 30, 10)
erfolg = held1.angreifen(held2)
self.assertTrue(erfolg)
self.assertEqual(held2.leben, 10)
self.assertTrue(held2.ist_am_leben)
# Zweiter Angriff besiegt Monster
erfolg2 = held1.angreifen(held2)
self.assertTrue(erfolg2)
self.assertEqual(held2.leben, 0)
self.assertFalse(held2.ist_am_leben)
# Weiterer Angriff auf besiegtes Monster schlägt fehl
erfolg3 = held1.angreifen(held2)
self.assertFalse(erfolg3, "Ein Angriff auf ein besiegtes Ziel sollte False zurückgeben!")
def test_05_status_text(self):
"""Prüft die Statusanzeige."""
h = aufgabe.Held("Arthur", 100, 20)
self.assertEqual(
h.status_text(),
"[Arthur] HP: 100/100 | Kraft: 20 | Status: Lebendig"
)
h.schaden_erleiden(100)
self.assertEqual(
h.status_text(),
"[Arthur] HP: 0/100 | Kraft: 20 | Status: Besiegt"
)
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -0,0 +1,77 @@
# Kapitel 04: Schöne Ausgaben mit `__str__` 🧾
Hast du schon mal versucht, ein selbst erstelltes Objekt direkt mit `print()` auszugeben?
```python
class Buch:
def __init__(self, titel, autor):
self.titel = titel
self.autor = autor
mein_buch = Buch("Harry Potter", "J.K. Rowling")
print(mein_buch)
```
Die Ausgabe sieht dann so aus:
```
<__main__.Buch object at 0x7fa2b0485d90>
```
Das ist kryptisch und hilft im Alltag nicht weiter. Aber Python hat dafür eine geniale Dunder-Methode: **`__str__`**!
---
## ✨ Die `__str__`-Methode
Wann immer Python ein Objekt in einen Text umwandeln soll (z.B. bei `print(mein_objekt)` oder `str(mein_objekt)` oder in einem f-String `f"Das Buch: {mein_objekt}"`), schaut Python nach, ob deine Klasse eine `__str__`-Methode hat.
Wenn ja, ruft Python sie automatisch auf:
```python
class Buch:
def __init__(self, titel, autor):
self.titel = titel
self.autor = autor
def __str__(self):
# Muss immer einen STRING zurückgeben (return)!
return f"'{self.titel}' von {self.autor}"
mein_buch = Buch("Harry Potter", "J.K. Rowling")
print(mein_buch) # Ausgabe: 'Harry Potter' von J.K. Rowling 🎉
```
---
## 🧺 Objekte in Listen verwalten
Oft hat man Objekte, die eine Liste von *anderen* Objekten verwalten (z.B. ein Warenkorb, der Artikel enthält, oder ein Bus, der Fahrgäste enthält):
```python
class Schultasche:
def __init__(self):
self.buecher = [] # Startet mit einer leeren Liste
def buch_einpacken(self, buch):
self.buecher.append(buch)
def inhalt_anzeigen(self):
for b in self.buecher:
print(f"- {b}") # Hier wird automatisch b.__str__() genutzt!
```
---
## 🎯 Deine Aufgabe: Der Supermarkt-Kassenzettel
In `aufgabe.py` programmierst du zwei Klassen:
1. `Artikel`: Ein Produkt mit Name, Einzelpreis und Anzahl.
- `__str__`: Erzeugt einen Kassenbon-Text wie `"3x Apfel (je 0.50 €) = 1.50 €"`.
- `gesamtpreis()`: Berechnet den Preis für diesen Artikel (`preis * anzahl`).
2. `Warenkorb`: Verwaltet eine Liste von `Artikel`-Objekten.
- `artikel_hinzufuegen()`: Packt einen Artikel in den Warenkorb.
- `gesamtsumme()`: Berechnet die Gesamtsumme aller Artikel.
- `bon_text()`: Erzeugt einen formatierten mehrzeiligen Kassenbon.
### Testen:
```bash
python3 test_aufgabe.py
```

View file

@ -0,0 +1,105 @@
"""
Kapitel 04: __str__ und Objektlisten – Supermarkt & Warenkorb
============================================================
In dieser Aufgabe erstellst du zwei Klassen: 'Artikel' und 'Warenkorb'.
"""
class Artikel:
# ==========================================================================
# TODO 1: Konstruktor für Artikel
# Parameter: self, name (str), preis (float), anzahl (int, Standardwert 1)
# Speichere: self.name, self.preis, self.anzahl
# ==========================================================================
def __init__(self, name, preis, anzahl=1):
# Schreibe hier deinen Code für TODO 1:
pass
# ==========================================================================
# TODO 2: Methode "gesamtpreis(self)"
# Gibt den Preis für diesen Artikel multipliziert mit der Anzahl zurück.
# Beispiel: 3 Äpfel à 0.50€ -> 1.50
# ==========================================================================
def gesamtpreis(self):
# Schreibe hier deinen Code für TODO 2:
pass
# ==========================================================================
# TODO 3: Dunder-Methode "__str__(self)"
# Gibt einen String im folgenden Format zurück:
# "{anzahl}x {name} (je {preis:.2f} €) = {gesamtpreis:.2f} €"
#
# Beispiel:
# Bei name="Milch", preis=1.29, anzahl=2:
# -> "2x Milch (je 1.29 €) = 2.58 €"
# ==========================================================================
def __str__(self):
# Schreibe hier deinen Code für TODO 3:
pass
class Warenkorb:
# ==========================================================================
# TODO 4: Konstruktor für Warenkorb
# Parameter: self
# Initialisiere: self.artikel_liste als leere Liste []
# ==========================================================================
def __init__(self):
# Schreibe hier deinen Code für TODO 4:
pass
# ==========================================================================
# TODO 5: Methode "artikel_hinzufuegen(self, artikel)"
# Fügt das übergebene Artikel-Objekt an 'self.artikel_liste' an (.append).
# ==========================================================================
def artikel_hinzufuegen(self, artikel):
# Schreibe hier deinen Code für TODO 5:
pass
# ==========================================================================
# TODO 6: Methode "gesamtsumme(self)"
# Berechnet die Summe der Gesamtpreise aller Artikel in self.artikel_liste
# und gibt diese als float zurück. (Tipp: Schleife oder sum())
# Bei leerem Warenkorb soll 0.0 zurückgegeben werden.
# ==========================================================================
def gesamtsumme(self):
# Schreibe hier deinen Code für TODO 6:
pass
# ==========================================================================
# TODO 7: Methode "bon_text(self)"
# Erzeugt einen formatierten mehrzeiligen String für den Kassenzettel.
# Format:
# --- KASSENZETTEL ---
# 2x Milch (je 1.29 €) = 2.58 €
# 1x Brot (je 2.49 €) = 2.49 €
# --------------------
# Gesamtsumme: 5.07 €
#
# Tipp: Baue eine Liste von Zeilen und verbinde sie mit "\n".join(...)
# Die Zeilen für die Artikel erhältst du einfach mit str(artikel)!
# ==========================================================================
def bon_text(self):
# Schreibe hier deinen Code für TODO 7:
pass
# ==============================================================================
# Hauptprogramm zum Ausprobieren:
# ==============================================================================
if __name__ == "__main__":
print("🛒 --- SUPERMARKT EINKAUF --- 🛒")
korb = Warenkorb()
a1 = Artikel("Milch", 1.29, 2)
a2 = Artikel("Brot", 2.49, 1)
a3 = Artikel("Schokolade", 0.99, 3)
korb.artikel_hinzufuegen(a1)
korb.artikel_hinzufuegen(a2)
korb.artikel_hinzufuegen(a3)
if korb.artikel_liste:
print(korb.bon_text())
else:
print("Hinweis: Implementiere die TODOs, um den Kassenbon zu drucken!")

View file

@ -0,0 +1,43 @@
"""
Kapitel 04: __str__ und Objektlisten – Musterlösung
===================================================
"""
class Artikel:
def __init__(self, name, preis, anzahl=1):
self.name = name
self.preis = float(preis)
self.anzahl = int(anzahl)
def gesamtpreis(self):
return self.preis * self.anzahl
def __str__(self):
return f"{self.anzahl}x {self.name} (je {self.preis:.2f} €) = {self.gesamtpreis():.2f} €"
class Warenkorb:
def __init__(self):
self.artikel_liste = []
def artikel_hinzufuegen(self, artikel):
self.artikel_liste.append(artikel)
def gesamtsumme(self):
return sum(item.gesamtpreis() for item in self.artikel_liste)
def bon_text(self):
zeilen = ["--- KASSENZETTEL ---"]
for artikel in self.artikel_liste:
zeilen.append(str(artikel))
zeilen.append("--------------------")
zeilen.append(f"Gesamtsumme: {self.gesamtsumme():.2f} €")
return "\n".join(zeilen)
if __name__ == "__main__":
korb = Warenkorb()
korb.artikel_hinzufuegen(Artikel("Milch", 1.29, 2))
korb.artikel_hinzufuegen(Artikel("Brot", 2.49, 1))
korb.artikel_hinzufuegen(Artikel("Schokolade", 0.99, 3))
print(korb.bon_text())

View file

@ -0,0 +1,68 @@
import unittest
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel04(unittest.TestCase):
def test_01_artikel_init_und_gesamtpreis(self):
"""Prüft Artikel-Konstruktor und gesamtpreis."""
a = aufgabe.Artikel("Apfel", 0.60, 4)
self.assertEqual(a.name, "Apfel")
self.assertEqual(a.preis, 0.60)
self.assertEqual(a.anzahl, 4)
self.assertAlmostEqual(a.gesamtpreis(), 2.40, places=2)
def test_02_artikel_str(self):
"""Prüft die __str__ Implementierung von Artikel."""
a = aufgabe.Artikel("Milch", 1.29, 2)
erwartet = "2x Milch (je 1.29 €) = 2.58 €"
self.assertEqual(str(a), erwartet)
def test_03_warenkorb_init(self):
"""Prüft, ob der Warenkorb mit einer leeren Liste startet."""
korb = aufgabe.Warenkorb()
self.assertTrue(hasattr(korb, "artikel_liste"))
self.assertEqual(korb.artikel_liste, [])
def test_04_artikel_hinzufuegen_und_gesamtsumme(self):
"""Prüft das Hinzufügen von Artikeln und die Gesamtsumme."""
korb = aufgabe.Warenkorb()
self.assertAlmostEqual(korb.gesamtsumme(), 0.0, places=2)
a1 = aufgabe.Artikel("Brot", 2.50, 2) # 5.00 €
a2 = aufgabe.Artikel("Butter", 1.80, 1) # 1.80 €
korb.artikel_hinzufuegen(a1)
korb.artikel_hinzufuegen(a2)
self.assertEqual(len(korb.artikel_liste), 2)
self.assertAlmostEqual(korb.gesamtsumme(), 6.80, places=2)
def test_05_bon_text(self):
"""Prüft den formatierten Bon-Text."""
korb = aufgabe.Warenkorb()
korb.artikel_hinzufuegen(aufgabe.Artikel("Kaffee", 4.50, 1))
korb.artikel_hinzufuegen(aufgabe.Artikel("Keks", 1.20, 3))
bon = korb.bon_text()
erwartete_zeilen = [
"--- KASSENZETTEL ---",
"1x Kaffee (je 4.50 €) = 4.50 €",
"3x Keks (je 1.20 €) = 3.60 €",
"--------------------",
"Gesamtsumme: 8.10 €"
]
erwartet = "\n".join(erwartete_zeilen)
self.assertEqual(bon.strip(), erwartet.strip())
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -0,0 +1,71 @@
# Kapitel 05: Komposition – Objekte kombinieren & filtern 🎵
In der echten Softwareentwicklung (wie bei Spotify, Instagram oder Netflix) besteht ein System fast immer aus vielen verschiedenen Klassen, die zusammenarbeiten.
Wenn eine Klasse andere Objekte besitzt oder verwaltet, nennt man das **Komposition** (oder *"hat-ein"-Beziehung*):
- Eine **Playlist** *hat viele* **Songs**.
- Ein **Kino** *hat viele* **Säle** und **Filme**.
- Eine **Schule** *hat viele* **Klassen** und **Schüler**.
---
## 🎶 Das Zusammenspiel: Song & Playlist
Schauen wir uns an, wie eine Playlist mit Songs arbeiten kann:
```python
class Song:
def __init__(self, titel, dauer_sekunden):
self.titel = titel
self.dauer_sekunden = dauer_sekunden
class Playlist:
def __init__(self, name):
self.name = name
self.songs = [] # Hier landen die Song-Objekte
def hinzufuegen(self, song):
self.songs.append(song)
def finde_lange_songs(self, min_sekunden):
gefundene = []
for song in self.songs:
if song.dauer_sekunden >= min_sekunden:
gefundene.append(song)
return gefundene
```
---
## ⏱️ Zeit-Formatierung: Aus Sekunden werden Minuten & Sekunden
Um z.B. `215` Sekunden als `"03:35"` darzustellen, teilt man mit Ganzzahldivision `//` und Modulo `%`:
```python
sekunden_gesamt = 215
minuten = sekunden_gesamt // 60 # 3
sekunden = sekunden_gesamt % 60 # 35
# Mit :02d wird immer zweistellig mit führender Null formatiert:
print(f"{minuten:02d}:{sekunden:02d}") # "03:35"
```
---
## 🎯 Deine Aufgabe: Der Spotify-Playlist-Manager
In `aufgabe.py` baust du deine eigene Musikverwaltung:
1. **Klasse `Song`**:
- Attribute: `titel`, `kuenstler`, `dauer_sekunden`
- Methode: `formatierte_dauer()` -> `"MM:SS"`
- `__str__()` -> `"{titel}" von {kuenstler} ({MM:SS})`
2. **Klasse `Playlist`**:
- `song_hinzufuegen(song)`
- `anzahl_songs()`
- `gesamtdauer_sekunden()`
- `finde_songs_von(kuenstler)` (Groß-/Kleinschreibung ignorieren!)
- `laengster_song()` (liefert das Song-Objekt mit der längsten Dauer)
### Testen:
```bash
python3 test_aufgabe.py
```

View file

@ -0,0 +1,123 @@
"""
Kapitel 05: Komposition – Song & Playlist Manager
=================================================
In dieser Aufgabe baust du eine Musikverwaltung mit zwei Klassen:
'Song' und 'Playlist'.
"""
class Song:
# ==========================================================================
# TODO 1: Konstruktor für Song
# Parameter: self, titel (str), kuenstler (str), dauer_sekunden (int)
# Speichere: self.titel, self.kuenstler, self.dauer_sekunden
# ==========================================================================
def __init__(self, titel, kuenstler, dauer_sekunden):
# Schreibe hier deinen Code für TODO 1:
pass
# ==========================================================================
# TODO 2: Methode "formatierte_dauer(self)"
# Rechnet 'self.dauer_sekunden' in Minuten und Sekunden um und gibt einen
# String im Format "MM:SS" zurück (z.B. 215 -> "03:35", 70 -> "01:10").
# Tipp: Verwende // 60 und % 60 sowie f"{minuten:02d}:{sekunden:02d}"
# ==========================================================================
def formatierte_dauer(self):
# Schreibe hier deinen Code für TODO 2:
pass
# ==========================================================================
# TODO 3: Dunder-Methode "__str__(self)"
# Gibt einen String im folgenden Format zurück:
# '"{titel}" von {kuenstler} ({dauer})'
#
# Beispiel: '"Flowers" von Miley Cyrus (03:20)'
# ==========================================================================
def __str__(self):
# Schreibe hier deinen Code für TODO 3:
pass
class Playlist:
# ==========================================================================
# TODO 4: Konstruktor für Playlist
# Parameter: self, name (str)
# Speichere: self.name, self.songs (als leere Liste [])
# ==========================================================================
def __init__(self, name):
# Schreibe hier deinen Code für TODO 4:
pass
# ==========================================================================
# TODO 5: Methode "song_hinzufuegen(self, song)"
# Hängt das übergebene Song-Objekt an 'self.songs' an.
# ==========================================================================
def song_hinzufuegen(self, song):
# Schreibe hier deinen Code für TODO 5:
pass
# ==========================================================================
# TODO 6: Methode "anzahl_songs(self)"
# Gibt die Anzahl der Songs in der Playlist als Integer zurück.
# ==========================================================================
def anzahl_songs(self):
# Schreibe hier deinen Code für TODO 6:
pass
# ==========================================================================
# TODO 7: Methode "gesamtdauer_sekunden(self)"
# Gibt die Summe der Sekunden aller Songs in der Playlist zurück.
# Bei leerer Playlist: 0
# ==========================================================================
def gesamtdauer_sekunden(self):
# Schreibe hier deinen Code für TODO 7:
pass
# ==========================================================================
# TODO 8: Methode "finde_songs_von(self, kuenstler)"
# Sucht alle Songs in self.songs, bei denen der Künstler übereinstimmt.
# Wichtig: Groß- und Kleinschreibung soll ignoriert werden! (z.B. .lower())
# Gibt eine Liste von passenden Song-Objekten zurück.
# ==========================================================================
def finde_songs_von(self, kuenstler):
# Schreibe hier deinen Code für TODO 8:
pass
# ==========================================================================
# TODO 9: Methode "laengster_song(self)"
# Findet den Song mit der längsten Spieldauer in self.songs.
# - Gibt das gefundene Song-Objekt zurück.
# - Gibt None zurück, wenn die Playlist leer ist.
# ==========================================================================
def laengster_song(self):
# Schreibe hier deinen Code für TODO 9:
pass
# ==============================================================================
# Hauptprogramm zum Ausprobieren:
# ==============================================================================
if __name__ == "__main__":
print("🎧 --- MEINE PLAYLIST --- 🎧")
meine_hits = Playlist("Sommer Vibes")
s1 = Song("Flowers", "Miley Cyrus", 200)
s2 = Song("Cruel Summer", "Taylor Swift", 178)
s3 = Song("Anti-Hero", "Taylor Swift", 201)
s4 = Song("As It Was", "Harry Styles", 167)
meine_hits.song_hinzufuegen(s1)
meine_hits.song_hinzufuegen(s2)
meine_hits.song_hinzufuegen(s3)
meine_hits.song_hinzufuegen(s4)
print(f"Playlist: {meine_hits.name} ({meine_hits.anzahl_songs()} Songs)")
print(f"Gesamtlaufzeit: {meine_hits.gesamtdauer_sekunden()} Sekunden")
laengster = meine_hits.laengster_song()
if laengster:
print(f"Längster Song: {laengster}")
print("\nSongs von Taylor Swift:")
for s in meine_hits.finde_songs_von("taylor swift"):
print(f"- {s}")

View file

@ -0,0 +1,52 @@
"""
Kapitel 05: Komposition – Song & Playlist Manager – Musterlösung
================================================================
"""
class Song:
def __init__(self, titel, kuenstler, dauer_sekunden):
self.titel = titel
self.kuenstler = kuenstler
self.dauer_sekunden = int(dauer_sekunden)
def formatierte_dauer(self):
minuten = self.dauer_sekunden // 60
sekunden = self.dauer_sekunden % 60
return f"{minuten:02d}:{sekunden:02d}"
def __str__(self):
return f'"{self.titel}" von {self.kuenstler} ({self.formatierte_dauer()})'
class Playlist:
def __init__(self, name):
self.name = name
self.songs = []
def song_hinzufuegen(self, song):
self.songs.append(song)
def anzahl_songs(self):
return len(self.songs)
def gesamtdauer_sekunden(self):
return sum(s.dauer_sekunden for s in self.songs)
def finde_songs_von(self, kuenstler):
kuenstler_lower = kuenstler.lower()
return [s for s in self.songs if s.kuenstler.lower() == kuenstler_lower]
def laengster_song(self):
if not self.songs:
return None
return max(self.songs, key=lambda s: s.dauer_sekunden)
if __name__ == "__main__":
meine_hits = Playlist("Sommer Vibes")
meine_hits.song_hinzufuegen(Song("Flowers", "Miley Cyrus", 200))
meine_hits.song_hinzufuegen(Song("Cruel Summer", "Taylor Swift", 178))
meine_hits.song_hinzufuegen(Song("Anti-Hero", "Taylor Swift", 201))
print(f"Playlist: {meine_hits.name} ({meine_hits.anzahl_songs()} Songs)")
print(f"Längster Song: {meine_hits.laengster_song()}")

View file

@ -0,0 +1,87 @@
import unittest
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel05(unittest.TestCase):
def test_01_song_init_und_dauer(self):
"""Prüft Song Attribute und Formatierung der Dauer."""
s = aufgabe.Song("Vampire", "Olivia Rodrigo", 219)
self.assertEqual(s.titel, "Vampire")
self.assertEqual(s.kuenstler, "Olivia Rodrigo")
self.assertEqual(s.dauer_sekunden, 219)
self.assertEqual(s.formatierte_dauer(), "03:39")
s_kurz = aufgabe.Song("Intro", "Kuenstler", 5)
self.assertEqual(s_kurz.formatierte_dauer(), "00:05")
def test_02_song_str(self):
"""Prüft die __str__ Methode von Song."""
s = aufgabe.Song("Espresso", "Sabrina Carpenter", 175)
erwartet = '"Espresso" von Sabrina Carpenter (02:55)'
self.assertEqual(str(s), erwartet)
def test_03_playlist_hinzufuegen_und_dauer(self):
"""Prüft Playlist Erstellung, Hinzufügen, Anzahl und Gesamtlaufzeit."""
p = aufgabe.Playlist("Party")
self.assertEqual(p.anzahl_songs(), 0)
self.assertEqual(p.gesamtdauer_sekunden(), 0)
self.assertIsNone(p.laengster_song())
s1 = aufgabe.Song("Song A", "Artist 1", 100)
s2 = aufgabe.Song("Song B", "Artist 2", 150)
p.song_hinzufuegen(s1)
p.song_hinzufuegen(s2)
self.assertEqual(p.anzahl_songs(), 2)
self.assertEqual(p.gesamtdauer_sekunden(), 250)
def test_04_finde_songs_von(self):
"""Prüft die Filtersuche nach Künstlern (auch case-insensitive)."""
p = aufgabe.Playlist("Mix")
s1 = aufgabe.Song("Anti-Hero", "Taylor Swift", 200)
s2 = aufgabe.Song("Cruel Summer", "Taylor Swift", 180)
s3 = aufgabe.Song("Flowers", "Miley Cyrus", 210)
p.song_hinzufuegen(s1)
p.song_hinzufuegen(s2)
p.song_hinzufuegen(s3)
# Exakte Schreibweise
ergebnis = p.finde_songs_von("Taylor Swift")
self.assertEqual(len(ergebnis), 2)
self.assertIn(s1, ergebnis)
self.assertIn(s2, ergebnis)
# Kleinschreibung testen
ergebnis_lower = p.finde_songs_von("taylor swift")
self.assertEqual(len(ergebnis_lower), 2)
# Nicht vorhandener Künstler
ergebnis_leer = p.finde_songs_von("Ed Sheeran")
self.assertEqual(len(ergebnis_leer), 0)
def test_05_laengster_song(self):
"""Prüft das Ermitteln des längsten Songs."""
p = aufgabe.Playlist("Mix")
s1 = aufgabe.Song("Kurz", "A", 120)
s2 = aufgabe.Song("Lang", "B", 300)
s3 = aufgabe.Song("Mittel", "C", 200)
p.song_hinzufuegen(s1)
p.song_hinzufuegen(s2)
p.song_hinzufuegen(s3)
self.assertIs(p.laengster_song(), s2)
if __name__ == "__main__":
unittest.main(verbosity=2)

View file

@ -0,0 +1,48 @@
# Kapitel 06: Abschlussprojekt – Dein eigenes Tamagotchi! 🥚🐣
Herzlichen Glückwunsch! Du hast alle grundlegenden Konzepte der objektorientierten Programmierung gemeistert:
- Klassen & Instanzen
- `__init__` & `self`
- Methoden & Zustandsveränderungen
- `__str__` für ansprechende Textausgaben
- Beziehungen zwischen Objekten
Jetzt setzen wir alles in einem **vollwertigen, interaktiven Mini-Projekt** zusammen: Einem virtuellen Haustier (Tamagotchi)!
---
## 🎮 Wie funktioniert das Tamagotchi?
Dein Tamagotchi hat Eigenschaften, die sich durch deine Aktionen (und mit der Zeit) verändern:
- **Hunger** (0 = vollgefressen, 100 = verhungert)
- **Müdigkeit** (0 = voller Energie, 100 = total erschöpft)
- **Glück** (0 = unglücklich, 100 = überglücklich)
- **Alter in Tagen**
Du kannst mit ihm interagieren:
- 🍎 **Füttern** (`fuettern`): Senkt den Hunger, macht etwas müde und etwas glücklicher.
- ⚽ **Spielen** (`spielen`): Steigert das Glück, macht aber hungrig und müde. Zu müde Haustiere weigern sich zu spielen!
- 💤 **Schlafen** (`schlafen`): Setzt die Müdigkeit auf 0, lässt einen Tag vergehen (`alter_tage += 1`), macht aber etwas hungrig.
- ⏳ **Zeit vergeht** (`zeit_vergeht`): Das Haustier wird hungriger, müder und etwas trauriger. Wenn der Hunger 100 erreicht, schläft es für immer ein...
---
## 🕹️ Das interaktive Spiel
Sobald du die `Tamagotchi`-Klasse in `aufgabe.py` fertig programmiert hast und alle Tests grün sind, kannst du das Spiel direkt im Terminal starten:
```bash
python3 tamagotchi_spiel.py
```
Dort kannst du deinem Haustier einen Namen geben und dich live um es kümmern!
---
## 🎯 Deine Aufgabe
Öffne `aufgabe.py` und implementiere die `Tamagotchi`-Klasse von TODO 1 bis TODO 6.
### Tests ausführen:
```bash
python3 test_aufgabe.py
```

View file

@ -0,0 +1,129 @@
"""
Kapitel 06: Abschlussprojekt – Tamagotchi (Virtuelles Haustier)
==============================================================
Programmiere hier die Klasse 'Tamagotchi'.
"""
class Tamagotchi:
# ==========================================================================
# TODO 1: Konstruktor __init__(self, name, tierart="Drache")
#
# Initialisiere die folgenden Attribute:
# - self.name = name
# - self.tierart = tierart
# - self.hunger = 50 (0 = pappsatt, 100 = verhungert)
# - self.muedigkeit = 20 (0 = fit, 100 = erschöpft)
# - self.glueck = 80 (0 = traurig, 100 = super glücklich)
# - self.alter_tage = 0
# - self.ist_lebendig = True
# ==========================================================================
def __init__(self, name, tierart="Drache"):
# Schreibe hier deinen Code für TODO 1:
pass
# Hilfsmethode (optional, aber sehr nützlich):
# Hält einen Wert zwischen minimal 0 und maximal 100.
def _begrenze(self, wert):
return max(0, min(100, wert))
# ==========================================================================
# TODO 2: Methode "fuettern(self, menge=20)"
#
# Regeln:
# 1. Wenn not self.ist_lebendig:
# - Gib zurück: f"{self.name} reagiert nicht mehr..."
# 2. Wenn lebendig:
# - Verringere 'self.hunger' um 'menge' (darf nicht unter 0 fallen).
# - Erhöhe 'self.glueck' um 5 (darf nicht über 100 steigen).
# - Erhöhe 'self.muedigkeit' um 5 (darf nicht über 100 steigen).
# - Gib zurück: f"{self.name} mampft genüsslich! (Hunger: {self.hunger}/100)"
# ==========================================================================
def fuettern(self, menge=20):
# Schreibe hier deinen Code für TODO 2:
pass
# ==========================================================================
# TODO 3: Methode "spielen(self, spass=25)"
#
# Regeln:
# 1. Wenn not self.ist_lebendig:
# - Gib zurück: f"{self.name} reagiert nicht mehr..."
# 2. Wenn 'self.muedigkeit' > 80 ist:
# - Zu müde zum Spielen! Ändere keine Werte und gib zurück:
# f"{self.name} ist zu müde zum Spielen und gähnt nur..."
# 3. Wenn fit genug:
# - Erhöhe 'self.glueck' um 'spass' (max 100).
# - Erhöhe 'self.hunger' um 15 (max 100).
# - Erhöhe 'self.muedigkeit' um 20 (max 100).
# - Gib zurück: f"{self.name} hatte riesigen Spaß beim Spielen! (Glück: {self.glueck}/100)"
# ==========================================================================
def spielen(self, spass=25):
# Schreibe hier deinen Code für TODO 3:
pass
# ==========================================================================
# TODO 4: Methode "schlafen(self)"
#
# Regeln:
# 1. Wenn not self.ist_lebendig:
# - Gib zurück: f"{self.name} reagiert nicht mehr..."
# 2. Wenn lebendig:
# - Setze 'self.muedigkeit' auf 0.
# - Erhöhe 'self.hunger' um 15 (max 100).
# - Erhöhe 'self.alter_tage' um 1.
# - Gib zurück: f"{self.name} hat tief geschlafen und ist ausgeruht! Ein neuer Tag bricht an."
# ==========================================================================
def schlafen(self):
# Schreibe hier deinen Code für TODO 4:
pass
# ==========================================================================
# TODO 5: Methode "zeit_vergeht(self)"
# Simuliert das Verstreichen von Zeit.
#
# Regeln:
# 1. Wenn not self.ist_lebendig:
# - Mach nichts, gib False zurück.
# 2. Wenn lebendig:
# - Erhöhe 'self.hunger' um 10 (max 100).
# - Erhöhe 'self.muedigkeit' um 10 (max 100).
# - Verringere 'self.glueck' um 10 (min 0).
# - Prüfe, ob das Haustier stirbt:
# Wenn self.hunger >= 100 oder self.glueck <= 0:
# self.ist_lebendig = False
# - Gib self.ist_lebendig zurück (True wenn noch am Leben, False wenn gestorben).
# ==========================================================================
def zeit_vergeht(self):
# Schreibe hier deinen Code für TODO 5:
pass
# ==========================================================================
# TODO 6: Dunder-Methode "__str__(self)"
# Gibt eine hübsche Statusübersicht als mehrzeiligen String zurück.
#
# Format (wenn lebendig):
# "=== {self.name} ({self.tierart}, {self.alter_tage} Tage alt) ===\n"
# "Hunger: {self.hunger}/100\n"
# "Müdigkeit: {self.muedigkeit}/100\n"
# "Glück: {self.glueck}/100"
#
# Format (wenn tot / not self.ist_lebendig):
# "=== {self.name} ({self.tierart}) - RIP 🪦 ==="
# ==========================================================================
def __str__(self):
# Schreibe hier deinen Code für TODO 6:
pass
# ==============================================================================
# Schneller Testlauf:
# ==============================================================================
if __name__ == "__main__":
pet = Tamagotchi("Yoshi", "Dino")
print(pet)
print()
print(pet.fuettern())
print(pet.spielen())
print(pet.schlafen())
print(pet)

View file

@ -0,0 +1,81 @@
"""
Kapitel 06: Abschlussprojekt – Tamagotchi – Musterlösung
========================================================
"""
class Tamagotchi:
def __init__(self, name, tierart="Drache"):
self.name = name
self.tierart = tierart
self.hunger = 50
self.muedigkeit = 20
self.glueck = 80
self.alter_tage = 0
self.ist_lebendig = True
def _begrenze(self, wert):
return max(0, min(100, wert))
def fuettern(self, menge=20):
if not self.ist_lebendig:
return f"{self.name} reagiert nicht mehr..."
self.hunger = self._begrenze(self.hunger - menge)
self.glueck = self._begrenze(self.glueck + 5)
self.muedigkeit = self._begrenze(self.muedigkeit + 5)
return f"{self.name} mampft genüsslich! (Hunger: {self.hunger}/100)"
def spielen(self, spass=25):
if not self.ist_lebendig:
return f"{self.name} reagiert nicht mehr..."
if self.muedigkeit > 80:
return f"{self.name} ist zu müde zum Spielen und gähnt nur..."
self.glueck = self._begrenze(self.glueck + spass)
self.hunger = self._begrenze(self.hunger + 15)
self.muedigkeit = self._begrenze(self.muedigkeit + 20)
return f"{self.name} hatte riesigen Spaß beim Spielen! (Glück: {self.glueck}/100)"
def schlafen(self):
if not self.ist_lebendig:
return f"{self.name} reagiert nicht mehr..."
self.muedigkeit = 0
self.hunger = self._begrenze(self.hunger + 15)
self.alter_tage += 1
return f"{self.name} hat tief geschlafen und ist ausgeruht! Ein neuer Tag bricht an."
def zeit_vergeht(self):
if not self.ist_lebendig:
return False
self.hunger = self._begrenze(self.hunger + 10)
self.muedigkeit = self._begrenze(self.muedigkeit + 10)
self.glueck = self._begrenze(self.glueck - 10)
if self.hunger >= 100 or self.glueck <= 0:
self.ist_lebendig = False
return False
return True
def __str__(self):
if not self.ist_lebendig:
return f"=== {self.name} ({self.tierart}) - RIP 🪦 ==="
zeilen = [
f"=== {self.name} ({self.tierart}, {self.alter_tage} Tage alt) ===",
f"Hunger: {self.hunger}/100",
f"Müdigkeit: {self.muedigkeit}/100",
f"Glück: {self.glueck}/100"
]
return "\n".join(zeilen)
if __name__ == "__main__":
pet = Tamagotchi("Yoshi", "Dino")
print(pet)
print(pet.fuettern())
print(pet.spielen())
print(pet.schlafen())
print(pet)

View file

@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""
🎮 TAMAGOTCHI TERMINAL SPIEL 🎮
===============================
Führe dieses Skript aus, um dein Tamagotchi live im Terminal zu spielen:
python3 tamagotchi_spiel.py
"""
import sys
import time
try:
from aufgabe import Tamagotchi
except ImportError:
from musterloesung import Tamagotchi
def zeichne_balken(wert, max_wert=100, laenge=15, umgekehrt=False):
"""Erzeugt einen visuellen Ladebalken."""
gefuellt = int((wert / max_wert) * laenge)
ungefuellt = laenge - gefuellt
if umgekehrt:
# Bei Hunger/Müdigkeit ist weniger besser (grün -> rot)
farbe = "🟢" if wert < 40 else ("🟡" if wert < 75 else "🔴")
else:
# Bei Glück ist mehr besser
farbe = "🟢" if wert > 60 else ("🟡" if wert > 30 else "🔴")
balken = "█" * gefuellt + "░" * ungefuellt
return f"[{balken}] {wert:3d}% {farbe}"
def zeige_tamagotchi_dashboard(pet):
print("\n" + "=" * 45)
print(f" 🥚 VIRTUAL PET: {pet.name.upper()} 🥚")
print(f" Tierart: {pet.tierart} | Alter: {pet.alter_tage} Tag(e)")
print("=" * 45)
if not pet.ist_lebendig:
print("\n 🪦 R. I. P. 🪦")
print(f" {pet.name} ist leider verstorben...")
print("=" * 45 + "\n")
return
print(f" 🍖 Hunger: {zeichne_balken(pet.hunger, umgekehrt=True)}")
print(f" 💤 Müdigkeit: {zeichne_balken(pet.muedigkeit, umgekehrt=True)}")
print(f" 💖 Glück: {zeichne_balken(pet.glueck)}")
print("=" * 45)
def main():
print("\n" + "*" * 50)
print(" WILLKOMMEN BEIM TAMAGOTCHI SIMULATOR!")
print("*" * 50)
name = input("\nWie soll dein Haustier heißen? (z.B. Yoshi): ").strip()
if not name:
name = "Yoshi"
tierart = input(f"Welche Tierart ist {name}? (z.B. Drache / Katze / Pinguin): ").strip()
if not tierart:
tierart = "Drache"
pet = Tamagotchi(name, tierart)
print(f"\n🎉 Herzlichen Glückwunsch! {pet.name} das {pet.tierart}-Baby ist geschlüpft!\n")
while pet.ist_lebendig:
zeige_tamagotchi_dashboard(pet)
print("\nWas möchtest du tun?")
print(" [1] 🍎 Füttern")
print(" [2] ⚽ Spielen")
print(" [3] 💤 Schlafen legen")
print(" [4] ⏳ Nichts tun (Zeit vergeht)")
print(" [q] 🚪 Spiel beenden")
wahl = input("\nDeine Wahl (1/2/3/4/q): ").strip().lower()
nachricht = ""
if wahl == "1":
nachricht = pet.fuettern()
elif wahl == "2":
nachricht = pet.spielen()
elif wahl == "3":
nachricht = pet.schlafen()
elif wahl == "4":
nachricht = f"Du schaust {pet.name} eine Weile beim Dösen zu..."
elif wahl == "q":
print(f"\nTschüss! Pass gut auf {pet.name} auf!\n")
break
else:
print("❌ Ungültige Eingabe, bitte wähle 1, 2, 3, 4 oder q.")
continue
# Wenn eine Aktion ausgeführt wurde, vergeht auch etwas Zeit:
if wahl in ("1", "2", "4"):
pet.zeit_vergeht()
print("\n👉 " + nachricht)
time.sleep(0.5)
if not pet.ist_lebendig:
zeige_tamagotchi_dashboard(pet)
print("Game Over! Versuche es beim nächsten Mal besser zu versorgen.")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\nSpiel abgebrochen. Auf Wiedersehen!")

View file

@ -0,0 +1,111 @@
import unittest
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
try:
import aufgabe
except ImportError as e:
raise ImportError(f"Konnte 'aufgabe.py' nicht importieren: {e}")
class TestKapitel06(unittest.TestCase):
def test_01_init(self):
"""Prüft die Standard-Startwerte eines neuen Tamagotchis."""
t = aufgabe.Tamagotchi("Pikachu", "Maus")
self.assertEqual(t.name, "Pikachu")
self.assertEqual(t.tierart, "Maus")
self.assertEqual(t.hunger, 50)
self.assertEqual(t.muedigkeit, 20)
self.assertEqual(t.glueck, 80)
self.assertEqual(t.alter_tage, 0)
self.assertTrue(t.ist_lebendig)
def test_02_fuettern(self):
"""Prüft die Auswirkungen des Fütterns."""
t = aufgabe.Tamagotchi("Yoshi")
t.hunger = 50
t.muedigkeit = 20
t.glueck = 80
text = t.fuettern(20)
self.assertEqual(t.hunger, 30, "Hunger sollte um 20 sinken!")
self.assertEqual(t.glueck, 85, "Glück sollte um 5 steigen!")
self.assertEqual(t.muedigkeit, 25, "Müdigkeit sollte um 5 steigen!")
self.assertIn("Yoshi", text)
# Hunger darf nicht negativ werden
t.fuettern(100)
self.assertEqual(t.hunger, 0, "Hunger darf nicht unter 0 fallen!")
def test_03_spielen(self):
"""Prüft die Auswirkungen des Spielens und die Müdigkeitsgrenze."""
t = aufgabe.Tamagotchi("Sparky")
t.hunger = 30
t.muedigkeit = 20
t.glueck = 50
text = t.spielen(25)
self.assertEqual(t.glueck, 75)
self.assertEqual(t.hunger, 45)
self.assertEqual(t.muedigkeit, 40)
self.assertIn("Sparky", text)
# Zu müde zum Spielen (muedigkeit > 80)
t.muedigkeit = 85
t.glueck = 50
text_muede = t.spielen(20)
self.assertIn("zu müde", text_muede.lower())
self.assertEqual(t.glueck, 50, "Werte dürfen sich bei Müdigkeit nicht ändern!")
def test_04_schlafen(self):
"""Prüft Schlafen und Altern."""
t = aufgabe.Tamagotchi("Luna")
t.muedigkeit = 70
t.hunger = 20
t.alter_tage = 2
t.schlafen()
self.assertEqual(t.muedigkeit, 0, "Müdigkeit sollte auf 0 zurückgesetzt werden!")
self.assertEqual(t.hunger, 35, "Hunger sollte um 15 steigen!")
self.assertEqual(t.alter_tage, 3, "Alter in Tagen sollte um 1 steigen!")
def test_05_zeit_vergeht_und_lebenszyklus(self):
"""Prüft Verstreichen der Zeit und Ableben bei 100 Hunger oder 0 Glück."""
t = aufgabe.Tamagotchi("Rex")
t.hunger = 80
t.muedigkeit = 30
t.glueck = 20
lebendig = t.zeit_vergeht()
self.assertTrue(lebendig)
self.assertEqual(t.hunger, 90)
self.assertEqual(t.muedigkeit, 40)
self.assertEqual(t.glueck, 10)
# Noch ein Zeitschritt -> Hunger erreicht 100
lebendig2 = t.zeit_vergeht()
self.assertFalse(lebendig2)
self.assertFalse(t.ist_lebendig)
# Aktionen an totem Tamagotchi
reaktion = t.fuettern()
self.assertIn("reagiert nicht mehr", reaktion)
def test_06_str_darstellung(self):
"""Prüft die __str__ Repräsentation."""
t = aufgabe.Tamagotchi("Bobo", "Affe")
str_lebendig = str(t)
self.assertIn("Bobo", str_lebendig)
self.assertIn("Affe", str_lebendig)
self.assertIn("50/100", str_lebendig)
t.ist_lebendig = False
str_tot = str(t)
self.assertIn("RIP", str_tot)
if __name__ == "__main__":
unittest.main(verbosity=2)

53
README.md Normal file
View file

@ -0,0 +1,53 @@
# 🐍 Python OOP Übungsreihe: Der Einstieg in Klassen & Objekte
Willkommen zu deiner Übungsreihe für **Objektorientierte Programmierung (OOP)** in Python! 🎉
Hier lernst du Schritt für Schritt, wie man mit Klassen arbeitet, warum sie die Programmierung übersichtlicher machen und wie du deine eigenen, mächtigen Objekte baust.
---
## 🗺️ Der Lernpfad
Die Übungen sind so aufgebaut, dass jedes Kapitel auf dem vorherigen aufbaut:
| Kapitel | Thema | Was du lernst |
| :--- | :--- | :--- |
| **[01. Einstieg in Klassen](./01_einstieg_klassen/)** | Warum Klassen? | Bauplan vs. Objekt, Instanziierung & Attribute |
| **[02. Konstruktor & self](./02_init_und_self/)** | `__init__` & `self` | Objekte sauber mit Startwerten initialisieren |
| **[03. Methoden & Verhalten](./03_methoden_und_verhalten/)** | Methoden & Interaktion | Objekte können handeln & miteinander interagieren |
| **[04. Schöne Textausgabe](./04_str_und_darstellung/)** | `__str__` Dunder-Methode | Wie Objekte lesbar mit `print()` ausgegeben werden |
| **[05. Objekte kombinieren](./05_objekte_kombinieren/)** | Komposition | Listen von Objekten & komplexe Datenstrukturen |
| **[06. Abschlussprojekt](./06_abschlussprojekt_tamagotchi/)** | Mini-Projekt: Tamagotchi | Ein interaktives Konsolenspiel mit allem Gelernten |
---
## 📁 Aufbau eines Kapitels
In jedem Kapitelordner findest du:
1. **`README.md`**: Eine verständliche Erklärung mit Analogien aus dem Alltag und Codebeispielen.
2. **`aufgabe.py`**: Dein Arbeitsblatt! Hier steht der Starter-Code mit klaren `TODO`-Kommentaren.
3. **`test_aufgabe.py`**: Automatische Tests, mit denen du deine Lösung jederzeit überprüfen kannst.
4. **`musterloesung.py`**: Eine ausführlich kommentierte Beispiellösung (falls du mal nicht weiterkommst oder vergleichen möchtest).
---
## 🚀 Wie du vorgehst
1. Öffne den Ordner des aktuellen Kapitels (z.B. `01_einstieg_klassen`).
2. Lies dir die Erklärung in der jeweiligen `README.md` durch.
3. Öffne `aufgabe.py` und bearbeite die nummerierten `TODO`s.
4. Führe die Tests aus, um deine Lösung zu prüfen:
```bash
# Aus dem Hauptverzeichnis:
python3 -m unittest 01_einstieg_klassen/test_aufgabe.py
# Oder direkt im Ordner:
cd 01_einstieg_klassen
python3 test_aufgabe.py
```
5. Wenn alle Tests mit einem grünen `OK` durchlaufen: **Herzlichen Glückwunsch!** Du bist bereit für das nächste Kapitel! 🏆
---
## 💡 Tipp zum Lernen
Versuche zuerst selbst eine Lösung zu finden. Wenn ein Fehler auftritt, lies die Fehlermeldung der Tests aufmerksam durch – sie gibt dir oft schon den entscheidenden Hinweis! Wenn du gar nicht weiterkommst, wirf einen Blick in `musterloesung.py` und versuche zu verstehen, wie das Problem gelöst wurde.

73
test_all.py Normal file
View file

@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""
🧪 Gesamttest-Runner für alle Kapitel
===================================
Führe dieses Skript aus, um zu sehen, welche Kapitel du bereits erfolgreich gelöst hast:
python3 test_all.py
"""
import sys
import unittest
from pathlib import Path
KAPITEL = [
("Kapitel 01: Einstieg in Klassen", "01_einstieg_klassen"),
("Kapitel 02: Konstruktor & self", "02_init_und_self"),
("Kapitel 03: Methoden & Verhalten", "03_methoden_und_verhalten"),
("Kapitel 04: __str__ & Darstellung", "04_str_und_darstellung"),
("Kapitel 05: Objekte kombinieren", "05_objekte_kombinieren"),
("Kapitel 06: Abschlussprojekt Tamagotchi", "06_abschlussprojekt_tamagotchi"),
]
def main():
root = Path(__file__).parent.resolve()
print("=" * 60)
print("🎓 PYTHON OOP ÜBUNGSREIHE – GESAMTFORTSCHRITT")
print("=" * 60)
gesamt_bestanden = 0
for titel, ordner in KAPITEL:
kapitel_pfad = root / ordner
if not kapitel_pfad.exists():
continue
sys.path.insert(0, str(kapitel_pfad))
# Testsuite laden
loader = unittest.TestLoader()
suite = loader.discover(str(kapitel_pfad), pattern="test_aufgabe.py")
# Leisen Runner ausführen
runner = unittest.TextTestRunner(stream=open("/dev/null", "w"), verbosity=0)
result = runner.run(suite)
# Modul aus sys.path und sys.modules entfernen für saubere Isolation
if str(kapitel_pfad) in sys.path:
sys.path.remove(str(kapitel_pfad))
if "aufgabe" in sys.modules:
del sys.modules["aufgabe"]
if "test_aufgabe" in sys.modules:
del sys.modules["test_aufgabe"]
anzahl_tests = result.testsRun
erfolgreich = result.wasSuccessful() and anzahl_tests > 0
if erfolgreich:
print(f" ✅ {titel:42s} ({anzahl_tests}/{anzahl_tests} Tests OK)")
gesamt_bestanden += 1
else:
fehlgeschlagen = len(result.failures) + len(result.errors)
print(f" ⏳ {titel:42s} (Noch offen / {fehlgeschlagen} Fehler)")
print("=" * 60)
print(f"Ergebnis: {gesamt_bestanden} von {len(KAPITEL)} Kapiteln vollständig gelöst!")
if gesamt_bestanden == len(KAPITEL):
print("🏆 PERFEKT! Du hast alle OOP-Aufgaben mit Bravour gemeistert! 🎉")
else:
print("💡 Öffne das nächste offene Kapitel und starte mit aufgabe.py!")
print("=" * 60)
if __name__ == "__main__":
main()