Add some missing #include lines, use C99 as standard
[tennix.git] / game.c
blob464267d70672adcf59b398f02639325934dd26c8
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>
29 #include "tennix.h"
30 #include "game.h"
31 #include "graphics.h"
32 #include "input.h"
33 #include "sound.h"
36 GameState *gamestate_new() {
37 int x, y, z;
38 GameState *s;
40 GameState template = {
41 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false, -1, false },
43 { 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 },
44 { 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 },
49 "welcome to tennix " VERSION,
50 { 0 },
51 { 0 },
52 REFEREE_NORMAL,
54 WINNER_NONE,
55 false,
56 GR_CTT_GRASS,
57 -1,
58 { 0 },
60 false,
61 { { { 0 } } },
62 0.0,
63 SOUND_MAX,
64 0.0,
65 0.0,
69 false,
73 false,
74 REFEREE_COUNT,
75 SCORE_UNDECIDED,
79 s = (GameState*)malloc(sizeof(GameState));
80 if (s == NULL) abort();
82 memcpy(s, &template, sizeof(GameState));
84 game_setup_serve(s);
86 /* smoothen n-gram */
87 for( x = 0; x<NGRAM_STEPS; x++) {
88 for( y = 0; y<NGRAM_STEPS; y++) {
89 for( z = 0; z<NGRAM_STEPS; z++) {
90 s->ngram[x][y][z] = 1;
95 return s;
99 void gameloop(GameState *s) {
100 Uint32 ot = SDL_GetTicks();
101 Uint32 nt;
102 Uint32 dt = GAME_TICKS;
103 Uint32 diff;
104 Uint32 accumulator = 0;
105 bool quit = false;
107 #ifdef ENABLE_FPS_LIMIT
108 Uint32 ft, frames; /* frame timer and frames */
109 #endif
111 strcpy(s->game_score_str, format_game(s));
112 strcpy(s->sets_score_str, format_sets(s));
113 s->text_changed = true;
115 if (s->rain > 0) {
116 play_sample_background(SOUND_RAIN);
118 play_sample_loop(SOUND_AUDIENCE);
119 /* Reset the court type, so it is redrawn on first display */
120 s->old_court_type = -1;
122 #ifdef ENABLE_FPS_LIMIT
123 frames = 0;
124 ft = SDL_GetTicks();
125 #endif
126 while( !quit) {
127 nt = SDL_GetTicks();
128 diff = nt-ot;
129 if( diff > 2000) {
130 diff = 0;
133 accumulator += diff;
134 ot = nt;
136 while( accumulator >= dt) {
137 quit = step(s);
138 s->time += dt;
139 s->windtime += s->wind*dt;
140 accumulator -= dt;
142 if( s->was_stopped) {
143 ot = SDL_GetTicks();
144 s->was_stopped = false;
147 if (s->timelimit != 0 && s->time >= s->timelimit) {
148 quit = 1;
151 #ifdef ENABLE_FPS_LIMIT
152 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
153 SDL_Delay(10);
155 frames++;
156 #endif
158 render(s);
161 clear_screen();
162 store_screen();
164 stop_sample(SOUND_AUDIENCE);
165 stop_sample(SOUND_RAIN);
168 bool step( GameState* s) {
169 Uint8 *keys;
170 SDL_Event e;
171 bool ground_event = false;
172 int p;
174 if (s->ball.z < 0) {
175 /* ground event */
176 ground_event = true;
178 s->referee = REFEREE_NORMAL;
179 /* FIXME: set referee to correct "position" */
181 /* bounce from the ground */
182 if (fabsf(s->ball.move_z) > 0.5) {
183 s->ball.move_z *= -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 /* FIXME: make sure the ball appears OUTSIDE of the net */
202 /* see if we have something to score */
203 if (s->score_event == SCORE_UNDECIDED) {
204 if (NET_COLLISION_BALL(s->ball)) {
205 /* the ball "fell" into the net */
206 s->score_event = SCORE_EVENT_NET;
207 s->play_sound = SOUND_OUT;
208 s->status = "net!";
209 } else if (IS_OFFSCREEN(s->ball.x, s->ball.y)) {
210 /* ball flew offscreen */
211 s->score_event = SCORE_EVENT_OFFSCREEN;
212 s->play_sound = SOUND_OUT;
213 s->status = "out!";
214 } else if (ground_event) {
215 /* the ball hit the ground on the screen */
216 if (IS_OUT(s->ball.x, s->ball.y)) {
217 /* the ball bounced in the OUT area */
218 s->score_event = SCORE_EVENT_OUT;
219 s->play_sound = SOUND_OUT;
220 s->status = "out!";
221 } else if (GROUND_IS_VALID(s->ball.last_hit_by, s->ball.x, s->ball.y)) {
222 if (s->ball.ground_hit) {
223 s->score_event = SCORE_EVENT_GROUND_VALID;
224 s->play_sound = SOUND_OUT;
225 s->status = "did not catch the ball!";
226 } else {
227 /* first ground hit in valid area */
228 s->ball.ground_hit = true;
230 } else {
231 /* ball hit somewhere invalid */
232 s->score_event = SCORE_EVENT_GROUND_INVALID;
233 s->play_sound = SOUND_OUT;
234 s->status = "fault!";
239 if (s->score_event != SCORE_UNDECIDED) {
240 /* we have some scoring to do */
241 if (s->score_time == 0) {
242 /* schedule scoring in the future */
243 s->score_time = s->time + SCORING_DELAY;
244 } else if (s->time >= s->score_time) {
245 /* time has ran out - score now */
246 score_game(s);
247 s->score_time = 0;
249 strcpy(s->game_score_str, format_game(s));
250 strcpy(s->sets_score_str, format_sets(s));
251 s->text_changed = true;
253 /*if (FIXME - get winner) {
254 if( PLAYER(s, 1).type == PLAYER_TYPE_HUMAN && PLAYER(s, 2).type == PLAYER_TYPE_AI) {
255 s->status = "computer scores";
256 } else {
257 s->status = "player 2 scores";
259 s->referee = REFEREE_PLAYER2;
260 } else {
261 if( PLAYER(s, 1).type == PLAYER_TYPE_HUMAN && PLAYER(s, 2).type == PLAYER_TYPE_AI) {
262 s->status = "player scores";
263 } else {
264 s->status = "player 1 scores";
266 s->referee = REFEREE_PLAYER1;
269 game_setup_serve(s);
270 s->play_sound = SOUND_APPLAUSE;
271 /*FIXME n-gram predictor broken
272 s->history_size = 0;
273 s->history_is_locked = 0;
274 s->ngram_prediction = 0.0;*/
276 } else {
277 /* score is still undecided - do the racket swing thing */
278 for (p=1; p<=2; p++) {
279 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) {
280 /* RACKET HIT */
281 if (!s->ball.ground_hit && s->ball.move_x != 0.0) {
282 s->status = "volley!";
283 } else {
284 s->status = format_status(s);
286 switch (PLAYER(s, p).desire) {
287 case DESIRE_NORMAL:
288 /* normal swing */
289 s->ball.move_x = 2.7 + 2.0*PLAYER(s, p).state/PLAYER_STATE_MAX;
290 s->ball.move_z = 1.2*PLAYER(s, p).state/PLAYER_STATE_MAX;
291 break;
292 case DESIRE_TOPSPIN:
293 /* top spin */
294 s->ball.move_x = 1.1 + 2.2*PLAYER(s, p).state/PLAYER_STATE_MAX;
295 s->ball.move_z = 2.5*PLAYER(s, p).state/PLAYER_STATE_MAX;
296 break;
297 case DESIRE_SMASH:
298 /* smash */
299 s->ball.move_x = 4.0 + 3.0*PLAYER(s, p).state/PLAYER_STATE_MAX;
300 s->ball.move_z = 1.1*PLAYER(s, p).state/PLAYER_STATE_MAX;
301 break;
303 s->ball.move_y = get_move_y( s, p);
304 s->play_sound = SOUND_RACKET;
305 s->ball.ground_hit = false;
306 s->ball.inhibit_gravity = false;
307 s->ball.last_hit_by = p;
308 if (p==1) {
309 pan_sample(SOUND_RACKET, 0.4);
310 } else {
311 pan_sample(SOUND_RACKET, 0.6);
312 s->ball.move_x *= -1;
318 #if 0
319 FIXME n-gram predictor broken
320 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
321 if( IS_OUT_X(s->ball.x)) {
322 /*if( !s->history_is_locked && s->referee != REFEREE_OUT) {
323 s->history[s->history_size] = (int)(NGRAM_STEPS*s->ball.y/HEIGHT);
324 s->history_size++;
325 if( s->history_size == 3) {
326 s->ngram[s->history[0]][s->history[1]][s->history[2]] += 10;
327 s->ngram_prediction = ngram_predictor( s);
328 s->history[0] = s->history[1];
329 s->history[1] = s->history[2];
330 s->history_size--;
332 s->history_is_locked = true;
335 } else {
336 /*s->history_is_locked = false;*/
338 #endif
340 SDL_PollEvent(&e);
341 keys = SDL_GetKeyState(NULL);
342 switch(e.type) {
343 case SDL_JOYAXISMOTION:
344 if (e.jaxis.axis == JOYSTICK_Y_AXIS) {
345 s->joystick_y = JOYSTICK_PERCENTIZE(e.jaxis.value);
346 } else if (e.jaxis.axis == JOYSTICK_X_AXIS) {
347 s->joystick_x = JOYSTICK_PERCENTIZE(e.jaxis.value);
349 break;
350 case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN:
351 if (e.jbutton.button == JOYSTICK_BUTTON_A) {
352 s->joystick_a = (e.jbutton.state == SDL_PRESSED);
354 break;
357 if( s->time%50==0) {
359 * Maemo keys:
360 * F7 = "Decrease" key
361 * F8 = "Increase" key
363 if (keys['c'] || keys[SDLK_F8]) {
364 s->court_type++;
365 } else if (keys[SDLK_F7]) {
366 s->court_type--;
368 if (keys['r']) {
369 if (s->rain == 0) {
370 play_sample_background(SOUND_RAIN);
372 s->rain += 10;
374 if (keys['t']) {
375 s->fog++;
377 if (keys['1']) {
378 s->wind++;
380 if (keys['2']) {
381 s->wind--;
383 if (keys['n']) {
384 s->night = 1 - s->night;
386 if( s->court_type > GR_CTT_LAST) {
387 s->court_type = GR_CTT_FIRST;
388 } else if (s->court_type < GR_CTT_FIRST) {
389 s->court_type = GR_CTT_LAST;
393 if(!(SDL_GetTicks() < fading_start+FADE_DURATION) && !s->is_over) {
394 if( PLAYER(s, 1).type == PLAYER_TYPE_HUMAN) {
395 input_human( &PLAYER(s, 1),
396 keys['w'] || keys[SDLK_UP] || s->joystick_y < -JOYSTICK_TRESHOLD,
397 keys['s'] || keys[SDLK_DOWN] || s->joystick_y > JOYSTICK_TRESHOLD,
398 keys['d'] || keys[SDLK_SPACE] || keys[SDLK_LCTRL] || keys[SDLK_RETURN] || s->joystick_a,
399 keys['e'],
400 keys['f'],
401 #ifdef ENABLE_MOUSE
402 true,
403 #else
404 false,
405 #endif
407 } else {
408 input_ai( &PLAYER(s, 1), &s->ball, &PLAYER(s, 2), s);
411 if( PLAYER(s, 2).type == PLAYER_TYPE_HUMAN) {
412 input_human( &PLAYER(s, 2), keys['o'], keys['l'], keys['k'], keys['i'], keys['j'], false, s);
413 } else {
414 input_ai( &PLAYER(s, 2), &s->ball, &PLAYER(s, 1), s);
418 /* Maemo: The "F6" button is the "Fullscreen" button */
419 /*if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);*/
420 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
422 /* Maemo: The "F4" button is the "Open menu" button */
423 if( keys['p'] || keys[SDLK_F4]) {
424 while( keys['p'] || keys[SDLK_F4]) {
425 SDL_PollEvent( &e);
426 keys = SDL_GetKeyState( NULL);
427 SDL_Delay( 10);
429 while( (keys['p'] || keys[SDLK_F4]) == 0) {
430 SDL_PollEvent( &e);
431 keys = SDL_GetKeyState( NULL);
432 SDL_Delay( 10);
434 while( keys['p'] || keys[SDLK_F4]) {
435 SDL_PollEvent( &e);
436 keys = SDL_GetKeyState( NULL);
437 SDL_Delay( 10);
439 s->was_stopped = true;
442 if( keys[SDLK_ESCAPE] || keys['q']) return true;
444 limit_value( &PLAYER(s, 1).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
445 limit_value( &PLAYER(s, 2).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
447 if (s->ball.move_x > 0 && s->wind > 0) {
448 s->ball.x += fabsf(s->wind)/2;
449 } else if (s->ball.move_x < 0 && s->wind < 0) {
450 s->ball.x -= fabsf(s->wind)/2;
453 s->ball.z += s->ball.move_z;
454 if (!s->ball.inhibit_gravity) {
455 s->ball.move_z += GRAVITY;
458 s->ball.x += s->ball.move_x;
459 s->ball.y += s->ball.move_y;
461 if(PLAYER(s, 1).state) PLAYER(s, 1).state--;
462 if(PLAYER(s, 2).state) PLAYER(s, 2).state--;
464 return false;
467 void render( GameState* s) {
468 int x, y, b;
469 unsigned int i;
470 #ifdef EXTENDED_REFEREE
471 int t=1000;
472 #endif
473 if (s->play_sound != SOUND_MAX) {
474 play_sample(s->play_sound);
475 s->play_sound = SOUND_MAX;
477 if( s->winner != WINNER_NONE) {
478 if( !s->is_over) {
479 start_fade();
480 s->is_over = true;
482 clear_screen();
483 store_screen();
484 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);
485 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
486 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);
487 updatescr();
488 return;
490 if (s->old_court_type != s->court_type || s->text_changed || s->old_referee != s->referee) {
491 clear_screen();
492 fill_image(s->court_type, 120, 120, 400, 250);
493 show_image(GR_COURT, 0, 0, 255);
494 font_draw_string( GR_DKC2_FONT, s->game_score_str, 14, 14, 0, ANIMATION_NONE);
495 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);
496 #ifdef EXTENDED_REFEREE
497 switch (s->referee) {
498 case REFEREE_NORMAL:
499 t = 1000;
500 break;
501 case REFEREE_OUT:
502 t = 200;
503 break;
504 case REFEREE_PLAYER1:
505 case REFEREE_PLAYER2:
506 t = 400;
507 break;
509 t = (s->time/t)%4;
510 switch (t) {
511 case 0:
512 t=0;
513 break;
514 case 1:
515 t=1;
516 break;
517 case 2:
518 t=0;
519 break;
520 case 3:
521 t=2;
522 break;
524 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
525 if (voice_finished_flag == 0) {
526 show_sprite(GR_TALK, (s->time/150)%2, 2, 280, 45, 255);
528 #else
529 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
530 #endif
531 s->old_court_type = s->court_type;
532 s->text_changed = false;
533 s->old_referee = s->referee;
534 store_screen();
537 show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
538 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);
540 if( s->ball.move_x > 0) {
541 b = (s->time/100)%BALL_STATES;
542 } else if( s->ball.move_x < 0) {
543 b = BALL_STATES-1-(s->time/100)%BALL_STATES;
544 } else {
545 b = 0;
547 show_image(GR_SHADOW, s->ball.x-BALL_X_MID, s->ball.y-4, 255);
548 show_sprite(GR_BALL, b, BALL_STATES, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID-s->ball.z, 255);
550 /* Player 1's mouse rectangle */
551 if (!(PLAYER(s, 1).mouse_locked)) {
552 rectangle(PLAYER(s, 1).x-2+5, PLAYER(s, 1).mouse_y-2, 4, 4, 255, 255, 255);
553 rectangle(PLAYER(s, 1).x-1+5, PLAYER(s, 1).mouse_y-1, 2, 2, 0, 0, 0);
557 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);
559 for (i=0; i<s->rain; i++) {
560 x = rand()%WIDTH;
561 y = rand()%HEIGHT;
562 draw_line_faded(x, y, x+10+s->wind*5, y+30, 0, 0, 255, 100, 200, 255);
564 if (s->rain) {
566 * Cheap-ish update of the whole screen. This can
567 * probably be optimized.
569 update_rect(0, 0, WIDTH, HEIGHT);
572 #ifdef DEBUG
573 line_horiz( PLAYER(s, 1).y, 255, 0, 0);
574 line_horiz( PLAYER(s, 2).y, 0, 0, 255);
575 line_horiz( s->ball.y, 0, 255, 0);
577 line_vert( PLAYER(s, 1).x, 255, 0, 0);
578 line_vert( PLAYER(s, 2).x, 0, 0, 255);
579 line_vert( s->ball.x, 0, 255, 0);
581 line_horiz( GAME_Y_MIN, 100, 100, 100);
582 line_horiz( GAME_Y_MAX, 100, 100, 100);
583 #endif
584 switch (s->fog) {
585 default:
586 case 4:
587 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150-s->windtime/200, 0);
588 case 3:
589 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100-s->windtime/150, 20);
590 case 2:
591 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180-s->windtime/180, 80);
592 case 1:
593 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200-s->windtime/100, 0);
594 case 0:
595 break;
597 if (s->night) {
598 show_image(GR_NIGHT, 0, 0, 255);
601 updatescr();
604 void limit_value( float* value, float min, float max) {
605 if( *value < min) {
606 *value = min;
607 } else if( *value > max) {
608 *value = max;
612 float get_move_y( GameState* s, unsigned char player) {
613 float pct, dest, x_len, y_len;
614 float py, by, pa, move_x;
616 py = (player==1)?(PLAYER(s, 1).y):(PLAYER(s, 2).y);
617 by = s->ball.y - s->ball.z;
618 pa = RACKET_Y_MID*2;
619 move_x = s->ball.move_x;
621 /* -1.0 .. 1.0 for racket hit position */
622 pct = (by-py)/(pa/2);
623 limit_value( &pct, -1.0, 1.0);
625 /* Y destination for ball */
626 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
628 /* lengths for the ball's journey */
629 if( player == 1) {
630 x_len = fabsf(GAME_X_MAX - s->ball.x);
631 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
632 } else {
633 x_len = s->ball.x - GAME_X_MIN;
634 y_len = by - dest + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
637 /* return the should-be value for move_y */
638 return (y_len*move_x)/(x_len);
641 void input_human( Player* player, bool up, bool down, bool hit, bool topspin, bool smash, bool use_mouse, GameState* s) {
642 int diff = PLAYER_MOVE_Y;
643 int mb;
646 * Only use mouse control if the user isn't pressing any buttons
647 * this way, keyboard control still works when mouse control is
648 * enabled.
650 if (use_mouse && (down || up || hit)) {
652 * this is here so if the user decides to play
653 * with keyboard controls, we will lock the
654 * mouse and disable displaying the mouse cursor
656 player->mouse_locked = true;
658 if (use_mouse && !down && !up && !hit) {
659 mb = SDL_GetMouseState(&(player->mouse_x), &(player->mouse_y));
660 if (mb&SDL_BUTTON(SDL_BUTTON_LEFT)) {
661 if (player->mouse_y < player->y) {
662 down = false;
663 up = true;
664 diff = (player->y-player->mouse_y<diff)?(player->y-player->mouse_y):(diff);
665 } else if (player->mouse_y > player->y) {
666 up = false;
667 down = true;
668 diff = (player->mouse_y-player->y<diff)?(player->mouse_y-player->y):(diff);
670 player->mouse_locked = false;
671 } else if (!player->mouse_locked) {
672 hit = true;
676 if (fabsf(s->joystick_y) > JOYSTICK_TRESHOLD) {
677 diff = PLAYER_MOVE_Y*fabsf(s->joystick_y)/40.0;
678 if (diff > PLAYER_MOVE_Y) {
679 diff = PLAYER_MOVE_Y;
683 if (up) {
684 player->y -= fminf(diff, diff*player->accelerate);
685 player->accelerate *= PLAYER_ACCEL_INCREASE;
686 } else if (down) {
687 player->y += fminf(diff, diff*player->accelerate);
688 player->accelerate *= PLAYER_ACCEL_INCREASE;
689 } else {
690 player->accelerate = PLAYER_ACCEL_DEFAULT;
693 if(hit || topspin || smash) {
694 player->desire = (topspin)?(DESIRE_TOPSPIN):(DESIRE_NORMAL);
695 player->desire = (smash)?(DESIRE_SMASH):(player->desire);
697 if( !player->state && !player->state_locked) {
698 player->state = PLAYER_STATE_MAX;
699 player->state_locked = true;
701 } else {
702 player->state_locked = false;
706 void input_ai( Player* player, Ball* ball, Player* opponent, GameState* s) {
707 float fact = 1.7;
708 float target;
710 /* FIXME - this is broken since the new physics model has been introduced */
712 if( fabsf( player->y - (ball->y-ball->z)) > RACKET_Y_MID*5) {
713 fact = 3.5;
716 if(1) {
717 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, (ball->y-ball->z))) {
718 if( player->y < (ball->y-ball->z)) {
719 player->y += fmin( 2*fact, (ball->y-ball->z) - player->y);
720 } else if( player->y > (ball->y-ball->z)) {
721 player->y -= fmin( 2*fact, player->y - (ball->y-ball->z));
725 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*/) {
726 player->state = PLAYER_STATE_MAX;
728 } else if( s->ngram_prediction > 0.0) {
729 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
730 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
732 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
733 if( player->y < target) {
734 player->y += fmin( fact, (target-player->y)/40.0);
735 } else if( player->y > target) {
736 player->y -= fmin( fact, (player->y-target)/40.0);
742 void game_setup_serve( GameState* s) {
743 s->ball.z = 10.0;
744 s->ball.y = GAME_Y_MID;
745 s->ball.move_x = 0.0;
746 s->ball.move_y = 0.0;
747 s->ball.move_z = 0.0;
748 s->ball.last_hit_by = -1;
749 s->ball.inhibit_gravity = true;
751 if( s->serving_player == 1) {
752 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
753 } else {
754 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
758 float ngram_predictor( GameState* s) {
759 unsigned int count = 0;
760 unsigned long sum = 0;
761 int x, y, z;
762 float result;
764 if( s->history_size < 3) {
765 return 0.0;
768 x = s->history[1];
769 y = s->history[2];
771 for( z = 0; z<NGRAM_STEPS; z++) {
772 count += s->ngram[x][y][z];
773 sum += z * s->ngram[x][y][z];
776 result = ((float)(sum))/((float)(count));
777 #ifdef DEBUG
778 printf( "predicting next = %.2f\n", result);
779 #endif
781 return result;
784 void score_game(GameState* s) {
785 Player *last_hit_by = NULL;
786 Player *other = NULL;
788 Player* winner = NULL;
789 Player* loser = NULL;
791 /* determine "last hit by" and "other" */
792 if (s->ball.last_hit_by == 1) {
793 last_hit_by = &(PLAYER(s, 1));
794 other = &(PLAYER(s, 2));
795 } else {
796 last_hit_by = &(PLAYER(s, 2));
797 other = &(PLAYER(s, 1));
800 switch (s->score_event) {
801 case SCORE_EVENT_NET:
802 winner = other;
803 break;
804 case SCORE_EVENT_OUT:
805 case SCORE_EVENT_GROUND_INVALID:
806 case SCORE_EVENT_OFFSCREEN:
807 case SCORE_EVENT_GROUND_VALID:
808 if (s->ball.ground_hit) {
809 winner = last_hit_by;
810 } else {
811 winner = other;
813 break;
814 default:
815 break;
818 /* determine loser based on winner */
819 if (winner == last_hit_by) {
820 loser = other;
821 } else {
822 loser = last_hit_by;
825 if( s->current_set >= SETS_TO_WIN*2-1) {
826 return;
829 winner->game++;
830 if( loser->game < winner->game-1) {
831 if( winner->game >= 4) {
832 winner->game = loser->game = 0;
833 winner->sets[s->current_set]++;
835 /* serving is changed when the "game" is over */
836 s->serving_player = (s->serving_player==1)?(2):(1);
838 #ifdef HAVE_VOICE_FILES
839 /* speak the current score */
840 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);
841 #endif
843 /* scoring the set.. */
844 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
845 winner->sets[s->current_set] == 7) {
846 s->current_set++;
847 s->winner = game_get_winner( s);
852 /* forget this event - we've handled it */
853 s->score_event = SCORE_UNDECIDED;
856 char* format_sets( GameState* s) {
857 static char sets[100];
858 static char tmp[100];
859 int i, max = s->current_set;
861 sets[0] = '\0';
863 if( s->winner != WINNER_NONE) {
864 max--;
866 for( i=0; i<=max; i++) {
867 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
868 strcat( sets, tmp);
871 sets[strlen(sets)-2] = '\0';
873 return sets;
876 char* format_game( GameState* s) {
877 static char game[100];
878 static const int game_scoring[] = { 0, 15, 30, 40 };
880 if( PLAYER(s, 1).game < 4 && PLAYER(s, 2).game < 4) {
881 #ifdef HAVE_VOICE_FILES
882 if (PLAYER(s, 1).game > 0 || PLAYER(s, 2).game > 0) {
883 if (PLAYER(s, 1).game == PLAYER(s, 2).game) {
884 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_ALL);
885 } else {
886 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
889 #endif
890 sprintf( game, "%d - %d", game_scoring[PLAYER(s, 1).game], game_scoring[PLAYER(s, 2).game]);
891 } else if( PLAYER(s, 1).game > PLAYER(s, 2).game) {
892 #ifdef HAVE_VOICE_FILES
893 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
894 #endif
895 strcpy( game, "advantage player 1");
896 } else if( PLAYER(s, 1).game < PLAYER(s, 2).game) {
897 #ifdef HAVE_VOICE_FILES
898 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
899 #endif
900 strcpy( game, "advantage player 2");
901 } else {
902 #ifdef HAVE_VOICE_FILES
903 voice_say_list(1, VOICE_DEUCE);
904 #endif
905 strcpy( game, "deuce");
908 return game;
911 char* format_status( GameState* s) {
912 static char status[100];
913 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
915 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]);
917 return status;
920 int game_get_winner( GameState* s) {
921 unsigned int i;
922 int sets[2] = {0};
924 for( i=0; i<s->current_set; i++) {
925 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
926 sets[0]++;
927 } else {
928 sets[1]++;
932 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
933 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
935 return WINNER_NONE;