Tutorial: Creating a State Capital game



Tutorial: Creating a State Capital game

My goals in creating this game was to build a Flash ActionScript 3.0 version of the game I had done in an older version of Flash and to separate the coding so that a different map, say countries and capitals of Central America, could be swapped in with a minimal amount of effort. I then realized that some of the text messages used the term 'state', which would be inappropriate in a quiz involving countries. I decided to separate the text messages from the code and input them using an XML file.

Note: my separation of text could be used for different languages, but only if the grammar corresponds to English.

The opening screen is the following:

[pic]

Here is the screen after a wrong answer (clicking on Florida, say) after a question in which the correct answer is to click on New Mexico.

[pic]

The two Identify questions are presented by fading out all but one state:

[pic]

The correct answer in this case would be Oregon, which the player would indicate by typing in Oregon into the box. The game would respond:

[pic]

The most intricate question is the List neighbors (no map). All the states except one are made invisible (actually, alpha set to zero).

[pic]

As the player correctly types in the names of the neighbors, they re-appear on the map and the names are included. The game indicates "Okay" and not "Correct" because the question is not completely answered. For this and for other questions, it is possible to correct a wrong answer.

[pic]

The player must indicate explicitly when he or she is done by typing in an X. At this point, the game will produce "Correct".

Note: the game requires perfect spelling. This is a potential area for improvement! A related possibility is to allow the official 2 letter abbreviations.

Implementation

The implementation consists of 3 files: usstatesnew.fla, State.as, and texts.xml. The texts.xml is accessed during run time to populate the various variables holding Strings used for the questions and the follow-ups (e.g, CORRECT!). The State class definition is part of a package called statepuzzle. In concrete terms, this means the State.as file is in a folder named statepuzzle. The .fla file has a statement:

import statepuzzle.*;

and the Publish Settings are set to point to a folder containing the statepuzzle folder. The State.as file has code to load the texts.xml file.

I added the questions one by one and that would be the best way to implement such a game. However, I will describe the final (for now) implementation. The first step in any case is designing the how information is kept and creating the map elements. I took a map of the United States and cut out each state and made it into its own movie clip symbol. Unlike the jigsaw puzzle (see the jigsaw tutorials), the registration point/transformation point is not changed. All state symbols have the same registration point. The following screen shot of the Flash environment shows the symbol for New York (symbol name newyork and instance name newyork) as well as the colorado movie clip symbol in the Library. Notice the position of the cross-hairs for each one.

[pic]

This means that the states are all put together when they are at the same place—that is, when their x,y are the same. Actually, I moved Hawaii and Alaska slightly. They are not to scale nor positioned accurately in most maps, so this game is no different.

NOTE: it is important to be able to select the individual states within the map image. Images created within Flash have the characteristic that a patch of the same color can be selected. This is part of so-called vector graphics. Bitmaps do not have this characteristic. To turn a bitmap into a base that you can work with to select the individual states, do the following:

1. Insert New Symbol (movie clip). You can call it base.

2. Click on File/Import/Import to Stage. Then browse to the image file you have identified.

3. Click on Modify/Bitmap/Trace Bitmap. This will produce a vector graphic version of the image.

My way of encoding the information is to define a State class. The object variables for each State would include the state name, capital and a reference to the movie clip symbol that shows the state graphically. There would be many class variables as well as several class methods. One of the class variables is an array that holds for each state an array that lists all the border (neighboring) states. It is in alphabetical order by state. [Note: alphabetical order by the full state name. This is slightly different than by abbreviation.] Put more exactly, this array is set up through a call to the setup method. The State.as file is independent of any particular geographic data. The names and capitals and the borders was laborious to put in. For the previous version, I wrote code that calculated the borders and output the information using traces but I wasn't that confidant and did it this time 'by hand'.

Building the usstatesnew.fla file

The usstatesnew.fla file, then, consists of symbols for each state in the Library. Bring an instance of each one to the Stage and give it a name. Make the X Y for each state be the same thing. This will have the effect of reconstructed the map. As I indicated, you can take liberties with Hawaii and Alaska.

