I'm reading "Code Complete" by McConnel and I'm practicing using object oriented programming to "reduce complexity". (By that McConnel means, among other things, that it should be very easy to read through any given tiny part of a project and know what is happening without thinking at all about the rest of the program). To practice, I have written a Python program that reproduces the known win-rate results for random-move tic tac toe games. The main file uses an instance of the below Game class to call the play_game method... the parameter to the method indicates how many games are to be played. I'm just curious if an experienced OOP/Python person could let me know if the classes below are cohesive and well built.
There are two other classes in the program, one is a Board class that knows everything about the state and rules of the board at each point of the game, and the other is a Statistics class that tracks and reports the results. I have also included the Board class. I suppose issues such as "do the rules on wins and ties belong in the Board class or the Game class" could go either way: Does the Board know when a win is on itself... or does the Game know when there is a win?
By the way, when I pass 1000000 to play_game to run one million games, the whole program does reproduce the exact results of a 58.5% win rate for X, a 28.8% win rate for O, and a 12.7% tie rate for random Tic Tac Toe. (These values are for X always moving first). I'd love some feedback on whether the below classes are cohesive and well built.
from board import Board
class Game:
# makes Game class a singleton
_singleton = None
def __new__(cls, *args, **kwargs):
if not cls._singleton:
cls._singleton = super(Game, cls).__new__(cls, *args, **kwargs)
return cls._singleton
def __init__(self):
self.b = None
# responsible for playing the number of games requested from main.py
def play_game(self, number_of_games):
i = 1
while i <= number_of_games:
# create a fresh board object for the next game
self.b = Board()
# play one game
game_in_progress = True
while game_in_progress:
game_in_progress = self.make_move()
i = i + 1
def make_move(self):
# randomly select an available square
position = self.b.select_random_square()
# make a move
self.b.board[position] = self.b.next_move
# call game_over to see if the game is over
if self.b.game_over():
return False
# update who moves next
temp = self.b.next_move
self.b.next_move = self.b.previous_move
self.b.previous_move = temp
return True
And here is the Board class...
import random
from statistics import Statistics
class Board(Statistics):
end_of_game_report = False
def __init__(self):
# the following line causes the __init__ method from the Statistics class to still run
Statistics.__init__(self)
# game board
self.board = [' '] * 9
self.next_move = 'X'
self.previous_move = 'O'
self.all_possible_wins = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]]
self.letter_dict = {'X': -1, 'O': 1}
self.x_won = False
self.o_won = False
self.tie = False
# Printing an instance of the class will display a standard
# tic tac toe game image.
def __str__(self):
return "\n" + self.board[0] + "|" + self.board[1] + "|" + self.board[2] + "\n"+ self.board[3] + "|" + self.board[4] + "|" + self.board[5] + "\n" + self.board[6] + "|" + self.board[7] + "|" + self.board[8]
@classmethod
def turn_on_end_of_game_reporting(cls):
cls.end_of_game_report = True
# call turn_on_end_of_game_reporting from main.py to run this report method
def end_of_game_reporter(self):
if self.x_won is True:
print(self)
print('X won\n')
elif self.o_won is True:
print(self)
print('O won\n')
else:
print(self)
print('Its a tie\n')
def get_available_squares(self):
# creates a list containing the position of all open squares
list_of_open_squares = []
i = 0
for j in self.board:
if j == ' ':
list_of_open_squares.append(i)
i = i + 1
return list_of_open_squares
def select_random_square(self):
list_of_open_squares = self.get_available_squares()
# return a randomly selected available square
return random.choice(list_of_open_squares)
def game_won(self):
for three_contiguous in self.all_possible_wins:
row = 0
for i in three_contiguous:
if self.board[i] == ' ':
break
row = row + self.letter_dict[self.board[i]]
if row == -3 or row == 3:
if row == -3:
self.x_won = True
self.track_game_outcomes('x_won')
if row == 3:
self.o_won = True
self.track_game_outcomes('o_won')
# self.brain.prep_dict_entry(three_contiguous, self.board, self.previous_move)
if Board.end_of_game_report is True:
self.end_of_game_reporter()
return True
return False
def game_tied(self):
if ' ' not in self.board[:9]:
self.tie = True
self.track_game_outcomes('tie')
if Board.end_of_game_report is True:
self.end_of_game_reporter()
return True
return False
def game_over(self):
if self.game_won():
return True
elif self.game_tied():
return True
else:
return False
Board
class as well. \$\endgroup\$