Chapter 6, “Using Polymorphism via Interfaces.” Some functionality differs enough that it needs explanation, though.
The constructor for the game class takes a form variable and two ReversiPlayer variables as parameters, and it immediately assigns these to private variables so they can be accessed throughout the game:
Public Class ReversiGame Private aPieces(7, 7) As ReversiPiece Private FPlayer1 As ReversiPlayer Private FPlayer2 As ReversiPlayer Public Sub New(ByVal f As System.Windows.Forms.Form, _ ByVal p1 As ReversiPlayer, ByVal p2 As ReversiPlayer) MyBase.New() pForm = f FPlayer1 = p1 FPlayer2 = p2 If TypeOf FPlayer1 Is HumanReversiPlayer Then AddHandler CType(FPlayer1, HumanReversiPlayer).MyMoveLoc, _ AddressOf MoveToLocation ElseIf TypeOf FPlayer1 Is NetworkReversiPlayer Then AddHandler CType(FPlayer1, NetworkReversiPlayer).MyMoveLoc, _ AddressOf MoveToLocation ElseIf TypeOf FPlayer1 Is ComputerReversiPlayer Then AddHandler CType(FPlayer1, _ ComputerReversiPlayer).MyMakeBestMove, _ AddressOf MakeBestMove End If If TypeOf FPlayer2 Is HumanReversiPlayer Then AddHandler CType(FPlayer2, HumanReversiPlayer).MyMoveLoc, _ AddressOf MoveToLocation ElseIf TypeOf FPlayer2 Is NetworkReversiPlayer Then AddHandler CType(FPlayer2, NetworkReversiPlayer).MyMoveLoc, _ AddressOf MoveToLocation ElseIf TypeOf FPlayer2 Is ComputerReversiPlayer Then AddHandler CType(FPlayer2, _ ComputerReversiPlayer).MyMakeBestMove, _ AddressOf MakeBestMove End If StartGame() End Sub
You might notice that I’m not worried about coupling in the game class like I was in the player class. I’ve made the game class dependent on the player classes by passing them into the constructor. You should understand that coupling isn’t really a two-way street; I’ve kept the player class decoupled from the game class even though the game class is coupled to the player class.
Given the choice, the player class is better left independent because the potential for reuse is greater than it is for the game class. The game class seems to be much more specific to implementing a specific type of game, with its two-dimensional array of piece classes and so forth. Of course, you could employ some other design strategies to improve on the design even more. You could go back to abstracting the game and piece classes using an interface as in Chapter 6, “Using Polymorphism via Interfaces,” and then coupling the interfaces together rather than the specific classes. This might allow for better extensibility in future projects.
The large If..Then block that makes up the remainder of the constructor determines what types of players are playing this game and then attaches appropriate event handlers to these players based on their classes. For example, if either player is a computer player, then the MyMakeBestMove event is attached to a method within the game class called MakeBestMove. Human and network players have their MyMoveLoc events attached to a method named MoveToLocation, which handles placing a piece on the board, given an x, y coordinate.
Attaching event handlers between the player and game classes is how you achieve communication between them without having them tightly coupled. An instance of the player class doesn’t know that its event handlers are being attached to methods in a game class. All the player class is instructed to do at the appropriate time is to raise the event. Whatever has attached itself to this event will receive the information and do with it what it chooses.
The human and network players raise an event when it’s time to place a piece on the board. This event handler is attached to a method in the game class named MoveToLocation:
Private Overloads Sub MoveToLocation( _ ByVal oPlayer As ReversiPlayer, _ ByVal x As Integer, ByVal y As Integer) Dim aP As ReversiPiece aP = PieceLandedOn(x, y) If aP Is Nothing Then Exit Sub 'didn't land on a square MoveToLocation(oPlayer, aP) End Sub Private Overloads Sub MoveToLocation( _ ByVal oPlayer As ReversiPlayer, _ ByVal aP As ReversiPiece) If aP.UnOccupied Then If CanMoveHere(aP, oPlayer) Then MoveHere(aP, oPlayer) If TypeOf OtherPlayer(oPlayer) Is NetworkReversiPlayer Then CType(OtherPlayer(oPlayer), _ NetworkReversiPlayer).SendMyTurnToOpponent(aP) End If pForm.Invalidate() Application.DoEvents() Call CalcScores() Else RaiseEvent BadMove() Exit Sub End If If Not CheckIfGameOver() Then If PlayerCantMoveAnywhere(OtherPlayer(oPlayer)) Then RaiseEvent RepeatingTurn(oPlayer) oPlayer.MyTurn = True 're-fires event Else oPlayer.MyTurn = False OtherPlayer(oPlayer).MyTurn = True End If Else oPlayer.MyTurn = False OtherPlayer(oPlayer).MyTurn = False End If Else RaiseEvent BadMove() End If End Sub
There are actually two MoveToLocation methods in the game class, which is legal in a “real” object-oriented language as long as each like-named method has a different parameter list so the compiler can differentiate them. Having like-named methods on a class is known as overloading the method. Visual Basic .NET requires the Overloads keyword on each overloaded method, as you can see in the code.
The first, shorter MoveToLocation method merely determines which piece was clicked on based on the passed-in x, y coordinate pair by using a private method named PieceLandedOn. This method returns a ReversiPiece object instance. Once it finds out which piece was clicked, the first MoveToLocation method calls the second one, passing in that ReversiPiece instance.
The second, longer MoveToLocation method determines first if the passed-in piece instance represents a valid move for the passed-in player instance. For a piece to represent a valid move, it must first be unoccupied, and of course at least one opponent tile must be “flipped over” using the rules of the game. These rules are defined in the CanMoveHere method, which was shown in Chapter 6, “Using Polymorphism via Interfaces” (specifically, see Listing 6-11).
If the move is indeed valid, then the program makes the move. At this point, the program checks to see if the other player (the one not making this current move) is a network player. If so, then the program must notify that player of this current move so that instance of the game can update its board. A function named OtherPlayer is called often in this code and other code in the class. This method takes a player instance as a parameter and simply returns the player instance (stored in the private variables FPlayer1 and FPlayer2) that isn’t equal to that parameter.
After the program makes the move, it checks to see if the game is over. If not, then the program determines who gets to move next. Most of the time, player 2 will get to move next if player 1 just moved. However, if player 2 doesn’t have avalid move, then player 1 gets to move again. If neither player has a valid move, then the game is over.
The computer player class has an event wired up to a method named MakeBestMove to determine where to move next:
Private Sub MakeBestMove(ByVal oPlayer As ReversiPlayer) Dim aP As ReversiPiece Dim iScore As Integer Dim iHigh As Integer = -1 Dim aPHigh As ReversiPiece PushBoard() For Each aP In aPieces If aP.UnOccupied Then If CanMoveHere(aP, oPlayer) Then MoveHere(aP, oPlayer) iScore = BoardValue(oPlayer) If iScore > iHigh Then iHigh = iScore aPHigh = aP End If PopBoard() End If End If Next System.Threading.Thread.Sleep(1000) 'wait a sec If iHigh > 1 Then MoveToLocation(oPlayer, aPHigh) End If End Sub
This code works by checking every unoccupied location on the board and then moving there to see what the “board score” would be after flipping over all the opponent pieces. This code then selects the move resulting in the best board score as the place to move.
Before trying out moves, the state of the board is saved (pushed) into place, and after each move is attempted, the board is restored (popped) back to its original state. Finally, once the code determines the best move, it makes that move by calling the friendly old MoveToLocation method.
| Note |
A “smarter” move selection algorithm would travel more moves into the future. For example, the computer could determine which move would result in the worst possible board score for the human player on her next turn, even if that player selected her best possible move. This algorithm is called the minimax algorithm. |