/*
  XBubble - game.c
 
  Copyright (C) 2002  Ivan Djelic <ivan@savannah.gnu.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 <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <setjmp.h>

#include <X11/Xlib.h>
#include <X11/keysym.h>

#include "setting.h"
#include "gettext.h"

#include "utils.h"
#include "sprite.h"
#include "rgba.h"
#include "board.h"
#include "opponent.h"
#include "controls.h"
#include "dialog.h"
#include "timer.h"
#include "game.h"
#include "cell.h"

extern Display *display;
extern Window root;
extern Window win;
extern int screen;
extern int depth;

extern GC dialog_gc;
extern GC dialog_text_gc;
extern XFontStruct *dialog_font;
extern XFontStruct *menu_font;
extern Pixmap win_bg;
extern Pixmap cup_on;
extern Pixmap cup_off;
extern Pixmap cup_on_mask;
extern Pixmap cup_off_mask;
extern Pixmap board_decoration;
extern Pixmap board_decoration_mask;

extern int titlefont_color[3];
extern int menufont_color[3];

extern int scale;
extern unsigned long frame_duration;

enum GameState {
  PLAYING,
  PAUSED,
  OVER,
  FINISHED,
  STOPPED
};

enum GameComputerState {
  IDLE,
  THINKING,
  AIMING
}; 

struct _Game {
  enum GameMode mode;
  enum GameState state;
  enum GameResult result;
  enum GameComputerState computer_state[2];
  int player_left[2];
  int player_right[2];
  int player_fire[2];
  int escape_pressed;
  int pause_pressed;
  int key_pressed;
  int multi_player;
  int nb_boards;
  int handicap[2];
  int board_best_eval[2];
  int board_best_angle[2];
  int round;
  int need_restart_thinking;
  int computer_thinking_time[2];
  int board_x[2];
  int board_y[2];
  int *score;
  unsigned int width;
  unsigned int height;
  double canon_angle[2];
  Board board[2];
  Opponent opponent[2];
  GC board_gc;
  Pixmap board_pixmap[2];
  Pixmap board_bg[2];
  Window board_window[2];
  Window msg_box[2];
  Window cup_box[2];
  Window tags[2];
  RuleSet_t ruleset[2];
};

Game current_game = NULL;
sigjmp_buf restart_thinking;

const char *player_name[][2] = {
  { PLAYER1_TAG, "" },
  { PLAYER1_TAG, PLAYER2_TAG },
  { PLAYER1_TAG, COMPUTER_TAG },
  { DEMO_TAG, DEMO_TAG }};

/* When the timer is running, sleep up to ms time, or until a key gets pressed.
 * To be called when the timer is running.
 * Returns: whether it was interrupted. */
static int interupted_sleep(long ms,Game game) {
  long i;

  block_timer();
  if (game->state != FINISHED && game->state != STOPPED) 
     fail("interupted_sleep works only when the game is finished or stopped, it's %d\n",game->state);
  game->key_pressed = 0;
  for ( i = ms/50; i > 0; i-- ) {
     if (game->key_pressed) {
	unblock_timer();
	return 1;
     }
     unblock_timer();
     timer_sleep(50);
     block_timer();
  }
  unblock_timer();
  return 0;
}

static void display_cups( Game game, int n, int on ) {
  
  Pixmap back;
  int i, x, y, width, height, pos = 0;  
  GC gc;
  XGCValues gcv;
  unsigned long gcm;
  gcm = GCFunction | GCGraphicsExposures;
  gcv.graphics_exposures = False;
  gcv.function = GXcopy;
  gc = XCreateGC( display, root, gcm, &gcv);
  width = 2*scale;
  height = 4*board_spacing(scale)/3;
  
  x = game->board_x[n];
  y = game->board_y[n] - 5*board_spacing(scale)/3;
  back = XCreatePixmap( display, root, width, height, depth );
  XSetTSOrigin( display, dialog_gc, -x, -y );
  XFillRectangle( display, back, dialog_gc, 0, 0, width, height );
  XSetClipMask( display, gc, cup_off_mask );
  for ( i = 0; i < game->score[n]-1; i++ ) {
    XSetClipOrigin( display, gc, pos, 0 );
    XCopyArea( display, cup_off, back, gc, 0, 0, scale, height, pos, 0 );
    pos += scale;
  }
  if ( game->score[n] > 0 ) {
    XSetClipMask( display, gc, (( on )? cup_on_mask : cup_off_mask ));
    XSetClipOrigin( display, gc, pos, 0 );
    XCopyArea( display, (( on )? cup_on : cup_off ), back, gc, 0, 0,
	       scale, height, pos, 0 );
  }
  XSetWindowBackgroundPixmap( display, game->cup_box[n], back );
  XFreePixmap( display, back );
  XFreeGC( display, gc );
}

