/* XBubble - board.c Copyright (C) 2002 Ivan Djelic Copyright (C) 2003 Martin Quinson 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 #include /* memset */ #include #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_cell); 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; /* 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-- ) if ( board->array->cell[i] != EMPTY_CELL ) { bubble = board->array->cell[i]; bubble->vx = (rnd(100)-50)*EXPLODING_SPEED/10; bubble->vy = -rnd(100)*EXPLODING_SPEED/100; set_bubble_state( bubble, EXPLODING, TOP_LAYER, clock ); clock -= PROPAGATION_TIME; } 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_cell) { double x, y; cell_center( board->array, target_cell, &x, &y ); set_bubble_state( bubble, STUCK, BOTTOM_LAYER, 0); set_bubble_position( bubble, x, y); bubble->cell = target_cell; board->array->cell[target_cell] = 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 ); } 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 = 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 ) { 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); tmp->vx = EXPLODING_SPEED; // tmp->vx = (rnd(100)-50)*EXPLODING_SPEED/10; tmp->vy = EXPLODING_SPEED; // FIXME also explode those balls, not only the unhooked ones 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); 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) ) { 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; imalus_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; imalus_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; bubble->vy = sin(direction)*violence; bubble->vx *= EXPLODING_SPEED; bubble->vy *= EXPLODING_SPEED; board->array->cell[cell] = EMPTY_CELL; } } board->empty = cell_array_is_empty( board->array ); } 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 )); } 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; Frame cframe; 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 */ cframe = (Frame) canon_animation->element[frame]; 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); } 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.1 ); 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.1 ); 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; board->canon_speed = 1; } 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; }