Go to Insert/New Symbol and create a new symbol called hook. Move an instance to the Stage and name it hook. Place this on the Stage right below the map. It will be used to position the rest of the interface. Specifically, the State class method setup is invoked with hook as one of the parameters. The code for setting up the radio buttons and other parts of the interface are generated during runtime.

[pic]

Go to Window/Component and move a Radiobutton and a Button to the Library.

Go to File/Publish Settings Flash Settings and browse to the as3 folder (this is my standard that I do for all of these. It will contain a folder named statepuzzle that has a file named State.as).

The code for usstatenew.fla (go to Windows/Action and put the code in the first and only frame) is:

import statepuzzle.*;

var neighbors:Array = [];

var s1:State = new State("Alabama","Montgomery",alabama);

neighbors.push(["Florida","Georgia","Tennessee","Mississippi"]);

var s2:State = new State("Alaska","Juneau",alaska);

neighbors.push([]);

var s3:State = new State("Arizona","Phoenix",arizona);

neighbors.push(["New Mexico","Colorado","Utah","Nevada","California"]);

var s4:State = new State("Arkansas","Little Rock",arkansas);

neighbors.push(["Louisiana","Mississippi","Tennessee","Missouri","Oklahoma","Texas"]);

var s5:State = new State("California","Sacramento",california);

neighbors.push(["Arizona","Nevada","Oregon"]);

var s6:State = new State("Colorado","Denver",colorado);

neighbors.push(["Arizona","New Mexico","Oklahoma","Kansas","Nebraska","Wyoming","Utah"]);

var s7:State = new State("Connecticut","Hartford",connecticut);

neighbors.push(["New York","Rhode Island","Massachusetts"]);

var s8:State = new State("Delaware","Dover",delaware);

neighbors.push(["New Jersey","Pennsylvania","Maryland"]);

var s9:State = new State("Florida","Tallahassee",florida);

neighbors.push(["Georgia","Alabama"]);

var s10:State = new State("Georgia","Atlanta",georgia);

neighbors.push(["Florida","Alabama","Tennessee","South Carolina","North Carolina"]);

var s11:State = new State("Hawaii","Honolulu",hawaii);

neighbors.push([]);

var s12:State = new State("Idaho","Boise",idaho);

neighbors.push(["Washington","Oregon","Nevada","Utah","Wyoming","Montana"]);

var s13:State = new State("Illinois","Springfield",illinois);

neighbors.push(["Wisconsin","Iowa","Indiana","Kentucky","Missouri"]);

var s14:State = new State("Indiana","Indianapolis",indiana);

neighbors.push(["Illinois","Michigan","Ohio","Kentucky"]);

var s15:State = new State("Iowa","Des Moines",iowa);

neighbors.push(["Missouri","Illinois","Wisconsin","Minnesota","South Dakota","Nebraska"]);

var s16:State = new State("Kansas","Topeka",kansas);

neighbors.push(["Oklahoma","Missouri","Nebraska","Colorado"]);

var s17:State = new State("Kentucky","Frankfort",kentucky);

neighbors.push(["Missouri","Tennessee","Virginia","West Virginia","Ohio","Indiana","Illinois"]);

var s18:State = new State("Louisiana","Baton Rouge",louisiana);

neighbors.push(["Mississippi","Arkansas","Texas"]);

var s19:State = new State("Maine","Augusta",maine);

neighbors.push(["New Hampshire"]);

var s20:State = new State("Maryland","Annapolis",maryland);

neighbors.push(["Delaware","Pennsylvania","Virginia","West Virginia"]);

var s21:State = new State("Massachusetts","Boston",massachusetts);

neighbors.push(["New Hampshire","Vermont","Connecticut","Rhode Island","New York"]);

var s22:State = new State("Michigan","Lansing",michigan);

neighbors.push(["Ohio","Indiana","Wisconsin"]);

var s23:State = new State("Minnesota","St. Paul",minnesota);

neighbors.push(["North Dakota","South Dakota","Iowa","Wisconsin"]);

var s24:State = new State("Mississippi","Jackson",mississippi);

neighbors.push(["Alabama","Louisiana","Arkansas","Tennessee"]);

var s25:State = new State("Missouri","Jefferson City",missouri);

