//
// Title        : tetrislib - part of scopetris
// Author       : Lars Pontoppidan 
// Date         : Aug. 2007
// Version      : 0.1
// Target MCU   : AtMega32 at 8 MHz
//
//
//                       TETRIS LIBRARY
//
//           with vector rendering and animation effects
//
//                 with naive or real gravity
//
//                        for the AVR
//
//
// DESCRIPTION:
// Tetrislib contains game logic, game state and graphics rendering using 
// drawlib for a tetris game.
//
// CREDITS:
// Tetrislib was created by Lars Pontoppidan in August 2007.
//
// COMPILER INFO:
// This code compiles with avr-gcc v.4.1.2
// 
// DISCLAIMER:
// The author is in no way responsible for any problems or damage caused by
// using this code. Use at your own risk.
//
// LICENSE:
// This code is distributed under the GNU Public License
// which can be found at http://www.gnu.org/licenses/gpl.txt
//



#include <string.h>
#include <stdlib.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

#include "drawlib.h"
#include "vectorfont.h"
#include "tetrislib.h"


////////////////////////////////////////////////////////////////////////////////
// GAME DEFINITIONS AND DECLARATIONS
////////////////////////////////////////////////////////////////////////////////

//
// PLAYGROUND MATRIX is 10 cubes wide, 20 cubes high
// NOTE: The game might break if these defines are changed...
//
#define MATRIX_WIDTH 10
#define MATRIX_HEIGHT 20
// 
// Coordinate definition (x,y)
//
//   (0,20)     (10,20)
//     +----------+
//     |x        x|
//     |          |
//     .          .
//     .          .
//     |x        x|
//     +----------+
//   (0,0)      (10,0)
//


//
// CUBE OCCUPATION is stored as bits in a 16 bit var for each row
//
// NOTE: LSB is leftmost cube!
unsigned short occupancy[MATRIX_HEIGHT];

// NOTE: sentinel cubes on each side!
// NOTE: MSB in occupancy is used temporarily for animation
#define OCCUPANCY_FREE (1|(1<<(MATRIX_WIDTH+1)))
#define OCCUPANCY_NONE 0xFFF


// BRICKS
//  
// The actual state of the game is stored as an array of bricks.
// 
// The cubes of a brick are described in 4 bytes:
//
// 4 bytes:   brick[3]:   LSB X X X X... 
//            brick[2]:   LSB X X X X... 
//            brick[1]:   LSB X X X X... 
//            brick[0]:   LSB X X X X... 
//  
// For instance:        . . . .   =>   brick[3] = 0
//                      x . . .   =>   brick[2] = 1
//                      x x . .   =>   brick[1] = 3
//                      x . . .   =>   brick[0] = 1
//
// The coordinates of the brick indicate position of lower left cube 

struct Brick_Type {
  unsigned char brick[4];  
  unsigned char x;    // high nibble of x is used for y offset (pixels)
  signed char y;
};

//
// BRICKS IN GAME
// 
// The player controlled falling brick:
struct Brick_Type falling_brick;

// The pending brick
struct Brick_Type pending_brick;

// The array of bricks currently active in the game:
#define MAX_BRICKS 100
struct Brick_Type active_brick[MAX_BRICKS];

unsigned char active_bricks;



//
// BRICK TEMPLATES
//
// When a new brick is entering, it is randomly selected from one of these:
//
#define BRICK_TEMPLATES 7

const unsigned char brick_template[BRICK_TEMPLATES][2] PROGMEM = {
 {15,0},
  {7,1},
  {7,4},
  {3,3},
  {3,6},
  {7,2},
  {6,3}};
  
  
//
// GRAPHICS
//
// The graphics rendering reacts to graphics_status
// 
#define SHOW_FALLING_BRICK 1
#define SHOW_ZAPPED_ROWS   2
#define SHOW_ZAP_COUNT     4
unsigned char graphics_status;

