/*
  XBubble - board.c
 
  Copyright (C) 2002  Ivan Djelic <ivan@savannah.gnu.org>
  Copyright (C) 2003  Martin Quinson <martin.quinson@tuxfamily.org>
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
*/

#include <stdlib.h>
#include <string.h> /* memset */
#include <math.h>

#include "utils.h"
#include "cell.h"
#include "setting.h"
#include "frame.h"
#include "sprite.h"
#include "bubble.h"
#include "board.h"

extern int scale;
extern Set countdown_animation;
extern Set alert_animation;
extern Set canon_animation;

enum CountdownState {
  INACTIVE,
  ACTIVE,
  VISIBLE
};

struct _Board {
  enum BoardState state;
  int period;
  int launch_count;
  int launch_requested;
  int empty;
  int clock;
  int was_lowered;
  int color;
  int next_color;
  int last_fire_delay;
  int count;
  int canon_empty;
  int canon_angle;
  int nb_rising_bubbles;
  int nb_evaluations;
  int alert_on;
  int canon_direction;
  double vx[NB_ANGLES];
  double vy[NB_ANGLES];
  double canon_virtual_angle;
  double canon_speed;        /* speed increase with time */
  CellArray array;
  Set bubbles;
  Set tmpset;
  Set explosive_bubbles;
  Set malus_indicators;   /* yellow bubbles indicating 1 malus ball waiting */
  Set malus_indicators10; /* red bubbles indicating 10 malus balls waiting */
  SpritePool sprite_pool;
  Vector input;
  Vector output;
  Vector floating_cells;
  Vector exposion_dates;
  Sprite canon_sprite;
  Sprite countdown_sprite;
  Sprite alert;
  enum CountdownState countdown_state;
  RuleSet_t *ruleset;

  Vector placed_balls; /* temp for place_received_bubble() placed here to 
			  avoid mallocation at each call and do not get into trouble due to parallelism */
};

enum { 
  BOTTOM_LAYER = 0,
  CANON_LAYER,
  MEDIUM_LAYER,
  TOP_LAYER
};


void stick_bubble( Board board, Bubble bubble, int target);
void drop_bubbles(Board board);
void place_received_bubbles( Board board );



static Bubble new_board_bubble( Board board, int color ) {
  Bubble bubble = new_bubble( color, NEW_X, NEW_Y, MEDIUM_LAYER);
  set_bubble_state( bubble, NEW, MEDIUM_LAYER, 0);
  set_add( board->bubbles, bubble );
  add_sprite_to_pool( board->sprite_pool, bubble->sprite );
  return bubble;
}

/**
 * new_bubble_color:
 * @board:
 *
 * Compute which color the next bubble will be. It have to be one of the existing balls,
 * and no color must be choosen more often than the others
 */

static int new_bubble_color ( Board board ) {
  int possible[ NB_COLORS ];
  int i, count=0, choosen;

  if (board->ruleset->single) {
    memset( possible, 0, sizeof(possible) );
    for( i = 0; i < board->bubbles->size; i++ ) {
      possible[ ((Bubble) board->bubbles->element[i])->color ] = 1; 
    }
    for( i = 0; i < NB_COLORS; i++ )
      if (possible[i])
	count++;
    if (count == 0)  count = 1;
    
    choosen=rnd( count );
    for( i = 0; i < NB_COLORS; i++ )
      if (possible[i] && choosen-- <= 0) { 
	return i;
      }
    return 1;
  } else {
    return rnd(NB_COLORS);
  }
}