neighbors.push(["Arkansas","Oklahoma","Kansas","Nebraska","Iowa","Illinois","Kentucky","Tennessee"]);

var s26:State = new State("Montana","Helena",montana);

neighbors.push(["Idaho","Wyoming","North Dakota","South Dakota"]);

var s27:State = new State("Nebraska","Lincoln",nebraska);

neighbors.push(["Wyoming","Colorado","Kansas","Missouri","Iowa","South Dakota"]);

var s28:State = new State("Nevada","Carson City",nevada);

neighbors.push(["California","Oregon","Idaho","Utah","Arizona"]);

var s29:State = new State("New Hampshire","Concord",newhampshire);

neighbors.push(["Vermont","Massachusetts","Maine"]);

var s30:State = new State("New Jersey","Trenton",newjersey);

neighbors.push(["Pennsylvania","Delaware","New York"]);

var s31:State = new State("New Mexico","Santa Fe",newmexico);

neighbors.push(["Texas","Arizona","Colorado","Utah","Oklahoma"]);

var s32:State = new State("New York","Albany",newyork);

neighbors.push(["Vermont","Massachusetts","Connecticut","New Jersey","Pennsylvania"]);

var s33:State = new State("North Carolina","Raleigh",northcarolina);

neighbors.push(["South Carolina","Georgia","Tennessee","Virginia"]);

var s34:State = new State("North Dakota","Bismarck",northdakota);

neighbors.push(["Minnesota","South Dakota","Montana"]);

var s35:State = new State("Ohio","Columbus",ohio);

neighbors.push(["Michigan","Indiana","Pennsylvania","West Virginia","Kentucky"]);

var s36:State = new State("Oklahoma","Oklahoma City",oklahoma);

neighbors.push(["Texas","New Mexico","Colorado","Kansas","Missouri","Arkansas"]);

var s37:State = new State("Oregon","Salem",oregon);

neighbors.push(["Washington","Idaho","Nevada","California"]);

var s38:State = new State("Pennsylvania","Harrisburg",pennsylvania);

neighbors.push(["New York","New Jersey","Delaware","Maryland","West Virginia","Ohio"]);

var s39:State = new State("Rhode Island","Providence",rhodeisland);

neighbors.push(["Connecticut","Massachusetts"]);

var s40:State = new State("South Carolina","Columbia",southcarolina);

neighbors.push(["Georgia","North Carolina"]);

var s41:State = new State("South Dakota","Pierre",southdakota);

neighbors.push(["Iowa","Minnesota","North Dakota","Montana","Wyoming","Nebraska"]);

var s42:State = new State("Tennessee","Nashville",tennessee);

neighbors.push(["Mississippi","Alabama","Georgia","North Carolina","Virginia","Kentucky","Missouri","Arkansas"]);

var s43:State = new State("Texas","Austin",texas);

neighbors.push(["Louisiana","Arkansas","Oklahoma","New Mexico"]);

var s44:State = new State("Utah","Salt Lake City",utah);

neighbors.push(["New Mexico","Arizona","Nevada","Idaho","Wyoming","Colorado"]);

var s45:State = new State("Vermont","Montpelier",vermont);

neighbors.push(["New York","New Hampshire","Massachusetts"]);

var s46:State = new State("Virginia","Richmond",virginia);

neighbors.push(["Maryland","West Virginia","Kentucky","Tennessee","North Carolina"]);

var s47:State = new State("Washington","Olympia",washington);

neighbors.push(["Idaho","Oregon"]);

var s48:State = new State("West Virginia","Charleston",westvirginia);

neighbors.push(["Pennsylvania","Maryland","Virginia","Kentucky","Ohio"]);

var s49:State = new State("Wisconsin","Madison",wisconsin);

neighbors.push(["Michigan","Minnesota","Iowa","Illinois"]);

var s50:State = new State("Wyoming","Cheyenne",wyoming);

neighbors.push(["Idaoho","Montana","South Dakota","Nebraska","Colorado","Utah"]);

State.setup(hook,neighbors);

NOTE: you can omit the setting up of neighbors and get the rest of the game working.

Building the State.as file

The State.as file contains the working code for the game. This code will work for any map.