//
// Graphics definitions
//
#define BRICK_SIZE 10

#define X_OFFSET 50
#define Y_OFFSET 5

#define BRICK_INSET 2

//   |         |         |
//   |         |         |
// 2 | x       | x     x |
//   |         |         |
// 0 +---------+---------+
//   0 2       10
//     |
//  brick inset


// Tetris game statemachine. 
//
#define TETRIS_IDLE          0
#define TETRIS_NEW_BRICK     1
#define TETRIS_BRICK_FALLING 2
#define TETRIS_TRY_ZAP       3
#define TETRIS_DO_GRAVITY    4
#define TETRIS_SHOW_ZAPPED   5
#define TETRIS_GAME_OVER     255
//
unsigned char tetris_state;


// Tetris mode is an external bitflag variable and specifies: 
//
//   TETRIS_MODE_SPEEDUP  1    // falling brick speedup
//   TETRIS_MODE_GRAVITY  2    // for real gravity. If not set, naive gravity
//
unsigned char tetris_mode;


//
// Game and animation timing
//
// tetris_draw_frame is called with 100 Hz (tf = 10ms)
//
// tetris_speed defines how fast the brick is falling and is defined: 
//
// each frame brick moves pixels:      = tetris_speed / 64
// => each frame brick moves grids:    = tetris_speed / (64 * 10)
// => each sec   brick moves grids:    = tetris_speed / (64 * 10 * tf)
// => brick moves one grid each (sec): = (64 * 10 * tf) / tetris_speed
//                                     = 6.4(s) / tetris_speed
// for example: tetris_speed = 255   =>  tbg ~ 0.025 sec  (max speed)
// for example: tetris_speed = 128   =>  tbg = 0.050 sec
// for example: tetris_speed = 64    =>  tbg = 0.100 sec
// for example: tetris_speed = 32    =>  tbg = 0.2 sec
// for example: tetris_speed = 16    =>  tbg = 0.4 sec
// for example: tetris_speed = 8     =>  tbg = 0.8 sec
// for example: tetris_speed = 4     =>  tbg = 1.6 sec
// 
unsigned char tetris_speed;
 
#define SPEEDUP_SPEED 255     // The speed of speedup mode


//
// Falling brick pixel offset
//
signed short falling_pixels;  // this is stored in /256 fixpoint
                              // thus 2560 means 10 pixel offset

unsigned char animation_frame;

unsigned char lines_zapped;


//
// Score keeping
//
// Total number of lines zapped
unsigned short tetris_stats_lines;                              
//
// Number of zap-rounds
unsigned short tetris_stats_zapcount;                              



////////////////////////////////////////////////////////////////////////////////
// BRICK MANAGEMENT IMPLEMENTATION
////////////////////////////////////////////////////////////////////////////////
  
void inline copy_brick(struct Brick_Type *dest, struct Brick_Type *src)
{
  memcpy(dest, src, sizeof(struct Brick_Type));
}

// Expand array of bricks
void new_brick(struct Brick_Type *brick)
{
  if (active_bricks < MAX_BRICKS) {
    copy_brick(&(active_brick[active_bricks]), brick);
    active_bricks++;
  }
  
}

// Delete element from array of bricks
void delete_brick(unsigned char index)
{
  if (index < (active_bricks-1)) {
    copy_brick(&(active_brick[index]), &(active_brick[active_bricks-1]));
  }
  
  active_bricks--;
}
  
// New brick from template
void init_brick_template(struct Brick_Type *dest, unsigned char tno)
{

}



