Learn VB .NET Through Game Programming [Electronic resources]

Matthew Tagliaferri

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

Using More Complex Interfaces

All of the interfaces discussed to this point have contained a single member, but of course more complicated interfaces are possible. The game sample in this chapter consists of two-player games where the computer controls one of the players.

How would you declare the functionality for an interface that controls a two-player game? Such an interface would need to select a legal move and make it. It would also need to know if a move that the human opponent attempted was illegal so that it could thusly notify the player. It would need to be able to draw itself. Furthermore, it would probably need to be able to notify the outside caller if the game was over. It may also need to be able to relate the current score of the game, if it were the type of game where a running score was useful.

In coming up with the design for the two-player game interface, I stumbled across a second need for an interface. The game pieces for each game could probably be expressed via an interface. Each piece, for example, would probably need to have a location on the game board. In addition, it should be able to draw itself. Game pieces often have some sort of value (cards have face values and suits, Tic-Tac-Toe pieces are Xs or Os, and chess pieces are one of a finite type of piece).

Creating the Base IPCOpponent Interfaces

Without further delay, Listing 6-5 shows the two interfaces that you’ll use to create the two-player games in the sample program. You can find this code in the PCOpponent folder as part of the source code that accompanies this book.

Listing 6.5: Game Interfaces

Public Interface IPCOpponentGame 
Property pForm() As Form 
Event BadMove() 
Event PlayerWon() 
Even t ComputerWon() 
Event NobodyWon() 
Event CurrentScore(ByVal iPlayer As Integer, _ 
ByVal iComputer As Integer) 
Sub StartGame() 
Sub DrawBoard(ByVal sender As Object, _ 
ByVal e As System.Windows.Forms.PaintEventArgs) 
Sub MouseDown(ByVal sender As Object, _ 
ByVal e As System.Windows.Forms.MouseEventArgs) 
Sub MakeMove() 
End Interface 
Public Interface IPCOpponentGamePiece 
Property Value() As Integer 
Property Location() As Point 
Property Size() As Size 
Function MouseIn(ByVal x As Integer, _ 
ByVal y As Integer) As Boolean 
Sub Draw(ByVal g As Graphics) 
End Interface 

As promised earlier, these two interfaces each contain more than one member in them. Not only that, but the IPCOpponentGame interface contains at least one of each type of member—one property, five events, and four methods. The IPCOpponentGamePiece interface contains properties and methods but no events.

One of the first things you might notice about the interface members is that no access modifiers are listed for any of them. In other words, none of the members are declared as private, protected, friend, or public. In fact, if you try and put any of these access modifiers on one of the members, Visual Studio barks at you with a somewhat vague error:

C:\vbNetGames\PCOpponent\PCOpponent.vb(20): 'Protected' is not valid 
on an interface method declaration. 

The reason you can’t put access modifiers on interface member declarations is because interfaces describe only public functionality. It makes no sense to declare a private member as part of an interface because that member wouldn’t be visible to an outside user of the class implementing the interface anyway. If it’s not visible, then why make it part of an interface to the outside world? The same goes for any protected members of a class—these members aren’t available to the outside world anyway, so it doesn’t make sense to include them in an interface that’s meant to describe class functionality to the outside world.

One distinction, though: An interface may itself be declared as public, private, protected, or friend. The interface access modified describes what parts of the program may use that interface to declare new classes. However, all members of all interfaces are assumed public, so access modifiers on these members are redundant (not only that, they’re superfluous and unnecessary, too).

Creating the IPCOpponentGame Interface

Table 6-1 describes what each member of the IPCOpponentGame interface should accomplish once it’s implemented in a class.

Table 6-1: Members of the IPCOpponentGame Class

MEMBER NAME

TYPE

FUNCTION

pForm

Property

Acts as the “parent” for the game and its drawing surface

BadMove

Event

Fires when the player makes an illegal move

PlayerWon

Event

Fires when the human player wins the game

ComputerWon

Event

Fires when the computer player wins the game

NobodyWon

Event

Fires in the case of a tie

CurrentScore

Event

Fires to display score of each player

StartGame

Method

Clears the board to starting state and initializes pieces

DrawBoard

Method

Renders the board and all the pieces

OnMouseDown

Method

Called when the user clicks the parent form

MakeMove

Method

Causes the computer player to take a turn

All games that implement this interface require a form to act as the game’s drawing surface. The property pForm references this form. The various events return status information to the program that instantiates the game class. This allows the program to display scores or act appropriately in a game-ending condition.

The methods on the game class handle the bulk of the game functionality. The method StartGame sets up all of the board and piece information that the game requires. It may also be responsible for clearing any board and piece information from the last game played. The method DrawBoard renders the current board onto the form. The method MakeMove is what the class calls when it’s time for the computer player to make a move. Finally, the OnMouseDown method serves as the event handler that executes when the player clicks onto the game surface.

