Search code examples
arrayspython-3.xindexingtic-tac-toe

Compact way to index from x to y in 2D list


I am doing the tictactoe project from the Cisco NetAkad course : https://www.youtube.com/watch?v=7GDtI9SpGgU I did the game disregarding the stated project requirements. I used for the board a normal list: board = [1, 2, 3, 4, 5, 6, 7, 8, 9] The programm works and and the output is the same as the project asks for.

The project requirements state however specifically to use a 2D list: board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]. As I wanted to practise using 2D lists I rewrote the programm and found it requires more lines and more complex indexing.

My 1st question is: in the function, which checks whether there is a winner, is it possible with a 2D list to just use indexing to check whether there are 3 consecutive "X" or "O" for all directions? If yes, how? (I don't think writing the massive amount of 'if' combined with many 'and', like in the video, is efficient and using indexing works well with a normal list)

As a beginner my wording might be unclear, so here is the section checking whether "X" has won and below what I expect it to do:

if board[0][0:3].count("X") == 3 or board[1][0:3].count("X") == 3 \
or board[2][0:3].count("X") == 3 or board[0:3][0].count("X") == 3 \
or board[0:3][1].count("X") == 3 or board[0:3][2].count("X") == 3 \
or board[0:3][0:3].count("X") == 3 or board[0:3][3::-1].count("X") == 3:
    print("I won: you lost.")

The index [0][0:3], [1][0:3] and [2][0:3] work as expected (rows), it recognises the win. However [0:3][0] is being read as [0][0:3] when the programm is run. (checking colums doesn't work) [0:3][0:3] and [0:3][3::-1] obviously doesn't work (diagonal).

2nd question: What better way is there to check for a winner?

Last question: Is there an advantage of using a 2D list over a normal list in this case?

I am thankful in advance for any feedback.


Solution

  • You can do something like below:

    for key in ['X', 'O']:
        temp = []
        count = 0
        for row in board:
            if row.count(key) == 3:
                count = 3
                break
    
            if key in row:
                temp.append(row.index(key))
    
        if count == 3 or (len(list(set(temp))) == 1 and len(temp) == 3) or temp in ([0,1,2], [2,1,0]):
            print("I won: you lost. " + key + " wins")
    

    The idea of the solution is to get the indexes of the positions of 'X' in the inner lists (the usage of indexes justifies the reason to go for a '2D list' [list of lists] per your last question). This is done by looping over the list per 'row' of the tic-tac-toe. We store the indexes in the variable 'temp'.

    The first condition checks whether a row has 3 'X's and if yes, it breaks the execution of the for loop saving a count=3. The second condition is the most interesting because it stores the index of 'X' in the row. For example, when you have X in the middle column, at the end of the loop, temp will be equal to:

    [2,2,2] if the tic-tac-toe was 
    O-X-
    O-X-O
     -X-O
    

    Consequently, if there is a single unique number in the 'temp' list ([1,1,1] unique is 1, [2,2,2] unique is 2 etc), then there is a winner. This is done by:

    len(list(set(temp))) # (inside) Get unique values / Make it a list / Measure its length
    

    If the length is 1, then there is a unique position. In addition to that, and to account for the code running while the tic-tac-toe is not entirely filled, we check for len(temp) == 3. Finally, we check for a diagonal match using the 'temp in ([0,1,2], [2,1,0])' part of the condition

    The reason why the 'index [0:3][0]' does not work as you expect it to, is because the first slice points to the entire list for which you are taking the value at position 0 which is [1,2,3]. In other words board[0:3] is equal to board