Game new_game( enum GameMode mode,
	       RuleSet_t *ruleset,
	       int *colors,
	       int round,
	       int *score,
	       enum Level level ) {

  int i, gw, gh, bw, bh, bs;
  unsigned long gcm;
  XGCValues gcv;
  GC gc1;
  GC gc2;
  Game game = (Game) xmalloc( sizeof( struct _Game ));
  game->state = PLAYING;
  game->mode = mode;
  game->score = score;
  game->round = round;
  for ( i = 0; i < 2; i++ ) {
    game->player_left[i] = 0;
    game->player_right[i] = 0;
    game->player_fire[i] = 0;    
    game->computer_state[i] = IDLE;
    game->computer_thinking_time[i] = COMPUTER_DELAY;
    game->canon_angle[i] = 0.0;
  }
  game->need_restart_thinking = 0;
  game->multi_player = ( mode != SINGLE_PLAYER );

  game->nb_boards = ( game->multi_player ? 2 : 1 );
  /* create board(s) */
  for ( i = 0; i < game->nb_boards; i++ ) {

    game->board[i] = new_board( PERIOD, 
				&(ruleset[i]),
				( ! game->multi_player ),
				colors );

    game->opponent[i] = new_opponent( game->board[i], level );
  }
  gw = game_win_width(scale);
  gh = game_win_height(scale);
  bw = board_win_width(scale);
  bh = board_win_height(scale);
  bs = board_spacing(scale);

  /* one board in single player mode */
  if ( ! game->multi_player ) {
    game->board_x[0] = gw/2 - bw/2;
    game->board_y[0] = gh/2 - bh/2 + bs/2;
  }
  else { /* two boards in multi-player mode */
    game->board_x[1] = gw/2 - bw - bs/2;
    game->board_y[1] = gh/2 - bh/2 + bs/2;
    game->board_x[0] = gw/2 + bs/2;
    game->board_y[0] = game->board_y[1];
  }
  gcm = GCFunction | GCGraphicsExposures;
  gcv.graphics_exposures = False;
  gcv.function = GXcopy;
  game->board_gc = XCreateGC( display, root, gcm, &gcv);
  gc1 = XCreateGC( display, root, gcm, &gcv);
  gc2 = XCreateGC( display, root, gcm, &gcv);
  /* create windows */
  for ( i = 0; i < game->nb_boards; i++ ) {
    if ( game->mode == DEMO )
      game->score[i] = 0;
    game->cup_box[i] = XCreateSimpleWindow( display, 
					    win,
					    game->board_x[i],
					    game->board_y[i] - 5*bs/3, 
					    2*scale,
					    4*bs/3,
					    0, 0, 0 );
    display_cups( game, i, False );
    
    game->tags[i] = create_dialog( game->board_x[i] + bw/2,
				   game->board_y[i] - bs,
				   gettext(player_name[mode][i]),
				   menu_font,
				   get_pixel( menufont_color[0], menufont_color[1], 
				    menufont_color[2] ), 0, 1.0 );

    game->board_window[i] = XCreateSimpleWindow( display, 
						 win,
						 game->board_x[i],
						 game->board_y[i],
						 bw, bh, 0, 0, 0 );

    game->board_pixmap[i] = XCreatePixmap( display, root, bw, bh, depth );

    /* make board background (position dependant) */
    game->board_bg[i] = XCreatePixmap( display, root, bw, bh, depth );
    /* first draw window background */
    XSetFillStyle( display, gc1, FillTiled );
    XSetTile( display, gc1, win_bg );
    XSetTSOrigin( display, gc1, -game->board_x[i], -game->board_y[i] );
    XFillRectangle( display, game->board_bg[i], gc1, 0, 0, bw, bh );
    /* then draw decoration */
    XSetClipMask( display, gc2, board_decoration_mask );
    XSetClipOrigin( display, gc2, 0, 0 );
    XCopyArea( display, board_decoration, game->board_bg[i], gc2,
	       0, 0, bw, bh, 0, 0 );

    draw_sprite_pool( get_board_sprite_pool(game->board[i]), 
		      game->board_pixmap[i],
		      game->board_bg[i], bw, bh );

    XSelectInput( display, game->board_window[i], ExposureMask );
    XSetWindowBackgroundPixmap( display, game->board_window[i], None );
    XMapWindow( display, game->board_window[i] );
    XMapWindow( display, game->cup_box[i] );
  }
  XFreeGC( display, gc1);
  XFreeGC( display, gc2);
  return game;
}

