/*
 *  Paraball - Enhanced Slime Volleyball port for Fenix / GP32
 *
 *  Current release       : Paraball v1.0
 *  Last stable release   : 2005-03-12
 *  Project web site			: http://ahuh.free.fr/paraball
 *
 *  No licence. Do whatever you want with this :)
 *
 *  by Ahuh (2005)
 *
 */
PROGRAM ParaBall;

CONST
// General constants
FRAME_RATE = 60;										// Frame rate in frames per second									
MAXIMUM_FRAMESKIP = 1;							// Maximum frameskip
SOUND_FLAG = 1;											// 0 = no sound ; 1 = sound
X_SIZE = 320;												// Number of pixels - screen width
Y_SIZE = 240;     									// Number of pixels - screen height
GROUND_SIZE = 40;										// Number of pixels between the top of the ground and the bottom of the screen
NET_SIZE_X = 6;											// Number of pixels - net width
NET_SIZE_Y = 30;										// Number of pixels - net height
PLAYER_SIZE = 18;										// Player height = PLAYER_SIZE ; Player width = PLAYER_SIZE * 2
BALL_SIZE = 5;											// Ball height = Ball width = BALL_SIZE * 2
GRAVITY = 0.050000000000000003;			// Gravity constant
BULLET_TIME_RECOVERY_TIME = 100;		// Minimum number of frames between a bullet-time activation / deactivation
BULLET_TIME_GAUGE_INIT = 1000.0;		// Initial charge of the bullet-time gauges
BULLET_TIME_GAUGE_INC = 0.075;			// Bullet-time gauge increment at each frame

// Control constants

// GP32 keys :
// _left => Stick to Left
// _right => Stick to Right
// _up => Stick to Up
// _down => Stick to Down
// _control => A Button
// _alt => B Button
// _enter => Start Button
// _space => Select Button
// _tab => R Button
// _backspace => L Button

START = _enter;										// Start key => start / exit in menu
PAUSE = _backspace;								// Pause key
FULL_SCREEN_KEY = _f11;						// Full-screen on / off (PC only)
PC_EXIT_KEY = _esc;								// Exit game key (PC)
GP32_EXIT_KEY_1 = _tab;						// Exit game key 1 (GP32) |
GP32_EXIT_KEY_2 = _backspace;			// Exit game key 2 (GP32) |=> both must be pressed

MENU_UP = _up;										// Up in menu
MENU_DOWN = _down; 								// Down in menu
MENU_LEFT = _left;								// Left in menu
MENU_RIGHT = _right;							// Right in menu

OPM_PLAYER_ONE_LEFT = _left;			// Left key for player 1 in one-player match
OPM_PLAYER_ONE_RIGHT = _right;		// Right key for player 1 in one-player match
OPM_PLAYER_ONE_JUMP_1 = _alt;			// Jump key for player 1 in one-player match
OPM_PLAYER_ONE_JUMP_2 = _control;	// Alternate jump key for player 1 in one-player match
OPM_PLAYER_ONE_BT = _space;				// Bullet-time key for player 1 in one-player match

TPM_PLAYER_ONE_LEFT = _left;			// Left key for player 1 in two-player match
TPM_PLAYER_ONE_RIGHT = _right;		// Right key for player 1 in two-player match
TPM_PLAYER_ONE_JUMP = _up;				// Jump key for player 1 in two-player match
TPM_PLAYER_ONE_BT = _enter;				// Bullet-time key for player 1 in two-player match

TPM_PLAYER_TWO_LEFT = _alt;				// Left key for player 2 in two-player match
TPM_PLAYER_TWO_RIGHT = _control;	// Right key for player 2 in two-player match
TPM_PLAYER_TWO_JUMP = _space;			// Jump key for player 2 in two-player match
TPM_PLAYER_TWO_BT = _tab;					// Bullet-time key for player 2 in two-player match

// Score constants
CLASSIC_MATCH_POINTS_TO_WIN = 10;		// Number of games required to win a set in a classic match
ADVANTAGE_MATCH_POINTS_TO_WIN = 6;	// Number of games required to win a set in a advantage match
REBOUND_MATCH_POINTS_TO_WIN = 3;		// Number of games required to win a set in a rebound match
REBOUNDS_TO_END_GAME = 3;						// Number of rebounds of the ball to the ground to win a game in a rebound match

// Calculation constants
JUMP_FACTOR = 80.0;							// Factor applied to gravity (impulse) when a player jumps
MOVE_FACTOR = 1.5;							// Factor applied the player horizontal movement
PLAYER_FALL_FACTOR = 4.0; 			// Factor applied to gravity (fall) when a player jumps
PLAYER_ROTATION_FACTOR = 20.5;	// Factor applied to rotation of the player when he wins
BALL_FALL_FACTOR = 2.0; 				// Factor applied to gravity when the ball falls
BALL_BOUNCE_FACTOR = 80.0;			// Factor applied to gravity when the ball hits a player
BULLET_TIME_FACTOR = 2;					// Factor of time dilation during bullet-time mode

GLOBAL
// Font global variables
int font_id;										// ID of the moving text font

// Sound global variables     
int sound_sample[7];						// ID for each sound effect

// Players global variables
float player_x[1];							// Horizontal position for each player
float player_y[1];   		 				// Vertical position for each player
float player_vx[1];							// Horizontal speed for each player
float player_vy[1]; 						// Vertical speed for each player
int player_jump[1]; 						// Jump state for each player (0 = no jump ; 1 = jump)
float bullet_gauge[1];					// Gauge of bullet-time for each player (0 = no bullet-time left)
int bullet_gauge_display[1];		// Value of bullet-time gauge for each player in percentage (between 0 and 100)
int bullet_time[1];       			// Flag of bullet-time for each player (0 = real-time mode ; 1 = bullet-time mode)
int bullet_time_key_pressed[1];	// For each player, prevent bullet-time button from switching to fast
int cpu_bullet_time_trigger;		// 1 = no activity ; 1 = CPU activates his bullet-time mode
int com_serve;									// Type of serve for the CPU (0 = CPU not serving now ; 1 to 4 = type of serve)
int player_rebound_score[1];		// For each player, number of rebounds of the ball scored by the player in the current game
int player_game_score[1];				// For each player, number of games won by the player in the current set
int player_set_score[1];				// For each player, number of sets won by the player in the current match

// Ball global variables
float ball_x; 						// Horizontal position of the ball
float ball_y; 						// Vertical position of the ball
float ball_vx; 						// Horizontal speed of the ball
float ball_vy; 						// Vertical speed of the ball

// Shadow effect global variables
float ball_x_shadow[20];				// The 20 last horizontal positions of ball for the shadow effect
float ball_y_shadow[20];        // The 20 last vertical positions of ball for the shadow effect
float player_one_x_shadow[20];  // The 20 last horizontal positions of player 1 for the shadow effect
float player_one_y_shadow[20];  // The 20 last horizontal positions of player 1 for the shadow effect
float player_two_x_shadow[20];  // The 20 last horizontal positions of player 2 for the shadow effect
float player_two_y_shadow[20];  // The 20 last horizontal positions of player 2 for the shadow effect

// Text global variables
int moving_text_id;				// ID of the text which move from the right of the screen to the left (-1 = no text now)
int pause_text_id; 				// ID of the "pause" text (-1 = no text now)

// Game global variables
int cpu_level;						// 1 = easy ; 2 = medium ; 3 = hard
int bullet_time_mode;			// 0 = bullet-time off ; 1 = bullet-time on
string type_player_two;		// Who controls player 2 : "cpu" ; "human"
int game_status;					// Status of the current game (0 = still playing ; 1 = player 1 won ; 2 = player 2 won)
int set_status;						// Status of the current set (0 = still playing ; 1 = player 1 won ; 2 = player 2 won ; 3 = no set)
int match_status;					// Status of the current match (0 = still playing ; 1 = player 1 won ; 2 = player 2 won ; 3 = no match)
int player_to_serve;			// 0 = player 1 serves ; 1 = player 2 serves
int init_player_to_serve;	// Indicates who serves at the beginning of the match
string type_of_match;			// "classic", "advantage" or "rebound"
int points_to_win;				// Number of games required to win a set in the current match
int sets_to_win;					// Number of sets required to win in the current match

// UI variables
int text_gauge_value[1];				// Bullet-time gauge value (between 0 and 100) for each player (in white)
int text_gauge_value_gray[1];		// Bullet-time gauge value (between 0 and 100) for each player (in gray)
int text_gauge_percent[1];			// % written after each bullet-time gauge value (in white)
int text_gauge_percent_gray[1]; // % written after each bullet-time gauge value (in gray)
int text_bullet_time[1];				// "Bullet-time !" written near each bullet-time gauge when bullet-time is activated
int modify_text_gauge[1];				// Variable used for each player to know when to change bullet-time gauge value
int activate_bt_text[1];				// Variable used for each player to know when to show / hide "Bullet-time !"

