lehrkraefte:blc:informatik:ffprg1-2020:klassen

Klassen

Eine Klasse verbindet Daten und Funktionen zu einer Einheit. Das erlaubt es, innerhalb der Klasse quasi “globale” Variablen zu haben.

Eine Klasse ist eine Vorlage (wie eine Keksform). Sie legt, fest, welche Instanz-Variablen (self.irgend_ein_name) existieren sollen, und welche Methoden (Funktionen) damit arbeiten.

Von einer Klasse können beliebig viele Instanzen erzeugt werden, die alle unterschiedliche Werte in den Instanz-Variablen gespeichert haben können. Beispiel:

person.py
# Definition der Klasse
class Person():
    # Konstruktor, wird beim Erzeugen der Instanz ausgeführt
    def __init__(self, name, alter):
        self.name = name    # Instanzvariable initialisieren
        self.alter = alter  # Alter initialisieren
 
    # Methode zur Erhöhung des Alters
    def geburtstag(self):
        self.alter += 1
 
    # Info zur Instanz
    def sagwas(self):
        print("Ich heisse %s und bin %d Jahre alt." % (self.name, self.alter))
 
# Begin Programm       
# Instanz Klara
klara = Person("Klara", 13)
# Klara soll was sagen
klara.sagwas()
 
# Instanz Max
max = Person("Max", 11)
max.sagwas()
 
# Ein Jahr älter
max.geburtstag()
max.sagwas()

Ein Vorteil einer Klasse ist, dass die Klasse selbst für Ihre Daten verantwortlich ist. Z.B. könnte eine Überprüfung eingebaut werden, ob das Alter auch wirklich sinnvoll ist (z.B. ob es zwischen 0 und 130 liegt).

tictactoe.py
class TicTacToe:
    # Konstruktur, wird bei der Kreation der Instanz aufgerufen
    def __init__(self):
        # leeres Feld
        self.feld = [[0,0,0] for i in range(3)]
 
    def clear(self):
        for x in range(3):
            for y in range(3):
                self.feld[x][y] = 0
 
    # Standard-Methode zur Umwandlung in einen String
    def __str__(self):
        symbols = [" ", "O", "X"]
        res = ""
        for y in range(3):
            for x in range(3):
                res += " "+symbols[self.feld[x][y]]+" "
                if (x<2):
                    res += "|"
            res += "\n"
            if (y<2):
                res += "---+---+---\n"
        return res
 
    # Setzt ein Feld, wenn es leer ist
    def set(self,x,y,what):
        if (self.feld[x][y]==0):
            self.feld[x][y] = what
        else:
            raise RuntimeError("Feld ist schon belegt!")
 
    def isEmpty(self,x,y):
        return self.feld[x][y]==0
 
 
# Initialierung einer Instanz    
t = TicTacToe()
# Ruft automatisch __str__ auf, zur Umwandlung in einen String
print(t)
t.set(1,1,1);
print(t)
t.set(0,0,2);
print(t)
t.set(0,0,1); # Generiert einen Fehler.

Aufgaben

Damit die Maus-Abfrage innerhalb der Klasse erfolgen kann und keine globale Variable benötigt wird, kann eine Closure verwendet werden (das ist eine Funktion, die auf die Variablen Zugriff hat, die an der Stelle ihrer Erzeugung gültig waren):

class TicTacToe:
    # Konstruktur, wird bei der Kreation der Instanz aufgerufen
    def __init__(self):
        # leeres Feld, bestehend aus 3 Arrays mit 3 Einträgen
        self.feld = [[0,0,0] for i in range(3)]
        # Closure: Lokale Funktion, die auch später noch Zugriff auf self hat:
        def handler(x,y):
            self.onKlick(x,y)
        # Die Closure registrieren
        makeGPanel(0, 3, 0, 3, mousePressed = handler)
 
    def onKlick(self,x,y):
        # Tu wat sinnvolles damit...
        pass

Lösungsvorschlag

Lösungsvorschlag

tictactoe.py
from gpanel import *
 
class TicTacToe:
    # Konstruktur, wird bei der Kreation der Instanz aufgerufen
    def __init__(self):
        # leeres Feld, bestehend aus 3 Arrays mit 3 Einträgen
        self.feld = [[0,0,0] for i in range(3)]
 
        def handler(x,y):
            self.onKlick(x,y)
 
        makeGPanel(0, 2.99, 0, 2.99, mousePressed = handler)
 
 
    def draw(self):
        clear()
        for i in range(1,3):
            line(i,0,i,3)
            line(0,i,3,i)
        for x in range(3):
            for y in range(3):
                if self.feld[x][y]==1:
                    move(x+0.5, y+0.5)
                    circle(0.4)
                elif self.feld[x][y]==2:
                    line(x+0.1, y+0.1, x+0.9, y+0.9)
                    line(x+0.1, y+0.9, x+0.9, y+0.1)
 
    def onKlick(self,x,y):
        x = int(x)
        y = int(y)
        if (self.feld[x][y]==0):
            self.set(x,y,1)
            self.draw()
 
    # Setze alle Einträge auf 0
    def clear(self):
        for x in range(3):
            for y in range(3):
                self.feld[x][y] = 0
 
    # Standard-Methode zur Umwandlung in einen String, wird von print verwendet
    def __str__(self):
        symbols = [" ", "O", "X"]
        res = ""
        for y in range(3):
            for x in range(3):
                res += " "+symbols[self.feld[x][y]]+" "
                if (x<2):
                    res += "|"
            res += "\n"
            if (y<2):
                res += "---+---+---\n"
        return res
 
 
 
 
    # Setzt ein Feld, wenn es leer ist
    def set(self,x,y,what):
        if (self.feld[x][y]==0):
            self.feld[x][y] = what
        else:
            raise RuntimeError("Feld (%d, %d) ist schon mit %d belegt!" % (x,y,self.feld[x][y]))
 
    def isEmpty(self,x,y):
        return self.feld[x][y]==0
 
 