void delete_game( Game game, int no_clean ) {
  int i;
  if ( no_clean )
    /* avoid game window flickering when board windows are destroyed */
    XSetWindowBackgroundPixmap( display, win, None );
  XFreeGC( display, game->board_gc );
  for ( i = 0; i < game->nb_boards; i++ ) {
    delete_opponent( game->opponent[i] );
    delete_board( game->board[i] );
    XFreePixmap( display, game->board_pixmap[i] );
    XFreePixmap( display, game->board_bg[i] );   
    XDestroyWindow( display, game->board_window[i] );
    XDestroyWindow( display, game->cup_box[i] );
    XDestroyWindow( display, game->tags[i] );
  }
  free(game);
  if ( no_clean )
    XSetWindowBackgroundPixmap( display, win, win_bg );
}

static void redraw_game_window( Game game ) {
  int n;
  GC gc;
  SpritePool sp;
  for ( n = 0; n < game->nb_boards; n++ ) {
    sp = get_board_sprite_pool( game->board[n] );
    gc = get_sprite_pool_redraw_gc(sp);
    XCopyArea( display, game->board_pixmap[n], game->board_window[n], gc,
	       0,
	       0,
	       board_win_width(scale),
	       board_win_height(scale),
	       0,
	       0 );
  }
}

static void process_x_events( Game game ) {
  XEvent event;
  Window window;
  KeySym keysym;
  int i;

  game->pause_pressed = 0;
  game->escape_pressed = 0;
  if (game->state != FINISHED && game->state != STOPPED) /* keypress skips the animations */
     game->key_pressed = 0;

  while ( XPending(display) ) {
    XNextEvent( display, &event);
    switch ( event.type ) {

    case Expose:
      window = event.xexpose.window;
      /* redraw board windows only, game window has a background pixmap */
      for ( i = 0; i < game->nb_boards; i++ )
	if ( window == game->board_window[i] ) 
	  XCopyArea( display,
		     game->board_pixmap[i],
		     game->board_window[i], 
		     game->board_gc,
		     event.xexpose.x,
		     event.xexpose.y, 
		     event.xexpose.width,
		     event.xexpose.height, 
		     event.xexpose.x,
		     event.xexpose.y );
      break;
      
    case KeyRelease:
      keysym = XLookupKeysym( &event.xkey, 0);
      if ( PLAYER1_LEFT(keysym) )
	game->player_left[0] = 0;
      if ( PLAYER1_RIGHT(keysym) )
	game->player_right[0] = 0;
      if ( PLAYER1_FIRE(keysym) )
	game->player_fire[0] = 0;
      if ( PLAYER2_LEFT(keysym) )
	game->player_left[1] = 0;
      if ( PLAYER2_RIGHT(keysym) )
	game->player_right[1] = 0;
      if ( PLAYER2_FIRE(keysym) )
	game->player_fire[1] = 0;
      break;
      
    case KeyPress:
      keysym = XLookupKeysym( &event.xkey, 0);
      game->key_pressed = 1;
      if ( PLAYER1_LEFT(keysym) )
	game->player_left[0] = 1;
      if ( PLAYER1_RIGHT(keysym) )
	game->player_right[0] = 1;
      if ( PLAYER1_FIRE(keysym) )
	game->player_fire[0] = 1;
      if ( PLAYER2_LEFT(keysym) )
	game->player_left[1] = 1;
      if ( PLAYER2_RIGHT(keysym) )
	game->player_right[1] = 1;
      if ( PLAYER2_FIRE(keysym) )
	game->player_fire[1] = 1;
      /* escape & pause keys */
      if ( keysym == XK_Escape )
	game->escape_pressed = 1;
      if ( keysym == XK_q )
	game->escape_pressed = 1;
      if ( keysym == XK_p )
	game->pause_pressed = 1;
      break;
      
    default:
      break;
    }
  }
}