BEGIN
	// Graphic initialization
	full_screen = 0;												// No full-screen mode at the beginning
	SET_FPS(FRAME_RATE, MAXIMUM_FRAMESKIP);	// Framerate and maximum frameskip
	SET_MODE(X_SIZE, Y_SIZE, 8);						// 8bit graphics for GP32
	LOAD_FPG("paraball.fpg");								// Loading of the graphic library

  // Font initialization
  font_id = LOAD_FNT("1.fnt");
	
  // Sound initialization
  IF (SOUND_FLAG == 1)
  	// We load sound effects in memory
	  sound_sample[0] = LOAD_SONG("1.wav");
	  sound_sample[1] = LOAD_SONG("2.wav");
	  sound_sample[2] = LOAD_SONG("3.wav");
	  sound_sample[3] = LOAD_SONG("4.wav");
	  sound_sample[4] = LOAD_SONG("5.wav");
	  sound_sample[5] = LOAD_SONG("6.wav");
	  sound_sample[6] = LOAD_SONG("7.wav");
	  sound_sample[7] = LOAD_SONG("8.wav");
  END
  
  // Menu initialization
	type_of_match = "Classic";
	type_player_two = "CPU";
	cpu_level = 2;
	sets_to_win = 1;
	bullet_time_mode = 1;
  
	// Others initialization
	moving_text_id = -1;	// => no moving text at the beginning
	pause_text_id = -1;		// => no "pause" text at the beginning
	match_status = 3;			// => no match has already begun
 	
	LOOP		
		IF (match_status > 0)
			// There is no match currently, or the match is over : we show the menu
			
			// We create the menu process
			MainMenu();
		
			WHILE (GET_ID(TYPE MainMenu) > 0)
				// We wait until the menu process is over
			
				CLEAR_SCREEN();
				
				// We draw the "paraball" title in the menu screen
				PUT(0, 12, X_SIZE / 2, 45);
				
				FRAME;
				
				// We check the full-screen key
				InGameInterruption(0);
			END
			
			// We stop all the others processes and we delete all remaining texts
			LET_ME_ALONE();
			DELETE_TEXT(0);
		END
				
		// We start a new match if no match has already started
		IF (match_status > 0)
			MatchInit();
		END
	  
	  // We start a new set if no set has already started
	  IF (set_status > 0) 
	  	SetInit();
	  END
	  
	  // We start a new game
	  GameInit();
		
		// Creation of the processes  
		Ground(0, Y_SIZE - GROUND_SIZE, 0);
		Net(X_SIZE / 2 - NET_SIZE_X / 2, Y_SIZE - GROUND_SIZE - NET_SIZE_Y, 0);
		Ball(ball_x, ball_y, -20);
		IF (type_player_two == "Mensch")
			Player(0, "Mensch", player_x[0], player_y[0], -10, TPM_PLAYER_ONE_LEFT, TPM_PLAYER_ONE_RIGHT, TPM_PLAYER_ONE_JUMP, TPM_PLAYER_ONE_JUMP, TPM_PLAYER_ONE_BT);
		ELSE
			Player(0, "Mensch", player_x[0], player_y[0], -10, OPM_PLAYER_ONE_LEFT, OPM_PLAYER_ONE_RIGHT, OPM_PLAYER_ONE_JUMP_1, OPM_PLAYER_ONE_JUMP_2, OPM_PLAYER_ONE_BT);		
		END
		Player(1, type_player_two, player_x[1], player_y[1], -10, TPM_PLAYER_TWO_LEFT, TPM_PLAYER_TWO_RIGHT, TPM_PLAYER_TWO_JUMP, TPM_PLAYER_TWO_JUMP, TPM_PLAYER_TWO_BT);

		// *** DEBUG ***
	 	//WRITE_INT(0, 152, 230, 0, &fps);
	 	// *** DEBUG ***
    
    // We draw the gauges at the bottom of the screen
		IF (bullet_time_mode == 1)
			InitBulletTimeGauge();
		END
		
		// We draw the scores at the top of the screen
		ShowScore();

		// We write the name of the player who is serving
		IF (player_to_serve == 0)
			MovingText("1P am Start ...");
		ELSE
			IF (type_player_two == "Mensch")
				MovingText("2P am Start ...");
			ELSE
				MovingText("CPU am Start ...");
			END
		END
		
	  // We wait until the end of the game
		WHILE (game_status == 0)	
			CLEAR_SCREEN();	// We clear the screen at each frame to avoid garbage on the GP32 screen
      
      DrawBulletTimeShadow(); // We draw the shadows behind the players / the ball if bullet-time is activated
      
      // We redraw the gauges at each frame
      IF (bullet_time_mode == 1)
				RedrawBulletTimeGauge();
			END
      
			FRAME;
      
      // We check the pause key, the full-screen key and the exit keys
			InGameInterruption(1);
			WHILE(KEY(PAUSE))
				// We prevent the pause from switching to fast
				FRAME;
			END
			IF (match_status == 3)
				// Exit keys have been pressed : we leave the game
				BREAK;
			END
		END             
		
		IF (match_status <> 3)
			// End of game !
	    EndGame();
				
			IF (match_status == 0 AND set_status == 0)
				// ---------------------------
				// No set or match is finished
				// ---------------------------
				
				// We write the text which indicates who won
				IF (game_status == 1)
					MovingText("1P gewinnt 1 Pkt !");
				ELSEIF (game_status == 2)
					IF (type_player_two == "Mensch")
						MovingText("2P gewinnt 1 Pkt !");
					ELSE
						MovingText("CPU gewinnt 1 Pkt !");
					END
				END
			ELSE
				// ----------------------------------
				// A set or a match has just finished
				// ----------------------------------
				
				// We write the text which indicates who won
				IF (match_status == 1)
					MovingText("1P gewinnt das Spiel !");
				ELSEIF (match_status == 2)
					IF (type_player_two == "Mensch")
						MovingText("2P gewinnt das Spiel !");
					ELSE
						MovingText("CPU gewinnt das Spiel !");
					END
				ELSEIF (set_status == 1)
					MovingText("1P gewinnt !");
				ELSEIF (set_status == 2)
					IF (type_player_two == "Mensch")
						MovingText("2P gewinnt !");
					ELSE
						MovingText("CPU gewinnt !");
					END
				END
			END
			
			// While the text is moving from the right to the left : we wait
			WHILE (GET_ID(TYPE MovingText) > 0)
				CLEAR_SCREEN();
				FRAME;
				
				// We check the pause key, the full-screen key and the exit keys
				InGameInterruption(1);
				WHILE(KEY(PAUSE))
					// We prevent the pause from switching to fast
					FRAME;
				END
				IF (match_status == 3)
					// Exit keys have been pressed : we leave the game
					BREAK;
				END
			END
		END
    
		IF (match_status <> 3)
			// The match has not been cancelled
			IF (match_status > 0 OR set_status > 0)
				// While a player hasn't pressed start key : we wait
				LOOP
					// Loop on a moving text
					IF (GET_ID(TYPE MovingText) <= 0)
						MovingText("Drücke Start ...");
					END
				  
				  // Exit when start key pressed
					IF (KEY(START))
						BREAK;
					END
				
					CLEAR_SCREEN();
					FRAME;
					
					// We check the pause key, the full-screen key and the exit keys
					InGameInterruption(1);
					WHILE(KEY(PAUSE))
						// We prevent the pause from switching to fast
						FRAME;
					END
					IF (match_status == 3)
						// Exit keys have been pressed : we leave the game
						BREAK;
					END
				END
				
				WHILE (KEY(START))
					// Do nothing until user releases start key
					CLEAR_SCREEN();
					FRAME;
				END
			END
		END
		
		// The game is over
		
		// We stop all the others processes and we delete all remaining texts
		LET_ME_ALONE();
		DELETE_TEXT(0);
		
		// There is no moving text / pause text remaining
		moving_text_id = -1;
		pause_text_id = -1;
	END
END
/* --------------------------------------------------------------------------------------- */




/* ---------------------------------------------------------------------------------------
   |                           ****** PROCESSES ******                                   |
   --------------------------------------------------------------------------------------- */




/* ---------------------------------------------------------------------------------------
 *  PROCESS : MainMenu
 *
 *  Displays the main menu in the screen. The menu allows to select parameters for the
 *	following match. The player can also exit the game from here.
 *
 *  PARAMS:
 *      none
 *
 */
PROCESS MainMenu()

PRIVATE
int selected_items[5][1];	// Selected menu items
int selected_line[1];			// Selected menu line
int temp_text[14];				// ID of each text that needs to switch color
int row_index;						// Index to browse menu lines
int col_index;						// Index to browse menu items
int text_index;           // Index to browse text IDs
int arrow_offset;         // Y offset indicating where is the menu cursor
int key_up;               // Prevent rapid key switches