Using Private Methods in a Public Interface

Technically, I didn’t have to include the DrawBoard, OnMouseDown, and MakeMove methods in the IPCOpponentGame interface because the outside program in this chapter doesn’t access any of them directly. Each of these methods is called privately in the two game implementations that implement the interface.

I finally chose to include them in the interface for two reasons. The first was that I thought certain situations might benefit from having these methods callable from outside the class. For example, each of the games I’m about to explain waits for the human player to make the first move and then calls the MakeMove method internally after recording the player’s move. A useful extension might be to allow the computer opponent to move first, which may be implemented by exposing the MakeMove method to the outside world. The second reason I decided to include these methods in the interface was to complete the game functionality. I’ve decided that any two-player game written against this interface is going to need to implement a MakeMove method, whether that method ends up being public or not. By including it in the interface, I gain the benefit of Visual Studio’s design-time errors if I forget to implement the method.

Creating the IPCOpponentGamePiece Interface

The second interface describes the game piece that you must implement to write a two-player game. Table 6-2 describes the members of that interface.

Table 6-2: Members of the IPCOpponentGamePiece Class

MEMBER NAME

TYPE

FUNCTION

Value

Property

Represents the value of the piece (the player color, the suit, and so on).

Location()

Property

Represents the coordinates where the piece lives on the board.

Size()

Property

Represents the size of the piece when drawn (most pieces are all the same size in one game).

MouseIn

Method

Returns True if passed-in coordinates lie within the boundary of the piece. This determines if this piece was clicked.

Draw

Method

Renders this piece.

The IPCOpponentGamePiece class contains just enough information to render itself onto a surface and determine if a player has clicked it. Notice that there’s no information about how a piece is rendered onto the game surface—using bitmaps such as the dice game used in previous chapters or using Graphics Device Interchange, Plus (GDI+) calls such as the tile games used in Chapter 3, “Understanding Object-Oriented Programming from the Start.” The word how refers to implementation, and of course interfaces are devoid of implementation. Thus, the how doesn’t get answered until the classes are written that implement the interface in question. The next section describes the first implementation.

Creating the Tic-Tac-Toe Implementation

You’ve now defined the interfaces—it’s time to implement them and create a working two-player game. You’ll develop a simple Tic-Tac-Toe implementation for the first game, mainly so the second, more complicated game becomes a point of comparison on how you can implement quite different games using the same interface. Figure 6-2 shows the Tic-Tac-Toe game in action.

Figure 6-2. Tic-Tac-Toe

Creating the TicTacToePiece Class

Listing 6-6 shows the TicTacToePiece class, the first to be discussed. This class implements the IPCOpponentGamePiece class, as you can see from the first lines of the listing.

Listing 6.6: The TicTacToePiece Class

Public Class TicTacToePiece 
Implements IPCOpponentGamePiece 
Private FLocation As Point 
Public Property Location() As System.Drawing.Point _ 
Implements IPCOpponentGamePiece.Location 
Get 
Return FLocation 
End Get 
Set(ByVal Value As System.Drawing.Point) 
FLocation = Value 
End Set 
End Property 
Private FSize As Size 
Public Property Size() As System.Drawing.Size _ 
Implements IPCOpponentGamePiece.Size 
Get 
Return FSize 
End Get 
Set(ByVal Value As System.Drawing.Size) 
FSize = Value 
End Set 
End Property 
Private FValue As Integer 
Public Property Value() As Integer _ 
Implements IPCOpponentGamePiece.Value 
Get 
Return FValue 
End Get 
Set(ByVal i As Integer) 
If i < -1 Or i > 1 Then 
Throw New Exception("Invalid Piece Value") 
Else 
FValue = i 
End If 
End Set 
End Property 
Public Function MouseIn(ByVal x As Integer, _ 
ByVal y As Integer) As Boolean _ 
Implements IPCOpponentGamePiece.MouseIn 
Dim r As New Rectangle(Location, Size) 
Return r.Contains(x, y) 
End Function 
Public Sub Draw(ByVal g As System.Drawing.Graphics) _ 
Implements IPCOpponentGamePiece.Draw 
Dim r As New Rectangle(Location, Size) 
Dim p As New PointF(Location.X, Location.Y) 
Dim f As New Font("Tahoma", 36, FontStyle.Bold) 
r.Inflate(-2, -2) 
g.DrawRectangle(Pens.White, r) 
p.X += 28 
p.Y += 20 
Select Case Value 
Case -1 
g.DrawString("O", f, Brushes.Blue, p) 
Case 1 
g.DrawString("X", f, Brushes.Red, p) 
Case Else 
Exit Sub 
End Select 
End Sub 
End Class 