static void animate_game( Game game, int dt ) {
  int i, color, lost=0;
  for ( i = 0; i < game->nb_boards; i++ ) { 
    animate_board( game->board[i], dt );
    lost = lost || get_board_state( game->board[i] ) == LOST;
    redraw_sprite_pool( get_board_sprite_pool(game->board[i]), 
			game->board_pixmap[i], game->board_bg[i], 
			board_was_lowered( game->board[i] ) /* redraw everything if the board was lowered */ );
  }
  if (( game->multi_player )&&( game->state != OVER )&&( !lost )) {
    int given = 0;
    /* exchange bubbles if nobody is dead */
    while ( (color = give_bubble(game->board[0])) >= 0 ) {
      given = 1;
      color = rnd( NB_COLORS );
      get_bubble( game->board[1], color);
    }
    if (given) {
      recompute_malus_indicator( game->board[1] );
      given = 0;
    }
     
    while ( (color = give_bubble(game->board[1])) >= 0 ) {
      given = 1;
      color = rnd( NB_COLORS );
      get_bubble( game->board[0], color);
    }
    if (given) {
      recompute_malus_indicator( game->board[0] );
    }
  }
}

static void player_move( Game game, int n ) {
  if ( game->player_left[n] )  {
    canon_rotate_left( game->board[n] );
  } else if ( game->player_right[n] )  {
    canon_rotate_right( game->board[n] );
  } else if ( game->player_fire[n] )  {
    canon_fire(game->board[n]);
  } else {
    canon_stop(game->board[n]);
  }
}

static void computer_move( Game game, int n ) {
  int shift;
  /* if board not stable then restart from beginning */
  if (( get_board_state( game->board[n] ) != READY_TO_FIRE )||
      ( board_was_lowered( game->board[n] ))) {
    if ( game->computer_state[n] == THINKING )
      game->need_restart_thinking = 1;
    game->computer_state[n] = IDLE;
  }
  game->computer_thinking_time[n] -= frame_duration/1000;

  /* think only when board is stable */
  if ( get_board_state( game->board[n] ) == READY_TO_FIRE ) {
    switch ( game->computer_state[n] ) {

    case IDLE:
      game->computer_state[n] = THINKING;
      break;

    case THINKING:
      /* do nothing, computer_think() will switch to AIMING state */
      break;
      
    case AIMING:
      /* if canon is correctly aimed and thinking time is elapsed then fire */
      if (( get_canon_angle( game->board[n] ) == game->board_best_angle[n] )&&
	  ( game->computer_thinking_time[n] <= 0 )&&
	  /* if we are losing then we just wait */
	  ( game->board_best_eval[n] > 4 )) {
	canon_fire( game->board[n] );
	if ( game->mode == PLAYER_VS_COMPUTER )
	  /* roughly adjust our pace to our opponent's */
	  game->computer_thinking_time[n] = 
	    get_last_fire_delay(game->board[0]) + 500; /* 500 = canon load */
	else
	  game->computer_thinking_time[n] = COMPUTER_DELAY;
      }
      /* else adjust canon angle */
      shift = game->board_best_angle[n] - get_canon_angle(game->board[n]);
      if ( shift != 0 ) {
	if ( shift > 0 )
	  canon_rotate_right( game->board[n] );
	else
	  canon_rotate_left( game->board[n] );
	/* if necessary, fine tune canon rotation ( yes that's cheating ) */
	if ( abs(shift) < ( frame_duration/1000.0 )*CANON_ROTATING_SPEED ) {
	  canon_move( game->board[n], 
			    (int) floor( abs(shift)/CANON_ROTATING_SPEED ));
	  canon_stop( game->board[n] );
	}
      } else  {
	canon_stop( game->board[n] );
      }
    }
  }
}