Board new_board( int period, RuleSet_t *ruleset, int mov_ceiling,
		 int *colors /* vector containing the colors of the level */ ) {
  int i;
  int cell;
  Bubble bubble;

  Board board = (Board) xmalloc( sizeof( struct _Board ));
  board->array = cell_array_new( mov_ceiling );
  board->bubbles = set_new( 2*NB_CELLS );
  board->tmpset = set_new( 2*NB_CELLS );
  board->sprite_pool = new_sprite_pool( TOP_LAYER+1, 2*NB_CELLS );
  board->input = vector_new( NB_CELLS );
  board->output = vector_new( NB_CELLS );
  board->explosive_bubbles = set_new( NB_CELLS );
  board->malus_indicators = set_new( 10 );
  board->malus_indicators10 = set_new( NB_CELLS / 10 + 1 );
  board->exposion_dates = vector_new( NB_CELLS );
  board->floating_cells = vector_new( NB_CELLS );
  board->ruleset = ruleset;
  board->period = period;
  board->nb_rising_bubbles = 0;
  board->placed_balls = vector_new( 10 );

  /* alert blinking light */
  board->alert = new_sprite( BOTTOM_LAYER, 30, alert_animation, 0, 0);
  set_sprite_position( board->alert, 
		       scale_x( ALERT_X, scale ),
		       scale_y( ALERT_Y, scale ));
  add_sprite_to_pool( board->sprite_pool, board->alert );
  board->alert_on = 0;
  board->clock = 0;

  /* countdown */
  board->countdown_sprite = new_sprite( BOTTOM_LAYER, 30, countdown_animation,
					0, 0);
  set_sprite_position( board->countdown_sprite, 
		       scale_x( COUNTDOWN_X, scale ),
		       scale_y( COUNTDOWN_Y, scale ));
  board->countdown_state = INACTIVE;

  /* canon */
  board->canon_speed = 1;
  board->canon_angle = 0;
  board->canon_virtual_angle = 0.0;
  board->canon_direction = 0;
  board->canon_sprite = new_sprite( CANON_LAYER, 30, canon_animation, 0, 0);
  add_sprite_to_pool( board->sprite_pool, board->canon_sprite );
  canon_move( board, 0);

  /* precompute launching vectors */
  for ( i = 0; i < NB_ANGLES; i++ ) {
    board->vx[i] = LAUNCHING_SPEED*sin((i-CANON_ANGLE_MAX)*ANGLE_STEP );
    board->vy[i] =-LAUNCHING_SPEED*cos((i-CANON_ANGLE_MAX)*ANGLE_STEP );
  }
  board->launch_count = 0;
  board->last_fire_delay = 1000;
  board->launch_requested = 0;
  board->empty = 0;

  /* load the level */
  for ( cell = 0; cell < NB_CELLS-COLS; cell++ )
    if (( colors[cell] > 0 )&&
	( cell % COLS >= row_start( board->array, cell/COLS ))) {
      bubble = new_board_bubble( board, colors[cell]-1 );
      stick_bubble( board, bubble, cell);
    }    

  /* start with a new bubble */
  board->color = new_bubble_color( board );
  new_board_bubble( board, board->color );
  board->canon_empty = 1;
  
  board->state = WAITING_FOR_CANON;
  return board;
}

void delete_board(Board board) {
  int i;
  cell_array_free( board->array );
  for ( i = 0; i < board->bubbles->size; i++ )
    delete_bubble( board->bubbles->element[i] );
  set_free( board->bubbles );
  set_free( board->tmpset );  
  set_free( board->explosive_bubbles );
  set_free( board->malus_indicators );
  set_free( board->malus_indicators10 );
  vector_free( board->input );
  vector_free( board->output );
  vector_free( board->exposion_dates );
  vector_free( board->floating_cells );
  vector_free( board->placed_balls );
  delete_sprite_pool( board->sprite_pool );
  delete_sprite( board->canon_sprite );
  delete_sprite( board->countdown_sprite );
  delete_sprite( board->alert );
  free( board );
}

static int board_overflow(Board board) {
  return  (board->nb_rising_bubbles == 0) &&
     cell_array_is_overflow(board->array);
}

static void switch_alert_on( Board board ) {
  set_sprite_frame( board->alert, 1);
  board->alert_on = 1;
}

static void switch_alert_off( Board board ) {
  set_sprite_frame( board->alert, 0);
  board->alert_on = 0;
}

