import random, sys # "Konstanten" FIELD_EMPTY = " " # das Zeichen für ein noch nicht gesetztes Feld FIELD_SYMBOL = ("X", "O") # die Zeichen für die beiden Spieler def createEmptyBoard(size:int) -> list: """Erzeugt ein leeres Spielfeld quadratischer Größe size und gibt es zurück.""" board = [] # anfangs leer (wird im Folgenden aus mehreren Zeilen zusammengesetzt) for i in range(size): row = [FIELD_EMPTY]*size # eine Zeile (von anfangs leeren Zeichen) board.append(row) return board # entspricht bei size=3: [[" ", " ", " "], [" ", " ", " "], [" ", " ", " "]] def printBoard(board:list): """Gibt das Spielfeld board formatiert auf der Konsole aus.""" for i in board: # jede Zeile print("-" * (len(i)*4+1)) # pro Spalte 4 Zeichen plus ein Trenner zusätzlich for j in i: # jede Spalte print("| " + j + " ", end="") # 4 Zeichen (1xTrenner, 2xLeerzeichen, 1xSymbol) print("|") # letzter Trenner (Fencepost) und Zeilenumbruch # Fencepost-Problem: Abschließende Trennzeile: print("-" * (len(board[-1])*4+1)) def setSymbol(board:list, coord:tuple, symbol:str) -> bool: """Setzt das Symbol der übergebenen Koordinaten coord im Spielfeld board auf symbol. Die Koordinaten müssen als Tupel der Form (Zeile, Spalte) übergeben werden. Überprüft dann, ob der Spieler dadurch gewonnen hat und gibt entsprechend True/False zurück, d. h. True, falls der Spieler durch setzen des Feldes gewonnen hat.""" y = coord[0] # Zeile x = coord[1] # Spalte board[y][x] = symbol # setzt das Symbol im Feld # Sieg über (aktuelle) Spalte prüfen: won = True # Annahme for row in board: # gehe durch jede Zeile if row[x] != symbol: # überprüfe (aktuelle) Spalte in jeder Zeile won = False # nicht gewonnen über aktuelle Spalte, da mind. eine Zeile nicht passt if won: return True # Sieg über (aktuelle) Zeile prüfen: won = True # Annahme for col in board[y]: # gehe durch jede Spalte der aktuellen Zeile if col != symbol: won = False # nicht gewonnen über aktuelle Zeile, da mind. eine Spalte nicht passt if won: return True # Sieg über Diagonale 1 (von oben links nach unten rechts) prüfen: if (x == y): # nur dann muss Diagonale 1 überprüft werden! won = True # Annahme for i in range(len(board)): # Beispiel: Feld der Größe 3: gehe über 0, 1 und 2 if board[i][i] != symbol: won = False # nicht gewonnen über Diagonale 1 if won: return True # Sieg über Diagonale 2 (von unten links nach oben rechts) prüfen: if (x+y == len(board)-1): # nur dann muss Diagonale 2 überprüft werden! won = True # Annahme for i in range(len(board)): if board[i][len(board)-i-1] != symbol: won = False # nicht gewonnen über Diagonale 2 if won: return True return False # falls nie ein Sieg festgestellt wurde, dann logischerweise nicht gewonnen def isFinished(board:list) -> bool: """Gibt True zurück, falls alle Felder getippt wurden und das Spiel folglich unentschieden endete.""" # Hätte das Spiel nicht unentschieden geendet, so hätte die Funktion setSymbol entsprechend Rückmeldung gegeben. for i in board: # jede Zeile if FIELD_EMPTY in i: # -> noch unbelegtes Feld gefunden -> nicht fertig return False # else (nicht notwendig) return True def inputInt(description:str, minimumValue:int, maximumValue:int) -> int: """Diese Funktion liest einen ganzzahligen, positiven Zahlenwert (inkl. 0) vom Nutzer ein und gibt diesen zurück. minimumValue legt die untere Schranke fest (mind. 0), d. h. der Wert muss mindestens minimumValue sein. maximumValue legt die obere Schranke fest (unendlich = -1), d. h. der Wert darf maximal maximumValue sein.""" if minimumValue < 0: minimumValue = 0 # negative Wert können nur etwas komplizierter überprüft werden, daher ist hier das absolute Minimum 0 if maximumValue < 0: maximumValue = sys.maxsize # absolutes Maximum ist durch den PC bzw. Python begrenzt while True: value = input(str(description) + " ") # Beschreibung/Fragetext ausgeben if value.isdigit() and int(value) >= minimumValue and int(value) <= maximumValue: # Prüfe, ob eine gültige positive Ganzzahl eingegeben wurde # -> gültige Eingabe return int(value) else: # -> ungültige Eingabe print("Fehler: Bitte gib eine Ganzzahl im Wertebereich " + str(minimumValue) + " - " + str(maximumValue) + " ein.") def inputCoordinates(board:list) -> tuple: """Liest Koordinaten (Zeile, Spalte) vom Nutzer ein, wobei dieser Zeile und Spalte ab 1 (statt 0) eingeben soll. Die Funktion gibt die Koordinaten als Tupel zurück. Die zurückgegebenen Koordinaten sind dabei nicht bereits belegt und sind gültig im jeweiligen (als Parameter übergebenen) Feld.""" while True: zeile = inputInt("Gib eine Zeilennummer ein (1-" + str(len(board)) + "):\t", 1, len(board)) zeile -= 1 # weil der Benutzer ab 1 eingibt, intern aber ab 0 gearbeitet wird spalte = inputInt("Gib eine Spaltennummer ein (1-" + str(len(board[zeile])) + "):\t", 1, len(board[zeile])) spalte -= 1 # weil der Benutzer ab 1 eingibt, intern aber ab 0 gearbeitet wird if board[zeile][spalte] == FIELD_EMPTY: return (zeile, spalte) # else (muss nicht explizit geschrieben werden) print("Dieses Feld ist bereits mit " + board[zeile][spalte] + " belegt, bitte wähle ein anderes!") def playGame(): """Führt ein (einziges) TicTacToe-Spiel durch (-> Hauptfunktion).""" board = createEmptyBoard(inputInt("Wie groß soll das Spielfeld sein (1+)?", 1, -1)) # erstellt leeres Spielfeld (Spielfeldgröße (>= 2) wird vom Spieler bestimmt) currentPlayer = random.randint(0, 1) # legt den ersten Spieler zufällig fest (0 und 1 möglich) print("Alles klar! Heute beginnt Spieler " + str(currentPlayer+1) + ".\n") printBoard(board) # gibt das Spielfeld erstmalig aus (leer; Fencepost-Problem) while not isFinished(board): # solange noch eine Position frei ist (kein Unentschieden) print("\nSpieler " + str(currentPlayer+1) + " ist an der Reihe:") won = setSymbol(board, inputCoordinates(board), FIELD_SYMBOL[currentPlayer]) # Spieler gibt Koordinaten ein -> an dieser Position wird das Symbol gesetzt und geprüft, ob dadurch gewonnen wurde print("") # Leerzeile zwischen Eingabe des Spielers und Ausgabe des Spielfeldes printBoard(board) if won: # -> Spieler hat durch Setzen des Symbols gewonnen print("\nHerzlichen Glückwunsch, Spieler " + str(currentPlayer+1) + ". Du hast gewonnen!") break # -> else-Teil der while-Schleife wird nicht ausgeführt currentPlayer = (currentPlayer+1) % 2 # nächster Spieler (0->1, 1->0) else: # -> falls die while-Schleife nicht durch break verlassen wurde und die Bedingung nicht mehr wahr ist print("\nDas Spiel endet unentschieden!") def play(): """Führt so viele Spiele aus, wie gewünscht sind.""" print("\nWillkommen im Spiel TicTacToe!\n") # gibt eine Willkommensnachricht aus while True: # Schleife wird durch break verlassen playGame() # ein Spiel spielen if not input("\nMöchtet ihr erneut spielen [(j)a / (n)ein]? ").lower().startswith("j"): break print("\nVielen Dank für's Spielen!") play() # ruft die Funktion play auf