static void frame_update() {
  int i;
  int lost1;
  int lost2;
  Game game = current_game;
  if ( game == NULL )
    return;

  process_x_events(game);
  switch ( game->state ) {
    
  case PLAYING:
    redraw_game_window(game);

    /* pause */
    if ( game->pause_pressed ) {
      game->state = PAUSED;
      for ( i = 0; i < game->nb_boards; i++ )
	game->msg_box[i] =
	  create_dialog( game->board_x[i] + board_win_width(scale)/2,
			 game->board_y[i] + board_win_height(scale)/2,
			 _("Pause"),
			 dialog_font, 
			 get_pixel( menufont_color[0], menufont_color[1], 
				    menufont_color[2] ), 2, 1.5 );
      break;
    }
    /* abort with ESC key (or any key in demo mode) */
    if (( game->escape_pressed )||
	(( game->mode == DEMO )&&( game->key_pressed ))) {
      game->state = STOPPED;
      game->result = ABORTED;
      break;
    }

    switch ( game->mode ) {
    case TWO_PLAYERS:
      player_move( game, 1 );
    case SINGLE_PLAYER:
      player_move( game, 0 );
      break;
    case PLAYER_VS_COMPUTER: 
      player_move( game, 0 );
      computer_move( game, 1 );
      break;
    case DEMO:
      computer_move( game, 0 );
      computer_move( game, 1 );
      break;
    default:
      break;
    }
    animate_game( game, frame_duration/1000 );

    /* check if game is not over */
    if ( game->multi_player ) {
      lost1 = ( get_board_state( game->board[0] ) == LOST );
      lost2 = ( get_board_state( game->board[1] ) == LOST );
      if ( lost1 || lost2 ) {
	canon_stop( game->board[0] );
	canon_stop( game->board[1] );
	game->state = OVER;
	game->result = DRAW;
	if ( ! lost1 ) {
	  game->result = PLAYER1_WON;
	  explode_board( game->board[0] );
	}
	if ( ! lost2 ) {
	  game->result = PLAYER1_LOST;
	  explode_board( game->board[1] );
	} 
	/* stop computer thinking */
	game->need_restart_thinking = 1;
	game->computer_state[0] = IDLE;
	game->computer_state[1] = IDLE;
      }
    }
    else { /* single player mode */
      if ( board_empty( game->board[0] )) {
	canon_stop( game->board[0] );
	game->state = OVER;
	game->result = PLAYER1_WON;
	freeze_board( game->board[0] );
	break;
      }
      if ( get_board_state( game->board[0] ) == LOST ) {
	game->state = OVER;
	game->result = PLAYER1_LOST;
      }
    }
    break;
    
  case PAUSED:
    if ( game->pause_pressed ) {
      game->state = PLAYING;
      for ( i = 0; i < game->nb_boards; i++ )
	XDestroyWindow( display, game->msg_box[i] );
    }
    break;

  case OVER:
    redraw_game_window(game);
    /* wait for end of board animations */
    if (( get_board_state( game->board[0] ) == FROZEN )&&
	(( ! game->multi_player )||
	 ( get_board_state( game->board[1] ) == FROZEN )))
      game->state = FINISHED;
    else
      animate_game( game, frame_duration/1000 );
    break;

  default:
    break;
  }
  /* wait for X server */
  XSync( display , False );
  
  /* to restart computer thinking we do a non-local exit */
  if ( game->need_restart_thinking ) {
    game->need_restart_thinking = 0;
    siglongjmp( restart_thinking, 1 );
  }
}

static void show_board_msg( Game game, int i, char *msg ) {
  game->msg_box[i] =
    create_dialog( game->board_x[i] + board_win_width(scale)/2,
		   game->board_y[i] + board_win_height(scale)/2,
		   msg,
		   dialog_font, 
		   get_pixel( menufont_color[0], menufont_color[1], 
				    menufont_color[2] ), 2, 1.5 );
}

static void hide_game_msg( Game game ) {
  int i;
  for ( i = 0; i < game->nb_boards; i++ )
    XDestroyWindow( display, game->msg_box[i] );
}