/************************** countdown functions ***************************/

static void start_countdown( Board board ) {
  board->count = board->ruleset->max_fire_delay;
  board->countdown_state = ACTIVE;
}

static void cancel_countdown( Board board ) {
  switch ( board->countdown_state ) {
  case VISIBLE:
    remove_sprite_from_pool( board->sprite_pool, board->countdown_sprite);
  case ACTIVE:
    board->countdown_state = INACTIVE;
    board->last_fire_delay = board->ruleset->max_fire_delay - board->count;
    break;
  default:
    break;
  }
}
  
static void update_countdown( Board board, int dt ) {
  board->count -= dt;
  switch ( board->countdown_state ) {
  case ACTIVE:
    if ( board->count <= 0 )
      /* launch bubble automatically after timeout */
      board->launch_requested = 1;
    else
      if ( board->count <= COUNTDOWN ) {
	add_sprite_to_pool( board->sprite_pool, board->countdown_sprite );
	set_sprite_frame( board->countdown_sprite, 1+board->count/1000 ); 
	board->countdown_state = VISIBLE;
      }
    break;
  case VISIBLE:
    if ( board->count > 0 )
      set_sprite_frame( board->countdown_sprite, 1+board->count/1000 );  
    else {
      remove_sprite_from_pool( board->sprite_pool, board->countdown_sprite);
      board->countdown_state = ACTIVE;
    }
    break;
  default:
    break;
  }
}

/**************************************************************************/

void explode_board( Board board ) {
  int i, clock;
  Bubble bubble;
  float direction; /* direction of each bubble */
  /* sanity check */
  if (( board->state != LOST )&&
      ( board->state != WON )&&
      ( board->state != FROZEN )) {
    /* first explode all floating bubble sprites */
    for( i = 0; i < board->bubbles->size; i++ ) {
      bubble = board->bubbles->element[i];
      if (( bubble->state != RUNAWAY )&&
	  ( bubble->state != STUCK )&&
	  ( bubble->state != EXPLODING )) {
	bubble->vx = 0;
	bubble->vy = EXPLODING_SPEED;
	set_bubble_state( bubble, EXPLODING, TOP_LAYER, 0 );
      }
    }
    /* now explode stuck bubbles and add a clock skew */
    clock = 0;
    for ( i = NB_CELLS-1; i >= 0; i-- )
//    for ( i = 0 ; i < NB_CELLS-1; i++)
      if ( board->array->cell[i] != EMPTY_CELL ) {
	bubble = board->array->cell[i];
	 
	direction = ((float)rand())/((float)RAND_MAX+1.0) * 3.14159;
	bubble->vx = cos(direction)*EXPLODING_SPEED*25;
        bubble->vy = sin(direction)*EXPLODING_SPEED*25;

	set_bubble_state( bubble, EXPLODING, TOP_LAYER, clock );
	 clock -= PROPAGATION_TIME;//*(i%COLS == 0? 20: 0);
      }
    cell_array_empty( board->array );
    board->state = WON;
  }
}

void freeze_board( Board board ) {
  board->state = WON;
}

void stick_bubble( Board board, Bubble bubble, int target) {
  double x, y;
  cell_center( board->array, target, &x, &y );
  set_bubble_state( bubble, STUCK, BOTTOM_LAYER, 0);
  set_bubble_position( bubble, x, y);
  bubble->cell = target;
  board->array->cell[target] = bubble;
}

static void kill_bubble( Board board, Bubble bubble ) {
  set_bubble_state( bubble, RUNAWAY, TOP_LAYER, 0);
  remove_sprite_from_pool( board->sprite_pool, bubble->sprite );
}

