add generator code

This commit is contained in:
sigmasternchen 2024-10-23 23:05:10 +02:00
parent f9f79269cc
commit 2ee8804ab7
5 changed files with 240 additions and 0 deletions

39
generator/__main__.py Normal file
View file

@ -0,0 +1,39 @@
from jinja2 import Environment, select_autoescape, FileSystemLoader
from generator.model import Board, NOT_DETERMINED
from generator.tictactoe import calculate_best_move
env = Environment(
loader=FileSystemLoader(["./generator"]),
autoescape=select_autoescape()
)
template = env.get_template("template.html")
def render_board(initial_prefix, prefix, board):
with open("output/" + prefix + ".html", "w") as file:
file.write(template.render(board=board, prefix=prefix, reset=initial_prefix + ".html"))
pass
def generate_options(initial_prefix, prefix, board):
render_board(initial_prefix, prefix, board)
if board.winner() == NOT_DETERMINED:
for move in board.moves():
future = board.apply(move)
response, _ = calculate_best_move(future)
print(prefix, move, response)
generate_options(
initial_prefix,
prefix + str(move),
future.apply(response) if response else future
)
if __name__ == "__main__":
board = Board()
generate_options("g", "g", board)

104
generator/model.py Normal file
View file

@ -0,0 +1,104 @@
import copy
FREE = 0
DRAW = 0
NOT_DETERMINED = -1
WIN = 1
LOSS = -1
PLAYER1 = 1
PLAYER2 = 2
class Move:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return str(self.x) + str(self.y)
def __repr__(self):
return str(self)
class Board:
field = [
[FREE, FREE, FREE],
[FREE, FREE, FREE],
[FREE, FREE, FREE]
]
current = PLAYER1
def moves(self):
moves = []
for y in range(3):
for x in range(3):
if self.field[y][x] == FREE:
moves.append(Move(x, y))
return moves
def apply(self, move):
board = Board()
board.field = copy.deepcopy(self.field)
board.field[move.y][move.x] = self.current
board.current = PLAYER2 if self.current == PLAYER1 else PLAYER1
return board
def winner(self):
for y in range(3):
player = self.field[y][0]
won = True
if player != FREE:
for x in range(3):
if self.field[y][x] != player:
won = False
break
if won:
return player
for x in range(3):
player = self.field[0][x]
won = True
if player != FREE:
for y in range(3):
if self.field[y][x] != player:
won = False
break
if won:
return player
player = self.field[0][0]
won = True
if player != FREE:
for i in range(3):
if self.field[i][i] != player:
won = False
break
if won:
return player
player = self.field[2][0]
won = True
if player != FREE:
for i in range(3):
if self.field[2 - i][i] != player:
won = False
break
if won:
return player
for y in range(3):
for x in range(3):
if self.field[y][x] == FREE:
return NOT_DETERMINED
return DRAW
def visualize(self):
for y in range(3):
for x in range(3):
print(self.field[y][x], end=' ')
print("")

52
generator/template.html Normal file
View file

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>{{- "" -}}
<head>{{- "" -}}
<title>HTML-only Tic-Tac-Toe</title>{{- "" -}}
</head>
{{- "" -}}
<body>{{- "" -}}
<h1>HTML-only Tic-Tac-Toe</h1>{{- "" -}}
<h2>Yes, really.</h2>{{- "" -}}
{%- set winner = board.winner() -%}
<a href={{ reset }}>Start over</a>
<table border=1 cellpadding=10>
{%- for y in range(3) -%}
<tr>
{%- for x in range(3) -%}
<td>
{%- set cell = board.field[y][x] -%}
{%- if cell != 0 -%}
{{- 'X' if cell == 1 else 'O' -}}
{%- elif winner != -1 -%}
&nbsp;
{%- else -%}
<a href={{ prefix }}{{ x }}{{ y }}.html>?</a>
{%- endif -%}
</td>
{%- endfor -%}
</tr>
{%- endfor -%}
</table>
{{- "" -}}
{%- if winner != -1 -%}
{%- if winner == 0 -%}
<h3>Draw</h3>
{%- elif winner == 1 -%}
<h3>You won! (How??)</h3>
{%- elif winner == 2 -%}
<h3>You lost!</h3>
<h4>... At Tic-Tac-Toe...</h4>
<h5>... Against HTML...</h5>
{%- endif -%}
<a href={{ reset }}>Try again!</a>
{%- endif -%}
</body>
{{- "" -}}
</html>{{- "" -}}

44
generator/tictactoe.py Normal file
View file

@ -0,0 +1,44 @@
from generator.model import DRAW, NOT_DETERMINED, WIN, LOSS
def transform_board_to_result(board):
winner = board.winner()
if winner == DRAW:
return DRAW
elif winner == NOT_DETERMINED:
return NOT_DETERMINED
elif winner == board.current:
return WIN
else:
return LOSS
def calculate_best_move(board):
player = board.current
candidates = []
winner = board.winner()
if winner != NOT_DETERMINED:
return None, transform_board_to_result(board)
for move in board.moves():
future = board.apply(move)
winner = future.winner()
if winner == player:
return move, WIN
elif winner == DRAW:
candidates.append((move, DRAW))
elif winner == NOT_DETERMINED:
opponent_move = calculate_best_move(future)
if opponent_move[1] == DRAW:
candidates.append((move, DRAW))
if opponent_move[1] == WIN:
candidates.append((move, LOSS))
if opponent_move[1] == LOSS:
candidates.append((move, WIN))
candidates.sort(key=lambda candidate: candidate[1], reverse=True)
return candidates[0]

1
output/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.html