Remove n-gram predictor which didn't work for a while
[tennix.git] / game.c
blob0697b27136e1003b40ef47dd9f31777824c1c2be
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 <assert.h>
28 #include <unistd.h>
30 #include "tennix.h"
31 #include "game.h"
32 #include "graphics.h"
33 #include "input.h"
34 #include "sound.h"
35 #include "util.h"
38 GameState *gamestate_new() {
39 int x, y, z;
40 GameState *s;
42 GameState template = {
43 NULL,
44 -1,
45 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6, false, -1, false },
47 { NULL, -1, GAME_X_MIN-RACKET_X_MID*2, GAME_Y_MID, 0.0, POWER_UP_FACTOR, POWER_DOWN_FACTOR, true, 0, DESIRE_NORMAL, PLAYER_TYPE_AI, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true },
48 { NULL, -1, GAME_X_MAX+RACKET_X_MID*2, GAME_Y_MID, 0.0, POWER_UP_FACTOR, POWER_DOWN_FACTOR, true, 0, DESIRE_NORMAL, PLAYER_TYPE_AI, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true },
53 "welcome to tennix " VERSION,
54 { 0 },
55 { 0 },
56 REFEREE_NORMAL,
58 WINNER_NONE,
59 false,
60 GR_COUNT,
61 { 0 },
63 false,
64 { { { 0 } } },
65 0.0,
66 0, /* sound events */
67 0.0,
68 0.0,
72 false,
76 false,
77 REFEREE_COUNT,
78 SCORE_UNDECIDED,
82 s = (GameState*)malloc(sizeof(GameState));
83 if (s == NULL) abort();
85 memcpy(s, &template, sizeof(GameState));
87 game_setup_serve(s);
89 return s;
93 int gamestate_save(GameState* s, const char* filename)
95 Location *location;
96 const char* status;
97 InputDevice* input_devices[MAXPLAYERS];
98 int i, result = 0;
99 FILE* fp = NULL;
100 #ifndef WIN32
101 char tmp[MAXPATHLEN];
103 assert(getcwd(tmp, MAXPATHLEN) == tmp);
104 assert(chdir(getenv("HOME")) == 0);
105 #endif
108 * Process-specific data (pointers to data
109 * structures only of interest to this process)
110 * has to be "removed" before saving.
112 * It can later be restored (in gamestate_load).
115 fp = fopen(filename, "w");
116 if (fp == NULL) {
117 return -1;
120 /* Save data for later recovery + clear */
121 location = s->location;
122 s->location = NULL;
123 status = s->status;
124 s->status = NULL;
125 for (i=1; i<=MAXPLAYERS; i++) {
126 input_devices[i-1] = PLAYER(s, i).input;
127 PLAYER(s, i).input = NULL;
130 if (fwrite(s, sizeof(GameState), 1, fp) != 1) {
131 result = -2;
134 /* Restore process-specific data */
135 s->location = location;
136 s->status = status;
137 for (i=1; i<=MAXPLAYERS; i++) {
138 PLAYER(s, i).input = input_devices[i-1];
141 fclose(fp);
143 #ifndef WIN32
144 assert(chdir(tmp) == 0);
145 #endif
147 return result;
151 GameState* gamestate_load(const char* filename)
153 FILE* fp;
154 GameState* s = NULL;
155 #ifndef WIN32
156 char tmp[MAXPATHLEN];
158 assert(getcwd(tmp, MAXPATHLEN) == tmp);
159 assert(chdir(getenv("HOME")) == 0);
160 #endif
162 fp = fopen(filename, "r");
164 if (fp != NULL) {
165 s = (GameState*)malloc(sizeof(GameState));
166 if (s != NULL) {
167 if (fread(s, sizeof(GameState), 1, fp) == 1) {
168 /* Restore/create process-specific data */
169 s->status = format_status(s);
170 /* FIXME: s->location, players' "input" */
171 } else {
172 free(s);
173 s = NULL;
176 fclose(fp);
179 #ifndef WIN32
180 assert(chdir(tmp) == 0);
181 #endif
183 return s;
187 void gameloop(GameState *s) {
188 Uint32 ot = SDL_GetTicks();
189 Uint32 nt;
190 Uint32 dt = GAME_TICKS;
191 Uint32 diff;
192 Uint32 accumulator = 0;
193 bool quit = false;
194 int p;
196 #ifdef ENABLE_FPS_LIMIT
197 Uint32 ft, frames; /* frame timer and frames */
198 #endif
200 strcpy(s->game_score_str, format_game(s));
201 strcpy(s->sets_score_str, format_sets(s));
202 s->text_changed = true;
204 if (s->rain > 0) {
205 play_sample_background(SOUND_RAIN);
207 if (s->location->max_visitors > 100) {
208 play_sample_loop(SOUND_AUDIENCE);
210 /* Reset the court type, so it is redrawn on first display */
211 s->displayed_court_type = GR_COUNT;
213 for (p=1; p<=MAXPLAYERS; p++) {
214 input_device_join_game(PLAYER(s, p).input, s, p);
217 #ifdef ENABLE_FPS_LIMIT
218 frames = 0;
219 ft = SDL_GetTicks();
220 #endif
221 while( !quit) {
222 nt = SDL_GetTicks();
223 diff = nt-ot;
224 if( diff > 2000) {
225 diff = 0;
228 accumulator += diff;
229 ot = nt;
231 while( accumulator >= dt) {
232 quit = step(s);
233 s->time += dt;
234 s->windtime += s->wind*dt;
235 accumulator -= dt;
237 if( s->was_stopped) {
238 ot = SDL_GetTicks();
239 s->was_stopped = false;
242 if (s->timelimit != 0 && s->time >= s->timelimit) {
243 quit = 1;
246 #ifdef ENABLE_FPS_LIMIT
247 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
248 SDL_Delay(10);
250 frames++;
251 #endif
253 render(s);
256 for (p=1; p<=MAXPLAYERS; p++) {
257 input_device_part_game(PLAYER(s, p).input);
260 clear_screen();
261 rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
262 store_screen();
264 stop_sample(SOUND_AUDIENCE);
265 stop_sample(SOUND_RAIN);
268 bool step( GameState* s) {
269 Uint8 *keys;
270 SDL_Event e;
271 bool ground_event = false;
272 int p;
274 if (s->ball.z < 0) {
275 /* ground event */
276 ground_event = true;
278 s->referee = REFEREE_NORMAL;
280 /* bounce from the ground */
281 if (fabsf(s->ball.move_z) > 0.3) {
282 s->sound_events |= SOUND_EVENT_GROUND;
283 sample_volume_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, fmaxf(0.0, fminf(1.0, fabsf(s->ball.move_z)/2)));
284 pan_sample_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, fmaxf(0.0, fminf(1.0, (s->ball.x)/WIDTH)));
285 s->ball.move_z *= -s->ball.restitution;
286 } else {
287 s->ball.move_z = 0;
289 s->ball.z = 0;
292 if (NET_COLLISION_BALL(s->ball)) {
293 /* the net blocks movement of the ball */
294 while (NET_COLLISION_BALL(s->ball)) {
295 /* make sure the ball appears OUTSIDE of the net */
296 if (s->ball.move_x < 0) {
297 s->ball.x += 1;
298 } else {
299 s->ball.x -= 1;
302 s->ball.move_x = 0;
303 s->ball.move_y = 0;
306 /* see if we have something to score */
307 if (s->score_event == SCORE_UNDECIDED) {
308 if (NET_COLLISION_BALL(s->ball)) {
309 /* the ball "fell" into the net */
310 s->score_event = SCORE_EVENT_NET;
311 s->sound_events |= SOUND_EVENT_OUT;
312 s->status = "net!";
313 } else if (IS_OFFSCREEN(s->ball.x, s->ball.y)) {
314 /* ball flew offscreen */
315 s->score_event = SCORE_EVENT_OFFSCREEN;
316 s->sound_events |= SOUND_EVENT_OUT;
317 s->status = "out!";
318 } else if (ground_event) {
319 /* the ball hit the ground on the screen */
320 if (IS_OUT(s->ball.x, s->ball.y)) {
321 /* the ball bounced in the OUT area */
322 s->score_event = SCORE_EVENT_OUT;
323 s->sound_events |= SOUND_EVENT_OUT;
324 s->status = "out!";
325 } else if (GROUND_IS_VALID(s->ball.last_hit_by, s->ball.x, s->ball.y)) {
326 if (s->ball.ground_hit) {
327 s->score_event = SCORE_EVENT_GROUND_VALID;
328 s->sound_events |= SOUND_EVENT_OUT;
329 s->status = "did not catch the ball!";
330 } else {
331 /* first ground hit in valid area */
332 s->ball.ground_hit = true;
334 } else {
335 /* ball hit somewhere invalid */
336 s->score_event = SCORE_EVENT_GROUND_INVALID;
337 s->sound_events |= SOUND_EVENT_OUT;
338 s->status = "fault!";
343 if (s->score_event != SCORE_UNDECIDED) {
344 /* we have some scoring to do */
345 if (s->score_time == 0) {
346 /* schedule scoring in the future */
347 s->score_time = s->time + SCORING_DELAY;
348 s->referee = REFEREE_OUT;
349 } else if (s->time >= s->score_time) {
350 /* time has ran out - score now */
351 switch (score_game(s)) {
352 case WINNER_PLAYER1:
353 s->status = "player 1 scores";
354 s->referee = REFEREE_PLAYER1;
355 break;
356 case WINNER_PLAYER2:
357 s->status = "player 2 scores";
358 s->referee = REFEREE_PLAYER2;
359 break;
360 default:
361 assert(0);
362 break;
364 s->score_time = 0;
366 strcpy(s->game_score_str, format_game(s));
367 strcpy(s->sets_score_str, format_sets(s));
368 s->text_changed = true;
370 game_setup_serve(s);
371 if (s->location->max_visitors > 100) {
372 s->sound_events |= SOUND_EVENT_APPLAUSE;
375 } else {
376 /* score is still undecided - do the racket swing thing */
377 for (p=1; p<=2; p++) {
378 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).use_power && PLAYER(s, p).power > 30.0 && s->ball.last_hit_by != p) {
379 /* RACKET HIT */
380 if (!s->ball.ground_hit && s->ball.move_x != 0.0) {
381 s->status = "volley!";
382 } else {
383 s->status = format_status(s);
385 switch (PLAYER(s, p).desire) {
386 case DESIRE_NORMAL:
387 /* normal swing */
388 s->ball.move_x = 2.7 + 2.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
389 s->ball.move_z = 1.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
390 break;
391 case DESIRE_TOPSPIN:
392 /* top spin */
393 s->ball.move_x = 1.1 + 2.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
394 s->ball.move_z = 2.5*PLAYER(s, p).power/PLAYER_POWER_MAX;
395 break;
396 case DESIRE_SMASH:
397 /* smash */
398 s->ball.move_x = 4.0 + 3.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
399 s->ball.move_z = 1.1*PLAYER(s, p).power/PLAYER_POWER_MAX;
400 break;
402 s->ball.move_y = get_move_y( s, p);
403 s->sound_events |= SOUND_EVENT_RACKET;
404 s->ball.ground_hit = false;
405 s->ball.inhibit_gravity = false;
406 s->ball.last_hit_by = p;
407 if (p==1) {
408 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.3);
409 } else {
410 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.7);
411 s->ball.move_x *= -1;
417 SDL_PumpEvents();
418 keys = SDL_GetKeyState(NULL);
420 if( s->time%50==0) {
421 if (keys['r']) {
422 if (s->rain == 0) {
423 play_sample_background(SOUND_RAIN);
425 s->rain += 10;
427 if (keys['t']) {
428 s->fog++;
430 if (keys['1']) {
431 s->wind++;
433 if (keys['2']) {
434 s->wind--;
438 if(!(SDL_GetTicks() < fading_start+FADE_DURATION) && !s->is_over) {
439 for (p=1; p<=MAXPLAYERS; p++) {
440 if( PLAYER(s, p).type == PLAYER_TYPE_HUMAN) {
441 input_human(s, p);
442 } else {
443 input_ai(s, p);
448 /* Maemo: The "F6" button is the "Fullscreen" button */
449 /*if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);*/
450 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
452 /* Maemo: The "F4" button is the "Open menu" button */
453 if( keys['p'] || keys[SDLK_F4]) {
454 while( keys['p'] || keys[SDLK_F4]) {
455 SDL_PollEvent( &e);
456 keys = SDL_GetKeyState( NULL);
457 SDL_Delay( 10);
459 while( (keys['p'] || keys[SDLK_F4]) == 0) {
460 SDL_PollEvent( &e);
461 keys = SDL_GetKeyState( NULL);
462 SDL_Delay( 10);
464 while( keys['p'] || keys[SDLK_F4]) {
465 SDL_PollEvent( &e);
466 keys = SDL_GetKeyState( NULL);
467 SDL_Delay( 10);
469 s->was_stopped = true;
472 if( keys[SDLK_ESCAPE] || keys['q']) return true;
474 limit_value( &PLAYER(s, 1).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
475 limit_value( &PLAYER(s, 2).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
477 if (s->ball.move_x > 0 && s->wind > 0) {
478 s->ball.x += fabsf(s->wind)/2;
479 } else if (s->ball.move_x < 0 && s->wind < 0) {
480 s->ball.x -= fabsf(s->wind)/2;
483 s->ball.z += s->ball.move_z;
484 if (!s->ball.inhibit_gravity) {
485 s->ball.move_z += GRAVITY;
488 s->ball.x += s->ball.move_x;
489 s->ball.y += s->ball.move_y;
491 for (p=1; p<=MAXPLAYERS; p++) {
492 if (PLAYER(s, p).use_power) {
493 PLAYER(s, p).power = fmaxf(0.0, fminf(PLAYER(s, p).power*PLAYER(s, p).power_down_factor, PLAYER_POWER_MAX));
497 return false;
500 void render( GameState* s) {
501 int x, y, b;
502 unsigned int i;
503 float zoom;
504 float rotate;
505 int t=1000;
507 if (s->sound_events != 0) {
508 if (s->sound_events & SOUND_EVENT_GROUND) {
509 play_sample(SOUND_GROUND);
511 if (s->sound_events & SOUND_EVENT_OUT) {
512 play_sample(SOUND_OUT);
514 if (s->sound_events & SOUND_EVENT_APPLAUSE) {
515 play_sample(SOUND_APPLAUSE);
517 if (s->sound_events & SOUND_EVENT_RACKET) {
518 play_sample(SOUND_RACKET);
520 s->sound_events = 0;
522 if( s->winner != WINNER_NONE) {
523 if( !s->is_over) {
524 start_fade();
525 s->is_over = true;
527 clear_screen();
528 rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
529 store_screen();
530 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);
531 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
532 font_draw_string(FONT_LARGE, s->game_score_str, (WIDTH-font_get_string_width(FONT_LARGE, s->game_score_str))/2, HEIGHT/2 + 30);
533 updatescr();
534 return;
536 if (s->displayed_court_type != s->location->court_type || s->text_changed || s->old_referee != s->referee) {
537 clear_screen();
538 rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
539 fill_image(s->location->court_type, 120, 120, 400, 250);
540 show_image(GR_COURT, 0, 0, 255);
541 font_draw_string(FONT_XLARGE, s->game_score_str, 14, 14);
542 font_draw_string(FONT_XLARGE, s->sets_score_str, (WIDTH-font_get_string_width(FONT_XLARGE, s->sets_score_str))-14, 14);
543 if (s->location->has_referee) {
544 switch (s->referee) {
545 case REFEREE_NORMAL:
546 t = 1000;
547 break;
548 case REFEREE_OUT:
549 t = 200;
550 break;
551 case REFEREE_PLAYER1:
552 case REFEREE_PLAYER2:
553 t = 400;
554 break;
556 t = (s->time/t)%4;
557 switch (t) {
558 case 0:
559 t=0;
560 break;
561 case 1:
562 t=1;
563 break;
564 case 2:
565 t=0;
566 break;
567 case 3:
568 t=2;
569 break;
571 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
572 if (voice_finished_flag == 0) {
573 show_sprite(GR_TALK, (s->time/150)%2, 2, 280, 45, 255);
576 s->displayed_court_type = s->location->court_type;
577 s->text_changed = false;
578 s->old_referee = s->referee;
579 store_screen();
582 /* show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
583 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);*/
584 show_sprite( GR_RACKET, !PLAYER(s, 1).use_power, 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
585 show_sprite( GR_RACKET, !PLAYER(s, 2).use_power + 2, 4, PLAYER(s, 2).x-RACKET_X_MID, PLAYER(s, 2).y-RACKET_Y_MID, 255);
587 rectangle(10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
588 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
590 rectangle(10, HEIGHT-30, (int)(PLAYER(s, 1).power), 10, 200, 200, 200);
591 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, (int)(PLAYER(s, 2).power), 10, 200, 200, 200);
593 if( s->ball.move_x > 0) {
594 b = (s->time/100)%BALL_STATES;
595 } else if( s->ball.move_x < 0) {
596 b = BALL_STATES-1-(s->time/100)%BALL_STATES;
597 } else {
598 b = 0;
601 rotate = 0.0;
602 zoom = fmaxf(0.5, fminf(1.0, (float)(30.-(s->ball.z))/10.));
603 show_image_rotozoom(GR_SHADOW, s->ball.x, s->ball.y+3, rotate, zoom);
605 rotate = (-s->ball.move_x * (float)((float)s->time/10.));
606 zoom = 1.0 + fmaxf(0.0, (s->ball.z-10.)/100.);
607 show_image_rotozoom(GR_BALL, s->ball.x, s->ball.y - s->ball.z, rotate, zoom);
609 /* Player 1's mouse rectangle */
610 if (PLAYER(s, 1).input != NULL && PLAYER(s, 1).input->type == INPUT_TYPE_MOUSE) {
611 rectangle(PLAYER(s, 1).x-2+5, PLAYER(s, 1).input->my-2, 4, 4, 255, 255, 255);
612 rectangle(PLAYER(s, 1).x-1+5, PLAYER(s, 1).input->my-1, 2, 2, 0, 0, 0);
615 /* Player 2's mouse rectangle */
616 if (PLAYER(s, 2).input != NULL && PLAYER(s, 2).input->type == INPUT_TYPE_MOUSE) {
617 rectangle(PLAYER(s, 2).x-2+5, PLAYER(s, 2).input->my-2, 4, 4, 255, 255, 255);
618 rectangle(PLAYER(s, 2).x-1+5, PLAYER(s, 2).input->my-1, 2, 2, 0, 0, 0);
621 font_draw_string(FONT_MEDIUM, s->status, (WIDTH-font_get_string_width(FONT_MEDIUM, s->status))/2, HEIGHT-50);
623 for (i=0; i<s->rain; i++) {
624 x = rand()%WIDTH;
625 y = rand()%HEIGHT;
626 draw_line_faded(x, y, x+10+s->wind*5, y+30, 0, 0, 255, 100, 200, 255);
628 if (s->rain) {
630 * Cheap-ish update of the whole screen. This can
631 * probably be optimized.
633 update_rect(0, 0, WIDTH, HEIGHT);
636 #ifdef DEBUG
637 line_horiz( PLAYER(s, 1).y, 255, 0, 0);
638 line_horiz( PLAYER(s, 2).y, 0, 0, 255);
639 line_horiz( s->ball.y, 0, 255, 0);
641 line_vert( PLAYER(s, 1).x, 255, 0, 0);
642 line_vert( PLAYER(s, 2).x, 0, 0, 255);
643 line_vert( s->ball.x, 0, 255, 0);
645 line_horiz( GAME_Y_MIN, 100, 100, 100);
646 line_horiz( GAME_Y_MAX, 100, 100, 100);
647 #endif
648 switch (s->fog) {
649 default:
650 case 4:
651 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150-s->windtime/200, 0);
652 case 3:
653 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100-s->windtime/150, 20);
654 case 2:
655 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180-s->windtime/180, 80);
656 case 1:
657 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200-s->windtime/100, 0);
658 case 0:
659 break;
661 if (s->night) {
662 show_image(GR_NIGHT, 0, 0, 255);
665 updatescr();
668 void limit_value( float* value, float min, float max) {
669 if( *value < min) {
670 *value = min;
671 } else if( *value > max) {
672 *value = max;
676 float get_move_y( GameState* s, unsigned char player) {
677 float pct, dest, x_len, y_len;
678 float py, by, pa, move_x;
680 py = (player==1)?(PLAYER(s, 1).y):(PLAYER(s, 2).y);
681 by = s->ball.y - s->ball.z;
682 pa = RACKET_Y_MID*2;
683 move_x = s->ball.move_x;
685 /* -1.0 .. 1.0 for racket hit position */
686 pct = fmaxf(-1.0, fminf(1.0, (by-py)/(pa/2)));
688 /* Y destination for ball */
689 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
691 /* lengths for the ball's journey */
692 if( player == 1) {
693 x_len = GAME_X_MAX - s->ball.x;
694 } else {
695 x_len = s->ball.x - GAME_X_MIN;
697 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
699 /* return the should-be value for move_y */
700 return (y_len*move_x)/(x_len);
703 void input_human(GameState* s, int player) {
704 bool hit, topspin, smash;
705 float move_y;
707 /* For mouse input, hand the player coordinates to the InputDevice */
708 if (PLAYER(s, player).input->type == INPUT_TYPE_MOUSE) {
709 PLAYER(s, player).input->player_x = (int)(PLAYER(s, player).x);
710 PLAYER(s, player).input->player_y = (int)(PLAYER(s, player).y);
713 move_y = PLAYER_MOVE_Y*input_device_get_axis(PLAYER(s, player).input, INPUT_AXIS_Y);
715 hit = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_HIT);
716 topspin = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_TOPSPIN);
717 smash = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_SMASH);
719 if (move_y != 0) {
720 if (fabsf(move_y) > fabsf(move_y*PLAYER(s, player).accelerate)) {
721 move_y *= PLAYER(s, player).accelerate;
723 PLAYER(s, player).y += move_y;
724 PLAYER(s, player).accelerate *= PLAYER_ACCEL_INCREASE;
725 } else {
726 PLAYER(s, player).accelerate = PLAYER_ACCEL_DEFAULT;
729 if(hit || topspin || smash) {
730 PLAYER(s, player).desire = (topspin)?(DESIRE_TOPSPIN):(DESIRE_NORMAL);
731 PLAYER(s, player).desire = (smash)?(DESIRE_SMASH):(PLAYER(s, player).desire);
733 PLAYER(s, player).power = fmaxf(10.0, fminf(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
734 PLAYER(s, player).use_power = false;
735 } else {
736 PLAYER(s, player).use_power = true;
740 void input_ai(GameState* s, int player) {
741 float fact = 1.7;
742 float target;
743 int ball_approaching = 0;
745 if ((PLAYER(s, player).x < GAME_X_MID && s->ball.move_x <= 0) ||
746 (PLAYER(s, player).x > GAME_X_MID && s->ball.move_x >= 0)) {
747 ball_approaching = 1;
750 /* FIXME - this is broken since the new physics model has been introduced */
752 if (fabsf(PLAYER(s, player).y - (s->ball.y-s->ball.z)) > RACKET_Y_MID*5) {
753 fact = 3.5;
756 if(1) {
757 if( PLAYER(s, player).desire == DESIRE_NORMAL && !IS_NEAR_Y_AI(PLAYER(s, player).y, (s->ball.y-s->ball.z)) && ball_approaching) {
758 if( PLAYER(s, player).y < (s->ball.y-s->ball.z)) {
759 PLAYER(s, player).y += fminf(2*fact, (s->ball.y-s->ball.z) - PLAYER(s, player).y);
760 } else if( PLAYER(s, player).y > (s->ball.y-s->ball.z)) {
761 PLAYER(s, player).y -= fminf(2*fact, PLAYER(s, player).y - (s->ball.y-s->ball.z));
765 if (IS_NEAR_Y(PLAYER(s, player).y, (s->ball.y-s->ball.z)) && IS_NEAR_X_AI(PLAYER(s, player).x, s->ball.x) && PLAYER(s, player).power > 90.) {
766 PLAYER(s, player).use_power = true;
767 } else if (ball_approaching) {
768 PLAYER(s, player).power = fmaxf(10., fminf(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
769 PLAYER(s, player).use_power = false;
774 void game_setup_serve( GameState* s) {
775 s->ball.z = 10.0;
776 s->ball.y = GAME_Y_MID;
777 s->ball.move_x = 0.0;
778 s->ball.move_y = 0.0;
779 s->ball.move_z = 0.0;
780 s->ball.last_hit_by = -1;
781 s->ball.inhibit_gravity = true;
783 if( s->serving_player == 1) {
784 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
785 } else {
786 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
790 int score_game(GameState* s) {
791 Player *last_hit_by = NULL;
792 Player *other = NULL;
794 Player* winner = NULL;
795 Player* loser = NULL;
797 /* determine "last hit by" and "other" */
798 if (s->ball.last_hit_by == 1) {
799 last_hit_by = &(PLAYER(s, 1));
800 other = &(PLAYER(s, 2));
801 } else {
802 last_hit_by = &(PLAYER(s, 2));
803 other = &(PLAYER(s, 1));
806 switch (s->score_event) {
807 case SCORE_EVENT_NET:
808 winner = other;
809 break;
810 case SCORE_EVENT_OUT:
811 case SCORE_EVENT_GROUND_INVALID:
812 case SCORE_EVENT_OFFSCREEN:
813 case SCORE_EVENT_GROUND_VALID:
814 if (s->ball.ground_hit) {
815 winner = last_hit_by;
816 } else {
817 winner = other;
819 break;
820 default:
821 break;
824 /* determine loser based on winner */
825 if (winner == last_hit_by) {
826 loser = other;
827 } else {
828 loser = last_hit_by;
831 /* we cannot be in an "impossibly high" set */
832 assert(s->current_set < SETS_TO_WIN*2-1);
834 winner->game++;
835 if( loser->game < winner->game-1) {
836 if( winner->game >= 4) {
837 winner->game = loser->game = 0;
838 winner->sets[s->current_set]++;
840 /* serving is changed when the "game" is over */
841 s->serving_player = (s->serving_player==1)?(2):(1);
843 #ifdef HAVE_VOICE_FILES
844 /* speak the current score */
845 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);
846 #endif
848 /* scoring the set.. */
849 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
850 winner->sets[s->current_set] == 7) {
851 s->current_set++;
852 s->winner = game_get_winner( s);
857 /* forget this event - we've handled it */
858 s->score_event = SCORE_UNDECIDED;
860 if (winner == &PLAYER(s, 1)) {
861 return WINNER_PLAYER1;
862 } else {
863 return WINNER_PLAYER2;
867 char* format_sets( GameState* s) {
868 static char sets[100];
869 static char tmp[100];
870 int i, max = s->current_set;
872 sets[0] = '\0';
874 if( s->winner != WINNER_NONE) {
875 max--;
877 for( i=0; i<=max; i++) {
878 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
879 strcat( sets, tmp);
882 sets[strlen(sets)-2] = '\0';
884 return sets;
887 char* format_game( GameState* s) {
888 static char game[100];
889 static const int game_scoring[] = { 0, 15, 30, 40 };
891 if( PLAYER(s, 1).game < 4 && PLAYER(s, 2).game < 4) {
892 #ifdef HAVE_VOICE_FILES
893 if (PLAYER(s, 1).game > 0 || PLAYER(s, 2).game > 0) {
894 if (PLAYER(s, 1).game == PLAYER(s, 2).game) {
895 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_ALL);
896 } else {
897 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
900 #endif
901 sprintf( game, "%d - %d", game_scoring[PLAYER(s, 1).game], game_scoring[PLAYER(s, 2).game]);
902 } else if( PLAYER(s, 1).game > PLAYER(s, 2).game) {
903 #ifdef HAVE_VOICE_FILES
904 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
905 #endif
906 strcpy( game, "advantage player 1");
907 } else if( PLAYER(s, 1).game < PLAYER(s, 2).game) {
908 #ifdef HAVE_VOICE_FILES
909 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
910 #endif
911 strcpy( game, "advantage player 2");
912 } else {
913 #ifdef HAVE_VOICE_FILES
914 voice_say_list(1, VOICE_DEUCE);
915 #endif
916 strcpy( game, "deuce");
919 return game;
922 char* format_status( GameState* s) {
923 static char status[100];
924 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
926 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]);
928 return status;
931 int game_get_winner( GameState* s) {
932 unsigned int i;
933 int sets[2] = {0};
935 for( i=0; i<s->current_set; i++) {
936 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
937 sets[0]++;
938 } else {
939 sets[1]++;
943 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
944 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
946 return WINNER_NONE;