// Rotate brick
void rotate_brick(struct Brick_Type *dest, struct Brick_Type *src, unsigned char ccw)
{
  unsigned char c,b;
  
  if (ccw) {
    b=1;
    for (c=0; c<4; c++) {
      dest->brick[c] = ((src->brick[3] & b)?1:0) | ((src->brick[2] & b)?2:0) | 
                       ((src->brick[1] & b)?4:0) | ((src->brick[0] & b)?8:0);
      b = b<<1;
    }
  }
  else {
    b=8;
    for (c=0; c<4; c++) {
      dest->brick[c] = ((src->brick[0] & b)?1:0) | ((src->brick[1] & b)?2:0) | 
                       ((src->brick[2] & b)?4:0) | ((src->brick[3] & b)?8:0);
      b = b>>1;
    }
  }
  
  dest->x = src->x;
  dest->y = src->y; 
  
  // left adjust
  while (((dest->brick[0] & 1) | (dest->brick[1] & 1) |
          (dest->brick[2] & 1) | (dest->brick[3] & 1)) == 0) {
    dest->brick[0] >>= 1;
    dest->brick[1] >>= 1;
    dest->brick[2] >>= 1;
    dest->brick[3] >>= 1;
  }
  
  // down adjust
  while (dest->brick[0] == 0) {
    dest->brick[0] = dest->brick[1];
    dest->brick[1] = dest->brick[2];
    dest->brick[2] = dest->brick[3];
    dest->brick[3] = 0;
  }  

}

// Split brick in two. Splitrow refers to either 0,1,2,3 of source brick
void split_brick(struct Brick_Type *lower, struct Brick_Type *upper, 
                 struct Brick_Type *src, unsigned char splitrow)
{
  unsigned char c;
  
  copy_brick(upper, src);
  copy_brick(lower, src);
  
  for (c=0; c<=splitrow; c++) {
    upper->brick[c] = 0;
  }
   
  for (c=splitrow; c<4; c++) {
    lower->brick[c] = 0;
  }
 
}

// This function fixes a brick to have row 0 occupied, returns false if brick
// is empty
unsigned char fix_brick(struct Brick_Type *brick)
{
  // check emptiness
  if ((brick->brick[0]|brick->brick[1]|brick->brick[2]|brick->brick[3])==0) 
    return 0;
    
  // fix brick
  while (brick->brick[0] == 0) {
    brick->brick[0] = brick->brick[1];
    brick->brick[1] = brick->brick[2];
    brick->brick[2] = brick->brick[3];
    brick->brick[3] = 0;
    brick->y++;
  }
  
  return 1;
      
}

// Calculate occupancy grid according to active_brick array
void calc_occupancy_grid(void)
{
  unsigned char i;
  unsigned char c,y;
  signed char s;
  
  // clear occupancy grid (only sentinel cubes)
  for (i=0; i<MATRIX_HEIGHT; i++) {
    occupancy[i] = OCCUPANCY_FREE;
  }
  
  // or each brick into grid
  for (i=0; i<active_bricks; i++) {
    s = active_brick[i].x+1;
    y = active_brick[i].y;
    
    for (c=0; c<4; c++) {
      occupancy[c+y] |= (unsigned short)active_brick[i].brick[c] << s;
    }
  }
}

// Xors the brick's cubes in occupancy grid 
void occupancy_xor_brick(struct Brick_Type *brick) 
{
  unsigned char c,y;
  signed char s;
  
  s = brick->x+1;
  y = brick->y;
  
  for (c=0; c<4; c++) {
    occupancy[c+y] ^= (unsigned short)brick->brick[c] << s;
  }
}

// Test if brick fits in occupancy grid
unsigned char test_if_brick_fits(struct Brick_Type *brick) 
{
  unsigned char c,y;
  signed char s;
  unsigned short ret=0;
  
  s = brick->x+1;
  y = brick->y;
  
  for (c=0; c<4; c++) {
    ret |= occupancy[c+y] & ((unsigned short)brick->brick[c] << s);
  }
  
  return (ret==0);
}

