You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
294 lines
9.6 KiB
294 lines
9.6 KiB
import sys
|
|
import pygame
|
|
import numpy as np
|
|
import random
|
|
import copy
|
|
import time
|
|
|
|
from constants import *
|
|
|
|
# Initialize pygame
|
|
pygame.init()
|
|
screen = pygame.display.set_mode((WIDTH, LENGHT))
|
|
pygame.display.set_caption("Tic Tac Toe Game - AI ")
|
|
screen.fill( BACKGROUND_COLOR )
|
|
|
|
#Class : Matrix
|
|
class Matrix:
|
|
def __init__(self):
|
|
self.cases = np.zeros((GRID_ROWS, GRID_COLS))
|
|
self.empty_cases = self.cases
|
|
self.marked_case = 0
|
|
|
|
|
|
def winer(self, show=False):
|
|
'''
|
|
Check if there is a winner
|
|
return 0 if there is no winner yet
|
|
return 1 if player 1 win
|
|
return 2 if player 2 win
|
|
'''
|
|
|
|
|
|
# Check vertical lines
|
|
for column in range(GRID_COLS):
|
|
if self.cases[0][column] == self.cases[1][column] == self.cases[2][column] != 0:
|
|
if show:
|
|
color = PLAYER1_COLOR if self.cases[0][column] == 1 else PLAYER2_COLOR
|
|
initial_pos = (column * CASE_SIZE + CASE_SIZE//2, 20)
|
|
final_pos = (column * CASE_SIZE + CASE_SIZE//2, LENGHT - 20)
|
|
pygame.draw.line(screen, color, initial_pos, final_pos, LINE_WIDTH)
|
|
return self.cases[0][column]
|
|
|
|
# Check horizontal lines
|
|
for row in range(GRID_ROWS):
|
|
if self.cases[row][0] == self.cases[row][1] == self.cases[row][2] != 0:
|
|
if show:
|
|
color = PLAYER1_COLOR if self.cases[0][column] == 1 else PLAYER2_COLOR
|
|
initial_pos = (20, row * CASE_SIZE + CASE_SIZE//2)
|
|
final_pos = (WIDTH - 20, row * CASE_SIZE + CASE_SIZE//2)
|
|
pygame.draw.line(screen, color, initial_pos, final_pos, LINE_WIDTH)
|
|
return self.cases[row][0]
|
|
|
|
# Check diagonals
|
|
if self.cases[0][0] == self.cases[1][1] == self.cases[2][2] != 0:
|
|
if show:
|
|
color = PLAYER1_COLOR if self.cases[0][column] == 1 else PLAYER2_COLOR
|
|
initial_pos = (20, 20)
|
|
final_pos = (WIDTH - 20, LENGHT - 20)
|
|
pygame.draw.line(screen, color, initial_pos, final_pos, LINE_WIDTH)
|
|
return self.cases[0][0]
|
|
if self.cases[0][2] == self.cases[1][1] == self.cases[2][0] != 0:
|
|
if show:
|
|
color = PLAYER1_COLOR if self.cases[0][column] == 1 else PLAYER2_COLOR
|
|
initial_pos = (20, LENGHT - 20)
|
|
final_pos = (WIDTH - 20, 20)
|
|
pygame.draw.line(screen, color, initial_pos, final_pos, LINE_WIDTH)
|
|
return self.cases[0][2]
|
|
# Check if there is no winner yet
|
|
return 0
|
|
|
|
def empty_case(self, row,column):
|
|
return self.cases[row][column] == 0
|
|
|
|
def mark_case(self, row, column, player):
|
|
self.cases[row][column] = player
|
|
self.marked_case += 1
|
|
|
|
def get_empty_cases(self):
|
|
empty_cases = []
|
|
for row in range(GRID_ROWS):
|
|
for column in range(GRID_COLS):
|
|
if self.empty_case(row, column):
|
|
empty_cases.append((row, column))
|
|
return empty_cases
|
|
|
|
def isfull(self):
|
|
return self.marked_case == 9
|
|
|
|
def isempty(self):
|
|
return self.marked_case == 0
|
|
|
|
#Class : AI
|
|
class AI:
|
|
|
|
def __init__(self, level= 5, player=2):
|
|
self.level = level
|
|
self.player = player
|
|
|
|
def randomM(self,matrix):
|
|
empty_cases = matrix.get_empty_cases()
|
|
a = random.randrange(0,len(empty_cases))
|
|
|
|
return empty_cases[a] # (row, column)
|
|
|
|
def minimax(self, matrix, maximizing):
|
|
|
|
#Check if terminal case
|
|
case = matrix.winer()
|
|
#player 1 win
|
|
if case == 1:
|
|
return 1, None # eval, move
|
|
#player 2 win
|
|
if case == 2:
|
|
return -1, None # eval, move
|
|
#draw
|
|
elif matrix.isfull():
|
|
return 0, None # eval, move
|
|
|
|
if maximizing:
|
|
max_eval = -100
|
|
best_move = None
|
|
empty_cases = matrix.get_empty_cases()
|
|
|
|
for (row,column) in empty_cases:
|
|
temp_matrix = copy.deepcopy(matrix)
|
|
temp_matrix.mark_case(row, column, 1)
|
|
eval = self.minimax(temp_matrix, False)[0]
|
|
if eval > max_eval:
|
|
max_eval = eval
|
|
best_move = (row, column)
|
|
|
|
return max_eval, best_move # eval, move
|
|
|
|
elif not maximizing:
|
|
min_eval = 100
|
|
best_move = None
|
|
empty_cases = matrix.get_empty_cases()
|
|
|
|
for (row,column) in empty_cases:
|
|
temp_matrix = copy.deepcopy(matrix)
|
|
temp_matrix.mark_case(row, column, self.player)
|
|
eval = self.minimax(temp_matrix, True)[0]
|
|
if eval < min_eval:
|
|
min_eval = eval
|
|
best_move = (row, column)
|
|
|
|
return min_eval, best_move # eval, move
|
|
|
|
|
|
def evaluate(self, main_matrix):
|
|
if self.level == 0:
|
|
#random choice
|
|
eval = 'random'
|
|
move = self.randomM(main_matrix)
|
|
else:
|
|
#minimax algo choice
|
|
print("AI is thinking...")
|
|
time.sleep(1)
|
|
eval, move = self.minimax(main_matrix, False)
|
|
|
|
print(f"AI has chosen the pos {move} with an eval of {eval}")
|
|
|
|
return move # (row, column)
|
|
|
|
#Class : Game
|
|
class Game:
|
|
def __init__(self):
|
|
self.matrix = Matrix()
|
|
self.ai = AI()
|
|
self.player = 1 #1 = X, 2 = O
|
|
self.gamemode = 'ai' #1v1 or 1vAI
|
|
self.running = True
|
|
self.ai_starts = False
|
|
self.lines()
|
|
|
|
def draw_figures(self, row, column):
|
|
if self.player == 1:
|
|
#draw X
|
|
start_desc = (column * CASE_SIZE + CASE_SIZE // 4, row * CASE_SIZE + CASE_SIZE // 4)
|
|
end_desc = (column * CASE_SIZE + CASE_SIZE * 3 // 4, row * CASE_SIZE + CASE_SIZE * 3 // 4)
|
|
pygame.draw.line(screen,PLAYER1_COLOR, start_desc, end_desc, CROSS_WIDTH)
|
|
start_asc = (column * CASE_SIZE + CASE_SIZE // 4, row * CASE_SIZE + CASE_SIZE * 3 // 4)
|
|
end_asc = (column * CASE_SIZE + CASE_SIZE * 3 // 4, row * CASE_SIZE + CASE_SIZE // 4)
|
|
pygame.draw.line(screen,PLAYER1_COLOR, start_asc, end_asc, CROSS_WIDTH)
|
|
elif self.player == 2:
|
|
#draw O
|
|
center = ( column * CASE_SIZE + CASE_SIZE // 2, row * CASE_SIZE + CASE_SIZE // 2)
|
|
pygame.draw.circle(screen, PLAYER2_COLOR,center, CIRCLE_RADIUS, CIRCLE_WIDTH)
|
|
|
|
def make_move(self, row, col):
|
|
self.matrix.mark_case(row, col, self.player)
|
|
self.draw_figures(row, col)
|
|
self.player_turn()
|
|
|
|
def player_turn(self):
|
|
self.player = self.player % 2 + 1
|
|
|
|
def lines(self):
|
|
# Horizontal lines
|
|
pygame.draw.line(screen, LINE_COLOR, (0, CASE_SIZE), (WIDTH,CASE_SIZE), LINE_WIDTH)
|
|
pygame.draw.line(screen, LINE_COLOR, (0, LENGHT - CASE_SIZE), (WIDTH, LENGHT - CASE_SIZE), LINE_WIDTH)
|
|
|
|
#Vertical lines
|
|
pygame.draw.line(screen, LINE_COLOR, (CASE_SIZE, 0), (CASE_SIZE, LENGHT), LINE_WIDTH)
|
|
pygame.draw.line(screen, LINE_COLOR, (WIDTH - CASE_SIZE, 0), (WIDTH - CASE_SIZE, LENGHT), LINE_WIDTH)
|
|
|
|
def change_gamemode(self):
|
|
if self.gamemode == 'ai':
|
|
self.gamemode = '1v1'
|
|
else:
|
|
self.gamemode = 'ai'
|
|
|
|
def isover(self):
|
|
return self.matrix.winer(show=True) != 0 or self.matrix.isfull()
|
|
|
|
def reset(self):
|
|
self.__init__()
|
|
screen.fill(BACKGROUND_COLOR)
|
|
self.lines()
|
|
if self.ai_starts:
|
|
self.ai_first_move()
|
|
|
|
def ai_first_move(self):
|
|
if self.gamemode == 'ai' and self.player == self.ai.player and self.ai_starts:
|
|
row, column = self.ai.evaluate(self.matrix)
|
|
self.make_move(row, column)
|
|
self.ai_starts = False
|
|
|
|
def main():
|
|
|
|
# Object of Game class
|
|
game = Game()
|
|
matrix = game.matrix
|
|
ai = game.ai
|
|
|
|
# Main loop
|
|
while True:
|
|
|
|
for event in pygame.event.get():
|
|
|
|
if event.type == pygame.QUIT:
|
|
pygame.quit()
|
|
sys.exit()
|
|
|
|
if event.type == pygame.KEYDOWN:
|
|
|
|
# g-gamemode
|
|
if event.key == pygame.K_g:
|
|
game.change_gamemode()
|
|
|
|
# s-start
|
|
if event.key == pygame.K_s:
|
|
game.ai_starts = not game.ai_starts
|
|
game.ai_first_move()
|
|
|
|
#r-restart
|
|
if event.key == pygame.K_r:
|
|
game.reset()
|
|
matrix = game.matrix
|
|
ai = game.ai
|
|
|
|
# 0-random ai
|
|
if event.key == pygame.K_0:
|
|
ai.level = 0
|
|
|
|
# 1-easy ai
|
|
if event.key == pygame.K_1:
|
|
ai.level = 1
|
|
|
|
#click event
|
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
|
pos = event.pos
|
|
row = pos[1] // CASE_SIZE
|
|
column = pos[0] // CASE_SIZE
|
|
|
|
if matrix.empty_case(row,column) and game.running:
|
|
game.make_move(row, column)
|
|
|
|
if game.isover():
|
|
game.running = False
|
|
|
|
if game.gamemode == 'ai' and game.player == ai.player and game.running:
|
|
# update the screen
|
|
pygame.display.update()
|
|
|
|
# AI turn
|
|
row, column = ai.evaluate(matrix)
|
|
game.make_move(row, column)
|
|
|
|
if game.isover():
|
|
game.running = False
|
|
|
|
pygame.display.update()
|
|
main() |