Scripts and settings for profiling
[tennix.git] / game.c
blobc47bd771197c3ae674a30e0a6830822bd80d0ecc
2 /**
4 * Tennix! SDL Port
5 * Copyright (C) 2003, 2007, 2008 Thomas Perl <thp@perli.net>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
22 **/
24 #include <stdio.h>
25 #include <math.h>
27 #include "tennix.h"
28 #include "game.h"
29 #include "graphics.h"
30 #include "input.h"
31 #include "sound.h"
34 GameState *gamestate_new() {
35 int x, y, z;
36 GameState *s;
38 GameState template = {
39 { 0, 0, 0.0, 0.0, 0.0 },
40 { 0, 0, 0, 0, 0 },
41 { GAME_X_MIN-RACKET_X_MID*2, GAME_Y_MID, 0, 0, 1, DESIRE_NORMAL, PLAYER_TYPE_HUMAN, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true },
42 { GAME_X_MAX+RACKET_X_MID*2, GAME_Y_MID, 0, 0, 0, DESIRE_NORMAL, PLAYER_TYPE_AI, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true },
46 true,
47 "welcome to tennix " VERSION,
48 { 0 },
49 { 0 },
50 REFEREE_NORMAL,
52 WINNER_NONE,
53 false,
54 GR_CTT_GRASS,
55 -1,
56 { 0 },
58 false,
59 { { { 0 } } },
60 0.0,
61 SOUND_MAX,
62 0.0,
63 0.0,
67 false,
73 s = (GameState*)malloc(sizeof(GameState));
74 if (s == NULL) abort();
76 memcpy(s, &template, sizeof(GameState));
78 game_setup_serve(s);
80 /* smoothen n-gram */
81 for( x = 0; x<NGRAM_STEPS; x++) {
82 for( y = 0; y<NGRAM_STEPS; y++) {
83 for( z = 0; z<NGRAM_STEPS; z++) {
84 s->ngram[x][y][z] = 1;
89 return s;
93 void gameloop(GameState *s) {
94 strcpy(s->game_score_str, format_game(s));
95 strcpy(s->sets_score_str, format_sets(s));
97 Uint32 ot = SDL_GetTicks();
98 Uint32 nt;
99 Uint32 dt = GAME_TICKS;
100 Uint32 diff;
101 Uint32 accumulator = 0;
102 bool quit = false;
104 #ifdef ENABLE_FPS_LIMIT
105 Uint32 ft, frames; /* frame timer and frames */
106 #endif
108 if (s->rain > 0) {
109 play_sample_background(SOUND_RAIN);
111 play_sample_loop(SOUND_AUDIENCE);
112 /* Reset the court type, so it is redrawn on first display */
113 s->old_court_type = -1;
115 #ifdef ENABLE_FPS_LIMIT
116 frames = 0;
117 ft = SDL_GetTicks();
118 #endif
119 while( !quit) {
120 nt = SDL_GetTicks();
121 diff = nt-ot;
122 if( diff > 2000) {
123 diff = 0;
126 accumulator += diff;
127 ot = nt;
129 while( accumulator >= dt) {
130 quit = step(s);
131 s->time += dt;
132 s->windtime += s->wind*dt;
133 accumulator -= dt;
135 if( s->was_stopped) {
136 ot = SDL_GetTicks();
137 s->was_stopped = false;
140 if (s->timelimit != 0 && s->time >= s->timelimit) {
141 quit = 1;
144 #ifdef ENABLE_FPS_LIMIT
145 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
146 SDL_Delay(10);
148 frames++;
149 #endif
151 render(s);
154 clear_screen();
155 store_screen();
157 stop_sample(SOUND_AUDIENCE);
158 stop_sample(SOUND_RAIN);
161 bool step( GameState* s) {
162 Uint8 *keys;
163 SDL_Event e;
165 if( get_phase( s) < 1.0) {
166 if( !s->ground.jump) {
167 s->play_sound = SOUND_GROUND;
169 if( IS_OUT_Y( s->ball.y)) {
170 /* out - responsibilities stay the same */
171 s->status = "out!";
172 s->play_sound = SOUND_OUT;
173 s->referee = REFEREE_OUT;
174 } else {
175 /* not out - responsibilities change */
176 s->player1.responsible = !(s->player2.responsible = !s->player2.responsible);
177 s->status = format_status( s);
178 s->referee = REFEREE_NORMAL;
181 s->ground.jump = 3;
182 s->ground.x = s->ball.x;
183 s->ground.y = s->ball.y;
184 } else {
185 if( s->ground.jump && !(s->time%5)) s->ground.jump--;
188 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
189 if( IS_OFFSCREEN( s->ball.x, s->ball.y)) {
190 s->player1_serves = s->player1.responsible;
192 score_game( s, s->player2.responsible);
193 strcpy( s->game_score_str, format_game( s));
194 strcpy( s->sets_score_str, format_sets( s));
196 if( s->player1.responsible) {
197 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
198 s->status = "computer scores";
199 } else {
200 s->status = "player 2 scores";
202 s->referee = REFEREE_PLAYER2;
203 } else {
204 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
205 s->status = "player scores";
206 } else {
207 s->status = "player 1 scores";
209 s->referee = REFEREE_PLAYER1;
212 game_setup_serve( s);
213 s->play_sound = SOUND_APPLAUSE;
214 SDL_Delay( 500);
215 s->was_stopped = true;
216 s->history_size = 0;
217 s->history_is_locked = 0;
218 s->ngram_prediction = 0.0;
219 #ifdef DEBUG
220 printf( "-- game reset --\n");
221 #endif
224 if( IS_OUT_X(s->ball.x)) {
225 if( !s->history_is_locked && s->referee != REFEREE_OUT) {
226 s->history[s->history_size] = (int)(NGRAM_STEPS*s->ball.y/HEIGHT);
227 s->history_size++;
228 if( s->history_size == 3) {
229 s->ngram[s->history[0]][s->history[1]][s->history[2]] += 10;
230 #ifdef DEBUG
231 printf( "history: %d, %d, %d\n", s->history[0], s->history[1], s->history[2]);
232 #endif
233 s->ngram_prediction = ngram_predictor( s);
234 s->history[0] = s->history[1];
235 s->history[1] = s->history[2];
236 s->history_size--;
238 s->history_is_locked = true;
240 if( s->ball.move_x <= 0 && IS_NEAR_X( s->player1.x, s->ball.x) && IS_NEAR_Y( s->player1.y, s->ball.y) && s->player1.state && s->referee != REFEREE_OUT) {
241 s->ball.x = GAME_X_MIN;
242 if( s->player1.state == PLAYER_STATE_MAX) {
243 s->ball.move_x = PLAYER_POWERSHOT;
244 } else {
245 s->ball.move_x = 2.5 + 2.0*s->player1.state/PLAYER_STATE_MAX;
247 s->ball.move_y = get_move_y( s, 1);
248 s->player2.responsible = !(s->player1.responsible = 1);
249 s->ball.jump += 1.0-2.0*(s->player1.state<5);
250 s->play_sound = SOUND_RACKET;
251 pan_sample(SOUND_RACKET, 0.4);
252 } else if( s->ball.move_x >= 0 && IS_NEAR_X( s->player2.x, s->ball.x) && IS_NEAR_Y( s->player2.y, s->ball.y) && s->player2.state && s->referee != REFEREE_OUT) {
253 s->ball.x = GAME_X_MAX;
254 if( s->player2.state == PLAYER_STATE_MAX) {
255 s->ball.move_x = -PLAYER_POWERSHOT;
256 } else {
257 s->ball.move_x = -(2.5 + 2.0*s->player2.state/PLAYER_STATE_MAX);
259 s->ball.move_y = get_move_y( s, 2);
260 s->player1.responsible = !(s->player2.responsible = 1);
261 s->ball.jump += 1.0-2.0*(s->player2.state<5);
262 s->play_sound = SOUND_RACKET;
263 pan_sample(SOUND_RACKET, 0.6);
266 } else {
267 s->history_is_locked = false;
270 SDL_PollEvent( &e);
271 keys = SDL_GetKeyState( NULL);
272 switch(e.type) {
273 case SDL_JOYAXISMOTION:
274 if (e.jaxis.axis == JOYSTICK_Y_AXIS) {
275 s->joystick_y = JOYSTICK_PERCENTIZE(e.jaxis.value);
276 } else if (e.jaxis.axis == JOYSTICK_X_AXIS) {
277 s->joystick_x = JOYSTICK_PERCENTIZE(e.jaxis.value);
279 break;
280 case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN:
281 if (e.jbutton.button == JOYSTICK_BUTTON_A) {
282 s->joystick_a = (e.jbutton.state == SDL_PRESSED);
284 break;
287 if( s->time%50==0) {
289 * Maemo keys:
290 * F7 = "Decrease" key
291 * F8 = "Increase" key
293 if (keys['c'] || keys[SDLK_F8]) {
294 s->court_type++;
295 } else if (keys[SDLK_F7]) {
296 s->court_type--;
298 if (keys['r']) {
299 if (s->rain == 0) {
300 play_sample_background(SOUND_RAIN);
302 s->rain += 10;
304 if (keys['t']) {
305 s->fog++;
307 if (keys['1']) {
308 s->wind++;
310 if (keys['2']) {
311 s->wind--;
313 if (keys['n']) {
314 s->night = 1 - s->night;
316 if( s->court_type > GR_CTT_LAST) {
317 s->court_type = GR_CTT_FIRST;
318 } else if (s->court_type < GR_CTT_FIRST) {
319 s->court_type = GR_CTT_LAST;
323 if( !is_fading() && !s->is_over) {
324 if( s->player1.type == PLAYER_TYPE_HUMAN) {
325 input_human( &s->player1,
326 keys['w'] || keys[SDLK_UP] || s->joystick_y < -JOYSTICK_TRESHOLD,
327 keys['s'] || keys[SDLK_DOWN] || s->joystick_y > JOYSTICK_TRESHOLD,
328 keys['d'] || keys[SDLK_SPACE] || keys[SDLK_LCTRL] || keys[SDLK_RETURN] || s->joystick_a,
329 #ifdef ENABLE_MOUSE
330 true,
331 #else
332 false,
333 #endif
335 } else {
336 input_ai( &s->player1, &s->ball, &s->player2, s);
339 if( s->player2.type == PLAYER_TYPE_HUMAN) {
340 input_human( &s->player2, keys['o'], keys['l'], keys['k'], false, s);
341 } else {
342 input_ai( &s->player2, &s->ball, &s->player1, s);
346 /* Maemo: The "F6" button is the "Fullscreen" button */
347 if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);
348 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
350 /* Maemo: The "F4" button is the "Open menu" button */
351 if( keys['p'] || keys[SDLK_F4]) {
352 while( keys['p'] || keys[SDLK_F4]) {
353 SDL_PollEvent( &e);
354 keys = SDL_GetKeyState( NULL);
355 SDL_Delay( 10);
357 while( (keys['p'] || keys[SDLK_F4]) == 0) {
358 SDL_PollEvent( &e);
359 keys = SDL_GetKeyState( NULL);
360 SDL_Delay( 10);
362 while( keys['p'] || keys[SDLK_F4]) {
363 SDL_PollEvent( &e);
364 keys = SDL_GetKeyState( NULL);
365 SDL_Delay( 10);
367 s->was_stopped = true;
370 if( keys[SDLK_ESCAPE] || keys['q']) return true;
372 limit_value( &s->player1.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
373 limit_value( &s->player2.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
374 limit_value( &s->ball.jump, BALL_JUMP_MIN, BALL_JUMP_MAX);
376 /* Update ball_dest for debugging purposes */
377 get_move_y(s, 1);
378 get_move_y(s, 2);
380 if (s->ball.move_x > 0 && s->wind > 0) {
381 s->ball.x += fabsf(s->wind)/2;
382 } else if (s->ball.move_x < 0 && s->wind < 0) {
383 s->ball.x -= fabsf(s->wind)/2;
386 s->ball.x += s->ball.move_x;
387 s->ball.y += s->ball.move_y;
389 if(s->player1.state) s->player1.state--;
390 if(s->player2.state) s->player2.state--;
392 return false;
395 void render( GameState* s) {
396 int i, x, y;
397 #ifdef EXTENDED_REFEREE
398 int t=1000;
399 #endif
400 if (s->play_sound != SOUND_MAX) {
401 play_sample(s->play_sound);
402 s->play_sound = SOUND_MAX;
404 if( s->winner != WINNER_NONE) {
405 if( !s->is_over) {
406 start_fade();
407 s->is_over = true;
409 clear_screen();
410 store_screen();
411 show_sprite( GR_RACKET, 2*(s->winner-1), 4, WIDTH/2 - get_image_width( GR_RACKET)/8, HEIGHT/2 - get_image_height( GR_RACKET), 255);
412 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
413 font_draw_string( GR_DKC2_FONT, s->game_score_str, (WIDTH-font_get_string_width( GR_DKC2_FONT, s->game_score_str))/2, HEIGHT/2 + 30, s->time/20, ANIMATION_WAVE | ANIMATION_BUNGEE);
414 updatescr();
415 return;
417 if (s->old_court_type != s->court_type) {
418 clear_screen();
419 fill_image(s->court_type, 120, 120, 400, 250);
420 show_image(GR_COURT, 0, 0, 255);
421 s->old_court_type = s->court_type;
422 store_screen();
424 #ifdef EXTENDED_REFEREE
425 switch (s->referee) {
426 case REFEREE_NORMAL:
427 t = 1000;
428 break;
429 case REFEREE_OUT:
430 t = 200;
431 break;
432 case REFEREE_PLAYER1:
433 case REFEREE_PLAYER2:
434 t = 400;
435 break;
437 t = (s->time/t)%4;
438 switch (t) {
439 case 0:
440 t=0;
441 break;
442 case 1:
443 t=1;
444 break;
445 case 2:
446 t=0;
447 break;
448 case 3:
449 t=2;
450 break;
452 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
453 #else
454 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
455 #endif
456 if (voice_finished_flag == 0) {
457 show_sprite(GR_TALK, (s->time/150)%2, 2, 280, 45, 255);
459 show_image( GR_SHADOW, s->ball.x-BALL_X_MID, s->ball.y + get_phase( s) - BALL_Y_MID, 255);
461 show_sprite( GR_RACKET, (!s->player1.state), 4, s->player1.x-RACKET_X_MID, s->player1.y-RACKET_Y_MID, 255);
462 show_sprite( GR_RACKET, (!s->player2.state)+2, 4, s->player2.x-RACKET_X_MID, s->player2.y-RACKET_Y_MID, 255);
464 if( s->ball.move_x > 0) {
465 show_sprite( GR_BALL, (s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
466 } else if( s->ball.move_x < 0) {
467 show_sprite( GR_BALL, 3-(s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
468 } else {
469 show_sprite( GR_BALL, 0, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
472 /* Player 1's mouse rectangle */
473 if (!(s->player1.mouse_locked)) {
474 rectangle(s->player1.x-2+5, s->player1.mouse_y-2, 4, 4, 255, 255, 255);
475 rectangle(s->player1.x-1+5, s->player1.mouse_y-1, 2, 2, 0, 0, 0);
478 font_draw_string( GR_DKC2_FONT, s->game_score_str, 14, 14, 0, ANIMATION_NONE);
479 font_draw_string( GR_DKC2_FONT, s->sets_score_str, (WIDTH-font_get_string_width( GR_DKC2_FONT, s->sets_score_str))-14, 14, 0, ANIMATION_NONE);
481 font_draw_string( GR_DKC2_FONT, s->status, (WIDTH-font_get_string_width( GR_DKC2_FONT, s->status))/2, HEIGHT-50, s->time/30, ANIMATION_WAVE);
483 for (i=0; i<s->rain; i++) {
484 x = rand()%WIDTH;
485 y = rand()%HEIGHT;
486 draw_line_faded(x, y, x+10+s->wind*5, y+30, 0, 0, 255, 100, 200, 255);
488 if (s->rain) {
490 * Cheap-ish update of the whole screen. This can
491 * probably be optimized.
493 update_rect(0, 0, WIDTH, HEIGHT);
496 #ifdef DEBUG
497 line_horiz( s->player1.y, 255, 0, 0);
498 line_horiz( s->player2.y, 0, 0, 255);
499 line_horiz( s->ball.y, 0, 255, 0);
501 line_vert( s->player1.x, 255, 0, 0);
502 line_vert( s->player2.x, 0, 0, 255);
503 line_vert( s->ball.x, 0, 255, 0);
505 line_horiz( s->player1.ball_dest, 255, 0, 255);
506 line_horiz( s->player2.ball_dest, 0, 255, 255);
508 line_horiz( GAME_Y_MIN, 100, 100, 100);
509 line_horiz( GAME_Y_MAX, 100, 100, 100);
510 #endif
511 switch (s->fog) {
512 default:
513 case 4:
514 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150-s->windtime/200, 0);
515 case 3:
516 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100-s->windtime/150, 20);
517 case 2:
518 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180-s->windtime/180, 80);
519 case 1:
520 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200-s->windtime/100, 0);
521 case 0:
522 break;
524 if (s->night) {
525 show_image(GR_NIGHT, 0, 0, 255);
528 updatescr();
531 void limit_value( float* value, float min, float max) {
532 if( *value < min) {
533 *value = min;
534 } else if( *value > max) {
535 *value = max;
539 float get_phase( GameState* s) {
540 float pos, fract;
541 float x, min, max, direction;
543 x = s->ball.x;
544 min = GAME_X_MIN;
545 max = GAME_X_MAX;
546 direction = s->ball.move_x;
548 pos = (direction>0)?(1-GROUND_PHASE):(GROUND_PHASE);
550 fract = (x-min)/(max-min);
552 if( fract < pos) {
553 fract = fract/pos;
554 return fabsf( cosf(PI*fract/2))*PHASE_AMP*s->ball.jump;
555 } else {
556 fract = (pos-fract)/(1-pos);
557 return fabsf( sinf(PI*fract/2))*PHASE_AMP*s->ball.jump;
561 float get_move_y( GameState* s, unsigned char player) {
562 float pct, dest, x_len, y_len;
563 float py, by, pa, move_x;
565 py = (player==1)?(s->player1.y):(s->player2.y);
566 by = s->ball.y;
567 pa = RACKET_Y_MID*2;
568 move_x = s->ball.move_x;
570 /* -1.0 .. 1.0 for racket hit position */
571 pct = (by-py)/(pa/2);
572 limit_value( &pct, -1.0, 1.0);
574 /* Y destination for ball */
575 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
576 if( player == 1) {
577 s->player1.ball_dest = dest;
578 } else {
579 s->player2.ball_dest = dest;
582 /* lengths for the ball's journey */
583 if( player == 1) {
584 x_len = fabsf(GAME_X_MAX - s->ball.x);
585 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
586 } else {
587 x_len = s->ball.x - GAME_X_MIN;
588 y_len = by - dest + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
591 /* return the should-be value for move_y */
592 return (y_len*move_x)/(x_len);
595 void input_human( Player* player, bool up, bool down, bool hit, bool use_mouse, GameState* s) {
596 int diff = PLAYER_MOVE_Y;
597 int mb;
600 * Only use mouse control if the user isn't pressing any buttons
601 * this way, keyboard control still works when mouse control is
602 * enabled.
604 if (use_mouse && (down || up || hit)) {
606 * this is here so if the user decides to play
607 * with keyboard controls, we will lock the
608 * mouse and disable displaying the mouse cursor
610 player->mouse_locked = true;
612 if (use_mouse && !down && !up && !hit) {
613 mb = SDL_GetMouseState(&(player->mouse_x), &(player->mouse_y));
614 if (mb&SDL_BUTTON(SDL_BUTTON_LEFT)) {
615 if (player->mouse_y < player->y) {
616 down = false;
617 up = true;
618 diff = (player->y-player->mouse_y<diff)?(player->y-player->mouse_y):(diff);
619 } else if (player->mouse_y > player->y) {
620 up = false;
621 down = true;
622 diff = (player->mouse_y-player->y<diff)?(player->mouse_y-player->y):(diff);
624 player->mouse_locked = false;
625 } else if (!player->mouse_locked) {
626 hit = true;
630 if (fabsf(s->joystick_y) > JOYSTICK_TRESHOLD) {
631 diff = PLAYER_MOVE_Y*fabsf(s->joystick_y)/40.0;
632 if (diff > PLAYER_MOVE_Y) {
633 diff = PLAYER_MOVE_Y;
637 if (up) {
638 player->y -= fminf(diff, diff*player->accelerate);
639 player->accelerate *= PLAYER_ACCEL_INCREASE;
640 } else if (down) {
641 player->y += fminf(diff, diff*player->accelerate);
642 player->accelerate *= PLAYER_ACCEL_INCREASE;
643 } else {
644 player->accelerate = PLAYER_ACCEL_DEFAULT;
647 if( hit) {
648 if( !player->state && !player->state_locked) {
649 player->state = PLAYER_STATE_MAX;
650 player->state_locked = true;
652 } else {
653 player->state_locked = false;
657 void input_ai( Player* player, Ball* ball, Player* opponent, GameState* s) {
658 float fact = 1.7;
659 float target;
661 if( fabsf( player->y - ball->y) > RACKET_Y_MID*5) {
662 fact = 3.5;
665 target = GAME_Y_MID + (opponent->ball_dest - GAME_Y_MID)/5;
667 if( player->responsible) {
668 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, ball->y)) {
669 if( player->y < ball->y) {
670 player->y += fmin( 2*fact, ball->y - player->y);
671 } else if( player->y > ball->y) {
672 player->y -= fmin( 2*fact, player->y - ball->y);
676 if( (ball->move_x != 0 || IS_NEAR_Y_AI( player->y, ball->y)) && IS_NEAR_X_AI( player->x, ball->x) && !player->state && rand()%4==0) {
677 player->state = PLAYER_STATE_MAX;
679 } else if( ball->move_x == 0) {
680 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
681 if( player->y < target) {
682 player->y += fmin( fact, (target-player->y)/40.0);
683 } else if( player->y > target) {
684 player->y -= fmin( fact, (player->y-target)/40.0);
687 } else if( s->ngram_prediction > 0.0) {
688 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
689 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
691 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
692 if( player->y < target) {
693 player->y += fmin( fact, (target-player->y)/40.0);
694 } else if( player->y > target) {
695 player->y -= fmin( fact, (player->y-target)/40.0);
698 } else {/*
699 if( player->desire == DESIRE_NORMAL) {
700 if( !IS_NEAR_Y_AI( player->y, target)) {
701 player->y += (target - player->y)/40.0;
707 void game_setup_serve( GameState* s) {
708 s->ball.jump = 7.5;
709 s->ball.y = GAME_Y_MID;
710 s->ball.move_x = 0.0;
711 s->ball.move_y = 0.0;
713 if( s->player1_serves) {
714 s->player1.responsible = true;
715 s->player1.ball_dest = 0.0;
716 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
717 } else {
718 s->player1.responsible = false;
719 s->player2.ball_dest = 0.0;
720 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
723 s->player2.responsible = !(s->player1.responsible);
726 float ngram_predictor( GameState* s) {
727 unsigned int count = 0;
728 unsigned long sum = 0;
729 int x, y, z;
730 float result;
732 if( s->history_size < 3) {
733 return 0.0;
736 x = s->history[1];
737 y = s->history[2];
739 for( z = 0; z<NGRAM_STEPS; z++) {
740 count += s->ngram[x][y][z];
741 sum += z * s->ngram[x][y][z];
744 result = ((float)(sum))/((float)(count));
745 #ifdef DEBUG
746 printf( "predicting next = %.2f\n", result);
747 #endif
749 return result;
752 void score_game( GameState* s, bool player1_scored) {
753 Player* winner = (player1_scored)?(&(s->player1)):(&(s->player2));
754 Player* loser = (player1_scored)?(&(s->player2)):(&(s->player1));
756 if( s->current_set >= SETS_TO_WIN*2-1) {
757 return;
760 winner->game++;
761 if( loser->game < winner->game-1) {
762 if( winner->game >= 4) {
763 winner->game = loser->game = 0;
764 winner->sets[s->current_set]++;
766 #ifdef HAVE_VOICE_FILES
767 /* speak the current score */
768 voice_say_list(4, VOICE_ZERO_IN + (s->player1.sets[s->current_set])*2, VOICE_TO, VOICE_ZERO_OUT + (s->player2.sets[s->current_set])*2, VOICE_IN_THE_FIRST_SET+s->current_set);
769 #endif
771 /* scoring the set.. */
772 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
773 winner->sets[s->current_set] == 7) {
774 s->current_set++;
775 s->winner = game_get_winner( s);
781 char* format_sets( GameState* s) {
782 static char sets[100];
783 static char tmp[100];
784 int i, max = s->current_set;
786 sets[0] = '\0';
788 if( s->winner != WINNER_NONE) {
789 max--;
791 for( i=0; i<=max; i++) {
792 sprintf( tmp, "%d:%d, ", s->player1.sets[i], s->player2.sets[i]);
793 strcat( sets, tmp);
796 sets[strlen(sets)-2] = '\0';
798 return sets;
801 char* format_game( GameState* s) {
802 static char game[100];
803 static const int game_scoring[] = { 0, 15, 30, 40 };
805 if( s->player1.game < 4 && s->player2.game < 4) {
806 #ifdef HAVE_VOICE_FILES
807 if (s->player1.game > 0 || s->player2.game > 0) {
808 if (s->player1.game == s->player2.game) {
809 voice_say_list(2, VOICE_LOVE_IN + 2*(s->player1.game), VOICE_ALL);
810 } else {
811 voice_say_list(2, VOICE_LOVE_IN + 2*(s->player1.game), VOICE_LOVE_OUT + 2*(s->player2.game));
814 #endif
815 sprintf( game, "%d - %d", game_scoring[s->player1.game], game_scoring[s->player2.game]);
816 } else if( s->player1.game > s->player2.game) {
817 #ifdef HAVE_VOICE_FILES
818 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
819 #endif
820 strcpy( game, "advantage player 1");
821 } else if( s->player1.game < s->player2.game) {
822 #ifdef HAVE_VOICE_FILES
823 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
824 #endif
825 strcpy( game, "advantage player 2");
826 } else {
827 #ifdef HAVE_VOICE_FILES
828 voice_say_list(1, VOICE_DEUCE);
829 #endif
830 strcpy( game, "deuce");
833 return game;
836 char* format_status( GameState* s) {
837 static char status[100];
838 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
840 sprintf( status, "%d:%d in %s set", s->player1.sets[s->current_set], s->player2.sets[s->current_set], set_names[s->current_set]);
842 return status;
845 int game_get_winner( GameState* s) {
846 int i;
847 int sets[2] = {0};
849 for( i=0; i<s->current_set; i++) {
850 if( s->player1.sets[i] > s->player2.sets[i]) {
851 sets[0]++;
852 } else {
853 sets[1]++;
857 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
858 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
860 return WINNER_NONE;