The first thing you should notice about Chapter 2, “Writing Your First Game, Again,” if you’re feeling forgetful). The only tidbit of note in these three properties is the error checking logic in the Value property, which makes sure the passed-in value is in the range of –1 to 1 or else an exception is thrown. The game uses the value –1 to represent an O, 0 to represent a blank square, and 1 to represent an X. The reason you’re using these values will become apparent when the next section explains the game logic.

The MouseIn routine is interesting because you get to use some great .NET Framework class functionality to determine if a passed-in coordinate falls within the boundaries of this piece. The trick is to take the Location property (which is of type Point) and the Size property (of type Size), and construct a .NET Framework Rectangle class from this information. The Rectangle class has a constructor that accepts Point and Size parameters and returns a rectangle at that position and size. With such a rectangle constructed, you can then call the Contains method on the Rectangle class, which accepts an x and y coordinate as parameters and returns True if that coordinate is within the bounds of the rectangle. Two short, sweet lines of code to implement a hit test on the game piece!

The last method on the TicTacToePiece class is the Draw method. This method also uses a Rectangle class created from the location and size of this piece as specified by the two similarly named properties. This time, however, a method named Inflate is called to shrink the rectangle by two pixels along both the height and width, and then the rectangle is drawn in white pen on the drawing surface.

Tip

The method Inflate enlarges a rectangle when called with positive integer coordinates and shrinks the rectangle when called with negative coordinates, as in this example.

After the program draws the rectangle around the border of the piece, it draws the X or O in place using the DrawString method on the Graphics class (an instance of the Graphics class is passed into the Draw method). The DrawString method requires as parameters the string to draw (an X or O in this case), font information (encapsulated in the .NET Framework Font class), a Brush instance (which determines the color or pattern of the string), and a location in the form of a PointF class instance. The PointF class differs from the Point class in that it holds floating-point values. For some reason, the DrawString method can’t accept an integer-based Point class, so this program constructs a PointF class using the piece’s location information and passes this as the last parameter of the DrawString method.

Creating the TicTacToeGame Class

The class that implements the Tic-Tac-Toe game is a bit more complex than the TicTacToePiece class, so the following sections cover it on a member-by-member basis. The game class consists of both members to implement the IPCOpponentGame interface, as well as some additional private members to provide support functionality to the game.

Investigating the Declaration and Private Stuff

The game class implements a number of private members to handle piece storage and score evaluation. The following code shows these members:

Public Class TicTacToeGame 
Implements IPCOpponentGame 
Private aPieces As ArrayList 
Private FComputerWon As Integer 
Private FPlayerWon As Integer 
Private Function RowScore(ByVal i As Integer, _ 
ByVal j As Integer, ByVal k As Integer) As Integer 
Dim aPi, aPj, aPk As TicTacToePiece 
aPi = aPieces.Item(i) 
aPj = aPieces.Item(j) 
aPk = aPieces.Item(k) 
Return aPi.Value + aPj.Value + aPk.Value 
End Function 
Public Function RowScoreExists(ByVal iScore) As Boolean 
Return RowScore(0, 1, 2) = iScore OrElse _ 
RowScore(3, 4, 5) = iScore OrElse _ 
RowScore(6, 7, 8) = iScore OrElse _ 
RowScore(0, 3, 6) = iScore OrElse _ 
RowScore(1, 4, 7) = iScore OrElse _ 
RowScore(2, 5, 8) = iScore OrElse _ 
RowScore(0, 4, 8) = iScore OrElse _ 
RowScore(2, 4, 6) = iScore 
End Function 
Private Function HasPlayerWon() As Boolean 
Return RowScoreExists(3) 
End Function 
Private Function HasComputerWon() As Boolean 
Return RowScoreExists(-3) 
End Function 
Private Function EmptySpots() As Integer 
Dim aP As TicTacToePiece 
Dim r As Integer = 0 
For Each aP In aPieces 
If aP.Value = 0 Then 
r += 1 
End If 
Next 
Return r 
End Function 

The first part of the class declares a few private variables. The class defines the often-used Arraylist to hold the nine instances of the TicTacToePiece objects that the game will require. Additionally, the class declares integer variables named FComputerWon and FPlayerWon to hold the number of times each player wins a game. These variables will be passed back to the declarer of the class in the form of an event so that the score can be recorded.

The remaining private functions are all related to determining the state of the board. The function RowScore returns the summed values of three pieces on the board. For example, if all three locations are empty, this method returns 0. If all three passed-in locations contain an X, this function returns a 3. (X is represented by the value 1 in this game.) A row with one X, one O, and one empty spot returns 0 ( 1 + –1 + 0 = 0). The method RowScoreExists determines if any one of the eight possible places on the board that one can achieve three in a row contain a passed-in score. In other words, if the program wants to see if the board contains a situation where X has two in a row and the third location is empty (meaning X could win on the next turn), the program could call RowScoreExists(2) and look for a True result.

The methods HasComputerWon and HasPlayerWon are simple extensions of the RowScoreExists method, looking for the score that indicates the X player (3) or the O player (–3) has won the game.