BEGIN
	// Screen fades out / in
	fade_off();
	fade_on();
	
	// Initialization
	key_up = 0;
  
  // We indicate, for each line of the menu, the number of items
	selected_line[1] = 4;
	selected_items[0][1] = 2;
	selected_items[1][1] = 1;
	selected_items[2][1] = 2;
	selected_items[3][1] = 2;
	selected_items[4][1] = 1;
	
	// We initialize the menu with the current game parameters
	IF (type_of_match == "Klassik")
		selected_items[0][0] = 0;
	ELSEIF (type_of_match == "Vorteil")
		selected_items[0][0] = 1;
	ELSE
		selected_items[0][0] = 2;
	END
	IF (type_player_two == "CPU")
	  selected_items[1][0] = 0;
	ELSE
	  selected_items[1][0] = 1;
	END
	selected_items[2][0] = cpu_level - 1;
	selected_items[3][0] = sets_to_win - 1;
	selected_items[4][0] = bullet_time_mode;
	
	// We write the menu static texts
	SET_TEXT_COLOR(207);
	WRITE(0, X_SIZE / 2 - 125, Y_SIZE / 2 - 30, 0, "Typ");
	WRITE(0, X_SIZE / 2 - 125, Y_SIZE / 2 - 15, 0, "Gegner");
	WRITE(0, X_SIZE / 2 - 125, Y_SIZE / 2, 0, "Level");
	WRITE(0, X_SIZE / 2 - 125, Y_SIZE / 2 + 15, 0, "Sets");
	WRITE(0, X_SIZE / 2 - 125, Y_SIZE / 2 + 30, 0, "Bullet-Time");
	WRITE(0, X_SIZE / 2 - 10, Y_SIZE / 2 + 60, 0, "Exit");
	
	SET_TEXT_COLOR(255);
	WRITE(0, X_SIZE / 2 - 67, Y_SIZE / 2 + 100, 0, "<     Start Drücken      >");
				
	LOOP		
		FOR (row_index = 0 ; row_index <= selected_line[1] ; row_index ++)
			FOR (col_index = 0 ; col_index <= selected_items[row_index][1] ; col_index ++)
				// We draw each item of each line at each frame
				
				// We determine the text color : white = selected ; gray = not selected
				IF (selected_items[row_index][0] == col_index)
					SET_TEXT_COLOR(255);
				ELSE
					SET_TEXT_COLOR(182);
				END
			  
			  // We draw each text
				IF (row_index == 0 AND col_index == 0)
					temp_text[0] = WRITE(0, X_SIZE / 2 - 40, Y_SIZE / 2 - 30, 0, "Klassik");
				ELSEIF (row_index == 0 AND col_index == 1)
					temp_text[1] = WRITE(0, X_SIZE / 2 + 10, Y_SIZE / 2 - 30, 0, "Vorteil");
				ELSEIF (row_index == 0 AND col_index == 2)
					temp_text[2] = WRITE(0, X_SIZE / 2 + 57, Y_SIZE / 2 - 30, 0, "Aufschlag");
				ELSEIF (row_index == 1 AND col_index == 0)
					temp_text[3] = WRITE(0, X_SIZE / 2 - 40, Y_SIZE / 2 - 15, 0, "CPU");
				ELSEIF (row_index == 1 AND col_index == 1)
					temp_text[4] = WRITE(0, X_SIZE / 2 - 10, Y_SIZE / 2 - 15, 0, "Mensch");
				ELSEIF (row_index == 2 AND col_index == 0)
					temp_text[5] = WRITE(0, X_SIZE / 2 - 40, Y_SIZE / 2, 0, "einfach");
				ELSEIF (row_index == 2 AND col_index == 1)
					temp_text[6] = WRITE(0, X_SIZE / 2 + 10, Y_SIZE / 2, 0, "medium");
				ELSEIF (row_index == 2 AND col_index == 2)
					temp_text[7] = WRITE(0, X_SIZE / 2 + 60, Y_SIZE / 2, 0, "hart");
				ELSEIF (row_index == 3 AND col_index == 0)
					temp_text[8] = WRITE(0, X_SIZE / 2 - 40, Y_SIZE / 2 + 15, 0, "eins");
				ELSEIF (row_index == 3 AND col_index == 1)
					temp_text[9] = WRITE(0, X_SIZE / 2 - 10, Y_SIZE / 2 + 15, 0, "zwei");
				ELSEIF (row_index == 3 AND col_index == 2)
					temp_text[10] = WRITE(0, X_SIZE / 2 + 20, Y_SIZE / 2 + 15, 0, "drei");
				ELSEIF (row_index == 4 AND col_index == 0)
					temp_text[11] = WRITE(0, X_SIZE / 2 - 40, Y_SIZE / 2 + 30, 0, "aus");
				ELSEIF (row_index == 4 AND col_index == 1)
					temp_text[12] = WRITE(0, X_SIZE / 2 - 10, Y_SIZE / 2 + 30, 0, "an");
				END
			END
		END
		
		// We draw the menu cursor
		SET_TEXT_COLOR(255);
		IF (selected_line[0] <> 5)
			IF (selected_line[0] == 0)
				arrow_offset = -30;
			ELSEIF (selected_line[0] == 1)
				arrow_offset = -15;
			ELSEIF (selected_line[0] == 2)
				arrow_offset = 0;
			ELSEIF (selected_line[0] == 3)
				arrow_offset = 15;
			ELSEIF (selected_line[0] == 4)
				arrow_offset = 30;
			END
			
			temp_text[13] = WRITE(0, X_SIZE / 2 - 146, Y_SIZE / 2 + arrow_offset, 0, ">>>");
			temp_text[14] = WRITE(0, X_SIZE / 2 + 134, Y_SIZE / 2 + arrow_offset, 0, "<<<");
		ELSE
			// The cursor is of the "exit" item
			WRITE(0, X_SIZE / 2 - 31, Y_SIZE / 2 + 60, 0, ">>>");
			WRITE(0, X_SIZE / 2 + 19, Y_SIZE / 2 + 60, 0, "<<<");				
		END
		
		// We capture key press
		IF (key_up == 0)
			IF (KEY(MENU_DOWN))
				// The player has pressed down key : we cycle down the menu lines
				IF (selected_line[0] < 5)
					selected_line[0] ++;
				ELSE
					selected_line[0] = 0;
				END
				key_up = 1;
				PlaySound(7);
			ELSEIF (KEY(MENU_UP))
				// The player has pressed up key : we cycle up the menu lines
				IF (selected_line[0] > 0)
					selected_line[0] --;
				ELSE
					selected_line[0] = 5;
				END
				key_up = 1;
				PlaySound(7);
			ELSEIF (KEY(START) AND selected_line[0] < 5)
				// The player has pressed "start" : we leave the menu to begin a new match
				PlaySound(6);
				BREAK;
			ELSEIF (selected_line[0] == 5)
				IF (KEY(START))
					// The player has pressed "start" on the "exit" item : end of game
					exit("",0);
				END
			ELSEIF (KEY(MENU_RIGHT) AND selected_items[selected_line[0]][0] < selected_items[selected_line[0]][1])
				// The player has pressed right key : we move the cursor to the right in the selected line
				selected_items[selected_line[0]][0] ++;
				key_up = 1;
				PlaySound(7);
			ELSEIF (KEY(MENU_LEFT) AND selected_items[selected_line[0]][0] > 0)
				// The player has pressed left key : we move the cursor to the left in the selected line
				selected_items[selected_line[0]][0] --;
				key_up = 1;
				PlaySound(7);
			END
		END
		
		FRAME;

		IF (NOT (KEY(MENU_UP) OR KEY(MENU_DOWN) OR KEY(MENU_LEFT) OR KEY(MENU_RIGHT)))
			// We prevent keys from switching too fast
			key_up = 0;
		END
  
  	// After each frame, we delete all dynamic texts
		FOR (text_index = 0 ; text_index <= 14 ; text_index ++)
			DELETE_TEXT(temp_text[text_index]);
		END
	END
	
	WHILE (KEY(START))
		// We prevent keys from switching too fast
		FRAME;
	END
	
	// We update game parameters with the selected menu items
	IF (selected_items[0][0] == 0)
		type_of_match = "Klassik";
	ELSEIF (selected_items[0][0] == 1)
		type_of_match = "Vorteil";
	ELSE
		type_of_match = "Umkehr";
	END
	IF (selected_items[1][0] == 0)
	  type_player_two = "CPU";
	ELSE
	  type_player_two = "Mensch";
	END
	cpu_level = selected_items[2][0] + 1;
	sets_to_win = selected_items[3][0] + 1;
	bullet_time_mode = selected_items[4][0];
		
	// We delete all remaining texts
	DELETE_TEXT(0);
	
	// Screen fades out / in
	fade_off();
	fade_on();
END              
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  PROCESS : MovingText
 *
 *  Writes a text to the right of the screen. The text moves to the left until it goes out
 *  of the screen.
 *
 *  PARAMS:
 *			string text_to_write : text to write to the screen
 *
 */
PROCESS MovingText(string text_to_write) 

PRIVATE
INT text_movement;	// Offset of the text at each frame

BEGIN
	// If there is already a text moving in the screen, we delete it
	// and we kill the previous MovingText process
	IF (moving_text_id > -1)
		DELETE_TEXT(moving_text_id);
		SIGNAL(GET_ID(TYPE MovingText), s_kill);
	END
	
	// We write the text outside of the screen, to the right
	text_movement = 0;
	SET_TEXT_COLOR(255);
	moving_text_id = WRITE(font_id, X_SIZE + 10, Y_SIZE / 3.0, 0, text_to_write);
	
	// The text moves from right to left at each frame
	WHILE (text_movement < X_SIZE + 200)
		MOVE_TEXT(moving_text_id, X_SIZE + 10 - text_movement, Y_SIZE / 3.0);
		text_movement += 3;
		FRAME;
	END
	
	// We delete the text when it goes out of the screen to the left
	DELETE_TEXT(moving_text_id);
	moving_text_id = -1;
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  PROCESS : Player
 *
 *  Manages and displays a player. This player is controlled by a human or by the CPU.
 *	This process manages the left and right movement of the player and the jump move.
 *  If the player is controlled by the CPU, key press capture is replaced by an AI which
 *  decides when to move right / left / jump / and when to trigger bullet-time mode.
 *
 *  PARAMS:
 *			int player_id : 0 for 1P or 1 for 2P
 *			string player_type : "human" or "cpu"
 *      float pos_x : player horizontal position at the beginning
 *      float pos_y : player vertical position at the beginning
 *			int z : depth position of the sprite
 *			char left_key : left key
 *			char right_key : right key
 *			char jump_key_1 : jump key
 *			char jump_key_2 : alternate jump key
 *			char bullet_time_key : bullet-time key
 *
 */
PROCESS Player(int player_id, string player_type, float pos_x, float pos_y, int z, char left_key, char right_key, char jump_key_1, char jump_key_2, char bullet_time_key)

PRIVATE
INT frame_counter;								// Used to count how many frames must be drawn in bullet-time mode
INT player_jump_trigger = 0;			// Indicates when the player / CPU wants to jump (0 = no jump ; 1 = jump)
INT player_move_x;								// Indicates if the player move to the left (-1), to the right (1) or doesn't move (0)

