HW 6: Problems 2&3 - Northwestern University

Download Report

Transcript HW 6: Problems 2&3 - Northwestern University

HW 6: Problems 2&3

Simulating Connect 4

Problem 2: Connect 4 Board

•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).

Problem 2: Connect 4 Board

So how do we represent a connect 4 board in python???

Well, we would need a 2d data structure (i.e. list o’ lists) to symbolize the board and width and height variables (assuming these values could be something other than 6 x 7)

So our board class should have…..

•A variable data storing the two-dimensional array (list of lists), which is the game board •A variable height storing the number of rows on the game board •A variable width storing the number of columns on the game board

Problem 2: Connect 4 Board

And we’ll need methods to perform actions… __init__

, the constructor def __init__( self, width, height ): self.width = width self.height = height self.data = [] # this will be the board for row in range( self.height ): boardRow = [] for col in range( self.width ): boardRow += [' '] # add a space to this row self.data += [boardRow] # add this row to the board # do not need to return inside a constructor!

This is a constructor for Board objects that takes two arguments. This constructor takes in a number of columns and rows. The constructor will set the values of the data members of the object.

Problem 2: Connect 4 Board

And we’ll need methods to perform actions… __repr__

, for printing or any string representation

| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | -------------- 0 1 2 3 4 5 6

6 X 7 grid

def __repr__(self): #print out rows & cols s = '' # the string to return for row in range( self.height ): s += '|' # add the spacer character for col in range( self.width ): s += self.data[row][col] + '|' s += '\n' #print out separator (your code here) #print out numbers of each column #using mod if greater than 9, for spacing issues (your code here) return s # the board is complete, return it

Problem 2: Connect 4 Board

And we’ll need methods to perform actions…

•This method takes two inputs: the first input col represents the index of the column to which the checker will be added; the second input ox either an ‘X’ or ‘O’ •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. def addMove(self, col, ox ): #find the first row in the col without #a checker in it and #then add one there… #do this by checking values in self.data… >>> b = Board(7,6) >>> b.addMove(0, 'X') >>> b.addMove(0, 'O') >>> b.addMove(0, 'X') >>> b.addMove(3, 'O')

| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |X| | | | | | |X|O|O| | | | | -------------- 0 1 2 3 4 5 6

Problem 2: Connect 4 Board

And we’ll need methods to perform actions… clear

(self), should clear the board that calls it. Not much to say about clear( self ). It's useful, though!

delMove

(self, c) removes a checker from the board: This method should do the opposite of addMove. It should remove the top checker from the column c. If the column is empty, then delMove should do nothing. This function may not seem useful now, but it will become

very

useful when you try to implement your own Connect Four player.

Problem 2: Connect 4 Board

And we’ll need methods to perform actions… setBoard

, sets the board with the configuration you give it, useful for debugging… def setBoard( self, moveString ): 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' b.setBoard('023553')

| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |X| |O| | |O| |X|O| |X| | -------------- 0 1 2 3 4 5 6

Problem 2: Connect 4 Board

And we’ll need methods to perform actions… allowsMove

(self, c), for checking if a column is a legal move: This method should

return True

if the calling object (of type Board)

does

allow a move into column c. It

returns False

if column c is not a legal column number for the calling object. It also

returns False

if column c is full.

isFull

(self), checks if the board is full : This method should

return True

if the calling object (of type Board) is completely full of checkers. It should

return False

otherwise. Notice that you can leverage allowsMove to make this method very concise! Unless you're supernaturally patient, you'll want to test this on small boards:

Problem 2: Connect 4 Board

And we’ll need methods to perform actions… winsFor

(self, ox): checks if someone has won the game It should return True if there are four checkers of type ox in a row on the board. It should return False otherwise. One way to approach this is to consider each possible

anchor

checker that might start a four-in-a-row run. for example, all of the "anchors" that might start a horizontal run (going from left to right) must be in the columns

at least four places from the end of the board

. # 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 Important – Must check 1) Horizontally (shown here) 2) Vertically 3) Diagonally NE – SW 4) Diagonally NW - SE

Problem 2: Connect 4 Board

And we’ll need methods to perform actions… hostGame

(self): This method brings everything together into the familiar game. It should alternate turns between 'X' and 'O‘. It should ask the user to select a column number for each move.

Here are a few important points to keep in mind:

1) This method should print the board before prompting for each move. 2) After each input, you should check if the column chosen is a valid one.If invalid prompt for another move instead. 3) 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. 4) 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.)

Problem 2: Connect 4 Board

hostGame example…

Extra Credit Problem 3 Connect 4 Board

The Player class -- a preview

Well now that we have the board, lets have an automated player!

This player class will “examine” the board and determine the appropriate move to make The player will be programmed to look at the next move, or look up to several moves ahead

EC Problem 3: Connect 4 Board

Your Player class should have at least these three data members: ox

: A one-character string representing the checker, either 'X' or 'O', being used by the connect-four Player.

tbt:

A string, either 'LEFT', 'RIGHT', or 'RANDOM', representing the

tiebreaking type

of the player. This is the name for one of the three strategies to be described later.

ply

: A nonnegative integer representing how many moves into the future the player will look in order to evaluate possible moves.

