Learn VB .NET Through Game Programming [Electronic resources]

Matthew Tagliaferri

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

Developing DeducTile Reasoning

DeducTile Reasoning is a game where four tiles appear top to bottom, and the player must guess the correct sequence of the tiles based on the clues given. Like Brain Drain Concentration, the user’s opponent is the clock—he must solve the puzzle in a set amount of time. Figure 4-3 shows the game.

Figure 4-3: DeducTile Reasoning, guessing the order of tiles by textual clues

In this game, the puzzle is constrained by the fact that each shape and each color is represented only once each. This limits the number of possible puzzle permutations to 576. Where did that number come from? Well, you’re dealing with four shapes, which you can represent using the integers 0, 1, 2, and 3. There are 24 ways to order these four integers:

0,1,2,3

2,0,1,3

3,2,0,1

0,1,3,2

2,0,3,1

3,1,0,2

0,2,1,3

3,0,1,2

1,2,3,0

0,2,3,1

3,0,2,1

1,3,2,0

0,3,2,1

1,2,0,3

2,3,1,0

0,3,1,2

1,3,0,2

2,1,3,0

1,0,2,3

2,1,0,3

3,2,1,0

1,0,3,2

2,3,0,1

3,1,2,0

This gives you all the possible arrangements of the four shapes. The same 24 permutations represent the possible arrangements of colors. Multiplying 24 by 24 yields the total possible puzzle permutations of four shapes and four colors, giving a total of 576 puzzle possibilities.

Generating Clues

The difficult part of coding this game is generating the clues. The game must give enough information to solve the puzzle or it’ll be impossible to win. Take, for example, a case where you give a single clue:

The Red Tile is first. 

This obviously isn’t enough information to solve the puzzle. If this is the only clue you give, the game would devolve into a random guessing game, which isn’t fun. Adding a second clue yields a bit more information:

The Circle is not Red. 

This clue tells the player that the circle is green, blue, or yellow. When coupled with the first clue, the player knows that the circle must be in position 2, 3, or 4. This is more information than the player originally had but still not enough information to solve the puzzle.

The game must be sure it generates enough clues so that the player can solve the puzzle. To accomplish this, the program generates all 576 possible solutions and then picks one at random as the puzzle that will be presented to the user. Then, the program generates a clue that pertains to the puzzle, and all the non-solutions that this clue doesn’t pertain to are “crossed off the list.” Clue generation continues until only one of the 576 puzzles remains—this remaining puzzle is the same as the puzzle presented to the user.

Creating the FourTuple Class

You could spend a ton of time hard-coding the 576 possible puzzles, but you’re better off having the computer do the work for you. The first step of generating these puzzles is to store the 24 permutations of the numbers 0–3. Listing 4-9 shows the class to store four integers.

Listing 4.9: The FourTuple Class, Meant to Store Four Integers Only

Private Class FourTuple 
Public a As Integer 
Public b As Integer 
Public c As Integer 
Public d As Integer 
Public Sub New(ByVal ia As Integer, ByVal ib As Integer, _ 
ByVal ic As Integer, ByVal id As Integer) 
a= ia 
b = ib 
c = ic 
d = id 
End Sub 
End Class 

Note

This listing breaks one of my own rules by not even bothering to make “real” properties on this class to store the four integers—instead it uses simple public variables, so you can be pretty sure I’m not going to use this quickie class for anything important.

Creating the TileCombo Class

The next class, called TileCombo, holds four individual ColoredShape classes. Listing 4-10 shows the public interface for this class.

Listing 4.10: The TileCombo Class, Used During Clue Generation

Public Class TileCombo 
Public Sub New(ByVal a As ColoredShape, ByVal b As ColoredShape, _ 
ByVal c As ColoredShape, ByVal d As ColoredShape) 
Public Overloads Function Equals(ByVal t As TileCombo) As Boolean 
ReadOnly Property ColoredShape(ByVal i As Integer) As ColoredShape 
Property Eliminated() As Boolean 
Public Overrides Function ToString() As String 
End Class 

As you can see, the four ColoredShapes are passed right into the constructor, and no method exists to change them. The program uses a Boolean property named Eliminated when generating clues to determine if this clue has been “crossed off the list” as a possible puzzle solution based on the clues generated to this point.

Creating the TileComboPossiblesList Class