BEGIN
	IF (player_id == 0)
		graph = 1;	// => associates graph n°1 from the FPG file with this process
	ELSEIF (player_type == "Mensch")
		graph = 2;	// => associates graph n°2 (2P) from the FPG file with this process
	ELSE
		graph = 6;	// => associates graph n°6 (CPU) from the FPG file with this process
	END
	
	player_move_x = 0;						// => initiates x movement to none
	
	LOOP
    //  /-------------------\
		// < Movement management >
		//  \-------------------/
		
		IF ((player_id == 0 AND game_status == 1) OR (player_id == 1 AND game_status == 2))
			// The player won the point : he jumps
			player_move_x = 0;
			IF (player_jump[player_id] == 0)
				player_jump[player_id] = 1;
				player_jump_trigger = 1;
			END
			
			// The player is rotating
			angle += pi / PLAYER_ROTATION_FACTOR;
		ELSEIF ((player_id == 1 AND game_status == 1) OR (player_id == 0 AND game_status == 2))
			// The player lost the point : he doesn't move
			player_move_x = 0;
		ELSE
			IF (player_type == "Mensch")
				// -------------------
				// The player is human
				// -------------------
			  	
				// Key press test : move
				IF (player_jump[player_id] == 0)
					IF (key(left_key))
						player_move_x = -1;
					ELSEIF (key(right_key))
						player_move_x = 1;
					ELSE
						player_move_x = 0;
					END
			 	END
			 	
				// Key press test : jump
				IF ((key(jump_key_1) OR key(jump_key_2)) AND (player_jump[player_id] == 0))
					player_jump[player_id] = 1;
					player_jump_trigger = 1;
				END
			ELSE
				// -----------------
				// The player is CPU
				// -----------------
		
				IF (player_jump[player_id] == 0)
					// If the CPU is not jumping, we let it "think" about what to do and move
					player_move_x = CalculateCpuMove(pos_x, pos_y);
				ELSE
					// If the CPU is jumping, we let it "think" about what to do
					// but we don't let him move (he can trigger his bullet-time).
					CalculateCpuMove(pos_x, pos_y);
				END
					
				IF (ABS(player_move_x) * 1.0 > 1.0)
					// CPU has decided to jump
					IF (player_jump[player_id] == 0)
						// We make it jump only if it's not already jumping
			  		player_jump[player_id] = 1;
			  		player_jump_trigger = 1;
			  	END
			  		
					IF (player_move_x == -11) player_move_x = -1; END
					IF (player_move_x == 10) player_move_x = 0; END
					IF (player_move_x == 11) player_move_x = 1; END
				END
			END
		END
			
		// We update the player horizontal and vertical speed
		// - horizontal speed
		player_vx[player_id] = player_move_x * MOVE_FACTOR * 1.0;
		
		// - vertical speed   	
		IF (player_jump[player_id] == 1 AND player_jump_trigger == 1)
			player_vy[player_id] = -JUMP_FACTOR * GRAVITY * 1.0;
			player_jump_trigger = 0;
		END
			
		// We update the player horizontal position 
		pos_x += player_vx[player_id];
		
		// We prevent the player from going out of the playground
		IF (player_id == 0)
			// --------
			// Player 1
			// --------
			IF (pos_x < PLAYER_SIZE * 1.0)
				// Left boundary (edge of the screen)
				pos_x = PLAYER_SIZE * 1.0;
			ELSEIF (pos_x > (X_SIZE / 2 - NET_SIZE_X / 2 - PLAYER_SIZE) * 1.0)
				// Right boundary (net)
				pos_x = (X_SIZE / 2 - NET_SIZE_X / 2 - PLAYER_SIZE) * 1.0;
			END
		ELSE
			// --------
		  // Player 2
		  // --------
			IF (pos_x < (X_SIZE / 2 + NET_SIZE_X / 2 + PLAYER_SIZE) * 1.0)
				// Left boundary (net)
				pos_x = (X_SIZE / 2 + NET_SIZE_X / 2 + PLAYER_SIZE) * 1.0;
			ELSEIF (pos_x > (X_SIZE - PLAYER_SIZE) * 1.0)
				// Right boundary (edge of the screen)
				pos_x = (X_SIZE - PLAYER_SIZE) * 1.0;
			END
		END
		
		IF (player_jump[player_id] == 1)
			// If the player is jumping, we update his vertical position 
		  pos_y += player_vy[player_id] * 1.0;
		  player_vy[player_id] += GRAVITY * PLAYER_FALL_FACTOR * 1.0;
		  
		  // We prevent the player from falling under the ground
		  IF (pos_y >= (Y_SIZE - GROUND_SIZE) * 1.0)
		  	pos_y = (Y_SIZE - GROUND_SIZE) * 1.0;
		  	
		  	// End of the jump
		    player_jump[player_id] = 0;
		  END
		END
		
		// We update the global x;y position of the player for the others processes
		player_x[player_id] = pos_x;
		player_y[player_id] = pos_y;
		
		// We move the process to the calculated position
		x = player_x[player_id];
		y = player_y[player_id];
    
    //  /----------------------\
		// < Bullet-time management >
		//  \----------------------/
		
		IF (bullet_time_mode == 1)
			// Only if the game is not over
			IF (game_status == 0)
				// If the player bullet-time mode is activated despite his empty gauge,
				// we stop bullet-time mode and reset bullet-time recovery time
				IF (bullet_time[player_id] == 1 AND bullet_gauge[player_id] <= 0.0)
					StopBulletTime(player_id, 1);
					bullet_time_key_pressed[player_id] = BULLET_TIME_RECOVERY_TIME;
					bullet_gauge[player_id] = 0.0;
				END
			
				IF (player_type == "Mensch")
					// -------------------
					// The player is human
					// -------------------
				                              	                              		
					// The player hits the bullet-time key
					IF (key(bullet_time_key) AND bullet_time_key_pressed[player_id] == 0)
						IF (bullet_time[player_id] == 1)
							// Bullet-time mode is already activated : we stop it and reset
							// bullet-time recovery time
							StopBulletTime(player_id, 1);
							bullet_time_key_pressed[player_id] = BULLET_TIME_RECOVERY_TIME;
						ELSEIF (bullet_gauge[player_id] > 0.0)
							// Bullet-time mode is not activated and the gauge is not empty :
							// we start bullet-time mode and reset bullet-time recovery time
							StartBulletTime(player_id);
							bullet_time_key_pressed[player_id] = BULLET_TIME_RECOVERY_TIME;			
						END
					END
				ELSE
					// -----------------
					// The player is CPU
					// -----------------
			
					IF (cpu_bullet_time_trigger == 0 AND bullet_time[player_id] == 1 AND bullet_time_key_pressed[player_id] == 0)
						// CPU doesn't want to activate his bullet-time any more,
						// bullet-time mode is currently activated and the recovery time is reached :
						// we stop it and reset bullet-time recovery time
						StopBulletTime(player_id, 1);
						bullet_time_key_pressed[player_id] = BULLET_TIME_RECOVERY_TIME;
					END
			
					// CPU wants to activate his bullet-time mode
					IF (cpu_bullet_time_trigger == 1)
						cpu_bullet_time_trigger = 0;
						
						IF (bullet_time[player_id] == 0 AND bullet_gauge[player_id] > 0.0)
							// Bullet-time mode is not activated and the gauge is not empty :
							// we start bullet-time mode and reset bullet-time recovery time
							StartBulletTime(player_id);
							bullet_time_key_pressed[player_id] = BULLET_TIME_RECOVERY_TIME;			
						END
					END
				END
		    
		    // If recovery time is not reached yet, we decrease it at each frame
				IF (bullet_time_key_pressed[player_id] > 0)
					bullet_time_key_pressed[player_id] --;
				END
								
				IF (bullet_time[player_id] == 1)
					// Bullet-time mode is activated : we decrease the gauge value
					bullet_gauge[player_id] --;
				ELSE
					IF (bullet_gauge[player_id] < BULLET_TIME_GAUGE_INIT)
						// Bullet-time mode is not activated and the gauge is not full :
						// we increase it a little bit at each frame
						bullet_gauge[player_id] += BULLET_TIME_GAUGE_INC;
					END				
				END
				
				// We update the gauge display (value displayer between 0 and 100)
				bullet_gauge_display[player_id] = bullet_gauge[player_id] * 100.0 / BULLET_TIME_GAUGE_INIT;
				
				// If the other player has activated his bullet-time, and the specified
				// player hasn't, we draw several frames to slow the specified player down
				IF ((player_id == 0 AND bullet_time[1] == 1 AND bullet_time[0] == 0)
						OR (player_id == 1 AND bullet_time[0] == 1 AND bullet_time[1] == 0))
						
					FOR (frame_counter = 0 ; frame_counter <= BULLET_TIME_FACTOR - 1 ; frame_counter++)
						FRAME;
					END
				END
			END
		END
		
		FRAME;	// => Draws the graph associated with this process at each frame
	END
END
/* --------------------------------------------------------------------------------------- */    

/* ---------------------------------------------------------------------------------------
 *  PROCESS : Ball
 *
 *  Manages and displays the ball. This process manages the movements of the ball, the
 *	collision with the net, the players and the ground.
 *
 *  PARAMS:
 *      float pos_x : ball horizontal position at the beginning
 *      float pos_y : ball vertical position at the beginning
 *			int z : depth position of the sprite
 *
 */
PROCESS Ball(float pos_x, float pos_y, int z)

LOCAL
int player_id;
float rebound_x;
float rebound_y;
float ball_v;
float distance;
int i;

