Fix net collision, referee and ball restitution
[tennix.git] / game.c
blobd840ba013d91934271556b60f17a6db81ab6ca41
2 /**
4 * Tennix! SDL Port
5 * Copyright (C) 2003, 2007, 2008, 2009 Thomas Perl <thp@thpinfo.com>
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 <stdlib.h>
26 #include <string.h>
27 #include <math.h>
28 #include <assert.h>
30 #include "tennix.h"
31 #include "game.h"
32 #include "graphics.h"
33 #include "input.h"
34 #include "sound.h"
37 GameState *gamestate_new() {
38 int x, y, z;
39 GameState *s;
41 GameState template = {
42 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6, false, -1, false },
44 { GAME_X_MIN-RACKET_X_MID*2, GAME_Y_MID, 0, 0, DESIRE_NORMAL, PLAYER_TYPE_HUMAN, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true },
45 { GAME_X_MAX+RACKET_X_MID*2, GAME_Y_MID, 0, 0, DESIRE_NORMAL, PLAYER_TYPE_AI, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true },
50 "welcome to tennix " VERSION,
51 { 0 },
52 { 0 },
53 REFEREE_NORMAL,
55 WINNER_NONE,
56 false,
57 GR_CTT_GRASS,
58 -1,
59 { 0 },
61 false,
62 { { { 0 } } },
63 0.0,
64 SOUND_MAX,
65 0.0,
66 0.0,
70 false,
74 false,
75 REFEREE_COUNT,
76 SCORE_UNDECIDED,
80 s = (GameState*)malloc(sizeof(GameState));
81 if (s == NULL) abort();
83 memcpy(s, &template, sizeof(GameState));
85 game_setup_serve(s);
87 /* smoothen n-gram */
88 for( x = 0; x<NGRAM_STEPS; x++) {
89 for( y = 0; y<NGRAM_STEPS; y++) {
90 for( z = 0; z<NGRAM_STEPS; z++) {
91 s->ngram[x][y][z] = 1;
96 return s;
100 void gameloop(GameState *s) {
101 Uint32 ot = SDL_GetTicks();
102 Uint32 nt;
103 Uint32 dt = GAME_TICKS;
104 Uint32 diff;
105 Uint32 accumulator = 0;
106 bool quit = false;
108 #ifdef ENABLE_FPS_LIMIT
109 Uint32 ft, frames; /* frame timer and frames */
110 #endif
112 strcpy(s->game_score_str, format_game(s));
113 strcpy(s->sets_score_str, format_sets(s));
114 s->text_changed = true;
116 if (s->rain > 0) {
117 play_sample_background(SOUND_RAIN);
119 play_sample_loop(SOUND_AUDIENCE);
120 /* Reset the court type, so it is redrawn on first display */
121 s->old_court_type = -1;
123 #ifdef ENABLE_FPS_LIMIT
124 frames = 0;
125 ft = SDL_GetTicks();
126 #endif
127 while( !quit) {
128 nt = SDL_GetTicks();
129 diff = nt-ot;
130 if( diff > 2000) {
131 diff = 0;
134 accumulator += diff;
135 ot = nt;
137 while( accumulator >= dt) {
138 quit = step(s);
139 s->time += dt;
140 s->windtime += s->wind*dt;
141 accumulator -= dt;
143 if( s->was_stopped) {
144 ot = SDL_GetTicks();
145 s->was_stopped = false;
148 if (s->timelimit != 0 && s->time >= s->timelimit) {
149 quit = 1;
152 #ifdef ENABLE_FPS_LIMIT
153 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
154 SDL_Delay(10);
156 frames++;
157 #endif
159 render(s);
162 clear_screen();
163 store_screen();
165 stop_sample(SOUND_AUDIENCE);
166 stop_sample(SOUND_RAIN);
169 bool step( GameState* s) {
170 Uint8 *keys;
171 SDL_Event e;
172 bool ground_event = false;
173 int p;
175 if (s->ball.z < 0) {
176 /* ground event */
177 ground_event = true;
179 s->referee = REFEREE_NORMAL;
181 /* bounce from the ground */
182 if (fabsf(s->ball.move_z) > 0.5) {
183 s->ball.move_z *= -s->ball.restitution;
184 } else {
185 s->ball.move_z = 0;
187 s->ball.z = 0;
189 /* only make the sound if it's visibly bouncing hard */
190 if (!IS_OFFSCREEN(s->ball.x, s->ball.y) && fabsf(s->ball.move_z) > .5) {
191 s->play_sound = SOUND_GROUND;
195 if (NET_COLLISION_BALL(s->ball)) {
196 /* the net blocks movement of the ball */
197 s->ball.move_x = 0;
198 s->ball.move_y = 0;
199 while (NET_COLLISION_BALL(s->ball)) {
200 /* make sure the ball appears OUTSIDE of the net */
201 if (s->ball.x >= (NET_X+NET_WIDTH/2)) {
202 s->ball.x += 1;
203 } else {
204 s->ball.x -= 1;
209 /* see if we have something to score */
210 if (s->score_event == SCORE_UNDECIDED) {
211 if (NET_COLLISION_BALL(s->ball)) {
212 /* the ball "fell" into the net */
213 s->score_event = SCORE_EVENT_NET;
214 s->play_sound = SOUND_OUT;
215 s->status = "net!";
216 } else if (IS_OFFSCREEN(s->ball.x, s->ball.y)) {
217 /* ball flew offscreen */
218 s->score_event = SCORE_EVENT_OFFSCREEN;
219 s->play_sound = SOUND_OUT;
220 s->status = "out!";
221 } else if (ground_event) {
222 /* the ball hit the ground on the screen */
223 if (IS_OUT(s->ball.x, s->ball.y)) {
224 /* the ball bounced in the OUT area */
225 s->score_event = SCORE_EVENT_OUT;
226 s->play_sound = SOUND_OUT;
227 s->status = "out!";
228 } else if (GROUND_IS_VALID(s->ball.last_hit_by, s->ball.x, s->ball.y)) {
229 if (s->ball.ground_hit) {
230 s->score_event = SCORE_EVENT_GROUND_VALID;
231 s->play_sound = SOUND_OUT;
232 s->status = "did not catch the ball!";
233 } else {
234 /* first ground hit in valid area */
235 s->ball.ground_hit = true;
237 } else {
238 /* ball hit somewhere invalid */
239 s->score_event = SCORE_EVENT_GROUND_INVALID;
240 s->play_sound = SOUND_OUT;
241 s->status = "fault!";
246 if (s->score_event != SCORE_UNDECIDED) {
247 /* we have some scoring to do */
248 if (s->score_time == 0) {
249 /* schedule scoring in the future */
250 s->score_time = s->time + SCORING_DELAY;
251 s->referee = REFEREE_OUT;
252 } else if (s->time >= s->score_time) {
253 /* time has ran out - score now */
254 switch (score_game(s)) {
255 case WINNER_PLAYER1:
256 s->status = "player 1 scores";
257 s->referee = REFEREE_PLAYER1;
258 break;
259 case WINNER_PLAYER2:
260 s->status = "player 2 scores";
261 s->referee = REFEREE_PLAYER2;
262 break;
263 default:
264 assert(0);
265 break;
267 s->score_time = 0;
269 strcpy(s->game_score_str, format_game(s));
270 strcpy(s->sets_score_str, format_sets(s));
271 s->text_changed = true;
273 game_setup_serve(s);
274 s->play_sound = SOUND_APPLAUSE;
275 /*FIXME n-gram predictor broken
276 s->history_size = 0;
277 s->history_is_locked = 0;
278 s->ngram_prediction = 0.0;*/
280 } else {
281 /* score is still undecided - do the racket swing thing */
282 for (p=1; p<=2; p++) {
283 if (IS_NEAR_X(PLAYER(s, p).x, s->ball.x) && IS_NEAR_Y(PLAYER(s, p).y, s->ball.y-s->ball.z) && PLAYER(s, p).state && s->ball.last_hit_by != p) {
284 /* RACKET HIT */
285 if (!s->ball.ground_hit && s->ball.move_x != 0.0) {
286 s->status = "volley!";
287 } else {
288 s->status = format_status(s);
290 switch (PLAYER(s, p).desire) {
291 case DESIRE_NORMAL:
292 /* normal swing */
293 s->ball.move_x = 2.7 + 2.0*PLAYER(s, p).state/PLAYER_STATE_MAX;
294 s->ball.move_z = 1.2*PLAYER(s, p).state/PLAYER_STATE_MAX;
295 break;
296 case DESIRE_TOPSPIN:
297 /* top spin */
298 s->ball.move_x = 1.1 + 2.2*PLAYER(s, p).state/PLAYER_STATE_MAX;
299 s->ball.move_z = 2.5*PLAYER(s, p).state/PLAYER_STATE_MAX;
300 break;
301 case DESIRE_SMASH:
302 /* smash */
303 s->ball.move_x = 4.0 + 3.0*PLAYER(s, p).state/PLAYER_STATE_MAX;
304 s->ball.move_z = 1.1*PLAYER(s, p).state/PLAYER_STATE_MAX;
305 break;
307 s->ball.move_y = get_move_y( s, p);
308 s->play_sound = SOUND_RACKET;
309 s->ball.ground_hit = false;
310 s->ball.inhibit_gravity = false;
311 s->ball.last_hit_by = p;
312 if (p==1) {
313 pan_sample(SOUND_RACKET, 0.4);
314 } else {
315 pan_sample(SOUND_RACKET, 0.6);
316 s->ball.move_x *= -1;
322 #if 0
323 FIXME n-gram predictor broken
324 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
325 if( IS_OUT_X(s->ball.x)) {
326 /*if( !s->history_is_locked && s->referee != REFEREE_OUT) {
327 s->history[s->history_size] = (int)(NGRAM_STEPS*s->ball.y/HEIGHT);
328 s->history_size++;
329 if( s->history_size == 3) {
330 s->ngram[s->history[0]][s->history[1]][s->history[2]] += 10;
331 s->ngram_prediction = ngram_predictor( s);
332 s->history[0] = s->history[1];
333 s->history[1] = s->history[2];
334 s->history_size--;
336 s->history_is_locked = true;
339 } else {
340 /*s->history_is_locked = false;*/
342 #endif
344 SDL_PollEvent(&e);
345 keys = SDL_GetKeyState(NULL);
346 switch(e.type) {
347 case SDL_JOYAXISMOTION:
348 if (e.jaxis.axis == JOYSTICK_Y_AXIS) {
349 s->joystick_y = JOYSTICK_PERCENTIZE(e.jaxis.value);
350 } else if (e.jaxis.axis == JOYSTICK_X_AXIS) {
351 s->joystick_x = JOYSTICK_PERCENTIZE(e.jaxis.value);
353 break;
354 case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN:
355 if (e.jbutton.button == JOYSTICK_BUTTON_A) {
356 s->joystick_a = (e.jbutton.state == SDL_PRESSED);
358 break;
361 if( s->time%50==0) {
363 * Maemo keys:
364 * F7 = "Decrease" key
365 * F8 = "Increase" key
367 if (keys['c'] || keys[SDLK_F8]) {
368 s->court_type++;
369 } else if (keys[SDLK_F7]) {
370 s->court_type--;
372 if (keys['r']) {
373 if (s->rain == 0) {
374 play_sample_background(SOUND_RAIN);
376 s->rain += 10;
378 if (keys['t']) {
379 s->fog++;
381 if (keys['1']) {
382 s->wind++;
384 if (keys['2']) {
385 s->wind--;
387 if (keys['n']) {
388 s->night = 1 - s->night;
390 if( s->court_type > GR_CTT_LAST) {
391 s->court_type = GR_CTT_FIRST;
392 } else if (s->court_type < GR_CTT_FIRST) {
393 s->court_type = GR_CTT_LAST;
397 if(!(SDL_GetTicks() < fading_start+FADE_DURATION) && !s->is_over) {
398 if( PLAYER(s, 1).type == PLAYER_TYPE_HUMAN) {
399 input_human( &PLAYER(s, 1),
400 keys['w'] || keys[SDLK_UP] || s->joystick_y < -JOYSTICK_TRESHOLD,
401 keys['s'] || keys[SDLK_DOWN] || s->joystick_y > JOYSTICK_TRESHOLD,
402 keys['d'] || keys[SDLK_SPACE] || keys[SDLK_LCTRL] || keys[SDLK_RETURN] || s->joystick_a,
403 keys['e'],
404 keys['f'],
405 #ifdef ENABLE_MOUSE
406 true,
407 #else
408 false,
409 #endif
411 } else {
412 input_ai( &PLAYER(s, 1), &s->ball, &PLAYER(s, 2), s);
415 if( PLAYER(s, 2).type == PLAYER_TYPE_HUMAN) {
416 input_human( &PLAYER(s, 2), keys['o'], keys['l'], keys['k'], keys['i'], keys['j'], false, s);
417 } else {
418 input_ai( &PLAYER(s, 2), &s->ball, &PLAYER(s, 1), s);
422 /* Maemo: The "F6" button is the "Fullscreen" button */
423 /*if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);*/
424 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
426 /* Maemo: The "F4" button is the "Open menu" button */
427 if( keys['p'] || keys[SDLK_F4]) {
428 while( keys['p'] || keys[SDLK_F4]) {
429 SDL_PollEvent( &e);
430 keys = SDL_GetKeyState( NULL);
431 SDL_Delay( 10);
433 while( (keys['p'] || keys[SDLK_F4]) == 0) {
434 SDL_PollEvent( &e);
435 keys = SDL_GetKeyState( NULL);
436 SDL_Delay( 10);
438 while( keys['p'] || keys[SDLK_F4]) {
439 SDL_PollEvent( &e);
440 keys = SDL_GetKeyState( NULL);
441 SDL_Delay( 10);
443 s->was_stopped = true;
446 if( keys[SDLK_ESCAPE] || keys['q']) return true;
448 limit_value( &PLAYER(s, 1).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
449 limit_value( &PLAYER(s, 2).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
451 if (s->ball.move_x > 0 && s->wind > 0) {
452 s->ball.x += fabsf(s->wind)/2;
453 } else if (s->ball.move_x < 0 && s->wind < 0) {
454 s->ball.x -= fabsf(s->wind)/2;
457 s->ball.z += s->ball.move_z;
458 if (!s->ball.inhibit_gravity) {
459 s->ball.move_z += GRAVITY;
462 s->ball.x += s->ball.move_x;
463 s->ball.y += s->ball.move_y;
465 if(PLAYER(s, 1).state) PLAYER(s, 1).state--;
466 if(PLAYER(s, 2).state) PLAYER(s, 2).state--;
468 return false;
471 void render( GameState* s) {
472 int x, y, b;
473 unsigned int i;
474 #ifdef EXTENDED_REFEREE
475 int t=1000;
476 #endif
477 if (s->play_sound != SOUND_MAX) {
478 play_sample(s->play_sound);
479 s->play_sound = SOUND_MAX;
481 if( s->winner != WINNER_NONE) {
482 if( !s->is_over) {
483 start_fade();
484 s->is_over = true;
486 clear_screen();
487 store_screen();
488 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);
489 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
490 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);
491 updatescr();
492 return;
494 if (s->old_court_type != s->court_type || s->text_changed || s->old_referee != s->referee) {
495 clear_screen();
496 fill_image(s->court_type, 120, 120, 400, 250);
497 show_image(GR_COURT, 0, 0, 255);
498 font_draw_string( GR_DKC2_FONT, s->game_score_str, 14, 14, 0, ANIMATION_NONE);
499 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);
500 #ifdef EXTENDED_REFEREE
501 switch (s->referee) {
502 case REFEREE_NORMAL:
503 t = 1000;
504 break;
505 case REFEREE_OUT:
506 t = 200;
507 break;
508 case REFEREE_PLAYER1:
509 case REFEREE_PLAYER2:
510 t = 400;
511 break;
513 t = (s->time/t)%4;
514 switch (t) {
515 case 0:
516 t=0;
517 break;
518 case 1:
519 t=1;
520 break;
521 case 2:
522 t=0;
523 break;
524 case 3:
525 t=2;
526 break;
528 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
529 if (voice_finished_flag == 0) {
530 show_sprite(GR_TALK, (s->time/150)%2, 2, 280, 45, 255);
532 #else
533 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
534 #endif
535 s->old_court_type = s->court_type;
536 s->text_changed = false;
537 s->old_referee = s->referee;
538 store_screen();
541 show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
542 show_sprite( GR_RACKET, (!PLAYER(s, 2).state)+2, 4, PLAYER(s, 2).x-RACKET_X_MID, PLAYER(s, 2).y-RACKET_Y_MID, 255);
544 if( s->ball.move_x > 0) {
545 b = (s->time/100)%BALL_STATES;
546 } else if( s->ball.move_x < 0) {
547 b = BALL_STATES-1-(s->time/100)%BALL_STATES;
548 } else {
549 b = 0;
551 show_image(GR_SHADOW, s->ball.x-BALL_X_MID, s->ball.y-4, 255);
552 show_sprite(GR_BALL, b, BALL_STATES, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID-s->ball.z, 255);
554 /* Player 1's mouse rectangle */
555 if (!(PLAYER(s, 1).mouse_locked)) {
556 rectangle(PLAYER(s, 1).x-2+5, PLAYER(s, 1).mouse_y-2, 4, 4, 255, 255, 255);
557 rectangle(PLAYER(s, 1).x-1+5, PLAYER(s, 1).mouse_y-1, 2, 2, 0, 0, 0);
561 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);
563 for (i=0; i<s->rain; i++) {
564 x = rand()%WIDTH;
565 y = rand()%HEIGHT;
566 draw_line_faded(x, y, x+10+s->wind*5, y+30, 0, 0, 255, 100, 200, 255);
568 if (s->rain) {
570 * Cheap-ish update of the whole screen. This can
571 * probably be optimized.
573 update_rect(0, 0, WIDTH, HEIGHT);
576 #ifdef DEBUG
577 line_horiz( PLAYER(s, 1).y, 255, 0, 0);
578 line_horiz( PLAYER(s, 2).y, 0, 0, 255);
579 line_horiz( s->ball.y, 0, 255, 0);
581 line_vert( PLAYER(s, 1).x, 255, 0, 0);
582 line_vert( PLAYER(s, 2).x, 0, 0, 255);
583 line_vert( s->ball.x, 0, 255, 0);
585 line_horiz( GAME_Y_MIN, 100, 100, 100);
586 line_horiz( GAME_Y_MAX, 100, 100, 100);
587 #endif
588 switch (s->fog) {
589 default:
590 case 4:
591 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150-s->windtime/200, 0);
592 case 3:
593 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100-s->windtime/150, 20);
594 case 2:
595 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180-s->windtime/180, 80);
596 case 1:
597 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200-s->windtime/100, 0);
598 case 0:
599 break;
601 if (s->night) {
602 show_image(GR_NIGHT, 0, 0, 255);
605 updatescr();
608 void limit_value( float* value, float min, float max) {
609 if( *value < min) {
610 *value = min;
611 } else if( *value > max) {
612 *value = max;
616 float get_move_y( GameState* s, unsigned char player) {
617 float pct, dest, x_len, y_len;
618 float py, by, pa, move_x;
620 py = (player==1)?(PLAYER(s, 1).y):(PLAYER(s, 2).y);
621 by = s->ball.y - s->ball.z;
622 pa = RACKET_Y_MID*2;
623 move_x = s->ball.move_x;
625 /* -1.0 .. 1.0 for racket hit position */
626 pct = (by-py)/(pa/2);
627 limit_value( &pct, -1.0, 1.0);
629 /* Y destination for ball */
630 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
632 /* lengths for the ball's journey */
633 if( player == 1) {
634 x_len = fabsf(GAME_X_MAX - s->ball.x);
635 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
636 } else {
637 x_len = s->ball.x - GAME_X_MIN;
638 y_len = by - dest + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
641 /* return the should-be value for move_y */
642 return (y_len*move_x)/(x_len);
645 void input_human( Player* player, bool up, bool down, bool hit, bool topspin, bool smash, bool use_mouse, GameState* s) {
646 int diff = PLAYER_MOVE_Y;
647 int mb;
650 * Only use mouse control if the user isn't pressing any buttons
651 * this way, keyboard control still works when mouse control is
652 * enabled.
654 if (use_mouse && (down || up || hit)) {
656 * this is here so if the user decides to play
657 * with keyboard controls, we will lock the
658 * mouse and disable displaying the mouse cursor
660 player->mouse_locked = true;
662 if (use_mouse && !down && !up && !hit) {
663 mb = SDL_GetMouseState(&(player->mouse_x), &(player->mouse_y));
664 if (mb&SDL_BUTTON(SDL_BUTTON_LEFT)) {
665 if (player->mouse_y < player->y) {
666 down = false;
667 up = true;
668 diff = (player->y-player->mouse_y<diff)?(player->y-player->mouse_y):(diff);
669 } else if (player->mouse_y > player->y) {
670 up = false;
671 down = true;
672 diff = (player->mouse_y-player->y<diff)?(player->mouse_y-player->y):(diff);
674 player->mouse_locked = false;
675 } else if (!player->mouse_locked) {
676 hit = true;
680 if (fabsf(s->joystick_y) > JOYSTICK_TRESHOLD) {
681 diff = PLAYER_MOVE_Y*fabsf(s->joystick_y)/40.0;
682 if (diff > PLAYER_MOVE_Y) {
683 diff = PLAYER_MOVE_Y;
687 if (up) {
688 player->y -= fminf(diff, diff*player->accelerate);
689 player->accelerate *= PLAYER_ACCEL_INCREASE;
690 } else if (down) {
691 player->y += fminf(diff, diff*player->accelerate);
692 player->accelerate *= PLAYER_ACCEL_INCREASE;
693 } else {
694 player->accelerate = PLAYER_ACCEL_DEFAULT;
697 if(hit || topspin || smash) {
698 player->desire = (topspin)?(DESIRE_TOPSPIN):(DESIRE_NORMAL);
699 player->desire = (smash)?(DESIRE_SMASH):(player->desire);
701 if( !player->state && !player->state_locked) {
702 player->state = PLAYER_STATE_MAX;
703 player->state_locked = true;
705 } else {
706 player->state_locked = false;
710 void input_ai( Player* player, Ball* ball, Player* opponent, GameState* s) {
711 float fact = 1.7;
712 float target;
714 /* FIXME - this is broken since the new physics model has been introduced */
716 if( fabsf( player->y - (ball->y-ball->z)) > RACKET_Y_MID*5) {
717 fact = 3.5;
720 if(1) {
721 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, (ball->y-ball->z))) {
722 if( player->y < (ball->y-ball->z)) {
723 player->y += fmin( 2*fact, (ball->y-ball->z) - player->y);
724 } else if( player->y > (ball->y-ball->z)) {
725 player->y -= fmin( 2*fact, player->y - (ball->y-ball->z));
729 if( (ball->move_x != 0 || IS_NEAR_Y_AI( player->y, (ball->y-ball->z))) && IS_NEAR_X_AI( player->x, ball->x)/* && !player->state*/) {
730 player->state = PLAYER_STATE_MAX;
732 } else if( s->ngram_prediction > 0.0) {
733 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
734 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
736 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
737 if( player->y < target) {
738 player->y += fmin( fact, (target-player->y)/40.0);
739 } else if( player->y > target) {
740 player->y -= fmin( fact, (player->y-target)/40.0);
746 void game_setup_serve( GameState* s) {
747 s->ball.z = 10.0;
748 s->ball.y = GAME_Y_MID;
749 s->ball.move_x = 0.0;
750 s->ball.move_y = 0.0;
751 s->ball.move_z = 0.0;
752 s->ball.last_hit_by = -1;
753 s->ball.inhibit_gravity = true;
755 if( s->serving_player == 1) {
756 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
757 } else {
758 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
762 float ngram_predictor( GameState* s) {
763 unsigned int count = 0;
764 unsigned long sum = 0;
765 int x, y, z;
766 float result;
768 if( s->history_size < 3) {
769 return 0.0;
772 x = s->history[1];
773 y = s->history[2];
775 for( z = 0; z<NGRAM_STEPS; z++) {
776 count += s->ngram[x][y][z];
777 sum += z * s->ngram[x][y][z];
780 result = ((float)(sum))/((float)(count));
781 #ifdef DEBUG
782 printf( "predicting next = %.2f\n", result);
783 #endif
785 return result;
788 int score_game(GameState* s) {
789 Player *last_hit_by = NULL;
790 Player *other = NULL;
792 Player* winner = NULL;
793 Player* loser = NULL;
795 /* determine "last hit by" and "other" */
796 if (s->ball.last_hit_by == 1) {
797 last_hit_by = &(PLAYER(s, 1));
798 other = &(PLAYER(s, 2));
799 } else {
800 last_hit_by = &(PLAYER(s, 2));
801 other = &(PLAYER(s, 1));
804 switch (s->score_event) {
805 case SCORE_EVENT_NET:
806 winner = other;
807 break;
808 case SCORE_EVENT_OUT:
809 case SCORE_EVENT_GROUND_INVALID:
810 case SCORE_EVENT_OFFSCREEN:
811 case SCORE_EVENT_GROUND_VALID:
812 if (s->ball.ground_hit) {
813 winner = last_hit_by;
814 } else {
815 winner = other;
817 break;
818 default:
819 break;
822 /* determine loser based on winner */
823 if (winner == last_hit_by) {
824 loser = other;
825 } else {
826 loser = last_hit_by;
829 /* we cannot be in an "impossibly high" set */
830 assert(s->current_set < SETS_TO_WIN*2-1);
832 winner->game++;
833 if( loser->game < winner->game-1) {
834 if( winner->game >= 4) {
835 winner->game = loser->game = 0;
836 winner->sets[s->current_set]++;
838 /* serving is changed when the "game" is over */
839 s->serving_player = (s->serving_player==1)?(2):(1);
841 #ifdef HAVE_VOICE_FILES
842 /* speak the current score */
843 voice_say_list(4, VOICE_ZERO_IN + (PLAYER(s, 1).sets[s->current_set])*2, VOICE_TO, VOICE_ZERO_OUT + (PLAYER(s, 2).sets[s->current_set])*2, VOICE_IN_THE_FIRST_SET+s->current_set);
844 #endif
846 /* scoring the set.. */
847 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
848 winner->sets[s->current_set] == 7) {
849 s->current_set++;
850 s->winner = game_get_winner( s);
855 /* forget this event - we've handled it */
856 s->score_event = SCORE_UNDECIDED;
858 if (winner == &PLAYER(s, 1)) {
859 return WINNER_PLAYER1;
860 } else {
861 return WINNER_PLAYER2;
865 char* format_sets( GameState* s) {
866 static char sets[100];
867 static char tmp[100];
868 int i, max = s->current_set;
870 sets[0] = '\0';
872 if( s->winner != WINNER_NONE) {
873 max--;
875 for( i=0; i<=max; i++) {
876 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
877 strcat( sets, tmp);
880 sets[strlen(sets)-2] = '\0';
882 return sets;
885 char* format_game( GameState* s) {
886 static char game[100];
887 static const int game_scoring[] = { 0, 15, 30, 40 };
889 if( PLAYER(s, 1).game < 4 && PLAYER(s, 2).game < 4) {
890 #ifdef HAVE_VOICE_FILES
891 if (PLAYER(s, 1).game > 0 || PLAYER(s, 2).game > 0) {
892 if (PLAYER(s, 1).game == PLAYER(s, 2).game) {
893 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_ALL);
894 } else {
895 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
898 #endif
899 sprintf( game, "%d - %d", game_scoring[PLAYER(s, 1).game], game_scoring[PLAYER(s, 2).game]);
900 } else if( PLAYER(s, 1).game > PLAYER(s, 2).game) {
901 #ifdef HAVE_VOICE_FILES
902 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
903 #endif
904 strcpy( game, "advantage player 1");
905 } else if( PLAYER(s, 1).game < PLAYER(s, 2).game) {
906 #ifdef HAVE_VOICE_FILES
907 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
908 #endif
909 strcpy( game, "advantage player 2");
910 } else {
911 #ifdef HAVE_VOICE_FILES
912 voice_say_list(1, VOICE_DEUCE);
913 #endif
914 strcpy( game, "deuce");
917 return game;
920 char* format_status( GameState* s) {
921 static char status[100];
922 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
924 sprintf( status, "%d:%d in %s set", PLAYER(s, 1).sets[s->current_set], PLAYER(s, 2).sets[s->current_set], set_names[s->current_set]);
926 return status;
929 int game_get_winner( GameState* s) {
930 unsigned int i;
931 int sets[2] = {0};
933 for( i=0; i<s->current_set; i++) {
934 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
935 sets[0]++;
936 } else {
937 sets[1]++;
941 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
942 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
944 return WINNER_NONE;