The group of 576 TileCombo classes would also benefit from being encapsulated into their own class because it seems natural to think of this “group of tiles” as a separate object with its own responsibilities. I named this class TileComboPossiblesList; Listing 4-11 shows its public interface. TileComboPossiblesList stores the 576 possible solutions and the one puzzle that will be shown to the user.

Listing 4.11: TileComboPossiblesList

Public Class TileComboPossiblesList 
Public Sub New() 
Public Function Item(ByVal i As Integer) As TileCombo 
Public Function Solution() As TileCombo 
Public Function SolutionsLeft() As Integer 
Public Function AllNonSolutionsEliminated() As Boolean 
Public Function NumberClueWouldEliminate_ 
(ByVal c As TileComboClue) As Integer 
Public Sub EliminateBasedOnClue(ByVal c As TileComboClue) 
Public Sub EnumerateRemaining() 
End Class 

It’s the job of TileComboPossiblesList to generate the 576 permutations and then choose one at random as the puzzle to be shown to the player. In addition, clue instances will be passed into this class to eliminate “bad” tile combos from the list until only one remains.

As promised, the design for this seemingly simple game is getting complicated in a hurry. Perhaps seeing some code will help make some sense of how the classes fit together. Listing 4-12 shows a method on TileComboPossiblesList named GeneratePossibles, which creates the permutations of the solutions. This class is declared as private, which explains why it wasn’t listed in the public interface shown in Listing 4-11.

Listing 4.12: The GeneratePossibles Method

Private Sub GeneratePossibles() 
Dim FTuples As ArrayList 
Dim i, j As Integer 
Dim oTi, oTj As FourTuple 
'these are the 24 ordered possibilities for integers 0,1,2 ,3 
FTuples = New ArrayList 
With FTuples 
.Add(New FourTuple(0, 1, 2, 3)) 
.Add(New FourTuple(0, 1, 3, 2)) 
.Add(New FourTuple(0, 2, 1, 3)) 
(repeat 24 times for every numeric combination) 
End With 
'we need every permuation of every permutation. 
'this gives us 576 combinations 
FPossibles = New ArrayList 
For Each oTi In FTuples 
For Each oTj In FTuples 
FPossibles.Add(New TileCombo( _ 
ColoredShape.CreateByIndex(oTi.a, oTj.a), _ 
ColoredShape.CreateByIndex(oTi.b, oTj.b), _ 
ColoredShape.CreateByIndex(oTi.c, oTj.c), _ 
ColoredShape.CreateByIndex(oTi.d, oTj.d))) 
Next 
Next 
End Sub 

Listing 4-12 doesn’t show the repetitive code inside the block that starts With FTuples. As you can see, each numeric combination of integers 0–3 is stored in a FourTuple instance, and each instance is in turn stored in an ArrayList named FTuples. From there, a double For Each loop begins and inside a new TileCombo is created based on the integers stored in the FTuples ArrayList.

Creating the TileComboClue Class and Its Subclasses

What’s now needed is a class to encapsulate a single textual clue. Listing 4-13 shows the code that serves this purpose, the TileComboClue class.

Listing 4.13: The TileComboClue Class

Public MustInherit Class TileComboClue 
Public Sub New(ByVal t As TileCombo) 
MustOverride Function ClueText() As String 
MustOverride Function CluePertainsTo(ByVal t As TileCombo) As Boolean 
Function HalfTheTime() As Boolean 
Public Function PositionalText(ByVal iPos As Integer) As String 
End Class 

This class is declared MustInherit, which means you can’t create an instance of this class directly. Instead, you must create instances of the subclasses of this class. The subclasses of TileComboClue represent the several different styles of textual clues. For example, one clue type links the color and shape of a tile by stating something such as The Square is Red or The Blue tile is a Circle. Another type of clue indicates position, such as The first tile is Green or The second tile is the Triangle. There are eight separate clue subclasses, and you can add more by simply adding subclasses to the program. You’ll see a neat trick that allows the program to randomly select which clue subclasses to use a bit later in the section “Creating the PuzzleGenerator Class.”

Let’s look at one of the clue subclasses in detail to learn how it functions. Listing 4-14 shows the clue class that links the color of one of the tiles to the shape. The ComboClueShapeIsColor class tells the player The Square is Yellow or The Green Tile is a Triangle.

Listing 4.14: The ComboClueShapeIsColor Class

