Python/UML Übungsaufgabe: eine erstes Pacman-Inkrement

https://oer-informatik.de/python_pygame_oop-pacman

tl/dr; (ca. 90 min Bearbeitungszeit): Im Rahmen der Übungsaufgaben soll ein erstes Inkrement eines vereinfachten Pacman-Spiels mit Python objektorientiert entworfen und implementiert werden. Ausgeblendete Beispielantworten mit Links sind gegeben.

Entwurf eines stark vereinfachten Pacman-Spiels

Es soll ein vereinfachtes Pacman-Spiel entworfen und entwickelt werden. Die Anforderungen sind in folgenden Stichpunkten umrissen:

  • Das Spielfeld ist ein Rechteck. Das bei Pacman übliche Labyrinth wird zunächst nicht umgesetzt. Auf dem Spielfeld sind in regelmäßigem Abstand Punkte verteilt. Diese Punkte müssen durch den Spieler (“Pacman”) gefressen werden.
Stark vereinfachtes Pacman-Spielfeld aus Punkten und Puc
Stark vereinfachtes Pacman-Spielfeld aus Punkten und Puc
  • Pacman selbst wird als einfacher Kreis dargestellt. Er kann sich per Tastatureingabe nach oben, unten, rechts und links bewegen.

  • Auf allen Koordinaten, auf denen Pacman war, sind die Punkte “aufgegessen” und werden nicht mehr angezeigt.

  • Ziel des Spiels in der ersten Iteration ist es, möglichst schnell alle Punkte zu essen.

Der Quelltext wurde bereits vorbereitet und findet sich hier(link). Dieser Quelltext ist Grundlage der folgenden Aufgaben.

UML-Diagramm entwerfen

Ergänze anhand des oben verlinkten Quelltextes die Klasse “Pacman” mit allen dort genannten Details in dem folgenden UML-Diagramm. Eine Beziehung zwischen “Pacman” und “Background” muss nicht eingezeichnet werden.

UML-Diagramm der Klasse Background
UML-Diagramm der Klasse Background

UML-Diagramm der Klassen Background und Pacman
UML-Diagramm der Klassen Background und Pacman

Die Beziehung zwischen Background und Pacman muss nicht dargestellt werden, sondern nur die Klasse Pacman.

Zum Nachlesen finden sich Infos zum UML-Klassendiagramm in diesem Artikel.

Fragen zur Objektorientierung

  1. Das Spiel wurde mit den zwei Klassen (Pacman und Background) realisiert. Wie bezeichnet man die Objektbeziehung, die zwischen der Instanz von Pacman und der Instanz von Background existiert? Begründen Sie ihre Wahl anhand des Quelltextes!

Background und Pacman stehen in einer Teil-Ganzes-Beziehung. Im Quelltext kann man das daran erkennen, dass Background ein Attribut vom Typ Pacman hält:

Es kann sich also um eine Aggregation oder um eine Komposition handeln. Um eine Komposition handelt es sich, wenn das Löschen des Background auch den verknüpften Pacman mit löscht. Das ist insoweit gegeben, als im Programm keine Referenz dieses Pacman außerhalb von Background existiert (also z.B. auch die Objekterzeugung von Pacman innerhalb von Background stattfindet) und das Attribut - im Rahmen der Möglichkeiten von Python - als private gekennzeichnet ist. Die Beziehung ist also eine Komposition.

Zum Nachlesen finden sich Infos zu Objektbeziehungen in diesem Artikel.

  1. Wofür steht das “+” in dem obigen UML-Klassendiagramm bei der Methode setup()? (Name der Eigenschaft und Erklärung)

Das “+” an Attributen und Methoden im Klassendiagramm steht für den Sichtbakeitsmodifikator public. Der betreffende Member ist von allen anderen Programmteilen sichtbar und aufrufbar.

Zum Nachlesen finden sich Infos zu Sichtbarkeitsmodifikatoren in diesem Artikel.

  1. Wofür steht das “-” in dem obigen UML-Klassendiagramm bei dem Attribut dot_size? (Name der Eigenschaft und Erklärung)

Das “-” an Attributen und Methoden im Klassendiagramm steht für den Sichtbakeitsmodifikator private. Der betreffende Member soll nur innerhalb des eigenen Objekts sichtbar und aufrufbar sein.

Zum Nachlesen finden sich Infos zu Sichtbarkeitsmodifikatoren in diesem Artikel.

  1. Worin unterscheiden sich Assoziationen von Aggregationen und Kompositionen in der Objektorientierten Programmierung (OOP)? Grenzen Sie die Begriffe voneinander ab und nennen Sie die Notation im UML-Diagramm!

Assoziation:

Eine Assoziation stellt eine allgemeine Objektbeziehung dar. Zwei Objekte arbeiten temporär zusammen - etwa dadurch, dass ein Objekt Methoden oder Attribute des anderen Objekts nutzt. Im Gegensatz zu Aggregation und Komposition stellen Assoziationen keine Teil-Ganzes-Beziehung (“A besteht aus B”) dar, sondern können allgemein mit der Bezeichnung “A nutzt B” beschrieben werden.