static int animate_bubbles( Board board, int dt ) {
  int i;
  Bubble tmp;
  Bubble bubble;
  int clock;
  int active = 0;
  int count;
  int transition_allowed = 1;
  double x, y, t;
   
  /* 
     possible board state transitions:
     
     1. case NEW:       WAITING_FOR_CANON -> READY_TO_FIRE
     2. case READY:     READY_TO_FIRE     -> REBOUND
     3. case LAUNCHED:  REBOUND           -> WAITING_FOR_CANON | READY_TO_FIRE
     4. case RISING:    RECEIVING_BUBBLES -> WAITING_FOR_CANON | READY_TO_FIRE
  */

  /* make a fresh copy of bubble list */
  set_copy( board->bubbles, board->tmpset );

  /* process each bubble in copy */
  while ( board->tmpset->size > 0 ) {
    
    bubble = board->tmpset->element[0];
    set_remove_at( board->tmpset, 0 );

    /* update state of each bubble */
    switch ( bubble->state ) {

    case NEW:
      increase_sprite_clock( bubble->sprite, dt );
      if ( board->canon_empty ) {
	/* compute new position of bubble */
	t = M_PI * bubble->clock/2/CANON_LOAD_TIME;
	/* elliptic trajectory */
	x = CANON_X - ( CANON_X - NEW_X )*cos(t);
	y = NEW_Y + ( CANON_Y - NEW_Y )*sin(t);
	set_bubble_position( bubble, x, y);
	bubble->clock += dt;
	
	/* if bubble has reached canon then we're ready to fire */
	if ( bubble->clock > CANON_LOAD_TIME ) {
	  set_bubble_state( bubble, READY, MEDIUM_LAYER, 0 );
	  set_bubble_position( bubble, CANON_X, CANON_Y );
	  board->canon_empty = 0;
	  /* add a new bubble */
	  board->next_color = new_bubble_color( board );
	  new_board_bubble( board, board->next_color );
	  start_countdown(board);
	}
      }
      if (( ! board->canon_empty )&&
	  ( board->state == WAITING_FOR_CANON )&&( transition_allowed )) { 
	board->state = READY_TO_FIRE;
	transition_allowed = 0;
      }
      break;
      
    case READY:
      increase_sprite_clock( bubble->sprite, dt );
      update_countdown( board, dt );
      if (( board->launch_requested )&&
	  ( board->state == READY_TO_FIRE )&&( transition_allowed )) {
	/* launch bubble */
	set_bubble_state( bubble, LAUNCHED, MEDIUM_LAYER, 0);
	bubble->vx = board->vx[ board->canon_angle + CANON_ANGLE_MAX ];
	bubble->vy = board->vy[ board->canon_angle + CANON_ANGLE_MAX ];
	/* store target cell */
	bubble->cell = find_target_cell( board->array, 
					 board->canon_angle+CANON_ANGLE_MAX, 
					 &bubble->target_y,NULL );
	transition_allowed = 0;
	board->state = REBOUND;
	board->launch_count++;
	board->canon_empty = 1;
	board->launch_requested = 0;
	board->color = board->next_color;
	cancel_countdown(board);
      }
      break;
      
    case LAUNCHED:

      /* move bubble */
      bubble->x += dt*bubble->vx;
      bubble->y += dt*bubble->vy;
      /* bounce bubble against walls */
      if ( bubble->x < 0.5 ) {
//	bubble->x = 1.0 - bubble->x; //That is the other solution to stay inside. If you use it, change also cell.c
	bubble->x = 0.5;
	bubble->vx = -bubble->vx;
      }
      if ( bubble->x > COLS - 0.5 ) {
        bubble->x = COLS - 0.5;
//	bubble->x = 2*COLS - 1.0 - bubble->x;
	bubble->vx = -bubble->vx;
      }
      /* check if bubble has reached its target cell */
      if ( bubble->y <= bubble->target_y ) {
	stick_bubble( board, bubble, bubble->cell);
	
	/* check if bubble triggers an explosion */
	if ( (count=count_explosive_bubbles( board->array,
				      bubble->cell, 
				      board->explosive_bubbles,
				      board->exposion_dates )) >= 3 ) {
	  float violence=count*10;
	  float direction; /* direction of each ball */

	  if (violence > 100) violence=100;
	  
	  for ( i = 0; i < board->explosive_bubbles->size; i++ ) {
	     
	    tmp = board->explosive_bubbles->element[i];
	    /* simulate propagation */ 
	    clock = (1 - board->exposion_dates->element[i])*PROPAGATION_TIME;
	    set_bubble_state( tmp, EXPLODING, TOP_LAYER, clock);
	     
	    direction = ((float)rand())/((float)RAND_MAX+1.0) * -3.14159;
	    tmp->vx = cos(direction)*violence*EXPLODING_SPEED;
	    tmp->vy = sin(direction)*violence*EXPLODING_SPEED;
	    board->array->cell[tmp->cell] = EMPTY_CELL;
	    if ( i >= 3 ) /* immediately send extra bubbles to opponent */ 
	      vector_push( board->output, tmp->color );
	    /* this bubble has been processed */
	    set_remove( board->tmpset, tmp );
	  }
	}	
	/* note: no need to check for (transition_allowed) here */
	transition_allowed = 0;
	drop_bubbles(board);
	place_received_bubbles(board);
	/* allow transition only if no malus ball comes (or after the last malus ball arrives) */
	if ( board->nb_rising_bubbles == 0 ) 
	   board->state = ( board->canon_empty )? WAITING_FOR_CANON:READY_TO_FIRE;
      }
      else
	set_bubble_position( bubble, bubble->x, bubble->y);
      break;
      
    case STUCK:
      if ( ! bubble->clock )
        increase_sprite_clock( bubble->sprite, dt );
      break;
      
    case EXPLODING:
      active = 1;
      bubble->clock += dt;
      if ( bubble->clock <= 0 )
	break;
      if ( bubble->clock > get_bubble_animation_duration(bubble) &&
	   get_bubble_animation_nb_frame(bubble) > 1 ) {
	kill_bubble( board, bubble );
	break;
      }
      /* caution: no break -> code continues below */
      
    case FALLING:
      active = 1;
      /* FIXME: frottements */
      bubble->vx += dt*GRAVITY*10*(- bubble->vx);
      bubble->x += dt*bubble->vx;
      /* Apply gravity */
      bubble->y += dt*bubble->vy + 0.5*GRAVITY*dt*dt;
      bubble->vy += dt*GRAVITY;
      /* bounce bubble against walls */
      while ( bubble->x < 0.5 || bubble->x > COLS - 0.5001 ) { /* explosions can be quite violente */
	    
	 if ( bubble->x < 0.5 ) {
	    bubble->x = 1.0 - bubble->x;
	    bubble->vx = -bubble->vx*.8; /* Falling balls do not elasticly bounce against walls :) */
	 }
	 if ( bubble->x > COLS - 0.5001 ) {
	    bubble->x = 2*COLS - 1.0002 - bubble->x;
	    bubble->vx = -bubble->vx*.8; 
	 }
      }
      if (bubble->y < ( 0.5 + board->array->first_row*ROW_HEIGHT )) { /* bounce against the upper wall */
	 bubble->y -= board->array->first_row*ROW_HEIGHT;
	 bubble->y =  1.0 - bubble->y;
	 bubble->y += board->array->first_row*ROW_HEIGHT;
	 bubble->vy = -bubble->vy*.8; 
      }

      set_bubble_position( bubble, bubble->x, bubble->y);
      increase_sprite_clock( bubble->sprite, dt );
       
      /* detect runaway bubbles */
      if ( bubble->y > BOARD_HEIGHT - 0.5 ) {
	/* send bubble to opponent */
	if ( bubble->state == FALLING )
	  vector_push( board->output, bubble->color );
	kill_bubble( board, bubble );
      }
      break;
      
    case RISING:
      active = 1;
      set_bubble_position( bubble, 
			   bubble->x + dt*bubble->vx,
			   bubble->y + dt*bubble->vy );
      increase_sprite_clock( bubble->sprite, dt );
      /* check if bubble has reached its target cell */
      if ( bubble->y <= cell_y( bubble->cell )) {
	stick_bubble( board, bubble, bubble->cell);
	set_bubble_state( bubble, STUCK, TOP_LAYER, 0 );
	board->nb_rising_bubbles--;
	 
	/* if all bubbles have been received then unlock board */
	if ( board->nb_rising_bubbles == 0 ) {
	  board->state =
	    ( board->canon_empty )? WAITING_FOR_CANON : READY_TO_FIRE;
	  transition_allowed = 0;
	}
      }
      break;
      
    case DEAD:
      if ( bubble->clock <= 200 ) {
	if ( bubble->clock >= 0 ) 
	  increase_sprite_clock( bubble->sprite, dt );
	active = 1;
	bubble->clock += dt;
      }
      break;
      
    case RUNAWAY:
      /* destroy bubble */
      set_remove( board->bubbles, bubble );
      delete_bubble( bubble );
      
    default:
      break;
    }
  }
  return active;
}  