The last private method is EmptySpots, which merely returns the number of blank squares on the board. This method determines if the game has ended in a tie. If EmptySpots returns 0, if HasComputerWon is False, and if HasPlayerWon is False, then the game has ended in a draw.

Investigating the Interface Events

The following code shows the interface events of the game class:

Public Event BadMove() Implements IPCOpponentGame.BadMove 
Public Event PlayerWon() Implements IPCOpponentGame.PlayerWon 
Public Event ComputerWon() Implements IPCOpponentGame.ComputerWon 
Public Event NobodyWon() Implements IPCOpponentGame.NobodyWon 
Public Event CurrentScore(ByVal iPlayer As Integer, _ 
ByVal iComputer As Integer) _ 
Implements IPCOpponentGame.CurrentScore 

You implement the events in a desired interface simply by declaring them. Once you’ve declared them, you can raise them using the RaiseEvent keyword. You’ll see each of the events being raised in the implemented members that follow.

Investigating the StartGame Method

The following code shows the StartGame method of the game class:

Public Sub StartGame() Implements IPCOpponentGame.StartGame 
Const WID = 104 
Dim i As Integer 
Dim aP As TicTacToePiece 
aPieces = New ArrayList 
For i = 0 To 8 
aP = New TicTacToePiece 
aP.Location = New Point((i Mod 3) * WID, (i \ 3) * WID) 
aP.Size = New Size(WID, WID) 
aP.Value = 0 
aPieces.Add(aP) 
Next 
End Sub 

A new Tic-Tac-Toe game starts using the StartGame method. This method instantiates nine game piece classes and stores them in the private ArrayList named aPieces. Each piece has the same size and is placed into its proper spot in the 33 game grid. Also, each piece’s Value is set to 0, representing blank in this game.

Investigating the Drawing Code

The following code shows the drawing of the game class:

Public Sub DrawBoard(ByVal sender As Object, _ 
ByVal e As System.windows.forms.PaintEventArgs) _ 
Implements IPCOpponentGame.DrawBoard 
Dim aP As TicTacToePiece 
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias 
e.Graphics.FillRectangle(Brushes.Black, pForm.ClientRectangle) 
For Each aP In aPieces 
aP.Draw(e.Graphics) 
Next 
End Sub 

Because you’ve followed the maxim that an object should remain responsible for itself, drawing the board becomes a simple matter of asking each game piece to draw itself. The DrawBoard method, which is the implementation of the game interface, sets up the Graphics class to draw in an anti-aliased mode, fills the surface in black, and then calls the Draw method on each TicTacToePiece class found in the Arraylist.

Investigating the Method MakeMove

The following code shows the MakeMove method of the game class:

Public Sub MakeMove() Implements IPCOpponentGame.MakeMove 
Dim i As Integer 
Dim aP As TicTacToePiece 
'try every blank spot with me. See if I would win there 
For Each aP In aPieces 
If aP.Value = 0 Then 
aP.Value = -1 
If HasComputerWon() Then        'i win 
Exit Sub 
End If 
aP.Value = 0 
End If 
Next 
'try every blank spot with him. See if HE would win there 
For Each aP In aPieces 
If aP.Value = 0 Then 
aP.Value = 1 
If HasPlayerWon() Then        'player would win here. move there 
aP.Value = -1 
Exit Sub 
End If 
aP.Value = 0 
End If 
Next 
'try every blank spot with him. See if he would win there in 2 moves 
'try the center first (spot 4), though 
For i = 4 To aPieces.Count - 1 
aP = aPieces.Item(i) 
If aP.Value = 0 Then 
aP.Value = 1 
If RowScoreExists(2) Then        'player wins in 2 moves. move there 
aP.Value = -1 
Exit Sub 
End If 
aP.Value = 0 
End If 
Next 
For i = 0 To 3 
aP = aPieces.Item(i) 
If aP.Value = 0 Then 
aP.Value = 1 
If RowScoreExists(2) Then        'player wins in 2 moves. move there 
aP.Value = -1 
Exit Sub 
End If 
aP.Value = 0 
End If 
Next 
Debug.Assert(False, "Beep") 
End Sub 

The MakeMove method is where the logic exists so that the computer finds the best move available and puts his piece there. Granted, this isn’t the world’s greatest Tic-Tac-Toe computer opponent ever written. It uses the following simple algorithm to choose where to move:

If an empty spot exists that would result in winning the game by moving there, then move there.

If an empty spot exists that would result in the opponent winning by moving there, then move there to block the opponent.

If an empty spot exists that would result in the opponent having two in arow with the third space being empty, then move there (and give preference to the center square over others).

Each of the three subcomponents of the described algorithm are implemented as a For..Each loop in the MakeMove algorithm. Each loop works by checking for an empty spot. When it finds one, the algorithm temporarily puts an X or O (1 or –1) into the spot, checks the state of the board, and then removes that temporary piece if it doesn’t find the desired result.

