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.pyimport 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 0class 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.pyfrom board import Boardclass 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.pyimport numpy as npimport reuserInputValidator = 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 movedef 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 stringmain.pyimport uifrom game import Gamegame = 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 Noneui.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.
Console Tic Tac Toe
python;beginner;python 2.7;tic tac toe
Firstly, Python has a very well defined official style guide called PEP8, which you should follow. There are tools that are checking whether your code fits its requirements. For example, after I run flake8 tool on your code I've got:$ python2.7 -m flake8 board.py game.py main.py ui.pyboard.py:37:80: E501 line too long (82 > 79 characters)game.py:3:1: E302 expected 2 blank lines, found 1game.py:4:80: E501 line too long (113 > 79 characters)game.py:7:80: E501 line too long (91 > 79 characters)game.py:8:80: E501 line too long (94 > 79 characters)game.py:9:80: E501 line too long (94 > 79 characters)game.py:10:80: E501 line too long (92 > 79 characters)game.py:11:80: E501 line too long (94 > 79 characters)game.py:12:80: E501 line too long (93 > 79 characters)game.py:13:80: E501 line too long (100 > 79 characters)game.py:14:80: E501 line too long (100 > 79 characters)game.py:17:80: E501 line too long (122 > 79 characters)game.py:18:80: E501 line too long (92 > 79 characters)game.py:19:80: E501 line too long (123 > 79 characters)game.py:20:80: E501 line too long (93 > 79 characters)game.py:21:80: E501 line too long (159 > 79 characters)game.py:22:80: E501 line too long (94 > 79 characters)game.py:23:80: E501 line too long (125 > 79 characters)game.py:24:80: E501 line too long (95 > 79 characters)game.py:25:80: E501 line too long (126 > 79 characters)main.py:11:80: E501 line too long (118 > 79 characters)main.py:12:80: E501 line too long (91 > 79 characters)ui.py:1:1: F401 'np' imported but unusedui.py:7:1: E302 expected 2 blank lines, found 1ui.py:19:1: E302 expected 2 blank lines, found 1ui.py:36:1: E302 expected 2 blank lines, found 1ui.py:39:1: E302 expected 2 blank lines, found 1ui.py:45:1: E302 expected 2 blank lines, found 1ui.py:47:79: E231 missing whitespace after ','ui.py:47:80: E501 line too long (128 > 79 characters)ui.py:47:118: E231 missing whitespace after ','ui.py:49:39: E231 missing whitespace after ','ui.py:49:78: E231 missing whitespace after ','ui.py:49:80: E501 line too long (127 > 79 characters)ui.py:49:117: E231 missing whitespace after ','ui.py:51:39: E231 missing whitespace after ','ui.py:51:78: E231 missing whitespace after ','ui.py:51:80: E501 line too long (127 > 79 characters)ui.py:51:117: E231 missing whitespace after ','You should keep your lines shorter, watch the whitespaces around operators and commas and so on.board.py# 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 0class Board:    # ...We don't document classes in Python like that. Instead do:class Board:        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        # ...So you can display your help using interactive console, for example:$ pythonPython 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609] on linux2Type help, copyright, credits or license for more information.>>> import board>>> help(board.Board)Similarly, you document methods like this:def setCell(self, cell, player):    Will throw an exception if the cell is invalid    # ...It's called docstring and is defined in PEP257.I see that you try to use a lot of private variables, by enforcing a double underline before your variables. I'd suggest to drop that habit. This isn't Java, this isn't C++, in Python you usually don't try to hide insides of your class. There's nothing preventing user for accessing any part of the class so there's little point in trying to avoid it. You usually use a single underscore if you want to stress that a particular method or variable isn't really meant for an external use but is a part of the internal implementation that might change in future releases. Double underscores are used even less often. You can read about differences between these in here or by googling single vs double underscore in Python.I don't really see a point of using a np.array in a simple tic-tac-toe game. That sounds like an overkill. np library is useful mostly when you want to do some heavy computations on big amount of numbers. For a simple 9x9 grid using a regular list is more that enough. >>> l = [['_'] * 3 for l in range(3)] >>> l [['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] >>> l[0][1] = 'X' >>> l [['_', X, '_'], ['_', '_', '_'], ['_', '_', '_']]game.pyOh my, that looks scary. I think it isn't really the best idea to keep a dictionary of all posible positions in order to check whether the next move is a winning one. You should rather check whether adding a new value caused to a line (row, column o diagonal) to consist of same character (or sum to -3 or 3 if you decide to represent -1 as X and 1 as Y). Try to adapt this algorithm to Python.For inspiration, here's one way to do it. Assuming you use Python list of lists instead of np.array, for row and column you don't need a loop, list comprehension is enough:>>> board = [...     ['X', '_', 'O'],...     ['X', 'X', 'O'],...     ['_', 'O', '_']... ]>>> >>> >>> x, y = 1, 1>>> column = [board[i][x] for i in range(3)]>>> row = [board[y][i] for i in range(3)]>>> >>> print(column, row)(['_', 'X', 'O'], ['X', 'X', 'O'])Anti-diagonal and anti-diagonal consist both of three positions, so you can actually list these to check whether you're on one of them. If you are, you need to check it as well:>>> board = [...     ['X', '_', 'O'],...     ['X', 'X', 'O'],...     ['_', 'O', '_']... ]>>> >>> x, y = 1, 1>>> >>> diagonal = []>>> antidiagonal = []>>> >>> on_diagonal = [(0, 0), (1, 1), (2, 2)]>>> on_antidiagonal = [(0, 2), (1, 1), (2, 0)]>>> >>> if (x, y) in on_diagonal:...     diagonal = [board[i][j] for (i, j) in on_diagonal]... >>> if (x, y) in on_antidiagonal:...     antidiagonal = [board[i][j] for (i, j) in on_antidiagonal]... >>> print(diagonal, antidiagonal)(['X', 'X', '_'], ['O', 'X', '_'])After you have these four list of three positions, you can translate '0' and 'X' to numbers and sum them:>>> translate_table = {...     'X': -1,...     '_': 0,...     'O': -1... }>>> l = ['X', '_', 'O']>>> l = [translate_table[i] for i in l]>>> l[-1, 0, -1]main.pyYou often see in Python script a following pattern:def main():    # ...if __name__ == '__main__':    main()You can move your code inside this main() function. It's not necessary but usually you'll have a much better performance, since Python handles better code placed inside functions that inside global space. ui.pyPython handles multi line string literals so there's no need for that:helpTable = 0,0 | 0,1 | 0,2\n\            ---------------\n\            1,0 | 1,1 | 1,2\n \            ---------------\n \            2,0 | 2,1 | 2,2\nYou can just usedef printHelpTable():    helpTable = 0,0 | 0,1 | 0,2---------------1,0 | 1,1 | 1,2---------------2,0 | 2,1 | 2,2    print(helpTable)If you don't like lack of indentation, then there are two approaches you can take. One style is to group strings inside single parenthesis, so you don't have to use \ sign to mark breaking a line:def printHelpTable():    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)Alternatively you can use textwrap.dedent function from the standard libraryimport textwrapdef printHelpTable():    helpTable = textwrap.dedent(        0,0 | 0,1 | 0,2        ---------------        1,0 | 1,1 | 1,2        ---------------        2,0 | 2,1 | 2,2    )    print(helpTable)This is an overkill:tuple(map(lambda i: int(i), wantedMove.split(',')))Avoid unnecessary uses of map and lambdas. You can convert your move like this:>>> wantedMove = '2,1'>>> tuple(int(x) for x in wantedMove.split(','))(2, 1)Even better, you don't even need to convert anything. You use raw_input(), which treats input as string. You can simply use input() instead to simply get a pair of numbers:>>> a, b = input(Type your numbers: )Type your numbers: 2, 1>>> a, b(2, 1)>>> type(a), type(b)(<type 'int'>, <type 'int'>)Read here.Finally, printing a board written a bit differently:board = [    ['X', '_', 'O'],    ['X', 'X', 'O'],    ['_', 'O', '_']]for row_num, row in enumerate(board):    print('|'.join(row))    if row_num != 2:        print('-----')