Recall that static methods and variables mean that there is just one for the whole application. The other methods and variables are one for each object instance. So there is just one states array that holds all the states. In contrast, the variable statename exists for each State. There are two object methods: the constructor State and the function handleclick that detects that the movie clip instance representing the State was clicked on. All the other methods are class methods.

The setup method sets up the board, most specifically the radio buttons and a text field for input and 3 places for information for the player. The texts used in each of these were originally spread throughout the code. I then put them in variables. Finally, I include code that reads in the information from an XML file and replaces what is there. You can look at what is in the code to see the intent of the messages.

internal static var leadins:String = "Select type of question then click NEXT button ";

internal static var questiontexts:Array=

["Find state","Find state with capital","Identify state",

"Identify capital","List neighbors", "List neighbors (no map)",

"Pick your own"

];

internal static var correctokwrong:Array = [

"Correct",

"Okay",

"Wrong"

]

internal static var nextlabel:String = "NEXT";

internal static var moretexts:Array = [

" Now click what you named. ",

"Click on state named ",

"Click on state with capital ",

"Type name of highlighted state",

"Type capital of highlighted state",

"Type one at a time neighbors of ",

"Neighbors: ",

"Type one at a time neighbors of ",

"Type a state name then click on it in the map",

". Type X when done."

];

There are 7 questions, represented by the array questiontexts that holds what will be the labels for the radio buttons and the array methods

internal static var methods:Array=[askstate, askcapital, namestate,

namecapital, listneighbors, listneighborsnomap, pick];

These are the names of the methods that set up the handling for the questions. In most cases (all but the last, pick), this includes using Math.random to choose a State at random on which to base the question. The questiontexts and the methods arrays are what are called parallel structures. IF YOU WANT TO ADD ANOTHER QUESTION: you need to add an element to both arrays. This would mean adding to the xml file, also, to get the appropriate texts. You would need to modify and add to one or both of the handleclick and the getinput methods. The handleclick is the handler for clicking on a state and the getinput is the handler for typing in and clicking the enter key for the input text field named ans.

The handling of the interface after set up and during play can be thought of as being in three time periods: waiting for a radio button to be clicked determining the type of question, setting up the question, and then handling the player response. The responses are either clicking somewhere on the map or filling in the text input field. The methods that detect responses use various Boolean variables to determine which type of question is being asked or, to put it more precisely, how to check the answer. Note: I made this a rather loose design: a player can try again after getting something wrong (or after getting something correct). A player can call for a new question (click on the NEXT button or select a different one of the radio buttons and click on the NEXT button) even before answering a question. I saw this as more of a way to test myself on the states than as a contest. The code would need to be changed to make the game strict. I did make the find neighbors puzzle produce the complete list of neighboring states if the player clicked the X prematurely.

General comments on [my] jargon: A Boolean value, that is, one that can be true or false, is often called a flag. Something constructed using a class definition is called an object. The method named State is termed the constructor method for the class State. In Flash, movie clip instances also are objects. There also are Radiobutton objects, Button objects, etc. These things also may be called object instances. It is important to make sure the movie clip instances are given names. In this application, there are State objects. They are constructed by calls to the State method passing in the state name, the capital name and the movie clip instance name. Each constructed State will contain a reference to a movie clip instance. The reference is in the object variable mclip. Note that the use of the variable names statename, states, etc. are not shown to the player, so if you re-do this game (use the State.as file, but make your own .fla file) with countries and change the texts.xml file, everything should work. HOWEVER, do note that the coding assumes that a player is asked to use X to indicate he/she is through with naming neighbors.

|package statepuzzle{ |package open |

| import flash.text.*; | |

| import flash.events.*; | |

| import fl.events.*; | |

| import flash.display.*; | |

| import fl.controls.*; | |

| import fl.ponentEvent; | |

| import .*; | |

| public class State { |class definition open |

| internal static var states:Array=[]; |array of all state objects constructed |

| internal static var hook:MovieClip; |used for displaying the interface |

| internal static var ins:TextField; |instructions |

| internal static var ans:TextInput; |player input |

| internal static var work:TextField; |where the neighbors will go |

| internal static var results:TextField; |correct or wrong or okay |

| internal static var rbg:RadioButtonGroup=new RadioButtonGroup("gp"); |The group for the radio buttons (holding the |

| |questions) |

| internal static var rbystart:int=62; |for vertical placement of radio buttons |

| |(relationship to the hook object passed in) |

| internal static var borderq:Boolean; |indicates if this is a neighbor question |

| internal static var pickq:Boolean; |indicates if this is a pick your own question |

| internal static var bordernomapq:Boolean; |indicates if this is a neighbor with no map |

| |question |

| internal static var myXML:XML = new XML(); |For the texts.xml file |

| internal static var XML_URL:String = "texts.xml"; |name of file |

| internal static var myXMLURL:URLRequest = new URLRequest(XML_URL); |the request to fetch the file |

| internal static var myLoader:URLLoader = new URLLoader(); |the URLLoader object |

| internal static var leadins:String = "Select type of question then click NEXT button "; |The initial message |

| internal static var questiontexts:Array=["Find state","Find state with capital","Identify state",|Texts for the questions |

|"Identify capital","List neighbors", "List neighbors (no map)", "Pick your own" ]; | |

| internal static var correctokwrong:Array = ["Correct","Okay","Wrong"] |Texts for the results |

| internal static var nextlabel:String = "NEXT"; |Text for the next question button label |

| internal static var moretexts:Array = [ |Miscellaneous texts |

| " Now click what you named. ", | |

| "Click on state with capital ", | |

| "Click on state with capital ", | |

| "Type name of highlighted state", | |

| "Type capital of highlighted state", | |

| "Type one at a time neighbors of ", | |

| "Neighbors: ", | |

| "Type one at a time neighbors of ", | |

| "Type a state name then click on it in the map", | |

| ". Type X when done." | |

| ]; | |

| internal static var methods:Array=[askstate,askcapital,namestate, namecapital,listneighbors, |Holds the method names corresponding to the |

|listneighborsnomap,pick]; |questions indicated by the radio buttons |

| internal static var nextbtn:Button=new Button; |Part of interface |

| internal static var answertoquestion:State; |Used for certain questions |

| internal static var textanswer:String=""; |Used for certain questions |

| internal static var bd:Array; |Used for the neighbor questions |

| internal static var borders:Array; |Holds the neighbor information (set in setup) |

| internal static var n:int; |Set to be number of state objects constructed. |

| |This is to save calling states.length again and |

| |again. |

| |The next 3 are the 3 object variables: one per |

| |state constructed |

| internal var statename:String; |name of state |

| internal var capital:String; |name of state capital |

| internal var mclip:MovieClip; |the movie clip symbol that shows the state |

| | |

| public function State(sn:String,cn:String,mc:MovieClip):void { |Constructor function |

| statename=sn; |Sets object variable for the state name |

| capital=cn; |…. the capital name |

| mclip=mc; |…. the movie clip symbol |

| mclip.addEventListener(MouseEvent.CLICK,handleclick); |sets up event handling for the mouse click on |

| |this state |

| states.push(this); |add this state to the states array |

| n = states.length; |re-sets n so it will hold states.length. |

| } |close the State method |

| | |

| public static function setup(ahook:MovieClip,bd:Array):void { |Class method (just one) to do the setting up of |

| |the interface (radio buttons, next button, text |

| |fields, input text field) |

| borders=bd; |Set borders data |

| hook=ahook; |Set hook |

| ins=new TextField ; |Create new text field |

| ans=new TextInput ; |Create new text input field |

| work=new TextField ; |Create new text field |

| results=new TextField ; |Create new text field |

| hook.addChild(ins); |Add to display list, using hook. this also |

| |positions each object in terms of hook |

| hook.addChild(ans); | |

| hook.addChild(work); | |

| hook.addChild(results); | |

| ins.width=430; |reset widths |

| work.width=630; | |

| ans.width=200; | |

| results.width=200; | |

| ans.x=640; |move over |

| work.y=40; |move down |

| results.x=640; |move over |

| results.y=40; |…. and down |

| | |

| ans.text=""; |Put in initial text of blanks. |

| work.text=""; | |

| results.text=""; | |

| ans.addEventListener(ComponentEvent.ENTER,getinput); |Set up event handling for the text input field |

| | |

| myLoader.load(myXMLURL); |Start loading of the xml file |

| myLoader.addEventListener(PLETE,finishsetup); |Set up event handling for the xml file. NOTICE: |

| |no error handling |

| } |Close setup method |

| | |

| internal static function finishsetup(ev) { |Method that finishes the setup. This is the |

| |handler for the xml file complete loading |

| var i:int; |Used for iterations |

| myXML=XML(myLoader.data); |Creates an XML object. NOTICE: no error handling|

| |such as try and catch. |

| //trace("myXML is "+myXML); |You can uncomment this line to see the XML file |

| leadins = myXML.leadins; |Set this value and |

| ins.text= leadins; |… use it immediately. This is done to make the |

| |coding consistent. The leadins value does not |

| |need to be saved. |

| nextlabel = myXML.nextlabel; |Set this value and |

| nextbtn.label=nextlabel; |… use it immediately for the button label. This |

| |is to make the coding consistent. |

| correctokwrong[0] = myXML.correct; |Set values used for results: the good value |

| correctokwrong[1] = myXML.okay; |the okay value |

| correctokwrong[2] = myXML.wrong; |the wrong/bad value |

| i=0; |Because this is a for each, the iteration for |

| |questiontexts needs to be done explicitly |

| for each (var qt:XML in myXML.qtext) { |Sets successive elements of the questiontexts |

| |array |

| questiontexts[i] = qt[0]; |Set the value from the xml |

| i++; |increment i |

| } |close for each |

| for (i=0; i < questiontexts.length; i++) { |This iteration builds the RadioButton objects, |

| |positions them, sets the label |

| var rb:RadioButton=new RadioButton; |create a new radiobutton |

| rb.group=rbg; |associate it with the group |

| rb.x=10; |position it horizontally |

| rb.y=rbystart; |position vertically |

| rbystart+= 25; |… then increase rbystart for the next one |

| rb.setSize(200,30); |set the size |

| rb.label=questiontexts[i]; |get the label from questiontexts |

| rb.value=i; |set the value |

| hook.addChild(rb); |display by adding to hook |

| } |close for loop |

| | |

| hook.addChild(nextbtn); |display the button |

| nextbtn.x=200; |position the button horizontally |

| nextbtn.y=rbystart; | and vertically below the last radio button |

|nextbtn.addEventListener(MouseEvent.CLICK,getquestion); |Event handling is done for the next button (not |

| |for clicking on the radio buttons) |

|} |close the finishsetup method |

| | |

| internal static function getquestion(ev):void { |method for handling clicking on the button |

| var q:int=Number(rbg.selectedData); |picks up the data that will indicate which |

| |question |

| methods[q](); |Now use that number to invoke a specific method |

| } |close getquestion method |

| | |

| internal function handleclick(ev) { |handleclick method for handling (responding to) |

| |the event of clicking on a state |

| if (pickq) { |Determination done differently depending on if |

| |this is a pick your own or one of the find |

| |questions |

| if (this.statename==ans.text) { |(pick your own) does the name of this state |

| |match what the player typed into the ans field |

| results.text = correctokwrong[0]; |…good |

| } else { | |

| results.text = correctokwrong[2]; |… bad |

| } | |

| } else if (this == answertoquestion) { |Not a pick your own, so use the answertoquestion|

| |data to compare with this |

| results.text=correctokwrong[0]; |… good |

| } else { | |

| results.text=correctokwrong[2]; |… bad |

| } | |

| } |close method |

| | |

| internal static function getinput(ev) { |class method getinput. Handles the player typing|

| |and then clicking on the enter key. |

| if (pickq) { |If this is a pick your own |

| ins.text=moretexts[0]; | output a message |

| | |

| } else if (borderq) { |if one of the two neighbor questions |

| if (ans.text == "X") { | is the player saying it is all done? |

| if (bd.length == 0) { |If this is true, |

| results.text=correctokwrong[0]; |…. good |

| } else { |if not |

| results.text=correctokwrong[2]; |… bad |

| for (var j:int = 0; j ................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download