The last part of the algorithm (looking for rows with one opponent piece and two blanks) comes in two For..Each loops so the fourth location (the center square) can be checked first. This makes the algorithm favor the center square when blocking because of that square’s strategic importance.

The last line in the MakeMove method is a Debug.Assert that will stop the program if the code ever reaches this line. The algorithm is arranged in such a way that the program will always find a move using the three previous cases, and an Exit Sub ensures that the code leaves the method as soon as the program finds a proper move. If execution ever reaches the end of the method without finding a proper move, then the algorithm isn’t working properly, and Debug.Assert(false) will halt execution.

Investigating the OnMouseDown Method

The following code shows the OnMouseDown method of the game class:

Public Sub OnMouseDown(ByVal sender As Object, _ 
ByVal e As System.Windows.Forms.MouseEventArgs) _ 
Implements IPCOpponentGame.OnMouseDown 
Dim aP As TicTacToePiece 
Try 
'don't let him click again 
RemoveHandler fForm.MouseDown, AddressOf OnMouseDown 
For Each aP In aPieces 
If aP.MouseIn(e.X, e.Y) Then 
If aP.Value = 0 Then 
aP.Value = 1 
pForm.Invalidat e() 
If HasPlayerWon() Then 
FPlayerWon += 1 
RaiseEvent PlayerWon() 
RaiseEvent CurrentScore(FPlayerWon, FComputerWon) 
Exit For 
Else 
If EmptySpots() = 0 Then 
RaiseEvent NobodyWon() 
RaiseEvent CurrentScore(FPlayerWon, FComputerWon) 
Exit For 
Else 
MakeMove() 
pForm.Invalidate() 
If HasComputerWon() Then 
FComputerWon += 1 
RaiseEvent ComputerWon() 
RaiseEvent CurrentScore(FPlayerWon, FComputerWon) 
End If 
Exit For 
End If 
End If 
Else 
RaiseEvent BadMove() 
Exit For 
End If 
End If 
Next 
Finally 
AddHandler fForm.MouseDown, AddressOf OnMouseDown 
End Try 
End Sub 

The method OnMouseDown runs when the player clicks somewhere on the form. The first action is to temporarily remove the OnMouseDown handler from the form so that additional clicks don’t happen until the processing of this click is complete. Next, a loop starts to locate which piece within the game was clicked. When the MouseIn method locates that piece, the program first checks the piece to see if it’s empty (one can’t move onto a spot that’s already taken). If this piece isn’t empty, then the program raises the BadMove event. If the spot is empty, however, then the work begins.

The empty, clicked-on spot has its value changed from 0 to 1. If this results in the player winning, the program adds 1 to the variable that tracks player wins and then fires two methods. The first method indicates that the player has won, and the second relays the current score to the calling program. If the player doesn’t win with this move, then the computer player can make a move. This happens with the already-discussed MakeMove method. After the computer player makes a move, the program checks the board to see if the computer player has won. If it has, the program updates the score and calls the two proper methods.

The Finally block makes sure this procedure is reattached to the MoveDown event of the form no matter what has happened in the code beforehand.

Investigating the pForm Property and Constructor

The following code shows the pForm property and constructor of the game class:

Public Sub New(ByVal f As System.Windows.Forms.Form) 
MyBase.New() 
pForm = f 
End Sub 
Private fForm As Form 
Property pForm() As Form _ 
Implements IPCOpponentGame.pForm 
Get 
Return fForm 
End Get 
Set(ByVal Value As Form) 
fForm = Value 
fForm.Width = 324 
fForm.Height = 384 
AddHandler fForm.Paint, AddressOf DrawBoard 
AddHandler fForm.MouseDown, AddressOf OnMouseDown 
End Set 
End Property 

The last member to discuss is the pForm member, which references some form in the calling program. The pForm variable is set as in the class constructor (it could also be set outside the class). Once set, the width and height of the form are coded to set values, and the Paint and MouseDown events are linked to event handlers declared as part of this class. This is the thing that makes sure the OnMouseDown code runs when the outside form is clicked.

Creating the Reversi Implementation

The interface example contains two games, the Tic-Tac-Toe implementation just discussed and an implementation of Reversi (or Othello). You can see the Reversi game in action in Figure 6-3. Before looking at any of the code for this game, though, you should already have a pretty good idea of how this game will be structured. Because the game will contain implementations of the PCOpponentGame and PCOpponentGamePiece interfaces, you already have a good idea of what the classes that implement those interfaces look like from the first example.

Figure 6-3. A rousing game of Reversi

