Python Forum

Full Version: need help with solution-checker function
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
This is a self-guided exercise from a Python instruction book. The exercise is to design a simple text Tic-Tac-Toe game for two players. OOP has not been introduced by the book yet so the script takes a functional/procedural approach.

I'll post the entire 91-line script below for reference, but the question has to do with how to make one specific function less clunky. The function checks whether the game of Tic-Tac-Toe has been won with three Xs or three Os in a row.

def won(grid):
    for i in range(3):
        if grid[i][0] != '-':
            if grid[i][0] == grid [i][1] and grid[i][1] == grid[i][2]:
                return True
    if grid[0][0] != '-':
        if grid[0][0] == grid[0][1] and grid[0][1] == grid[0][2]:
            return True
    if grid[1][0] != '-':
        if grid[1][0] == grid[1][1] and grid[1][1] == grid[1][2]:
            return True
    if grid[2][0] != '-':
        if grid[2][0] == grid[2][1] and grid[2][1] == grid[2][2]:
            return True
    if grid[0][0] != '-':
        if grid[0][0] == grid[1][1] and grid[1][1] == grid[2][2]:
            return True
    if grid[0][2] != '-':
        if grid[0][2] == grid[1][1] and grid[1][1] == grid[2][2]:
            return True
    return False
Here's the whole script:

def lay_out_grid():
    return [['-','-','-'],['-','-','-'],['-','-','-']]
    
def print_grid(grid, player1, player2):
    print()
    print("{} is X, {} is O.".format(player1[0], player2[0]))
    print()
    for i in range(3):
        for j in range(3):
            print(grid[i][j], end=" ")
        print()
    print()
    
def choose_number(prompt):
    while True:
        try:
            number = int(input(prompt))
            if number > 0 and number < 4:
                return number
            print("Selection must be 1, 2, or 3.")
        except ValueError:
            print("Selection must be a number, 1, 2, or 3.")
            
def make_move(player, grid):
    print("\n{}, it's your move.".format(player[0]))
    while True:
        row = choose_number("Please choose a row (1, 2, or 3): ")
        column = choose_number("Please choose a column (1, 2, or 3: ")
        if grid[row-1][column-1] == "-":
            return row, column
        else:
            print("Square already used.")
    
def mark_spot(grid, row, column, player):
    grid[row-1][column-1] = player[1]
             
    
def evaluate_full(grid):
    for i in range(3):
        for j in range(3):
            if grid[i][j] == '-':
                return False
    return True
    
def won(grid):
    for i in range(3):
        if grid[i][0] != '-':
            if grid[i][0] == grid [i][1] and grid[i][1] == grid[i][2]:
                return True
    if grid[0][0] != '-':
        if grid[0][0] == grid[0][1] and grid[0][1] == grid[0][2]:
            return True
    if grid[1][0] != '-':
        if grid[1][0] == grid[1][1] and grid[1][1] == grid[1][2]:
            return True
    if grid[2][0] != '-':
        if grid[2][0] == grid[2][1] and grid[2][1] == grid[2][2]:
            return True
    if grid[0][0] != '-':
        if grid[0][0] == grid[1][1] and grid[1][1] == grid[2][2]:
            return True
    if grid[0][2] != '-':
        if grid[0][2] == grid[1][1] and grid[1][1] == grid[2][2]:
            return True
    return False
    
def play(grid, player1, player2):
    print_grid(grid, player1, player2)
    while True:
        for player in [player1, player2]:
            row, column = make_move(player, grid)
            mark_spot(grid, row, column, player)
            print_grid(grid, player1, player2)
            if evaluate_full(grid):
                print("The grid is full. The game is over.\n")
                return
            for player in [player1, player2]:
                if won(grid):
                    print("{} has won!".format(player[0]))
                    return
    
def prepare():
    print("\nThis is a game of Tic-Tac-Toe for two players.")
    print("The rows are 1, 2, and 3 left to right, the columns 1, 2, and 3 top down.")
    player1 = input("\nFirst player, please enter your name: "), "X"
    player2 = input("\nSecond player, please enter your name: "), "O"
    return player1, player2

def main():
    grid = lay_out_grid()
    player1, player2 = prepare()
    play(grid, player1, player2)
    print("\nThanks for playing! Have a nice day!\n")
    
if __name__=='__main__':
    main()
You could use a generator
def indexes():
    r = (0 ,1, 2)
    for i in r:
        yield [(i, j) for j in r]
        yield [(j, i) for j in r]
    yield [(i, i) for i in r]
    yield [(2-i, i) for i in r]
    
def won(grid):
    for r in indexes():
        a, b, c = [grid[i][j] for (i, j) in r]
        if a != '-' and a == b and b == c:
            return True
    return False
I always done grid/board as one line.
 grid = ['-', '-', '-', '-', '-', '-', '-', '-', '-']
win_pattern = ((0,1,2), (3,4,5), (6,7,8), # across
               (0,3,6), (1,4,7), (2,5,8), # down
               (0,4,8), (2,4,6))

def check_win(grid, letter):
    for x,y,z in self.win_pattern:
        if grid[x] == grid[y] == grid[z] == letter:
            return True
    return False
A generator would be a good idea but the book instructs readers not to make use of knowledge that is taught in later chapters. Generators are in a later chapter. Still, that generator example is valuable code for me to study.

Thanks also for the idea of redoing the script using a simple list instead of a matrix. I'll see whether it works better. It could improve other parts of the script.
NumPy can give you the best of both worlds. If you can't decide.
import numpy as np

# Flat array
grid = np.array([str(i) for i in range(9)])
# Reshape 3 by 3 grid
grid.shape = (3,3)
print(grid)
# still let you use it as a flat array
grid.flat[3] = 'X'
print()
print(grid)