Public Class ComboClueTheShapeIsColor 
Inherits TileComboClue 
Private FTile As ColoredShape 
Public Sub New(ByVal t As TileCombo) 
MyBase.New(t) 
Dim oRand As New Random 
FTile = t.ColoredShape(oRand.Next(0, 4)) 
End Sub 
Overrides Function ClueText() As String 
If HalfTheTime() Then 
Return "The " & FTile.ColorWord & _ 
" tile is a " & FTile.ShapeWord 
Else 
Return "The " & FTile.ShapeWord & _ 
" is " & FTile.ColorWord 
End If 
End Function 
'return true if the 
Overrides Function CluePertainsTo(ByVal t As TileCombo) As Boolean 
Dim ocs As ColoredShape 
Dim i As Integer 
For i = 0 To 3 
ocs = t.ColoredShape(i) 
If ocs.Color.Equals(FTile.Color) Then 
Return ocs.ShapeWord.Equals(FTile.ShapeWord) 
End If 
Next 
End Function 
End Class 

The constructor of all the TileComboClue classes receives a TileCombo instance as its parameter. In all cases, this TileCombo instance is the solution to the puzzle that will be displayed to the player. It’s the job of the class to take this puzzle and construct a clue based on the puzzle. This class generates clue by simply choosing one of the four tiles randomly and storing it in the local variable FTile for later use.

The method ClueText is the function that returns the text of the clue. As with many of the clues, the clue class can return the same clue in different forms to further vary the appearance of the clues. In this method, a sentence of the form The <shape> is <color> displays 50 percent of the time, and a sentence of the form The <color> tile is <shape> displays the remainder of the time. Both forms describe the same information, obviously; they just do so slightly differently.

The clue classes use the CluePertainsTo method to determine if a passed-in TileCombo is correctly described by the clue. This is used when crossing possible tile combinations off the list when whittling the list of 576 possible puzzles down to the final solution. In this subclass, each of the four tiles is scanned until the one matching the clue is found. Once that tile is found, the method returns True if that tile is the same shape as the tile in the solution. For example, if one instance of this class generated the clue The Triangle is Red, then the CluePertainsTo method would return True for any TileCombo passed into it in which the triangle tile is also red. The method would return False for all TileCombos passed in where the triangle is some other color.

Creating the PuzzleGenerator Class

The final class to discuss in the clue generation process is the class that links all the other classes together. You can name this class, appropriately enough, PuzzleGenerator. One instance of the PuzzleGenerator class is created each time the game is played. Listing 4-15 shows the complete PuzzleGenerator class so you can study how all these other classes fit together.

Listing 4.15: The PuzzleGenerator Class

Public Class PuzzleGenerator 
Private oPL As TileComboPossiblesList 
Private FClueClassNames As ArrayList 
Private FClues As ArrayList 
Private FAsm As Reflection.Assembly 
Public Sub New() 
MyBase.New() 
oPL = New TileComboPossiblesList 
GetClueClasses() 
GenerateClues() 
End Sub 
'use reflection to find all the clue subclasses and load them up 
Private Sub GetClueClasses() 
FAsm = System.Reflection.Assembly.GetExecutingAssembly() 
Dim t As Type 
FClueClassNames = New ArrayList 
'GetType used when not instantiating 
'(can't instantiate b/c of MustInherit) 
Dim tParent As Type = GetType(TileComboClue) 
For Each t In FAsm.GetTypes 
If t.IsSubclassOf(tParent) Then 
FClueClassNames.Add(t) 
End If 
Next 
End Sub 
Private Sub GenerateClues() 
Dim c As TileComboClue 
Dim oTyp As Type 
Dim oRand As New Random 
Dim oArgs() As Object = {oPL.Solution} 
Debug.WriteLine("-------------------------------------------") 
Debug.WriteLine(oPL.Solution.ToString) 
Debug.WriteLine("-------------------------------------------") 
FClues = New ArrayList 
Do 
oTyp = FClueClassNames.Item(_ 
oRand.Next(0, FClueClassNames.Count)) 
c = Activator.CreateInstance(oTyp, oArgs) 
If oPL.NumberClueWouldEliminate(c) > 0 Then 
oPL.EliminateBasedOnClue(c) 
FClues.Add(c) 
End If 
Loop Until oPL.AllNonSolutionsEliminated 
End Sub 
Public Sub PopulateListBox(ByVal lb As ListBox) 
Dim c As TileComboClue 
lb.Items.Clear() 
For Each c In FClues 
lb.Items.Add(c.ClueText) 
Next 
End Sub 
Public Function IsSolution(ByVal a As ColoredShape, _ 
ByVal b As ColoredShape, ByVal c As ColoredShape, _ 
ByVal d As ColoredShape) As Boolean 
Dim t As New TileCombo(a, b, c, d) 
Return t.Equals(oPL.Solution) 
End Function 
End Class 