EC Problem 3: Connect 4 Board

__init__

(self, ox, tbt, ply): This is a constructor for Player objects that takes three arguments.

__repr__

(self): This method returns a string representing the Player object that calls it. This should simply print the three important characteristics of the object: its checker string, its tiebreaking type, and its ply.

EC Problem 3: Connect 4 Board

class Player: """ an AI player for Connect Four """ def __init__( self, ox, tbt, ply ): """ the constructor """ self.ox = ox self.tbt = tbt self.ply = ply def __repr__( self ): """ creates an appropriate string """ s = "Player for " + self.ox + "\n" s += "with tiebreak type: " + self.tbt + "\n" s += "and ply == " + str(self.ply) + "\n\n" return s

Example

>>> p = Player('X', 'LEFT', 2) >>> p Player for X with tiebreak: LEFT and ply == 2 >>> p = Player('O', 'RANDOM', 0) >>> p Player for O with tiebreak: RANDOM and ply == 0

EC Problem 3: Connect 4 Board

oppCh

(self): This method should return the

other

kind of checker or playing piece, i.e., the piece being played by self's opponent. In particular, if self is playing 'X', this method returns 'O' and vice-versa. >>> p = Player('X', 'LEFT', 3) >>> p.oppCh() 'O'

EC Problem 3: Connect 4 Board

scoreBoard

(self, b): This method should return a

single

float value representing the score of the input b, which you may assume will be an object of type Board. This should return 100.0 if the board b is a win for self, 50.0 if it is neither a win nor a loss for self, and it should return 0.0 if it is a loss for self >>> b = Board(7,6) >>> b.setBoard( '01020305' ) >>> b >>> p = Player( 'X', 'LEFT', 0 ) >>> p.scoreBoard(b) 100.0

>>> Player('O', 'LEFT', 0).scoreBoard(b) 0.0

EC Problem 3: Connect 4 Board

tiebreakMove

(self, scores): This method takes in scores, which will be a nonempty list of floating-point numbers.

If there is only one highest score

in that scores list, this method should return

its COLUMN number

, not the actual score! If there is

more than one

highest score because of a tie, this method should return the

COLUMN number

of the highest score appropriate to the player's tiebreaking type. >>> scores = [0, 0, 50, 0, 50, 50, 0] >>> p = Player('X', 'LEFT', 1) >>> p2 = Player('X', 'RIGHT', 1) >>> p.tiebreakMove(scores) 2 >>> p2.tiebreakMove(scores) 5

EC Problem 3: Connect 4 Board

scoresFor

(self, b): This method is the heart of the Player class! Its job is to return a list of scores, with the cth score representing the "goodness" of the input board

after the player moves to column c

. And, "goodness" is measured by what happens in the game after self.ply moves.

EC Problem 3: Connect 4 Board

Base Case

If a particular column is full, it assigns a

-1.0

score for that column.

Another Base Case

Next, if the object's ply is 0, no move is made. What's more, the column, if not full, is evaluated for the self player. (Which method in the Player class will do this?) When self.ply is 0, this means that all of the non-full columns will have the same score. After all, this is to be expected if the player is not looking at all into the future.

And Another Base Case

If the game is already over, then there's no point in making any additional moves (indeed, it's not allowed!) -- simply evaluate the board and use that score for the current column under consideration.

Recursive Case

But, if the object's ply is greater than 0 and the game isn't over,

the code should make a move into the column that is under consideration

. This will use some of the methods in Board, such as addMove.

EC Problem 3: Connect 4 Board

0-ply scores for O : 1-ply scores for O: 2-ply scores for O : 3-ply scores for O :

b

‘X’ ‘O’

col 0

-1

col 0

-1

col 1

50

col 1

50

col 2

50

col 2

50

col 3

50

col 3

100

col 4

50

col 4

50

col 5

50

col 5

50

col 6

50

col 6

50

col 0

-1

col 0

-1

col 1

0

col 1

0

col 2

0

col 2

0

col 3

100

col 3

100

col 4

0

col 4

0

col 5

0

col 5

0

col 6

50

col 6

100

EC Problem 3: Connect 4 Board

nextMove

(self, b): This method takes in b, an object of type Board and returns an integer - namely, the column number that the calling object (of class Player) chooses to move to. This is the primary interface to Player, but it is really just a "wrapper" for the heavy lifting done by the other methods, particularly scoresFor. Thus, nextMove should use scoresFor and tiebreakMove to return its move. def nextMove( self, b): #get list of scores’’’ # use self.tiebreakMove(L) to figure out which column to use

EC Problem 3: Connect 4 Board

playGame(self, px, po): Copy and change your hostGame method from hw6pr2 in order to create this playGame method. playGame does just that -- it calls on the nextMove method in px and po, which will be objects of type Player in order to play a game. One additional twist: you should handle the case in which either px or po is the string 'human' instead of an object of type Player. In this 'human' case, the playGame method should simply puase and ask the user to input the next column to move for that player, with error-checking just as in hostGame.

EC Problem 3: Connect 4 Board Hints for playgame…

Using nextMove to add the best move

… self.addMove(po.nextMove(self), nextCheckerToMove)

Testing if won:

if self.winsFor( nextCheckerToMove )