6

I am trying to write a code that determines the winner of a tic-tac-toe game. (This is for a college assignment)

I have written the following function to do so:

This code only checks for horizontal lines, I haven't added the rest. I feel that this is something that needs a bit of hardcoding.

def iswinner(board, decorator):
    win = True
    for row in range(len(board)):
        for col in range(len(board)):
            if board[row][col] == decorator:
                win = True
            else:
                win = False
                break

Where "board" is a 2D array of size n^2 and "decorator" is the "X" or "O" value

What I hope to accomplish is that the function loops through the 2D array's rows. Then loops through the values in each row. If that element matches the "decorator" then it continues and checks the next but if it doesn't, then it breaks from the first loop and goes to the next row. It does this until it finds n elements in the same row. Then it would give a bool value of True otherwise False.

The code doesn't seem to do that and even when I checked with the following "board" it gave me an output of "True"

check_list = [['O', 'X', 'X'],
              ['O', 'X', 'O'],
              ['O', 'X', 'X']]

Thank you so much!

Best, Seyed

Efferalgan
  • 1,681
  • 1
  • 14
  • 24
Saadat
  • 163
  • 1
  • 2
  • 12

10 Answers10

11

You can just make a set of each row, and check its length. If it contains only one element, then the game has been won.

def returnWinner(board):
    for row in board:
        if len(set(row)) == 1:
            return row[0]
    return -1

This will return "O" if there is a full line of "O", "X" if there is a line of "X", and -1 otherwise.

Below is the code of a full Tic-Tac-Toe checker, it should not be hard to understand, but do not hesitate to ask:

import numpy as np

def checkRows(board):
    for row in board:
        if len(set(row)) == 1:
            return row[0]
    return 0

def checkDiagonals(board):
    if len(set([board[i][i] for i in range(len(board))])) == 1:
        return board[0][0]
    if len(set([board[i][len(board)-i-1] for i in range(len(board))])) == 1:
        return board[0][len(board)-1]
    return 0

def checkWin(board):
    #transposition to check rows, then columns
    for newBoard in [board, np.transpose(board)]:
        result = checkRows(newBoard)
        if result:
            return result
    return checkDiagonals(board)


a = [['X', 'A', 'X'],
     ['A', 'X', 'A'],
     ['A', 'X', 'A']]

print(checkWin(a))

Note that this works regardless of the symbols you choose to put in your tic-tac-toe ("O" & "X" is as fine as "bloop" & "!"), and for any size of grid, as long as it is a square.