As shown in Listing 4-15, the PuzzleGenerator class uses a private instance of the TileComboPossiblesList class to store the 576 puzzle possibilities and remove them in the GenerateClues method. It accomplishes this by generating a random clue that pertains to the solution and then attempting to remove remaining puzzle possibilities whose tile arrangements don’t match this clue. If new possibilities are indeed removed, then this clue is added to the list of clues to be shown to the user. If the newly generated clue doesn’t remove any new tile combinations, then this clue adds no new information to the puzzle and it’s discarded. An example of this might be a new clue being generated that reads The Triangle is Red when clues already exist that read The Triangle is neither Blue nor Yellow and The Triangle is not Green. Because of these two clues, the clue engine has already crossed off all possibilities except those in which the triangle is red, so this latest clue adds no new information.

One other interesting piece of code in the PuzzleGenerator class is how it selects one of the clue classes randomly as the generation process occurs. This happens using a feature of the .NET Framework known as reflection. Reflection is the ability to write programs that describe the class structure of other programs. The clich example is the ability to write a program that can enumerate all of the classes in the .NET Framework for display in a treeview control, similar to the Visual Studio Object Browser (shown in Figure 4-4). You can imagine that in order to write such a program, you’d need the ability to open the .NET Framework assemblies (DLLs) and loop through the classes found within them.

Figure 4-4: You could write a program such as the Object Browser using reflection

In DeducTile Reasoning, you use reflection to loop through the game executable and find all the subclasses of the TileComboClue class. These subclass types are stored in the ever-useful ArrayList, and then this ArrayList is plundered at random when a new clue is required. Listing 4-16 shows the GetClueClasses method.

Listing 4.16: The GetClueClasses Method

'use reflection to find all the clue subclasses and load them up 
Private Sub GetClueClasses() 
FAsm = System.Reflection.Assembly.GetExecutingAssembly() 
Dim t As Type 
FClueClassNames = New ArrayList 
Dim tParent As Type = GetType(TileComboClue) 
For Each t In FAsm.GetTypes 
If t.IsSubclassOf(tParent) Then 
FClueClassNames.Add(t) 
End If 
Next 
End Sub 

The Assembly.GetExecutingAssembly method (a shared class method) is the one that returns the currently executing assembly (a.k.a. this program) as a variable that can be opened and the parts therein studied. In this case, you’re interested in looking for all of the classes in your program that inherit from the TileComboClue class. You do this using a class named Type, a class that represents a type declaration in the assembly.

Note

A type declaration can be a class, interface, array, value, or enumeration declaration in an assembly.

As each TileComboClue subclass is discovered, a Type variable representing this subclass is stored in the ArrayList named FClueClassNames. Later, in the GenerateClues method, the code that actually selects one of the clues at random is as follows:

Dim c As TileComboClue 
Dim oTyp As Type 
Dim oRand As New Random 
Dim oArgs() As Object = {oPL.Solution} 
oTyp = FClueClassNames.Item(_ 
oRand.Next(0, FClueClassNames.Count)) 
c = Activator.CreateInstance(oTyp, oArgs) 

What this code does is select an item out of the ArrayList, place that item in a Type variable, and then use the Activator.CreateInstance method to create an instance variable of this type. The trickiness is that if any parameters are required in the constructor of this variable, then these parameters must be sent to the Activator.CreateInstance method enclosed within an array. The array in the previous code is named oArgs. Because all the TileComboClue classes need a TileCombo (the puzzle solution) sent to them in their constructor, you can enclose the TileCombo (the oPL.Solution property) in the array. When the previous code runs, the variable c contains a random clue. The beauty of using reflection in this way is that you can add new subclasses of the TileComboClue classes to the program that represent different styles of clues, and you don’t have to change any of the clue generation code to “activate” these new clue styles.