Assoziationen werden durch einfache Linien zwischen Objekten dargestellt, an deren Ende mit Hilfe von Pfeil oder Kreuz die Navigierbarkeit dargestellt werden kann.

Wie bei Aggregationen und Kompositionen können Mulitplizitäten an den Enden und Namen mit Leserichtung an der Linie angegeben werden.

Zum Nachlesen finden sich Infos zu Objektbeziehungen in diesem Artikel.

Aggregation:

Eine Aggregation ist eine Teil-Ganzes-Beziehung, bei der ein Teil auch unabhängig vom Ganzen existieren kann. Dies wird realisiert, in dem ein Teil als Attribut in einem Ganzen organisiert wird. Teile können in diesem Fall auch mehreren Ganzen zugeordnet sein (und Ganze mehrere Teile aggregieren).

Zum Nachlesen finden sich Infos zu Objektbeziehungen in diesem Artikel.

Komposition:

Eine Komposition ist eine starke Teil-Ganzes-Beziehung, bei der ein Teil existenzabhängig von einem Ganzen ist: wird ein Ganzes gelöscht, so werden auch dessen Teile gelöscht. Dies wird realisiert, in dem ein Teil als Attribut in einem Ganzen organisiert wird und keine Referenzen auf das verknüpfte Teil außerhalb des Ganzen existieren. Ein Ganzes kann mehrere Teile besitzen, ein Teil aber zu höchstens einem Teil gehören.

Zum Nachlesen finden sich Infos zu Objektbeziehungen in diesem Artikel.

  1. Wie nennt man die Methode __init__(), was ist ihre Aufgabe und was sind ihre Eigenschaften?

Bei __init__() handelt es sich um den Konstruktor, der bei jeder Objekterzeugung aufgerufen wird und die Argumente übernimmt, die bei der Objekterzeugung übergeben wurden (Beispiel: pacman = Pacman(color=(10,255,0))). In der Regel werden im Konstruktor die Attribute mit übergebenen Parameterwerten oder Defaultwerten gesetzt und alle Operationen ausgeführt, die nötig sind, damit das Objekt erstellt wird.

Bei der Methode __init__() handelt es sich um eine magic method, also um eine Methode, die nicht direkt aufgerufen werden soll, daher ist sie mit Doppelunterstrichen markiert.

Zum Nachlesen finden sich Infos Konstruktoren in der OOP allgemein in diesem Artikel.

  1. Was versteht man in der Objektorientierung unter “Kapselung” und was sind die Vorteile dieses Prinzips?

Bei Kapselung handelt es sich um eine Technik, mit deren Hilfe der Zustand eines Objektes vor dem Zugriff von außen versteckt wird. Der Zugriff kann dann nicht mehr direkt über die Attribute erfolgen, sondern über Getter- und Settermethoden.

Ein Vorteil der Kapselung ist, dass der Code weniger Abhängigkeiten enthält und leichter zu ändern ist. Da sichergestellt ist, dass niemand direkt auf das Attribut zugreift, kann die Art und Weise wie dieser Zustand gespeichert wird später geändert werden. Lediglich die Getter und Setter müssen weiter existieren. Ein weiterer Vorteil ist, dass an zentraler Stelle Validierung und Anpassungen erfolgen können, bevor ein Zustand ein- und ausgegeben wird.

Zum Nachlesen finden sich Infos zu Sichtbarkeitsmodifikatoren und Kapselung in der UML allgemein in diesem Artikel. Ein spezieller Artikel zur Möglichkeit in Python zu kapseln findet sich hier.

  1. Warum ist Kapselung in Python nicht leicht umzusetzen?

Python verfügt über keine Sichtbarkeitsmodifizierer im klassischen OOP-Stil. Hier können Attribute und Methoden lediglich versteckt (dunder - mit doppelten Unterstrichen) oder als versteckt markiert werden (mit einem Unterstrich). Faktisch ist der Zugriff auf die Attribute so aber immer noch möglich.

Anstelle von Getter- und Settermethoden (z.B. get_name() und set_name(name:str)) werden daher bei Python oft Properties genutzt, wenn gekapselt werden soll.

Noch weiter verbreitet ist jedoch in Python, auf Kapselung komplett zu verzichten.

Zum Nachlesen finden sich Infos zur Möglichkeit in Python zu kapseln hier.

  1. Aus welchen Gründen haben sich die Erfinder von Python dagegen entschieden, Sichtbarkeitsmodifizierer in ihre Sprache zu integrieren?

Der Philosophie von Python nach sind Entwickler*innen verantwortlich für den gesamten Code und sollen damit auch verantwortlich umgehen können - dazu gehört, dass davon ausgegangen wird, dass man sich nicht selbst aus Codebereichen aussperren muss.