This isn’t anything revolutionary, of course, but the fact that you can obtain a good mental picture about the structure of a class that you’ve never seen is what interfaces are all about. Interfaces create expectations. In particular, they create expectations about exactly what members need to be implemented by a class. In a more abstract way, they create an expectation of a certain set of functionality. You now know that if this book presents a program that implements the PCOpponentGame and PCOpponentGamePiece interfaces, then you’ll have a two-player game program, with one human and one computer-controlled player. You can assume all of that just by learning that a class is an implementation of an interface.

The nice thing about the Reversi implementation is that much of the code is similar (or even identical) to the Tic-Tac-Toe game, so this section won’t repeat it. Of course, hearing that two classes have identical code should start the warning bells flashing in your head. If two classes share the same code, why not go with an inheritance scheme and put the shared code in a base class? Furthermore, if you’re going to go with inheritance, then do you really need the interface at all?

The answer in the short term is “probably not,” but the developer often has to look beyond the current project and into the future. Even if the first two implementations of the interface end up sharing code, this won’t necessarily hold true for future implementations of the game. Say you write a game that uses DirectX, for example—you certainly won’t be able to share any drawing code with the drawing code found in these simple GDI+ games.

Note

Still, I can’t say for certainty that the interface solution is better than a class hierarchy, just as I can’t say that the walls in my bedroom are better in beige than they are in red or that my living room arrangement is better with the couch against the far wall than with it on a diagonal. These are all design issues, and there often isn’t a “right” or “best” solution. You say to-may-to, I say to-mah-to; you say inheritance, I say interfaces.

Creating the ReversiPiece Implementation

Most of the interface members of the ReversiPiece class are identical to the TicTacToePiece class, so this section won’t duplicate the effort of explaining those members again. The Draw method is a bit different because the Reversi pieces are simply colored squares, but this code is all stuff you’ve seen before. The first place that the Reversi piece differs from its Tac-Tac-Toe counterpart is in some functionality to tell where on the board an instance of this class is located. You can see this functionality in Listing 6-7.

Listing 6.7: Location-Specific Code in the ReversiPiece Class

ReadOnly Property xElt() As Integer 
Get 
Return Location.X \ Size.Width 
End Get 
End Property 
ReadOnly Property yElt() As Integer 
Get 
Return Location.Y \ Size.Height 
End Get 
End Property 
Public Function IsEdge() As Boolean 
Return xElt = 0 OrElse yElt = 0 _ 
OrElse xElt = 7 OrElse yElt = 7 
End Function 
Public Function IsCorner() As Boolean 
Return (xElt = 0 And yElt = 0) OrElse _ 
(xElt = 0 And yElt = 7) OrElse _ 
(xElt = 7 And yElt = 0) OrElse _ 
(xElt = 7 And yElt = 7) 
End Function 

The properties xElt and yElt define the location of this piece on the board, with location 0,0 being the upper-left corner. The IsEdge and IsCorner methods use these coordinates to determine if a piece is along the edge of a board or in one of the four corners. Edge and corner pieces are more valuable in Reversi, so it’s important that you can find these pieces on the board when trying to make the best move.

The IsEdge and IsCorner methods both use the OrElse operator, which is new to Visual Basic .NET. OrElse is a Boolean operator that usually provides the same functionality as the standard Or operator, but it uses a short-circuiting logic to do so. What this means is that if the left side of an OrElse statement is True, then the right-side evaluation is skipped altogether. Skipping the evaluation of part of an expression might result in a faster program, especially if this expression is evaluated many times in a loop.

The second new functionality provided by the ReversiPiece class is the ability to “save” the value of the piece and to then restore it. It accomplishes this using simple push and pop routines, shown in Listing 6-8.

Listing 6.8: State-Saving Code in the ReversiPiece Class

Private FSaveValue As Integer 
'store prior value 
Public Sub PushValue() 
FSaveValue = Value 
End Sub 
Public Sub PopValue() 
Value = FSaveValue 
End Sub 

There’s nothing fancy here, just a private variable to store the value of this piece and routines to save the current value into the private spot and restore it from that same spot. This functionality becomes important when evaluating potential moves because the program actually makes each available move to determine how good that move is, restoring the board back to its current state between each attempt.

There’s one final point about the game piece to make here—you might have noticed that this new functionality doesn’t have anything to do with the original interface. Is this legal? That is, can a class implement an interface and provide functionality beyond that interface? The answer is an unqualified “yes.” An interface is meant to provide some subset of functionality, but the classes implementing that functionality may do much more than what the interface suggests. Think back to the tile games in Chapter 4, “More OOPing Around.” You might recall that you had to implement the IComparable interface so that you could sort the tiles in top-top-bottom location order on the screen for the DeducTile Reasoning game. And of course the tile class provided much more functionality than that found in this one interface.

Creating the ReversiGame Implementation

The Reversi game is obviously more complex than Tic-Tac-Toe, so of course the implementation of that game will be more complex as well. Not every member of the class needs to be discussed because once again some functionality is similar to the TicTacToeGame implementation. The more complex elements of the class warrant some dialogue, though.