void place_received_bubbles( Board board ) {
  Bubble bubble;
  int color, target_cell;
  double x, y;
  Vector placed = board->placed_balls;
   
  board->nb_rising_bubbles = 0;
  if (board->input->size == 0)
     return;
  vector_increase_maxsize(placed, board->input->size);
  vector_empty(placed);
	
  while ( board->input->size > 0 && placed->size < 5) {
    color = vector_pop(board->input);
    /* find a random target cell */
    target_cell = find_random_empty_cell(board->array,board->ruleset->malus_top);
    if ( target_cell != OUT_OF_BOUNDS ) {
      board->state = RECEIVING_BUBBLES;
      /* create rising bubble */
      bubble = new_board_bubble( board, color);
      set_bubble_state( bubble, RISING, TOP_LAYER, 0);
      cell_center( board->array, target_cell, &x, &y);
      set_bubble_position( bubble, x, BOARD_HEIGHT - 0.5);
      bubble->vx = 0;
      bubble->vy = -RISING_SPEED;
      bubble->cell = target_cell;
      board->nb_rising_bubbles++;
      /* lock cell to avoid 2 bubbles having the same target */
      board->array->cell[target_cell] = bubble;
      vector_push(placed, target_cell);
    }
  }
  /* unlock cells */
  while ( placed->size > 0 ) {
    target_cell = vector_pop(placed);
    if ( target_cell != OUT_OF_BOUNDS ) {
      board->array->cell[target_cell] = EMPTY_CELL;
    }
  }
  recompute_malus_indicator( board );
}