// Changes brick positions according to naive gravity, as in traditional tetris
// Returns false if no changes
// Assumes up-to-date occupancy grid, and updates occupancy also.
//
unsigned char apply_naive_gravity(void)
{
  // To emulate naive gravity, the occupancy grid is checked from down to up
  // for any completely cleared rows. If found, all bricks above the cleared
  // row are moved down with animation.
  
  unsigned char i,y;
  unsigned char moving = 0;
  
  // First, check if bricks are currently moving in animation
  for (i=0; i<active_bricks; i++) {
    if (active_brick[i].x & 0xF0) {
      active_brick[i].x -= 1 << 4;
      moving = 1;
    }
  }
    
  if (!moving) {
    // Find first free row in occupancy grid
    y = 0;
    while (occupancy[y] != OCCUPANCY_FREE) {
      if (++y == MATRIX_HEIGHT)
        return 0;
    }
  
    // Find all bricks above this row
    for (i=0; i<active_bricks; i++) {
      if (active_brick[i].y > y) {
        // Remove brick from occupancy grid
        occupancy_xor_brick(&active_brick[i]);
        // Move down and start animation
        active_brick[i].y--;
        // Add brick to occupancy grid in new position
        occupancy_xor_brick(&active_brick[i]);
        // Start animation
        active_brick[i].x |= 10 << 4 ;
        moving = 1;
      }
    }
  }
  
  return moving;
}


// Changes brick positions according to gravity, returns false if no changes
// Assumes up-to-date occupancy grid, and updates occupancy also.
//
unsigned char apply_gravity(void)
{
  unsigned char i;
  unsigned char moving = 0;
  
  // First, check if bricks are currently moving in animation
  for (i=0; i<active_bricks; i++) {
    if (active_brick[i].x & 0xF0) {
      active_brick[i].x -= 1 << 4;
      moving = 1;
    }
  }
  
  // Test all bricks if they are able to move one cube down
  if (!moving) {
    for (i=0; i<active_bricks; i++) {
      if (active_brick[i].y > 0) {
        // Remove brick from occupancy grid
        occupancy_xor_brick(&active_brick[i]);
        // Test if brick is ok moving one cube down
        active_brick[i].y--;
        if (test_if_brick_fits(&active_brick[i])) {
          // brick did fit, keep new y coordinate
          // add brick in occupancy grid at new position 
          occupancy_xor_brick(&active_brick[i]);
          // Start animation
          active_brick[i].x |= 10 << 4 ;
          moving = 1;
        }
        else {
          // didn't fit, restore old y coordinate
          active_brick[i].y++;
          // add brick at old position
          occupancy_xor_brick(&active_brick[i]);          
        }
      }
    }
  }
  
  return moving;
}

// Zap row with coordinate y. 
// Changes both active_brick array and occupancy grid
void zap_row(signed char y)
{
  unsigned char i;
  signed char dy;
  struct Brick_Type tmp1,tmp2;
  
  for (i=0; i<active_bricks; i++) {
    dy = y - active_brick[i].y;
    if ((dy >=0) && (dy < 4)) {
      // this brick must be split
      split_brick(&tmp1, &tmp2, &active_brick[i], dy);
      if (fix_brick(&tmp1)) {
        // lower brick is ok, copy this brick on top of original
        copy_brick(&active_brick[i], &tmp1);
        
        if (fix_brick(&tmp2)) {
          // higher brick is also ok, add this brick to end of array
          new_brick(&tmp2);
        } 
      }
      else if (fix_brick(&tmp2)) {
        // only higher brick is ok, copy this brick on top of original 
        copy_brick(&active_brick[i], &tmp2);
      }
      else {
        // nothing left of the brick!
        // delete entry in array and re-check it
        delete_brick(i);
        i--;
      }
    }
  }
  
  // The effect on occupancy grid is to remove one row completely
  occupancy[y] = OCCUPANCY_FREE;
  
}

// returns coordinate of row to zap, or 0xFF if no row to zap
//
unsigned char find_zap_row(void)
{
  unsigned char i;
  
  for (i=0; i<MATRIX_HEIGHT; i++) {
    if (occupancy[i] == OCCUPANCY_NONE) 
      return i;
  }
  
  return 0xFF;
  
}

