Homework 6, Problem 2: Connect Four: The Board class
[60 points; individual or pair]

 

Submission: Submit your hw6pr2.py file to Canvas

 

Connect Four is a variation of tic-tac-toe played on a 7x6 rectangular board:

The game is played by two players, alternating turns, with each trying to place four checkers in a row vertically, horizontally, or diagonally. One constraint in the game is that because the board stands vertically, the checkers cannot be placed into an arbitrary position. A checker may only be placed at the top of one of the currently existing columns (or it may start a new column).

The Board class -- a preview

In this problem, you will need to create a class named Board that implements some of the features of the Connect Four game. The Board class will have three data members: there will be a two-dimensonal list (a list of lists) containing characters to represent the game area, and a pair of variables holding the number of rows and columns on the board (6 rows and 7 columns is standard), but your Board datatype will be able to handle boards of any size.

Even as we allow arbitrary board sizes, however, we will preserve the four-in-a-row requirement for winning the game. Admittedly, this makes it hard to win the game on a 3x3 board.

Your task is to write the Board class. Details appear here:

The Board class

Your Board class should have at least three data members:

Note that the two-dimensional list is a two-dimensional list of characters, which are just strings of length 1. You should represent an empty slot by ' ', which is the space character -- not the empty string. You should represent player X's checkers with an 'X' (the capital x character) and you should represent player O's checkers with an 'O' (the capital o character).

Warning!
A very difficult bug to find occurs if you use the zero
'0' character instead of the 'O' character (capital-O) to represent one of the players. The problem occurs when you or the graders start comparing values in the board with the wrong one! Be sure to stay consistent with the capital-O character.

Methods required for the Board class :

You should provide your Board class with write the following methods. Be sure to try the hints on how to test each one after writing it! The first two methods were written in class and are provided for copy-and-paste, below. However, they were written with magic numbers of 7 columns and 6 rows. You will need to change the code below so that arbitrarily-sized boards are available.

__init__, the constructor

 

__repr__, for printing or any string representation

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

---------------

 0 1 2 3 4 5 6

In order to keep things in line, the column-numbering should be done "mod 10," as this larger 5-row, 15-column (5x15) example shows:

| | | | | | | | | | | | | | | |

| | | | | | | | | | | | | | | |

| | | | | | | | | | | | | | | |

| | | | | | | | | | | | | | | |

| | | | | | | | | | | | | | | |

-------------------------------

 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4


One string, multiple lines?
The string

'\n'

represents the newline character in Python. Thus, you can get multiple lines into a single string by adding '\n'. For example,

>>> s = 'This is the top line.'

>>> s += '\n'

>>> s += 'This is a second line!\n'

>>> print(s)

This is the top line.

This is a second line!

 

>>> 


Notice that the '\n' can be included within a string, as well. In this case, it added an extra blank line at the end of s.

The code to start with...
Here is the code to start with -- remember that you will need to get rid of the magic numbers (7 columns and 6 rows) as you generalize this code to have
width columns and height rows.

class Board:

""" a datatype representing a C4 board

with an arbitrary number of rows and cols

"""

 

def __init__( self, width, height ):

""" the constructor for objects of type Board """

self.width = width

self.height = height

self.data = [] # this will be the board

 

for row in range( 6 ):

boardRow = []

for col in range( 7 ):

boardRow += [' '] # add a space to this row

self.data += [boardRow] # add this row to the board

 

# do not need to return inside a constructor!

 

 

def __repr__(self):

""" this method returns a string representation

for an object of type Board

"""

s = ''   # the string to return

for row in range( 6 ):

s += '|' # add the spacer character

for col in range( 7 ):

s += self.data[row][col] + '|'

s += '\n'

 

# add the bottom of the board

# and the numbers underneath here

 

return s # the board is complete, return it



Next, implement the following methods in your Board class. Be sure to test each one after you write it -- it's much easier to make sure each works one-at-a-time than trying to debug a huge collection of methods all at once.

addMove, for dropping a checker into the board

Remember that the checker slides down from the top of the board! Thus, your code will have to find the appropriate row number available in column col and put the checker in that row. In addMove you do not have to check that col is a legal column number or that there is space in column col. That checking is important, however. The next method, which is called allowsMove, will do just that.

Testing addMove
Here is a sequence for testing
addMove -- try it out!

>>> b = Board(7,6)

>>> b.addMove(0, 'X')

>>> b.addMove(0, 'O')

>>> b.addMove(0, 'X')

>>> b.addMove(3, 'O')

>>> b.addMove(4, 'O')   # cheating by letting O go again!

>>> b.addMove(5, 'O')

>>> b.addMove(6, 'O')

>>> b

 

| | | | | | | |

| | | | | | | |

| | | | | | | |

|X| | | | | | |

|O| | | | | | |

|X| | |O|O|O|O|

---------------

 0 1 2 3 4 5 6

 

clear, should clear the board that calls it.

Not much to say about clear( self ). It's useful, though!

setBoard, for checking if a column is a legal move

This one is really useful for quickly creating a board to test your lookahead in the next problem. Here is the code we used for

setBoard:

 

def setBoard( self, moveString ):

