Saturday, 28 April 2012

Game Start

After a short break, it's time to get back to the game portion of project.


Firstly, the test code in the case STATE_INTRO block needs to be removed.  This is replaced with the last two commented out lines from the previous block.
// Workaround to ensure frame rate ok before menu is called
if timer() > 3.0 then setGameState( STATE_MENU )
The comment slashes (//) have been removed from the second line and the value increased from, 2.0 to 3.0 to allow for loading time.

This advances the game state from STATE_INTRO to STATE_MENU after the game has been running three seconds.

Now the beginning the select statement should look like this.
   select GameState()
      case STATE_SETUP
         // Setup Code
         testNum = LoadObjectData( "standard" )
         setGameState( STATE_INTRO )
      endcase
      case STATE_INTRO
         // Intro Code
         // Workaround to ensure frame rate ok before menu is called
         if timer() > 3.0 then setGameState( STATE_MENU )
      endcase
      case STATE_DEMO
         // Demo Code
      endcase
      case STATE_MENU
         // Menu Code
We transition out of the menu, what is needed next is a transition into the game.

At the moment, the panel text Objects - used to open and close the panel - is visible as soon as the project starts, it should really only become visible once a game starts.

This is done by making it invisible, just after it's depth is set in the Initialise() function.
setTextVisible( panelText() , 0 )
This uses the setTextVisible() command with a 0 parameter.  Now the text will not be visible during the setup, initialise and menu states.

Now as we did with the menu, we can check if this is visible to determine when the game block is being called for the first time.

Inside the case STATE_RUNNING, block, the text is checked to see if it is visible.
if getTextVisible( PanelText() ) = 0
   // Game starting 
   setTextVisible( panelText() , 1 )
   setTextColor( panelText() , 127 , 255 , 127 , 0 )
   PositionPanelIcons()
   //  Activate Transition
   StartTransition( TRANS_FADEIN , MENU_FADE )
If the text is not visible, it is made visible and it's colour set, including an alpha value of zero to make it fully transparent.

The PositionPanelIcons() function is used to make sure the panel is in the right position, then - as with the menu - a transition is started.  This uses the parameters from the menu transition at the moment.

The next block of code is very similar to that used in the menu.
elseif (Transition() = TRANS_FADEIN) or (Transition() = TRANS_FADEOUT) 
   thisAlpha = 255.0 * TransitionCount()
   if Transition() = TRANS_FADEOUT then thisAlpha = 255 - thisAlpha
   setTextColorAlpha( panelText() , thisAlpha )
   if TransitionCount() = 1.0
      if Transition() = TRANS_FADEOUT
         // Game Exiting    
      
      else
         // Game Starting
      
      endif
      StopTransition()     
   endif
endif
Here only the alpha of the panel text is changed, though the method is identical.

An empty if/else/endif structure has been used in the middle as a place to put code for the end of the fade transition, when added.

The existing call to checkGamePointer() now follows this code.

With this, the word Objects at the top of the panel should fade in when the game starts.

Monday, 23 April 2012

Loading Icons

At the moment, the icon images are loaded in the Initialise() function with the line.
display.iconImage = SafeloadImage( "objects.png" )
This now needs to be changed to.
display.iconImage = 0
and the loading takes place within the outer if/elseif loop of the LoadObjectData() function.
if getFileExists( thisTextName$ ) and thisImageName$ <> ""
   // Both data files exist
   display.iconImage = SafeloadImage( thisImageName$ )
   // Prepare temp array
   dim tempArray[ 1000 ] as string
This uses the variable thisImageName$ rather than an absolute name.

We are almost ready to be able to see the loaded object in the game, there is just one more thing to add.

Since we are storing the groups in the same array as the standard objects, we need a quick way to identify them.  This saves having to search the array each time we want an object group.

These can be stored in the same way group members are stored, as a string of Base96 Codes, and as with the group members, this needs to be done after the object array is set up so that the indexes are correct.

The group list is stored in a string field of the gameType UDT.
type GameType
   state
   level
   topObject
   groupObject$
endtype
It has the usual return function.
function GameGroups()
endfunction game.groupObject$
And is set up in the Initialise() function.
game.groupObject$ = ""
AND in the LoadObjectData() function.
// Group Memberships
game.groupObject$ = ""
for i=1 to topObject()
All pretty familiar stuff by now.

Just inside the if ObjectIsGroup( i ) > 0 block, we put the line which adds the group to the list.
if ObjectIsGroup( i ) > 0 and ObjectDetail( i ) <> ""
   game.groupObject$ = game.groupObject$ + Base96Encode( i )   
This means that only groups which contain objects will be included in the group list.

After the for/next loop has completed, near the end of the function, the final list is sorted.
      next i
      game.groupObject$ = Base96Sort( GameGroups() )
   endif
endfunction thisArraySize
Now we not only have a list of groups, but we know that they are only the groups with member objects.


End of Week 2 (ish)

I took a break after week one, so allowing for that, this is the end of the second week of posts.

The Blog is closer to three weeks old, but either way, it's come a long way.

We now have object loaded from a data file, but to see them in the game, needs an updated icon sheet.

Here is the source, without transparency - to go in the artwork folder with the name standard source.png.
Here, I filled the magenta bits in orange to help me line up the new graphics.  Filling the orange grid with magenta when all the objects have been added will put this back to normal.

And this is the transparent one for the media folder with the name standard.png.
They have both been optimised to keep the sizes down.

And here's a complete code check  - Just over 900 lines - to keep everything in sync.

// 23 April 2012 - Tabs converted to 3 spaces
// Constants must go at start of program

#constant PANEL_ROWS   3
#constant PANEL_COLS   4

#constant DEPTH_MENU   10
#constant DEPTH_SCORE   20
#constant DEPTH_PANEL   30
#constant DEPTH_WORK   40

#constant MAX_WORKOBJECTS   100

#constant FRAME_HIGHLIGHT   251
#constant FRAME_TRASHCAN      252

#constant STATE_SETUP   0
#constant STATE_INTRO   1
#constant STATE_DEMO   2
#constant STATE_MENU   3
#constant STATE_RUNNING   4
#constant STATE_EXIT   99

#constant MENU_FADE      1.0
#constant MENU_ITEMS      2

#constant MENU_DIVTOP   0.25
#constant MENU_DIVSIZE   0.10

#constant MENU_RED      255
#constant MENU_GREEN   165
#constant MENU_BLUE      0
#constant MENU_REDHI   255
#constant MENU_GREENHI   255
#constant MENU_BLUEHI   0

#constant TRANS_NONE   0
#constant TRANS_FADEIN   1
#constant TRANS_FADEOUT   2

// User Defined Types - custom data structures

type displayType
   physWidth#
   physHeight#
   virtWidth#
   virtHeight#
   iconSize#
   textHeight#
   lastFPS#
   iconImage
   highSprite
   transType
   transCount#
   transStep#
endtype

type touchType
   pointDX#
   pointDY#
   pointWX#
   pointWY#
   spritePicked
   objectPicked
   menuPicked
   pickOffsetX#
   pickOffsetY#
endtype

type panelType
   height#
   speed#
   isOpen
   image
   sprite
   text
   sound
   iconMax
endtype

type WorkObjectType
   isFree
   objectID
   sprite
endtype

type GameType
   state
   level
   topObject
endtype

type MainObjectType
   name$
   detail$
   icon
   isKnown
   isGroup
endtype

// I must remember to put this somewhere

global numWorkObjects = 0

Initialise()

// Temporary Objects

imageRef = SafeloadImage( "finger.png" )
spriteRef = createSprite( imageRef )

// Main Loop
do
   LoopStart()
   select GameState()
      case STATE_SETUP
         // Setup Code
         testNum = LoadObjectData( "standard" )
         setGameState( STATE_INTRO )
         // Workaround to ensure frame rate ok before menu is called
         // if timer() > 2.0 then setGameState( STATE_MENU )
      endcase
      case STATE_INTRO
         // Intro Code
         print(" Objects : " + str( TopObject() ) )
         for i=1 to TopObject()
            printc( str(i) + " : " + ObjectName( i ) + " : " + str( ObjectIcon( i) ) )
            if ObjectIsGroup( i ) > 0
               printc( ", Group: ")
               thisString$ = ObjectDetail( i )
               thisNum = len( thisString$ ) / 2
               for j = 1 to thisNum
                  thisCode$ = mid( thisString$ , j*2 - 1 , 2)
                  thisObject = Base96Decode( thisCode$ )
                  printc( ObjectName( thisObject ) )
                  if j < thisNum then printc( ", " )
               next j
               print( "." )
            else
               print( ", " + chr(34) + ObjectDetail( i ) + chr( 34 ))
            endif
         next i
         print( " ----- ")
      endcase
      case STATE_DEMO
         // Demo Code
      endcase
      case STATE_MENU
         // Menu Code
         if getTextVisible( menuText[ 1 ]) = 0
            // Menu starting - Activate Transition
            StartTransition( TRANS_FADEIN , MENU_FADE )
            thisX# = VirtWidth() / 2.0
            thisY# = VirtHeight() * MENU_DIVTOP
            thisSize# = VirtHeight() * MENU_DIVSIZE
            for i=1 to MENU_ITEMS
               setTextSize( menuText[ i ] , thisSize# )
               setTextAlignment( menuText[ i ] , 1 )
               setTextDepth( menuText[ i ] , DEPTH_MENU )
               setTextPosition( menuText[ i ] , thisX# , thisY# )
               setTextVisible( menuText[ i ] , 1 )
               setTextColor( menuText[ i ] , MENU_RED , MENU_GREEN, MENU_BLUE , 0 )
               thisY# = thisY# + getTextTotalHeight( menuText[ i ] ) * 2.0
            next i
         elseif (Transition() = TRANS_FADEIN) or (Transition() = TRANS_FADEOUT)
            thisAlpha = 255.0 * TransitionCount()
            if Transition() = TRANS_FADEOUT then thisAlpha = 255 - thisAlpha
            for i=1 to MENU_ITEMS
               setTextColorAlpha( menuText[ i ] , thisAlpha )
            next i
            if TransitionCount() = 1.0
               if Transition() = TRANS_FADEOUT
                  // Menu Exiting
                  for i=1 to MENU_ITEMS
                     setTextVisible( menuText[ i ] , 0 )
                  next i
                  select MenuPicked()
                     case 1
                        // Menu Option 1
                        setGameState( STATE_RUNNING )
                     endcase
                     case 2
                        // Menu Option 2
                        setGameState( STATE_EXIT )
                     endcase
                  endselect
               endif
               StopTransition()
            endif
         endif
         checkMenuPointer()
      endcase
      case STATE_RUNNING
         // Running Code
         checkGamePointer()
      endcase
      case default
         // Picks up unknown states - including STATE_EXIT
         exit
      endcase
   endselect

   // Position temporary finger sprite
   setSpritePosition( spriteRef , PointDX() , PointDY() )

   PrintDiagnostics()
   sync()
loop

end

// Loop Functions

function LoopStart()
   display.lastFPS# = screenFPS()
   panel.speed# = panelHeight() / (lastFPS() * 1.25)
   touch.pointDX# = getPointerX()
   touch.pointDY# = getPointerY()
   touch.pointWX# = ScreentoWorldX(PointDX())
   touch.pointWY# = ScreentoWorldY(PointDY())
   if Transition() > TRANS_NONE then UpdateTransition()
endfunction

// Game Functions

function checkGamePointer()
   if getPointerPressed() > 0
      if PointDY() >= getTextY( panelText() ) and PointDY() < getSpriteY( panelSprite() )
         ToggleTrayOpen()
      elseif PointDY() < getTextY( panelText() )
         touch.spritePicked = getSpriteHit( PointWX() , pointWY() )
         if SpritePicked() > 0
            touch.objectPicked = FindWorkObjectBySprite( SpritePicked() )
            if ObjectPicked() > 0
             touch.pickOffsetX# = PointWX() - getSpriteXByOffset( SpritePicked() )
             touch.pickOffsetY# = PointWY() - getSpriteYByOffset( SpritePicked() )
            endif
         endif
      endif
   elseif getPointerReleased() > 0
      if thisObjectPicked > 0
         thisX# = PointWX() - PickOffsetX()
         thisY# = PointWY() - PickOffsetY()
         HideHighLight()
         if WorldToScreenY( thisY#) >= getTextY( panelText() )
            DelWorkObject( ObjectPicked() )
         else
            setSpritePositionByOffset( SpritePicked() , thisX# , thisY# )
            setSpriteDepth( SpritePicked() , DEPTH_WORK - 1 )
            thisObjectUnder = WorkObjectIsOver( ObjectPicked() )
            if thisObjectUnder > 0
               // This is where the merge objects code goes
            endif
         endif
      endif
      touch.spritePicked = 0
      touch.objectPicked = 0
      touch.pickOffsetX# = 0.0
      touch.pickOffsetY# = 0.0
   elseif getPointerState() > 0
      if ObjectPicked() > 0
         thisX# = PointWX() - PickOffsetX()
         thisY# = PointWY() - PickOffsetY()
         setSpritePositionByOffset( SpritePicked() , thisX# , thisY# )
         setSpriteDepth( SpritePicked() , DEPTH_PANEL - 5 )
         if WorldToScreenY( thisY#) >= getTextY( panelText() )
            HighlightSprite( SpritePicked() , FRAME_TRASHCAN )
         else
            thisObjectUnder = WorkObjectIsOver( ObjectPicked() )
            if thisObjectUnder > 0
               HighlightSprite( WorkObjectSprite( thisObjectUnder ) , FRAME_HIGHLIGHT )
            else
               HideHighLight()
            endif
         endif
      endif
   endif

   if panelIsOpen() = 1
      if getSpriteY( panelSprite() ) > ( PhysHeight() - panelHeight() )
         MovePanel( -panelSpeed() )
      endif
   else
      if getSpriteY( panelSprite() ) < PhysHeight()
         MovePanel( panelSpeed() )
      endif
   endif
endfunction

// Menu Functions

function checkMenuPointer()
   thisMenuSelected = 0
   for i=1 to MENU_ITEMS
      if getTextHitTest( menuText[ i ] , PointDX() , PointDY() ) > 0
         thisMenuSelected = i
         exit
      endif
   next i
   if getPointerPressed() > 0
      // Pointer Pressed
      touch.menuPicked = thisMenuSelected
   elseif getPointerReleased() > 0
      // Pointer Released
      if thisMenuSelected > 0 and thisMenuSelected = MenuPicked() then StartTransition( TRANS_FADEOUT , MENU_FADE )
   elseif getPointerState() > 0
      // PointerHeld
      if MenuPicked() > 0
         if thisMenuSelected = MenuPicked()
            setTextColorRed( menuText[ MenuPicked() ] , MENU_REDHI )
            setTextColorGreen( menuText[ MenuPicked() ] , MENU_GREENHI )
            setTextColorBlue( menuText[ MenuPicked() ] , MENU_BLUENHI )
         else
            setTextColorRed( menuText[ MenuPicked() ] , MENU_RED )
            setTextColorGreen( menuText[ MenuPicked() ] , MENU_GREEN )
            setTextColorBlue( menuText[ MenuPicked() ] , MENU_BLUE )
         endif
      endif
   endif
endfunction

// Transition Functions

function StartTransition( thisType , thisTime# )
   display.transType = thisType
   display.transCount# = 0.0
   display.transStep# = 1.0 / ( lastFPS() * thisTime# )
endfunction

function StopTransition()
   display.transType = TRANS_NONE
endfunction

function UpdateTransition()
   if TransitionCount() < 1.0 then display.transCount# = TransitionCount() + TransitionStep()
   if TransitionCount() > 1.0 then display.transCount# = 1.0
endfunction

// Diagnostic Functions

function PrintDiagnostics()
   printc( "Phy: " + str( PhysWidth() , 0) + " x " + str( PhysHeight() , 0 ))
   printc( ", Vir: " + str( VirtWidth() , 1) + " x " + str( VirtHeight() , 1 ))
   print( ", @ " + str ( lastFPS() , 2 ) + " fps" + chr(10) )
   printc("Transition Type : " + str( Transition() ) )
   printc(", Count: " + str( TransitionCount() , 3 ) )
   print(", Step: " + str( TransitionStep() , 3 ) + chr(10))
   print("Game State : " + str( GameState() ) + chr(10))
   print("Objects : " + str( TopObject() ) + chr(10) )

   printc( "Pointer (Display): " )
   print( str( PointDX() ) + " x " + str( PointDY() ) )
   printc( "Pointer (World): " )
   print( str( PointWX() ) + " x " + str( PointWY() ) )
   printc( "Sprite: ")
   print( str( getSpriteHit( PointDX() , PointDY() )))
   printc( "Sprite Picked: ")
   printc( str( SpritePicked() ))
   printc( ", Object Picked: ")
   printc( str( ObjectPicked() ))
   printc( ", Over : " )
   print( str( WorkObjectIsOver( ObjectPicked() ) ))
endfunction

// Panel Functions

function ToggleTrayOpen()
   panel.isOpen = 1 - panelIsOpen()
   if panelSound() > 0 then playSound( panelSound() )
endfunction

function MovePanel( distance# )
   setSpriteY( panelSprite() , getSpriteY( panelSprite() ) + distance# )
   setTextY( panelText() , getTextY( panelText() ) + distance# )
   PositionPanelIcons()
endfunction

function PositionPanelIcons()
   iconDepth = getSpriteDepth( PanelSprite() ) - 1
   for icon = 1 to panelIconMax()
      offset = icon - 1
      col = offset mod PANEL_COLS
      row = offset / PANEL_COLS
      x = col * IconSize() + 2
      y = row * IconSize() + getSpriteY( PanelSprite() ) + 2
      setSpritePosition( panelIconSprite[ icon ] , x , y )
      setSpriteDepth( panelIconSprite[ icon ] , iconDepth )
   next icon
endfunction

// Load Functions

function SafeLoadImage( thisFileName$ )
   if getFileExists( thisFileName$ )
      thisImageRef = loadImage( thisFileName$ )
   else
      thisImageRef = 0
   endif
endfunction thisImageRef

function SafeLoadSound( thisFileName$ )
   if getFileExists( thisFileName$ )
      thisSoundRef = loadSound( thisFileName$ )
   else
      thisSoundRef = 0
   endif
endfunction thisSoundRef

// Base 96 Functions

function Base96Encode( thisValue )
   lowValue = thisValue mod 96
   highValue = ( thisValue / 96 ) mod 96
   thisString$ = chr( highValue + 32 ) + chr( lowValue + 32 )
endfunction thisString$

function Base96Decode( thisString$ )
   thisLen = len(thisString$)
   if thisLen = 0
      thisValue = 0
   else
      if thisLen = 1 then thisString$ = " " + thisString$
      highValue = asc( left( thisString$ , 1 ) ) - 32
      lowValue = asc( mid( thisString$ , 2, 1 ) ) - 32
      thisValue = highValue * 96 + lowValue
   endif
endfunction thisValue

function Base96Sort( thisString$ )
   thisLen = len(thisString$)
   if thisLen > 2 and (thisLen && 1) = 0
      thisArraySize = thisLen / 2
      dim tempArray[ thisArraySize ] as string
      for i=1 to thisArraySize
         tempArray[ i ] = mid( thisString$ , i*2 - 1 , 2)
      next i
      SortTempArray( thisArraySize )
      thisString$ = ""
      for i=1 to thisArraySize
         thisString$ = thisString$ + tempArray[ i ]
      next i
   endif
endfunction thisString$

function SortTempArray( thisSize )
   for i=1 to thisSize - 1
      thisLow = i
      for j = i + 1 to thisSize
         if tempArray[j] < tempArray[thisLow] then thisLow = j
      next j
      if thisLowest <> i
         temp$ = tempArray[i]
         tempArray[i] = tempArray[thisLow]
         tempArray[thisLow] = temp$
      endif
   next i
endfunction

// Main Object Functions

function ObjectValid( thisObject )
   thisBool = ( thisObject > 0 and thisObject <= TopObject() )
endfunction thisBool

function FindObjectByName( thisName$ )
   thisName$ = upper( thisName$ )
   for thisObject = 1 to TopObject()
      if upper( ObjectName( thisObject )) = thisName$ then exit
   next thisObject
   if thisObject > TopObject() then thisObject = 0
endfunction thisObject

// Work Object Functions

function TopWorkObject()
endfunction numWorkObjects

function WorkObjectValid( thisObject )
   thisBool = ( thisObject > 0 and thisObject <= TopWorkObject() )
endfunction thisBool

function FirstFreeWorkObject()
   for thisObject = 1 to TopWorkObject()
      if WorkObjectIsFree( thisObject ) > 0 then exit
   next thisObject
   if thisObject > MAX_WORKOBJECTS then thisObject = 0
endfunction thisObject

function AddWorkObject( thisObjectID, thisSprite )
   thisObject = FirstFreeWorkObject()
   if thisObject > TopWorkObject() then numWorkObjects = thisObject
   if WorkObjectValid( thisObject )
      workObject[ thisObject ].isFree = 0
      workObject[ thisObject ].objectID = thisObjectID
      workObject[ thisObject ].sprite = thisSprite
   endif
endfunction thisObject

function DelWorkObject( thisObject )
   if WorkObjectValid( thisObject )
      workObject[ thisObject ].isFree = 1
      if WorkObjectSprite( thisObject ) > 0
         thisSprite = WorkObjectSprite( thisObject )
         workObject[ thisObject ].sprite = 0
         if getSpriteExists( thisSprite ) then deleteSprite( thisSprite )
      endif
      while WorkObjectIsFree( TopWorkObject() ) = 1 and TopWorkObject() >= 0
         dec numWorkObjects , 1
      endwhile
   endif
endfunction

function CreateWorkObjectSprite( thisObject , thisX , thisY )
   if WorkObjectIsFree( thisObject ) = 0
      if WorkObjectSprite( thisObject ) = 0 then workObject[ thisObject ].sprite = createSprite( IconImage() )
      thisSprite = WorkObjectSprite( thisObject )
      fixSpriteToScreen( thisSprite , 0 )
      setSpriteSize( thisSprite , IconSize() , - 1)
      setSpriteOffset( thisSprite , iconSize() / 2.0 , iconSize() / 2.0 )
      setSpritePositionByOffset( thisSprite , thisX , thisY )
      setSpriteDepth( thisSprite , DEPTH_WORK - 1 )
      setSpriteAnimation( thisSprite , 64 , 64 , 252 )
      setSpriteFrame( thisSprite , WorkObjectID( thisObject ) )
   endif
endfunction

function WorkObjectIsOver( thisWorkObject )
   thisSprite = WorkObjectSprite( thisWorkObject )
   if thisSprite > 0
      thisX# = getSpriteXByOffset( thisSprite )
      thisY# = getSpriteYByOffset( thisSprite )
      for thisObject = 1 to TopWorkObject()
         if thisObject <> thisWorkObject
            thisSprite = WorkObjectSprite( thisObject )
            if thisSprite > 0
               diffX# = abs( getSpriteXByOffset( thisSprite ) - thisX# )
               diffY# = abs( getSpriteYByOffset( thisSprite ) - thisY# )
               if diffX# < iconSize() and diffY# < iconSize() then exit
            endif
         endif
      next thisObject
      if thisObject > TopWorkObject() then thisObject = 0
   else
      thisObject = -1
   endif
endfunction thisObject

function FindWorkObjectBySprite( thisSprite )
   for thisObject = 1 to TopWorkObject()
      if WorkObjectSprite( thisObject ) = thisSprite then exit
   next thisObject
   if thisObject > TopWorkObject() then thisObject = 0
endfunction thisObject

function HideHighLight()
   if HighSprite() > 0
      if getSpriteExists( HighSprite() ) then deleteSprite( HighSprite() )
      display.highSprite = 0
   endif
endfunction

function HighlightSprite( thisSprite , thisFrame )
   if thisSprite > 0 and thisFrame > 0 and thisFrame < 253
      if getSpriteExists( thisSprite )
         HideHighLight()
         display.highSprite = cloneSprite( thisSprite )
         setSpriteFrame( HighSprite() , thisFrame )
         setSpriteDepth( HighSprite() , getSpriteDepth( HighSprite() ) - 1)
      endif
   endif
endfunction

// Game State Functions

function SetGameState( thisState )
   game.state = thisState
endfunction

// Data Load Functions

function LoadObjectData( thisName$ )
   // Pass the name without extension
   thisTextName$ = thisName$ + ".txt"
   thisImageName$ = FindImageFile( thisName$ )
   if getFileExists( thisTextName$ ) and thisImageName$ <> ""
      // Both data files exist
      dim tempArray[ 1000 ] as string
      thisArraySize = 0
      thisFile = openToRead( thisTextName$ )
      thisString$ = readLine( thisFile )
      repeat
         if thisString$ <> ""
            inc thisArraySize , 1
            tempArray[ thisArraySize ]  = thisString$
         endif
         thisString$ = readLine( thisFile )
      until fileEof( thisFile ) > 0
      closefile( thisFile )
      // Convert Data to Array
      game.topObject = thisArraySize
      dim MainObject[ topObject() ] as MainObjectType
      for i=1 to topObject()
         thisString$ = tempArray[ i ]
         thisLen = len( thisString$ )
         thisComma = FindSubString( thisString$ , "," , 1 )
         thisColon = FindSubString( thisString$ , ":" , thisComma + 1 )
         if thisComma > 0
            MainObject[ i ].name$ = left( thisString$ , thisComma - 1)
            if thisColon > 0
               MainObject[ i ].detail$ = right( thisString$ , thisLen - thisColon )
               thisLen = thisColon - 1
               thisString$ = left( thisString$ , thisLen)
            else
               MainObject[ i ].detail$ = ""
            endif
            thisString$ = right( thisString$ , thisLen - thisComma )
            thisValue = val( thisString$ )
            MainObject[ i ].icon = abs( thisValue )
            MainObject[ i ].isKnown = 0
            if thisValue < 0
               MainObject[ i ].isGroup = 1
            else
               MainObject[ i ].isGroup = 0
            endif
         endif
      next i
      // Recipes Go Here
      for i=topObject() + 1 to thisArraySize

      next i
      // Clear Temp Array
      dim tempArray[ 0 ]
      // Group Memberships
      for i=1 to topObject()
         if ObjectIsGroup( i ) > 0 and ObjectDetail( i ) <> ""
            thisString$ = ObjectDetail( i )
            thisLen = len( thisString$ )
            thisStart = 1
            thisCoded$ = ""
            while thisStart < thisLen
               thisComma = FindSubString( thisString$ , "," , thisStart )
               if thisComma = 0 then thisComma = thisLen + 1
               thisObject$ = mid( thisString$ , thisStart , thisComma - thisStart )
               // convert to base96
               thisObject = FindObjectByName( thisObject$ )
               if thisObject > 0 then thisCoded$ = thisCoded$ + Base96Encode( thisObject )
               thisStart = thisComma + 1
            endwhile
            MainObject[ i ].detail$ = Base96Sort( thisCoded$ )
         endif
      next i
   endif
endfunction thisArraySize

function FindImageFile( thisName$ )
   thisString$ = ""
   if getFileExists( thisName$ + ".png" )
      thisString$ = thisName$ + ".png"
   elseif getFileExists( thisName$ + ".jpg" )
      thisString$ = thisName$ + ".jpg"
   elseif getFileExists( thisName$ + ".bmp" )
      thisString$ = thisName$ + ".bmp"
   endif
endfunction thisString$

// String Functions

function FindSubString( thisString$ , thisSearch$ , thisStart )
   thisLen = len( thisSearch$ )
   thisSearch$ = upper( thisSearch$ )
   thisString$ = upper( thisString$ )
   thisEnd = len( thisString$ ) - thisLen + 1
   for thisPos = thisStart to thisEnd
      if mid( thisString$ , thisPos , thisLen ) = thisSearch$ then exit
   next i
   if thisPos > thisEnd then thisPos = 0
endfunction thisPos


// Initialisation Functions

function Initialise()
   // Prepare Globals
   global game as gameType
      game.level = 0
      game.state = STATE_SETUP
      game.topObject = 0

   global touch as touchType
      touch.pointDX# = 0.0
      touch.pointDY# = 0.0
      touch.pointWX# = 0.0
      touch.pointWY# = 0.0
      touch.spritePicked = 0
      touch.objectPicked = 0
      touch.menuPicked = 0
      touch.pickOffsetX# = 0.0
      touch.pickOffsetY# = 0.0

   global display as displayType
      display.physWidth# = getDeviceWidth()
      display.physHeight# = getDeviceHeight()
      display.virtWidth# = PhysWidth()
      display.virtHeight# = PhysHeight()
      display.iconSize# = PhysWidth() / PANEL_COLS
      display.textHeight# = PhysHeight() / 20.0
      display.highSprite = 0
      display.iconImage = SafeloadImage( "objects.png" )
      display.transType = TRANS_NONE
      display.transCount# = 0.0
      display.transStep# = 0.0

   global panel as panelType
      panel.iconMax = PANEL_ROWS * PANEL_COLS
      panel.height# = IconSize() * PANEL_ROWS
      panel.speed# = 0.0
      panel.isOpen = 0
      panel.Image = SafeloadImage( "panel.jpg" )
      panel.sprite = createSprite( PanelImage() )
      panel.text = createText( "Objects" )
      panel.sound = SafeLoadSound("panel.wav")

   setImageWrapU( PanelImage() , 1 )
   setImageWrapV( PanelImage() , 1 )

   uScale# = getImageWidth( PanelImage() ) / PhysWidth()
   vScale# = getImageHeight( PanelImage() ) / PanelHeight()
   setSpriteUVScale( PanelSprite() , uScale# , vScale# )
   setSpriteSize( PanelSprite() , PhysWidth() , PanelHeight() )
   setSpritePosition( PanelSprite() , 0 , PhysHeight() )
   setSpriteDepth( PanelSprite() , DEPTH_PANEL)

   setTextSize( panelText() , textHeight() )
   setTextAlignMent( panelText() , 1)
   setTextPosition( panelText() , PhysWidth() / 2.0 , PhysHeight() - TextHeight() )
   setTextDepth( panelText() , DEPTH_PANEL - 1 )

   // Prepare Arrays

   dim panelIconSprite[ panelIconMax() ]
      for icon = 1 to panelIconMax()
         panelIconSprite[ icon ] = createSprite( 0 )
         setSpriteSize( panelIconSprite[ icon ] , IconSize() - 4, IconSize() - 4)
         setSpriteColor( panelIconSprite[ icon ] , 255 , 255 , 255 , 127 )
      next icon

   dim workObject[ MAX_WORKOBJECTS ] as WorkObjectType
      for thisObject = 1 to MAX_WORKOBJECTS
         workObject[ thisObject ].isFree = 1
         workObject[ thisObject ].objectID = 0
         workObject[ thisObject ].sprite = 0
      next thisObject
   setVirtualResolution( VirtWidth(), VirtHeight() )
   PositionPanelIcons()

   dim menuString[ 5 ] as string = [ "" , "Start Game" , "Exit", "Option 4" , "Option 5" , "Option 6" ]
   dim menuText[ MENU_ITEMS ]
      for i=1 to MENU_ITEMS
         menuText[ i ] = createText( menuString[i] )
         setTextVisible( menuText[ i ] , 0 )
      next i

   dim MainObject[0] as MainObjectType

endfunction

// Return Functions - Game

function GameLevel()
endfunction game.level
function GameState()
endfunction game.state
function TopObject()
endfunction game.topObject

// Return Functions - Display

function PhysWidth()
endfunction display.physWidth#
function PhysHeight()
endfunction display.physHeight#
function VirtWidth()
endfunction display.virtWidth#
function VirtHeight()
endfunction display.virtHeight#
function IconSize()
endfunction display.iconSize#
function TextHeight()
endfunction display.textHeight#
function LastFPS()
endfunction display.lastFPS#
function IconImage()
endfunction display.iconImage
function HighSprite()
endfunction display.highSprite
function Transition()
endfunction display.transType
function TransitionCount()
endfunction display.transCount#
function TransitionStep()
endfunction display.transStep#

// Return Functions - Touch

function PointDX()
endfunction touch.pointDX#
function PointDY()
endfunction touch.pointDY#
function PointWX()
endfunction touch.pointWX#
function PointWY()
endfunction touch.pointWY#
function SpritePicked()
endfunction touch.spritePicked
function ObjectPicked()
endfunction touch.objectPicked
function MenuPicked()
endfunction touch.menuPicked
function PickOffsetX()
endfunction touch.pickOffsetX#
function PickOffsetY()
endfunction touch.pickOffsetY#

// Return Functions - Panel

function PanelHeight()
endfunction panel.height#
function PanelSpeed()
endfunction panel.speed#
function PanelIsOpen()
endfunction panel.isOpen
function PanelImage()
endfunction panel.image
function PanelSprite()
endfunction panel.sprite
function PanelText()
endfunction panel.text
function PanelSound()
endfunction panel.sound
function PanelIconMax()
endfunction panel.iconMax

// Return Functions - WorkObject Array

function WorkObjectIsFree( thisObject )
   if WorkObjectValid( thisObject )
      thisIsFree = workObject[ thisObject ].isFree
   else
      thisIsFree = -1
   endif
endfunction thisIsFree

function WorkObjectID( thisObject )
   if WorkObjectValid( thisObject )
      thisObjectID = workObject[ thisObject ].objectID
   else
      thisObjectID = -1
   endif
endfunction thisObjectID

function WorkObjectSprite( thisObject )
   if WorkObjectValid( thisObject )
      thisSprite = workObject[ thisObject ].sprite
   else
      thisSprite = -1
   endif
endfunction thisSprite

// Return Functions - Main Object

function ObjectName( thisObject )
   if ObjectValid( thisObject )
      thisString$ = MainObject [ thisObject ].name$
   else
      thisString$ = ""
   endif
endfunction thisString$

function ObjectDetail( thisObject )
   if ObjectValid( thisObject )
      thisString$ = MainObject [ thisObject ].detail$
   else
      thisString$ = ""
   endif
endfunction thisString$


function ObjectIcon( thisObject )
   if ObjectValid( thisObject )
      thisIcon = MainObject [ thisObject ].icon
   else
      thisIcon = -1
   endif
endfunction thisIcon

function ObjectIsKnown( thisObject )
   if ObjectValid( thisObject )
      thisBool = MainObject [ thisObject ].isKnown
   else
      thisBool = -1
   endif
endfunction thisBool

function ObjectIsGroup( thisObject )
   if ObjectValid( thisObject )
      thisBool = MainObject [ thisObject ].isGroup
   else
      thisBool = -1
   endif
endfunction thisBool
//

Sunday, 22 April 2012

Group Encoding

All the remains of the group membership read routine is the code that converts the string names to Base96.

We will also need a routine to deal with the recipes - which comes later. For now we will just add a comment and the outline of a loop.

Here is the location - between the end of the conversion loop and the start of the group loop.
 ...(conversion loop)...
      if thisValue < 0
         MainObject[ i ].isGroup = 1
      else
         MainObject[ i ].isGroup = 0
      endif
   endif
next i
// Recipes Go Here
for i=topObject() + 1 to thisArraySize

next i
// Clear Temp Array
dim tempArray[ 0 ]
// Group Memberships
for i=1 to topObject()
   if ObjectIsGroup( i ) > 0 and ObjectDetail( i ) <> ""
      ...(group membership loop)...
Also in this section is where the temp array is cleared.

Within the if/endif block for the object groups - immediately after thisStart is set to one, a new local variables is created to hold the encoded string.
thisCoded$ = ""
Now where we had the comment line.
// convert to base96
The following lines are used to replace it.
thisObject = FindObjectByName( thisObject$ )
if thisObject > 0 then thisCoded$ = thisCoded$ + Base96Encode( thisObject )
This looks for the object name in the object array, and if it is found, encodes it's index using Base96 and adds it to the thisCoded$.

When all the objects have been processed ( after the endwhile command), the string is sorted and put back into the detail$ field.
MainObject[ i ].detail$ = Base96Sort( thisCoded$ )
Which completes the group membership encoding.

To test this, we change the group print command to a printc command in the test code.
printc( ", Group: ")
And follow it with the following lines.
thisString$ = ObjectDetail( i )
thisNum = len( thisString$ ) / 2
for j = 1 to thisNum
   thisCode$ = mid( thisString$ , j*2 - 1 , 2)
   thisObject = Base96Decode( thisCode$ )
   printc( ObjectName( thisObject ) )
   if j < thisNum then printc( ", " )
next j
print( "." )
Firstly this transfers the coded string into local variable thisString$, and puts half it's length (the number of encoded objects) into thisNum.

It then performs a loop decoding each one in turn and printing the results. If the one being printed is not the last one, it also prints a comma.

At the end, it prints a full stop and ends the line.
For anyone wondering why tree comes before stone in the member list for resource - which was supposedly sorted when it was encoded.

The answer is, that it was sorted based on the object index, not it's name.  In index order, they are sorted.

If we want them sorted by name, then we have two choices.
  • Sort the object array by object name when first read
  • Add another function to sort the group members based on object name.
For now we wont be doing either.  But the option is there for the future.

Group Contents

In the last post, I used the field name detail$ rather than description$ because it would have another purpose.

This purpose is the group contents, a list of which objects are members of the groups defined in the data file.
tool,-1:stone axe
resource,-2:tree,stone
device,-3
job,-4
process,-5
storage,-6
tree,7:Home to Birds and Monkeys
stone,8:Small hard piece of planet
stone axe,9:Primative chopping device
log,10:Rough tree part, might burn well
kindling,11:Basis of a good fire
spark,12:Warning - fire Hazard
flame,13:Fire's younger sibling
fire,14:Good source of heat and light
Again the colon is used to separate the list, which is a string of names separated by more commas.  These names must match exactly the names for the objects.

Initially we don't have much in our groups, but it is enough to start with and use for testing the routine.

Group memberships can't be assigned like the other fields and will require another loop after the array has been filled with objects.

For example, when processing the data for the "Tool" object (which is a group), the object listed as a member, "stone axe", has not yet been converted to the array, so the program doesn't know it exists yet.

This is not a problem, since we are already putting the string after the colon into the details field in it's source or "raw" form.

The membership pass will simply convert the "raw" string names stored in the detail$ field to the Base96 code which represents the object.

Yes, this is where the Base96 routines we did a long time ago, will come into play.

Once the convert for/next loop has finished, the membership routine starts a new for/next loop. The routine works in a number of nested structures.
// Group Memberships
for i=1 to topObject()
   if ObjectIsGroup( i ) > 0 and ObjectDetail( i ) <> ""
      thisString$ = ObjectDetail( i )
      thisLen = len( thisString$ )
      thisStart = 1
      while thisStart < thisLen

      endwhile
   endif
next i
Here's a brief rundown of these outer loops structures;
  • It does a full pass of the array using the for/next loop.
  • It then checks if the object is a group with members using the if condition.
  • It retrieves the raw string from the array and stores it in a local variable.
  • It retrieves the length of that string and stores this in a local variable.
  • It sets a start point of 1 using a local variable.
  • It then enters a while/endwhile loop.
I have outlined the outer structure this way as everything here should be familiar by now and so I can concentrate on the main part of the routine, which is inside the while/endwhile block.

I want to first cover, what it is we are going to do, using an example, this will be for the group resource.
resource,-2:tree,stone
Above is the data in the file.  When it gets to this routine, the files are set as follows.
  • name$ contains "resource"
  • detail$ contains "tree,stone"
  • icon contains 2
  • isGroup contains 1
When the program gets to the while/endwhile loop, it has the following local variables.
  • thisString contains "tree,stone"
  • thisLen contains 10
  • thisStart contains 1
So the while condition is met ( 1 is less than 10 ) and so it enters the block.
thisComma = FindSubString( thisString$ , "," , thisStart )
This looks for a comma, starting at the position in thisStart ( 1 ) storing the result ( 5 ) in thisComma.
if thisComma = 0 then thisComma = thisLen + 1
If no comma was found, then use the length of the string plus 1. In our case, a comma was found so this is not done and thisComma stays at 5.
thisObject$ = mid( thisString$ , thisStart , thisComma - thisStart )
This retrieves the portion of the string from the position in thisStart to the position before the comma.

Since the mid() command requires a start and a length, and we have a start and an end, we do a bit of maths to work the length out.

thisComma - thisStart = 5 - 1  = 4

So thisObject$ contains part of the string from position 1, length 4 which is "tree".

I'm going to leave the encode part for now, so you can follow the next step of this loop.  Just insert the comment line.
// Convert to Base96
And I'll pick this up next time.

The last instruction in the while/endwhile loop is.
thisStart = thisComma + 1
Which moves the start position ready for the next object.  thisStart becomes 6.

In the second pass of the loop, it does the search from position 6, but won't find the comma, so thisComma becomes thisLen + 1 or 11.

When it extracts the string, this time it uses.

thisComma - thisStart = 11 - 6  = 5

So it starts from position 6 and uses length 5, thisObject$ becomes "stone".

After the next encode bit, it sets thisStart to thisComma + 1, so it becomes 12.

Since 12 is greater than 10, it exits the while/endwhile loop.

The full loop structure now looks like this.
// Group Memberships
for i=1 to topObject()
   if ObjectIsGroup( i ) > 0 and ObjectDetail( i ) <> ""
      thisString$ = ObjectDetail( i )
      thisLen = len( thisString$ )
      thisStart = 1
      while thisStart < thisLen
         thisComma = FindSubString( thisString$ , "," , thisStart )
         if thisComma = 0 then thisComma = thisLen + 1
         thisObject$ = mid( thisString$ , thisStart , thisComma - thisStart + 1 )
         // convert to base96
         thisStart = thisComma + 1
      endwhile
   endif
next i
Next we will do the conversion to Base96, which will round off Week 2.

Object Description

To add an object description, we need to add more to each object line in the data file standard.txt.  This will go at the end, after the icon number.

In order to find the end of the icon number and the start of the description, another delimiter needs to be used.

For this I have chosen the colon (:), which would not occur in either the name or the icon value.

With some mildly humorous sample information added, the file would look something like this.
tool,-1
resource,-2
device,-3
job,-4
process,-5
storage,-6
tree,7:Home to Birds and Monkeys
stone,8:Small hard piece of planet
stone axe,9:Primative chopping device
log,10:Rough tree part, might burn well
kindling,11:Basis of a good fire
spark,12:Warning - fire Hazard
flame,13:Fire's younger sibling
fire,14:Good source of heat and light
This would still work with the existing code as the Val() command would stop evaluating the number at the colon and the same value.

To use the description, we need another field in MainObjectType.
type MainObjectType
   name$
   detail$
   icon
   isKnown
   isGroup
endtype
This time, rather than using the obvious name description, I've gone for the more ambiguous detail.  This is to allow it to be used for groups, where it will have a different purpose.

The field needs a return function.
function ObjectDetail( thisObject )
if ObjectValid( thisObject )
   thisString$ = MainObject [ thisObject ].detail$
else
   thisString$ = ""
endif
endfunction thisString$
Which is very similar to the ObjectName() function, which could be copied and changed accordingly.

The first step in converting the data is finding the delimiter - the colon, which we do at the same time we find the comma, by adding the following immediately after.
thisColon = FindSubString( thisString$ , ":" , thisComma + 1 )
This performs a similar search function, but starts after the found comma position so that the search doesn't need to recheck the string up to that point.

If the comma was not found - heaven forbid, thisComma will be zero and adding 1 will still be a valid start point for this search.

Within the If block, just after the name is assigned to the array, we now add the following.
if thisColon > 0
   MainObject[ i ].detail$ = right( thisString$ , thisLen - thisColon )
   thisLen = thisColon - 1
   thisString$ = left( thisString$ , thisLen)
else
   MainObject[ i ].detail$ = ""
endif
If a colon was found, this transfers the string after it to the the detail$ field.

Then is sets thisLen to be the position before the colon and chops the string down to that size.

This makes the string the same as it would have been without the colon and description, so the rest of the routine works unaltered.

If the colon was not found, the field is set to an empty string.

In the test code and the main loop, the line.
print( ", " + chr(34) + "" + chr( 34 ))
Is now changed to include the description field.
print( ", " + chr(34) + ObjectDetail( i ) + chr( 34 ))
And can be tested.
The LoadObjectData() function currently looks like this.
function LoadObjectData( thisName$ )
   // Pass the name without extension
   thisTextName$ = thisName$ + ".txt"
   thisImageName$ = FindImageFile( thisName$ )
   if getFileExists( thisTextName$ ) and thisImageName$ <> ""
      // Both data files exist
      dim tempArray[ 1000 ] as string
      thisArraySize = 0
      thisFile = openToRead( thisTextName$ )
      thisString$ = readLine( thisFile )
      repeat
         if thisString$ <> ""
            inc thisArraySize , 1
            tempArray[ thisArraySize ]  = thisString$
         endif
         thisString$ = readLine( thisFile )
      until fileEof( thisFile ) > 0
      closefile( thisFile )
      // Convert Data to Array
      game.topObject = thisArraySize
      dim MainObject[ topObject() ] as MainObjectType
      for i=1 to topObject()
         thisString$ = tempArray[ i ]
         thisLen = len( thisString$ )
         thisComma = FindSubString( thisString$ , "," , 1 )
         thisColon = FindSubString( thisString$ , ":" , thisComma + 1 )
         if thisComma > 0
            MainObject[ i ].name$ = left( thisString$ , thisComma - 1)
            if thisColon > 0
               MainObject[ i ].detail$ = right( thisString$ , thisLen - thisColon )
               thisLen = thisColon - 1
               thisString$ = left( thisString$ , thisLen)
            else
               MainObject[ i ].detail$ = ""
            endif
            thisString$ = right( thisString$ , thisLen - thisComma )
            thisValue = val( thisString$ )
            MainObject[ i ].icon = abs( thisValue )
            MainObject[ i ].isKnown = 0
            if thisValue < 0
               MainObject[ i ].isGroup = 1
            else
               MainObject[ i ].isGroup = 0
            endif
         endif
      next i



      dim tempArray[ 0 ]
   endif
endfunction thisArraySize

Saturday, 21 April 2012

Object Storage 3

With the basic file read operation in place, we can now expand it's functionality.

As groups and objects are stored in the same array, we need to identify which is which.  Groups will be identified with a field called isGroup in the MainObjectType UDT.
type MainObjectType
   name$
   icon
   isKnown
   isGroup
endtype
As always, we provide a return function.
function ObjectIsGroup( thisObject )
   if ObjectValid( thisObject )
      thisBool = MainObject [ thisObject ].isGroup
   else
      thisBool = -1
   endif
endfunction thisBool
The code for this is almost identical to the ObjectIsKnown() function, which can be duplicated and only the function name and array field need to be changed.

We can identify groups without needing to change the data too much, by simply making the icon number for groups a negative value in standard.txt, which now looks like this.
tool,-1
resource,-2
device,-3
job,-4
process,-5
storage,-6
tree,7
stone,8
stone axe,9
log,10
kindling,11
spark,12
flame,13
fire,14
Now when the value is transferred to the Object array, in the line.
MainObject[ i ].icon = val( thisString$ )
We use the abs() command to get the absolute, or positive version of the value.
thisValue = val( thisString$ )
MainObject[ i ].icon = abs( thisValue )
This has been split into two parts to simplify the code and prepare for the next step, which is a check to see if the value is negative.
if thisValue < 0
   MainObject[ i ].isGroup = 1
else
   MainObject[ i ].isGroup = 0
endif
This goes immediately after the isKnown field is set and before the endif.

To test this works, we modify the test code.  Changing the print in the line.
print( str(i) + " : " + ObjectName( i ) + " : " + str( ObjectIcon( i) ) )
To printc, and adding the following lines immediately after.
print( " - Group: " + str( ObjectIsGroup( i ) ) )
Which now shows which objects are groups with a 1 at the end of the line.
The icon values stored in the array are unchanged.

Now the line just added is replaced with the following lines.
if ObjectIsGroup( i ) > 0
   print( ", Group: ")
else
   print( ", " + chr(34) + "" + chr( 34 ))
endif
Which sets us up for the next step - adding descriptions for the objects.
Which will appear inside those quotes.

Friday, 20 April 2012

Object Storage 2

To convert the temp array data to the object array, we need to first dim the object array.

In the LoadObjectData() function, change the line.
game.topObject = 0
to
game.topObject = thisArraySize
Which now sets the top object to match the number of lines read into the temp array.

Immediately after, add the line.
dim MainObject[ topObject() ] as MainObjectType
Which now prepares the object array for the correct number of objects.

Next is a for/next loop to copy the data into the array.
for i=1 to topObject()
   thisString$ = tempArray[ i ]
   thisLen = len( thisString$ )
   thisComma = FindSubString( thisString$ , "," , 1 )
   if thisComma > 0
      MainObject[ i ].name$ = left( thisString$ , thisComma - 1)
      thisString$ = right( thisString$ , thisLen - thisComma )
      MainObject[ i ].icon = val( thisString$ )
      MainObject[ i ].isKnown = 0
   endif
next i
First, this copies the contents of position i in the temp array into local variable thisString$ and gets the length of this string and stores it in thisLen.

Using these local variables not only makes the rest of the code more readable, but it helps track down any problems if the routine doesn't work as it should.

The comes the string search, which looks for a comma, storing the result in thisComma.

If the result is greater than zero - ie a comma was found, then it processes the line further. We will come back to what happens if it doesn't find one in a bit, for now it just moves onto the next string.

Within the conditional loop, it uses the left() command to extract the portion of the string before the comma, putting it into the name field of the object array at position i.

Then it removes both the left portion and the comma, by using the right() command to extract the part after the comma, putting the result back into thisString$.

It obtains the value of the remainder of the string and places this in the icon field of the object array - again at position i.

Finally, it sets the isKnown field to zero, as all objects are unknown to begin with.

The conditional block ends with the endif and moves onto the next object with the next command.

The final step is to remove the two slashes from the line.
//      dim tempArray[ 0 ]
Which changes it from a comment into an active command.  This command now clears the temp array.

Compile and run the code and you should get.
Showing that the object array is now populated with the correct information.

Thursday, 19 April 2012

String Functions 1

Here are the first few lines of our object data again.
tool,1
resource,2
device,3
job,4
In order to transfer the information to the correct fields in the object array, we need to be able to identify the different parts in the line.

Looking at it, it seems obvious, the name is the part before the comma, the icon number is the part after.

In programming terms, we need to search for the comma, which is in a different position on each line.

While the routine to do that is simple enough, it would be better if we had a general function which could find any string inside any other string.

Better still would be a function that could find several things without having to keep checking the entire string.

To address this, we have a number of short general purpose string functions, the first of which is a basic sub-string search called FindSubString().
function FindSubString( thisString$ , thisSearch$ , thisStart )
   thisLen = len( thisSearch$ )
   thisSearch$ = upper( thisSearch$ )
   thisString$ = upper( thisString$ )
   thisEnd = len( thisString$ ) - thisLen + 1
   for thisPos = thisStart to thisEnd
      if mid( thisString$ , thisPos , thisLen ) = thisSearch$ then exit
   next i
   if thisPos > thisEnd then thisPos = 0
endfunction thisPos
This looks for the first occurrence of the string thisSearch$ in the string thisString$, starting at the position specified in thisStart.  If it finds one it returns the position, if not it returns zero.

The function can be tested with the following code in the main loop.
String$ = "Hello World"
Search$ = "lo"
found = FindSubString( String$ , Search$ , 1 )
print( Search$ + " in " + String$ + " at position " + str( found ))
Which looks for the string "lo" inside the string "hello world" starting at position 1. The result should look like this.
Feel free to try a few different search strings to make sure it finds them all, and when you've done, remove the test code.

The same call can now be used in our load function.

Before we do that, let's change the code in the main loop to show the contents of the object array rather than the temp array.

Change the lines.
// Intro Code
print(" Lines : " + str( testNum ) )
for i=1 to testNum
   print( str(i) + " : " + tempArray[ i ] )
next i
print( " ----- ")
To.
print(" Objects : " + str( TopObject() ) )
for i=1 to TopObject()
   print( str(i) + " : " + ObjectName( i ) + " : " + str( ObjectIcon( i ) ) )
next i
print( " ----- ")
So that it's now using the object array rather than the temp array.

Compile and run the program and you should get.
As there are no objects in the array yet.

Object Storage

Now that we have read the object file, we need somewhere to put it.

This will get quite complex, so I'll start with the basics and expand on it as more data is added.

Firstly we need to know how many objects were loaded. This will be a new field called topObject in the GameType UDT, which now looks like this.
type GameType
   state
   level
   topObject
endtype
As always we create a return function for this.
function TopObject()
endfunction game.topObject
And set the initial value in the Initialise() function.
game.topObject = 0
And also after the closefile() command in the LoadObjectData() function.
game.topObject = 0
The array to store the game data will be called MainObject[].

It's structure is defined in another UDT.
type MainObjectType
   name$
   icon
   isKnown
endtype
Initially, this just includes fields for the object name and it's icon number, as well as the field isKnown, which indicates if the object is known about.

The array is dimmed with zero elements in the Initialise() function.
dim MainObject[0] as MainObjectType
Though it is empty, we can still set up the basic functions which will eventually be needed to access the array.  These are very similar to the ones used by the WorkObject[] array.

The validation function.
// Main Object Functions

function ObjectValid( thisObject )
   thisBool = ( thisObject > 0 and thisObject <= TopObject() )
endfunction thisBool
A function to locate an object by its name.
function FindObjectByName( thisName$ )
   thisName$ = upper( thisName$ )
   for thisObject = 1 to TopObject()
      if upper( ObjectName( thisObject )) = thisName$ then exit
   next thisObject
   if thisObject > TopObject() then thisObject = 0
endfunction thisObject
which compares the value passed against each array object's name field, after both have been converted to upper case. This ensures that the case used does not affect the search.

And the return functions for the array
// Return Functions - Main Object

function ObjectName( thisObject )
   if ObjectValid( thisObject )
      thisString$ = MainObject [ thisObject ].name$
   else
      thisString$ = ""
   endif
endfunction thisString$

function ObjectIcon( thisObject )
   if ObjectValid( thisObject )
      thisIcon = MainObject [ thisObject ].icon
   else
      thisIcon = -1
   endif
endfunction thisIcon

function ObjectIsKnown( thisObject )
   if ObjectValid( thisObject )
      thisBool = MainObject [ thisObject ].isKnown
   else
      thisBool = -1
   endif
endfunction thisBool
Everything is now in place to start filling the array with data.

As a final step, we add a print statement just after the games State line in PrintDiagnostics() function.
print("Objects : " + str( TopObject() ) + chr(10) )
So we can see at any time how many objects are loaded.

File Read

In order to read data from a file, you must first open it with the openToRead() command, this takes a filename as its parameter and returns a reference, used to access that file.
thisFile = openToRead( thisTextName$ )
In this case, the filename is the one created previously with the .txt extension and stored in thisTextName$. Local variable thisFile holds the resulting file reference.

We will be reading the file a line at a time using the readLine() command.  This takes the file reference as it's parameter.
thisString$ = readLine( thisFile )
The first line is read into a temporary string variable thisString$, initialising it ready for the read loop.
repeat
   if thisString$ <> ""
      inc thisArraySize , 1
      tempArray[ thisArraySize ] = thisString$
   endif
   thisString$ = readLine( thisFile )
until fileEof( thisFile ) > 0
This uses the repeat/until structure, checking for the end of the file using the fileEOF() command at then end of the loop.

Within the loop, the temporary string is checked to make sure it is not empty.

If it is not, then the array size variable thisArraySize is incremented by 1 with the inc command and the contents of the temporary string are copied into the temporary array at that position.

Before the loop completes, the next line is read into thisString$ and the end of file check is performed.  If the end of file has not been reached, the loop repeats.

When the loop exists,. the file is closed with the closeFile() command.
closefile( thisFile )
In order to test that this was successful, we will need to temporarily return the size of the array. This is done by adding the variable thisArraySize after the endfunction command.
endfunction thisArraySize
The function now looks like this.
// Data Load Functions

function LoadObjectData( thisName$ )
   // Pass the name without extension
   thisTextName$ = thisName$ + ".txt"
   thisImageName$ = FindImageFile( thisName$ )
   if getFileExists( thisTextName$ ) and thisImageName$ <> ""
      // Both data files exist
      dim tempArray[ 1000 ] as string
      thisArraySize = 0
      thisFile = openToRead( thisTextName$ )
      thisString$ = readLine( thisFile )
      repeat
         if thisString$ <> ""
            inc thisArraySize , 1
            tempArray[ thisArraySize ]  = thisString$
         endif
         thisString$ = readLine( thisFile )
      until fileEof( thisFile ) > 0
      closefile( thisFile )

//   dim tempArray[ 0 ]
   endif
endfunction thisArraySize
The dim command at the end is commented out so it does not run, this is there purely as a reminder for when the function is finished.

For now, we are exiting the function with tempArray[] fully populated so that we can check the information read.

To do this we add a couple of lines to the STATE_SETUP block in the main loop and comment out the existing line.
case STATE_SETUP
   // Setup Code
   testNum = LoadObjectData( "standard" )
   setGameState( STATE_INTRO )
   // Workaround to ensure frame rate ok before menu is called
   // if timer() > 2.0 then setGameState( STATE_MENU )
endcase
This now calls the function just created, storing the result - the size of the array - in local variable testNum.

It then changes the game state so that on the next loop, the STATE_INTRO block will be used.

The variable testNum is local to the main loop so will keep its value, which will be picked up by the following code.
case STATE_INTRO
   // Intro Code
   print(" Lines : " + str( testNum ) )
   for i=1 to testNum
      print( str(i) + " : " + tempArray[ i ] )
   next i
   print( " ----- ")
endcase
This prints out the value in testNum and then performs a loop, printing the contents of tempArray[].
Because the game state is not changed again, this block is re-used every loop to keep the values on screen.

Data Files

The information used by the game - the objects and recipes, needs to come from somewhere.

In theory, we could create a long list of constants in the program, or we could assign the data to an array as we did with the menu text, but neither of these methods is really practical.

The best solution is to store it in a separate data file, which is loaded when the program starts.  This way, the data can be added to or changed without needing to change the program.

This also opens up the ability to have multiple sets of objects and recipes, each with their own theme.

To make this easier, the task is split into two parts.
  • Read the data from the file
  • Convert the data to a usableform
This is the modular programming concept again, we can create a routine to read the data without knowing what format that data will be in and we can create a routine to convert the data without knowing where it came from.

Since objects have both a name and an image associated with them, we will need a data file to store each.  To simplify matters, both files will have the same name, but a different file extension.
  • The name and description will be stored in a text file with the extension ".txt"
  • The images will be stored in an image file...
While we have been using a ".png" file for our images so far, it is possible that we may want to support other file types such as ".bmp" and ".jpg".   This means that the file extension could be on of several.

We can allow for this by having a function which checks for several file types and returns the one present or an empty string if none are.
function FindImageFile( thisName$ )
   thisString$ = ""
   if getFileExists( thisName$ + ".png" )
      thisString$ = thisName$ + ".png"
   elseif getFileExists( thisName$ + ".jpg" )
      thisString$ = thisName$ + ".jpg"
   elseif getFileExists( thisName$ + ".bmp" )
      thisString$ = thisName$ + ".bmp"
   endif
endfunction thisString$
This takes a name string thisName$ and checks for the existence of files based on that name with a variety of known extensions. If it finds one, it stores the full name in thisString$ for returning.

Combined with a check for a file with a ".txt" extension, we can now check that both data files exist before we commit to reading them.
function LoadObjectData( thisName$ )
   // Pass the name without extension
   thisTextName$ = thisName$ + ".txt"
   thisImageName$ = FindImageFile( thisName$ )
   if getFileExists( thisTextName$ ) and thisImageName$ <> ""
      // Both data files exist - Start Read

   endif
endfunction
This is the outline for our data loading function. It takes a name (without file extension) and verifies that both files exist before attempting to load the data.

The text data will be read into a temporary string array - the same one used before, tempArray[]. As arrays are global, we must ensure we don't use the Base96Sort() function - which also uses this array, while we load the data or it will mess everything up.
// Both data files exist - Start Read
dim tempArray[ 1000 ] as string
thisArraySize = 0 
Since the file could be any size, we use a high value in the dim command just to be sure. The local variable thisArraySize will be used to indicate how much of the array has actually been used.

As before, when we have done with the array, we will dim it to zero, freeing up the memory used.

Before we can go any further, we are going to need some files.

For the image file, make a copy of the objects.png file in the media folder and call the copy standard.png.  We are not using this right away, so it is just a place marker to pass the check above.

For the text file, create a file called standard.txt and in it put the following;

tool,1
resource,2
device,3
job,4
process,5
storage,6
tree,7
stone,8
stone axe,9
log,10
kindling,11
spark,12
flame,13
fire,14

This is simply a list of objects and a number separated by a comma, which represent.
  • The Object Name
  • The Icon Number
This will be expanded upon, but as test data for the file read, it's all we need.

You can even use the AGK editor to create the file (with new/empty file), which will make editing it even easier as is will show as part of your project.
Remember to specify the source location within the media folder.

Wednesday, 18 April 2012

Level 1

As another break from coding, I thought I'd cover another aspect of the game not yet mentioned, levels.

As stated at the start, the game is based on Alchemy, but yet some time ago, I enigmatically defined gameType as.
type GameType
   state
   level
endtype
Those familiar with Alchemy will know that it does not have levels, its just an open game. So what's the deal?

I also said somewhere down the line, that the mechanics will be based on alchemy, but the game itself will be something different.

In this game there will be levels, and each level will have a goal.

So before we do any more programming, it would be a good idea to design a typical level, this can be used to decide how the game is structured and as an indicator of if we are going in the right direction.

Level 1 - Fire.
In Alchemy, you get this at the beginning. Which kind of undermines how difficult it was to first produce.

In our history, the discovery of fire was a crucial step in our evolution, our survival even.

I thought it would be a nice idea to set this as the goal for the first level as a tribute to its importance in our development as a species.

So what do we need to make fire?

In scientific terms, fire requires three things.
  • Oxygen
  • Fuel
  • Ignition
Thanks to Joseph Priestley (1733-1804), we know that oxygen is all around us, so not much of a challenge there.

There are various type of fuel, but historically, the first would have been wood which was in plentiful supply hidden inside trees.
This gives us a starting point and introduces a new concept which will form the basis for one of our object groups - resources.
A Tree is a resource, and a very valuable one at that.  They produce Oxygen and if left alone, are pretty self sustaining.  All they need is sunlight, water and the nutrients found in the earth.
So now we have to decide how many steps it will take to get the wood and what else will be needed.

We will certainly need a tool of some kind, but we don't want to give too much away early on.  An Axe is a good start - we can make the user work a little more to get a saw.
It would be difficult to have to make the axe, since this will need wood, and we need the axe to get the wood, so we will have to give the user the axe to start with.

Now we need a spark.
The simplest is to bang two stones together, stones are in plentiful supply so are another resource we can start with.  Alternatively we can hit a stone with an axe, but this may blunt the axe.
Of course a spark hitting a log will not start a fire, so we would need kindling.
This would be obtained chopping logs with the axe.

Lighting the kindling would technically get fire, but we can introduce a middle step, the flame.
Which in turn would be combined with the logs to produce fire.
This should provide enough the give the user an introduction into the game.

The starting objects are.
  • Tree
  • Stone
  • Axe
The other objects in the process are.
  • Blunt Axe
  • Kindling
  • Spark
  • Flame
  • Fire
Now we need the recipes - the combinations and their results
  • Combining tree with axe will produce logs and axe.
  • Combining logs with axe will produce kindling and axe
  • Combining stone with stone will produce the two stones and spark
  • Combining stone with axe will produce stone and blunt axe
  • Combining spark with kindling will produce flame
  • Combining flame with logs will produce fire
So we have the basics of our level, which being the first is not meant to be that challenging.

Menu Clean up

There are just a few clean up tasks now to complete the menu.

First we add constant to replace the 255 value used for the highlighted text.
#constant MENU_GREENHI    255
While putting this constant in place, I realised that the line within if getPointerPressed().
if thisMenuSelected > 0 then setTextColorGreen( menuText[ thisMenuSelected ] , MENU_GREENHI )
Is not actually needed.  so I removed it.

The highlighting of the text will be picked up in the following loop by getPointerState(), and the program operates so fast, this will not be noticeable.

This opens up the possibility of having a highlight version of all the colours, which offers much more flexibility. So we can have
#constant MENU_REDHI 255
#constant MENU_GREENHI 255
#constant MENU_BLUEHI 0
And use them within the getPointerState() portion.
elseif getPointerState() > 0
   // PointerHeld
   if MenuPicked() > 0
      if thisMenuSelected = MenuPicked()
         setTextColorRed( menuText[ MenuPicked() ] , MENU_REDHI )
         setTextColorGreen( menuText[ MenuPicked() ] , MENU_GREENHI )
         setTextColorBlue( menuText[ MenuPicked() ] , MENU_BLUENHI )
      else
         setTextColorRed( menuText[ MenuPicked() ] , MENU_RED )
         setTextColorGreen( menuText[ MenuPicked() ] , MENU_GREEN )
         setTextColorBlue( menuText[ MenuPicked() ] , MENU_BLUE )
      endif
   endif
endif
Next, we move the pointer code into a function called checkMenuPointer().
function checkMenuPointer()
   thisMenuSelected = 0
   for i=1 to MENU_ITEMS
      if getTextHitTest( menuText[ i ] , PointDX() , PointDY() ) > 0
         thisMenuSelected = i
         exit
      endif
   next i
   if getPointerPressed() > 0
      // Pointer Pressed
      touch.menuPicked = thisMenuSelected
   elseif getPointerReleased() > 0
      // Pointer Released
      if thisMenuSelected > 0 and thisMenuSelected = MenuPicked() then StartTransition( TRANS_FADEOUT , MENU_FADE )
   elseif getPointerState() > 0
      // PointerHeld
      if MenuPicked() > 0
         if thisMenuSelected = MenuPicked()
            setTextColorRed( menuText[ MenuPicked() ] , MENU_REDHI )
            setTextColorGreen( menuText[ MenuPicked() ] , MENU_GREENHI )
            setTextColorBlue( menuText[ MenuPicked() ] , MENU_BLUENHI )
         else
            setTextColorRed( menuText[ MenuPicked() ] , MENU_RED )
            setTextColorGreen( menuText[ MenuPicked() ] , MENU_GREEN )
            setTextColorBlue( menuText[ MenuPicked() ] , MENU_BLUE )
         endif
      endif
   endif
endfunction
And replace it with a call to this function.
checkMenuPointer()
Now the main loop is reduced in size again.

We add a print line after the line that starts print(", Step: " +... in the PrintDiagnostics() function to display the current game state.
print("Game State : " + str( GameState() ) + chr(10))
We also need to add a case statement to pick up STATE_EXIT in the Game state select structure .
case default
   exit
endcase
This uses the default case, which is called when no other case block matches the value returned by GameState(), and exits the main loop when the state is not recognised.

To exit the program, this needs the following line after the loop command.
end
Which instructs the program to end.

I think we need to reduce the fade time of the menu, which is a little long at the moment.
#constant MENU_FADE 1.0
This is now just 1 second, which I think this looks much better.

Finally, we need to put the menu back the way we came to it, that is we need to make the menu text invisible, so that when we return to the menu, the fade in transition will activate.

This requires the following code adding within the block which performs the menu select block (just after the line if Transition() = TRANS_FADEOUT).
for i=1 to MENU_ITEMS
   setTextVisible( menuText[ i ] , 0 )
next i
That just about does it for the menu for now.

Here's how the main program looks now - functions not included.
// Constants must go at start of program
#constant PANEL_ROWS   3
#constant PANEL_COLS   4

#constant DEPTH_MENU   10
#constant DEPTH_SCORE   20
#constant DEPTH_PANEL   30
#constant DEPTH_WORK   40

#constant MAX_WORKOBJECTS   100

#constant FRAME_HIGHLIGHT   251
#constant FRAME_TRASHCAN      252

#constant STATE_SETUP   0
#constant STATE_INTRO   1
#constant STATE_DEMO   2
#constant STATE_MENU   3
#constant STATE_RUNNING   4
#constant STATE_EXIT   99

#constant MENU_FADE      1.0
#constant MENU_ITEMS      2

#constant MENU_DIVTOP   0.25
#constant MENU_DIVSIZE   0.10

#constant MENU_RED      255
#constant MENU_GREEN   165
#constant MENU_BLUE      0
#constant MENU_REDHI   255
#constant MENU_GREENHI   255
#constant MENU_BLUEHI   0

#constant TRANS_NONE   0
#constant TRANS_FADEIN   1
#constant TRANS_FADEOUT   2

// User Defined Types - custom data structures

type displayType
   physWidth#
   physHeight#
   virtWidth#
   virtHeight#
   iconSize#
   textHeight#
   lastFPS#
   iconImage
   highSprite
   transType
   transCount#
   transStep#
endtype

type touchType
   pointDX#
   pointDY#
   pointWX#
   pointWY#
   spritePicked
   objectPicked
   menuPicked
   pickOffsetX#
   pickOffsetY#
endtype

type panelType
   height#
   speed#
   isOpen
   image
   sprite
   text
   sound
   iconMax
endtype

type WorkObjectType
   isFree
   objectID
   sprite
endtype

type GameType
   state
   level
endtype

// I must remember to put this somewhere

global numWorkObjects = 0

Initialise()

// Temporary Objects

imageRef = SafeloadImage( "finger.png" )
spriteRef = createSprite( imageRef )

// Main Loop
do
   LoopStart()

   select GameState()
      case STATE_SETUP
         // Setup Code
         // Workaround to ensure frame rate ok before menu is called
         if timer() > 2.0 then setGameState( STATE_MENU )
      endcase
      case STATE_INTRO
         // Intro Code
      endcase
      case STATE_DEMO
         // Demo Code
      endcase
      case STATE_MENU
         // Menu Code
         if getTextVisible( menuText[ 1 ]) = 0
            // Menu starting - Activate Transition
            StartTransition( TRANS_FADEIN , MENU_FADE )
            thisX# = VirtWidth() / 2.0
            thisY# = VirtHeight() * MENU_DIVTOP
            thisSize# = VirtHeight() * MENU_DIVSIZE
            for i=1 to MENU_ITEMS
               setTextSize( menuText[ i ] , thisSize# )
               setTextAlignment( menuText[ i ] , 1 )
               setTextDepth( menuText[ i ] , DEPTH_MENU )
               setTextPosition( menuText[ i ] , thisX# , thisY# )
               setTextVisible( menuText[ i ] , 1 )
               setTextColor( menuText[ i ] , MENU_RED , MENU_GREEN, MENU_BLUE , 0 )
               thisY# = thisY# + getTextTotalHeight( menuText[ i ] ) * 2.0
            next i
         elseif (Transition() = TRANS_FADEIN) or (Transition() = TRANS_FADEOUT)
            thisAlpha = 255.0 * TransitionCount()
            if Transition() = TRANS_FADEOUT then thisAlpha = 255 - thisAlpha
            for i=1 to MENU_ITEMS
               setTextColorAlpha( menuText[ i ] , thisAlpha )
            next i
            if TransitionCount() = 1.0
               if Transition() = TRANS_FADEOUT
                  // Menu Exiting
                  for i=1 to MENU_ITEMS
                     setTextVisible( menuText[ i ] , 0 )
                  next i
                  select MenuPicked()
                     case 1
                        // Menu Option 1
                        setGameState( STATE_RUNNING )
                     endcase
                     case 2
                        // Menu Option 2
                        setGameState( STATE_EXIT )
                     endcase
                  endselect
               endif
               StopTransition()
            endif
         endif
         checkMenuPointer()
      endcase
      case STATE_RUNNING
         // Running Code
         checkGamePointer()
      endcase
      case default
         // Picks up unknown states - including STATE_EXIT
         exit
      endcase
   endselect

   // Position temporary finger sprite
   setSpritePosition( spriteRef , PointDX() , PointDY() )

   PrintDiagnostics()
   sync()
loop

end


Menu Selections 2

Firstly, the program performs a loop.
// Check Pointer
thisMenuSelected = 0
for i=1 to MENU_ITEMS
   if getTextHitTest( menuText[ i ] , PointDX() , PointDY() ) > 0
      thisMenuSelected = i
      exit
   endif
next i
This uses the getTextHitTest() command, which returns a 1 if the pointer is currently within the bounds of the text object used by that menu option. If so, then the loop value (and so the menu option) is stored in thisMenuSelected.  This is the current menu option.

Then it enters the three part structure outlined previously.  First checking if the pointer was just pressed.
if getPointerPressed() > 0
   // Pointer Pressed
   touch.menuPicked = thisMenuSelected
   if thisMenuSelected > 0 then setTextColorGreen( menuText[ thisMenuSelected ] , 255 )
This copies the current menu option just obtained to the global field touch.menuPicked.  This is the picked menu option.

If this value is not zero, then the corresponding text item is highlighted by setting it's green value to 255.

If the pointer was just released, the second block is used.
elseif getPointerReleased() > 0
   // Pointer Released
   if thisMenuSelected > 0 and thisMenuSelected = MenuPicked() then StartTransition( TRANS_FADEOUT , MENU_FADE )
This checks that the current menu option is not zero and matches the picked menu option using MenuPicked().

If both cases are true, it begins the fade out transition.

If the pointer is still held, the third block is used.
elseif getPointerState() > 0
   // PointerHeld
   if MenuPicked() > 0
      if thisMenuSelected = MenuPicked()
         setTextColorGreen( menuText[ MenuPicked() ] , 255 )
      else
         setTextColorGreen( menuText[ MenuPicked() ] , MENU_GREEN )
      endif
   endif
endif
If the picked menu option is not zero, then it checks if the current menu option is the same the one picked, setting the green to 255 if this is the case, otherwise setting it back to its original colour.

If the user moves off the picked option, it is un-highlighted.

If the user then moves back onto it without releasing the pointer, it is re highlighted.

If the user releases the pointer on the picked option, the menu fades out.

This is the complete pointer check routine.
// Check Pointer
thisMenuSelected = 0
for i=1 to MENU_ITEMS
   if getTextHitTest( menuText[ i ] , PointDX() , PointDY() ) > 0
      thisMenuSelected = i
      exit
   endif
next i
if getPointerPressed() > 0
   // Pointer Pressed
   touch.menuPicked = thisMenuSelected
   if thisMenuSelected > 0 then setTextColorGreen( menuText[ thisMenuSelected ] , 255 )
elseif getPointerReleased() > 0
   // Pointer Released
   if thisMenuSelected > 0 and thisMenuSelected = MenuPicked() then StartTransition( TRANS_FADEOUT , MENU_FADE )
elseif getPointerState() > 0
   // PointerHeld
   if MenuPicked() > 0
      if thisMenuSelected = MenuPicked()
         setTextColorGreen( menuText[ MenuPicked() ] , 255 )
      else
         setTextColorGreen( menuText[ MenuPicked() ] , MENU_GREEN )
      endif
   endif
endif
To act on the menu option, the program must look for the fade out to complete, so it's back to the previous section.

Now the line.
if TransitionCount() = 1.0 then StopTransition()
Is replaced with.
if TransitionCount() = 1.0
   if Transition() = TRANS_FADEOUT
      select MenuPicked()
         case 1
            // Menu Option 1
            setGameState( STATE_RUNNING )
         endcase
         case 2
            // Menu Option 2
            setGameState( STATE_EXIT )
         endcase
         case 3
            // Menu Option 3
         endcase
         case 4
            // Menu Option 4
         endcase
         case 5
            // Menu Option 5
         endcase
      endselect
   endif
   StopTransition()
endif
Which checks if the transition is a "fade out", in which case it starts another select structure, this time for the menu option picked.

In each case, the game state is changed to match the option picked.

As with the initial menu strings, additional case statements are included to allow other menu options to be added.

We now have a fully working menu, which fades in, processes user selections, fades out and acts upon those selections.