Creating the Piece Storage Implementation

The first change to discuss is that this game stores the board in an actual two-dimensional array instead of an ArrayList. The entire algorithm refers to rows, columns, and traveling along straight lines, and it’s simply much easier to think in terms of a square matrix of piece objects. The declaration for the array looks like this:

Private aPieces(7, 7) As ReversiPiece 

and the board is filled as shown in Listing 6-9, which is the implementation of the IPCOpponentGame.StartGame member.

Listing 6.9: Initializing the Board in the ReversiGame Class

Public Sub StartGame() Implements IPCOpponentGame.StartGame 
Dim x, y As Integer 
For x = 0 To 7 
For y = 0 To 7 
aPieces(x, y) = New ReversiPiece 
With aPieces(x, y) 
.Location = New Point(x * 32, y * 32) 
.Size = New Size(32, 32) 
Select Case (y * 8) + x 
Case 27, 36 
.Value = 1 
Case 28, 35 
.Value = -1 
Case Else 
.Value = 0 
End Select 
End With 
Next 
Next 
Call CalcScores() 
End Sub 

This method fills the 88 array with instances of the ReversiPiece class, sets up the location and size of each, and then initializes the four center pieces to their starting colors, as shown in Figure 6-4.

Figure 6-4. The starting board in a Reversi game

Looking at Similar Stuff

The following members are similar to the TicTacToeGame class:

Event declarations: The events declared in the IPCOpponentGame require only declaration in the game class. You declare these events in the same way as in the TicTacToeGame class.

DrawBoard method: This method is almost identical to the same-named method in the TicTacToeGame class. It loops through each piece in the array and calls that class’s Draw method.

Class constructor: The constructor is also identical—it sets the pForm variable to the passed-in Form parameter.

pForm property: The pForm property differs only in the hard-coded size that it makes the form in order to hold the board.

OnMouseDown method: This method controls what happens when the user clicks the form. The flow of this method is similar to the same-named method in the prior game. The class first determines if the user has clicked a board location that represents a valid move. If this is a legal move, then the class makes the move. After the player moves, the code checks to see if anyone has won the game. If not, then the computer moves, and the program checks the winning conditions again.

Making a Move

A player placing a piece on the board in Reversi flips over the pieces of the opponent in all eight directions, only if these pieces are “bounded” by a piece of that player. Much of the game algorithm consists of implementing the basic piece movement and takeover rule. Listing 6-10 shows the three routines used when a player makes a move on the board.

Listing 6.10: Placing a Piece in Reversi

Private Sub MoveHere(ByVal aP As ReversiPiece, ByVal Player As Integer) 
Dim x, y As Integer 
aP.Value = Player 
For x = -1 To 1 
For y = -1 To 1 
If Not (x = 0 And y = 0) Then 
If CanMoveOnThisLine(aP, Player, x, y) Then 
MoveGuysOnThisLine(aP, Player, x, y) 
End If 
End If 
Next 
Next 
Call CalcScores() 
End Sub 
Private Function CanMoveOnThisLine(_ 
ByVal aP As ReversiPiece, _ 
ByVal Player As Integer, ByVal iX As Integer, _ 
ByVal iY As Integer) As Boolean 
Dim x, y As Integer 
Dim bDone As Boolean 
Dim bFound As Boolean = False 
'travel 1 piece away in the proper direction 
x = aP.xElt + iX 
y = aP.yElt + iY 
'if off board, exit 
If x < 0 Or x > 7 Then Exit Function 
If y < 0 Or y > 7 Then Exit Function 
'make sure piece one away is opposite color 
If aPieces(x, y).Value <> -Player Then Exit Function 
'now, start looping. Looking for one of 
'our pieces before the edge of the board or a blank 
x += iX 
y += iY 
bDone = (x < 0 Or x > 7 Or y < 0 Or y > 7) 
Do While Not (bDone Or bFound) 
If aPieces(x, y).Value = Player Then 
bFound = True 
ElseIf aPieces(x, y).Value = 0 Then 
bDone = True 
Else 
x += iX 
y += iY 
bDone = (x < 0 Or x > 7 Or y < 0 Or y > 7) 
End If 
Loop 
Return bFound 
End Function 
Private Sub MoveGuysOnThisLine(_ 
ByVal aP As ReversiPiece, _ 
ByVal Player As Integer, ByVal iX As Integer, _ 
ByVal iY As Integer) 
Dim x, y As Integer 
Dim bDone As Boolean 
'travel 1 piece away in the proper direction 
'don't have to check that piece is right color 
'or off board, already determined 
x = aP.xElt + iX 
y = aP.yElt + iY 
bDone = False 
Do While Not bDone 
If aPieces(x, y).Value = Player Then 
bDone = True 
ElseIf aPieces(x, y).Value = 0 Then 
bDone = True 
Else 
aPieces(x, y).Value = Player 
x += iX 
y += iY 
bDone = (x < 0 Or x > 7 Or y < 0 Or y > 7) 
End If 
Loop 
End Sub 