BEGIN
	graph = 3;	// => associates graph n°3 from the FPG file with this process
	
	WHILE (game_status == 0)
    // We move the ball depending its speed
    pos_x += ball_vx * 1.0;
    pos_y += ball_vy * 1.0;
    
    // We apply gravity to the ball vertical speed
    ball_vy += GRAVITY * BALL_FALL_FACTOR * 1.0;
    
    // We prevent the ball from going out of the screen
    IF (pos_x < BALL_SIZE * 1.0)
    	// Left boundary : the ball bounces to the right
    	pos_x = BALL_SIZE * 1.0;
    	ball_vx = - ball_vx * 1.0;
    ELSEIF (pos_x > (X_SIZE - BALL_SIZE) * 1.0)
    	// Right boundary : the ball bounces to the left
			pos_x = (X_SIZE - BALL_SIZE) * 1.0;
			ball_vx = - ball_vx * 1.0;
		END
		
		// We check if the ball is hitting the net
    IF (pos_x > (X_SIZE / 2 - NET_SIZE_X / 2 - BALL_SIZE) * 1.0
    		AND pos_x < (X_SIZE / 2 + NET_SIZE_X / 2 + BALL_SIZE) * 1.0)
    		
    	IF (pos_y > (Y_SIZE - GROUND_SIZE - NET_SIZE_Y) * 1.0)
    		// The ball is under the top of the net : it bounces horizontally
	      IF (ball_vx < 0.0)
	        pos_x = (X_SIZE / 2 + NET_SIZE_X / 2 + BALL_SIZE + 1) * 1.0;
	        ball_vx = -ball_vx * 1.0;
	      ELSEIF (ball_vx > 0.0)
	        pos_x = (X_SIZE / 2 - NET_SIZE_X / 2 - BALL_SIZE - 1) * 1.0;
	        ball_vx = -ball_vx * 1.0;
	      END
	      
	      // We play the net rebound sound
	      PlaySound(2);
			ELSEIF (pos_y > (Y_SIZE - GROUND_SIZE - NET_SIZE_Y - BALL_SIZE) * 1.0)
				// The ball is above the top of the net : it bounces up (vertically)
				pos_y = (Y_SIZE - GROUND_SIZE - NET_SIZE_Y - BALL_SIZE) * 1.0;
				ball_vy = -ABS(ball_vy) * 1.0;
				
				// We play the net rebound sound
				PlaySound(2);
    	END
    	
    END
     
    FOR (player_id = 0; player_id <= 1 ; player_id++)

			distance = SQRT((player_x[player_id] - pos_x) * (player_x[player_id] - pos_x) + (player_y[player_id] - pos_y) * (player_y[player_id] - pos_y)) * 1.0;
      // => Distance between the center of the ball and the center of the player
      
    	// For each player, we check if the ball is hitting the player    	
	    IF (distance < (PLAYER_SIZE + BALL_SIZE) * 1.0)
	    	IF (pos_y <= player_y[player_id])
	    		// ----------------------------
	    		// The ball is above the player
	    		// ----------------------------
	    	
					IF (com_serve != 0)
						// Resetting the CPU serve mode : service is over
						com_serve = 0;
					END
					
					ball_v = SQRT(ball_vx * ball_vx + ball_vy * ball_vy) * 1.0;
					// => Intensity of ball speed vector
					rebound_x = (pos_x - player_x[player_id]) * 1.0 / (PLAYER_SIZE + BALL_SIZE) * 1.0;
					// => Direction and factor of horizontal rebound of the ball
					rebound_y = (pos_y - player_y[player_id]) * 1.0 / (PLAYER_SIZE + BALL_SIZE) * 1.0;
					// => Direction and factor of vertical rebound of the ball
					
					// We increase the ball horizontal speed when it bounces against a player
					ball_vx += rebound_x * (ball_v + BALL_BOUNCE_FACTOR * GRAVITY) * 1.0;
					
					// Limitation of ball maximum horizontal speed
					IF (ball_vx < -BALL_BOUNCE_FACTOR * 1.25 * GRAVITY)
						ball_vx = -BALL_BOUNCE_FACTOR * 1.25 * GRAVITY;
					ELSEIF (ball_vx > BALL_BOUNCE_FACTOR * 1.25 * GRAVITY)
						ball_vx = BALL_BOUNCE_FACTOR * 1.25 * GRAVITY;
					END
					
					// We increase the ball vertical speed when it bounces against a player
					// If the player is jumping, the increase is smaller than if the player is not jumping
					IF (player_jump[player_id] == 0)
						ball_vy = rebound_y * BALL_BOUNCE_FACTOR * GRAVITY * 1.0;
					ELSE
						ball_vy = rebound_y * BALL_BOUNCE_FACTOR * GRAVITY * 1.0 / 8;
					END
					
					// We accelerate the ball movement to be sure that the ball goes "out" of the player
					pos_x += ball_vx * 2.0;
					pos_y += ball_vy * 2.0;
	    	ELSE
	    		// ----------------------------
	    		// The ball is under the player
	    		// ---------------------------- 
	    		
	    		// We have to be sure that the ball bounces down
	      	ball_vy = ABS(ball_vy) * 1.0;
	      END
	      
	      // We play the player rebound sound
	      PlaySound(player_id);
			END
    END
    
    // We check if the ball is hitting the ground
		IF (pos_y > Y_SIZE - GROUND_SIZE - BALL_SIZE)
			IF (type_of_match <> "Umkehr")				
				// The match is not of type Rebound : the game is over
				
				IF (pos_x < X_SIZE / 2)
					// The ball fell on the left side : player 2 wins
					game_status = 2;
				ELSE
					// The ball fell on the right side : player 1 wins
				  game_status = 1;
				END
			  
			  // End game sound
				PlaySound(3);
			ELSE
				// The match is of type Rebound

				IF (pos_x < X_SIZE / 2)
					// The ball fell on the left side : rebound for player 2
					player_rebound_score[1] ++;
					
					IF (player_rebound_score[1] == REBOUNDS_TO_END_GAME)
						// Player 2 won the game
						game_status = 2;
						
					  // End game sound
						PlaySound(3);
					ELSE
						// The game is not over ...
						
						// The ball bounces !
						ball_vy = - ABS(ball_vy) * 1.0;
						
						IF (type_player_two == "Mensch")
							MovingText("Aufschlag für 2P !");
						ELSE
							MovingText("Aufschlag für CPU !");
						END
						
						// Net rebound sound
						PlaySound(2);
					END
				ELSE
					// The ball fell on the right side : rebound for player 1
					player_rebound_score[0] ++;
					
					IF (player_rebound_score[0] == REBOUNDS_TO_END_GAME)
						// Player 1 won the game
						game_status = 1;
						
					  // End game sound
						PlaySound(3);
					ELSE
						// The game is not over ...
						
						// The ball bounces !
						ball_vy = - ABS(ball_vy) * 1.0;
						
						MovingText("Aufschlag für 1P !");
						
						// Net rebound sound
						PlaySound(2);
					END
				END

			END			
		END
	  
	  // We update the global x;y position of the ball for the others processes
	  ball_x = pos_x;
	  ball_y = pos_y;
	  
	  // We move the process to the calculated position
	  x = pos_x;
	  y = pos_y;
	  	
		// If at least one player has activated his bullet-time, we draw several
		// frames to slow the ball down
		IF (bullet_time_mode == 1)
			IF (bullet_time[0] == 1 OR bullet_time[1] == 1)
				FOR (i = 0 ; i <= BULLET_TIME_FACTOR - 1 ; i++)
					FRAME;
				END
			END
		END
	  
		FRAME;	// => Draws the graph associated with this process at each frame
		
	END
	
	// End of the game : the ball doesn't move and it's shrinking until it disappears
	WHILE (SIZE > 0)
		SIZE --;
		FRAME;
	END
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  PROCESS : Ground
 *
 *  Displays the ground on the screen.
 *
 *  PARAMS:
 *      int x : ground horizontal position
 *      int y : ground vertical position
 *			int z : depth position of the sprite
 *
 */
PROCESS Ground(x, y, z)

BEGIN
	graph = 4;	// => associates the graph n°4 from the FPG file to this process
	
	LOOP 
		FRAME;	// => Draws the graph associated with this process at each frame
	END
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  PROCESS : Net
 *
 *  Displays the net on the screen.
 *
 *  PARAMS:
 *      int x : net horizontal position
 *      int y : net vertical position
 *			int z : depth position of the sprite
 *
 */
PROCESS Net(x, y, z)

BEGIN
	graph = 5;	// => associates the graph n°5 from the FPG file to this process
	
	LOOP 
		FRAME;	// => Draws the graph associated with this process at each frame
	END
END
/* --------------------------------------------------------------------------------------- */




/* ---------------------------------------------------------------------------------------
   |                           ****** FUNCTIONS ******                                   |
   --------------------------------------------------------------------------------------- */




/* ---------------------------------------------------------------------------------------
 *  FUNCTION : PlaySound
 *
 *  Plays the specified sound effect. If the sound effect is bullet time, the others sounds
 *  are disabled.
 *
 *  PARAMS:
 *      int sound_sample_id : ID of the sound effect to play
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION PlaySound(int sound_sample_id)

BEGIN
  IF (sound_sample_id == 3
  		OR sound_sample_id == 4
  		OR sound_sample_id == 5
  		OR sound_sample_id == 6
  		OR sound_sample_id == 7
  		OR (bullet_time[0] == 0 AND bullet_time[1] == 0))
  	// If the sound is a rebound with a player or with the net, we only play it
  	// if bullet-time is deactivated for both players
  		
		IF (SOUND_FLAG == 1)
			PLAY_SONG(sound_sample[sound_sample_id], 0);
		END
	END
	
	return;
END              
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : InGameInterruption
 *
 *  Checks if the player presses special keys during game : full-screen key, pause key and
 *	exit keys.
 *
 *  PARAMS:
 *			int game_mode : 0 = menu screen ; 1 = in game
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION InGameInterruption(int game_mode)

PRIVATE
signal_command;	// Signal to send to others processes : freeze or wake up

BEGIN
	IF (game_mode == 1)
		// We're not at the menu screen
		
		IF (KEY(PC_EXIT_KEY)
			OR (KEY(GP32_EXIT_KEY_1) AND KEY(GP32_EXIT_KEY_2)))
			
			// If the player presses exit key(s), we cancel the match
			match_status = 3;
			
			PlaySound(6);
		END

		IF (KEY(PAUSE))
			IF (pause_text_id == -1)
				// The player presses the pause key and game is not paused : we pause the game
				// and display the "pause" text
				SET_TEXT_COLOR(255);
				pause_text_id = WRITE(0, X_SIZE / 2 - 18, Y_SIZE / 2 - 15, 0, "Pause");
				signal_command = s_freeze;
			ELSE
				// The player presses the pause key and game is paused : we stop the pause
				// and delete the "pause" text
				DELETE_TEXT(pause_text_id);
				pause_text_id = -1;
				signal_command = s_wakeup;
			END
			
			// We send stop / restart signals to others processes (ball, each player and moving text)
			SIGNAL(GET_ID(TYPE Ball), signal_command);
			SIGNAL(GET_ID(TYPE Player), signal_command);
			SIGNAL(GET_ID(TYPE Player), signal_command);
			SIGNAL(GET_ID(TYPE MovingText), signal_command);
			
			PlaySound(7);
		END	
	END
	
	// The player presses full-screen key : we switch full-screen display
	IF (KEY(FULL_SCREEN_KEY))
		IF (full_screen == 0)
			full_screen = 1;
			SET_MODE(X_SIZE, Y_SIZE, 8);
		ELSE
			full_screen = 0;
			SET_MODE(X_SIZE, Y_SIZE, 8);
		END
	END
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : CalculateCpuMove
 *
 *  AI of the CPU. It decides if the CPU moves or not. The CPU can go left, right and jump.
 *
 *  PARAMS:
 *      float pos_x : player cpu horizontal position
 *      float pos_y : player cpu vertical position
 *
 *  RETURN VALUE: 
 *      int : 0 = no move ; -1 = move to the left ; 1 = move to the right
 *					  10 = jump ; -11 = jump to the left ; 11 = jump to the right
 *
 */
FUNCTION INT CalculateCpuMove(float pos_x, float pos_y)

LOCAL
int return_value;	// Value to return at the end of the function
float d9;
float d10;
float d8;
float d14;
float d15;
float d2;
float d20;
float d11;
float d6;
float d3;
float d12;
float d7;

