Code for How to Build a Tic Tac Toe Game in Python Tutorial


View on Github

tictactoe.py

import pygame
from pygame.locals import *

pygame.init()
pygame.font.init()

window_size = (450, 500)

screen = pygame.display.set_mode(window_size)
pygame.display.set_caption("Tic Tac Toe")


class TicTacToe():

	def __init__(self, table_size):
		self.table_size = table_size
		self.cell_size = table_size // 3
		self.table_space = 20

		self.player = "X"
		self.winner = None
		self.taking_move = True
		self.running = True
		self.table = []
		for col in range(3):
			self.table.append([])
			for row in range(3):
				self.table[col].append("-")

		self.background_color = (255, 174, 66)
		self.table_color = (50, 50, 50)
		self.line_color = (190, 0, 10)
		self.instructions_color = (17, 53, 165)
		self.game_over_bg_color = (47, 98, 162)
		self.game_over_color = (255, 179, 1)
		self.font = pygame.font.SysFont("Courier New", 35)
		self.FPS = pygame.time.Clock()


	# draws table representation
	def _draw_table(self):
		tb_space_point = (self.table_space, self.table_size - self.table_space)
		cell_space_point = (self.cell_size, self.cell_size * 2)
		r1 = pygame.draw.line(screen, self.table_color, [tb_space_point[0], cell_space_point[0]], [tb_space_point[1], cell_space_point[0]], 8)
		c1 = pygame.draw.line(screen, self.table_color, [cell_space_point[0], tb_space_point[0]], [cell_space_point[0], tb_space_point[1]], 8)
		r2 = pygame.draw.line(screen, self.table_color, [tb_space_point[0], cell_space_point[1]], [tb_space_point[1], cell_space_point[1]], 8)
		c2 = pygame.draw.line(screen, self.table_color, [cell_space_point[1], tb_space_point[0]], [cell_space_point[1], tb_space_point[1]], 8)


	def _change_player(self):
		self.player = "O" if self.player == "X" else "X"


	# processing clicks to move
	def _move(self, pos):
		try:
			x, y = pos[0] // self.cell_size, pos[1] // self.cell_size
			if self.table[x][y] == "-":
				self.table[x][y] = self.player
				self._draw_char(x,y,self.player)
				self._game_check()
				self._change_player()
		except:
			print("Click inside the table only")


	# draws character of the recent player to the selected table cell
	def _draw_char(self, x, y, player):
		if self.player == "O":
			img = pygame.image.load("images/Tc-O.png")
		elif self.player == "X":
			img = pygame.image.load("images/Tc-X.png")
		img = pygame.transform.scale(img, (self.cell_size, self.cell_size))
		screen.blit(img, (x * self.cell_size, y * self.cell_size, self.cell_size, self.cell_size))


	# instructions and game-state messages
	def _message(self):
		if self.winner is not None:
			screen.fill(self.game_over_bg_color, (130, 445, 193, 35))
			msg = self.font.render(f'{self.winner} WINS!!', True, self.game_over_color)
			screen.blit(msg,(144,445))
		elif not self.taking_move:
			screen.fill(self.game_over_bg_color, (130, 445, 193, 35))
			instructions = self.font.render('DRAW!!', True, self.game_over_color)
			screen.blit(instructions,(165,445))
		else:
			screen.fill(self.background_color, (135, 445, 188, 35))
			instructions = self.font.render(f'{self.player} to move', True, self.instructions_color)
			screen.blit(instructions,(135,445))


	def _game_check(self):
		# vertical check
		for x_index, col in enumerate(self.table):
			win = True
			pattern_list = []
			for y_index, content in enumerate(col):
				if content != self.player:
					win = False
					break
				else:
					pattern_list.append((x_index, y_index))
			if win == True:
				self._pattern_strike(pattern_list[0],pattern_list[-1],"ver")
				self.winner = self.player
				self.taking_move = False
				break

		# horizontal check
		for row in range(len(self.table)):
			win = True
			pattern_list = []
			for col in range(len(self.table)):
				if self.table[col][row] != self.player:
					win = False
					break
				else:
					pattern_list.append((col, row))
			if win == True:
				self._pattern_strike(pattern_list[0],pattern_list[-1],"hor")
				self.winner = self.player
				self.taking_move = False
				break

		# left diagonal check
		for index, row in enumerate(self.table):
			win = True
			if row[index] != self.player:
				win = False
				break
		if win == True:
			self._pattern_strike((0,0),(2,2),"left-diag")
			self.winner = self.player
			self.taking_move = False

		# right diagonal check
		for index, row in enumerate(self.table[::-1]):
			win = True
			if row[index] != self.player:
				win = False
				break
		if win == True:
			self._pattern_strike((2,0),(0,2),"right-diag")
			self.winner = self.player
			self.taking_move = False

		# blank table cells check
		blank_cells = 0
		for row in self.table:
			for cell in row:
				if cell == "-":
					blank_cells += 1
		if blank_cells == 0:
			self.taking_move = False


	# strikes a line to winning patterns if already has
	def _pattern_strike(self, start_point, end_point, line_type):
		# gets the middle value of the cell
		mid_val = self.cell_size // 2

		# for the vertical winning pattern
		if line_type == "ver":
			start_x, start_y = start_point[0] * self.cell_size + mid_val, self.table_space
			end_x, end_y = end_point[0] * self.cell_size + mid_val, self.table_size - self.table_space

		# for the horizontal winning pattern
		elif line_type == "hor":
			start_x, start_y = self.table_space, start_point[-1] * self.cell_size + mid_val
			end_x, end_y = self.table_size - self.table_space, end_point[-1] * self.cell_size + mid_val

		# for the diagonal winning pattern from top-left to bottom right
		elif line_type == "left-diag":
			start_x, start_y = self.table_space, self.table_space
			end_x, end_y = self.table_size - self.table_space, self.table_size - self.table_space

		# for the diagonal winning pattern from top-right to bottom-left
		elif line_type == "right-diag":
			start_x, start_y = self.table_size - self.table_space, self.table_space
			end_x, end_y = self.table_space, self.table_size - self.table_space

		# draws the line strike
		line_strike = pygame.draw.line(screen, self.line_color, [start_x, start_y], [end_x, end_y], 8)


	def main(self):
		screen.fill(self.background_color)
		self._draw_table()
		while self.running:
			self._message()
			for self.event in pygame.event.get():
				if self.event.type == pygame.QUIT:
					self.running = False

				if self.event.type == pygame.MOUSEBUTTONDOWN:
					if self.taking_move:
						self._move(self.event.pos)

			pygame.display.flip()
			self.FPS.tick(60)


if __name__ == "__main__":
	g = TicTacToe(window_size[0])
	g.main()