erste Version am 27.12.2022
letzte Änderung am
27.12.2022
Tic Tac Toe im Terminal
In den letzten Wochen habe ich mir zum Spielen nach Feierabend immer
mal wieder eine Programmieraufgabe bei LeetCode rausgesucht.
Wenn mein Script eigentlich funktioniert hat, aber wegen
schlechten Laufzeitverhaltens abgelehnt wurde, habe ich gelegentlich
bei NeetCode gespickt - sofern es dort eine
Lösung gab.
An meinem Sudoku-Solver habe ich
sogar zwei/drei Abende rumgebastelt (das Ding war als Difficulty=Hard
deklariert).
Am Schluß hatte mein Script 602 Zeilen und wurde akzeptiert. Kurz
danach war ich allerdings etwas frustriert, als ich mir andere
Lösungen angesehen hatte.
Meine Lösung ist erst als allerletzten Ausweg in die Rekursion
eingestiegen. Alle einfachen Fälle wurden vorab anderweitig
abgearbeitet. So quasi mit O(N).
Alle anderen gesichteten Lösungen waren direkt rekusiv ... einfach
stumpfes Durchprobieren. Offenbar aber trotzdem schnell genug. Doof.
Hatte ich nicht erwartet.
Aber egal.
Die jüngste Programmieraufgabe habe ich mir selbst gestellt. Zwar
gibt es bei LeetCode Aufgaben bzgl. TicTacToe, aber die
1275 ist trivial und die 348 ist
immerhin als medium deklariert, jedoch bekommt man die
nur als zahlender Kunde zu sehen. Hier könnte man sie
zwar sehen...aber wenige Sekunden von dem Video reichen mir, um
da wohl keinen Spaß dran zu haben.
Laut dem Film War Games geht das
Spiel immer unentschieden aus, wenn beide Spieler keine Fehler
machen. Das will ich!
Also ein Spiel, gegen das man bestenfalls ein Unentschieden erreichen kann.
Und es soll auch gegen sich selbst spielen können.
Aber ohne TensorFlow o.ä.
- dafür braucht es keine KI.
Und unsere heutige KI wird wohl noch weit davon entfernt
sein, die Sinnlosigkeit dieses Spiels zu erkennen .... um
daraus schließlich abzuleiten, dass ein weltweiter Atomkrieg
ebenso sinnlos ist....
Der Film war unterhaltsam, hatte sogar eine klare Botschaft
(lasst Computer niemals die Entscheidung über das Abfeuern
von ICBMs treffen),
das Ende der Story war jedoch Quatsch.
Hier der Python-Code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# TicTacToe.py
#
# Detlev Ahlgrimm 23.12.2022
#
# 27.12.2022 D.A. Spieler wählbar
import sys
import time
import random
KEYS = [ "7", "8", "9", "4", "5", "6", "1", "2", "3", "q" ]
TRANSPOSE = { "7": 0, "8": 1, "9": 2,
"4": 3, "5": 4, "6": 5,
"1": 6, "2": 7, "3": 8 }
WON = [ (0, 1, 2), (3, 4, 5), (6, 7, 8),
(0, 3, 6), (1, 4, 7), (2, 5, 8),
(0, 4, 8), (2, 4, 6), ]
SWAP = { "X": "O", "O": "X" }
board = [ " ", " ", " ", " ", " ", " ", " ", " ", " " ]
# ----------------------------------------------------------------------
# Zeigt ein Spielfeld mit den jeweiligen Tasten an.
def show_keys():
for y in (0, 1, 2):
for x in (0, 1, 2):
if x < 2:
print(" %s |"%(KEYS[y*3 + x],), end="")
else:
print(" %s"%(KEYS[y*3 + x],))
if y < 2:
print("---+---+---")
print()
# ----------------------------------------------------------------------
# Liefert den gewählten Spielmode.
def get_mode():
print("0 = Mensch/Mensch")
print("1 = Maschine/Mensch")
print("2 = Mensch/Maschine")
print("3 = Maschine/Maschine")
st = input("Spieltyp [0, 1, (2), 3]:")
if st == "" or st not in ("0", "1", "2", "3"):
st = "2"
return int(st)
# ----------------------------------------------------------------------
# Zeigt das aktuelle Spielfeld an.
def print_game():
print()
for y in (0, 1, 2):
for x in (0, 1, 2):
if x < 2:
print(" %s |"%(board[y*3 + x],), end="")
else:
print(" %s"%(board[y*3 + x],))
if y < 2:
print("---+---+---")
# ----------------------------------------------------------------------
# Liefert "X" oder "O", wenn der jeweilige Spieler gewonnen hat. Sonst
# wird None geliefert.
def is_won():
found = False
for t in WON:
s = ""
for p in t:
y, x = p // 3, p % 3
if board[y*3 + x] == " ":
break
s += board[y*3 + x]
if len(s) != 3:
continue
if s[0] == s[1] and s[1] == s[2]:
return s[0]
return
# ----------------------------------------------------------------------
# Liefert eine Liste mit den Index-Werten von unbelegten Feldern.
def get_free():
free = []
for i in range(9):
if board[i] == " ":
free.append(i)
return free
# ----------------------------------------------------------------------
# Liefert den nächsten Zug von Spieler "player" als Index.
def make_move(player, computer):
best_rank = None
best_move = None
free = get_free()
if len(free) == 9: # wenn der computer anfängt...
return random.randint(0, 8) # ...zufälliges Feld liefern
for i in free:
board[i] = player
rank = find_rank(SWAP[player], computer)
board[i] = " "
if best_rank is None or rank > best_rank:
best_rank = rank
best_move = i
return best_move
# ----------------------------------------------------------------------
# Liefert eine Bewertung für die aktuelle Spiel-Stellung aus Sicht von
# Spieler "computer".
def find_rank(player, computer):
free = get_free()
iw = is_won()
if iw == computer:
return 1
elif iw == SWAP[computer]:
return -1
if len(free) == 0:
return 0
ranks = []
for i in free:
board[i] = player
ranks.append(find_rank(SWAP[player], computer))
board[i] = " "
if player == computer:
return max(ranks)
return min(ranks)
# ----------------------------------------------------------------------
#
if __name__ == '__main__':
show_keys()
st = get_mode()
current_player = "X"
if st == 1:
computer = "X"
elif st == 2:
computer = "O"
was_quit = False
while " " in board: # solange es noch freie Felder gibt
print_game()
t = is_won() # auf Ende durch Sieg testen
if t is not None:
print("\033[7A")
break
if st == 0 or (st in (1, 2) and current_player != computer):
print(" "*20, "\033[A") # Zeile löschen
p = input("Feld [1-9]:")
if not p in KEYS:
print("falsche Taste!")
continue
if p == "q":
was_quit = True
print("\033[8A") # acht Zeilen hoch
break
p = TRANSPOSE[p]
elif st == 3:
print()
p = make_move(current_player, current_player)
time.sleep(0.5)
elif st in (1, 2) and current_player == computer:
p = make_move(current_player, computer)
print()
y, x = p // 3, p % 3
if board[y*3 + x] == " ":
board[y*3 + x] = current_player
else:
print("Das Feld ist schon belegt!")
continue
current_player = SWAP[current_player]
print("\033[8A") # acht Zeilen hoch
print_game()
t = is_won()
if t is None and not was_quit:
print("unentschieden!")
elif t is not None:
if t == "X":
print("X hat gewonnen!")
else:
print("O hat gewonnen!")
print()
download als ZIP
Im Terminal sieht es dann beispielweise so aus:
dede@c12:~> ./TicTacToeV2.py
7 | 8 | 9
---+---+---
4 | 5 | 6
---+---+---
1 | 2 | 3
0 = Mensch/Mensch
1 = Maschine/Mensch
2 = Mensch/Maschine
3 = Maschine/Maschine
Spieltyp [0, 1, (2), 3]:3
X | X | O
---+---+---
O | O | X
---+---+---
X | O | X
unentschieden!
dede@c12:~>
|
Oder so, wenn man nicht aufpasst:
dede@c12:~> ./TicTacToeV2.py
7 | 8 | 9
---+---+---
4 | 5 | 6
---+---+---
1 | 2 | 3
0 = Mensch/Mensch
1 = Maschine/Mensch
2 = Mensch/Maschine
3 = Maschine/Maschine
Spieltyp [0, 1, (2), 3]:1
X | | O
---+---+---
X | X | X
---+---+---
O | | O
X hat gewonnen!
dede@c12:~>
|
Sinnigerweise wird der Ziffernblock zur Eingabe genutzt.