BEGIN
	return_value = 0;
	
	// d9 = altitude at which cpu wants to intercept the ball
	d9 = (Y_SIZE - GROUND_SIZE - PLAYER_SIZE - ball_y) * 1.0;
	
	// d10 = time needed for the ball to reach the d9 altitude
	d10 = (SQRT(ball_vy * ball_vy + 2 * GRAVITY * BALL_FALL_FACTOR * d9) - ball_vy) * 1.0 / (GRAVITY * BALL_FALL_FACTOR * 1.0);
	
	// d8 = horizontal position of the ball when it reaches d9 altitude
	d8 = (ball_x + ball_vx * d10) * 1.0;
	
	// We calculate rebound at the left and the right of the screen to calculate d8
	IF (d8 > X_SIZE * 1.0)
		d8 = (X_SIZE * 2 - d8) * 1.0;
	ELSEIF (d8 < 0.0)
		d8 = -d8 * 1.0;
	END
	
	// d14 = Y speed of the ball when it reaches d9 altitude
	d14 = (ball_vy + GRAVITY * BALL_FALL_FACTOR * d10) * 1.0;
	
	// d15 = Speed intensity of the ball when it reaches d9 altitude
	d15 = SQRT(ball_vx * ball_vx + d14 * d14) * 1.0;

	if (d15 < 0.00000000000001) d15 = 0.00000000000001; END // avoid division by zero !
	
	IF (pos_x < (X_SIZE / 2 + X_SIZE / 5))
		return_value = 1;
	ELSEIF (pos_x > (X_SIZE / 2 + X_SIZE / 5))
		return_value = -1;
	END
	
	d2 = ((PLAYER_SIZE + BALL_SIZE) * ball_vx * 1.0) / ((d15 + BALL_BOUNCE_FACTOR * GRAVITY) * 1.0);
	
	IF (d8 > (X_SIZE / 2 - PLAYER_SIZE * 2) * 1.0)
    // La balle tombera dans la moitié arrière du terrain cpu allongée d'une longueur du joueur
    IF (d8 > (X_SIZE / 2 + X_SIZE / 4) * 1.0)
	    // La balle tombera dans la moitié arrière du terrain cpu : cpu ne saute pas !
	    IF ((d8 + d2 + (BALL_SIZE / 2)) * 1.0 > pos_x)
	    	// Le cpu est devant le point de contact qui permettra à la balle de doubler sa vitesse actuelle
	    	return_value = 1;
	    ELSEIF ((d8 + d2 + (BALL_SIZE / 2)) * 1.0 < pos_x)
	    	// Le cpu est derrière le point de contact qui permettra à la balle de doubler sa vitesse actuelle
	    	return_value = -1;
	    ELSE
	    	return_value = 0;
	    END
    ELSEIF (ABS(ball_vx) * 1.0 < BALL_BOUNCE_FACTOR * GRAVITY * 30.0 / 80.0)
	    // La balle tombera entre "la moitié arrière du terrain cpu + la taille du joueur" et "la moitié arrière du terrain cpu"
			// ET sa vitesse est actuellement inférieure à un seuil
	    IF (player_x[0] < (X_SIZE / 3) * 1.0 AND ABS(ball_y - pos_y) * 1.0 > PLAYER_SIZE * 2.5)
				// Le joueur 1 est situé dans les 2/3 arrière de son terrain
	      // ET la balle est située à une altitude d'au moins 2,5 fois la taille du joueur : cpu ne saute pas !
	      IF ((ball_x + BALL_SIZE + ball_vx * 10.0 * FRAME_RATE / 125.0) * 1.0 > pos_x)
	      	// Dans 10 cycles, la balle sera derrière le cpu
	      	return_value = 1;
	      ELSEIF ((ball_x + BALL_SIZE + ball_vx * 10.0 * FRAME_RATE / 125.0) * 1.0 < pos_x)
	      	// Dans 10 cycles, la balle sera devant le cpu
	      	return_value = -1;
	      ELSE
	      	return_value = 0;
	      END
	    ELSEIF ((d8 + d2 + (BALL_SIZE * 2)) * 1.0 > pos_x)
	    		// Le cpu est devant le point de contact qui permettra à la balle de doubler sa vitesse actuelle
	        return_value = 1;
	    ELSEIF ((d8 + d2 + (BALL_SIZE * 2)) * 1.0 < pos_x)
	    		// Le cpu est derrière le point de contact qui permettra à la balle de doubler sa vitesse actuelle
	        return_value = -1;
	    ELSE
	        return_value = 0;
	    END
    ELSEIF ((d8 + d2 + (BALL_SIZE / 6)) * 1.0 > pos_x)
   		// Le cpu est devant le point de contact qui permettra à la balle de doubler sa vitesse actuelle    
    	return_value = 1;
    ELSEIF ((d8 + d2 + (BALL_SIZE / 6)) * 1.0 < pos_x)
   		// Le cpu est derrière le point de contact qui permettra à la balle de doubler sa vitesse actuelle
    	return_value = -1;
    ELSE
    	return_value = 0;
    END
    
    IF (ball_x < (X_SIZE / 2) * 1.0
    		AND ABS(player_x[0] - ball_x) * 1.0 < PLAYER_SIZE
    		AND player_jump[0] != 0
    		AND pos_x < (X_SIZE / 2 + X_SIZE / 4) * 1.0
    		AND ball_x > (X_SIZE / 3) * 1.0)
    	// Le balle est actuellement dans les 2/3 avant du terrain du joueur 1
			// ET le joueur 1 est à une distance d'une longueur de joueur de la balle
			// ET le joueur 1 est en train de sauter
			// ET le cpu est situé dans la moitié avant de son terrain 
			
			IF (cpu_level == 3)
      	return_value = -11;
      ELSE
      	return_value = -1;
      END
    END
    
    IF (ABS(ball_vx) * 1.0 < BALL_BOUNCE_FACTOR * GRAVITY * 25.0 / 80.0
    		AND ball_vy > (PLAYER_SIZE * 8.0 / 35.0 + (pos_y - ball_y) * 2.0 * GRAVITY) * GRAVITY * 1.0
    		AND ABS(ball_y - pos_y) * 1.0 > PLAYER_SIZE * 2.5
    		AND pos_x < (X_SIZE / 2 + X_SIZE / 4) * 1.0
    		AND (ball_vx * PLAYER_SIZE * 20.0 / 35.0 + ball_x) * 1.0 < (pos_x + BALL_SIZE) * 1.0
    		AND (ball_vx * PLAYER_SIZE * 20.0 / 35.0 + ball_x) * 1.0 > (pos_x - PLAYER_SIZE) * 1.0)
    	// La vitesse de la balle est actuellement inférieure à un seuil
			// ET ?
    		    		
      IF (player_x[0] < X_SIZE * 1.0 / 2.7000000000000002 OR player_jump[0] == 1)
				// Le joueur 1 est à peu près dans les 2/3 arrières de son terrain
				// OU le joueur 1 est en train de sauter
				
				IF (cpu_level == 3 OR (cpu_level == 2 AND RAND(0,9) == 9))
	      	return_value = 10;
	      ELSE
	      	return_value = 0;
	      END
      END
		END
	END
  
	d20 = (pos_x - PLAYER_SIZE * 1.5 - ball_x) * 1.0;
	IF (d20 > 0.0
			AND pos_x < (X_SIZE / 2 + X_SIZE / 4) * 1.0
			AND (ball_vx < -0.00000000000001 OR ball_vx > 0.00000000000001)) // avoid division by zero !
		// La balle est derrière le point situé devant le cpu à une longueur de joueur
		// ET le cpu est dans la moitié avant de son terrain
		// ET la balle n'est pas en train de rebondir sur place
					
		d11 = (d20 / ball_vx) * 1.0;
		d6 = (ball_y + d11 * ball_vy + (GRAVITY * BALL_FALL_FACTOR * d11 * d11) / 2.0) * 1.0;
		
		IF (d6 > (pos_y - NET_SIZE_Y - PLAYER_SIZE * 1.5) * 1.0
				AND d6 < (pos_y - NET_SIZE_Y) * 1.0)
				
			IF (d11 > 10.0 AND d11 < 30.0 * FRAME_RATE / 125.0)
		    d3 = (d20 - PLAYER_SIZE * 60.0 / 35.0) * 1.0;
		    
		    IF (d3 > 0.0 AND pos_x < ((X_SIZE / 2 + X_SIZE / 4) + PLAYER_SIZE * 60.0 / 35.0) * 1.0)
	        d12 = (d3 / ball_vx) * 1.0;
	        d7 = (ball_y + d12 * ball_vy + (GRAVITY * BALL_FALL_FACTOR * d12 * d12) / 2.0) * 1.0;
	        
	        IF (d7 > (pos_y - NET_SIZE_Y - PLAYER_SIZE * 1.5) * 1.0
	        		AND d7 < (pos_y - NET_SIZE_Y) * 1.0
	        		AND d12 > 13.0 * FRAME_RATE / 125.0 
	        		AND d12 < 15.0 * FRAME_RATE / 125.0)
	        		
						IF (cpu_level == 3)
			      	return_value = -11;
			      ELSE
			      	return_value = -1;
			      END
	        END
		    ELSE
	        return_value = -1;
		    END
			ELSEIF (d11 > 7.0 * FRAME_RATE / 125.0 AND d11 < 5.0 * FRAME_RATE / 125.0)
				IF (cpu_level == 3 OR (cpu_level == 2 AND RAND(0,9) == 9))
	      	return_value = 10;
	      ELSE
	      	return_value = 0;
	      END
			END
		END
	END

	// Bullet-time activation
	IF (bullet_time_mode == 1)
		IF (bullet_gauge[1] >= BULLET_TIME_GAUGE_INIT * 1.0 / 10
				AND ball_y > Y_SIZE - GROUND_SIZE - PLAYER_SIZE * 3.0
				AND ball_x > X_SIZE * 1.0 / 2
				AND d8 > X_SIZE * 1.0 / 2
				AND d8 < X_SIZE * 1.0
				AND ((d8 > pos_x AND d8 > pos_x + d10 * MOVE_FACTOR * 1.0 + PLAYER_SIZE * 1.0 / 2 + BALL_SIZE * 2.0 AND d8 < pos_x + d10 * MOVE_FACTOR * BULLET_TIME_FACTOR * 1.0 + PLAYER_SIZE * 1.0 / 2 + BALL_SIZE * 2.0)
						OR (d8 < pos_x AND d8 < pos_x - d10 * MOVE_FACTOR * 1.0 - PLAYER_SIZE * 1.0 / 2 - BALL_SIZE * 2.0 AND d8 > pos_x - d10 * MOVE_FACTOR * BULLET_TIME_FACTOR * 1.0 - PLAYER_SIZE * 1.0 / 2 - BALL_SIZE * 2.0))
				)
	    // We activate bullet-time if cpu gauge is at least at 10%
	    // and if the ball is under an altitude of 3 player sizes
	    // and the ball is in the cpu side of the screen
	    // and the ball is going to fall in the cpu side of the screen
	    // and cpu doesn't have enough time to reached the ball, but it has enough
	    // time to reached the ball in bullet-time mode 
	    
	    IF (cpu_level == 3 OR (cpu_level == 2 AND RAND(0,9) == 9))
				cpu_bullet_time_trigger = 1;
			END
		END
	END
  
  // Service routine
	IF (com_serve != 0)
		IF (com_serve == 1)
			// First type of service : cpu is slightly at the right of the ball impact point
	    IF ((d8 + (BALL_SIZE * 2.0)) * 1.0 < pos_x)
	    	return_value = -1;
	    ELSEIF ((d8 + (BALL_SIZE * 2.0)) * 1.0 > pos_x)
	    	return_value = 1;
	    ELSE
	    	return_value = 0;
	    END
		ELSEIF (com_serve == 2)
			// First type of service : cpu is at the right of the ball impact point
	    IF ((d8 + (BALL_SIZE * 2.5)) * 1.0 < pos_x)
	    	return_value = -1;
	    ELSEIF ((d8 + (BALL_SIZE * 2.5)) * 1.0 > pos_x)
	    	return_value = 1;
	    ELSE
	    	return_value = 0;
	    END
		ELSEIF (com_serve == 4)
			// First type of service : cpu is slightly at the left of the ball impact point
	    IF ((d8 - (BALL_SIZE * 1.0)) * 1.0 < pos_x)
	    	return_value = -1;
	    ELSEIF ((d8 - (BALL_SIZE * 1.0)) * 1.0 > pos_x)
	    	return_value = 1;
	    ELSE
	    	return_value = 0;
	    END
		END
	END
	
	// If the CPU has decided to jump and he's serving
	// then we prevent it from jumping
	IF (ABS(return_value) * 1.0 > 1.0 AND com_serve != 0)
		IF (return_value == -11) return_value = -1; END
		IF (return_value == 10) return_value = 0; END
		IF (return_value == 11) return_value = 1; END
	END

	return return_value;
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : MatchInit
 *
 *	Variable initialization at the beginning of a match.
 *
 *  PARAMS:
 *			none
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION MatchInit()
BEGIN
	// A new match has begun
	match_status = 0;
	set_status = 3;
  
  // Number of points to win a set
  IF (type_of_match == "Klassik")
  	points_to_win = CLASSIC_MATCH_POINTS_TO_WIN;
  ELSEIF (type_of_match == "Vorteil")
  	points_to_win = ADVANTAGE_MATCH_POINTS_TO_WIN;
  ELSEIF (type_of_match == "Aufschlag")
  	points_to_win = REBOUND_MATCH_POINTS_TO_WIN;
  END

	// Set score is reset for each player
	player_set_score[0] = 0;
	player_set_score[1] = 0;
	
	// At the beginning of a match, chance decides who serves
	init_player_to_serve = RAND(0, 1);
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : SetInit
 *
 *	Variable initialization at the beginning of a set.
 *
 *  PARAMS:
 *			none
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION SetInit()
BEGIN
	// The screens fades out / in
	fade_off();
	fade_on();

	// A new set has begun
	set_status = 0;

	// Game score is reset for each player
	player_game_score[0] = 0;
	player_game_score[1] = 0;
	
	// At the beginning of a set, the player who serves is not the player who served
	// in the previous set
	IF (init_player_to_serve == 0)
		init_player_to_serve = 1;
	ELSE
		init_player_to_serve = 0;
	END
	player_to_serve = init_player_to_serve; 
	
	// We reset bullet-time gauge values
	bullet_gauge[0] = BULLET_TIME_GAUGE_INIT;
	bullet_gauge[1] = BULLET_TIME_GAUGE_INIT;
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : GameInit
 *
 *	Variable initialization at the beginning of a game.
 *
 *  PARAMS:
 *			none
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION GameInit()
BEGIN
	// A new game has begun
	game_status = 0;
	
	// Rebound score is reset for each player
	player_rebound_score[0] = 0;
	player_rebound_score[1] = 0;
	
	// - Position and speed of player 1
	player_x[0] = 2.0 * PLAYER_SIZE;
	player_y[0] = (Y_SIZE - GROUND_SIZE) * 1.0;
	player_vx[0] = 0.0;
	player_vy[0] = 0.0;
	player_jump[0] = 0;
	
	// - Bullet-time init for player 1
	bullet_time_key_pressed[0] = 0;
	StopBulletTime(0, 0);
	
	// - Position and speed of player 2 / CPU
	player_x[1] = (X_SIZE - 2.0 * PLAYER_SIZE) * 1.0;
	player_y[1] = (Y_SIZE - GROUND_SIZE) * 1.0;
	player_vx[1] = 0.0;
	player_vy[1] = 0.0;
	player_jump[1] = 0;
	
	// - Bullet-time init for player 2 / CPU
	bullet_time_key_pressed[1] = 0;
	StopBulletTime(1, 0);
	cpu_bullet_time_trigger = 0;
	
	// - CPU type of serve
	IF (player_to_serve == 0 OR type_player_two == "Mensch")
		com_serve = 0;
	ELSE
		com_serve = RAND(1, 4);
	END
	
	// - Position (depending on who is serving) and speed of the ball
	ball_x = player_x[player_to_serve] * 1.0;
	ball_y = (player_y[player_to_serve] - 2.0 * PLAYER_SIZE) * 1.0;
	ball_vx = 0.0;
	ball_vy = -50.0 * GRAVITY;
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : EndGame
 *
 *	End of a game. We stop bullet-time if necessary, and we update scores.
 *
 *  PARAMS:
 *			none
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION EndGame()
BEGIN
	// We stop bullet-time mode for each player if necessary
	IF (bullet_time_mode == 1)
		IF (bullet_time[0] == 1)
			StopBulletTime(0, 0);
		END
		IF (bullet_time[1] == 1)
			StopBulletTime(1, 0);
		END
	END
	
	// We increase the winner's score, and we indicate that he will serve
	// in the next game (if the set / match is not over) 
	IF (game_status == 1)
		// Player 1 won the game
		IF (type_of_match <> "Vorteil" OR (type_of_match == "Vorteil" AND player_to_serve == 0))
			// We increase the points only if the match is not of type Advantage,
			// or if the match is of type Advantage and the player had the service
			player_game_score[0] ++;
		END
		player_to_serve = 0;
		
		IF (player_game_score[0] == points_to_win)
			// Player 1 won the set
			player_set_score[0] ++;
			set_status = 1;
			
			IF (player_set_score[0] == sets_to_win)
				// Player 1 won the match
				match_status = 1;
			END
		END
	ELSE
		// Player 2 won the game
		IF (type_of_match <> "Vorteil" OR (type_of_match == "Vorteil" AND player_to_serve == 1))
			// We increase the points only if the match is not of type Advantage,
			// or if the match is of type Advantage and the player had the service
			player_game_score[1] ++;
		END
		player_to_serve = 1;
		
		IF (player_game_score[1] == points_to_win)
			// Player 2 won the set
			player_set_score[1] ++;
			set_status = 2;
			
			IF (player_set_score[1] == sets_to_win)
				// Player 2 won the match
				match_status = 2;
			END
		END
	END
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : ShowScore
 *
 *	Draws the score at the top of the screen. Depending on the game type, the following
 *	scores are displayed or hidden : sets, points, rebounds. In Advantage Match, "Adv" 
 *	is showed at the left or the right of the screen to know who serves for the point.
 *
 *  PARAMS:
 *			none
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION ShowScore()

