Skip to content

Commit

Permalink
added Tictactoe files
Browse files Browse the repository at this point in the history
  • Loading branch information
hthant committed Jun 14, 2022
0 parents commit 2646d79
Show file tree
Hide file tree
Showing 18 changed files with 399 additions and 0 deletions.
1 change: 1 addition & 0 deletions .dmypy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"pid": 6732, "connection_name": "\\\\.\\pipe\\dmypy-TIxfa6Iz.pipe"}
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/Tictactoe_AI.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added assets/batman.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/happy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/star.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from tictactoe.board import Board
from tictactoe.gui import Gui
from minimax.engine import Engine

import pygame
import time

def main():
board = Board()
gui = Gui(500, 500, board, "Tic Tac Toe Pro")
player = board.P1
ai = board.P2
engine = Engine(ai, player)
clock = pygame.time.Clock()

running = True
while running:
if board.turn == ai and not board.is_gameover():
ai_move = engine.evaluate_best_move(board)
board.push(ai_move)
time.sleep(1)

for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONUP:
if board.is_gameover():
board.reset()
continue
if board.turn != player:
continue
tile = gui.get_clicked_tile(event.pos)
board.push(tile)

gui.update_display()
clock.tick(30)

pygame.quit()


if __name__ == "__main__":
main()
Empty file added minimax/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions minimax/engine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from tictactoe.board import Board

class Engine:
def __init__(self, ai: str, player: str) -> None:
self.ai = ai
self.player = player

def minimax(self, board: Board, ai_turn: bool, last_move: int) -> tuple:
available_moves = board.empty_squares()
if len(available_moves) == 0 or board.is_gameover():
return self.evaluate_board(board), last_move

if ai_turn:
max_eval = -1000
best_move = None
for move in available_moves:
board.move(move, self.ai)
eval_ = self.minimax(board, False, move)[0]
board.undo(move)
max_eval = max(max_eval, eval_)
if max_eval == eval_:
best_move = move
return max_eval, best_move
else:
min_eval = 1000
best_move = None
for move in available_moves:
board.move(move, self.player)
eval_ = self.minimax(board, True, move)[0]
board.undo(move)
min_eval = min(min_eval, eval_)
if min_eval == eval_:
best_move = move
return min_eval, best_move

def evaluate_board(self, board: Board) -> int:
if board.winner() == self.ai:
return 1
elif board.winner() == self.player:
return -1
return 0

def evaluate_best_move(self, board: Board) -> int:
_, best_move = self.minimax(board, True, 0)
return best_move

2 changes: 2 additions & 0 deletions tictactoe/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@


140 changes: 140 additions & 0 deletions tictactoe/board.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
from typing import Optional

class Board:
P1 = 'O'
P2 = 'X'
EMPTY = ' '
DRAW = 11

def __init__(self, size: int = 3) -> None:
self.size = size
self.first_move = self.P1
self.turn = self.first_move

self.SQUARES = self._squares()
self.ROWS, self.COLS = self._get_rows_cols()
self.DIAS = self._get_diagonals()
self.WIN_CONDITIONS = self.ROWS + self.COLS + self.DIAS
self.table = self._new_table()

def _squares(self) -> dict:
return {(r, c): r*self.size+c+1 for r in range(self.size) for c in range(self.size)}

def _get_rows_cols(self) -> tuple[list, list]:
rows: list[list] = [[] for _ in range(self.size)]
columns: list[list] = [[] for _ in range(self.size)]
for index, square in self.SQUARES.items():
r, c = index
rows[r].append(square)
columns[c].append(square)
return rows, columns

def _get_diagonals(self) -> list:
diagonals: list[list] = [[], []]
i = 1
j = self.size
for _ in range(self.size):
diagonals[0].append(i)
diagonals[1].append(j)
i += self.size + 1
j += self.size - 1
return diagonals

def _new_table(self) -> dict:
return {square: self.EMPTY for square in self.SQUARES.values()}

def reset(self) -> None:
self.table = self._new_table()
self.first_move = self.P2 if self.first_move == self.P1 else self.P1
self.turn = self.first_move

def print(self) -> None:
if self.winner():
print("Match Over!")
print("*"*13)
else:
print("Turn->> ", self.turn)
print('-' * 13)
for index, square_name in self.SQUARES.items():
r, c = index
sign = square_name if self.is_empty(square_name) else self.table[square_name]
print('|', end=' ')
print(sign, end=' ')
if c == self.size-1:
print('|')
print('-'*13)

def get_tile_index(self, square: int) -> Optional[tuple]:
for k, v in self.SQUARES.items():
if v == square:
return k
return None

def is_empty(self, square: int) -> bool:
return self.table[square] == self.EMPTY

def empty_squares(self) -> list[int]:
return [square for square in self.SQUARES.values() if self.is_empty(square)]

def move(self, square: int, symbol: str) -> bool:
if not self.is_empty(square):
return False
self.table[square] = symbol
return True

def undo(self, square: int) -> None:
self.table[square] = self.EMPTY

def push(self, square: Optional[int]) -> None:
if square is None:
return
if not self.move(square, self.turn):
print("Invalid Move!")
return
self.turn = self.P2 if self.turn == self.P1 else self.P1

def is_draw(self) -> bool:
if self.check_connection() is None and len(self.empty_squares()) == 0:
return True
return False

def winner(self) -> Optional[str]:
connected = self.check_connection()
if connected == self.P1:
return self.P1
elif connected == self.P2:
return self.P2
return None

def is_gameover(self) -> bool:
return self.winner() is not None or self.is_draw()

def check_connection(self) -> Optional[str]:
for row in self.WIN_CONDITIONS:
checklist = []
for square_name in row:
if self.is_empty(square_name):
continue
checklist.append(self.table[square_name])
if len(checklist) == self.size and len(set(checklist)) == 1:
return checklist[0]
return None

def main():
board = Board(3)
board.print()
running = True
while running:
move = int(input(f"Enter {board.turn} 's move: "))
board.push(move)
board.print()
if board.is_gameover():
running = False
if board.is_draw():
print("Draw! What a great match!")
else:
print(board.winner(), " Wins....!")


if __name__ == "__main__":
main()
Loading

0 comments on commit 2646d79

Please sign in to comment.