commit
c8414ae304
@ -0,0 +1,259 @@
|
||||
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):
|
||||
'''
|
||||
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:
|
||||
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:
|
||||
return self.cases[row][0]
|
||||
|
||||
# Check diagonals
|
||||
if self.cases[0][0] == self.cases[1][1] == self.cases[2][2] != 0:
|
||||
return self.cases[0][0]
|
||||
if self.cases[0][2] == self.cases[1][1] == self.cases[2][0] != 0:
|
||||
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.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() != 0 or self.matrix.isfull()
|
||||
|
||||
def reset(self):
|
||||
self.__init__()
|
||||
screen.fill(BACKGROUND_COLOR)
|
||||
self.lines()
|
||||
|
||||
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()
|
||||
|
||||
#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()
|
||||
|
Binary file not shown.
@ -0,0 +1,25 @@
|
||||
|
||||
#Window
|
||||
WIDTH = 800
|
||||
LENGHT = 800
|
||||
|
||||
GRID_ROWS = 3
|
||||
GRID_COLS = 3
|
||||
|
||||
CASE_SIZE = WIDTH // GRID_COLS
|
||||
|
||||
LINE_WIDTH = 15
|
||||
|
||||
# Circle
|
||||
CIRCLE_RADIUS = CASE_SIZE // 4
|
||||
CIRCLE_WIDTH = 15
|
||||
|
||||
# Cross
|
||||
CROSS_WIDTH = 25
|
||||
|
||||
# Colors (RGB)
|
||||
|
||||
BACKGROUND_COLOR = (46, 98, 163)
|
||||
LINE_COLOR = (255,205, 0)
|
||||
PLAYER2_COLOR = (98,195,248)
|
||||
PLAYER1_COLOR = (215, 0, 0)
|
Loading…
Reference in new issue