3

I have a dictionary:

squares = {
          'r1c1':{'location':[0,0,150,150],'status':'o'},
          'r1c2':{'location':[150,0,300,150],'status':None},
          'r1c3':{'location':[300,0,450,150],'status':None},
          'r2c1':{'location':[0,150,150,300],'status':'x'},
          'r2c2':{'location':[150,150,300,300],'status':'x'},
          'r2c3':{'location':[300,150,450,300],'status':'x'},
          'r3c1':{'location':[0,300,150,450],'status':None},
          'r3c2':{'location':[150,300,300,450],'status':None},
          'r3c3':{'location':[300,300,450,450],'status':'o'}
          }

Ignore location. R stands for row; C stands for column. I want to know if there is a compact function I can use to check if all all rows, columns, or diagonals have the same value ... think TicTacToe ... As of now, I have it all in a big if statement:

def TicTacToe(self):
    #rows
    if self.squares['r1c1']['status'] == 'x' and self.squares['r1c2']['status'] == 'x' and self.squares['r1c3']['status'] == 'x':
        self.gameOver('x')
    elif self.squares['r2c1']['status'] == 'x' and self.squares['r2c2']['status'] == 'x' and self.squares['r2c3']['status'] == 'x':
        self.gameOver('x')
    elif self.squares['r3c1']['status'] == 'x' and self.squares['r3c2']['status'] == 'x' and self.squares['r3c3']['status'] == 'x':
        self.gameOver('x')

    elif self.squares['r1c1']['status'] == 'o' and self.squares['r1c2']['status'] == 'o' and self.squares['r1c3']['status'] == 'o':
        self.gameOver('o')
    elif self.squares['r2c1']['status'] == 'o' and self.squares['r2c2']['status'] == 'o' and self.squares['r2c3']['status'] == 'o':
        self.gameOver('o')
    elif self.squares['r3c1']['status'] == 'o' and self.squares['r3c2']['status'] == 'o' and self.squares['r3c3']['status'] == 'o':
        self.gameOver('o')
    #columns
    elif self.squares['r1c1']['status'] == 'x' and self.squares['r2c1']['status'] == 'x' and self.squares['r3c1']['status'] == 'x':
        self.gameOver('x')
    elif self.squares['r1c2']['status'] == 'x' and self.squares['r2c2']['status'] == 'x' and self.squares['r3c2']['status'] == 'x':
        self.gameOver('x')
    elif self.squares['r1c3']['status'] == 'x' and self.squares['r2c3']['status'] == 'x' and self.squares['r3c3']['status'] == 'x':
        self.gameOver('x')

    elif self.squares['r1c1']['status'] == 'o' and self.squares['r2c1']['status'] == 'o' and self.squares['r3c1']['status'] == 'o':
        self.gameOver('o')
    elif self.squares['r1c2']['status'] == 'o' and self.squares['r2c2']['status'] == 'o' and self.squares['r3c2']['status'] == 'o':
        self.gameOver('o')
    elif self.squares['r1c3']['status'] == 'o' and self.squares['r2c3']['status'] == 'o' and self.squares['r3c3']['status'] == 'o':
        self.gameOver('o')
    #diagonal
    elif self.squares['r1c1']['status'] == 'x' and self.squares['r2c2']['status'] == 'x' and self.squares['r3c3']['status'] == 'x':
        self.gameOver('x')
    elif self.squares['r1c3']['status'] == 'x' and self.squares['r2c2']['status'] == 'x' and self.squares['r3c1']['status'] == 'x':
        self.gameOver('x')
    elif self.squares['r1c1']['status'] == 'o' and self.squares['r2c2']['status'] == 'o' and self.squares['r3c3']['status'] == 'o':
        self.gameOver('o')
    elif self.squares['r1c3']['status'] == 'o' and self.squares['r2c2']['status'] == 'o' and self.squares['r3c1']['status'] == 'o':
        self.gameOver('o')

But this is ugly and I want to do better. Any ideas?...