# Initialierung einer Instanz    
t = TicTacToe()
# Ruft automatisch __str__ auf, zur Umwandlung in einen String
print(t)
t.set(1,1,1);
print(t)
t.set(0,0,2);
print(t)
t.draw()
  • Fügen Sie eine weitere Instanz-Variable hinzu, die speichert, wer gerade an der Reihe ist, damit mit der Maus abwechslungsweise ein Kreis und ein Kreuz gesetzt werden kann.
  • Fügen Sie eine Überprüfung hinzu, ob jemand schon gewonnen hat. Sie sollten dazu möglichst wenig Code kopieren. Idee: Eine Funktion, die Start-Koordinaten und einen Vektor bekommt, in welche Richtung 3 Felder überprüft werden sollen.

minimal spielbare Version

minimal spielbare Version

tictactoe.py
from gpanel import *
 
 
class TicTacToe:
    # Konstruktur, wird bei der Kreation der Instanz aufgerufen
    def __init__(self):
        # leeres Feld, bestehend aus 3 Arrays mit 3 Einträgen
        self.feld = [[0,0,0] for i in range(3)]
        self.clear()
        def handler(x,y):
            self.onKlick(x,y)
 
        makeGPanel(0, 2.99, 0, 2.99, mousePressed = handler)
 
 
    def draw(self):
        clear()
        for i in range(1,3):
            line(i,0,i,3)
            line(0,i,3,i)
        for x in range(3):
            for y in range(3):
                if self.feld[x][y]==1:
                    move(x+0.5, y+0.5)
                    circle(0.4)
                elif self.feld[x][y]==2:
                    line(x+0.1, y+0.1, x+0.9, y+0.9)
                    line(x+0.1, y+0.9, x+0.9, y+0.1)
 
    def onKlick(self,x,y):
        if self.winner()>0 or self.placed==9:
            # 1 Sekunde warten
            self.clear()
            self.draw()
            return
        x = int(x)
        y = int(y)
        if (self.feld[x][y]==0):
            self.placed += 1
            self.set(x,y,self.player)
            self.draw()
            self.player = 3-self.player
 
    # Setze alle Einträge auf 0
    def clear(self):
        self.player = 1
        self.placed = 0
        for x in range(3):
            for y in range(3):
                self.feld[x][y] = 0
 
    # Standard-Methode zur Umwandlung in einen String, wird von print verwendet
    def __str__(self):
        symbols = [" ", "O", "X"]
        res = ""
        for y in range(3):
            for x in range(3):
                res += " "+symbols[self.feld[x][y]]+" "
                if (x<2):
                    res += "|"
            res += "\n"
            if (y<2):
                res += "---+---+---\n"
        return res
 
    def check(self,p,v):
        feld = self.feld[p[0]][p[1]]
        if feld==0:
            return 0
        # Liste/Array kopieren
        p = [p[i] for i in range(len(p))]
        for i in range(2):
            # Punkt vorruecken
            for j in range(2):
                p[j] += v[j]
            if self.feld[p[0]][p[1]]!=feld:
                return 0
        return feld
 
    # Liefert 0 (kein Gewinner) oder die Nummer 1 oder 2, wenn jemand gewonnen hat
    def winner(self):
        for i in range(3):
            # Horizontal
            w = self.check((0,i),(1,0))
            if w>0:
                return w
            # Vertikal
            w = self.check((i,0),(0,1))
            if w>0:
                return w
        # erste Diagonale
        w = self.check((0,0),(1,1))
        if w>0:
            return w
        # zweite Diagonale
        w = self.check((0,2),(1,-1))
        if w>0:
            return w
 
 
 
    # Setzt ein Feld, wenn es leer ist
    def set(self,x,y,what):
        if (self.feld[x][y]==0):
            self.feld[x][y] = what
        else:
            raise RuntimeError("Feld (%d, %d) ist schon mit %d belegt!" % (x,y,self.feld[x][y]))
 
    def isEmpty(self,x,y):
        return self.feld[x][y]==0
 
 
# Initialierung einer Instanz    
t = TicTacToe()
# Ruft automatisch __str__ auf, zur Umwandlung in einen String
t.draw()
  • lehrkraefte/blc/informatik/ffprg1-2020/klassen.txt
  • Last modified: 2020/04/15 13:03
  • by Ivo Blöchliger