The remainder of the PuzzleGenerator class is pretty simple. A PopulateListBox method fills a listbox with the text of all the generated clues, and an IsSolution method determines if four ColoredShape tile classes match the generated puzzle.

You now have a complete PuzzleGenerator class, and all that’s needed to complete this game is the user interface.

Creating the DeducTile Reasoning Interface

The hard part of this seemingly endless game is done. Completing the game requires creating four tiles on the screen, displaying the clue text, allowing the user to move the tiles around, and determining if the player wins or loses.

You create and display the tiles as you did in the Brain Drain Concentration game. You add four ColoredShape instances both to the game’s form and to a private ArrayList so you can get at them exclusively when needed. Listing 4-17 shows the procedure StartGame, in which the tiles are created and added to these collections.

Listing 4.17: The Method StartGame on the DeducTile Reasoning Form

Private Sub StartGame() 
Dim oShape As ColoredShape 
Dim oRand As New Random 
Dim i As Integer 
Me.Cursor = Cursors.WaitCursor 
oGT.StopTimer() 
If Not FTiles Is Nothing Then 
For Each oShape In FTiles 
Me.Controls.Remove(oShape) 
Next 
End If 
FTiles = New ArrayList 
For i = 0 To 3 
oShape = New ColoredShape(i, i) 
With oShape 
.Backwards = False 
.Width = 64 
.Location = New Point(16, 32 + (i * 64)) 
.AllowDrop = True 
AddHandler .MouseDown, AddressOf ShapeMouseDown 
AddHandler .MouseMove, AddressOf ShapeMouseMove 
AddHandler .MouseUp, AddressOf ShapeMouseUp 
AddHandler .DragOver, AddressOf ShapeDragOver 
AddHandler .DragDrop, AddressOf ShapeDragDrop 
End With 
'adding a reference to two places, the form and an arraylist 
FTiles.Add(oShape) 
Me.Controls.Add(oShape) 
Next 
FPuzGen = New DeductileReasoning.PuzzleGenerator 
FPuzGen.PopulateListBox(lbClues) 
oGT.StartAt = New TimeSpan(0, 3, 0) 
oGT.StartTimer() 
Me.Cursor = Cursors.Default 
End Sub 

In addition to the four user interface tiles being created and attached to awhole slew of event handlers, you can also see the PuzzleGenerator variable (FPuzGen) being instantiated and the PopulateListBox method being called. The puzzle solution and all the clues are generated as part of this class’s constructor, so these two lines of code are all that are required by an outside user to create a batch of clues and to fill a listbox with the text of those clues. Finally, you can see another GameTimer class (oGT)created and set up to act as the foil of the player.

Dragging and Dropping

This game has a unique interface in that it allows dragging with both the left and right mouse buttons. Dragging one tile onto the other using the left mouse button switches the positions of the two tiles. Dragging one tile onto another with the right mouse button switches only the color of the two tiles, but the shapes remain in their current places. The user interface code has to handle both types of drag operations.

Listing 4-17 showed five event handlers attached to the four ColoredShape tiles that are displayed on the form, and all five handlers relate to dragging and dropping. Table 4-2 describes these events.

Table 4-2: Drag/Drop Event Handler Code for DeducTile Reasoning

HANDLER NAME

EVENT

PURPOSE

ShapeMouseDown

MouseDown

Defines a rectangle outside of which the dragging should begin

ShapeMouseMove

MouseMove

Begins a drag operation if the mouse moves outside of this rectangle (with the button still down)

ShapeMouseUp

MouseUp

Clears the rectangle

ShapeDragOver

DragOver

Displays the drag icon

ShapeDragDrop

DragDrop

Performs the tile switching

All of the event handlers except for the last one are standard drag/drop code that you can find in the online help, so they won’t be defined here. Listing 4-18 shows the DragDrop code.

Listing 4.18: Dropping One Tile onto Another

Private Sub ShapeDragDrop(ByVal sender As Object, ByVal e As DragEventArgs) 
Dim oDest As ColoredShape = CType(sender, ColoredShape) 
If oDest.Equals(FDragShape) Then Exit Sub 
If Not FDidRight Then 
'left drag, simply swap positions 
Dim p As Point 
p = FDragShape.Location 
FDragShape.Location = oDest.Location 
oDest.Location = p 
Else 
'right drag, swap colors 
Dim c As Color 
c = FDragShape.Color 
FDragShape.Color = oDest.Color 
oDest.Color = c 
oDest.Invalidate() 
FDragShape.Invalidate() 
End If 
End Sub 