The procedure MoveHere is where the desired piece, passed in as a parameter, is set to be one player’s color. Then the fun starts. A double loop is set up from –1 to +1 in both the x and y directions. The effect of this loop is to travel in eight directions outward from the given piece. (The case where both x=0 and y=0 is ignored because this would result in moving in no direction.)

For each direction, the program determines if the opponent has one or more pieces directly adjacent to the piece just moved, with an eventual “bound” of the player’s piece. This happens in the function CanMoveOnThisLine. This method takes a piece, a player value (–1 or 1), and an x and y direction. It travels on the board along this direction, starting at the passed-in piece, and looks for opponent pieces. If it finds one, it continues along the line until it finds a blank square, until it reaches the edge of the board, or until it finds a piece of the player’s own color. In the case of a blank square or the edge, the function must return False—no bounding piece was found. In the case that it finds the bounding piece, the function can return True.

The Searching Loop

The structure of the loop in the CanMoveOnThisLine method is one I use frequently. I call it the “searching” loop. In searching loops, I always declare Boolean variables named bDone and bFound and then start this loop with the following code:

Do While not (bDone or bFound) 

I like this line of code because it reads like English. Within the loop, I set bFound to True if I find the thing I’m searching for (in this case, a bounding piece), and I set bDone to True if I reach the end (an empty piece or the edge of the board). At the end of the loop, the variable bFound tells me if I found the element for which I was looking.

The method MoveGuysOnThisLine is similar in structure to the method CanMoveOnThisLine. It differs in that its job is to actually change the color of pieces in a certain direction until it reaches the bounding piece. This method assumes that the row in question has already been verified to have a bounding piece somewhere down the line, so it doesn’t do any searching.

Finding a Move to Make

You’ve seen how to make a move, but the program needs to determine which move is the best. The procedure MakeMove, along with a few support routines, handles this task. Listing 6-11 shows this functionality.

Listing 6.11: The Method MakeMove and Related Methods

Public Sub MakeMove() Implements IPCOpponentGame.MakeMove 
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.Value = 0 Then 
If CanMoveHere(aP, -1) Then 
MoveHere(aP, -1) 
iScore = BoardScore(-1) 
If iScore > iHigh Then 
iHigh = iScore 
aPHigh = aP 
End If 
PopBoard() 
End If 
End If 
Next 
If iHigh > 1 Then 
MoveHere(aPHigh, -1) 
pForm.Invalidate() 
Else 
MsgBox("computer has to pass") 
End If 
End Sub 
Private Function BoardScore(ByVal Player As Integer) As Integer 
Dim aP As ReversiPiece 
Dim r As Integer 
For Each aP In aPieces 
If aP.Value = Player Then 
If aP.IsCorner Then 
r += 20 
ElseIf aP.IsEdge Then 
r += 5 
Else 
r += 1 
End If 
End If 
Next 
Return r 
End Function 
Private Function CanMoveHere(ByVal aP As ReversiPiece, _ 
ByVal Player As Integer) As Boolean 
Dim x, y As Integer 
For x = -1 To 1 
For y = -1 To 1 
If Not (x = 0 And y = 0) Then 
If CanMoveOnThisLine(aP, Player, x, y) Then Return True 
End If 
Next 
Next 
Return False 
End Function 
Private Sub PushBoard() 
Dim aP As ReversiPiece 
For Each aP In aPieces 
aP.PushValue() 
Next 
End Sub 
Private Sub PopBoard() 
Dim aP As ReversiPiece 
For Each aP In aPieces 
aP.PopValue() 
Next 
End Sub 

The basic flow of the method MakeMove is as follows: The method checks each empty board space. If it determines that a computer piece could be placed here (remember, not every free space is eligible to receive a given piece—at least one opponent piece must be “trapped” and its color flipped, or the move is invalid), then it saves the current board and makes that move. Then, it determines the “score” of that board. The score is a simple ranking system that adds up all of the pieces of the computer’s color, giving bonus points for edge and corner squares. Of all the legal moves, the algorithm chooses the one that results in the highest score after that move is made.

It’s possible that no legal move is available. In this case, that player has to pass his turn and allow the other player to go again. Board configurations are possible that cause one player or the other to have to pass several turns in arow (this usually happens when one player is crushing the other, and there are so few of the opponents pieces that there’s no empty space adjacent to those pieces).

The MakeMove method uses the PushBoard and PopBoard routines to save and restore the state of the board.

Note

Even though I used the terminology of a computer “stack,” my push and pop routines don’t work like a traditional stack from the standpoint that I push the state of the board a single time, but I pop it back to this state several times within a loop. The traditional stack allows only one pop for each push operation.