void get_bubble( Board board, int color ) {
  vector_push( board->input, color );
}

int give_bubble(Board board) {
  if ( board->output->size > 0 )
    return vector_pop(board->output);
  return -1;
}

void recompute_malus_indicator( Board board ) {
  Bubble bubble;
  int i;
   
  return; /* disabled for now since there is no proper graphic for malus balls */
  while (board->malus_indicators10->size > board->input->size / 10) {
     bubble = board->malus_indicators10->element[board->malus_indicators10->size-1];
     set_remove_at( board->malus_indicators10,   board->malus_indicators10->size-1); 
     remove_sprite_from_pool( board->sprite_pool, bubble->sprite );
     delete_bubble( bubble );
  }
  while (board->malus_indicators10->size < board->input->size / 10) {
     bubble = new_bubble( 1, NEW_X, NEW_Y, TOP_LAYER);
     set_bubble_state( bubble, NEW, TOP_LAYER, 0);
     add_sprite_to_pool( board->sprite_pool, bubble->sprite );
     set_add( board->malus_indicators10, bubble );
  }
   
  while (board->malus_indicators->size > board->input->size % 10) {
     bubble = board->malus_indicators->element[board->malus_indicators->size-1];
     set_remove_at( board->malus_indicators,   board->malus_indicators->size-1);
     remove_sprite_from_pool( board->sprite_pool, bubble->sprite );
     delete_bubble( bubble );
  }
  while (board->malus_indicators->size < board->input->size % 10) {
     bubble = new_bubble( 2, NEW_X, NEW_Y, TOP_LAYER);
     set_bubble_state( bubble, NEW, TOP_LAYER, 0);
     add_sprite_to_pool( board->sprite_pool, bubble->sprite );
     set_add( board->malus_indicators, bubble);
  }
  for (i=0; i<board->malus_indicators10->size; i++) {
     double x,y;
     bubble=board->malus_indicators10->element[i];
     cell_center( board->array, cellCL(board->array, -1, i+1), &x, &y);
     set_bubble_position( bubble, x, y);
  }
  for (i=0; i<board->malus_indicators->size; i++) {
     double x,y;
     bubble=board->malus_indicators->element[i];
     cell_center( board->array, cellCL(board->array, -1, i+board->malus_indicators10->size), &x, &y);
     set_bubble_position( bubble, x, y);
  }
   
}