This routine first makes sure that a tile isn’t dragged and dropped onto itself. If this happens, then no action is required. If a tile is dragged onto another tile, however, then the code determines whether the right or mouse button was the one doing the dragging (via the Boolean variable FDidRight). If it was a left drag, then the Location variables of the two ColoredShape classes are swapped. If it was aright drag, then the colors are swapped and the two tiles are redrawn.

Ending the Game

The user now has the ability to read the clues and arrange the tiles until he thinks he might have the puzzle solved, at which point he clicks the Guess button at the bottom of the form to see if he’s right. Listing 4-19 shows the code that handles the Guess button click.

Listing 4.19: Taking a Guess

Private Sub cbGuess_Click(ByVal sender As System.Object, _ 
ByVal e As System.EventArgs) Handles cbGuess.Click 
FTiles.Sort()           'calls compareTo 
If FPuzGen.IsSolution(FTiles.Item(0), FTiles.Item(1), _ 
FTiles.Item(2), FTiles.Item(3)) Then 
oGT.StopTimer() 
If MsgBox("You win, play again?", _ 
MsgBoxStyle.YesNo Or MsgBoxStyle.Question, _ 
"Try Again") = MsgBoxResult.Yes Then 
StartGame() 
Else 
Me.Close() 
End If 
Else 
oGT.AddTime(New TimeSpan(0, 0, -5)) 
End If 
End Sub 

The first step in the guess routine is to call the Sort method on the ArrayList that holds the tiles. The reason this is required is because the tiles originally went into the array in a certain order, but the user has most likely swapped the physical order of the tiles on the form by dragging and dropping. The IsSolution method on the PuzzleGenerator assumes that the topmost tile is going to be the first parameter passed in, the second tile passed in second, and so on. Before sorting, you really don’t know if the first tile in the FTiles ArrayList is really the topmost tile on the form anymore.

The remarkable part of this code is how the Sort method shown in Listing 4-19 “knows” that it needs to sort the tiles according to their positions on the form. Why doesn’t it sort them in, say, alphabetical order by color or in descending alphabetical order by the last letter of the name of their shape? These sort criteria seem as arbitrary as the tiles’ top-to-bottom arrangement on the form.

The answer to this puzzle is that you told the system how you planned to sort the tiles earlier. Do you remember when? If you’ll recall, the ColoredShape tile class implemented the IComparable interface, meaning that it contained a CompareTo method. The following code is the implementation of that method:

Public Function CompareTo(ByVal obj As Object) _ 
As Integer Implements System.IComparable.CompareTo 
Dim o As ColoredShape = CType(obj, ColoredShape) 
Return Me.Top.CompareTo(o.Top) 
End Function 

The result of this CompareTo method is the result of comparing the Top property (an integer) of the two tiles in question. If you were to consult the online help for how CompareTo works on integers, you would find that it returns –1 if the method caller (Me.Top previously) is less than the parameter (o.Top), 0 if they’re equal, and 1 if the method caller is greater. This gives you the result you’re after—sorting the tiles in their top-to-bottom order on the form.

You might imagine cases where different sorting requirements exist for the same class depending on the circumstance. For example, you might create classes to implement a deck of cards for a card game. In some cases, a pile of cards may have to be sorted according to their position on a form. In other cases, they might have to be sorted by their face value or their suit. In still other cases, acrazy sort value might be required (such as in the game Euchre, where the highest card is one of the Jacks, and the second highest card is the other Jack of the same color).

The .NET Framework can handle classes with multiple sorting requirements by passing an instance of a class that implements the IComparer interface (which is different from IComparable). This isn’t covered in detail here except in passing— it’s important at this time only to point out that implementing multiple sorting mechanisms within a class is possible.

Once you’ve sorted the tiles in the Arraylist according to their physical positions on the form, you can pass them to the IsSolution method of the PuzzleGenerator variable. If the solution is indeed correct, the program displays a message and asks if the player wants to play again. If the solution is incorrect, the player is “punished” by having five seconds removed from the available time.

Believe it or not, this finally completes the discussion of the DeducTile Reasoning game! I personally enjoy playing this game as much as any other in the book; it presents a nice little brainteaser for the player.