""" takes in a string of columns and places

alternating checkers in those columns,

starting with 'X'

 

For example, call b.setBoard('012345')

to see 'X's and 'O's alternate on the

bottom row, or b.setBoard('000000') to

see them alternate in the left column.

 

moveString must be a string of integers

"""

nextCh = 'X'

for colString in moveString:

col = int(colString)

if 0 <= col <= self.width:

self.addMove(col, nextCh)

if nextCh == 'X': nextCh = 'O'

else: nextCh = 'X'

 

allowsMove, for checking if a column is a legal move



Testing allowsMove
Here is an example sequence for testing -- try it!

>>> b = Board(2,2)

>>> b

 

| | |

| | |

-----

 0 1

 

>>> b.addMove(0, 'X')

>>> b.addMove(0, 'O')

>>> b

 

|O| |

|X| |

-----

 0 1

 

>>> b.allowsMove(-1)

False

 

>>> b.allowsMove(0)

False

 

>>> b.allowsMove(1)

True

 

>>> b.allowsMove(2)

False

 

isFull, checks if the board is full



Testing isFull
Here is an example sequence for testing (it uses
setBoard, above)

>>> b = Board(2,2)

>>> b.isFull()

False

 

>>> b.setBoard( '0011' )

>>> b

 

|O|O|

|X|X|

-----

 0 1

 

>>> b.isFull()

True

 

delMove, removes a checker from the board

Testing delMove
Here is an example sequence for testing:

>>> b = Board(2,2)

>>> b.setBoard( '0011' )

>>> b.delMove(1)

>>> b.delMove(1)

>>> b.delMove(1)

>>> b.delMove(0)

 

>>> b

 

| | |

|X| |

-----

 0 1

 

 

winsFor, checks if someone has won the game

# check for horizontal wins

for row in range(0,self.height):

for col in range(0,self.width-3):

if self.data[row][col] == ox and \

self.data[row][col+1] == ox and \

self.data[row][col+2] == ox and \

self.data[row][col+3] == ox:

return True

 

Note the backslash characters -- these tell Python that the line of code will continue on the next line of the file. Note, too, the -3 that keeps the checking in bounds. Different directions will require different guards against going out of bounds.

Warning I would advise against explicitly counting checkers to see if you reach four. The problem is that you must visit each checker in the right order. Vertical and horizontal orderings aren't bad, but visiting each checker in diagonal order is neither easy nor informative. It's more convenient to check for all four checkers at once, as in the previous example.

This is an important method to test thoroughly! The following is only one test:

Testing winsFor
Here is an example sequence for testing:

>>> b = Board(7,6)

>>> b.setBoard( '00102030' )

>>> b.winsFor('X')

True

 

>>> b.winsFor('O')

True

 

 

>>> b = Board(7,6)

>>> b.setBoard( '00102030' )

>>> b.winsFor('X')

True

 

>>> b.winsFor('O')

True

 

>>> b = Board(7,6)

>>> b.setBoard( '23344545515' )

>>> b

 

| | | | | | | |

| | | | | | | |

| | | | | |X| |

| | | | |X|X| |

| | | |X|X|O| |

| |O|X|O|O|O| |

---------------

 0 1 2 3 4 5 6

 

>>> b.winsFor('X')  # diagonal

True

 

>>> b.winsFor('O')

False

 

hostGame, hosts a full game of Connect Four

This method should print the board before prompting for each move.

After each input, you should check if the column chosen is a valid one. Thus, this method should detect illegal moves, either out-of-bounds or a full column, and prompt for another move instead. You do not have to check if the input is an integer, however; you may assume it will always be one. This method should place the checker into its (valid) column. Then, it should check if that player has won the game or if the board is now full.

If the game is over for either reason, the game should stop, the board should be printed out one last time, and the program should report who won (or that it was a tie.)

If the game is not over, the other player should be prompted to make a move, and so on.

Be sure to test this method by playing the game a few times (with each possible ending)!

Here is an example run, to give a sense of the input and output:

>>> b = Board(7,6)

>>> b.hostGame()

 

 

Welcome to Connect Four!

 

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

---------------

 0 1 2 3 4 5 6

 

X's choice: 3

 

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | |X| | | |

---------------

 0 1 2 3 4 5 6

 

O's choice: 4

 

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | |X|O| | |

---------------

 0 1 2 3 4 5 6

 

X's choice: 2

 

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | |X|X|O| | |

---------------

 0 1 2 3 4 5 6

 

O's choice: 4

 

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | |O| | |

| | |X|X|O| | |

---------------

 0 1 2 3 4 5 6

 

X's choice: 1

 

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | |O| | |

| |X|X|X|O| | |

---------------

 0 1 2 3 4 5 6

 

O's choice: 2

 

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | |O| |O| | |

| |X|X|X|O| | |

---------------

 0 1 2 3 4 5 6

 

X's choice: 0

 

 

X wins -- Congratulations!

 

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | | | | | | |

| | |O| |O| | |

|X|X|X|X|O| | |

---------------

 0 1 2 3 4 5 6

 

>>> 


If you have gotten to this point, you have completed problem 2! You should submit your hw6pr2.py file at Canvas .

 

The next (extra!) problem will ask you to make a copy of this file and extend it to build an "AI" player for the game of connect four!

 

Next

hw6pr2

Lab 6

Homework 6