Efferalgan
  • 1,681
  • 1
  • 14
  • 24
  • can you explain what the "set" part is doing? – Saadat Oct 07 '16 at 17:56
  • It is converting the array to a set. A set is an [unordered collection of unique elements](https://docs.python.org/3/tutorial/datastructures.html#sets). Thus `set(['X', 'O', 'X'])` only contains `'X', 'O'`, and `set(['X', 'X', 'X'])` only contains `'X'`. If the set only contains one element, then there is only one type of decorator on the line. – Efferalgan Oct 07 '16 at 18:04
5

One way to do this would be to create a set (a generator function would be even better) of all the possible index combinations to check for the win. Then loop through those index combinations and check if they all contain the same value, if so, then it's a win.

def win_indexes(n):
    # Rows
    for r in range(n):
        yield [(r, c) for c in range(n)]
    # Columns
    for c in range(n):
        yield [(r, c) for r in range(n)]
    # Diagonal top left to bottom right
    yield [(i, i) for i in range(n)]
    # Diagonal top right to bottom left
    yield [(i, n - 1 - i) for i in range(n)


def is_winner(board, decorator):
    n = len(board)
    for indexes in win_indexes(n):
        if all(board[r][c] == decorator for r, c in indexes):
            return True
    return False
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • Is there a way to generalise this so that it checks horizontally, vertically, and diagonally or is that impossible? – Saadat Oct 07 '16 at 17:55
  • Yes, this is generallized. It currently checks horizontally, vertically, and diagonally. If you wanted to add more win conditions (like 4 corners) you would just `yield` additional indexes from the `win_indexes` generator. – Brendan Abel Oct 07 '16 at 17:57
  • Can you please explain what the yield does in your code? I am also reading up on it myself. – Saadat Oct 07 '16 at 17:59
  • It's a generator. If you didn't want to use a generator, instead of `yield`ing each of those index lists, you could just `append` them to a list and return the entire list. – Brendan Abel Oct 07 '16 at 18:01
  • when I try to run the code, it gives me "NameError: name 'n' is not defined" – Saadat Oct 07 '16 at 18:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/125198/discussion-between-saadat-and-brendan-abel). – Saadat Oct 07 '16 at 18:13
  • Hi @BrendanAbel, I've tried to run your code -debugging it- to understand it but I don't now what decorator is. Could you give me a clue? – belfastcowboy24 Jul 27 '20 at 20:16
  • `decorator` is probably a bad parameter name, I just used the same name as the example in the question. `decorator` will be the character to check for (`"X"` or `"O"`) – Brendan Abel Jul 27 '20 at 22:52
  • Thanks @brendanabel, that was my initial thought, but the word decorator confused me. – belfastcowboy24 Jul 28 '20 at 17:25
  • 1
    The best answer, thank you. It works as expected. – Kostanos Apr 08 '23 at 21:08
2
def check_winner(board,mark):
    return((board[1]==mark and board[2]== mark and board[3]==mark )or #for row1 

            (board[4]==mark and board[5]==mark and board[6]==mark )or #for row2

            (board[7]==mark and board[8]==mark and board[9]==mark )or #for row3

            (board[1]==mark and board[4]==mark and board[7]== mark )or#for Colm1 

            (board[2]==mark and board[5]==mark and board[8]==mark )or #for Colm 2

            (board[3]==mark and board[6]==mark and board[9]==mark )or #for colm 3

            (board[1]==mark and board[5]==mark and board[9]==mark )or #daignole 1

            (board[3]==mark and board[5]==mark and board[7]==mark )) #daignole 2
StupidWolf
  • 45,075
  • 17
  • 40
  • 72
1

I just came up with this little piece of code:

def eval_w(board):
possible = []

#rows
for i in range(3):
    possible.append( board[i][0] + board[i][1] + board[i][2] )

#columns
for i in range(3):
    possible.append( board[0][i] + board[1][i] + board[2][i] )

#diagonals
possible.append( board[0][0] + board[1][1] + board[2][2] )
possible.append( board[0][2] + board[1][1] + board[2][0] )

if 3 in possible:
    ended = True
    return 1
elif -3 in possible:
    ended = True
    return -1
else:
    return 0

I haven't seen anything similar here, so I might as well post it.

Hawk_x6x
  • 11
  • 3
1

This code will work for boards of any size, any length required for a win and any amount of players. It requires the board to be a 2d array. settings["len"] is the length (or width) of the board, and settings["win"] is the required length for a player to win. empty board positions should be integers

[[0, 1, 2],
[3, 4, 5],
[6, 7, 8]]

like this and taken spots should be the name of the player

[[X, 1, 2],
[O, X, 5],
[X, 7, 8]]

like this. The variable currentTurn is the name of the player the function is checking for.

    def getRow(value) -> int: return floor(value/settings["len"]) % settings["height"]

    def getColumn(value) -> int: return value%settings["len"]

    def getBoardPosition(value) -> int: return board[getRow(value)][getColumn(value)]
    #

    def checkTie():
        for i in board:
            for j in i:
                if type(j) == int: return False # free spaces are integers
        return True

    def checkWin() -> bool:

        takenSpaces = list()
        length = {"=":int(), "||":int(), "\\": int(), "//":int()}

        for i, row in enumerate(board):
            for j, spot in enumerate(row):
                if spot == currentTurn: takenSpaces.append(i * settings["len"] + j) # get currentTurn's spaces

        print(f"your spaces: {takenSpaces}")

        def trackLength(direction):
            length[direction] += 1
            if length[direction] >= settings["win"]: return True

        for i in takenSpaces:
            for j in range(settings["win"]):

                if i + j in takenSpaces and getRow(i+j) == getRow(i): # horizontal
                    if trackLength("="): return True
                else: length["="] = 0

                if i + j * settings["len"] in takenSpaces and getColumn(i+j*settings["len"]) == getColumn(i): # vertical
                    if trackLength("||"): return True
                else: length["||"] = 0

                if i + j * settings["len"] + j in takenSpaces and getColumn(i+j*settings["len"]+j) == getColumn(i)+j: # diagonal left
                    if trackLength("\\"): return True
                else: length["\\"] = 0

                if i + j * settings["len"] - j in takenSpaces and getColumn(i+j*settings["len"]-j) == getColumn(i)-j: # diagonal right
                    if trackLength("//"): return True
                else: length["//"] = 0

            for j in length: length[j] = 0 # reset lengths

It will return True if the current player has won.

poendie
  • 21
  • 5
  • Self explanatory but settings["height"] is the height of the board. – poendie Jun 28 '22 at 20:41
  • And i don't pass in a board, settings and the current player, because i copy pasted it from my own code, where it was inside my game function. To solve this just pass in a 2d array as a board, the settings as a dictionary and the player its checking for. `floor(...)` also has to be imported from the math library. – poendie Jun 28 '22 at 20:46
0

You can do it by creating a generator, lines() that yields all 8 lines (3 rows, 3 columns and 2 diagonals) and then checking if any of the lines is made up of only one element and that element isn't None

Once you've got something like

board = [
    [ 'o', 'x', None], 
    [None, 'x', None], 
    [None, 'x',  'o']
]

Do this:

def _lines(board):
    yield from board  # the rows
    yield [board[i][i] for i in range(len(board))]  # one of the diagonals

def lines(board):
    yield from _lines(board)
    # rotate the board 90 degrees to get the columns and the other diagonal
    yield from _lines(list(zip(*reversed(board))))

def who_won(board):
    for line in lines(board):
        if len(set(line)) == 1 and line[0] is not None:
            return line[0]
    return None  # if we got this far, there's no winner

For the board I gave above, list(lines(board)) will return

[['o', 'x', None], 
 [None, 'x', None], 
 [None, 'x', 'o'], 
 ['o', 'x', 'o'], 
 (None, None, 'o'), 
 ('x', 'x', 'x'), 
 ('o', None, None), 
 [None, 'x', None]]

3 of those elements are tuples instead of lists because zip returns tuples. You can convert them using a list comprehension, see this question for how to do that and more details about what zip(*reversed(some_list)) does.

Then it's just a matter of checking each one to see if it has only one unique element by converting to a set and that that unique element is not None.

This works unmodified for any sized tic-tac-toe board, not just 3×3.

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
0

I'm a bit late but using numpy would be a better approach as it solves the hassle of iterating through multiple lists. It basically makes 'x' as 1 and 'o' as 0 so if w add all the elements in a row or column and we get 3 or 0, x or o would have won.if its a draw we get -1 in result array

import numpy as np
def check(arr):
    if np.sum(arr)==3:
        return 'x'
    elif np.sum(arr)==0:
        return 'o'
    else: return -1
if __name__=='__main__':
    a=np.array([['X','O','X'],['O','X','O'],['X','X','O']]) #input array
    b=np.where(a=='X',1,0)
    r1=check(b[:,:1])
    r2=check(b[:,1:2])
    r3=check(b[:,2:3])
    c1=check(b[:1,:])
    c2=check(b[1:2,:])
    c3=check(b[2:3,:])
    d1=check(np.diagonal(b))
    d2=check(np.diagonal(np.fliplr(b)))
    result=np.array([r1,r2,r3,c1,c2,c3,d1,d2])
    if 'x' in result:print('x won')
    elif 'o' in result:print('o won')
    else:print('draw again')
Shreyas H.V
  • 109
  • 1
  • 3
0

Just faced the same issue but the answers in here didn't really fulfill my needs so I made my own solution. Only works for 3x3 fields tho but it's really simple and could be scaled.

import sys

board=[
        ["X", "O", "X"],
        ["O", "X", "O"],
        ["X", "X", "O"]
]

def victory():
    for x in range(3):
        if board[x][0] == board[x][1] == board[x][2]:
            return board[x][0]
    for y in range(3):
        if board[0][y] == board[1][y] == board[2][y]:
            return board[0][y]
    for x in range(0, 3, 2):
        if board[x][0] == board[1][1] == board[2-x][2]:
            return board[x][0]
    return False

if victory() is not False:
    print("Winner is: "+victory())
-1

There are a total of 3 states that a cell can have

  1. 0 if it is not yet filled ( There is a possibility that the game gets over in 5 moves)
  2. 1 if it is filled with 'X'
  3. -1 if it is filled with 'O'

I will expand on @EfferLagan answer

def checkRows(board):
for row in board:
    if (len(set(row)) == 1) and (row[0] != 0):
        return row[0]
return 999

def checkDiagonals(board):
    if (len(set([board[i][i] for i in range(len(board))])) == 1) and (board[0][0] != 0):
        return board[0][0]
    if (len(set([board[i][len(board)-i-1] for i in range(len(board))])) == 1) and (board[0][0] !=0):
        return board[0][len(board)-1]
    return 999

def checkWin(board):
    #transposition to check rows, then columns
    for newBoard in [board, np.transpose(board)]:
        result = checkRows(newBoard)
        if result:
            return result
    return checkDiagonals(board)

randomInput=[
    [0,0,1],
    [-1,-1,1],
    [0,0,0]
]

You have 3 outputs 1, -1, and 999 ( which means neither won ) checkWin(randomInput)

Anant Gupta
  • 1,090
  • 11
  • 11
-2
  def check_winer(board):
    #used to check the winner in row
    for row in range(0,3):
        if board[row][0]==board[row][1]==board[row][2] and board[row][0] != 0:
            if chance=='o':
                print("x is winner")
            elif chance=='x':
                print("o is winner")
    #used to check thw winner in column
    for col in range(0,3):
        if board[0][col]==board[1][col]==board[2][col] and board[0][col] != 0:
            if chance=='o':
                print("x is winner")
            elif chance=='x':
                print("o is winner")
    #used to check winner in one diagonal
    if board[0][0]==board[1][1]==board[2][2] and board[0][0] !=0:
        if chance=='o':
            print('x is winner')
        elif chance=='x':
            print("o is winner")
    #used to check winner in another diagonal
    if board[0][2]==board[1][1]==board[2][0] and board[0][2]!=0:
        if chance=='o':
            print("x is winner")
        elif chance=='x':
            print("o is winner")

Two check the winner in tic-tac-toe you can use this function. you have to create a 2d-array like this first:

board=[[0,0,0],
      [0,0,0],
      [0,0,0]]

so when it is x's turn click the box and change the value in array as 1 with respect to the box. And change the chance from x to o and from o to x in each click. If it is o's turn change the value in array as 2 with respect to the coordinates. you have to call this function each time you click the box to place x or o in position.

Please make sure you call this function after you change the chance from x to o or from o to x. Because if you don't this function will give wrong output .

For example if this the board:

[[1,2,1],
 [2,1,0],
 [1,0,0]]

Then this function will give output as:

o is the winner
  • You need to fully document how OP's current code can be used by this solution. This is an old question and your answer is too abstract and complicated in the sense that developers who are asking OPs question are not likely to understand or benefit from your contribution. Seeing that OP is not likely to respond, you have to put more effort to provide a complete solution and convince the rest of us why your solution is superior to the others. – Chris Schaller Sep 14 '20 at 11:21