Compare commits
5 Commits
8d4cb47c57
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fb0a8dd856 | |||
| 945c7df194 | |||
| 1e0559f386 | |||
| 5232310e3e | |||
| 45edb546f7 |
75
README.md
Normal file
75
README.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Nimm-Spiel
|
||||
|
||||
Dieses Programm ist eine einfache KI, die das Nimm-Spiel spielt. Damit kann einfach gezeigt werden
|
||||
wie das Training einer KI funktioniert.
|
||||
|
||||
## Regeln
|
||||
|
||||
Auf einen Haufen liegen zwölf Hölzchen. Zwei Spieler müssen abwechselnd ein bis drei Hölzchen vom
|
||||
Haufen nehmen. Der Spieler, der das letzte Hölzchen nimmt, hat verloren.
|
||||
|
||||
## Optimale Strategie
|
||||
|
||||
Wenn man dran ist und nur ein Hölzchen hat, hat man verloren, da man es nehmen muss. Wenn also zwei
|
||||
bis vier Hölzchen hat, kann man immer so viele Hölzchen wegnehmen, dass der andere nur noch eins
|
||||
hat, sodass er verloren hat und man selbst gewonnen. Mit fünf Hölzchen hat man dann wieder verloren,
|
||||
da man so viel Hölzchen wegnehmen muss, dass der andere zwei bis vier hat.
|
||||
|
||||
Dieses Muster setzt sich weiter fort und kann zu folgender Regel verallgemeinert werden: Hat ein
|
||||
Spieler $n$ Hölzchen, dann gibt es für seiner Gegner eine Gewinnstrategie genau dann, wenn $n \bmod 4 = 1$.
|
||||
|
||||
Daraus resultiert die optimale Strategie, mit der man versucht so viele Hölzchen wegzunehmen, dass
|
||||
noch $n \bmod 4 = 1$ da sind. Wenn das nicht geht, da man selbst $n \bmod 4 = 1$ Hölzchen hat, ist es
|
||||
egal, was man macht. Diese Strategie habe ich in der Funktion `optimal_move` umgesetzt.
|
||||
|
||||
## KI-Implementierung
|
||||
|
||||
Für die Implementierung der KI habe ich die Programmiersprache Python gewählt, da ich mit ihr
|
||||
vertraut bin. Dabei fängt die KI immer mit dem ersten Zug an, da sie sonst gar nicht gewinnen kann.
|
||||
|
||||
Training und Evaluation habe ich mit allen Kombinationen aus der optimalen und zufälligen Strategie
|
||||
gemacht, da es erstens interessant ist, ob die KI auch von einem Gegner, der schlecht spielt, gut
|
||||
lernen kann. Zweitens hätte es auch vorkommen können, dass, wenn man mit der optimalen Strategie
|
||||
trainiert, die KI bei der Evaluation mit der zufälligen Strategie gar nicht so gut ist, weil der
|
||||
zufällige Gegner auch Züge macht, die die KI vorher noch nie gesehen hat.
|
||||
|
||||
### Erste Version
|
||||
|
||||
Die erste Version der KI nimmt immer nur den letzten Zug, den sie gemacht hat raus, wenn sie
|
||||
verloren hat. Wenn sie keinen der Züge bevorzugt, dann wählt sie einfach einen zufälligen Zug aus.
|
||||
Mit dieser KI werden allerdings nur wenige Spiele gewonnen:
|
||||
|
||||
```
|
||||
KI: erste Version trainiert mit random_move und evaluiert mit random_move
|
||||
Die KI hat 70.078% der Spiele gewonnen.
|
||||
|
||||
KI: erste Version trainiert mit random_move und evaluiert mit optimal_move
|
||||
Die KI hat 4.884% der Spiele gewonnen.
|
||||
|
||||
KI: erste Version trainiert mit optimal_move und evaluiert mit random_move
|
||||
Die KI hat 70.192% der Spiele gewonnen.
|
||||
|
||||
KI: erste Version trainiert mit optimal_move und evaluiert mit optimal_move
|
||||
Die KI hat 4.984999999999999% der Spiele gewonnen.
|
||||
```
|
||||
|
||||
### bessere KI
|
||||
|
||||
Die bessere Version erweitert ihr Wissen stückweise. Sie markiert auch wie die erste Version Züge,
|
||||
die unmittelbar verlieren als verlierend. Allerdings markiert sie zusätzlich, wenn alle Züge
|
||||
verlierend sind, auch den letzten Zug, den sie gemacht hat als verlierend. Diese KI schafft es alle
|
||||
Spiele zu gewinnen:
|
||||
|
||||
```
|
||||
KI: optimale Version trainiert mit random_move und evaluiert mit random_move
|
||||
Die KI hat 100.0% der Spiele gewonnen.
|
||||
|
||||
KI: optimale Version trainiert mit random_move und evaluiert mit optimal_move
|
||||
Die KI hat 100.0% der Spiele gewonnen.
|
||||
|
||||
KI: optimale Version trainiert mit optimal_move und evaluiert mit random_move
|
||||
Die KI hat 100.0% der Spiele gewonnen.
|
||||
|
||||
KI: optimale Version trainiert mit optimal_move und evaluiert mit optimal_move
|
||||
Die KI hat 100.0% der Spiele gewonnen.
|
||||
```
|
||||
41
nimm.py
41
nimm.py
@ -9,11 +9,14 @@ class Move:
|
||||
def optimal_move():
|
||||
global state
|
||||
for n in [1, 2, 3]:
|
||||
if (state - n) % 4 == 1:
|
||||
if (state - n) % 4 == 1: # Wenn der andere Spieler bei diesem Zug verliert
|
||||
return n
|
||||
return random.randint(1, 3) # Wenn keine perfekte Wahl, dann irgendein Zug
|
||||
|
||||
def ki_move():
|
||||
def random_move():
|
||||
return random.randint(1, 3)
|
||||
|
||||
def ki_move(lastMove, better_version):
|
||||
global state
|
||||
antiMoves = lostMoves.get(state)
|
||||
if antiMoves == None:
|
||||
@ -24,6 +27,10 @@ def ki_move():
|
||||
for i, good in enumerate(availableMoves):
|
||||
if good:
|
||||
return i + 1
|
||||
|
||||
# Es gibt keine guten Züge mehr, also muss der letzte Zug schlecht gewesen sein.
|
||||
if better_version:
|
||||
addLostMove(lastMove.state, lastMove.move)
|
||||
return random.randint(1, 3)
|
||||
|
||||
def makeMove(move): # gibt True zurück, wenn der Spieler verloren hat
|
||||
@ -39,23 +46,23 @@ def addLostMove(state, move):
|
||||
if moves == None:
|
||||
moves = []
|
||||
moves.append(move)
|
||||
lostMoves[state] = moves
|
||||
|
||||
def game(train):
|
||||
def game(train, opponent, better_version):
|
||||
global state
|
||||
state = 12
|
||||
lastMove = None
|
||||
while True:
|
||||
# KI beginnt, sonst kann sie nicht gewinnen
|
||||
move = ki_move()
|
||||
move = ki_move(lastMove, better_version)
|
||||
lost = makeMove(move)
|
||||
if lost:
|
||||
if train:
|
||||
addLostMove(state + move, move)
|
||||
addLostMove(lastMove.state, lastMove.move)
|
||||
return 0 # optimale Strategie hat gewonnen
|
||||
lastMove = Move(state + move, move)
|
||||
|
||||
move = optimal_move()
|
||||
move = opponent()
|
||||
lost = makeMove(move)
|
||||
if lost:
|
||||
return 1 # KI hat gewonnen
|
||||
@ -64,13 +71,19 @@ state = 12
|
||||
lostMoves = {}
|
||||
|
||||
# train
|
||||
for _ in range(1000):
|
||||
game(True)
|
||||
opponents = [random_move, optimal_move]
|
||||
for ki_version in [False, True]:
|
||||
for train_opponent in opponents:
|
||||
for eval_opponent in opponents:
|
||||
ki_text = "optimale Version" if ki_version else "erste Version"
|
||||
print(f"KI: {ki_text} trainiert mit {train_opponent.__name__} und evaluiert mit {eval_opponent.__name__}")
|
||||
for _ in range(1000):
|
||||
game(train=True, opponent=train_opponent, better_version=ki_version)
|
||||
|
||||
# eval
|
||||
numberEvalGames = 100000
|
||||
wonGames = 0
|
||||
for _ in range(numberEvalGames):
|
||||
wonGames += game(False)
|
||||
# eval
|
||||
numberEvalGames = 100000
|
||||
wonGames = 0
|
||||
for _ in range(numberEvalGames):
|
||||
wonGames += game(False, opponent=eval_opponent, better_version=ki_version)
|
||||
|
||||
print(f"Die KI hat {wonGames / numberEvalGames * 100}% der Spiele gewonnen.")
|
||||
print(f"Die KI hat {wonGames / numberEvalGames * 100}% der Spiele gewonnen.\n")
|
||||
|
||||
Reference in New Issue
Block a user