diff --git a/generator/__main__.py b/generator/__main__.py new file mode 100644 index 0000000..e110fa6 --- /dev/null +++ b/generator/__main__.py @@ -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) diff --git a/generator/model.py b/generator/model.py new file mode 100644 index 0000000..2e53931 --- /dev/null +++ b/generator/model.py @@ -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("") diff --git a/generator/template.html b/generator/template.html new file mode 100644 index 0000000..b54de3a --- /dev/null +++ b/generator/template.html @@ -0,0 +1,52 @@ + +{{- "" -}} +{{- "" -}} + HTML-only Tic-Tac-Toe{{- "" -}} + +{{- "" -}} +{{- "" -}} +

HTML-only Tic-Tac-Toe

{{- "" -}} +

Yes, really.

{{- "" -}} + +{%- set winner = board.winner() -%} + +Start over + + {%- for y in range(3) -%} + + {%- for x in range(3) -%} + + {%- endfor -%} + + {%- endfor -%} +
+ {%- set cell = board.field[y][x] -%} + {%- if cell != 0 -%} + {{- 'X' if cell == 1 else 'O' -}} + {%- elif winner != -1 -%} +   + {%- else -%} + ? + {%- endif -%} +
+{{- "" -}} + +{%- if winner != -1 -%} +{%- if winner == 0 -%} +

Draw

+{%- elif winner == 1 -%} +

You won! (How??)

+{%- elif winner == 2 -%} +

You lost!

+

... At Tic-Tac-Toe...

+
... Against HTML...
+{%- endif -%} + +Try again! +{%- endif -%} + + +{{- "" -}} + + +{{- "" -}} \ No newline at end of file diff --git a/generator/tictactoe.py b/generator/tictactoe.py new file mode 100644 index 0000000..2f3303a --- /dev/null +++ b/generator/tictactoe.py @@ -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] diff --git a/output/.gitignore b/output/.gitignore new file mode 100644 index 0000000..0b84df0 --- /dev/null +++ b/output/.gitignore @@ -0,0 +1 @@ +*.html \ No newline at end of file