void drop_bubbles(Board board) {
   int i, cell;
   float violence; /* big or small explosion ? */
   float direction; /* direction of each ball */
   Bubble bubble;
   count_floating_bubbles( board->array, board->floating_cells );
   if (board->floating_cells->size) {
      /* Choose the violence of the explosion based on its size */
      violence = board->floating_cells->size*20;
      if (violence > 100) violence=100;
      
      for ( i = 0; i < board->floating_cells->size; i++ ) {
	 cell = board->floating_cells->element[i];
	 bubble = board->array->cell[cell];
	 set_bubble_state( bubble, FALLING, TOP_LAYER, 0);
	 /* Which direction this ball goes to */
	 direction = ((float)rand())/((float)RAND_MAX+1.0) * -3.14159;
	 bubble->vx = cos(direction)*violence*EXPLODING_SPEED;
	 bubble->vy = sin(direction)*violence*EXPLODING_SPEED;
	 board->array->cell[cell] = EMPTY_CELL;
      }
   }
   board->empty = cell_array_is_empty( board->array );
}

static void lower_board(Board board) {
  int i, color;
  Bubble bubble;

  cell_array_lower( board->array );
  for ( i = 0; i < board->bubbles->size; i++ ) {
    bubble = board->bubbles->element[i];
    if ( bubble->state == STUCK ) 
      set_bubble_position( bubble, bubble->x, bubble->y+ROW_HEIGHT );
    /* update cell record, avoiding overflow */
    if (bubble->cell + COLS < NB_CELLS) {
       bubble->cell += COLS;
    } else {
       set_remove( board->bubbles, bubble );
       delete_bubble(bubble);
    }
  }
  if ( ! board->array->moving_ceiling ) { /* add a new row of bubbles */
    board->array->cell[0] = EMPTY_CELL;
    for ( i = row_start( board->array, 0); i < COLS; i++ ) {
      color = new_bubble_color( board );
      bubble = new_board_bubble( board, color);
      stick_bubble( board, bubble, i );
    }
  }
  else /* lower alert light */
    set_sprite_position( board->alert, scale_x( ALERT_X, scale ),
			 scale_y( ALERT_Y+board->array->first_row*ROW_HEIGHT,
				  scale ));
}

static void kill_board( Board board ) {
  int i, clock;
  Bubble bubble;
  /* kill bubbles */
  for( i = 0; i < board->bubbles->size; i++ ) {
    bubble = board->bubbles->element[i];
    if (( bubble->state != RUNAWAY )&&( bubble->state != STUCK )) {
      if (( bubble->state == EXPLODING )||( bubble->state == FALLING ))
	kill_bubble( board, bubble );
      else
	set_bubble_state( bubble, DEAD, BOTTOM_LAYER, 0 );
    }
  }
  clock = 0;
  for ( i = NB_CELLS-1; i >= 0; i-- )
    if ( board->array->cell[i] != EMPTY_CELL ) {
      bubble = board->array->cell[i];
      set_bubble_state( bubble, DEAD, BOTTOM_LAYER, clock );
      clock -= PROPAGATION_TIME;
    }
}

void canon_move( Board board, int dt ) {
  int frame, x, y;
  board->canon_virtual_angle += board->canon_direction*dt*CANON_ROTATING_SPEED*board->canon_speed;
  board->canon_virtual_angle = clip( board->canon_virtual_angle,
				     -CANON_ANGLE_MAX, CANON_ANGLE_MAX );
  board->canon_angle = (int) floor( board->canon_virtual_angle + 0.5 );
  frame = (board->canon_angle+CANON_ANGLE_MAX)*canon_animation->size/NB_ANGLES;
  /* compute canon center coordinates */
  x = scale_x( CANON_X, scale );
  y = scale_y( CANON_Y, scale );
  set_sprite_frame( board->canon_sprite, frame);
  set_sprite_position( board->canon_sprite, x, y);
}

static void animate_alert( Board board ) {
  int blinking;
  /* blink alert when launch count is almost equal to period */
  if ( board->launch_count >= board->period - 1 ) {
    blinking = ( board->launch_count >= board->period )?
      FAST_BLINKING : SLOW_BLINKING;
    if (( board->alert_on )&&(( board->clock/blinking ) % 2 ))
      switch_alert_off(board);
    if (( ! board->alert_on )&&(( board->clock/blinking ) % 2 == 0 ))
      switch_alert_on(board);
  }
}

void animate_board( Board board, int dt ) {
  board->clock += dt;
  board->was_lowered = 0;

  canon_move( board, dt );

  switch( board->state ) {

  case WAITING_FOR_CANON:
  case READY_TO_FIRE:
    if ( board_overflow(board) ) {
      kill_board(board);
      board->state = LOST;
      return;
    }
    /* lower all bubbles if enough were launched */
    if ( board->launch_count > board->period ) {
      lower_board(board);
      board->launch_count = 0;
      switch_alert_off(board);
      board->was_lowered = 1;
    }
    if ( board_overflow(board) ) {
      kill_board(board);
      board->state = LOST;
      return;
    }
    
  case REBOUND:
  case RECEIVING_BUBBLES:
    animate_alert(board);
    animate_bubbles( board, dt );
    break;

  case WON:
  case LOST:
    if ( ! animate_bubbles( board, dt ) )
      board->state = FROZEN;
    break;

  case FROZEN:
  default:
    /* do nothing */
    break;
  }
  board->launch_requested = 0;
}

void canon_rotate_left( Board board ) {
  if (board->canon_direction == -1)
    board->canon_speed += MIN( (5-board->canon_speed)/10, 0.2 );
  else
    board->canon_speed=1;
  board->canon_direction = -1;
}

void canon_rotate_right( Board board ) {
  if (board->canon_direction == 1)
    board->canon_speed += MIN( (5-board->canon_speed)/10, 0.2 );
  else
    board->canon_speed=1;
  board->canon_direction = 1;
}

void canon_stop( Board board ) {
  board->canon_direction = 0;
  board->canon_speed = 1;
}

void canon_fire(Board board) {
  board->launch_requested = 1;
  canon_stop(board);
}

SpritePool get_board_sprite_pool( Board board ) {
  return board->sprite_pool;
}

enum BoardState get_board_state( Board board ) {
  return board->state;
}

int board_empty( Board board ) {
  return board->empty;
}

int get_canon_angle( Board board ) {
  return board->canon_angle;
}

int board_was_lowered( Board board ) {
  return board->was_lowered;
}

CellArray get_board_array( Board board ) {
  return board->array;
}

int get_last_fire_delay( Board board ) {
  return board->last_fire_delay;
}

void get_board_info( Board board, 
		     double **vx,
		     double **vy,
		     int *color,
		     int *next_color,
		     int *launch_count,
		     int *period ) {
  *vx = board->vx;
  *vy = board->vy;
  *color = board->color;
  *next_color = board->next_color;
  *launch_count = board->launch_count;
  *period = board->period;
}