////////////////////////////////////////////////////////////////////////////////
// GRAPHICS RENDERING
////////////////////////////////////////////////////////////////////////////////

unsigned char two_powers[4] = {1,2,4,8};

unsigned char inline brick_cube(struct Brick_Type *brick, 
                                  signed char x, signed char y)
{
//if ((x>=0) && (x<4) && (y>=0) && (y<4))  // below is equivalent
  if (((unsigned char)x<4) && ((unsigned char)y<4))
    return brick->brick[y] & two_powers[x];
  else
    return 0;
}


// This macro convinces compiler that its ok to use unsigned 8 bit
#define UCHAR_MUL(x,y) ((unsigned char)((unsigned char)(x)*(unsigned char)(y)))

//
// Draws the brick by tracing the edge all the way around.
// The brick position is basically defined by its x and y coordinate, but it 
// can be offset a number of pixels by y_offset and high nibble of x coordinate 
// 
// y_offset = 0xFF is a special case for drawing the pending brick left of the
// playground.
void draw_brick(struct Brick_Type *brick, unsigned char y_offset)
{
  signed char dx, dy;
  signed char x, y, xs;
  unsigned char tmp;
  
  // Find leftmost brick in lower row
  tmp = brick->brick[0];
  
  for (xs=0; xs<4; xs++) {
    if (tmp & two_powers[xs])
      break;
  }
  
  // Start pen
  if (y_offset == 0xFF) {
    // this special case is used for drawing the pending brick on the left
    draw_set_xy(UCHAR_MUL(xs, BRICK_SIZE) + 8 + BRICK_INSET, 180);    
  }
  else {
    x = (brick->x & 0x0F) + xs;
    y_offset += brick->x >> 4;
    y = brick->y;
    
    draw_set_xy(UCHAR_MUL(x, BRICK_SIZE) + X_OFFSET + BRICK_INSET, 
                UCHAR_MUL(y, BRICK_SIZE) + Y_OFFSET + BRICK_INSET+y_offset);
  }
              
  // Trace brick edge, start seeking right
  x = xs;
  y = 0;
  dx = 1;
  dy = 0;
  
  do {
    if (brick_cube(brick, x+dy, y-dx)) {
      // turn downwards
      tmp = -dx;
      dx = dy;
      dy = tmp;
      
      // move inset*2
      draw_pen_dxdy(dx, dy, BRICK_INSET * 2);
      
      // one pixel down the new direction
      x += dx;
      y += dy;
    }
    else if (brick_cube(brick, x+dx, y+dy)) {
      // Continue one pixel this direction
      draw_pen_dxdy(dx, dy, BRICK_SIZE);
      
      // update pixelpos
      x += dx;
      y += dy;
    }
    else {
      // stay in this pixel and turn upwards
      draw_pen_dxdy(dx, dy, BRICK_SIZE - BRICK_INSET * 2);
      
      tmp = dx;
      dx = -dy;
      dy = tmp;
      
    }
    
    // check if we are back facing right on start pixel
  } while (!((dx == 1) && (dy == 0) && (y == 0) && (x == xs)) );
  
}

// This function draws the playground square
void draw_playground(void)
{
  unsigned char x1,x2,y1,y2;
  
  x1 = X_OFFSET-1;
  x2 = X_OFFSET + BRICK_SIZE*MATRIX_WIDTH;
  y1 = Y_OFFSET-1;
  y2 = Y_OFFSET + BRICK_SIZE*MATRIX_HEIGHT;
  
  draw_set_xy(x1, y2);   
  draw_pen_dx(2,  (x2-x1)>>1);   // draw the playground square extra fast
  draw_pen_dy(-2, (y2-y1)>>1);
  draw_pen_dx(-2, (x2-x1)>>1);   
  draw_pen_dy(2,  (y2-y1)>>1);

}


