So I started Python this week, and I never programmed a game before. Even though Tic Tac Toe is probably the simplest game ever, I had some challenges while coding the game rules. I split my project in 4 files : board.py
, game.py
, main.py
, ui.py
. I think my file content is self-explanatory enough that I don't need to explain the purpose of each of them.
board.py
import numpy as np
# The board is a zero-based index 2D array
# The X player is represented using 1
# The O player is represented using -1
# Empty cells are represented using 0
class Board:
def __init__(self):
self.__board = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
def isFull(self):
boardShape = np.shape(self.__board)
for rowIndex in xrange(boardShape[0]):
for colIndex in xrange(boardShape[1]):
if self.__board[rowIndex, colIndex] == 0:
return False
return True
def setCell(self, cell, player):
#Will throw an exception if the cell is invalid
self.__validateSetCell(cell)
self.__board[cell] = player
def isCellEmpty(self, cell):
return self.__board[cell] == 0
def toArray(self):
return np.copy(self.__board)
def __validateSetCell(self, cell):
if not self.isCellEmpty(cell):
raise ValueError("The field already has a value. Cell : " + str(cell))
boardShape = np.shape(self.__board)
if cell[0] >= boardShape[0] or cell[1] >= boardShape[1]:
raise IndexError("The cell is out of bound. Cell : " + str(cell))
def __len__(self):
return len(self.__board)
def __getitem__(self, item):
return self.__board[item]
def __iter__(self):
return iter(self.__board)
game.py
from board import Board
class Game:
# If the abs sum of a row (for example) equals 3, it means the row has 3 times the same token, so it's a win.
SumToWin = 3
winOnTopRow = staticmethod(lambda b: abs(b[0, 0] + b[0, 1] + b[0, 2]) == Game.SumToWin)
winOnMiddleRow = staticmethod(lambda b: abs(b[1, 0] + b[1, 1] + b[1, 2]) == Game.SumToWin)
winOnBottomRow = staticmethod(lambda b: abs(b[2, 0] + b[2, 1] + b[2, 2]) == Game.SumToWin)
winOnLeftCol = staticmethod(lambda b: abs(b[0, 0] + b[1, 0] + b[2, 0]) == Game.SumToWin)
winOnMiddleCol = staticmethod(lambda b: abs(b[0, 1] + b[1, 1] + b[2, 1]) == Game.SumToWin)
winOnRightCol = staticmethod(lambda b: abs(b[0, 2] + b[1, 2] + b[2, 2]) == Game.SumToWin)
winOnLeftToRightDiag = staticmethod(lambda b: abs(b[0, 0] + b[1, 1] + b[2, 2]) == Game.SumToWin)
winOnRightToLeftDiag = staticmethod(lambda b: abs(b[0, 2] + b[1, 1] + b[2, 0]) == Game.SumToWin)
__winningMoveValidator = {}
__winningMoveValidator[(0, 0)] = lambda b: Game.winOnLeftCol(b) or Game.winOnTopRow(b) or Game.winOnLeftToRightDiag(b)
__winningMoveValidator[(0, 1)] = lambda b: Game.winOnTopRow(b) or Game.winOnMiddleCol(b)
__winningMoveValidator[(0, 2)] = lambda b: Game.winOnTopRow(b) or Game.winOnRightCol(b) or Game.winOnRightToLeftDiag(b)
__winningMoveValidator[(1, 0)] = lambda b: Game.winOnMiddleRow(b) or Game.winOnLeftCol(b)
__winningMoveValidator[(1, 1)] = lambda b: Game.winOnMiddleRow(b) or Game.winOnMiddleCol(b) or Game.winOnLeftToRightDiag(b) or Game.winOnRightToLeftDiag(b)
__winningMoveValidator[(1, 2)] = lambda b: Game.winOnMiddleRow(b) or Game.winOnRightCol(b)
__winningMoveValidator[(2, 0)] = lambda b: Game.winOnLeftCol(b) or Game.winOnBottomRow(b) or Game.winOnRightToLeftDiag(b)
__winningMoveValidator[(2, 1)] = lambda b: Game.winOnMiddleCol(b) or Game.winOnBottomRow(b)
__winningMoveValidator[(2, 2)] = lambda b: Game.winOnRightCol(b) or Game.winOnBottomRow(b) or Game.winOnLeftToRightDiag(b)
XValue = 1
OValue = -1
def __init__(self):
self.__currentPlayer = Game.XValue
self.__board = Board()
self.__winner = None
def getBoardAsArray(self):
return self.__board.toArray()
def getCurrentPlayer(self):
return self.__currentPlayer
def playForCurrentPlayer(self, cell):
if self.isOver():
raise StandardError("The Game is over, you can't play anymore.")
self.__board.setCell(cell, self.__currentPlayer)
if self.__isWinningMove(cell):
self.__winner = self.__currentPlayer
return
# X = 1, O = -1
self.__currentPlayer = -self.__currentPlayer
def isValidMove(self, cell):
return self.__board.isCellEmpty(cell)
def isOver(self):
return self.__board.isFull() or self.__winner is not None
def getWinner(self):
return self.__winner
def __isWinningMove(self, cell):
return Game.__winningMoveValidator[cell](self.getBoardAsArray())
ui.py
import numpy as np
import re
userInputValidator = re.compile("[0-2],[0-2]")
boardIntToString = {0: ' ', 1: 'X', -1: 'O'}
def printInstructions():
print("Welcome to Tic Tac Toe.")
print("When the game asks for your input, refer to this table for help :")
helpTable = "0,0 | 0,1 | 0,2\n"\
"---------------\n"\
"1,0 | 1,1 | 1,2\n" \
"---------------\n" \
"2,0 | 2,1 | 2,2\n"
print(helpTable)
print("Good luck.")
print("-------------------------")
def requestValidPlayerMove(validatiionFunction):
isValid = False
move = None
while not isValid:
wantedMove = raw_input("Where do you want to play? ")
isValid = userInputValidator.match(wantedMove)
if isValid:
move = tuple(map(lambda i: int(i), wantedMove.split(',')))
isValid = validatiionFunction(move)
if not isValid:
print("Invalid move, choose again.")
return move
def printCurrentPlayer(player):
print("It is " + boardIntToString[player] + "'s turn.")
def printGameOver(winner=None):
if winner is None:
print("Game over, no one won.")
else:
print("Player " + boardIntToString[winner] + " has won the game.")
def printBoard(board):
string = "\n"
string += boardIntToString[board[0, 0]] + " | " + boardIntToString[board[0,1]] + " | " + boardIntToString[board[0,2]] + "\n"
string += "----------\n"
string += boardIntToString[board[1,0]] + " | " + boardIntToString[board[1,1]] + " | " + boardIntToString[board[1,2]] + "\n"
string += "----------\n"
string += boardIntToString[board[2,0]] + " | " + boardIntToString[board[2,1]] + " | " + boardIntToString[board[2,2]] + "\n"
print string
main.py
import ui
from game import Game
game = Game()
ui.printInstructions()
while not game.isOver():
ui.printCurrentPlayer(game.getCurrentPlayer())
ui.printBoard(game.getBoardAsArray())
# The UI validates the input format, but we need to check the game state to see if move is valid, hence the lambda
cellToPlay = ui.requestValidPlayerMove(lambda (row, col): game.isValidMove((row, col)))
game.playForCurrentPlayer(cellToPlay)
ui.printBoard(game.getBoardAsArray())
# If there is no winner, ui expects None
ui.printGameOver(game.getWinner())
The main thing I dislike is how I validate if a move is a "winning" move, meaning if a move causes the game to end with a winner. The dictionary is pretty ugly and there is some code repetition. I feel like there is not pretty way to do this though. Obviously, I'd also like to know if I made any "noob" mistakes regarding Python standards.