Skip to content

Instantly share code, notes, and snippets.

@TalhaProgrammer92
Forked from CodeWithHarry/Tic-Tac-Toe.py
Last active May 28, 2025 13:24
Show Gist options
  • Select an option

  • Save TalhaProgrammer92/6b5f2ae1e740b6080b75d304ae34cd19 to your computer and use it in GitHub Desktop.

Select an option

Save TalhaProgrammer92/6b5f2ae1e740b6080b75d304ae34cd19 to your computer and use it in GitHub Desktop.
Code for the tic tac toe game we created in this video: https://youtu.be/E8fmDDtaHLU on CodeWithHarry channel on YouTube
from random import randint, shuffle
from os import system, mkdir, name
from os.path import join, exists
import time
import sqlite3 as sq
# * Check if folder 'save' exists or not
if not exists('save'):
mkdir('save')
# * Function - Clear screen
def clrscr() -> None:
system('cls' if name == 'nt' else 'clear')
##################################
# Player - Handle player data
##################################
class Player:
def __init__(self, **kwargs):
self.__name: str = kwargs.get('name', 'unknown')
self.__score: int = kwargs.get('score', 0)
self.__symbol: str = kwargs.get('symbol', self.__name[randint(0, len(self.__name) - 1)])
# * Getters
@property
def name(self) -> str:
return self.__name
@property
def score(self) -> int:
return self.__score
@property
def symbol(self) -> str:
return self.__symbol
@property
def get_data(self) -> list:
return [self.__name, self.__symbol, str(self.__score)]
@property
def get_title(self) -> list[str]:
return ['name', 'symbol', 'score']
# * Method - Increase score
def increment_score(self) -> None:
self.__score += 1
# * Method - Reset score
def reset_score(self) -> None:
self.__score = 0
# * Method - Representation
def __repr__(self) -> str:
return f'Name:\t{self.name}\nScore:\t{self.score}\nSymbol:\t{self.symbol}'
#######################################
# Cell - Handle cell of game board
#######################################
class Cell:
def __init__(self, **kwargs):
self.__index: int = kwargs.get('index', 0)
self.symbol: str = kwargs.get('symbol', str(self.__index + 1))
# * Getter
@property
def index(self) -> int:
return self.__index
@property
def is_empty(self) -> bool:
return str(self.index + 1) == self.symbol
# * Method - Equal comparision operator
def __eq__(self, value: 'Cell') -> 'Cell':
return self.symbol == value.symbol
# * Method - Representation
def __repr__(self) -> str:
return self.symbol
################################
# Board - Handle game board
################################
class Board:
def __init__(self):
self.grid: list[list[Cell]] = []
self.__available: list[int] = [0, 1, 2, 3, 4, 5, 6, 7, 8]
# * Getters
@property
def available_cells(self) -> list[int]:
return self.__available
@property
def get_data(self) -> list:
data: list = []
for i in range(3):
for j in range(3):
data.append([i, j, self.grid[i][j].symbol])
return data
@property
def get_title(self) -> list[str]:
return ['row', 'column', 'symbol']
# * Method - Remove available
def remove_from_available(self, index: int) -> None:
self.__available.remove(index)
# * Method - Reset grid
def reset(self) -> None:
# ? Empty case
if len(self.grid) == 0:
count: int = 0
for i in range(3):
l: list[Cell] = []
for j in range(3):
l.append(Cell(index=count))
count += 1
self.grid.append(l)
# ? Filled case
else:
for row in self.grid:
for cell in row:
cell = Cell(index=cell.index)
# * Method - Fill grid
def fill(self, grid: list[list[Cell]]) -> None:
for i in range(3):
for j in range(3):
cell: Cell = grid[i][j]
# ? If the cell is not empty
if not cell.is_empty:
self.remove_from_available(cell.index)
self.grid[i][j] = cell
# * Method - Check if the board is filled
def is_filled(self) -> bool:
for row in self.grid:
for cell in row:
# ? If not empty
if not cell.is_empty:
return False
# ? If empty
return True
# * Method - Display grid
def display(self) -> None:
for i in range(3):
for j in range(3):
print('', self.grid[i][j], end=' |' if j < 2 else '')
print('\n---+---+---' if i < 2 else '\n')
########################################################################
# Data Handler - Handles game state data for save/load feature
########################################################################
class DataHandler:
def __init__(self, **kwargs):
self.__connection: sq.Connection = sq.connect(join('save', kwargs.get('file', time.strftime('%Y-%m-%d %H:%M:%S')) + '.db'))
self.__cursor: sq.Cursor = self.__connection.cursor()
# * Method - Execute query
def execute(self, query: str) -> None:
self.__cursor.execute(query)
self.__connection.commit()
# * Method - Create tables
def create_tables(self) -> None:
# ? Player table
self.execute("""
CREATE TABLE IF NOT EXISTS Player(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
symbol TEXT NOT NULL,
score INT NOT NULL
);
""")
# ? Board table
self.execute("""
CREATE TABLE IF NOT EXISTS Board(
id INTEGER PRIMARY KEY AUTOINCREMENT,
row INT NOT NULL,
column INT NOT NULL,
symbol TEXT NOT NULL
);
""")
# ? Game table
self.execute("""
CREATE TABLE IF NOT EXISTS Game(
id INTEGER PRIMARY KEY AUTOINCREMENT,
turn INT NOT NULL,
game_over INT NOT NULL,
matches INT NOT NULL
);
""")
# * Method - Insert data
def insert_into(self, table: str, titles: list[str], data: list) -> None:
self.execute(f"""
INSERT INTO {table}({','.join(titles)}) VALUES({','.join(data)});
""")
# * Method - Reterieve data
def reterieve_data(self, table: str) -> list:
self.execute(f"SELECT * FROM {table}")
return self.__cursor.fetchall()
# * Method - Close connection
def close(self) -> None:
self.__connection.close()
#########################
# Game - Handle game
#########################
class Game:
def __init__(self, **kwargs):
self.__board: Board = kwargs.get('board', Board())
self.__players: list[Player] = kwargs.get('players', [Player(), Player()])
self.__turn: int = kwargs.get('turn', 0)
self.__matches: int = kwargs.get('matches', 0)
self.__game_over: bool = kwargs.get('game_over', False)
# * Getters
@property
def get_data(self) -> list:
return [str(self.__turn), str(int(self.__game_over)), str(self.__matches)]
@property
def get_title(self) -> list[str]:
return ['turn', 'game_over', 'matches']
# * Method - Reset game
def reset(self) -> None:
self.__board.reset()
self.__matches = 0
self.__game_over = False
for player in self.__players:
player.reset_score()
# * Method - Check winner (0-none, 1-winner, 2-draw)
def __check_winner(self) -> int:
# ? Check row & column
for i in range(3):
if (self.__board.grid[i][0].symbol == self.__board.grid[i][1].symbol and
self.__board.grid[i][1].symbol == self.__board.grid[i][2].symbol and
not self.__board.grid[i][1].is_empty):
return 1
if (self.__board.grid[0][i].symbol == self.__board.grid[1][i].symbol and
self.__board.grid[1][i].symbol == self.__board.grid[2][i].symbol and
not self.__board.grid[1][i].is_empty):
return 1
# ? Check Diagonal
if (self.__board.grid[0][0].symbol == self.__board.grid[1][1].symbol and
self.__board.grid[1][1].symbol == self.__board.grid[2][2].symbol and
not self.__board.grid[1][1].is_empty):
return 1
elif (self.__board.grid[0][2].symbol == self.__board.grid[1][1].symbol and
self.__board.grid[1][1].symbol == self.__board.grid[2][0].symbol and
not self.__board.grid[1][1].is_empty):
return 1
# ? Check if draw
if self.__board.is_filled():
return 2
# ? No winner found
return 0
# * Method - Update game state
def __update_turn(self) -> None:
self.__turn ^= 1
# * Method - Start game
def start_game(self) -> None:
while not self.__game_over:
# ? Clear Screen
clrscr()
# ? Display board
self.__board.display()
# ? Display turn
print(f'Turn of {self.__players[self.__turn].name}!')
# ? Get input
while True:
# try:
index = input('Enter index: ')
if index == 'save':
handler: DataHandler = DataHandler(file='Save01')
handler.create_tables()
# ! Player data
for player in self.__players:
handler.insert_into('Player', player.get_title, player.get_data)
# ! Board data
for bdata in self.__board.get_data():
handler.insert_into('Board', self.__board.get_title, bdata)
# ! Game data
handler.insert_into('Game', self.get_title, self.get_data)
handler.close()
print("Saved Successfull!")
index = int(index)
if index - 1 in self.__board.available_cells:
break
else:
print('The selected cell is not empty')
# except Exception as e:
# print(f'Incorrect entry! {e}')
# ? Place symbol
index -= 1
i: int = int(index / 3)
j: int = index % 3
self.__board.grid[i][j].symbol = self.__players[self.__turn].symbol
self.__board.remove_from_available(index)
# ? Check winner
cw: int = self.__check_winner()
# ? If a winner found
if cw == 1:
print(f'Winner is {self.__players[self.__turn].name}!')
self.__players[self.__turn].increment_score()
self.__game_over = True
# ? If draw
elif cw == 2:
print('Game is Draw!')
self.__game_over = True
# ? Update game state
self.__update_turn()
# ? Increment the match variable
self.__matches += 1
if __name__ == "__main__":
board: Board = Board()
board.reset()
players: list[Player] = [
Player(name='Talha', symbol='T'),
Player(name='Rayan', symbol='R')
]
game: Game = Game(board=board, players=players)
game.start_game()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment