Learn VB .NET Through Game Programming [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Learn VB .NET Through Game Programming [Electronic resources] - نسخه متنی

Matthew Tagliaferri

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید





Understanding Life and Something Like It

You can find the polymorphic game example in the solution CellularAutomata, which contains the three programs you’ll develop in this chapter. A cellular automaton is a type of program that produces a lattice of cells. Each cell exists in one of a finite number of states at a given time. With each tick of some dictatorial time clock, the state of each cell updates by using some local logic or rule set. This rule set often depends on the state of the cell’s neighbors. Figure 5-1 shows the cellular automaton displayer in action.


Figure 5-1: The Cellular Automata program

The most well known of all the cellular automaton programs is Conway’s Game of Life. It’s so well known, in fact, that most student programmers learn these concepts in the reverse order presented in this chapter—first they learn about Conway’s Game of Life, and then they learn that this program is but one example of a group of programs named cellular automaton programs. Presenting these concepts in the reverse order emphasizes that all the instances of this type of program are to be treated equally, at least from the standpoint of this example program. The main point is that the cellular automaton display program treats all cellular automaton programs equally and can run each through the concept of polymorphism without having to know details about what each program is doing. You’ll see the details of how each type of cellular automaton program works in due time.


Creating the CellularAutomataGame Class


One of the most common ways a program can achieve polymorphism is through inheritance, as demonstrated in the PolymorphismExample program. All five of the objects on the sample form are descendants of the Object class (as is every object), so the program accesses the ToString method of each object without having to know which object it is. In fact, the code within that Click event can access any member defined in the Object class directly without having to know what specific class it is.


The solution CellularAutomata, found in the source code that accompanies this book, contains several other examples of polymorphic behavior through inheritance hierarchy.

Creating the Public Members of the CellularAutomataGame Class


The CellularAutomata solution contains three different cellular automaton examples. All three games inherit off of the ancestor class CellularAutomataGame. This class handles the storage of the cell lattice and the user interface requirements for all of the game types. Listing 5-1 shows the public interface for that class.

Listing 5.1: The CellularAutomataGame Class Public Interface



Public MustInherit Class CellularAutomataGame
Public Sub New(ByVal oCtl As Control)
Property CellRadius() As Integer
ReadOnly Property GenerationCount() As Integer
ReadOnly Property TimerTicksElapsed() As Long
Public Sub Tick()
End Class



There certainly isn’t a whole lot going on here, right? First, notice that this class is declared MustInherit, meaning that you can’t create an instance of it directly. Second, there’s a constructor that takes some type of Control as a parameter. This Control serves as the drawing surface for the game. There’s also a CellRadius property to define how big each cell will be, a Tick method that increments the all-seeing clock by one unit, and some read-only properties to describe how many generations have been run and how many timer ticks it took for the last generation to run. As you can see, the public interface for this program remains small, so it should be quite easy to create an instance of this class and execute the Tick method repeatedly to watch the patterns created by the cellular automaton programs.

Creating the Protected Members of the CellularAutomataGame Class


Although the public interface for the CellularAutomataGame is quite small, there’s quite a bit going on in this class under the hood. Listing 5-2 shows the protected interface for the same class.


Listing 5.2: The Protected Interface for the CellularAutomataGame Class



Public MustInherit Class CellularAutomataGame
Protected FCtl As Control
Protected FCells As ArrayList
Protected oRand As Random
Protected FRows As Integer = 16
Protected FCols As Integer = 16
Protected Function HalfTheTime() As Boolean
Protected Sub IndexToRowCol(ByVal i As Integer, _
ByRef iRow As Integer, ByRef iCol As Integer)
Protected Function RowColToCell(ByVal iRow As Integer, _
ByVal iCol As Integer, _
Optional ByVal bWrap As Boolean = False) As CellularAutomataCell
Protected MustOverride Function _
CreateOneCell(ByVal FPos As Point) As CellularAutomataCell
Protected MustOverride Sub RunAGeneration()
End Class



Now you can start to see some of the work this class does. A number of protected variables are declared inside this ancestor class, including a holding place for the passed-in Control that will serve as the painting surface, an ArrayList to hold all of the cell objects, a random number generator, and integers to store the number of rows and columns that will make up the lattice. As for protected methods, there’s a HalfTheTime method (something that simply returns True half the time and False the other half—kind of a “coin flipping” method) and two translation methods that convert a row/column pair into a straight integer index and vice versa. The integer index serves as the index into the ArrayList variable, but much of the logic of the program is easier to think of in terms of row/column indexes in the cell lattice, especially when considering neighbor cells.

The last two protected methods on the CellularAutomataGame class are the most interesting. The first is the CreateOneCell method. This method is responsible for creating one cell in the lattice and initializing that cell’s state. Each cellular automaton game can have different possible cell states, and therefore the initialization of each cell needs to take place in the individual game classes. This explains why this method is declared MustOverride. The last method is RunAGeneration. This method is responsible for updating the state of each cell during a timer tick. Again, because the rules of each cellular automaton game are different, this method is declared MustOverride.

Creating the CreateData Member of the CellularAutomataGame Class


Between the public and protected interfaces of the CellularAutomataGame class, you can pretty much discern how this class works. However, let’s look at a few private methods. The first of these is CreateData, shown in Listing 5-3. This method is responsible for creating and initializing all of the individual cell objects.

Listing 5.3: The CreateData Member of the CellularAutomataGame Class



Private Sub CreateData()
Dim oC As CellularAutomataCell
Dim iRow As Integer = 0
Dim iCol As Integer
Dim oPt As Point
FCells = New ArrayList
Do
iCol = 0
Do
oPt = New Point(iCol * CellRadius, iRow * CellRadius)
oC = Me.CreateOneCell(oPt)
FCells.Add(oC)
iCol += 1
Loop Until iCol * CellRadius > FCtl.Width
iRow += 1
Loop Until (iRow * CellRadius) > FCtl.Height
FRows = iRow
FCols = iCol
FGenerationCount = 0
Debug.Assert(FRows * FCols = FCells.Count)
End Sub



The CreateData method is called when the class is first created or whenever the surface control FCtl is resized. This ensures that the array of cells completely covers the surface area of the form. In this method, one loop is nested inside a second, and these loops terminate based on cells covering the current width and height. Within the loop, the CreateOneCell method is called to create an instance of the CellularAutomataCell class, and then this cell is added to the Arraylist FCells. When the loops are completed, the protected variables FRows and FCols are set to the number of cells that make up a row and column of the cell lattice. Also, the variable that holds the generation count is reset back to 0.

Other than the loops and a bit of math to determine how many cells to create across and down, there isn’t much to notice about the CreateData method, with one important exception. Review the line of code that creates an individual cell:


oC = Me.CreateOneCell(oPt)

Do you remember discussing the CreateOneCell method? (You should—it was only one page ago!) This method is defined on the game class, but it’s declared as MustOverride, meaning there’s no implementation in the current class. Each subclass must create its own CreateOneCell method, and then the line of code calls it, without having to know the details of what that method is doing. Sound familiar? It should. This is a perfect example of polymorphism in action. You’ve got a class calling a member that provides the same functionality (creating an instance of a different CellularAutomataCell class) but through different implementations (the code for the CreateOneCell method in each subclass can do many different things as long as it finally returns the requested class instance back to the caller).

Creating the User Interface Methods of the CellularAutomataGame Class


The last functionality that the ancestor CellularAutomataGame class implements is the basic user interface functions. To do this, it takes the passed-in Control variable and attaches some event handlers to it. Listing 5-4 shows the full code for the constructor of the class as well as these event handlers.

Listing 5.4: Event Handlers Attached to the Passed-in Control Class



Public Sub New(ByVal oCtl As Control)
MyBase.New()
FCtl = oCtl
AddHandler FCtl.Paint, AddressOf ControlPaint
AddHandler FCtl.Resize, AddressOf ControlResize
AddHandler FCtl.MouseDown, AddressOf ControlMouseDown
oRand = New Random
oHRT = New HighResTimer
CreateData()
FCtl.Invalidate()
End Sub
Private Sub ControlPaint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs)
Dim oC As CellularAutomataCell
For Each oC In FCells
oC.Draw(e.Graphics)
Next
End Sub
Private Sub ControlMouseDown(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs)
Dim oC As CellularAutomataCell
For Each oC In FCells
If oC.ClientRectangle.Contains(e.X, e.Y) Then
If e.Button = MouseButtons.Left Then
oC.OnMouseDown(e)
FCtl.Invalidate()
Exit Sub
End If
End If
Next
End Sub
Private Sub ControlResize(ByVal sender As Object, _
ByVal eventargs As System.EventArgs)
CreateData()
FCtl.Invalidate()
End Sub



This code sets up event handlers for the control’s Paint event, the MouseDown event, and the Resize event. As mentioned earlier, the cell lattice is re-created via a call to the CreateData method whenever the source control is resized, which you can see in the simple ControlResize event in Listing 5-4. The only other line of code in this event handler invalidates the control, which forces it to repaint itself.

The other two event handlers contain some further examples of polymorphism. The ControlPaint event handler loops through all of the cell objects in the FCells Arraylist and calls the Draw method on each. The call to this method works no matter what subclass of CellularAutomataCell is stored in the ArrayList, even though different classes may implement the drawing in different ways. The ControlMouseDown event also acts polymorphically—it first determines which cell was clicked by the left mouse button, and then it calls the OnMouseDown method on that cell class. Each subclass of the CellularAutomataCell class can do whatever it likes in this method—the job of this code is to simply notify the cell that it has been clicked.


Creating the CellularAutomataCell Class


The CellularAutomataCell class serves as the ancestor class to each cell type for the different types of cellular automaton games. Each type of game has a descendant of the CellularAutomataGame class and a corresponding CellularAutomataCell class descendant to go with it. This class is much simpler than the game class; Listing 5-5 shows its public interface (along with the implementation of one method, the Draw method).

Listing 5.5: The CellularAutomataCell Class