PRIVATE
INT game_score_y;		// y position of the game score
INT rebound_score_y;	// y position of the rebound score

BEGIN
	// We display the static texts in green
	SET_TEXT_COLOR(207);
	game_score_y = 4;
	rebound_score_y = 14;
	IF (sets_to_win > 1)
		// We show the sets only if at least 2 sets are needed to win
		WRITE(0, X_SIZE / 2 - 15, 5, 0, "Sets");
		game_score_y = 14;
		rebound_score_y = 23;
	END
	WRITE(0, X_SIZE / 2 - 20, game_score_y, 0, "Punkte");
	IF (type_of_match == "Aufschlag")
		// We show the sets only if at least 2 sets are needed to win
		WRITE(0, X_SIZE / 2 - 30, rebound_score_y, 0, "Aufschläge");
	END
	
	// We display the scores in white for player 1
	SET_TEXT_COLOR(255);
	IF (sets_to_win > 1)
		// We show the sets only if at least 2 sets are needed to win 
		WRITE_INT(0, X_SIZE / 2 - 80, 5, 0, &player_set_score[0]);
		WRITE(0, X_SIZE / 2 - 65, 5, 0, "/");
		WRITE_INT(0, X_SIZE / 2 - 50, 5, 0, &sets_to_win);
	END
	WRITE_INT(0, X_SIZE / 2 - 80, game_score_y, 0, &player_game_score[0]);
	WRITE(0, X_SIZE / 2 - 65, game_score_y, 0, "/");
	WRITE_INT(0, X_SIZE / 2 - 50, game_score_y, 0, &points_to_win);
	IF (type_of_match == "Aufschlag")
		WRITE_INT(0, X_SIZE / 2 - 80, rebound_score_y, 0, &player_rebound_score[0]);
		WRITE(0, X_SIZE / 2 - 65, rebound_score_y, 0, "/");
		WRITE(0, X_SIZE / 2 - 50, rebound_score_y, 0, REBOUNDS_TO_END_GAME);
	END
  
  // We display the scores in white for player 2
	IF (sets_to_win > 1)
		// We show the sets only if at least 2 sets are needed to win 
		WRITE_INT(0, X_SIZE / 2 + 39, 5, 0, &player_set_score[1]);
		WRITE(0, X_SIZE / 2 + 54, 5, 0, "/");
		WRITE_INT(0, X_SIZE / 2 + 69, 5, 0, &sets_to_win);
	END
	WRITE_INT(0, X_SIZE / 2 + 39, game_score_y, 0, &player_game_score[1]);
	WRITE(0, X_SIZE / 2 + 54, game_score_y, 0, "/");
	WRITE_INT(0, X_SIZE / 2 + 69, game_score_y, 0, &points_to_win);
	IF (type_of_match == "Aufschlag")
		WRITE_INT(0, X_SIZE / 2 + 39, rebound_score_y, 0, &player_rebound_score[1]);
		WRITE(0, X_SIZE / 2 + 54, rebound_score_y, 0, "/");
		WRITE(0, X_SIZE / 2 + 69, rebound_score_y, 0, REBOUNDS_TO_END_GAME);
	END
	
	// We display "Adv" for the player who serves in Advantage Match
	IF (type_of_match == "Vorteil")
		SET_TEXT_COLOR(207);
		IF (player_to_serve == 0)
			WRITE(0, 10, 10, 0, "adv");
		ELSE
			WRITE(0, X_SIZE - 30, 10, 0, "adv");
		END
	END
END 
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : InitBulletTimeGauge
 *
 *	Draws the bullet-time gauge at the bottom of the screen for player 1 or 2. Called at
 *	the beginning of a game.
 *
 *  PARAMS:
 *			none
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION InitBulletTimeGauge()

PRIVATE
INT layout_x;			// x position of the bullet-time gauge
INT layout_y;			// y position of the bullet-time gauge
INT player_index;	// 0 for 1P or 1 for 2P

