250 likes | 380 Views
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.
E N D
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 # 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. 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
Problem 2: Connect 4 Board And we’ll need methods to perform actions… __repr__, for printing or any string representation 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 | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | --------------- 0 1 2 3 4 5 6 6 X 7 grid
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. >>> b = Board(7,6) >>> b.addMove(0, 'X') >>> b.addMove(0, 'O') >>> b.addMove(0, 'X') >>> b.addMove(3, 'O') 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… | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |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 • Horizontally (shown here) • Vertically • Diagonally NE – SW • 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 ThePlayerclass -- 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.
b EC Problem 3: Connect 4 Board ‘X’ ‘O’ col 0 col 1 col 2 col 3 col 4 col 5 col 6 0-ply scores for O: 50 50 -1 50 50 50 50 col 0 col 1 col 2 col 3 col 4 col 5 col 6 1-ply scores for O: -1 50 50 50 50 100 50 col 0 col 1 col 2 col 3 col 4 col 5 col 6 2-ply scores for O: 0 0 -1 0 100 0 50 col 0 col 1 col 2 col 3 col 4 col 5 col 6 3-ply scores for O: 0 0 0 -1 0 100 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 )