Search code examples
pythonnested-loopsnested-lists

Nested loops in nested lists


I am trying to build a simple Tic-Tac-Toe game in python, to check for a win I am using a nested loop to search for a match in a nested lists. For some reason my code will only search the first nested list and not the rest of them as I expected it to.

board = [ 'O', 'X', ' ', 'O', ' ', 'X', 'O', 'X', 'X' ]
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]]

def checkWin(player):
    win = False
    for test in wins:
        print (test)
        count = 0
        for squares in test:
            if board[squares] == player:
                count = count + 1
            if count == 3:
                win = True
        return win

if __name__ == '__main__':
    print ("\nChecking board for X win ...\n")
    if checkWin("X"): 
        print ("Game over, X wins!")

    print ("\nChecking board for O win ...\n")
    if checkWin("O"):
        print ("Game over, O wins")

Based on the board provided a win for O is expected instead, this is the output I get:

Checking board for X win ...

[0, 1, 2]

Checking board for O win ...

[0, 1, 2]

Does anyone know why this happens?


Solution

  • You return from the first nested list test, regardless of wether those three squares matched. Instead, only return if win is true:

    def checkWin(player):
        win = False
        for test in wins:
            count = 0
            for squares in test:
                if board[squares] == player:
                    count = count + 1
                if count == 3:
                    win = True
            if win:
                return True
        return False
    

    If win is false, the above continues to the next nested list for the next test.

    Better still, just return when count is set to 3, since you know you have found a match at that stage:

    def checkWin(player):
        for test in wins:
            count = 0
            for squares in test:
                if board[squares] == player:
                    count = count + 1
                if count == 3:
                    return True
        return False
    

    Instead of counting, you could use the all() function:

    def checkWin(player):
        for test in wins:
            if all(board[square] == player for square in test):
                return True
        return False
    

    all() returns False early, as soon as one of the tests in the generator expression fails.

    And the ultimate version adds any() to complete the test in one line:

    def checkWin(player):
        return any(all(board[square] == player for square in test)
                   for test in wins)
    

    Demo:

    >>> board = [ 'O', 'X', ' ', 'O', ' ', 'X', 'O', 'X', 'X' ]
    >>> 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]]
    >>> def checkWin(player):
    ...     return any(all(board[square] == player for square in test)
    ...                for test in wins)
    ... 
    >>> checkWin('X')
    False
    >>> checkWin('O')
    True