Learn VB .NET Through Game Programming [Electronic resources]

Matthew Tagliaferri

نسخه متنی -صفحه : 106/ 55
نمايش فراداده

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.