Skip to content

Commit

Permalink
restructured
Browse files Browse the repository at this point in the history
  • Loading branch information
Vivek Dagar committed Apr 5, 2024
1 parent a601bba commit 4c09da4
Show file tree
Hide file tree
Showing 7 changed files with 439 additions and 1 deletion.
6 changes: 5 additions & 1 deletion .idea/TicTacAI.iml

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

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

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

5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
prettytable~=3.5.0
tabulate~=0.9.0
click~=8.1.7
setuptools~=68.0.0
wheel
28 changes: 28 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from setuptools import setup, find_packages

setup(
name='tictacai',
version='1.0',
packages=find_packages(),
install_requires=[
'prettytable~=3.5.0',
'tabulate~=0.9.0',
'click~=8.1.7',
'setuptools~=68.0.0',
'wheel'
],
entry_points={
'console_scripts': [
'tictacai = tictacai.interface:tictactoe',
],
},
author='Vivek Dagar',
author_email='[email protected]',
description='Tic Tac Toe game that allows you to play against a human or an AI powered by Minimax algorithm and '
'also saves your score.',
url='https://github.com/vivekkdagar/TicTacAI',
long_description="""Please refer to the Github for usage guide and more {
https://github.com/vivekkdagar/TicTacAI}""",
long_description_content_type='text/markdown',
license='MIT License',
)
282 changes: 282 additions & 0 deletions tictacai/game.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from math import inf as infinity
from random import choice
import platform
import time
from os import system
from tabulate import tabulate
from .statistics import Statistics


def abort():
print("\nGame aborted. Thanks for playing!")
exit()


def wins(state, player):
win_state = [
[state[0][0], state[0][1], state[0][2]],
[state[1][0], state[1][1], state[1][2]],
[state[2][0], state[2][1], state[2][2]],
[state[0][0], state[1][0], state[2][0]],
[state[0][1], state[1][1], state[2][1]],
[state[0][2], state[1][2], state[2][2]],
[state[0][0], state[1][1], state[2][2]],
[state[2][0], state[1][1], state[0][2]],
]
return [player, player, player] in win_state


def empty_cells(state):
cells = []
for x in range(len(state)):
for y in range(len(state[0])):
if state[x][y] == ' ':
cells.append([x, y])
return cells


def clean():
os_name = platform.system().lower()
if 'windows' in os_name:
system('cls')
else:
system('clear')


class Game:
def __init__(self):
self.HUMAN = 'X'
self.COMP = 'O'
self.board = [[' ' for _ in range(3)] for _ in range(3)]
self.h_choice = ''
self.c_choice = ''
self.score_card = Statistics()

def wipe_board(self):
self.board = [
[' ', ' ', ' '],
[' ', ' ', ' '],
[' ', ' ', ' '],
]

def evaluate(self, state):
if wins(state, self.COMP):
score = +1
elif wins(state, self.HUMAN):
score = -1
else:
score = 0

return score

def game_over(self, state):
return wins(state, self.HUMAN) or wins(state, self.COMP)

def valid_move(self, x, y):
return [x, y] in empty_cells(self.board)

def set_move(self, x, y, player):
if self.valid_move(x, y):
self.board[x][y] = player
return True
return False

def minimax(self, state, depth, player, alpha, beta):
if player == self.COMP:
best = [-1, -1, -infinity]
else:
best = [-1, -1, +infinity]

if depth == 0 or self.game_over(state):
score = self.evaluate(state)
return [-1, -1, score]

for cell in empty_cells(state):
x, y = cell[0], cell[1]
state[x][y] = player
score = self.minimax(state, depth - 1, self.HUMAN if player == self.COMP else self.COMP, alpha, beta)
state[x][y] = ' '
score[0], score[1] = x, y

if player == self.COMP:
if score[2] > best[2]:
best = score
alpha = max(alpha, best[2])
else:
if score[2] < best[2]:
best = score
beta = min(beta, best[2])

if alpha >= beta:
break

return best

def render(self):
chars = {'X': self.h_choice, 'O': self.c_choice, ' ': ' '}
table = [[chars[cell] for cell in row] for row in self.board]

print(tabulate(table, tablefmt="fancy_grid"))

def ai_turn(self):
depth = len(empty_cells(self.board))
if depth == 0 or self.game_over(self.board):
return

clean()
print(f'Computer turn [{self.c_choice}]')
self.render()

