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