Overview

The goal of this project is to allow you to explore artificial intelligence by creating a bot in the Python programming language that plays the game Connect 4:

Connect Four (also known as Captain's Mistress, Four Up, Plot Four, Find Four, Fourplay, Four in a Row, Four in a Line and Gravitrips (in Soviet Union) ) is a two-player connection game in which the players first choose a color and then take turns dropping colored discs from the top into a seven-column, six-row vertically suspended grid. The pieces fall straight down, occupying the next available space within the column. The objective of the game is to be the first to form a horizontal, vertical, or diagonal line of four of one's own discs. -- Wikipedia

In class, we will explore how to represent the Connect 4 board and how to simulate a game between two opponents. For the project, you will need to create a bot that plays as one of the players in the game.

To develop your bot, create a new Jupyter Notebook titled Project01.ipynb and use this notebook to program and test your bot.

This Project assignment is due in class Tuesday, November 22, 2016 and is to be done in pairs.

Partners

Both members of each pair should submit a Notebook. Be sure to identify your partner at the top of the Notebook.

Overview

The following is an overview of the code skeleton provided below.

As we will discuss in class, a Connect 4 board can be represented as a two dimensional list:

Board = create_board()
print Board

[[' ', ' ', ' ', ' ', ' ', ' ', ' '],
 [' ', ' ', ' ', ' ', ' ', ' ', ' '],
 [' ', ' ', ' ', ' ', ' ', ' ', ' '],
 [' ', ' ', ' ', ' ', ' ', ' ', ' '],
 [' ', ' ', ' ', ' ', ' ', ' ', ' '],
 [' ', ' ', ' ', ' ', ' ', ' ', ' ']]

The create_board function below initializes a board of size ROWS (i.e. 6) and COLUMNS (i.e. 7) with PIECE_NONE (i.e. ' ') in each cell.

We can place a piece on the board by doing:

drop_piece(Board, 3, PIECE_ONE)
print_board(Board)

| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | |x| | | |

This calls the drop_piece function to place PIECE_ONE (i.e. x) into the column 3 of the board.

To check if there is a winner on the board, we can use the find_winner function:

winner = find_winner(Board)
if winner:
    print 'The winner is {}'.format(winner)

Finally, to display the board using SVG graphics, we can use the create_board_svg function:

display_html(create_board_svg(Board, 40))

All of these functions are combined into a game simulator given below.

Code Skeleton

The following is the support code provided to you in class:

# Imports

from IPython.display import display, HTML, clear_output
import random
import time

# Game Constants

ROWS       = 6
COLUMNS    = 7

PIECE_NONE = ' '
PIECE_ONE  = 'x'
PIECE_TWO  = 'o'

PIECE_COLOR_MAP = {
    PIECE_NONE : 'white',
    PIECE_ONE  : 'black',
    PIECE_TWO  : 'red',
}

DIRECTIONS = (
    (-1, -1), (-1, 0), (-1, 1),
    ( 0, -1),          ( 0, 1),
    ( 1, -1), ( 1, 0), ( 1, 1),
)

# Board Functions

def create_board(rows=ROWS, columns=COLUMNS):
    ''' Creates empty Connect 4 board '''
    board = []

    for row in range(rows):
        board_row = []
        for column in range(columns):
            board_row.append(PIECE_NONE)
        board.append(board_row)

    return board

  # Copy board

  def copy_board(board):
      ''' Return a copy of the board '''
      rows    = len(board)
      columns = len(board[0])
      copied  = create_board(rows, columns)

      for row in range(rows):
          for column in range(columns):
              copied[row][column] = board[row][column]
      return copied

def print_board(board):
    ''' Prints Connect 4 board '''
    for row in board:
        print '|' + '|'.join(row) + '|'

def drop_piece(board, column, piece):
    ''' Attempts to drop specified piece into the board at the
    specified column

    If this succeeds, return True, otherwise return False.
    '''

    for row in reversed(board):
        if row[column] == PIECE_NONE:
            row[column] = piece
            return True

    return False

def find_winner(board, length=4):
    ''' Return whether or not the board has a winner '''

    rows    = len(board)
    columns = len(board[0])

    for row in range(rows):
        for column in range(columns):
            if board[row][column] == PIECE_NONE:
                continue

            if check_piece(board, row, column, length):
                return board[row][column]

    return None

def check_piece(board, row, column, length):
    ''' Return whether or not there is a winning sequence starting from
    this piece
    '''
    rows    = len(board)
    columns = len(board[0])

    for dr, dc in DIRECTIONS:
        found_winner = True

        for i in range(1, length):
            r = row + dr*i
            c = column + dc*i

            if r not in range(rows) or c not in range(columns):
                found_winner = False
                break

            if board[r][c] != board[row][column]:
                found_winner = False
                break

        if found_winner:
            return True

    return False

# HTML/SVG Functions

def display_html(s):
    ''' Display string as HTML '''
    display(HTML(s))

def create_board_svg(board, radius):
    ''' Return SVG string containing graphical representation of board '''

    rows     = len(board)
    columns  = len(board[0])
    diameter = 2*radius

    svg  = '<svg height="{}" width="{}">'.format(rows*diameter, columns*diameter)
    svg += '<rect width="100%" height="100%" fill="blue"/>'

    for row in range(rows):
        for column in range(columns):
            piece = board[row][column]
            color = PIECE_COLOR_MAP[piece]
            cx    = column*diameter + radius
            cy    = row*diameter + radius
            svg += '<circle cx="{}" cy="{}" r="{}" fill="{}"/>'.format(cx, cy, radius*.75, color)

    svg += '</svg>'

    return svg

Here are two initial players you can use to test your bot:

def HumanPlayer(board, history, players):
    ''' Read move from human player '''
    columns = len(board[0])
    column  = -1

    while column not in range(0, columns):
        column = input('Which column? ')

    return column

def RandomPlayer(board, history, players):
    ''' Randomly select a column '''
    columns = len(board[0])
    return random.randint(0, columns - 1)

To simulate a game of Connect 4, you may use the following code:

# Globals

Players = (PIECE_ONE, PIECE_TWO)
History = []
Board   = create_board()
Radius  = 40
Winner  = None
Tries   = 0

# Game Loop

while not Winner:
    turn = len(History)

    if turn % 2 == 0:
        move = HumanPlayer(Board, History, Players)   # Player One
    else:
        move = RandomPlayer(Board, History, Players)  # Player Two

    if drop_piece(Board, move, Players[turn % 2]):
        Tries = 0
        History.append(move)

    if Tries > 3:
        print 'Player {} is stuck!'.format((turn % 2) + 1)
        break

    clear_output()
    display_html(create_board_svg(Board, Radius))

    time.sleep(1)

    Winner = find_winner(Board)

print 'The Winner is {}'.format(PIECE_COLOR_MAP[Winner])

Bot

Your objective for this project is to create an artificial intelligence agent that can successfully play Connect 4 against humans and other bots. To do so, you will need to define a function:

def BotPlayer(board, history, players):
    '''
    This is your AI bot player.  It is given the following arguments:

        Board:    This is the current board.
        History:  This is the history of the previous moves (columns).
        Players:  This is the list of players.

    Your function must not modify any of these objects.

    After analyzing the inputs, your bot should return the column that
    represents the best possible move.
    '''

An example of an artificial intelligence agent is given above in RandomPlayer, which simply chooses a random column.

Note: Feel free to rename BotPlayer to another creative name.

Requirements

Your bot needs to be better than random placement. In order to get full credit, your bot must do at least the following:

  1. It must select a winning move if it exists.

  2. It must select a blocking move (i.e. prevent opponent from winning) if it exists.

When there is neither a winning move or a blocking move is where your creativity and your bot's intelligence manifests yourself.

  1. One possible strategy is to simulate moves and see which moves gives you the most two-in-a-rows, three-in-a-rows, etc. That is simulate placing a piece in a column and see what the possible outcomes are. You will then need to score and rank each move.

  2. The rigorous way to solve Connect 4 is to implement the Minimax algorithm which would guarantee a win or a tie for your bot.

Hints

  1. Use the copy_board function to create a copy of the board before simulating a move.

  2. Use the drop_piece function on the copied board to simulate a move.

  3. Use the find_winner function to check if there is a winner in the simulated board.

  4. Use the find_winner or check_piece function to test for lengths other than 4.

Battle Royale

On November 22, we will have a Connect 4 tournament during class where your bot will compete against bots written by other students. The winner of the tournament will face a bot written by the instructional staff and be given extra credit.

To facilitate this tournament, please make sure you submit your Notebook on time and that you have at least one group member present.

Submission

To submit your notebook, follow the same directions for Notebook 01.