static void show_start_boxes( Game game ) {
  static char msg[40];

  switch ( game->mode ) {
  case SINGLE_PLAYER:
    sprintf( msg, _("Stage %d"), game->round );
    show_board_msg( game, 0, msg );
    break;
  case TWO_PLAYERS:
  case PLAYER_VS_COMPUTER:
    sprintf( msg, _("Round %d"), game->round );
    show_board_msg( game, 0, msg );
    show_board_msg( game, 1, msg );
    break;
  case DEMO:
    break;
  }
}

static void show_game_result( Game game ) {
  static char msg[40];

  switch ( game->result ) {
  case PLAYER1_WON:
    if ( game->mode == SINGLE_PLAYER ) {
      sprintf( msg, _("Stage %d cleared !"), game->round );
      show_board_msg( game, 0, msg );
    } else
      show_board_msg( game, 0, _("Win") );
    if ( game->multi_player ) {
      show_board_msg( game, 1, _("Lose") );
      game->score[0]++;
    }
    break;
  case PLAYER1_LOST:
    show_board_msg( game, 0, _("Lose") );
    if ( game->multi_player ) {
      show_board_msg( game, 1, _("Win") );
      game->score[1]++;
    }
    break;
  case DRAW:
    show_board_msg( game, 0, _("Draw") );
    show_board_msg( game, 1, _("Draw") );
    break;
  case ABORTED:
    break;
  }
}
 
static void show_blinking_cups( Game game ) {
  int i,done=0;
  int on = 1;
  int winner = -1;
  if ( game->multi_player ) {
     if ( game->result == PLAYER1_WON )
       winner = 0;
     if ( game->result == PLAYER1_LOST )
       winner = 1;
  }

  /* display blinking cup for 3 seconds */
  for ( i = 0; i < 15 && !done; i++ ) {
    if (( winner >= 0 ) && ( game->mode != DEMO )) {

      block_timer();
       
      /* Skip the animation on key press */
      if (game->key_pressed) {
	 done = 1;
      }
	 
      display_cups( game, winner, on );
      XClearWindow( display, game->cup_box[winner] );
      unblock_timer();
      on = 1 - on;
    }
    done = done || interupted_sleep(200,game);
  }
  if (( winner >= 0 ) && ( game->mode != DEMO )) 
    display_cups( game, winner, 1 );
}

static void computer_think( Game game ) {
  int n;
  int state;
  int best_eval;
  int best_angle;
  for ( n = 0; n < game->nb_boards; n++ ) {
    block_timer();
    state = game->computer_state[n];
    unblock_timer();
    if ( state == THINKING ) {
      best_angle = find_best_angle( game->opponent[n], &best_eval );
      /* switch to AIMING state */
      block_timer();
      game->computer_state[n] = AIMING;
      game->board_best_angle[n] = best_angle;
      game->board_best_eval[n] = best_eval;
      unblock_timer();
    }
  }
}

enum GameResult play_game( Game game ) {
  int i, pressed;
  enum GameState state;
  current_game = game;
  game->state = STOPPED;
  start_timer( frame_duration, frame_update );
  /*-------------------- enter danger zone ----------------------*/

  /* From now on, we'll do libc/libX11 calls and non-local exits in 
     frame_update(), so we shouldn't do anything dangerous (i.e.
     non-reentrant) here until our timer is stopped.
  */
  if ( game->mode != DEMO ) {
    block_timer();           /* enter critical section */
    show_start_boxes(game);
    game->key_pressed = 0;
    unblock_timer();         /* exit critical section */

    interupted_sleep(1500,game);
     
    block_timer();
    hide_game_msg(game);
    unblock_timer();
  }
  /* start playing */
  block_timer();
  game->state = PLAYING;
  unblock_timer();

  /* we jump here when we need to restart the thinking process */
  sigsetjmp(restart_thinking, 1);
  
  /* loop until game is finished or aborted */
  do {
    /* use our spare time to think */
    computer_think(game);
    pause();
    /* get consistent game state */
    block_timer();
    state = game->state;
    unblock_timer();
  } while (( state != FINISHED )&&( state != STOPPED ));
  
  /* show results */
  if ( state == FINISHED ) {
    block_timer();
    show_game_result(game);
    unblock_timer();
    
    show_blinking_cups(game);
    
    block_timer();
    hide_game_msg(game);
    unblock_timer();
  }

  stop_timer();
  /*---------------------- exit danger zone ---------------------*/
  current_game = NULL;
  return game->result;
}