Jay
  • 1,289
  • 3
  • 11
  • 22
  • 1
    In Python, functions, class methods and variables are named using `lowercase_with_underscores`, note `camelCase` or `CapitalCase`. The latter is used for `class WhateverClass` and the former isn't used. – Boris Verkhovskiy Dec 28 '19 at 06:14
  • 1
    step 1) convert your board into some better representation like a list of lists instead of what you have. Step 2) take your pick: https://codereview.stackexchange.com/questions/24764/tic-tac-toe-victory-check https://codereview.stackexchange.com/questions/215893/beginner-tic-tac-toe-game-for-python https://codereview.stackexchange.com/questions/230212/simulate-tic-tac-toe-game-in-python https://codereview.stackexchange.com/questions/24764/tic-tac-toe-victory-check https://stackoverflow.com/questions/47423891/tic-tac-toe-diagonal-check – Boris Verkhovskiy Dec 28 '19 at 06:20
  • Are you implying I should use tic_tac_toe or ticTacToe, instead of TicTacToe with the first comment? Or are you saying something else, and it's going over my head? – Jay Dec 28 '19 at 06:28
  • 2
    `def tic_tac_toe(squares):` . (I changed the argument name from `self` because you shouldn't name arguments `self` unless it's the first argument of a [class method](https://docs.python.org/3/tutorial/classes.html#a-first-look-at-classes), you can use the name `squares` again if you just call this function like `tic_tac_toe(squares)`) – Boris Verkhovskiy Dec 28 '19 at 06:32
  • 1
    Yes, it's within a class. I just didn't include the whole program for simplicity sake. But thanks for the tip!!! – Jay Dec 28 '19 at 06:33

5 Answers5

2

First, convert squares to something better, like this

board = [[squares[f"r{row}c{col}"]["status"] for row in range(1, 4)] for col in range(1, 4)]

now board is a list of lists:

[[ 'o', 'x', None], 
 [None, 'x', None], 
 [None, 'x',  'o']]

You can now use any of the thousands of code samples that exist for this problem:

def check_columns(board):
    for column in board:
        if len(set(column)) == 1 and column[0] is not None:
            return column[0]

def check_rows(board):
    return check_columns(zip(*reversed(board)))  # rotate the board 90 degrees

def check_diagonals(board):
    if ((board[0][0] == board[1][1] == board[2][2]) or
        (board[2][0] == board[1][1] == board[0][2])):
        if board[1][1] is not None:
            return board[1][1]

def who_won(board):
    for check in [check_columns, check_rows, check_diagonals]:
        result = check(board)
        if result is not None:
            return result

Use it like this:

>>> who_won(board)
'x'
>>> board[1][1] = 'o'
>>> who_won(board)
'o'
>>> board[1][1] = None
>>> who_won(board)
>>> 
>>> # nothing printed means it returned `None` (because there's no winner)
Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
  • See https://stackoverflow.com/questions/34347043/how-can-i-rotate-this-list-of-lists-with-python for what `zip(*board)` does – Boris Verkhovskiy Dec 28 '19 at 06:53
  • 1
    also, I might've confused rows and columns, depending on how you look at it. It really doesn't matter though, because tic-tac-toe is completely symmetrical anyway – Boris Verkhovskiy Dec 28 '19 at 08:11
0

Here's a generic solution for a NxN dimensional square board.

squares = {
    'r1c1':{'location':[0,0,150,150],'status':'o'},
    'r1c2':{'location':[150,0,300,150],'status':None},
    'r1c3':{'location':[300,0,450,150],'status':None},
    'r2c1':{'location':[0,150,150,300],'status':'x'},
    'r2c2':{'location':[150,150,300,300],'status':'x'},
    'r2c3':{'location':[300,150,450,300],'status':'x'},
    'r3c1':{'location':[0,300,150,450],'status':None},
    'r3c2':{'location':[150,300,300,450],'status':None},
    'r3c3':{'location':[300,300,450,450],'status':'o'}
}
N = 3 # Assuming you have NxN dimensional board
def check_rows():
    # Check for all rows
    for i in range(N):
        row = 'r'+str(i+1)
        flag = True
        # Check if all value in current row are NOT 'x' or 'o'
        for j in range(N-1):
            cell1 = row+'c'+str(j+1)
            cell2 = row+'c'+str(j+2)
            if squares[cell1]['status'] != squares[cell2]['status']:
                flag = False
                break
        if flag: # All values are same
            print('Found match in',row)
            return True
    return False # No match
def check_cols():
    # Check for all cols
    for i in range(N):
        col = 'c'+str(i+1)
        flag = True
        # Check if all value in current col are NOT 'x' or 'o'
        for j in range(N-1):
            cell1 = 'r'+str(j+1)+col
            cell2 = 'r'+str(j+2)+col
            if squares[cell1]['status'] != squares[cell2]['status']:
                flag = False
                break
        if flag: # All values are same
            print('Found match in',col)
            return True
    return False # No match
def check_left_diag():
    flag = True
    # Check if all value in left diagonal are NOT 'x' or 'o'
    for i in range(N-1):
        cell1 = 'r'+str(i+1)+'c'+str(i+1)
        cell2 = 'r'+str(i+2)+'c'+str(i+2)
        if squares[cell1]['status'] != squares[cell2]['status']:
            flag = False
            break
    if flag: # All values are same
        print('Found match in left diagonal')
        return True
    return False # No match
def check_right_diag():
    flag = True
    # Check if all value in right diagonal are NOT 'x' or 'o'
    for i in range(N-1):
        cell1 = 'r'+str(i+1)+'c'+str(N-i)
        cell2 = 'r'+str(i+2)+'c'+str(N-i-1)
        if squares[cell1]['status'] != squares[cell2]['status']:
            flag = False
            break
    if flag: # All values are same
        print('Found match in right diagonal')
        return True
    return False # No match

print(check_rows())
print(check_cols())
print(check_left_diag())
print(check_right_diag())

Output

Found match in r2
True
False
False
False
Ajay Dabas
  • 1,404
  • 1
  • 5
  • 15
0

Here's an even shorter way to check the results of a tic-tac-toe game by creating a generator, lines() that yields all 8 lines (3 rows, 3 columns and 2 diagonals) and then checking if any of them 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 _rows_and_diagonal(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 _rows_and_diagonal(board)
    # rotate the board 90 degrees to get the columns and the other diagonal
    yield from _rows_and_diagonal(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

This will work for any sized tic-tac-toe board, not just 3×3.

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
0
def check_win():
  #Horizontal Algo
  for row in game:
      check_win2(row)
check_win2(horcheck)
check = []
  #Vertical algo
  for col in range(len(game[0])):
      check = []
      for row in game:
          check.append(row[col])
      check_win2(check)
  #Diagonal Algo
  rev_diags = []
  diags = []
  cols = list(reversed(range(len(game))))
  rows = range(len(game))
  for col, row in zip(cols, rows):
    rev_diags.append(col)
    diags.append(row)
  check_win2(rev_diags)
  check_win2(diags)

def check_win2(lists):
  if lists.count(lists[0]) == len(lists) and lists[0] != 0:
    print(f'Player {lists[0]} won the game')
    exit()
-1

With your current setup this would be very difficult to achieve. I don't think a dictionary alone would be a good idea. What you would need is a 2 dimensional matrix of dictionary object containing location and status. This way you can very easily iterate on the row and the column of your 2d matrix with two for loop and check the status of each cells.

Yacine Mahdid
  • 723
  • 5
  • 17
  • I don't yet know what a 2 dimensional matrix is, but I'm going to look it up - thank you very much, friend! (Edit: nvm, you mean a list of lists ...) – Jay Dec 28 '19 at 06:25
  • 2
    think about it as a list that contain other list of the same size. So if you want to make a 2 dimensional matrix of height and width = 3 it will look like this: `matrix = [[1,2,3],[1,2,3],[1,2,3],]` – Yacine Mahdid Dec 28 '19 at 06:29