-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"pid": 6732, "connection_name": "\\\\.\\pipe\\dmypy-TIxfa6Iz.pipe"} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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() |
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
|
||
|
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() |