if depth == 9:
x = choice([0, 1, 2])
y = choice([0, 1, 2])
else:
move = self.minimax(self.board, depth, self.COMP, -infinity, infinity)
x, y = move[0], move[1]

self.set_move(x, y, self.COMP)
time.sleep(1)

def human_turn(self):
depth = len(empty_cells(self.board))
if depth == 0 or self.game_over(self.board):
return

move = -1
moves = {
1: [0, 0], 2: [0, 1], 3: [0, 2],
4: [1, 0], 5: [1, 1], 6: [1, 2],
7: [2, 0], 8: [2, 1], 9: [2, 2],
}

clean()
print(f'Human turn [{self.h_choice}]')
self.render()

while move < 1 or move > 9:
try:
move = int(input('Use numpad (1..9): '))
coord = moves[move]
can_move = self.set_move(coord[0], coord[1], self.HUMAN)

if not can_move:
print('Occupied cell')
move = -1
except (EOFError, KeyboardInterrupt):
print('Bye')
exit()
except (KeyError, ValueError):
print('Bad choice')

def play2_turn(self):
depth = len(empty_cells(self.board))
if depth == 0 or self.game_over(self.board):
return

move = -1
moves = {
1: [0, 0], 2: [0, 1], 3: [0, 2],
4: [1, 0], 5: [1, 1], 6: [1, 2],
7: [2, 0], 8: [2, 1], 9: [2, 2],
}

clean()
print(f'Player 2 turn [{self.c_choice}]')
self.render()

while move < 1 or move > 9:
try:
move = int(input('Use numpad (1..9): '))
coord = moves[move]
can_move = self.set_move(coord[0], coord[1], self.COMP)

if not can_move:
print('Bad move')
move = -1
except (EOFError, KeyboardInterrupt):
print('Bye')
exit()
except (KeyError, ValueError):
print('Bad choice')

def symbols(self):
clean()
self.h_choice = ''
self.c_choice = ''

while self.h_choice != 'O' and self.h_choice != 'X':
try:
print('')
self.h_choice = input('Choose X or O\nChosen: ').upper()
except (EOFError, KeyboardInterrupt):
print('Bye')
exit()
except (KeyError, ValueError):
print('Bad choice')

if self.h_choice == 'X':
self.c_choice = 'O'
else:
self.c_choice = 'X'

def play_game(self, mode):
first = ''
self.wipe_board()
while first != 'Y' and first != 'N':
try:
first = input('Player1/Human is first to start?[y/n]: ').upper()
except (EOFError, KeyboardInterrupt):
print('Bye')
exit()
except (KeyError, ValueError):
print('Bad choice')

while len(empty_cells(self.board)) > 0 and not self.game_over(self.board):
if first == 'N':
if mode == 'c':
self.ai_turn()
elif mode == 'h':
self.play2_turn()
first = ''

self.human_turn()
if mode == 'c':
self.ai_turn()
elif mode == 'h':
self.play2_turn()

if wins(self.board, self.HUMAN):
clean()
print(f'Human turn [{self.h_choice}]')
self.render()
if mode == 'c':
print('YOU WIN!')
Statistics().update_statistics("Player 1 wins against Computer", False)
elif mode == 'h':
print('PLAYER1 WINS!')
self.score_card.update_statistics("Player 1 wins against Human", False)
self.score_card.update_statistics('Updating total', True) # Total
elif wins(self.board, self.COMP):
clean()
if mode == 'c':
print(f'Computer turn [{self.c_choice}]')
else:
print(f'Player 2 turn [{self.h_choice}]')
self.render()
if mode == 'c':
print('YOU LOSE!')
elif mode == 'h':
print('PLAYER2 WINS!')
self.score_card.update_statistics('Updating total', True) # Total
else:
clean()
self.render()
print('DRAW!')
self.score_card.update_statistics('Draw', False)
self.score_card.update_statistics('Updating total', True) # Total
print("Going back to menu in 3 seconds")
time.sleep(3)
clean()
34 changes: 34 additions & 0 deletions tictacai/interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import click

from .game import Game


@click.group()
def tictactoe():
pass


@tictactoe.command()
def human():
game = Game()
game.symbols()
game.play_game('h')


@tictactoe.command()
def ai():
game = Game()
game.symbols()
game.play_game('c')


@tictactoe.command()
def scores():
Game().score_card.display_statistics()


if __name__ == '__main__':
tictactoe()
Loading

0 comments on commit 4c09da4

Please sign in to comment.