0

So i made a tic-tac-toe game in python and the main game board is basically a list of lists as follows:

board = [
        [" ", " ", " "],
        [" ", " ", " "],
        [" ", " ", " "],
    ]

Making a move in the game would modify this board variable. Now I have to check after each move whether a player has won or the game has tied. Basically have to check every configuration in board which would in an actual game be a win.

For which I have the following function, defining win_lines as a list which contains all the possible winning lines.

def check_has_won(board: list, player):
    win_lines = [
         board[0],      # top row
         board[1],      # mid row
         board[2],      # bottom row
        [row[0] for row in board],    # first col
        [row[1] for row in board],    # sec col
        [row[2] for row in board],    # third col
        [board[0][0], board[1][1], board[2][2]],  # diag 1
        [board[0][2], board[1][1], board[2][0]],  # diag 2
    ]


    for line in win_lines:
        if " " not in line:
            if foe[player] not in line:   #foe = {"X":"O", "O":"X"}
                return True # player has won
    else:
        for row in board:
            if " " in row:  # implies theres still empty positions to play
                return False
        else:           # Game has tied
            return None

The problem I have doing it this way is:

I find it inefficient to keep defining the win_lines variable after every move, it would be nice if it would modify itself whenever board is modified in a move. So in a way linking the correct elements of board with elements of win_lines. Is this possible? If so, how could I do it?

Possibly helpful code: (If there's more required code I'm missing, let me know.)

def make_move(board, player, position):
    """
    checks 'board' to see if 'position' is playable. if so plays there, else prompts again\n
    """

    board_indices = [
            None, # so that `position` matches the index of this list.
                  # make first element of `board_indices` (index 0) = None
            [0, 0], #e.g `position = 1` corresponds with index [0][0] in `board`
            [0, 1],
            [0, 2],
            [1, 0],
            [1, 1], # another e.g.  `position = 5` corresponds with index [1][1] in `board`
            [1, 2],
            [2, 0],
            [2, 1],
            [2, 2],
    ]
    f_index, s_index = board_indices[position]

    if board[f_index][s_index] == " ":
        board[f_index][s_index] = player

        return None

    else:
        position = input("Choose a new position: ")
        return make_move(board, player, position)

martineau
  • 119,623
  • 25
  • 170
  • 301
Muhd Mairaj
  • 557
  • 4
  • 13
  • Make your game more object-oriented by making your game board a `class` that has a `move()` method and also maintains a `win_lines` attribute — which the class's method(s) can keep updated as needed (and other parts of you game can use assuming it always up-to-date). – martineau May 31 '21 at 16:13
  • I tried making a class, and in the `__init__()` method i added `self.win_lines = [ self.board[0], self.board[1], ... ]` but it doesnt work. – Muhd Mairaj May 31 '21 at 17:38
  • "…but it doesn't work" is fairly meaningless at this point — there more work that needs to be done i.e. implementing the rest of the class and modifying the other parts of your code use it. – martineau May 31 '21 at 18:08
  • Well, I made the `class Board()` and also `make_move` and `check_has_won` methods to test out your suggestion of using a class. I dont see how sharing the whole code of that is needed. The problem is the same, i still need to redefine `win_lines` (or rather `self.win_lines`) everytime a move is made. It doesnt do the 'linking' that im keen on doing. – Muhd Mairaj May 31 '21 at 18:17
  • @martineau I would appreciate more information as to what youre trying to say, as far as I understood, I added the `win_lines` attribute so that it would keep updated with the `board`, but it still has the same problem that i asked this question for :( – Muhd Mairaj May 31 '21 at 18:25
  • Guess I didn't understand what you meant by "linking the correct elements of board with elements of win_lines". I was describing how encapsulate the updating of `win_lines` is a `Board` class so that it would be automatically updated whenever `make_move()` was called and `check_has_won()` could consult it. Depending on what you mean about "linking", perhaps `win_lines` should be a class (too). Another possibility to forget about having and maintining a `win_lines` at all and just check for a win by examining the board layout like a human would (check for three in a row). – martineau May 31 '21 at 19:03
  • The issue with checking the board layout for 3 in a row is that, in tic-tac-toe, there are 8 possible ways to form 3 in a row, not all of which can be done with a simple `for` loop. In fact i dont see it possible to check the board in such a way e.g. how would you check the diagonal of the boardin such a way. – Muhd Mairaj May 31 '21 at 21:04
  • Checking one of the corner-to-corner diagonal simply means checking `board[0][0]`, `board[1][1]`, and `board[2][2]` — notice the pattern? There are patterns for all winning configurations. – martineau May 31 '21 at 22:27
  • Oh so you mean to check each of these one by one. Yea i suppose that makes sense, but that is basically what im doing with my `win_lines`, Im just doing it all in one for loop, as opposed to seperately – Muhd Mairaj Jun 01 '21 at 20:22
  • I meant code that is like what's in some of the answers to [Algorithm for Determining Tic Tac Toe Game Over](https://stackoverflow.com/questions/1056316/algorithm-for-determining-tic-tac-toe-game-over). – martineau Jun 01 '21 at 21:18
  • That works i guess, but i didnt want extended `ifs` and `elifs` in my code. I felt it was easier to read if i prediefined all the winning configs in a list and check all of them in one `for` loop as opposed to the link where it does it seperately for different configuration. Anywho i have found a method that works (down below) and i also find that what i originally asked doesnt really work as i wished it would. Having said that, Thank you for your time in trying to help me – Muhd Mairaj Jun 01 '21 at 22:43

1 Answers1

0

So it looks I've answered my own question. Its not exactly what i was expecting but it does work. What I noticed was that modifying any of the lists board[0], board[1] and board[2] (which happen to be the rows) modified the elements in board and vice versa.

So I figured if I convert board to a numpy array and use its indexing to get the columns, it would allow me to do this with columns as well. So that would make 6 of the possible winning options. luckily, the array.diagonal() allowed me to achieve the same with the diagonal elements too.

import numpy as np
board = np.array(board)
win_lines = [
    board[0],  #top row
    board[1],  # mid row
    board[2],  #bottom row
    board[:,0],    # left col
    board[:,1],    # mid col
    board[:,2],    # right col
    board.diagonal(),             # diag 1
    np.fliplr(board).diagonal(),  # diag 2
]

and now... any modification in board is reflected in win_lines. It is probably important to note that win_lines must be a list of arrays, and making it a numpy array makes it not work anymore.

Working example:

make_move(board, "X", 2)
>>> print("board", board, sep="\n")
board
[[' ' 'X' ' ']
 [' ' ' ' ' ']
 [' ' ' ' ' ']]

>>> make_move(board, "O", 5)
>>> make_move(board, "X", 9)
>>> print("board", board, sep="\n")
board
[[' ' 'X' ' ']
 [' ' 'O' ' ']
 [' ' ' ' 'X']]

>>> for line in win_lines:
...     print(line)
...
[' ' 'X' ' ']    # this is the top row
[' ' 'O' ' ']    # this is the mid row
[' ' ' ' 'X']    # this is the bottom row
[' ' ' ' ' ']    # this is the left col
['X' 'O' ' ']    # this is the mid col
[' ' ' ' 'X']    # this is the right col
[' ' 'O' 'X']    # diag 1
[' ' 'O' ' ']    # diag 2

Nicely, win_lines modified itself along with board, without having to redefine win_lines every time the move was made

Muhd Mairaj
  • 557
  • 4
  • 13