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:
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