Tutorial: Pattern guessing game
The pattern guessing game, similar to a board game called Mastermind, presents the player with a challenge to guess a pattern made from 4 choices from 6 colors.
The player plays by dragging one of the circles to one of the 4 slots. More exactly, the player presses down on the mouse over one of the circles on the left, which produces a new circle. The player drags it to a slot.
After placing a circle in each of the 4 slots, the program produces the feedback indicating how the player's guess matches the hidden pattern. They player can put circles on top of circles with just the last one counting, but when 4 slots have been filled, the program provides the feedback.
At this point, the player knows that there are red, blue and gold circles, but not where they are. There could be a 4th color or a repeat of one of the three. My next guess gives me information, even though I know it is not the hidden pattern:
So I now know that there are no light blue or purple circles, and there are 2 reds. I need to guess where the blue and gold are:
I guessed correctly! If it was wrong, I would have known the correct pattern for the next move. An important strategic decision for playing the game (NOT implementing it), is to use moves for information.
The buttons give the player the option to try again, either with the same pattern or a new one. They can be used at any time; that is, not just at the end of a game.
The game is fun to play but/and it is important to realize that implementing the game is not the same as playing it. I did use the trace function to display the hidden pattern and other information while I was building the application so I would know what to do to check for certain conditions.
Overview of implementation techniques
The initial steps of building an application are to determine
· the static and dynamic elements on the screen
· the critical events, especially the player actions
· the implementation of the rules of the game
· re-starting the game
For this game, I chose to create the circles on the left and the first set of what I call guess slots dynamically. This is partially because the moving circles and the following game slots are produced dynamically along with the feedback objects. Producing the circles on the left dynamically also means that setting up the event listeners can be done in a loop instead of individually. The circles are implemented as Sprites with circle shapes. An array named colors holds the RGB numbers in hexadecimal. The Slots and feedback objects (I call them blackpeg, whitepeg and nopeg in honor of the board version of the game) are movie clip symbols. After each is created, I click on Linkage to set it up so they can be created using ActionScript during runtime (see below). The rest of the screen (Stage) does not change: the information in the separate Static text fields and the two buttons. All the dynamically created objects are added to the Display list, meaning they will be displayed, using hook.addchild where hook is an instance on the Stage of an empty movie clip symbol.
The critical events are the affordances for the player: a fancy way of saying what the player can do. For this game, the player constructs a guess for the pattern by dragging the different colored circles to the slots. What actually happens is that a MouseDown event on a circle triggers the creation of a new Sprite which is then made into a circle. [Note: Sprites are movie clips without timelines.] This Sprite is made draggable. When the player releases the mouse, the MouseUp event triggers a call to a function that stops the dragging and then checks if all 4 slots have been filled. If that is not the case, nothing further happens. If it is the case, the calculation is done to provide feedback to the player.
At this point, I note that implementing the rules of the game means that the program starts with the generation of a hidden pattern. This is done in the usual way with Math.random and Math.floor. The hidden pattern is held (represented) by an array named answer, with 4 integer elements, each 0 to 5. These are indices into the colors array. NOTE: there is no internal representation using colored circles.
The feedback calculation involves first checking each of the 4 positions. An internal array called feedback is set accordingly. Another array, called present, is modified in any case when there is a match so that that position is removed. THEN a check is made if any circle not in the exact position matches anything in the present array. If it does, the feedback array is modified. This is necessary to prevent extraneous "present but not in the correct place" indications from being produced. The feedback array is used to create the appropriate blackpeg, whitepeg, and nopeg objects. If the calculation produces 4 matches, then the game is done and a text message is issued displaying the number of turns. This message is generating dynamically when the program first loads, but at first nothing is in it and then it is re-set to the empty string when there is a new try. The message is formatted (bigger than the default size and the color blue) using a dynamically created TextFormat object.
Re-starting the game, either with a new setting of the answer array or the same one, often is problematic. In this case, it involves removing all the created stuff except the circles on the left, and then re-creating the first set of guess slots. An array, called created, is used to keep references to all the created slots, circle sprites and 'pegs' just so they can be removed from the display. It also requires re-setting the variable guesscount that holds the number of tries and re-setting the guessposy variable to start the guess slots at the top. I changed the initial definition of the function for setting things up so that setting the hidden pattern is separate from setting up the circles.
The game is triggered by a call to setupguess in the setup function. The function settle (invoked by detection of a MouseUp event caused by player action) may invoke a call to givefeedback which, in turn, may invoke setupguess. When setting up something that uses recursion, it is important to think if the process stops. There are situations in which givefeedback does NOT invoke setupguess: namely, when the player enters in a match to the hidden pattern. In practicality, the game is limited because the feedback and the new slots will be positioned off the screen. The pattern should be guessed before this point. I do count on the player giving up and closing the application. This is an area for improvement!
Implementation
This application is entirely contained in the one .fla file. I could have put the code in the first frame in a Class Document file, but chose not to because unlike some of the previous applications, the coding seemed tied to the material in the .fla file. The screen shot shows what is on the Stage and in the Library.
You must create 4 movie clip symbols for the slot, blackpeg, whitepeg, and nopeg. For each of these, right click to get a menu, and then click on Linkage. Click to fill in the Export for ActionScript and then type in Blackpeg. You will get a message that no Class definition exists for Blackpeg and so Flash will use the default. It turns out that this is all you need for each of these 4 symbols. This is because the only thing needed is the properties that are inherited from Movieclip. [For a more complex situation, see the bouncing stuff application.]
I used Windows/Common Libraries/Button to get the buttons. Editing each symbol in the Library, I changed the texts to Try again and New pattern respectively. The hook movie clip symbol is an empty (no material) symbol. I bring an instance to the Stage, placing it the upper left corner, and give it the name hook to be used in the code when creating new objects.
You must place on the Stage the 6 Static text fields and instances of the blackpeg, whitepeg and nopeg movie clips that appear as part of the instructions. These instances do not need names because they are not referenced.
The code is in the first and only frame. The code includes:
import statements
variables
functions
free-standing statements executed upon loading, most critically the call to setup();
import flash.display.*; / for adding to the Display listimport flash.events.MouseEvent; / … mouse events
import flash.geom.*; / … drawing circles
import flash.text.*; / … creating and formatting text fields
var created:Array = []; / Used so that objects can be removed from display
var colors:Array = [ / Colors used for circles
0xFF0000,
0x00FF00,
0x0000FF,
0xCCCC00,
0xCC00CC,
0x00CCCC
];
var tformat:TextFormat = new TextFormat(); / For msg
tformat.size = 20; / slightly bigger text
tformat.color = 0x0000FF; / make it blue
var msg:TextField = new TextField(); / the game over message
hook.addChild(msg); / add to display list
msg.x = 10; / position it on the screen, towards the left
msg.y = 350; / … down the screen
msg.width = 152; / adjust the width
msg.height = 60; / … height
msg.setTextFormat(tformat); / use the tformat format
var bx:int = 50; / horizontal placement for circles
var bystart:int = 90; / starting vertical placement
var by:int = bystart; / initial vertical
var guess:Array = [-1,-1,-1,-1]; / will hold the guess
var guesscount:int = 0; / number of tries
var answer:Array=[-1,-1,-1,-1]; / will hold the hidden pattern
function setupanswer() { / function setting up answer
var i:int;
for (i=0; i<answer.length; i++) { / Looping through
answer[i]=Math.floor(Math.random()*colors.length); / … assign each element a random value 0 to 5
}
function setup() { / Set up (answer, colors)
var i:int;
setupanswer(); / Set up answer
for (i=0; i<colors.length; i++) { / Looping through
var circle1:Sprite = new Sprite(); / create a new Sprite
circle1.graphics.beginFill(colors[i]); / use graphics to make it the indicated color
circle1.graphics.drawCircle(bx, by, 20); / draw a circle at the indicated position
circle1.buttonMode = true; / Set buttonMode so the player sees the cursor change to a hand
circle1.addEventListener(MouseEvent.MOUSE_DOWN,mdown); / Set up event handling for MouseDown
circle1.name = "b"+String(i); / Will be used to determine which color was placed in a slot
by+=42; / Increment the vertical position for the next one
hook.addChild(circle1); / Add to display list
} / Close loop through colors
setupguess(); / Call setupguess to set up the first row of slots
} / Close function
var guessslots:Array = []; / Will hold the 4 guess slots
var guessposy:int = bystart-25; / set vertical position slightly above the bystart position
function setupguess() { / Function to set up a guess
guess = [-1,-1,-1,-1]; / Initialize to -1. This is what I use for not yet set values
guessslots = []; / Initialize guessslots. This must be done here since this function gets over and over
guesscount++; / Increment guesscount (the count of tries)
var i:int;
var gx:int = bx+100; / Initial horizontal position is over from bx.
var gy:int = guessposy; / Initial vertical is the current guessposy
for (i=0; i<answer.length; i++) { / Loop (it will iterate 4 times, but the coding is more flexible)
var s:Slot= new Slot(); / Create a new Slot object
guessslots.push(s); / Add it to the array guessslots
created.push(s); / Add it to the created array
hook.addChild(s); / Add to display list
s.x = gx; / Position in x
s.y = gy; / Position in y
gx+=61; / Increment the gx variable to move over for next time
} / Close the loop
guessposy +=61; / Increment the guessposy for the next set of 4 slots
bx = 50; / reset bx
} / Close the function
function mdown(event:MouseEvent):void { / Function that handles the player pushing the mouse button down on a circle
var picked:Sprite = event.target as Sprite; / Picked set by extracting from the event the target, treated as a Sprite.
var type:int = Number(picked.name.substr(1,1)); / Extract and convert to a number from the name of the circle Sprite. This will be the index into colors array
var circlep:Sprite = new Sprite(); / Create a new circle Sprite as before
circlep.graphics.beginFill(colors[type]); / set the color using type
circlep.graphics.drawCircle(0,0,20); / draw the circle
circlep.addEventListener(MouseEvent.MOUSE_UP,settle); / Set up handling for the event of lifting up the mouse button
circlep.name = String(type); / Give this circle Sprite a name
hook.addChild(circlep); / Make it visible
created.push(circlep); / Add to created array
circlep.x = bx; / Position it on top of the circle button in x
circlep.y = bystart+type*45; / …. y
circlep.startDrag(true); / Start dragging
} / Close function
function settle(event:MouseEvent):void { / Function that handles the event of the mouse button released when dragging a circle
var i:int;
var s:Sprite = event.target as Sprite; / Extract the Sprite
s.stopDrag(); / Stop dragging
for (i=0; i<answer.length; i++) { / Loop to check which slot
if (guessslots[i].hitTestPoint(s.x,s.y)) { / Is this Sprite's x and y within the ith guessslot?
guess[i] = Number(s.name); / Set up guess value for the ith position by converting the name to a number
break; / break out of loop since the slot was found
} / Close if
} / Close loop
givefeedback(); / Invoke givefeedback
} / Close function
function givefeedback() { / function checking if time to give feedback
var present:Array = answer.slice(); / present is copy of the answer array
var i:int;
var j:int;
for (i=0; i<answer.length; i++) { / Loop to check if there is a valid value for each element of guess