BEGIN
	layout_y = Y_SIZE - GROUND_SIZE / 2;
	
	FOR (player_index = 0 ; player_index <= 1 ; player_index ++)
		// We initialize the gauge position depending on the specified player
		IF (player_index == 0)
			layout_x = 35;
		ELSE
			layout_x = X_SIZE - 35;
		END
		
		// "init" phase
		
		// We draw the gauge in white
		SET_TEXT_COLOR(255);
		text_gauge_value[player_index] = WRITE_INT(0, layout_x - 14, layout_y - 3, 0, &bullet_gauge_display[player_index]);
		text_gauge_percent[player_index] = WRITE(0, layout_x + 10, layout_y - 3, 0, "%");
		text_bullet_time[player_index] = -1;
		
		// We draw the gray gauge out of the screen (invisible)
		SET_TEXT_COLOR(182);
		text_gauge_value_gray[player_index] = WRITE_INT(0, layout_x - 14, Y_SIZE + 30, 0, &bullet_gauge_display[player_index]);
		text_gauge_percent_gray[player_index] = WRITE(0, layout_x + 10, Y_SIZE + 30, 0, "%");
    
    // We draw "Bullet-Time !" texts out of the screen (invisible)
		SET_TEXT_COLOR(207);
		IF (player_id == 0)
			text_bullet_time[player_index] = WRITE(0, layout_x + 30, Y_SIZE + 30, 0, "Bullet Time !");
		ELSE
			text_bullet_time[player_index] = WRITE(0, layout_x - 105, Y_SIZE + 30, 0, "Bullet Time !");
		END
		
		modify_text_gauge[player_index] = 0;
		activate_bt_text[player_index] = 0;
	END
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : RedrawBulletTimeGauge
 *
 *	It draws the "Bullet-time !" text when bullet-time is activated, and it changes the gauge
 *	color (white or gray). Called at each frame to refresh display.
 *
 *  PARAMS:
 *			none
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION RedrawBulletTimeGauge()

PRIVATE
INT layout_x;			// x position of the bullet-time gauge
INT layout_y;			// y position of the bullet-time gauge
INT player_index;	// 0 for 1P or 1 for 2P

BEGIN
	layout_y = Y_SIZE - GROUND_SIZE / 2;
	//MOVE_TEXT(moving_text_id, X_SIZE + 10 - text_movement, Y_SIZE / 3.0);
	FOR (player_index = 0 ; player_index <= 1 ; player_index ++)	
		// We initialize the gauge position depending on the specified player
		IF (player_index == 0)
			layout_x = 35;
		ELSE
			layout_x = X_SIZE - 35;
		END
		
		// "redraw" phase  
		IF (modify_text_gauge[player_index] == 0 AND bullet_time_key_pressed[player_index] > 0)
			// Bullet-time recovery time is not yet elapsed
						
			// - We switch white and gray gauges : gray is visible
			MOVE_TEXT(text_gauge_value_gray[player_index], layout_x - 14, layout_y - 3);
			MOVE_TEXT(text_gauge_percent_gray[player_index], layout_x + 10, layout_y - 3);
			MOVE_TEXT(text_gauge_value[player_index], layout_x - 14, Y_SIZE + 30);
			MOVE_TEXT(text_gauge_percent[player_index], layout_x + 10, Y_SIZE + 30);
			
			modify_text_gauge[player_index] = 1;
		ELSEIF (modify_text_gauge[player_index] == 1 AND bullet_time_key_pressed[player_index] == 0)
			// Bullet-time recovery time has just been reached			
		  			
			// - We switch white and gray gauges : white is visible
			MOVE_TEXT(text_gauge_value[player_index], layout_x - 14, layout_y - 3);
			MOVE_TEXT(text_gauge_percent[player_index], layout_x + 10, layout_y - 3);
			MOVE_TEXT(text_gauge_value_gray[player_index], layout_x - 14, Y_SIZE + 30);
			MOVE_TEXT(text_gauge_percent_gray[player_index], layout_x + 10, Y_SIZE + 30);
			
			modify_text_gauge[player_index] = 0;
		END
		
		IF (activate_bt_text[player_index] == 0 AND bullet_time[player_index] == 1)	
			// Bullet-time is activated and no "Bullet-time !" text is displayed
			
			// We show the "Bullet-time !" text in green
			IF (player_index == 0)
				MOVE_TEXT(text_bullet_time[player_index], layout_x + 30, layout_y - 3);
			ELSE
				MOVE_TEXT(text_bullet_time[player_index], layout_x - 105, layout_y - 3);
			END
			
			activate_bt_text[player_index] = 1;
		ELSEIF (activate_bt_text[player_index] == 1 AND bullet_time[player_index] == 0)
			// Bullet-time is deactivated and "Bullet-time !" text is displayed
			
			// We hide the "Bullet-time !" text
			IF (player_index == 0)
				MOVE_TEXT(text_bullet_time[player_index], layout_x + 30, Y_SIZE + 30);
			ELSE
				MOVE_TEXT(text_bullet_time[player_index], layout_x - 105, Y_SIZE + 30);
			END
			
			activate_bt_text[player_index] = 0;
		END
	END
END
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : StartBulletTime
 *
 *  Starts bullet-time mode for the specified player.
 *
 *  PARAMS:
 *      int player_id : 0 = player 1 starts his bullet-time ; 0 = player 2 starts his bullet-time
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION StartBulletTime(int player_id)

BEGIN
	// We activate bullet-time mode for the player
	bullet_time[player_id] = 1;
	
	// We play the starting bullet-time sound
	PlaySound(4);

	return;
END 
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : StopBulletTime
 *
 *  Stops bullet-time mode for the specified player.
 *
 *  PARAMS:
 *      int player_id : 0 = player 1 stops his bullet-time ; 0 = player 2 stops his bullet-time
 *      int play_sound : 0 = don't play bullet-time sound effect ; 1 = play bullet-time sound effect
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION StopBulletTime(int player_id, int play_sound)

PRIVATE
INT shadow_id;	// ID of the shadow (between 0 and 20)

BEGIN
	// We deactivate bullet-time mode for the player
	bullet_time[player_id] = 0;
	
	// We reset the shadows positions for the next bullet-time
	FOR (shadow_id = 0; shadow_id <= 20 ; shadow_id ++)
		ball_x_shadow[shadow_id] = -1.0;
		ball_y_shadow[shadow_id] = -1.0;
		player_one_x_shadow[shadow_id] = -1.0;
		player_one_y_shadow[shadow_id] = -1.0;
		player_two_x_shadow[shadow_id] = -1.0;
		player_two_y_shadow[shadow_id] = -1.0;
	END
  
  IF (play_sound == 1)
  	// We play the ending bullet-time sound if specified
		PlaySound(5);
	END
	
	return;
END              
/* --------------------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------------------
 *  FUNCTION : DrawBulletTimeShadow
 *
 *  Draws the player / ball shadow effect when bullet time is activated.
 *
 *  PARAMS:
 *      none
 *
 *  RETURN VALUE: 
 *      none
 *
 */
FUNCTION DrawBulletTimeShadow()

PRIVATE
INT shadow_id;	// ID of the shadow (between 0 and 20)

BEGIN
	// We only draw the shadow effect if a player has activated his bullet-time mode
	IF (bullet_time[0] == 1 OR bullet_time[1] == 1)
		
		//  /------------------\
		// < Ball shadow effect >
	  //  \------------------/
	  
		FOR (shadow_id = 0; shadow_id <= 20 ; shadow_id += 2)
			IF (ball_x_shadow[shadow_id] > -1.0)
				// For each shadow ball coordinates already calculated since bullet-time mode
				// activation, we draw it on the screen
				PUT(0, 7, ball_x_shadow[shadow_id], ball_y_shadow[shadow_id]);
			END
		END
		
		IF (pause_text_id == -1)
			// After having drawn the shadows, we shift the shadows positions for next move :
			// - current position becomes the new position n°0
			// - previous position n°0 becomes the new position n°1
			// - ...
			// - previous position n°19 becomes the new position n°20
			ball_x_shadow[0] = ball_x;
			ball_y_shadow[0] = ball_y;
			FOR (shadow_id = 20; shadow_id >= 1 ; shadow_id --)
				ball_x_shadow[shadow_id] = ball_x_shadow[shadow_id - 1];
				ball_y_shadow[shadow_id] = ball_y_shadow[shadow_id - 1];
			END
		END
		
		IF (bullet_time[0] == 1 AND bullet_time[1] == 0)
		
    	//  /----------------------\
			// < Player 2 shadow effect >
			//  \----------------------/
			
			// (player 1 bullet-time mode is activated and player 2 bullet-time mode is deactivated)
			
			FOR (shadow_id = 0; shadow_id <= 20 ; shadow_id += 2)
				IF (player_two_x_shadow[shadow_id] > -1.0)
					// For each shadow player coordinates already calculated since bullet-time mode
					// activation, we draw it on the screen
					IF (type_player_two == "human")
						PUT(0, 9, player_two_x_shadow[shadow_id], player_two_y_shadow[shadow_id]);
					ELSE
						PUT(0, 10, player_two_x_shadow[shadow_id], player_two_y_shadow[shadow_id]);
					END
				END
			END
			
			IF (pause_text_id == -1)
				// After having drawn the shadows, we shift the shadows positions for next move
				player_two_x_shadow[0] = player_x[1];
				player_two_y_shadow[0] = player_y[1];
				FOR (shadow_id = 20; shadow_id >= 1 ; shadow_id --)
					player_two_x_shadow[shadow_id] = player_two_x_shadow[shadow_id - 1];
					player_two_y_shadow[shadow_id] = player_two_y_shadow[shadow_id - 1];
				END
			END
		ELSEIF (bullet_time[0] == 0 AND bullet_time[1] == 1)
		
    	//  /----------------------\
			// < Player 1 shadow effect >
			//  \----------------------/
			
			// (player 1 bullet-time mode is deactivated and player 2 bullet-time mode is activated)
		
			FOR (shadow_id = 0; shadow_id <= 20 ; shadow_id += 2)
				// For each shadow ball coordinates already calculated since bullet-time mode
				// activation, we draw it on the screen
				IF (player_one_x_shadow[shadow_id] > -1.0)
					PUT(0, 8, player_one_x_shadow[shadow_id], player_one_y_shadow[shadow_id]);
				END
			END
			
			IF (pause_text_id == -1)
				// After having drawn the shadows, we shift the shadows positions for next move :
				player_one_x_shadow[0] = player_x[0];
				player_one_y_shadow[0] = player_y[0];
				FOR (shadow_id = 20; shadow_id >= 1 ; shadow_id --)
					player_one_x_shadow[shadow_id] = player_one_x_shadow[shadow_id - 1];
					player_one_y_shadow[shadow_id] = player_one_y_shadow[shadow_id - 1];
				END
			END
		END
	END
	
	return;
END
/* --------------------------------------------------------------------------------------- */