tut 09
This commit is contained in:
@ -0,0 +1,514 @@
|
|||||||
|
---
|
||||||
|
marp: true
|
||||||
|
paginate: true
|
||||||
|
class: invert
|
||||||
|
# theme: uncover
|
||||||
|
footer: Tutorium 09 - 16.12.2023 - Nils Pukropp - https://s.narl.io/s/tutorium-09
|
||||||
|
header:
|
||||||
|
---
|
||||||
|
|
||||||
|
# Tutorium 09
|
||||||
|
|
||||||
|
Korrektur 08 - Vererbung, OOP, Datenkapselung
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Korrektur 08
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Punkteverteilung
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Häufige Fehler
|
||||||
|
|
||||||
|
* `@dataclass` nicht verwendet
|
||||||
|
* `__init__` überschrieben, obwohl `@dataclass` das macht und dann `super().__init__()` vergessen
|
||||||
|
* Kein Polymorphismus verwendet, also Code Duplikate oder auf `self` gematched/`isinstance()` verwendet
|
||||||
|
* Code nicht getestet, Datei nicht ausführbar => **0 Punkte**!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 a,b)
|
||||||
|
|
||||||
|
```py
|
||||||
|
import math
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Vec2:
|
||||||
|
x: float
|
||||||
|
y: float
|
||||||
|
|
||||||
|
def abs(self) -> float:
|
||||||
|
return math.sqrt(self.x * self.x + self.y * self.y)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 c)
|
||||||
|
|
||||||
|
```py
|
||||||
|
@dataclass
|
||||||
|
class GameObject:
|
||||||
|
position: Vec2
|
||||||
|
radius: int
|
||||||
|
alive: bool
|
||||||
|
color: tuple[int, int, int]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Projectile(GameObject):
|
||||||
|
speed: float
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StaticObject(GameObject):
|
||||||
|
rotation: float
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 c)
|
||||||
|
|
||||||
|
```py
|
||||||
|
class Item(StaticObject):
|
||||||
|
amount: int
|
||||||
|
|
||||||
|
class Ammunition(Item):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Health(Item):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Ship(GameObject):
|
||||||
|
shots: int
|
||||||
|
hp: int
|
||||||
|
|
||||||
|
class Asteroid(StaticObject):
|
||||||
|
special: bool
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 d)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class GameObject:
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def update(self, width: int, height: int, delta: float):
|
||||||
|
if not (0 <= self.position.x < width and 0 <= self.position.y < height):
|
||||||
|
self.alive = False
|
||||||
|
|
||||||
|
class Projectile(GameObject):
|
||||||
|
speed: float
|
||||||
|
|
||||||
|
def update(self, width: int, height: int, delta: float):
|
||||||
|
self.position.y -= delta * self.speed
|
||||||
|
super().update(width, height, delta)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 d)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class StaticObject(GameObject):
|
||||||
|
rotation: float
|
||||||
|
|
||||||
|
def update(self, width: int, height: int, delta: float):
|
||||||
|
self.position.y += delta
|
||||||
|
self.rotation += delta / self.radius
|
||||||
|
super().update(width, height, delta)
|
||||||
|
|
||||||
|
class Ship(GameObject):
|
||||||
|
shots: int
|
||||||
|
hp: int
|
||||||
|
|
||||||
|
def update(self, width: int, height: int, delta: float):
|
||||||
|
if self.hp <= 0:
|
||||||
|
self.hp = 0
|
||||||
|
self.alive = False
|
||||||
|
super().update(width, height, delta)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 e)
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class GameObject:
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def is_colliding(self, other: "GameObject") -> bool:
|
||||||
|
dist = Vec2(self.position.x - other.position.x, self.position.y - other.position.y)
|
||||||
|
return dist.abs() <= self.radius + other.radius
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 f)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class GameObject:
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def on_collision(self, other: "GameObject"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Projectile(GameObject):
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def on_collision(self, other: 'GameObject'):
|
||||||
|
if not isinstance(other, Ship):
|
||||||
|
self.alive = False
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 f)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class StaticObject(GameObject):
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def on_collision(self, other: 'GameObject'):
|
||||||
|
self.alive = False
|
||||||
|
|
||||||
|
class Ship(GameObject):
|
||||||
|
# ...
|
||||||
|
def on_collision(self, other: 'GameObject'):
|
||||||
|
match other:
|
||||||
|
case Asteroid():
|
||||||
|
self.hp -= other.radius
|
||||||
|
case Health():
|
||||||
|
self.hp += other.amount
|
||||||
|
case Ammunition():
|
||||||
|
self.shots += other.amount
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 f)
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Asteroid(StaticObject):
|
||||||
|
special: bool
|
||||||
|
|
||||||
|
def on_collision(self, other: 'GameObject'):
|
||||||
|
if not isinstance(other, Asteroid):
|
||||||
|
self.alive = False
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 g)
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class Ship(GameObject):
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def shoot(self) -> Projectile:
|
||||||
|
alive = False
|
||||||
|
if self.shots:
|
||||||
|
self.shots -= 1
|
||||||
|
alive = True
|
||||||
|
pos = Vec2(self.position.x, self.position.y)
|
||||||
|
return Projectile(pos, 5, alive, (255, 0, 0), 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Musterlösung - Aufgabe 8 h)
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class GameObject:
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
def draw(self, screen: pygame.Surface):
|
||||||
|
pygame.draw.circle(screen, self.color, (self.position.x, self.position.y), self.radius)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Override-Dekorator
|
||||||
|
|
||||||
|
- ist in `typing`
|
||||||
|
- Wird über Methoden geschrieben, die überschrieben werden
|
||||||
|
- Pylance zeigt einen Fehler an, wenn die überschriebene Methode in keiner Oberklasse gefunden wird
|
||||||
|
- Hilft Fehler vorzubeugen - falsche Signatur, Parameter, ...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Override-Dekorator - Beispiel
|
||||||
|
```python
|
||||||
|
from typing import override
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GameObject:
|
||||||
|
|
||||||
|
def on_collision(self, other: 'GameObject'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class StaticObject(GameObject):
|
||||||
|
|
||||||
|
@override
|
||||||
|
def on_collisoin(self, other: 'GameObject'): # Pylance-Error
|
||||||
|
self.alive = False
|
||||||
|
```
|
||||||
|
---
|
||||||
|
|
||||||
|
# Override-Dekorator - Beispiel
|
||||||
|
```python
|
||||||
|
from typing import override
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GameObject:
|
||||||
|
|
||||||
|
def on_collision(self, other: 'GameObject'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class StaticObject(GameObject):
|
||||||
|
|
||||||
|
@override
|
||||||
|
def on_collision(self): # Pylance-Error
|
||||||
|
self.alive = False
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Datenkapselung
|
||||||
|
|
||||||
|
- Man möchte manche Implementierung verstecken
|
||||||
|
- Wenn andere deinen Code verwenden, dann möchte man eine Schnittstelle anbieten die intuitiv ist.
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class MyList[T]:
|
||||||
|
internal_list: list[T] = []
|
||||||
|
|
||||||
|
def add(self, item: T) -> None:
|
||||||
|
self.internal_list += [other]
|
||||||
|
```
|
||||||
|
---
|
||||||
|
# Datenkapselung - warum ist das schlecht?
|
||||||
|
|
||||||
|
```python
|
||||||
|
from my_collections import MyList
|
||||||
|
|
||||||
|
xs = MyList()
|
||||||
|
xs.internal_list # ????
|
||||||
|
```
|
||||||
|
|
||||||
|
- was sollen wir mit `internal_list`?
|
||||||
|
- andere sollten nur auf `add()` zugreifen können
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Private Attribute
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class MyList[T]:
|
||||||
|
internal_list: InitVar[list[T]]
|
||||||
|
length: InitVar[int]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__internal_list = []
|
||||||
|
self.__length = 0
|
||||||
|
|
||||||
|
def add(self, item: T):
|
||||||
|
self.__internal_list += [item]
|
||||||
|
self.__length += 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def length(self) -> int:
|
||||||
|
return self.__length
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Private Attribute
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class MyList[T]:
|
||||||
|
internal_list: InitVar[list[T]]
|
||||||
|
length: InitVar[int]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__internal_list = []
|
||||||
|
self.__length = 0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Private Attribute
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class MyList[T]:
|
||||||
|
internal_list: InitVar[list[T]]
|
||||||
|
length: InitVar[int]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__internal_list = []
|
||||||
|
self.__length = 0
|
||||||
|
|
||||||
|
def add(self, item: T):
|
||||||
|
self.__internal_list += [item]
|
||||||
|
self.__length += 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def length(self) -> int:
|
||||||
|
return self.__length
|
||||||
|
```
|
||||||
|
---
|
||||||
|
|
||||||
|
# Private Attribute - Setter
|
||||||
|
|
||||||
|
- Manchmal wollen wir trotzdem private Attribute setzen
|
||||||
|
- Aber vielleicht nur wenn bestimmte Bedingungen erfüllt sind
|
||||||
|
|
||||||
|
```python
|
||||||
|
class GameObject:
|
||||||
|
position: InitVar[tuple[int, int]]
|
||||||
|
|
||||||
|
def __post__init__(self, position: tuple[int, int]):
|
||||||
|
assert (0, 0) <= position
|
||||||
|
self.__position = position
|
||||||
|
|
||||||
|
@property
|
||||||
|
def position(self) -> tuple[int, int]:
|
||||||
|
return self.__position
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Private Attribute - Setter
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class GameObject:
|
||||||
|
position: InitVar[tuple[int, int]]
|
||||||
|
|
||||||
|
def __post_init__(self, position: tuple[int, int]):
|
||||||
|
assert (0, 0) > position
|
||||||
|
self.__position = position
|
||||||
|
|
||||||
|
@property
|
||||||
|
def position(self) -> tuple[int, int]:
|
||||||
|
return self.__position
|
||||||
|
|
||||||
|
@position.setter
|
||||||
|
def position(self, position: tuple[int, int]):
|
||||||
|
if (0, 0) > position:
|
||||||
|
return
|
||||||
|
self.__position = position
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Comprehensive-Guide to `class`
|
||||||
|
|
||||||
|
## `@dataclass`
|
||||||
|
|
||||||
|
- Attribute werden im Klassenrumpf definiert
|
||||||
|
- können mit einem Standardwert definiert werden
|
||||||
|
- `__init__`, `__post_init__`, `__repr__`, `__eq__`, `__lt__`, `__le__`, `__gt__`, `__ge__`, ... werden automatisch generiert
|
||||||
|
- In der Vorlesung benutzen wir nur `dataclass`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Comprehensive-Guide to `class`
|
||||||
|
|
||||||
|
## `@dataclass`
|
||||||
|
|
||||||
|
- Attribute die im Klassenrumpf definiert werden, werden automatisch in die `__init__` generiert, auch wenn es einen Standardwert gibt!
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass
|
||||||
|
class A:
|
||||||
|
x: int
|
||||||
|
y: int = 0
|
||||||
|
|
||||||
|
def __init__(self, x: int, y: int = 0): # das macht @dataclass von selber!
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
```
|
||||||
|
---
|
||||||
|
|
||||||
|
# Comprehensive-Guide to `class`
|
||||||
|
|
||||||
|
## `Enum`
|
||||||
|
|
||||||
|
- Wenn man eine endliche Aufzählung braucht (endliche Menge)
|
||||||
|
- macht die Fallunterscheidung einfach weil es endliche Elemente gibt
|
||||||
|
- Versichert auch dass kein quatsch übergeben wird wie zb bei `str`
|
||||||
|
- **niemals** mit `@dataclass`, sonst geht alles kaputt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Enum - Beispiel
|
||||||
|
|
||||||
|
```python
|
||||||
|
def eval[T: (int | float)](operator: str, x: T, y: T) -> T:
|
||||||
|
match operator:
|
||||||
|
case '+':
|
||||||
|
return x + y
|
||||||
|
case '-':
|
||||||
|
return x - y
|
||||||
|
case '*':
|
||||||
|
return x * y
|
||||||
|
case '/' if y != 0:
|
||||||
|
return x / y
|
||||||
|
case _:
|
||||||
|
return 0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Enum - Beispiel
|
||||||
|
|
||||||
|
```python
|
||||||
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
class Op(Enum):
|
||||||
|
ADD = auto()
|
||||||
|
SUB = auto()
|
||||||
|
DIV = auto()
|
||||||
|
MUL = auto()
|
||||||
|
```
|
||||||
|
Jetzt passen wir die Methodensignatur an
|
||||||
|
```python
|
||||||
|
def eval[T: (int | float)](operator: Op, x: T, y: T) -> T:
|
||||||
|
```
|
||||||
|
Jetzt kann nichts beliebiges als `operator` übergeben werden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Blatt 09 - Fragen?
|
||||||
|
|
||||||
|
- Abgabe: 18.12. - 09:00
|
||||||
|
- Testet euren Code!
|
||||||
|
- Es gibt keine dummen Fragen wenns ums Verständnis geht
|
BIN
Tutorium/tut09/img/pointdistribution_ex07.png
Normal file
BIN
Tutorium/tut09/img/pointdistribution_ex07.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
Tutorium/tut09/img/pointdistribution_ex08.png
Normal file
BIN
Tutorium/tut09/img/pointdistribution_ex08.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
31
Tutorium/tut09/src/classes.py
Normal file
31
Tutorium/tut09/src/classes.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class A:
|
||||||
|
x: int
|
||||||
|
y: int = 0
|
||||||
|
|
||||||
|
# die init die von `dataclass` generiert wird, also unnötig
|
||||||
|
# siehe class B
|
||||||
|
def __init__(self, x: int, y: int = 0):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class B:
|
||||||
|
x: int
|
||||||
|
y: int = 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
my_a = A(1)
|
||||||
|
assert my_a.x == 1
|
||||||
|
assert my_a.y == 0
|
||||||
|
my_other_a = A(1, y = 1)
|
||||||
|
assert my_other_a.x == 1
|
||||||
|
assert my_other_a.y == 1
|
||||||
|
my_b = B(1)
|
||||||
|
assert my_b.x == 1
|
||||||
|
assert my_b.y == 0
|
||||||
|
my_other_b = B(1, y = 1)
|
||||||
|
assert my_other_b.x == 1
|
||||||
|
assert my_other_b.y == 1
|
55
Tutorium/tut09/src/my_collections.py
Normal file
55
Tutorium/tut09/src/my_collections.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
from dataclasses import dataclass, InitVar
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MyList[T]:
|
||||||
|
internal_list: InitVar[list[T]]
|
||||||
|
length: InitVar[int]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__internal_list = []
|
||||||
|
self.__length = 0
|
||||||
|
|
||||||
|
def add(self, item: T):
|
||||||
|
self.__internal_list += [item]
|
||||||
|
self.__length += 1
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def length(self) -> int:
|
||||||
|
return self.__length
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GameObject:
|
||||||
|
position: InitVar[tuple[int, int]]
|
||||||
|
|
||||||
|
def __post_init__(self, position: tuple[int, int]):
|
||||||
|
assert (0, 0) <= position
|
||||||
|
self.__position = position
|
||||||
|
|
||||||
|
@property
|
||||||
|
def position(self) -> tuple[int, int]:
|
||||||
|
return self.__position
|
||||||
|
|
||||||
|
@position.setter
|
||||||
|
def position(self, position: tuple[int, int]):
|
||||||
|
if (0, 0) > position:
|
||||||
|
return
|
||||||
|
self.__position = position
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
xs: MyList[int] = MyList()
|
||||||
|
xs.add(100)
|
||||||
|
assert xs.length == 1
|
||||||
|
position = (0, 0)
|
||||||
|
my_obj = GameObject(position)
|
||||||
|
assert my_obj.position == (0, 0)
|
||||||
|
try:
|
||||||
|
GameObject((0, -1))
|
||||||
|
except AssertionError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise AssertionError(f"{my_obj} should have thrown a assertation error")
|
||||||
|
my_obj.position = (-1, 0)
|
||||||
|
assert my_obj.position == (0, 0)
|
||||||
|
|
Reference in New Issue
Block a user