void tetris_draw_frame(void)
{
  unsigned char i;

  DRAW_PIXEL_SPEED = 20;
  
  for (i=0; i<active_bricks; i++) {
    draw_brick(&active_brick[i],0);
  }
  
  if (graphics_status & SHOW_FALLING_BRICK) {
    draw_brick(&falling_brick, (falling_pixels & 0xFF00) >> 8);
  }
    
  if (graphics_status & SHOW_ZAPPED_ROWS) {
    DRAW_PIXEL_SPEED = 50;
    for (i=0; i<MATRIX_HEIGHT; i++) {
      if (occupancy[i] & 0x8000) {
        draw_box(X_OFFSET + 3, 
          Y_OFFSET + 3 + UCHAR_MUL(i, BRICK_SIZE),
          X_OFFSET + MATRIX_WIDTH*BRICK_SIZE - BRICK_INSET,
          Y_OFFSET + BRICK_SIZE - 3 + UCHAR_MUL(i, BRICK_SIZE));
      } 
    }
    DRAW_PIXEL_SPEED = 20;
  }

  draw_brick(&pending_brick, 0xFF);  

  if (graphics_status & SHOW_ZAP_COUNT) {
    vfont_number(X_OFFSET+MATRIX_WIDTH*0.5*BRICK_SIZE - (animation_frame>>2), 
                 Y_OFFSET+MATRIX_HEIGHT*0.75*BRICK_SIZE - (animation_frame>>1), 
                 animation_frame>>2, lines_zapped);
    if (--animation_frame == 5) {
      graphics_status &= ~SHOW_ZAP_COUNT;
    }
  }
  
  vfont_number(10,155,4,tetris_stats_lines);
  vfont_number(10,135,4,tetris_stats_zapcount);
  
  DRAW_PIXEL_SPEED = 10;
  draw_playground();
  
}

////////////////////////////////////////////////////////////////////////////////
// GAME PLAY IMPLEMENTATION
////////////////////////////////////////////////////////////////////////////////


// alter the falling brick
void tetris_modify_brick(unsigned char dx, unsigned char rot)
{
  struct Brick_Type tmp;

  if (dx) {
    // try to move brick and see if its ok
    falling_brick.x += dx;
    if (!test_if_brick_fits(&falling_brick)) {
      // not ok, move back
      falling_brick.x -= dx;
    }
    
  }
  
  if (rot) {
    // try rotate and see if its ok
    rotate_brick(&tmp, &falling_brick, 0);
    
    if (test_if_brick_fits(&tmp)) {
      // its ok!
      copy_brick(&falling_brick, &tmp);
    }
    else {
      // Not ok, this might be because brick is hitting right edge
      // try moving brick left
      if (tmp.x>2) {
        if (tmp.x--, test_if_brick_fits(&tmp)) {
          copy_brick(&falling_brick, &tmp);
        }
        else if (tmp.x--, test_if_brick_fits(&tmp)) {
          copy_brick(&falling_brick, &tmp);
        }
        else if (tmp.x--, test_if_brick_fits(&tmp)) {
          copy_brick(&falling_brick, &tmp);
        }        
      }
    }
  }
  
}

void get_new_brick(void)
{
  unsigned char c;
  
  // Promote pending brick to falling brick
  copy_brick(&falling_brick, &pending_brick);
  
  do {
    c = rand() & 7;
  } while (c == 7);
 
  // Make new pending brick from template
  pending_brick.brick[0] = pgm_read_byte_near(&brick_template[c][0]);
  pending_brick.brick[1] = pgm_read_byte_near(&brick_template[c][1]);
  pending_brick.brick[2] = 0;
  pending_brick.brick[3] = 0;
  pending_brick.x = MATRIX_WIDTH/2-1;
  pending_brick.y = MATRIX_HEIGHT-2; 
  
}

// Game statemachine implementation
void tetris_tick(void)
{
  unsigned char c;
  
  if (tetris_state == TETRIS_NEW_BRICK) {
    // add new falling brick
    get_new_brick();
    falling_pixels = BRICK_SIZE<<8;  // reset animation  
    
    if (test_if_brick_fits(&falling_brick)) {
      graphics_status |= SHOW_FALLING_BRICK;
      // go to next state
      tetris_state = TETRIS_BRICK_FALLING; 
    }
    else {
      // brick doesn't fit... game over
      tetris_state = TETRIS_GAME_OVER;
    }
     
  }
  else if (tetris_state == TETRIS_BRICK_FALLING) {
    
    if (tetris_mode & TETRIS_MODE_SPEEDUP) 
      falling_pixels -= ((unsigned short)SPEEDUP_SPEED) << 2;
    else
      falling_pixels -= ((unsigned short)tetris_speed) << 2;
    
      
    if (falling_pixels < 0) {
      falling_pixels += BRICK_SIZE<<8;
      
      // move brick down one step
      falling_brick.y--;
    
      if ((falling_brick.y>=0) && test_if_brick_fits(&falling_brick)) {
        // still fits 
      }
      else {
        // it hit the ground, add brick from previous position
        graphics_status &= ~SHOW_FALLING_BRICK;
        falling_brick.y++;
        new_brick(&falling_brick);
        // update occupancy grid
        occupancy_xor_brick(&falling_brick);
   
        lines_zapped = 0;
        
        // test zapping
        tetris_state = TETRIS_TRY_ZAP;
        
      }  
    }  
  }
  else if (tetris_state == TETRIS_TRY_ZAP) {
    
    // we need a new brick...
    tetris_state = TETRIS_NEW_BRICK;
    
    // ...unless there are lines to zap
    while ((c=find_zap_row()) != 0xFF) {
      zap_row(c);
      occupancy[c] |= 0x8000; // use MSB in occupancy to indicate zapped row
      lines_zapped++;
      tetris_state = TETRIS_SHOW_ZAPPED;
      animation_frame = 20;
    }
    
    if ((tetris_state == TETRIS_NEW_BRICK) && (lines_zapped > 0)) {
      // We are done zapping, update score
      tetris_stats_lines += lines_zapped;
      tetris_stats_zapcount++;
      
      // Brag with result if its good enough
      if (lines_zapped > 3) {
        graphics_status |= SHOW_ZAP_COUNT; 
        animation_frame = 100;
      }
    }
    
  }
  else if (tetris_state == TETRIS_DO_GRAVITY) {
    
    if (tetris_mode & TETRIS_MODE_GRAVITY) 
      c = apply_gravity();
    else      
      c = apply_naive_gravity();
      
    if (c)
      tetris_state = TETRIS_DO_GRAVITY; // something still moving
    else  
      tetris_state = TETRIS_TRY_ZAP;    // try zapping again
    
  }
  else if (tetris_state == TETRIS_SHOW_ZAPPED) {
    animation_frame--;
    
    if (animation_frame == 0) {
        // Animation is done, remove MSB in occupancy grid
        calc_occupancy_grid();
        // Apply gravity
        tetris_state = TETRIS_DO_GRAVITY;
        graphics_status &= ~SHOW_ZAPPED_ROWS;
    } 
    else {
      // Show zapped rows
      graphics_status |= SHOW_ZAPPED_ROWS;
    }
    
  }
  
}

//
// Must be called with 100 Hz
//
void tetris_handler(void)
{
  // draw frame
  tetris_draw_frame();
  
  // update game
  tetris_tick();
  
}

// Must be called at startup
void tetris_init(void)
{
  active_bricks = 0; 
  graphics_status = 0;

  tetris_stats_lines = 0;
  tetris_stats_zapcount = 0;
  
  tetris_state = TETRIS_NEW_BRICK;
  tetris_mode = 0;
  tetris_speed = 16;
  calc_occupancy_grid();
  get_new_brick();
}