Implementierung einer Methode

Es soll eine weitere Klasse erstellt werden: in Pacman gibt es Geister, die sich auf den Pacman zubewegen.

UML-Diagramm der Klasse Background
UML-Diagramm der Klasse Background

Implementiere anhand der Klasse Ghost (siehe UML-Diagramm oben) die Methode move(), die als Parameter den Pacman übergeben bekommt. In der Methode move() soll sich der Geist eine halbe Einheit nach oben bewegen, wenn Pacman sich oberhalb des Geistes befindet, nach unten, wenn er unterhalb ist, nach rechts, wenn er rechts des Geistes ist und nach links, wenn er sich links befindet. Die Bewegung wird durch Anpassung der Koordinaten x_position und y_position umgesetzt.

Implementierung einer Superklasse

  1. Pacman und die Geister haben eine Menge Gemeinsamkeiten. Entwerfen Sie ein UML-Klassendiagramm, in dem die gemeinsamen Superklasse Figure mit ihren Attributen und Methoden steht sowie die beiden Klassen Pacman und Ghost (bei beiden müssen keine Methoden/Attribute genannt werden). Ergänzen Sie die Beziehungen zwischen den Klassen.
Die Klasse Figure
Die Klasse Figure

(Zur Beantwortung der Frage langt die Klasse Figure sowie die Klassen Pacman und Ghost, diese jedoch ohne Attribut.)

Zum Nachlesen finden sich Infos zur Vererbung in UML und Python hier.

  1. Wie müssen die Attribute der Superklasse im UML-Klassendiagramm angelegt werden, damit Pacman und Ghost direkt auf diese zugreifen können?

Die Sichtbarkeit der Attribute darf nicht privat sein oder die Attribute müssen Getter und Setter (oder Properties) zur Verfügung stellen.

(In anderen Programmiersprachen wäre der Königsweg, das Attribut als protected zu deklarieren, damit es innerhalb der Vererbungshierarchie zugreifbar ist. Diese Möglichkeit gibt es aber in Python nicht.)

Zum Nachlesen finden sich Infos zur Vererbung in UML und Python hier.

  1. Erstellen Sie den Klassenrumpf und den Konstruktor von Ghost in Python.

Pygame Framework

Erstellen Sie für das Hauptprogramm (die Game-Loop) einen Programm-Ablaufplan (PAP). Es sollen nur die Aufrufe des Hauptprogramms dargestellt werden – Vorgänge im inneren der Objekte müssen nicht dargestellt werden.

PAP der Gameengine des Programms
PAP der Gameengine des Programms

Listen mit Python

Es sollen Listenoperationen vorgenommen werden. Als Hilfe dient das folgende UML-Klassendiagramm

Die Klasse list im Klassendiagramm
Die Klasse list im Klassendiagramm
  1. Erstellen Sie mit einem Python Befehl eine neue leere Liste namens ghostnames:
  1. Weisen Sie der Liste in einem Befehl die Geisternamen “Blinky”, “Stinki”, “Ponky” und “Inky” zu.
  1. Fügen Sie mit einem Python Befehl “Clyde” hinten an die Liste an.
  1. Löschen Sie mit einem Python Befehl das Element mit dem Index 1!
  1. Fügen Sie mit einem Python Befehl an die 2. Stelle “Pinky” an!
  1. Löschen Sie das Element mit dem Inhalt “Ponky” aus der Liste!
  1. Ändern Sie den Namen des ersten Elements (“Blanky”) zu “Blinky”!
  1. Geben Sie mit einem Python Befehl den Inhalt der Liste aus!
  1. Welchen Inhalt hat die Liste jetzt?
  • Sehr lesenswert hierzu sind die Beiträge in Jörg Kantels Blog Schockwellenreiter, der in der Objektorientierung noch einen Schritt weiter geht, und auch die Oberfläche selbst zum Objekt macht (und andere interessante Artikel hat)

  • Die Website vom Framework PyGame

Quellen und offene Ressourcen (OER)

Die Ursprungstexte (als Markdown), Grafiken und zugrunde liegende Diagrammquelltexte finden sich (soweit möglich in weiterbearbeitbarer Form) in folgendem git-Repository:

https://gitlab.com/oer-informatik/python-basics/erste-schritte-mit-python

Sofern nicht explizit anderweitig angegeben sind sie zur Nutzung als Open Education Resource (OER) unter Namensnennung (H. Stein, oer-informatik.de) freigegeben gemäß der Creative Commons Namensnennung 4.0 International Lizenz (CC BY 4.0).

Creative Commons Lizenzvertrag


Hinweis zur Nachnutzung

Dieses Werk und dessen Inhalte sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Erste Schritte mit Python” von Hannes Stein, Lizenz: CC BY 4.0. Die Quellen dieses Werks sind verfügbar auf GitLab.

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: