Hackbright Code Challenges

Tic-Tac-Random

Tic-Tac-Random

Challenge

Harder

Concepts

Data Structures

Download

tic-tac-random.zip

Solution

Tic-Tac-Random: Solution


We’ll build a tic-tac-toe game with a incredibly naive computer player.

The game should:

  • make the human X

  • make the computer O

  • let X play first

It should then loop:

  • print the board

  • if it’s the human’s turn, prompt for a move by cell position (1-9, where 1=top-left, 3=top-right, and 9=bottom-right) and play X in that cell

  • if it’s the computer’s turn, it will fill the first empty cell with O

  • check if there’s a winner and if the board is full

If there is a winner, or the board is full, it should quit and congratulate the winner (if any).

For example:

. . .
. . .
. . .

Enter move (1-9)> 1

X . .
. . .
. . .

O played in position 2

X O .
. . .
. . .

Enter move (1-9)> 4

X O .
X . .
. . .

O played in position 3

X O O
X . .
. . .

Enter move (1-9)> 7
Congratulations to X

Design

Before you look at our skeleton file, thing about the problem—how might you design a program like this?

How can you represent the game board?

It would be smart to break this into functions that handle the different parts of the problem—checking for a win, making a human move, making a computer move, etc.

How might you design tests to prove this works?

Make some notes about how you would design and name things.

Code

We’ve given you a skeleton a lot of code:

def tic_tac_random():
    """Play game of tic-tac-toe.

    The human will be X and the computer will be O.

    Loop:
    - Show the board
    - If it's the human's turn, prompt for a position (1-9) and make their move
    - If it's the computer's turn, make any legal move
    - If there's a winner or the board is full, quit the game

    At the end of the game, announce the winner (if any).
    """

    board = setup_board()
    current_player = 'X'
    winner = None
    full = False

    while not winner and not is_board_full(board):
        print()
        print_board(board)
        print()
        if current_player == 'X':
            move = input("Enter move (1-9)> ")
            position = int(move)
            make_move(board, position, 'X')
            current_player = 'O'
        else:
            position = make_random_move(board, 'O')
            print("O played in position %s" % position)
            current_player = 'X'

        winner = find_winner(board)

    if winner:
        print("Congratulations to " + winner)
    else:
        print("How boring, a tie")


def setup_board():
    """Create an empty tic-tac-toe board.

    Create a board as a list-of-rows, each row being a list-of-cells.

    Put '.' in each cell to mark it as empty.

    Return the board.

    >>> setup_board()
    [['.', '.', '.'], ['.', '.', '.'], ['.', '.', '.']]
    """

    return [['.', '.', '.'], ['.', '.', '.'], ['.', '.', '.']]


def is_board_full(board):
    """Return True is board is full, False otherwise.

    >>> is_board_full([['.', '.', '.'], ['X', '.', 'O'], ['.', '.', '.']])
    False

    >>> is_board_full([['X', 'O', '.'], ['X', 'O', 'X'], ['X', 'O', 'X']])
    False

    >>> is_board_full([['X', 'O', 'X'], ['X', 'X', 'O'], ['O', 'O', 'O']])
    True
    """


def make_random_move(board, player):
    """Find an empty cell and play into it.

    player = 'X' or 'O', depending on who should move.

    This should change the board in-place. It should return the
    position (1-9) it played into.

    You don't need to do this randomly -- it can simply use the first empty
    cell it finds.

    >>> board = [['X', 'O', 'X'], ['X', 'X', 'O'], ['O', 'O', '.']]
    >>> make_random_move(board, 'X')
    9

    >>> board
    [['X', 'O', 'X'], ['X', 'X', 'O'], ['O', 'O', 'X']]
    """


def find_winner(bd):
    """"Given board, determine if winner. Return 'X', 'O', or None if no winner.

    >>> print(find_winner([['.', '.', '.'], ['X', '.', 'O'], ['.', '.', '.']]))
    None

    >>> find_winner([['X', '.', '.'], ['X', '.', 'O'], ['X', '.', '.']])
    'X'

    >>> find_winner([['X', 'O', 'X'], ['O', 'O', 'X'], ['O', 'X', 'X']])
    'X'

    >>> find_winner([['X', '.', 'O'], ['X', 'O', 'O'], ['O', '.', '.']])
    'O'
    """


def print_board(board):
    """Given a board[col][row], print it out.

    >>> print_board([['.', '.', '.'], ['X', '.', 'O'], ['.', '.', '.']])
    . . . 
    X . O 
    . . . 
    """

    for row in board:
        for cell in row:
            print(cell, end=' ')
        print()


def make_move(board, position, player):
    """Play into position 1-9.

    position = 1-9 (top-left, top-middle, top-right ... bottom-right)
    player = 'X' or 'O'

    This should update the board to play there. It does not return anything.

    >>> board = [['X', '.', 'O'], ['X', 'O', 'O'], ['O', '.', '.']]

    >>> make_move(board, 2, 'O')
    >>> board
    [['X', 'O', 'O'], ['X', 'O', 'O'], ['O', '.', '.']]

    >>> make_move(board, 9, 'X')
    >>> board
    [['X', 'O', 'O'], ['X', 'O', 'O'], ['O', '.', 'X']]
    """


if __name__ == "__main__":
    import sys

    if len(sys.argv) > 1 and sys.argv[1] == 'test':
        import doctest

        if doctest.testmod().failed == 0:
            print("\n*** ALL TESTS PASS. FANTASTIC WORK!\n")
    else:
        tic_tac_random()

However, four functions are unimplemented:

def is_board_full(board):
    """Return True is board is full, False otherwise.

    >>> is_board_full([['.', '.', '.'], ['X', '.', 'O'], ['.', '.', '.']])
    False

    >>> is_board_full([['X', 'O', '.'], ['X', 'O', 'X'], ['X', 'O', 'X']])
    False

    >>> is_board_full([['X', 'O', 'X'], ['X', 'X', 'O'], ['O', 'O', 'O']])
    True
    """
def make_random_move(board, player):
    """Find an empty cell and play into it.

    player = 'X' or 'O', depending on who should move.

    This should change the board in-place. It should return the
    position (1-9) it played into.

    You don't need to do this randomly -- it can simply use the first empty
    cell it finds.

    >>> board = [['X', 'O', 'X'], ['X', 'X', 'O'], ['O', 'O', '.']]
    >>> make_random_move(board, 'X')
    9

    >>> board
    [['X', 'O', 'X'], ['X', 'X', 'O'], ['O', 'O', 'X']]
    """
def find_winner(bd):
    """"Given board, determine if winner. Return 'X', 'O', or None if no winner.

    >>> print(find_winner([['.', '.', '.'], ['X', '.', 'O'], ['.', '.', '.']]))
    None

    >>> find_winner([['X', '.', '.'], ['X', '.', 'O'], ['X', '.', '.']])
    'X'

    >>> find_winner([['X', 'O', 'X'], ['O', 'O', 'X'], ['O', 'X', 'X']])
    'X'

    >>> find_winner([['X', '.', 'O'], ['X', 'O', 'O'], ['O', '.', '.']])
    'O'
    """
def make_move(board, position, player):
    """Play into position 1-9.

    position = 1-9 (top-left, top-middle, top-right ... bottom-right)
    player = 'X' or 'O'

    This should update the board to play there. It does not return anything.

    >>> board = [['X', '.', 'O'], ['X', 'O', 'O'], ['O', '.', '.']]

    >>> make_move(board, 2, 'O')
    >>> board
    [['X', 'O', 'O'], ['X', 'O', 'O'], ['O', '.', '.']]

    >>> make_move(board, 9, 'X')
    >>> board
    [['X', 'O', 'O'], ['X', 'O', 'O'], ['O', '.', 'X']]
    """

Using the doctests as a guide, implement those functions.

You can test your code with:

$ python tictacrandom.py test