Created
August 21, 2019 10:29
-
-
Save tombasche/ef867b2bf7ecafa575833b04a18dfc20 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Create a tic-tac-toe board of 'n' width and height. | |
""" | |
import os | |
import sys | |
from typing import List | |
import numpy as np | |
class Square: | |
""" | |
Holds logic for a particular square including which token is on it, | |
and how to print it to stdout. | |
""" | |
def __init__(self): | |
self.token = "" | |
def print(self, end): | |
""" Pretty hacky print method """ | |
print("|{}|".format(" {} ".format(self.token) if self.token else " "), end=end) | |
def minimax(board, depth, player) -> List[int]: | |
if player == board.computer: | |
best = [0, -500] | |
else: | |
best = [0, 500] | |
if depth == 0 or board.game_over(): | |
score = board.evaluate() | |
return [0, score] | |
for cell in board.available_cells(): | |
board.squares[cell].token = player | |
score = minimax( | |
board, | |
depth - 1, | |
board.player if player == board.computer else board.computer, | |
) | |
board.squares[cell].token = "" | |
score[0] = cell | |
if player == board.computer: | |
if score[1] > best[1]: | |
best = score # max value | |
else: | |
if score[1] < best[1]: | |
best = score # min value | |
return best | |
class Board: | |
def __init__(self, size_x: int, size_y: int, player: str, computer: str): | |
self.size_x = size_x | |
self.size_y = size_y | |
self.squares = [ | |
Square() for _ in range(0, self.size_y) for _ in range(0, self.size_x) | |
] | |
self.winning_lines = self._calc_winning_lines() | |
self.player = player | |
self.computer = computer | |
def _calc_winning_lines(self) -> List[List[int]]: | |
winning_combos = [] | |
rows = [] # save this for building the numpy array | |
i = 0 | |
for _ in range(self.size_y): | |
row = list(range(i, self.size_y + i)) | |
winning_combos.append(row) | |
rows.append(row) | |
i += self.size_y | |
j = 0 | |
for _ in range(self.size_x): | |
column = list(range(j, self.size_x * self.size_x, self.size_x)) | |
winning_combos.append(column) | |
j += 1 | |
matrix = np.array(rows) | |
self.index_matrix = matrix | |
matrix_diags = [matrix[::-1, :].diagonal(i) for i in range(len(self.squares))] | |
matrix_diags.extend( | |
matrix.diagonal(i) for i in range(self.size_x, -self.size_y, -1) | |
) | |
for diag in matrix_diags: | |
# its the full board | |
if len(diag) == self.size_x: | |
winning_combos.append(diag.tolist()) | |
return winning_combos | |
def print(self): | |
for i, sx in enumerate(self.squares): | |
end = "" | |
if i % self.size_x == self.size_x - 1: | |
end = "\n" | |
self.squares[i].print(end=end) | |
print("") | |
print("---------") | |
for row in self.index_matrix: | |
print([index + 1 for index in row]) | |
def has_won(self, who) -> bool: | |
for winning_combo in self.winning_lines: | |
if all([self.squares[i].token == who for i in winning_combo]): | |
return True | |
return False | |
def tie(self) -> bool: | |
if all([sq.token != "" for sq in self.squares]): | |
return True | |
return False | |
def add(self, who: str, position: int): | |
if self.squares[position - 1].token == "": | |
self.squares[position - 1].token = who | |
else: | |
raise Exception | |
def game_over(self) -> bool: | |
if self.tie(): | |
return True | |
if self.has_won(self.player) or self.has_won(self.computer): | |
return True | |
return False | |
def available_cells(self) -> List[int]: | |
available_indices = [] | |
for i, sq in enumerate(self.squares): | |
if sq.token == "": | |
available_indices.append(i) | |
return available_indices | |
def ai_turn(self) -> int: | |
depth = len(self.available_cells()) | |
alg_result = minimax(b, depth, self.computer) | |
choice = alg_result[0] + 1 | |
return choice | |
def evaluate(self) -> int: | |
""" | |
Evaluate the current score for a particular outcome | |
for the minimax algorithm | |
Returns: a score between -1 .. 1 | |
""" | |
if self.has_won(self.computer): | |
score = 1 | |
elif self.has_won(self.player): | |
score = -1 | |
else: | |
score = 0 | |
return score | |
if __name__ == "__main__": | |
try: | |
dims = int(sys.argv[1]) | |
except Exception: | |
dims = 3 # default to 3 x 3 | |
b = Board(dims, dims, "x", "o") | |
computer_token = "o" | |
player_token = "x" | |
current_token = "x" | |
os.system("clear") | |
while True: | |
b.print() | |
if b.tie(): | |
os.system("clear") | |
b.print() | |
print("It's a draw!") | |
break | |
if current_token == computer_token: | |
b.add(current_token, b.ai_turn()) | |
else: | |
valid_move = False | |
while not valid_move: | |
square = input("Enter a number between 1 and {}".format(dims * dims)) | |
try: | |
b.add(current_token, int(square)) | |
except Exception: | |
print(f"{square} is invalid.") | |
else: | |
valid_move = True | |
os.system("clear") | |
if b.has_won(current_token): | |
os.system("clear") | |
b.print() | |
print(f"{current_token} has won!!") | |
break | |
if current_token == player_token: | |
current_token = computer_token | |
else: | |
current_token = player_token | |
os.system("clear") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage:
python3 board.py <optional board size>
where optional board size is an odd integer