Kinh Luan’s two-player game example abstracted the responsibilities of a game player into a series of interfaces and then implemented those interfaces in a series of classes. The game uses different classes for a human player, a computer-controlled player, and a local version of a network player.
For this program, instead of using interfaces to define functionality, you’ll instead create a base class and then create subclasses from it. This enables you to put some commonly used code inside the base class without duplicating it. Listing 7-1 shows the base player class for the network Reversi game.
Listing 7.1: The ReversiPlayer Class, the Base Class for All Reversi Players
Public MustInherit Class ReversiPlayer Private FName As String Private FColor As Color Private FScore As Integer Private FMyTurn As Boolean Public Event IsMyTurn(ByVal oPlayer As ReversiPlayer) Sub New(ByVal cNm As String, ByVal cClr As Color) MyBase.New() FName = cNm 'only two colors allowed Debug.Assert(cClr.Equals(Color.Red) Or cClr.Equals(Color.Blue)) FColor = cClr End Sub ReadOnly Property Name() As String Get Return FName End Get End Property ReadOnly Property Color() As Color Get Return FColor End Get End Property ReadOnly Property OpponentColor() As Color Get Return IIf(FColor.Equals(Color.Red), _ Color.Blue, Color.Red) End Get End Property Overridable Property MyTurn() As Boolean Get Return FMyTurn End Get Set(ByVal Value As Boolean) FMyTurn = Value If Value Then RaiseEvent IsMyTurn(Me) End If End Set End Property Property Score() As Integer Get Return FScore End Get Set(ByVal Value As Integer) FScore = Value End Set End Property End Class
The Reversi player class contains properties for the player name, the color of the player’s pieces on the board (currently constrained to blue or red), the color of the opponent’s pieces on the board (if this player is red, then the opponent is blue, and vice versa), the player’s score (how many pieces of her color are on the board), and a Boolean property named MyTurn that represents if it’s currently her turn. There’s also an event raised whenever the MyTurn property is set to True. The Name and Color properties are defined as ReadOnly, and they’re set in the constructor for this class. This structure makes sure that an instance of this class can’t change its name or color after it has been initially created.
The first variant of the player class to discuss is the player controlled by a human. There’s only a small amount of specialized code beyond the base player class; Listing 7-2 shows that code.
Listing 7.2: The HumanReversiPlayer Class
Public Class HumanReversiPlayer Inherits ReversiPlayer Private FForm As Form Public Event MyMoveLoc As MoveToLocationDef Sub New(ByVal cNm As String, ByVal cClr As Color, ByVal f As Form) MyBase.New(cNm, cClr) FForm = f End Sub Overrides Property MyTurn() As Boolean Get Return MyBase.MyTurn End Get Set(ByVal Value As Boolean) If Value Then AddHandler FForm.MouseDown, AddressOf OnMouseDown Else RemoveHandler FForm.MouseDown, AddressOf OnMouseDown End If MyBase.MyTurn = Value End Set End Property Private Sub OnMouseDown(ByVal sender As Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) If Not MyTurn Then Exit Sub If Not e.Button = MouseButtons.Left Then Exit Sub RaiseEvent MyMoveLoc(Me, e.X, e.Y) End Sub End Class
The first thing that differs in the human-specific version of the player class is the constructor. This constructor takes a form variable as well as a Color property and a Name property. This passed-in form variable is equal to a private variable, so the class can access it later.
The next extension to the human player class is an overridden version of the MyTurn property. Some interesting syntax exists here. Your intent is to store the MyTurn variable data in the same way but to selectively attach or detach a MouseDown event handler to the form variable depending on whether it’s currently the human player’s turn. By doing this, you can disable the ability for the human player to click the form when it’s not her turn.
The property Get function returns the value of MyBase.MyTurn, which is another way of saying “The value of MyTurn in the ancestor class ReversiPlayer.” Likewise, the last line of the Set statement for this property writes the intended value to MyBase.MyTurn, meaning that the underlying property is still being used to hold the MyTurn variable data.
The remainder of the MyTurn property’s Set statement adds or removes the MouseDown handler of the private form variable, depending on the new value of the property. If MyTurn is True, then the MouseDown event handler becomes active. If MyTurn is False, then the MouseDown event handler is unattached.
The MouseDown event itself, named OnMouseDown, raises an event named MyMoveLoc when it’s called. Look at the definition of the MyMoveLoc event earlier in the HumanReversiPlayer class definition:
Public Event MyMoveLoc As MoveToLocationDef
This event declaration looks just like a variable declaration, doesn’t it? It contains an access specifier (Public), a name (MyMoveLoc), and a type (MoveToLocationDef). So exactly what type is MoveToLocationDef? Is it an integer, a string, or a class?
In truth, MoveToLocationDef describes a type of function. Think about that statement for a second. Can a function be of a certain type? Or, perhaps more clearly put, can two functions be of different types? The answer is obviously “yes”; a function is of a specific type, and that type is described by the parameters sent to the function. So, if function A accepts a single integer parameter, then you could say that the function is of type A, and if function B accepts an integer and a string parameter, then that function is of type B.
The description of a function type happens in Visual Basic .NET through a delegate.Adelegate is a function type declaration. It describes a class of functions that are related from the standpoint that they all pass the same parameter lists. You can declare the delegate declaration for the MoveToLocationDef event as follows:
Public Delegate Sub MoveToLocationDef(ByVal sender As ReversiPlayer, _ ByVal x As Integer, ByVal y As Integer)
This declaration declares a type of function (actually, a Sub in this case) that takes a ReversiPlayer parameter and two integer parameters named x and y. Once you make this declaration, you can declare an event as having this type:
Public Event MyMoveLoc As MoveToLocationDef
What this means is that the MyMoveLoc event will pass back the same three parameters as specified in the delegate declaration—a ReversiPiece variable and two integers. Looking back at the MyTurn property declaration in Listing 7-2, you can see that this is indeed the parameter list when the program raises the event:
RaiseEvent MyMoveLoc(Me, e.X, e.Y)
The class that controls the computer player is similarly simple. It changes little from the base class, as shown in Listing 7-3.
Listing 7.3: The ComputerReversiPlayer Class
Public Delegate Sub MakeBestMoveDef(ByVal sender As ReversiPlayer) Public Class ComputerReversiPlayer Inherits ReversiPlayer Public Event MyMakeBestMove As MakeBestMoveDef Sub New(ByVal cNm As String, ByVal cClr As Color) MyBase.New(cNm, cClr) End Sub Overrides Property MyTurn() As Boolean Get Return MyBase.MyTurn End Get Set(ByVal Value As Boolean) MyBase.MyTurn = Value If Value Then RaiseEvent MyMakeBestMove(Me) End If End Set End Property End Class
This class declares a second delegate for use by the computer player class. This delegate, named MakeBestMoveDef, describes a function that takes a single parameter of type ReversiPlayer. It also declares an event within the ComputerReversiPlayer class as being of this type, and it raises this event whenever the MyTurn property of this class is set to True. In other words, the event executes whenever it becomes the computer’s turn.
The player class that represents a network player is sort of a “proxy” class. Imagine two people who are going to play the game over a network, player A and player B. On the machine being used by player A, a HumanReversiPlayer class instance will be created to represent and handle the responsibilities of player A, and NetworkReversiPlayer will be created to send and receive the move information over the network to the computer of player B. On player B’s machine, the opposite is true. Player B will have a HumanReversiPlayer instance to control her own moves and a NetworkReversiPlayer instance to send and receive move data from player A. Listing 7-4 shows the NetworkReversiPlayer class, which contains the send and receive move code.
Listing 7.4: The NetworkReversiPlayer Class
Public Class NetworkReversiPlayer Inherits ReversiPlayer Private FStream As NetworkStream Public Event MyMoveLoc As MoveToLocationDef Sub New(ByVal cNm As String, ByVal cClr As Color, _ ByVal oStream As NetworkStream) MyBase.New(cNm, cClr) FStream = oStream End Sub Public Sub LookForTurn() Do If FStream.DataAvailable Then Dim cPiece As String Dim oPiece As New ReversiPiece Dim oSer As New XmlSerializer(oPiece.GetType) Dim oRead As StreamReader oRead = New StreamReader(FStream) cPiece = oRead.ReadLine oPiece = oSer.Deserialize(New StringReader(cPiece)) RaiseEvent MyMoveLoc(Me, _ oPiece.Location.X, oPiece.Location.Y) End If System.Threading.Thread.Sleep(250) Loop Until False End Sub Public Sub SendMyTurnToOpponent(ByVal aP As ReversiPiece) Dim oSer As New XmlSerializer(aP.GetType) Dim oSW As New StringWriter Dim oWriter As New XmlTextWriter(oSW) Dim oByte() As Byte Dim cSend As String oSer.Serialize(oSW, aP) oWriter.Close() cSend = oSW.ToString cSend = cSend.Replace(Chr(10), ") cSend = cSend.Replace(Chr(13), ") 'add crlf so READLINE works cSend &= Microsoft.VisualBasic.vbCrLf Try oByte = System.Text.Encoding.ASCII.GetBytes(cSend.ToCharArray()) FStream.Write(oByte, 0, oByte.Length) Catch oEX As SocketException MsgBox(oEX.Message) End Try End Sub End Class
There’s no overridden functionality in the network player class, only new functionality. It declares an event named MyMoveLoc, which is raised within the LookForTurn method once it’s determined that this network player has moved somewhere. This LookForTurn method is an endless loop that polls for data on an object called NetworkStream. This class sends data over a network from one program to another. The section “Sending Game Data” discusses the format of the data; let’s focus on the class structure of the program for the time being. The new SendMyTurnToOpponent method also uses this NetworkStream object instance to send the information of a move over the wire to the other player.
You might be asking yourself “Where’s the beef?” after studying the player classes. In other words, the player classes don’t seem to do much in the way of game playing, do they? They contain no information about the board, for instance, or any logic to decide what move might be the best to make. In fact, all they seem to do is store some player-specific information and raise some events.
This design choice was made, uh, by design. The player classes were kept “thin” purposefully so that they know as little as possible about the specific game being played. Take a second look at the player classes, and you’ll probably agree that you could use these same classes for a number of different games, not only for the Reversi game being discussed here.
This is what’s known as keeping the coupling between classes loose. Coupling (apart from a hilarious sitcom on the BBC) describes how “linked” classes are to one another. Listing 7-5 shows a sample definition of a more tightly coupled player class.
Listing 7.5: A Tight Coupling of a Player and Game Class
Public Class TightlyCoupledComputerReversiPlayer Inherits ReversiPlayer Private FReversiGame as ReversiGame Sub New(ByVal cNm As String, ByVal cClr As Color, g as ReversiGame) MyBase.New(cNm, cClr) FreversiGame = g End Sub <more stuff here> End Class
This version of the class passes in an instance of a ReversiGame class, probably so that the code that determines the best move to make resides inside the computer player class. Although this makes sense from a “responsibilities” standpoint, it doesn’t make sense from a “loose coupling” standpoint because the player class and the game class are now forever linked to one another. Such linkage is bad because it prevents the reuse of the player class in another game without modification.