Step 3.3.3: The game loop

The game loop is the middle loop of our program where the actual gameplay happens. This loop is responsible for the following:

  1. Put the current piece on the top of the playfield, above the ceiling
  2. Get the next piece and display the piece preview
  3. Display score
  4. Do the update loop until the piece hits the bottom
  5. Check if topped out, exit the loop if so
  6. Otherwise check if rows must be cleared
  7. Update the score and level if needed

Let's follow the learn by coding method again.

REM --
REM -- The game loop
REM --
REPEAT
  REM -- Copy next shape to current shape
  shape_no! = nxt_shape_no!
  shape = nxt_shape
  shape_color! = colors![shape_no!]
  
  REM -- Get next shape and display preview
  nxt_shape_no! = CAST!(RND%() * 7.0)
  nxt_shape = get_shape(nxt_shape_no!, 0)
  CALL draw_preview
  
  REM -- Get current shape and put piece
  REM -- on top of the playfield
  piece_x! = 7
  piece_y! = 0
  piece_r! = 0
  
  REM -- Display current score
  TEXTAT 27, 14, score%

  REM --
  REM -- PASTE THE UPDATE LOOP HERE!
  REM --
  
  REM -- The shape hit the bottom
  REM -- A little correction first - the piece is currently erased!
  DEC piece_y! : CALL draw_shape(shape, piece_x!, piece_y!, shape_color!, 1)
  
  REM -- Lock piece and make it part of the playfield
  CALL lock_piece
  
  REM -- Check if we have topped out
  IF piece_y! > 4 THEN
    REM -- ..no we haven't
    REM -- Check if there are rows to be cleared
    REM -- and remove them
    rows_cleared! = 0
    row_no! = piece_y! + 3 : IF row_no! >= 24 THEN row_no! = 23
    REPEAT
      REM -- Check if row is full
      IF playfield[row_no!] & %0011111111111100 THEN
        CALL clear_row(row_no!)
        INC rows_cleared!
      ELSE
       DEC row_no!
      ENDIF
    UNTIL row_no! = piece_y!
    
    REM -- Update score and check if it's
    REM -- time for next level
    score% = score% + bonus%[rows_cleared!] * CAST%(level!)
    ttl_rows_cleared! = ttl_rows_cleared! + rows_cleared!
    IF ttl_rows_cleared! >= 100 THEN
      INC level!
      delay! = 11 - level!
      TEXTAT 27, 11, level!
      ttl_rows_cleared! = ttl_rows_cleared! - 100
    ENDIF
  ELSE 
    REM -- ...yes, topped out
    REM -- Assigning 1 to game_status will effectively
    REM -- exit the loop
    game_status! = 1
  ENDIF

  REM -- Everything is done, repeat for next shape
  REM -- or exit loop
UNTIL game_status! = 1

REM -- Bonus added to the score when clearing
REM --           0,    1,    2,     3,   4 rows
DATA bonus%[] = 0.0, 40.0, 100.0, 300.0, 1200.0

This loop is again a REPEAT … UNTIL loop. It loops until game_status! becomes 1, that is, the game is over.

Let's discuss what might be something new for you. There is a line somewhere near the beginning:

nxt_shape_no! = CAST!(RND%() * 7.0)

This is to randomly get a new shape for the next piece and there are several things in here that may not be straight forward. The first thing is the way we use the RND() function. This function is a little different in XC=BASIC as has three type variants: RND!() to get a random byte, RND() to get a random integer and RND%() to get a random float. Now the first two are not very useful for us as they return a number between 0-255 and -32768-32767, respectively. So we use the third one to get a random floating point number between 0 and 1, then multiply it with 7. But why 7.0? This is to tell the compiler that we mean 7 as a floating point number. This way the two numbers can be multiplied without type casting. After we're done with multiplying, we use the CAST!() function to cast the result as a byte. When casting from floating point to byte or integer, the number will be rounded, and that's what we actually need.

The rest is comprehensive I think. There's only one step left, that is, wrapping the above code in the program loop.