mirror of
https://github.com/sigmasternchen/html-tictactoe
synced 2025-03-15 03:28:55 +00:00
add generator code
This commit is contained in:
parent
f9f79269cc
commit
2ee8804ab7
5 changed files with 240 additions and 0 deletions
39
generator/__main__.py
Normal file
39
generator/__main__.py
Normal 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
104
generator/model.py
Normal 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
52
generator/template.html
Normal 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 -%}
|
||||
|
||||
{%- 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
44
generator/tictactoe.py
Normal 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
1
output/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.html
|
Loading…
Reference in a new issue