Public MustInherit Class CellularAutomataCell
Public Sub New(ByVal oPos As Point, ByVal r As Integer)
ReadOnly Property Position() As Point
ReadOnly Property Radius() As Integer
ReadOnly Property ClientRectangle() As Rectangle
Public MustOverride Function GetColor() As Color
Public MustOverride Sub OnMouseDown(_
ByVal e As System.Windows.Forms.MouseEventArgs)
Public Sub Draw(ByVal g As Graphics)
Dim r As Rectangle
Dim b As Brush
r = New Rectangle(FPos.X, FPos.Y, FDrawRad, FDrawRad)
b = New SolidBrush(Me.GetColor)
g.FillRectangle(b, r)
End Sub
End Class



This little class does nothing out of the ordinary, so this chapter won’t cover all of the implementation details on a line-by-line basis. The constructor takes a point instance and a radius as parameters. All that the constructor does with these parameters is store them in private variables, which are both exposed through the read-only properties Position and Radius, respectively.

The Draw method is nothing you haven’t seen in previous chapters; it uses the FillRectangle method on the passed-in Graphics class to draw the cell at the appropriate position on the control’s surface. You’ll notice that it uses the GetColor method to determine what color to draw the rectangle. The GetColor method is declared on this class but is declared MustOverride, meaning that its functionality is implemented entirely in the subclasses. If you’ve perceived that the call to the GetColor method from Draw is another example of polymorphic behavior, you’re correct. Note that the Draw method has no idea how the subclass determines what color will be returned—it cares only that a color is returned so it knows how to draw the cell.

The ClientRectangle property returns a .NET Framework Rectangle object that represents the location and radius of this cell (it’s used in the ControlMouseDown event handler of the game class, shown in Listing 5-4). The last method, OnMouseDown, instructs the cell how to behave when the cell is clicked. You saw that this method is called polymorphically in Listing 5-4. It’s declared MustOverride, meaning that no implementation exists in this ancestor class.

/ 106