Chapter 6, “Using Polymorphism via Interfaces.” Some functionality differs enough that it needs explanation, though.
Creating the Constructor
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.
Creating the MoveToLocation Methods
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.
Setting Up the Computer Moves
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. |