Skip to content

Instantly share code, notes, and snippets.

@tombasche
Created August 21, 2019 10:29
Show Gist options
  • Save tombasche/ef867b2bf7ecafa575833b04a18dfc20 to your computer and use it in GitHub Desktop.
Save tombasche/ef867b2bf7ecafa575833b04a18dfc20 to your computer and use it in GitHub Desktop.
"""
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")
@tombasche
Copy link
Author

Usage:

  • install numpy
    python3 board.py <optional board size> where optional board size is an odd integer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment