//------------------------------------------------------------------------------
// FLIPULL MINI-VERSION by Slàinte - 2004
// Current release: BETA 0.4
//------------------------------------------------------------------------------
// This game is a simple remake for the original FLIPULL/PLOTTING (TAITO 1989)
// It is developed as a sample Fenix Game and includes LOADS of comments on how
// the game logic was implemented.  Anyway don't expect this code to be like a
// tutorial on how to code games... my methods work fine for me and this game
// allows for several tricks some other games won't allow.
//------------------------------------------------------------------------------
// This code is distributed as is, with no implied warranty on it's reliability
// Any comments on this code can be addresed to fenix@divsite.net
//------------------------------------------------------------------------------

#DEFINE LEVEL_SIZE 144    // TILES IN A LEVEL
#DEFINE LEVEL_ROWS 12     // SQUARE AREA (ROWS X COLS)
#DEFINE LEVEL_COLS 12
#DEFINE TILE_W 8          // TILE INFORMATION (WIDTH X HEIGHT)
#DEFINE TILE_H 8
#DEFINE MAX_LEVEL 15      // CURRENT MAX LEVEL IN THE LEVELS.DAT FILE

program flipull ;

// LEVEL INFORMATION
//------------------------------------------------------------------------------
// Level information will be read from a file in "chunks" of a single level
// with the following structure, additionally a CODE list will be loaded first
// and used for pass check
//------------------------------------------------------------------------------

TYPE NIVEL
                            // Level data on a file structure [151 BYTES]
  INT  secs ;               // Seconds to finish level (up to 255
  INT  blocks ;             // Start block count for the level
  INT  target ;             // Target block count
  BYTE data[LEVEL_SIZE-1] ; // Level DATABLOCK
                            //--------------------------------------------------
END

// HIGH SCORE INFORMATION
//------------------------------------------------------------------------------
// Hich scores will be left in a small DAT file with the following structure
//------------------------------------------------------------------------------

TYPE HSCORE

  CHAR name[3] ;            // Name (3 letters + \0)
  INT  mscore ;             // High score value
                            //--------------------------------------------------
END

// GENERAL CONSTANTS
//------------------------------------------------------------------------------

CONST

  // COLLISION MASKS
  COLD_H_MASK = 008h ;  // 000000001000 (BIN) HORIZONTAL COLLISION
  COLD_V_MASK = 010h ;  // 000000010000 (BIN) VERTICAL COLLISION
  STATIC_MASK = 020h ;  // 000000100000 (BIN) STATIC BLOCK
  
  // MAIN AUTOMATON STATUS
  STAT_INIT   = 0 ;
  STAT_MENU   = 1 ;
  STAT_GAME   = 2 ;
  STAT_OVER   = 3 ;
  STAT_SCORE  = 4 ;
  STAT_END    = 5 ;

  // BLOB AUTOMATON STATUS
  BLOB_STATIC = 0 ;
  BLOB_MOVING = 1 ;
  BLOB_FIRING = 2 ;
  BLOB_HALTED = 3 ;
  BLOB_PASSWD = 4 ;
  BLOB_WAITBL = 5 ;
  
  // DIRECTION MASKS
  DIR_HORIZONTAL = 01h ; // 00000001 (BIN)
  DIR_VERTICAL   = 02h ; // 00000010 (BIN)
  
  // VELOCITY
  VO  = 25 ;
  GR  = 10 ;

END

// GLOBALS
//------------------------------------------------------------------------------

global

  // CHARMAP
  //----------------------------------------------------------------------------
  // Texts will be created as processes in the game to allow for some effects
  // also because fnts under 8x8 are not supported "on the fly", Numbers will
  // be worked in the same way, but they don't need a charmap as they map to the
  // same position as their ordinal value (0-9)
  //----------------------------------------------------------------------------

  CHAR  fntmap[26] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" ; // CHARACTER MAP
  
  BYTE  zoomed = 0 ;  // Current 2X mode
  NIVEL curr_level ;  // Current in-play level
  INT   score      ;  // Score
  INT   fpgfile ;     // FPG file
  INT   fpgnumbers ;  // FPG file for the numbers
  INT   bl ;          // Destroyed blocks in a line (BONUS MULTIPLIER)
  INT   pj ;          // Main character (if any)
  INT   dp ;          // Main fire-block (if any)
  INT   pjy ;         // Main character y tile position
  INT   fired ;       // signaling variable for fired blocks
  INT   ret ;         // signaling variable for returning blocks
  INT   KEY_SIM = 0 ; // key-up simulation
  INT   mv = 0 ;      // available moves ? 1 : 0
  INT   lives = 3 ;   // Life counter
  INT   end_level ;   // End level flag
  INT   state = 0 ;   // MAIN AUTOMATON STATUS
  INT   option = 0;   // Menu option (in STAT_MENU)
  INT   level = 1 ;   // Current level
  INT   timeout = 0 ; // Time out flag
  INT   bonust  = 0 ; // Bonus time from level to level (up to 30secs)
  BYTE  timer_on = 0; // Timer activation
  
  BYTE  mclean = 0 ;  // Flag used to garbage collect when needed

  HSCORE hscoretable[5] ;       // 5 highscore entries

  NIVEL levels[MAX_LEVEL-1] ;   // LEVELS DATA
  
  INT  idata[LEVEL_SIZE] ;      // instance information for every cell
  BYTE ldata[LEVEL_SIZE] ;      // collision information for every cell

END

// MAIN PROC START
//------------------------------------------------------------------------------

PRIVATE

  INT i,j ;            // General counters
  INT music  = 0 ;     // Current music
  INT KEY_UP_ESC = 1 ; // ESCAPE key-up simulator

END

BEGIN

  // APPLICATION INIT
  //----------------------------------------------------------------------------

  state = STAT_INIT ;

  // STATE AUTOMATON
  //----------------------------------------------------------------------------
  
  WHILE (state!=STAT_END)
  
    SWITCH (state)
  
      CASE STAT_INIT:
  
        // Initialize all that is needed and enter the sutomaton loop
        //----------------------------------------------------------------------

        DUMP_TYPE    = 0 ;
        RESTORE_TYPE = 0 ;
        screen_init(zoomed) ;
        fpgfile      = load_fpg("fpg/flipull.fpg") ;
        set_fps(40,1) ;
        set_title ("FLIPULL") ;
        set_icon (fpgfile,10) ;
        screen_init(zoomed) ;
        map_clear(0,0,0) ;
        // Intro sequence... shows comapny (SMALL PIXELS GROUP) logo
        fade_off() ;
        WHILE (fading) frame; END
        put(fpgfile,9,70,35) ;
        write_at(16,65,"SMALL PIXELS GROUP PRESENTS",10) ;
        write_at(30,75,"FLIPULL MINI VERSION",10) ;
        fade_on() ;
        WHILE (fading) frame; END
        frame(10000) ;
        fade_off() ;
        WHILE (fading) frame; END
        clear_screen() ;
        let_me_alone() ;
        // Intro sequence... shows TAITO logo
        put_screen(fpgfile,11) ;
        fade_on() ;
        chdir("dat") ;

        // Initialize levels data
        load("levels.dat",levels) ;
        // Initialize high score data
        IF (file_exists("hscore.dat"))
          load("hscore.dat",hscoretable) ;
        ELSE
          // If no file is found generate default values
          FOR (i=0;i<5;i++)
            hscoretable[i].name="ACE" ;
            hscoretable[i].mscore = 10000 ;
          END
          // Save it for future use...
          save("hscore.dat",hscoretable) ;
        END
        
        chdir("..") ;

        // Generate the numbers fnt..
        // No charmap will be needed... mapping is directly DIGIT+1.
        fpgnumbers = fpg_new() ;
        FOR (i=0;i<10;i++)
          // We copy the red font in the 8 possible 1px shifted positions
          // And last we copy the yellow font on top, centered, so it's outlined
          j = new_map(8,8,8) ;
          map_clear(0,j,0) ;
          map_block_copy(0,j,0,0,16,i*6,0,6,8,0) ;
          map_block_copy(0,j,2,0,16,i*6,0,6,8,0) ;
          map_block_copy(0,j,0,1,16,i*6,1,6,7,0) ;
          map_block_copy(0,j,0,1,16,i*6,0,6,8,0) ;
          map_block_copy(0,j,1,0,16,i*6,1,6,7,0) ;
          map_block_copy(0,j,1,1,16,i*6,0,6,8,0) ;
          map_block_copy(0,j,2,0,16,i*6,1,6,7,0) ;
          map_block_copy(0,j,2,1,16,i*6,0,6,8,0) ;
          map_block_copy(0,j,1,0,15,i*6,0,6,8,0) ;

          fpg_add(fpgnumbers,i+1,0,j) ;

        END
        
        frame(5000) ;
        
        WHILE (fading) frame; END

        state = STAT_MENU ;

        //----------------------------------------------------------------------

      END
      
      CASE STAT_MENU:

        // Draw the menu and keep track of the options
        // Also if enough time passes (60 seconds) swap to the High Score table
        //----------------------------------------------------------------------

        IF (music!=0) fade_music_off(music) ; END
        fade_off() ;
        WHILE(fading) FRAME; END

        IF (music!=0) stop_song() ; END
        
        // Garbage collect
        mclean = 1 ;
        FRAME ;
        mclean = 0 ;

        let_me_alone() ;
        delete_text(ALL_TEXT) ;
        clear_screen() ;

        music = load_song("s3m/Plotting_Title.s3m") ;
        play_song(music,-1) ;

        // Draw the background
        drawing_map(0,0) ;
        drawing_color(104) ;
        draw_box(10,8,130,48) ;
        
        // Title
        put(fpgfile,8,70,29) ;
        
        // Top and bottom WALL TILE rows
        FOR (i=0;i<5;i+=4)
          FOR (j=0;j<15;j++)
            put(fpgfile,56,j*8+14,i*8+12) ;
          END
        END

        // Left and right WALL TILE colums
        FOR (i=0;i<3;i++)
          put(fpgfile,56,14,i*8+20) ;
          put(fpgfile,56,126,i*8+20) ;
        END

        // Decorative blobs
        blob(BLOB_HALTED,30,28,0) ;
        blob(BLOB_HALTED,110,28,1) ;

        // Menu selector
        selector() ;

        // Write text options
        write_at(60,60,"NEW GAME",1) ;
        write_at(60,67,"PASSWORD",2) ;
        write_at(60,74,"OPTIONS",3) ;
        write_at(60,81,"EXIT",4) ;
        
        // Initialize option selection
        option = 1 ;
        
        // Show it up
        fade_on() ;
        WHILE(fading) FRAME; END
        
        // Initialize timer
        timer[0] = 0 ;

        WHILE (state==STAT_MENU)
        
          // Controlling option changes... we use height as a SLOWDOWN, height
          // is an unused local var for us so no need to define another one
          
          IF (height>0)
             height-- ;
          ELSE
            IF (option <4 && key(_DOWN))
              option++ ;
              height = 5 ;
            END
            IF (option >1 && key(_UP))
             option-- ;
             height = 5 ;
            END
          END
          
          // Controlling timeout for HIGHSOCRES view (60 SECONDS)
          IF (timer[0]>=6000) state = STAT_SCORE ; END

          // Ending the game
          IF (key(_ESC))
            // ESCAPE key-up simulation
            IF (KEY_UP_ESC)
              state = STAT_END;
            END
          ELSE
            KEY_UP_ESC = 1 ;
          END
          
          // Activate option
          IF (key(_ENTER) || key(_SPACE))

            SWITCH (option)
              CASE 1:
                // Start the game
                state = STAT_GAME ;
                // Initialize to first level
                level = 0 ;
              END
              
              CASE 4:
                // Exit the game
                state = STAT_END ;
              END
            END
            
          END
          frame(200) ;
        END
        
        fade_off() ;
        WHILE (fading) frame ; END

        //----------------------------------------------------------------------
      
      END
      
      CASE STAT_GAME:
      
        // Garbage collect
        mclean = 1 ;
        FRAME ;
        mclean = 0 ;

        // Draw game area and start a level
        //----------------------------------------------------------------------

        level = 0 ;
        score = 0 ;
        lives = 3 ;
        let_me_alone() ;
        clear_screen() ;
        fade_music_off(music) ;
        stop_song() ;
        music = load_song("s3m/Plotting_Game.s3m") ;
        play_song(music,-1) ;

        fade_on() ;
        WHILE(fading) frame; END
        DUMP_TYPE = 1 ;
        RESTORE_TYPE = 1 ;

        WHILE (state==STAT_GAME)

          // Clear screen
          clear_screen() ;
          
          // Garbage collect
          mclean = 1 ;
          FRAME ;
          mclean = 0 ;
        
          // INIT end_level, score, and all...
          end_level = 0 ;
          timeout   = 0 ;
          ret       = 0 ;
          fired     = 0 ;
          timer_on  = 0 ;
          
          IF (level<2) bonust=0; END
          
          // LOAD LEVEL
          memcopy(OFFSET curr_level,OFFSET levels[level] , SIZEOF (NIVEL)) ;
          memcopy(OFFSET ldata, OFFSET curr_level.data, LEVEL_SIZE) ;

          // Draw side
          drawing_map(0,0) ;
          drawing_map(0,0) ;
          drawing_color(78) ;
          draw_box(96,0,140,96) ;
          
          // Draw separation
          FOR(i=0;i<LEVEL_ROWS;i++)
           put(fpgfile,56,92,TILE_W/2+i*TILE_H) ;
          END

          // Display texts
          write_at(99,3,"LIVES",0) ;
          live_counter() ;
          write_at(99,13,"BLOCKS",0) ;
          block_counter() ;
          write_at (99,23,"TARGET",0) ;
          target_counter() ;
          write_at (99,33,"STAGE",0) ;
          stage_counter() ;
          score_counter() ;
          time_counter() ;

          // DISPLAY LEVEL CAPTION
          WRITE(0,48,48,4,"STAGE " + level) ;
          FRAME(8000) ;
          delete_text(0) ;

          // DISPLAY LEVEL
          drawing_color(104) ;
          draw_box(0,0,96,96) ;
          
          // PROCESS CREATION
          FOR (i=0;i<LEVEL_ROWS;i++)
            FOR (j=0;j<LEVEL_COLS;j++)
              IF (ldata[i*LEVEL_COLS+j]!=0)
                idata[i*LEVEL_COLS+j] = block(ldata[i*LEVEL_COLS+j],j,i) ;
              END
            END
          END

          frame ;

          // SPRITES
          //--------------------------------------------------------------------
          pj = blob(BLOB_STATIC,4,92,0) ;
          dp = disparo() ;
          flecha() ;
          mv = 1 ;


          // MAIN LEVEL LOOP
          //--------------------------------------------------------------------
          // This loop goes on until we finish a level, loose all our lives or
          // we are past the last level
          //--------------------------------------------------------------------

          timer_on = 1 ; // Activate the timer...

          WHILE (end_level==0 && lives>0 && level < MAX_LEVEL && timeout==0)

            IF (key(_F1)) screen_init(1) ; END
            IF (key(_F2)) screen_init(0) ; END
            IF (key(_ESC))
              state = STAT_MENU ;
              KEY_UP_ESC = 0 ;
              BREAK ;
            END
            frame ;

          END

          IF (timeout==1 || lives <= 0 || level>=MAX_LEVEL)
            state = STAT_MENU;
          ELSE
            level++ ;
            let_me_alone() ;
          END
        
        END

        // GAME OVER GOES HERE... Everything is still there

        let_me_alone() ;

      END
      
      CASE STAT_SCORE:

        // Show high score table while no key is pressed or 30 secs have passed
        //----------------------------------------------------------------------
        fade_off() ;
        IF (music!=0) fade_music_off(music) ; END
        WHILE(fading) FRAME ; END
        
        // Garbage collect
        mclean = 1 ;
        FRAME ;
        mclean = 0 ;

        let_me_alone() ;
        clear_screen() ;

        // Draw the background
        drawing_map(0,0) ;
        drawing_color(104) ;
        draw_box(18,16,122,80) ;

        // Top and bottom WALL TILE rows
        FOR (i=0;i<10;i+=9)
          FOR (j=1;j<14;j++)
            put(fpgfile,56,j*8+14,i*8+12) ;
          END
        END

        // Left and right WALL TILE colums
        FOR (i=0;i<8;i++)
          put(fpgfile,56,14,i*8+20) ;
          put(fpgfile,56,126,i*8+20) ;
        END
        
        // Corners
        FOR (i=1;i<10;i+=7)
          FOR (j=1;j<14;j+=12)
            put(fpgfile,56,j*8+14,i*8+12) ;
          END
        END
        
        // Write scores
        FOR (i=0;i<5;i++)
          write_at(40,28+10*i,hscoretable[i].name,0) ;
          // Put gfx for the numbers...
          put(fpgnumbers,(hscoretable[i].mscore%10+1),99,30+10*i) ;
          put(fpgnumbers,((hscoretable[i].mscore/10)%10+1),93,30+10*i) ;
          put(fpgnumbers,((hscoretable[i].mscore/100)%10+1),87,30+10*i) ;
          put(fpgnumbers,((hscoretable[i].mscore/1000)%10+1),81,30+10*i) ;
          put(fpgnumbers,((hscoretable[i].mscore/10000)%10+1),75,30+10*i) ;
          write(0,52,26+10*i,0,".....") ;
        END

        fade_on() ;
        IF (music!=0) stop_song() ; END
        music = load_song("s3m/Plotting_Opt.s3m") ;
        play_song(music,-1) ;
        
        WHILE(fading) FRAME; END
        
        
        timer[0] = 0 ;
        
        // MAIN HIGH SCORE TABLE LOOP
        WHILE (state == STAT_SCORE)

          IF (timer[0]>=6000 || key(_ESC))
            state = STAT_MENU;
            IF (key(_ESC)) KEY_UP_ESC = 0 ; END
          END
          
          FRAME ;

        END
        
      END

    END
    
  END
  

  // GAME END
  //----------------------------------------------------------------------------
  let_me_alone() ;
  clear_screen() ;
  
  // CLEAN UP
  //----------------------------------------------------------------------------

  stop_song() ;
  unload_fpg(fpgfile) ;
  unload_song(music) ;
  
  // EXIT
  //----------------------------------------------------------------------------

  exit() ;

END

//------------------------------------------------------------------------------
// BLOB : BLOB SPRITE
//------------------------------------------------------------------------------
// Controls UP - DOWN - SPACE (FIRE)
// ANIM 2 frames up/down, 1 random blink, 3 frames push
// LIST OF FRAMES:
//   1 - WALK  ANIM 0
//   2 - WALK  ANIM 1
//   3 - BLINK ANIM 0
//   4 - FIRE  ANIM 0
//   5 - FIRE  ANIM 1
//   6 - FIRE  ANIM 2
//------------------------------------------------------------------------------

PROCESS blob(INT stat, INT x, INT y, INT flags)

PRIVATE

  INT freq       = 10 ;           // Blink probability
  INT row        = 11 ;           // Current row - initialized to bottom row
  INT moving     = 0 ;            // Blob is moving FLAG
  INT dir        = 0 ;            // Moving direction (1/-1 : DOWN/UP)
  INT acum       = 0 ;            // Current moving offset
  INT inc        = 2 ;            // Moving speed
  INT beat       = 2 ;            // Beat frequency
  INT cont       = 0 ;            // Beat control
  INT up_pressed = 0 ;            // FLAG for KEY-UP SYM -> UP
  INT dw_pressed = 0 ;            // FLAG for KEY-UP SYM -> DOWN
  INT sp_pressed = 0 ;            // FLAG for KEY-UP SYM -> SPACE

begin

  // SPRITE INIT
  //----------------------------------------------------------------------------

  FILE  = fpgfile ;
  GRAPH = 1 ;

  pjy = row ;     // We will need to know the current blob row in other procs
  
  // SPRITE LOOP
  //----------------------------------------------------------------------------
  // BEAT deterines how "fast" a process interacts with the user, actions can
  // only take place when the BEAT allows... this is a common tech I use to
  // easilly increase/decreas a process avg. speed, just change the beat and
  // the process gets faster/slower.  It is also common to use percentual FRAME
  // for this, but I prefer this way as it allows for other operations in the
  // non interactive frames
  //----------------------------------------------------------------------------
  while (1)

    // Check for beat sincro, use >= just in case we have missed some frames due
    // to FPS lose or any unexpected problem
    
    if (ret==1 && stat!=BLOB_WAITBL) stat = BLOB_WAITBL; END
    if (ret==0 && stat==BLOB_WAITBL) stat = BLOB_STATIC; END
    
    if (cont >= beat)

      // We are in a beat sincro, reset the counter
      cont = 0 ;
      
      // Reset the beat value... blinks ara a bit faster than common frames, so
      // I just check for the "blink" beat and restore the original
      IF (beat==1)
        beat = 2 ;
        GRAPH = 1 ;
      END

      // SPRITE AUTOMATON
      //------------------------------------------------------------------------
      // This block is the core for the BLOB sprite... it keeps control of moves
      // and actions based on a basic STATE AUTOMATON using variable STAT.  The
      // possible states for the BLOB are defined as constants for easy reading
      //
      // The walking animation is a bit tricky for this one... as it has only 2
      // frames I assigned codes 1 and 2 to the frames in the FPG file... This
      // way I can easily animate with a simple BOOLEAN LOGIC operation - XOR
      //
      // FRAME 1   -> code 1 -> 01h -> 0001 (BIN)
      // FRAME 2   -> code 2 -> 02h -> 0010 (BIN)
      // ANIMATION MASK -> 3 -> 03h -> 0011 (BIN)
      //
      // 0 XOR 0 = 0
      // 1 XOR 0 = 1
      // 0 XOR 1 = 1
      // 1 XOR 1 = 0
      //
      // 01h XOR 03h -> 02h
      // 02h XOR 03h -> 01h
      //------------------------------------------------------------------------

      SWITCH (stat)

        CASE BLOB_STATIC:

          // The blob is halted... doing nothing, actions are possible in this
          // state, so we check for starting any possible action
          //--------------------------------------------------------------------
          
          IF (dir!=0)
            // UP or DOWN where pressed, so we must start moving
            moving = 1 ;
            stat   = BLOB_MOVING ;
            acum  += inc ;
            y     += inc*dir ;
            GRAPH ^= 03h ;
          ELSE
            // if we are not moving check for a blink... blinking is a small
            // animation, a single frame, that gives the character some lilvely
            // effect... it is just a "cosmetic" add-on to the game, but it
            // really works, the blink is a short action, so beat is reduced a
            // bit to compensate frame duration
            IF (rand(0,100)<freq)
              GRAPH = 3 ;
              beat  = 1 ;
            END
          END
        
          //--------------------------------------------------------------------
          
        END

        CASE BLOB_WAITBL:

          // The blob is WITING for the returning block... just check for blink
          //--------------------------------------------------------------------

          IF (rand(0,100)<freq)
            GRAPH = 3 ;
            beat  = 1 ;
          END

          //--------------------------------------------------------------------

        END

        CASE BLOB_MOVING:

          // The blob is moving... actions are not possible while moving
          //--------------------------------------------------------------------

          IF (acum<TILE_H)
            // still not fully advanced into the next "cell"
            acum  += inc ;
            y     += inc*dir ;
            GRAPH ^= 03h ;
          ELSE
            // fully advanced into the next "cell", must reset movement
            acum   = 0 ;
            moving = 0 ;
            stat   = BLOB_STATIC ;
            row   += dir ;
            row   %= 12 ;  // This is not really needed but for error trapping
            dir    = 0 ;
            pjy    = row ;
          END

          //--------------------------------------------------------------------

        END
        
        CASE BLOB_FIRING:

          // The blob is firing... must animate accordingly using the shoot anim
          //--------------------------------------------------------------------

          IF (GRAPH<6)
            // if not already finished the animation advance 1 frame
            GRAPH++ ;
            IF (GRAPH==6) fired = 1; END
          ELSE
            // reset animation and state
            GRAPH = 1 ;
            stat  = BLOB_STATIC ;
            beat  = 2 ;
          END

          //--------------------------------------------------------------------

        END
        
        CASE BLOB_HALTED:
        
          // The blob is halted in place: decorative blobs, like the menu screen
          //--------------------------------------------------------------------

          IF (GRAPH != 1)
            // Go back to normal state
            GRAPH = 1 ;
            beat = 2 ;
          ELSE
            // Animation...
            IF (rand(0,100)<freq)
              // BLINK animation
              GRAPH = 3 ;
              beat = 1 ;
            ELSE
              // RAISE animation
              IF (rand(0,100)<freq)
                GRAPH = 2 ;
              END
            END
          END

          //--------------------------------------------------------------------

        END

      END

    ELSE
    
      cont++ ;

    END

    // CHECK KEYS EVERY FRAME... ALSO ADD A "KEY-UP" SYM - KEYS DON'T GET CACHED
    //--------------------------------------------------------------------------
    // CHECKED O.K.
    //--------------------------------------------------------------------------
    
    IF (stat!=BLOB_HALTED && stat!=BLOB_WAITBL)
    
      IF (key(_UP))
        IF (row>0 && !moving && !up_pressed)
          // IF CAN GO UP, NOT ALREADY MOVING AND KEY WAS RELEASED
          if (KEY_SIM) up_pressed = 1 ; end
          dir = -1 ;
        END
      ELSE
        // KEY_UP SYM
        up_pressed = 0 ;
      END

      IF (key(_DOWN))
        IF (row<LEVEL_ROWS-1 && !moving && !dw_pressed)
          // IF CAN GO UP, NOT ALREADY MOVING AND KEY WAS RELEASED
          IF (KEY_SIM) dw_pressed = 1 ; end
          dir = 1 ;
        END
      ELSE
        // KEY_UP SYM
        dw_pressed = 0 ;
      END

      IF (key(_SPACE) && !moving)
        sp_pressed = 1 ;
        // RESET MOVES
        dir = 0 ;
        beat = 1 ;
        stat = BLOB_FIRING ;
        GRAPH = 4 ;
      ELSE
        // KEY_UP SYM
        sp_pressed = 0 ;
      END

    END
    
    frame ;
  
  end

end

//------------------------------------------------------------------------------
// BLOCK : BLOCK PROCESS
//------------------------------------------------------------------------------
// Blocks are a tricky group in this game... I have hard-coded them to allow for
// direct MASKING with their graph value.  The data imprinted in the graph value
// is as follows:
//
// 1 BIT  - static block
// 2 BITS - collision checks V & H (1 BIT EACH)
// 3 BITS - block ident
//
// The resulting values were:
//
// CIRCLE   : 24 ->  00 0 11 000
// SQUARE   : 25 ->  00 0 11 001
// TRIANGLE : 26 ->  00 0 11 010
// CROSS    : 27 ->  00 0 11 011
// SPECIAL  : 28 ->  00 0 11 100
// TUBE     : 41 ->  00 1 01 001
// WALL     : 56 ->  00 1 11 000
//
// Using the collision MASKS defined in the CONST section you can easilly work
// out cell based collisions for the blocks
//------------------------------------------------------------------------------
PROCESS block(BYTE g,INT px, INT py)

PRIVATE

  int  inc     = 2 ;     // Speed increment
  int  acum    = 0 ;     // Movement offset
  int  moving  = 0 ;     // Block is moving FLAG
  int  static  = 0 ;     // Block is static FLAG
  BYTE dt      = 0 ;     // Block below type
  
END

BEGIN

  // SPRITE INIT
  //----------------------------------------------------------------------------

  GRAPH  = g & 0FFh ;         // Make sure value is trimmed (might be a byte)
  x      = TILE_W/2 + 8*px ;  // Initial position based on cells
  y      = TILE_H/2 + 8*py ;
  static = g & STATIC_MASK ;  // Static or not
  z      = -10 ;              // depth

  // SPRITE LOOP
  //----------------------------------------------------------------------------
  // Blocks are quite silly things... they just stay in place if they are static
  // or go down falling until the find an obstable below (or the bottom of the
  // screen)
  //----------------------------------------------------------------------------

  WHILE (1)
    IF (static==0)
      // This is not a static block, must fall
      IF (py<LEVEL_ROWS-1)
        // Not already on the bottom of the screen
        IF (acum==0)
          // this block just ended moving last frame or was just stopped
          dt = ldata[((py+1)*LEVEL_COLS+px)] ;
          IF ((dt&COLD_V_MASK)==0)
            // if our block below is passable vertically
            y    += inc ;
            acum += inc ;
          END
        ELSE
          // Block is already falling
          y    += inc ;
          acum += inc ;
          IF (acum==8)
            // Finished move, reset old data in level and instance datablocks
            ldata[(py*LEVEL_COLS+px)] = 0 ;
            idata[(py*LEVEL_COLS+px)] = 0 ;
            // Reset movement
            py++ ;
            acum = 0 ;
            // Update new hardness
            ldata[(py*LEVEL_COLS+px)] = GRAPH ;
            idata[(py*LEVEL_COLS+px)] = id ;
          END
        END
      END
    END
    frame ;
  END

END

//------------------------------------------------------------------------------
// DISPARO
//------------------------------------------------------------------------------
// Process for the block you fire
//------------------------------------------------------------------------------

PROCESS disparo()

PRIVATE

  INT moving = 0 ;  // Block is moving
  INT dirx   = 0 ;  // Moving in X axis FLAG
  INT diry   = 0 ;  // Moving in Y axis FLAG
  INT dir_fl = 0 ;  // Moving flag
  INT inc    = 4 ;  // Block speed
  INT px     = 0 ;  // X position (cell)
  INT py     = 0 ;  // Y position (cell)
  INT acum   = 0 ;  // Current movement offset
  INT dt     = 0 ;  // Next cell information
  INT ct     = 0 ;  // Current collision type
  INT nt     = 0 ;  // Next collision type
  INT bd     = 0 ;  // Destroyed blocks (also bonus multiplier)
  INT idx    = 0 ;  // Current check index
  INT al     = -1 ; // solved angle
  FLOAT a,b,c,tmp ; // Helper vars to solve the 2ond degree equation
  FLOAT dx, dy ;    // Helper for x & y distance conversion
  FLOAT vx,vy ;     // vectorial components for Vo
  FLOAT t,tinc ;    // Time and Time Increment
  INT xo,yo,hm ;    // start X position
  INT cvo ;         // current speed

BEGIN

  // Initialization for the sprite
  //----------------------------------------------------------------------------
  GRAPH = 28 ;
  x     = pj.x + TILE_W ;
  y     = pj.y ;
  py    = 11 ;
  px    = 1 ;
  z     = 0 ;
  ct    = GRAPH ;
  
  // Sprite loop
  //----------------------------------------------------------------------------
  WHILE (1)
  
    IF (!fired)
      // Block is not fired
      //------------------------------------------------------------------------
      // We have not fired the block... in this state the block just follows the
      // blob 'round... so this is an easy one...
      //------------------------------------------------------------------------
      y  = pj.y ;
      py = pjy ;
      ct = GRAPH ;
    ELSE
      // Block is fired
      //------------------------------------------------------------------------
      // This is quite tricky... the block must advance until it collides with
      // some block or the screen edges.  If the block fired is an S block it
      // will destroy any NON-STATIC block it finds first and after that it will
      // act a s this block type just destroyed.  If the block fired is a common
      // block it will destroy any NON-STATIC block of THE SAME TYPE, then, AND
      // IF IT HAS DESTROYED ANY BLOCK, it will SWAP POSITIIONS with the first
      // NON-STATIC and NON-S block it encounters.  If the fired block meets a
      // STATIC-BLOCK it will change direction from HORIZONTAL to VERTICAL.  The
      // block will bounce back if it meets the end of screen (HORIZONTAL or
      // VERTICAL) or if it's first collision is with a NON-STATIC and NON-S
      // block of a DIFFERENT TYPE than itself.
      //------------------------------------------------------------------------
      
      IF (ret==0)

        IF (!moving)
          // Start moving if needed
          moving = 1 ;
          dir_fl++ ;
        END

        // Check horizontal collision
        //----------------------------------------------------------------------
        IF (dir_fl==DIR_HORIZONTAL)
          // Follow right while not collide
          IF (px<LEVEL_COLS)
            // Not colliding with the right end of the screen
            IF (acum==0)
              // Get information of the block in the next cell
              idx = py*LEVEL_COLS+px+1 ;
              dt  = ldata[idx] ;
              // Check horizontal collision with the next block
              IF ((dt&COLD_H_MASK)==0)
                // No colllision... we can go on
                x    += inc ;
                acum += inc ;
              ELSE

                // COLLISION DETECTED
                //--------------------------------------------------------------
                // We just detected a collision... now it is needed to know what
                // block type we just collided with
                //--------------------------------------------------------------

                IF ((dt&STATIC_MASK)==STATIC_MASK)
                  // STATIC-BLOCK -> Change directions
                  dir_fl++ ;
                ELSE
                  // NON-STATIC BLOCK... see if we have to destroy it
                  IF (ct==28 || ct==dt || dt==28)
                    // We can destroy this block...
                    bd++ ;
                    x    += inc ;
                    acum += inc ;
                    // Destroy the block
                    signal(idata[idx],S_KILL) ;
                    // Update status matrix
                    ldata[idx] = 0 ;
                    idata[idx] = 0 ;
                    // If we had S block now we act as the block just destroyed
                    IF (ct==28)
                      ct = dt ;
                    END
                    // If we collide with an S block we gain an extra life
                    IF (dt==28)
                      lives++ ;
                    END
                  ELSE
                    // We cannot destroy this block...
                    IF (bd>0)
                      score_update(bd) ;
                      // Decrease LEVEL block count
                      curr_level.blocks -= bd ;
                      // We killed some blocks... we xchange with next block
                      ldata[idx]       = ct ;
                      idata[idx].graph = ct ;
                      ct = dt ;
                      graph = ct ;
                      // Check for END LEVEL situation
                      IF (curr_level.blocks <= curr_level.target)
                        score+=bd*100 ;
                        end_level = 1 ;
                        fired = 0 ;
                      ELSE
                        // Check for available moves
                        mv     = moves() ;
                        IF (mv==0)
                          // No available Moves
                          lives-- ;
                          GRAPH = 28 ;
                          ct    = 28 ;
                        END
                      END
                    END

                    ret = 1 ;
                    
                  END
                END
              END
            ELSE
              // Still moving, advance position
              x    += inc ;
              acum += inc ;
              IF (acum==TILE_W)
                // Advanced enough, update positions and reset acumulator
                px++ ;
                acum = 0 ;
              END
            END
          ELSE
            // Bouncing on the screen margin - cannot happen with current levels
            IF (bd>0)
              score_update(bd) ;
              // Decrease LEVEL block count
              curr_level.blocks -= bd ;
              // Check for END LEVEL situation
              IF (curr_level.blocks <= curr_level.target)
                score+=bd*100 ;
                end_level = 1 ;
                fired = 0 ;
              ELSE
                // Check for available moves
                mv     = moves() ;
                IF (mv==0)
                  // No available Moves
                  lives-- ;
                  GRAPH = 28 ;
                  ct    = 28 ;
                END
              END
            END

            ret = 1 ;
            
          END
        END

        // Check vertical collision
        //----------------------------------------------------------------------
        IF (dir_fl==DIR_VERTICAL)
          // Follow down while not collide
          IF (py<LEVEL_ROWS-1)
            // Not colliding with the bottom end of the screen
            IF (acum==0)
              // Get information of the block in the next cell
              dt = ldata[((py+1)*LEVEL_COLS+px)] ;
              // Check vertical collision with the next block
              IF ((dt&COLD_V_MASK)==0)
                // No colllision... we can go on
                y    += inc ;
                acum += inc ;
              ELSE
              
                // COLLISION DETECTED
                //--------------------------------------------------------------
                // We just detected a collision... now it is needed to know what
                // block type we just collided with
                //--------------------------------------------------------------

                IF ((dt&STATIC_MASK)==STATIC_MASK)
                  // STATIC-BLOCK -> Reset moves
                  IF (bd>0)
                    score_update(bd) ;
                    // Decrease LEVEL block count
                    curr_level.blocks -= bd ;
                    // Check for END LEVEL situation
                    IF (curr_level.blocks <= curr_level.target)
                      score+=bd*100 ;
                      end_level = 1 ;
                      fired = 0 ;
                    ELSE
                      // Check for available moves
                      mv     = moves() ;
                      IF (mv==0)
                        // No available Moves
                        lives-- ;
                        GRAPH = 28 ;
                        ct    = 28 ;
                      END
                    END
                  END

                  ret = 1 ;

                ELSE
                  // NON-STATIC BLOCK... see if we have to destroy it
                  IF (ct==28 || ct==dt || dt==28)
                    // We can destroy this block...
                    bd++ ;
                    y    += inc ;
                    acum += inc ;
                    // Destroy the block
                    signal(idata[((py+1)*LEVEL_COLS+px)],S_KILL) ;
                    // Update status matrix
                    ldata[((py+1)*LEVEL_COLS+px)] = 0 ;
                    idata[((py+1)*LEVEL_COLS+px)] = 0 ;
                    // If we had S block now we act as the block just destroyed
                    IF (ct==28)
                      ct = dt ;
                    END
                    // If we collide with an S block we gain an extra life
                    IF (dt==28)
                      lives++ ;
                    END
                  ELSE
                    // We cannot destroy this block...
                    IF (bd>0)
                      score_update(bd) ;
                      // Decrease LEVEL block count
                      curr_level.blocks -= bd ;
                      // We killed some blocks... we xchange with next block
                      ldata[((py+1)*LEVEL_COLS+px)] = ct ;
                      idata[((py+1)*LEVEL_COLS+px)].graph = ct ;
                      ct = dt ;
                      graph = ct ;
                      // Check for END LEVEL situation
                      IF (curr_level.blocks <= curr_level.target)
                        score+=bd*100 ;
                        end_level = 1 ;
                        fired = 0 ;
                      ELSE
                        // Check for available moves
                        mv     = moves() ;
                        IF (mv==0)
                          // No available Moves
                          lives-- ;
                          GRAPH = 28 ;
                          ct    = 28 ;
                        END
                      END
                    END
                    // Returning... the block must go back to the blob
                    ret = 1 ;
                  END
                END
              END
            ELSE
              // Still moving, advance position
              y    += inc ;
              acum += inc ;
              IF (acum==TILE_H)
                // Advanced enough, update positions and reset acumulator
                py++ ;
                acum = 0 ;
              END
            END
          ELSE
            // Bouncing on the screen bottom
            IF (bd>0)
              score_update(bd) ;
              // Decrease LEVEL block count
              curr_level.blocks -= bd ;
              // Check for END LEVEL situation
              IF (curr_level.blocks <= curr_level.target)
                score+=bd*100 ;
                end_level = 1 ;
                fired = 0 ;
              ELSE
                // Check for available moves
                mv     = moves() ;
                IF (mv==0)
                  // No available Moves
                  lives-- ;
                  GRAPH = 28 ;
                  ct    = 28 ;
                END
              END
            END

            ret = 1 ;

          END
        END

      ELSE

        // Bouncing back
        //----------------------------------------------------------------------
        // WARNING: some heavy maths ahead... while bouncing we will be going
        // along a parabolic course passing through the destination point on
        // the downwards part of the parametric curve.
        //
        // Parabolic equations:
        // x = Vo * cos(alpha) * t
        // y = Vo * sin(alpha) * t + ((-g)*t^2)/2
        //
        // If we reduce t we get a tan(alpha) second degree equation in the form
        // (g*x^2)/(2*Vo^2)*tan(alpha)^2 - x*tan(alpha) + ((g*x^2)/(2*Vo^2)+y)=0
        //
        // Solving this equation we get 2 solotuions, one in the upwards curve
        // section (using +) and one in the descending section (using -)
        //
        // We fix a base Vo that will be updated if needed (not enough force) to
        // trace a minimal curve and apply the formulae for x & y until we reach
        // our target; X = 12 and Y = pj.y ;
        //--------------------------------------------------------------------------

        IF (al<0)

          // Distance conversions
          dx = x - 12 ;
          dy = y - pj.y ;

          cvo = VO ;

          REPEAT
            // First we must solve the eqution
            a = (dx * dx * GR) / (2 * cvo * cvo) ;
            b = -dx ;
            c = a + dy ;

            tmp = (b*b) - (4*a*c) ;
            tmp = sqrt(tmp) ;
            tmp = (-b+tmp)/(2*a) ;

            al = atan(tmp) ;
            vx = cvo * cos(al) ;
            vy = cvo * sin(al) ;
            // increment start speed for next iteration if needed
            cvo += 5 ;
            // Until we get a valid angle (angle > 0)
          until (al>0) ;

          // time increment for our interpolation
          tinc = 0.2 ;
          t  = tinc  ;
          xo = x ;
          yo = y ;

          
        ELSE

          IF (x>12)

            t+=tinc ;
            x = xo - vx*t ;
            y = yo - (vy*t) + ((GR*t*t)/2) ;
            angle += 30000 ;
          
          ELSE

            bd     = 0 ;
            graph  = ct ;
            x      = 12 ;
            y      = pj.y ;
            px     = 1 ;
            py     = pjy ;
            dir_fl = 0 ;
            acum   = 0 ;
            moving = 0 ;
            fired  = 0 ;
            angle  = 0 ;
            ret = 0 ;
            al = -1 ;
            
          END

        END
      END
    END
    FRAME ;
  
  END

END

//------------------------------------------------------------------------------
// MOVES
//------------------------------------------------------------------------------
// Check for available moves... return accordingly
// This process checks recursively all the rows in search of a valid move, stops
// after finding the first ocurrence.  Returns 0 if no moves are availbal or 1
// otherwise.  It is called after a "FIRE" operation is ended
// There is a problem here... we need to have the UPDATED STATE MATRIX to check
// for this... so we will be creating a copy of the STATE MATRIX and update it
// before deciding...  This will solve the
//------------------------------------------------------------------------------

PROCESS moves()

PRIVATE

  INT found = 0 ;             // Moves found
  INT count = LEVEL_ROWS-1 ;  // From bottom row up, faster than the other way
  INT dt    = 0 ;             // Next cell block type
  int aux   = 0 ;             // auxiliary xchange data variable
  INT dirx  = 0 ;             // Moving LEFT->RIGHT
  INT diry  = 0 ;             // Moving UP->DOWN
  INT px    = 0 ;             // Current X position (cell)
  INT py    = 0 ;             // Current Y position (cell)
  INT i,j;                    // General counters

  
  BYTE tmpdata[LEVEL_SIZE-1]; // Work copy of the collision MATRIX
  
BEGIN



  // if SPECIAL BLOCK a move is always possible
  IF (dp.graph == 28) return (1) ; END
  
  // Copy data to tempdata
  memcopy(OFFSET tmpdata,OFFSET ldata,LEVEL_SIZE) ;
  
  // Update the matrix... we follow from right to left, bottom-up
  // We start on LEVEL_COLS-2 as the last col is always of WALL TILES
  // This is not optimized yet, as it works the full level


  FOR (i=LEVEL_COLS-1; i>1 ; i-- )
    FOR (j = LEVEL_ROWS-1; j>0 ; j--)
      dt = tmpdata[j*LEVEL_COLS+i] ;

      IF (dt==0)
        aux = tmpdata[(j-1)*LEVEL_COLS+i] ;
        IF (aux<STATIC_MASK)
          tmpdata[j*LEVEL_COLS+i] = aux;
          tmpdata[(j-1)*LEVEL_COLS+i] = 0 ;
        END
      END
    END
  END


  // otherwise work out the full level starting from bottom row as it is easier
  // to match a block faster from the bottom-up
  WHILE (found==0 && count>=0)
    // work the full "route" for this row as if we shooted our current block
    dirx = 1 ;
    diry = 0 ;
    py   = count ;
    px   = 1 ;

    // start moving horizontally
    WHILE (!found && dirx==1)
      dt = tmpdata[count*LEVEL_COLS+px+1] ;

      // Follow RIGHT while not collide
      IF (px<LEVEL_COLS)
        IF ((dt&COLD_H_MASK)==0)
          // No block just to the right
          px++;
        ELSE
          IF ((dt&STATIC_MASK)==STATIC_MASK)
            // solid block (wall or tube)
            dirx = 0 ;
            diry = 1 ;
          ELSE
            // destroyable block
            IF (dp.graph==dt || dt==28)
              // it is the same type or it is a special block
              dirx = 0 ;
              found = 1 ;
            ELSE
              // diff. type, end check for this row
              dirx = 0 ;
            END
          END
        END
      ELSE
        // RIGHTMOST of the level... SHOULD NEVER OCUR
        dirx = 0 ;
      END
    END
    // start moving vertically, normally after a static-block collision
    WHILE (found==0 && diry==1)
      dt = tmpdata[((py+1)*LEVEL_COLS+px)] ;
      // Follow down while not collide
      IF (py<LEVEL_ROWS)
        IF ((dt&COLD_V_MASK)==0)
          // No block just below
          py++;
        ELSE
          IF ((dt&STATIC_MASK)==STATIC_MASK)
            // Collide with a wall or tube, end check for this column
            diry = 0 ;
          ELSE
            // destroyable block
            IF (dp.graph==dt || dt==28)
              // it is the same type or it is a special block
              diry = 0 ;
              found = 1 ;
            ELSE
              // diff. type, end check for this column
              diry = 0 ;
            END
          END
        END
      ELSE
        // bottom of the level
        diry = 0 ;
      END
    END

    // go up 1 row
    count-- ;

   END

   // give the results of our search
   return (found) ;
   
END

//------------------------------------------------------------------------------
// flecha
//------------------------------------------------------------------------------
// Marker for next "impact" point.  This process traces every frame the route
// for the current firing block.  The original game just used the VERTICAL part
// but I have implemented both... Discarding horizontal is quite trivial though
// Base graph for the arrow is GRAPHIC_ID 7
//------------------------------------------------------------------------------

PROCESS flecha()

PRIVATE

  px     = 1 ;   // X position (cell)
  py     = 0 ;   // Y position (cell)
  fl_dir = 0 ;   // Moving direction flag
  found  = 0 ;   // Found the end position
  dt     = 0 ;   // Next cell collision information
  
BEGIN

  // Sprite loop
  //----------------------------------------------------------------------------
  
  WHILE (1)
  
    IF (fired)
      // Show only when we have a block to "fire"
      graph = 0 ;
    ELSE
      // Reset counters and positions
      px     = 1 ;
      py     = pjy ;
      found  = 0 ;
      fl_dir = DIR_HORIZONTAL ;

      // Moving in the X axis... work out the route
      WHILE (found==0 && fl_dir==DIR_HORIZONTAL)
        dt = ldata[py*LEVEL_COLS+px+1] ;
        // Follow RIGHT while not collide
        IF (px<LEVEL_COLS)
          IF ((dt&COLD_H_MASK)==0)
            // No block just to the right
            px++;
          ELSE
            IF ((dt&STATIC_MASK)==STATIC_MASK)
              // Static block (wall or tube), must turn
              fl_dir = DIR_VERTICAL ;
            ELSE
              // Normal block... HACK HERE TO REMOVE HORIZONTAL ARROW
              fl_dir = 0 ;
              found  = 1 ;
              GRAPH = 0 ;
            END
          END
        ELSE
          // RIGHTMOST of the level... SHOULD NEVER HAPPEN
          found  = 1 ;
          fl_dir = 0 ;
          GRAPH  = 0 ;
        END
      END

      // Moving in the Y axis... work out the route
      WHILE (found==0 && fl_dir==DIR_VERTICAL)
        dt = ldata[((py+1)*LEVEL_COLS+px)] ;
        // Follow down while not collide
        IF (py<LEVEL_ROWS)
          IF ((dt&COLD_V_MASK)==0)
            // No block just below
            py++;
          ELSE
            // We are ready to go
            fl_dir = 0 ;
            found  = 1 ;
            GRAPH  = 7 ;
          END
        ELSE
          // bottom of the level
          found  = 1 ;
          fl_dir = 0 ;
          GRAPH  = 7 ;
        END
      END

      // Normalize results... if there are no blocks we might end up below the
      // last row or past the rightmost so we must normalize values
      IF (px>=LEVEL_COLS) px = LEVEL_COLS-1 ; END
      IF (py>=LEVEL_ROWS) py = LEVEL_ROWS-1 ; END

      // Calculate positioning
      x = px*8 + TILE_W/2 ;
      y = py*8 + TILE_H/2 ;

    END
    frame ;
  END
  
END


//------------------------------------------------------------------------------
// SELECTOR
//------------------------------------------------------------------------------
// Small graphic showing the selected option in the menus... just repositions
// every frame based on the selected option
//------------------------------------------------------------------------------

PROCESS selector()

BEGIN

  graph = 14 ;
  x     = 55 ;

  // Sprite loop
  //----------------------------------------------------------------------------
  
  WHILE (1)
    y = 55 + option * 7 ;
    frame ;
  END

END

//------------------------------------------------------------------------------
// WRITE_AT
//------------------------------------------------------------------------------
// Write a given text using the 4x5 font.  This process generates 2 maps for the
// given text, allowing flipping for them in the menus.  Texts are written using
// a base letter set in a single map (maps 12 & 13).  The menu roll-overs are
// controlled by the OPT variable (checked against global var OPTION).  There is
// a global CHARACTER MAPPING array used to find the letters in the graphic
//------------------------------------------------------------------------------

PROCESS write_at(INT x,INT y,STRING text,INT opt)

PRIVATE

  INT  nsel,sel ; // Graphic identifiers for non-selected and selected texts
  INT  i,pos ;    // General counters
  INT  length ;   // String length
  CHAR p ;        // current char in the string

BEGIN

  length = LEN(text) ;               // Get the string length

  nsel = new_map(4*length,5,8) ;     // Generate the new maps
  sel  = new_map(4*length,5,8) ;
  map_clear(0,nsel,0) ;              // Clear the maps
  map_clear(0,sel,0) ;
  set_center(0,nsel,0,0) ;           // Set control point at 0,0
  set_center(0,sel,0,0) ;

  FOR (i=0;i<length;i++)
    // First we reset the counter
    pos = 0 ;
    // We get the next letter in the string
    p = substr(text,i,1) ;
    // Search the letter in the font mapping
    WHILE (fntmap[pos]!=p && pos<27)
      pos++ ;
    END
    // Copy the letters in place
    map_block_copy(0,nsel,i*4,0,12,pos*4,0,4,5,0) ;
    map_block_copy(0,sel,i*4,0,13,pos*4,0,4,5,0) ;
  END

  // Sprite loop
  //----------------------------------------------------------------------------
  
  WHILE(mclean==0)
    IF  (option==opt)
      // Selected
      GRAPH = sel ;
    ELSE
      // Not selected
      graph = nsel ;
    END
    frame ;
  END
  
  // Garbage collect
  //----------------------------------------------------------------------------
  // liberate all memory used by the texts
  //----------------------------------------------------------------------------
  unload_map(0,sel) ;
  unload_map(0,nsel) ;

END


//------------------------------------------------------------------------------
// SCORE_UPDATE
//------------------------------------------------------------------------------
// Updates the score
//------------------------------------------------------------------------------

PROCESS score_update(int bl)

PRIVATE

  bvalue = 10 ;

BEGIN

  score += (bvalue * bl) * bl ;

END


//------------------------------------------------------------------------------
// LIVE COUNTER
//------------------------------------------------------------------------------
// Writes the number of lives
//------------------------------------------------------------------------------

PROCESS live_counter()

PRIVATE

  byte old_lives ;

BEGIN

  graph = 0 ;
  old_lives = lives ;
  
  number(lives+1,135,5) ;
  
  WHILE (1)
  
    if (old_lives!=lives)
      son.graph = lives+1 ;
      old_lives = lives ;
    end
    frame ;
  
  END

END


//------------------------------------------------------------------------------
// BLOCK COUNTER
//------------------------------------------------------------------------------
// Writes the number of blocks
//------------------------------------------------------------------------------

PROCESS block_counter()

PRIVATE

  byte old_blocks ;
  int  digits[1] ;

BEGIN

  graph = 0 ;
  old_blocks = curr_level.blocks ;

  digits[0] = number(old_blocks%10+1,135,15) ;
  digits[1] = number(old_blocks/10+1,129,15) ;

  WHILE (1)

    if (old_blocks!=curr_level.blocks)
      old_blocks = curr_level.blocks ;
      digits[0].graph = old_blocks%10+1 ;
      digits[1].graph = old_blocks/10+1;
    end
    frame ;

  END

END


//------------------------------------------------------------------------------
// TARGET COUNTER
//------------------------------------------------------------------------------
// Writes the target number
//------------------------------------------------------------------------------

PROCESS target_counter()

PRIVATE

  int  digits[1] ;

BEGIN

  graph = 0 ;

  digits[0] = number(curr_level.target%10+1,135,25) ;
  digits[1] = number(curr_level.target/10+1,129,25) ;

  WHILE (1)

    frame ;

  END

END


//------------------------------------------------------------------------------
// STAGE COUNTER
//------------------------------------------------------------------------------
// Writes the stage info
//------------------------------------------------------------------------------

PROCESS stage_counter()

PRIVATE

  int  digits[1] ;

BEGIN

  graph = 0 ;

  digits[0] = number((level)%10+1,135,35) ;
  digits[1] = number((level)/10+1,129,35) ;
  

  WHILE (1)

    frame ;

  END

END


//------------------------------------------------------------------------------
// SCORE COUNTER
//------------------------------------------------------------------------------
// Writes the score
//------------------------------------------------------------------------------

PROCESS score_counter()

PRIVATE

  int old_score ;
  int digits[4] ;

BEGIN

  graph = 0 ;
  old_score = score ;

  digits[0] = number(old_score%10+1,135,45) ;
  digits[1] = number((old_score/10)%10+1,129,45) ;
  digits[2] = number((old_score/100)%10+1,123,45) ;
  digits[3] = number((old_score/1000)%10+1,117,45) ;
  digits[4] = number((old_score/10000)%10+1,111,45) ;

  WHILE (1)

    if (old_score!=score)
      old_score = score ;
      digits[0].graph = old_score%10+1 ;
      digits[1].graph = (old_score/10)%10+1 ;
      digits[2].graph = (old_score/100)%10+1 ;
      digits[3].graph = (old_score/1000)%10+1 ;
      digits[4].graph = (old_score/10000)%10+1 ;
    end
    frame ;

  END

END


//------------------------------------------------------------------------------
// Time Counter
//------------------------------------------------------------------------------
// Put the timer in place and update accordingly every second
//------------------------------------------------------------------------------

PROCESS time_counter()

PRIVATE

  int digits[2] ;
  int old_time, aux  ;

BEGIN

  graph = 0 ;
  
  // Initializing level timer
  old_time = curr_level.secs + bonust ;
  
  // Initialize bonus time for next level... it is always based on last 30"
  bonust = 30 ;

  // Level has not timed-out
  timeout = 0 ;
  
  // Determine and create the digits
  aux = old_time ;
  digits[2] = number((aux/60)%60+1,120,55) ;
  aux -= (aux/60)%60*60 ;
  digits[0] = number(aux%10+1,135,55) ;
  digits[1] = number((aux/10)%10+1,129,55) ;

  WHILE (1)

    IF (!timer_on) timer[0]=0 ; END
    
    // Every 100 100ths of a second (that is the timers tick) reduce 1 second
    // in the timer
    //--------------------------------------------------------------------------
    IF (timer[0]>=100)
      old_time-- ;
      // Update digits
      aux = old_time ;
      digits[2].graph = (aux/60)%60+1 ;
      aux -= (aux/60)%60*60 ;
      digits[0].graph = aux%10+1 ;
      digits[1].graph = (aux/10)%10+1 ;
      // reduce the current timer in 1 second... note we use a MODULE (%) so in
      // case a given check takes longer than expected the secons will still be
      // seconds and won't be out of sincro
      timer[0]%=100 ;
      // Update bonus time if needed
      IF (old_time<30) bonust = old_time; END
      // Rise time out flag if needed
      IF (old_time<=0) timeout=1 ; END
    END

    frame ;

  END

END

//------------------------------------------------------------------------------
// NUMBER
//------------------------------------------------------------------------------
// Writes a number in screen...
//------------------------------------------------------------------------------

PROCESS number(int graph,int x,int y)

BEGIN

  FILE = fpgnumbers ;

  while (1)
    frame ;
  end

END

//------------------------------------------------------------------------------
// SCREEN_INIT
//------------------------------------------------------------------------------
// Start the required video mode with a given ZOOM activation
//------------------------------------------------------------------------------

PROCESS screen_init(INT zoom)

BEGIN

  IF (!zoom)
    set_mode  (140,96,MODE_8BITS,MODE_WINDOW) ;
//    set_mode  (320,200,MODE_8BITS,MODE_WINDOW) ; // used for console tests
  ELSE
    set_mode  (140,96,MODE_8BITS,MODE_WINDOW|MODE_2XSCALE) ;
  END
    
END

