Class-based Python bot framework (proper)
[tennix.git] / game.c
blobd94f8b565bff374a754b1a8d53e0c60f18c14b82
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 /* smoothen n-gram */
90 for( x = 0; x<NGRAM_STEPS; x++) {
91 for( y = 0; y<NGRAM_STEPS; y++) {
92 for( z = 0; z<NGRAM_STEPS; z++) {
93 s->ngram[x][y][z] = 1;
98 return s;
102 int gamestate_save(GameState* s, const char* filename)
104 Location *location;
105 const char* status;
106 InputDevice* input_devices[MAXPLAYERS];
107 int i, result = 0;
108 FILE* fp = NULL;
109 #ifndef WIN32
110 char tmp[MAXPATHLEN];
112 assert(getcwd(tmp, MAXPATHLEN) == tmp);
113 assert(chdir(getenv("HOME")) == 0);
114 #endif
117 * Process-specific data (pointers to data
118 * structures only of interest to this process)
119 * has to be "removed" before saving.
121 * It can later be restored (in gamestate_load).
124 fp = fopen(filename, "w");
125 if (fp == NULL) {
126 return -1;
129 /* Save data for later recovery + clear */
130 location = s->location;
131 s->location = NULL;
132 status = s->status;
133 s->status = NULL;
134 for (i=1; i<=MAXPLAYERS; i++) {
135 input_devices[i-1] = PLAYER(s, i).input;
136 PLAYER(s, i).input = NULL;
139 if (fwrite(s, sizeof(GameState), 1, fp) != 1) {
140 result = -2;
143 /* Restore process-specific data */
144 s->location = location;
145 s->status = status;
146 for (i=1; i<=MAXPLAYERS; i++) {
147 PLAYER(s, i).input = input_devices[i-1];
150 fclose(fp);
152 #ifndef WIN32
153 assert(chdir(tmp) == 0);
154 #endif
156 return result;
160 GameState* gamestate_load(const char* filename)
162 FILE* fp;
163 GameState* s = NULL;
164 #ifndef WIN32
165 char tmp[MAXPATHLEN];
167 assert(getcwd(tmp, MAXPATHLEN) == tmp);
168 assert(chdir(getenv("HOME")) == 0);
169 #endif
171 fp = fopen(filename, "r");
173 if (fp != NULL) {
174 s = (GameState*)malloc(sizeof(GameState));
175 if (s != NULL) {
176 if (fread(s, sizeof(GameState), 1, fp) == 1) {
177 /* Restore/create process-specific data */
178 s->status = format_status(s);
179 /* FIXME: s->location, players' "input" */
180 } else {
181 free(s);
182 s = NULL;
185 fclose(fp);
188 #ifndef WIN32
189 assert(chdir(tmp) == 0);
190 #endif
192 return s;
196 void gameloop(GameState *s) {
197 Uint32 ot = SDL_GetTicks();
198 Uint32 nt;
199 Uint32 dt = GAME_TICKS;
200 Uint32 diff;
201 Uint32 accumulator = 0;
202 bool quit = false;
203 int p;
205 #ifdef ENABLE_FPS_LIMIT
206 Uint32 ft, frames; /* frame timer and frames */
207 #endif
209 strcpy(s->game_score_str, format_game(s));
210 strcpy(s->sets_score_str, format_sets(s));
211 s->text_changed = true;
213 if (s->rain > 0) {
214 play_sample_background(SOUND_RAIN);
216 if (s->location->max_visitors > 100) {
217 play_sample_loop(SOUND_AUDIENCE);
219 /* Reset the court type, so it is redrawn on first display */
220 s->displayed_court_type = GR_COUNT;
222 for (p=1; p<=MAXPLAYERS; p++) {
223 input_device_join_game(PLAYER(s, p).input, s, p);
226 #ifdef ENABLE_FPS_LIMIT
227 frames = 0;
228 ft = SDL_GetTicks();
229 #endif
230 while( !quit) {
231 nt = SDL_GetTicks();
232 diff = nt-ot;
233 if( diff > 2000) {
234 diff = 0;
237 accumulator += diff;
238 ot = nt;
240 while( accumulator >= dt) {
241 quit = step(s);
242 s->time += dt;
243 s->windtime += s->wind*dt;
244 accumulator -= dt;
246 if( s->was_stopped) {
247 ot = SDL_GetTicks();
248 s->was_stopped = false;
251 if (s->timelimit != 0 && s->time >= s->timelimit) {
252 quit = 1;
255 #ifdef ENABLE_FPS_LIMIT
256 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
257 SDL_Delay(10);
259 frames++;
260 #endif
262 render(s);
265 for (p=1; p<=MAXPLAYERS; p++) {
266 input_device_part_game(PLAYER(s, p).input);
269 clear_screen();
270 store_screen();
272 stop_sample(SOUND_AUDIENCE);
273 stop_sample(SOUND_RAIN);
276 bool step( GameState* s) {
277 Uint8 *keys;
278 SDL_Event e;
279 bool ground_event = false;
280 int p;
282 if (s->ball.z < 0) {
283 /* ground event */
284 ground_event = true;
286 s->referee = REFEREE_NORMAL;
288 /* bounce from the ground */
289 if (absf(s->ball.move_z) > 0.3) {
290 s->sound_events |= SOUND_EVENT_GROUND;
291 sample_volume_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, maxf(0.0, minf(1.0, absf(s->ball.move_z)/2)));
292 pan_sample_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, maxf(0.0, minf(1.0, (s->ball.x)/WIDTH)));
293 s->ball.move_z *= -s->ball.restitution;
294 } else {
295 s->ball.move_z = 0;
297 s->ball.z = 0;
300 if (NET_COLLISION_BALL(s->ball)) {
301 /* the net blocks movement of the ball */
302 while (NET_COLLISION_BALL(s->ball)) {
303 /* make sure the ball appears OUTSIDE of the net */
304 if (s->ball.move_x < 0) {
305 s->ball.x += 1;
306 } else {
307 s->ball.x -= 1;
310 s->ball.move_x = 0;
311 s->ball.move_y = 0;
314 /* see if we have something to score */
315 if (s->score_event == SCORE_UNDECIDED) {
316 if (NET_COLLISION_BALL(s->ball)) {
317 /* the ball "fell" into the net */
318 s->score_event = SCORE_EVENT_NET;
319 s->sound_events |= SOUND_EVENT_OUT;
320 s->status = "net!";
321 } else if (IS_OFFSCREEN(s->ball.x, s->ball.y)) {
322 /* ball flew offscreen */
323 s->score_event = SCORE_EVENT_OFFSCREEN;
324 s->sound_events |= SOUND_EVENT_OUT;
325 s->status = "out!";
326 } else if (ground_event) {
327 /* the ball hit the ground on the screen */
328 if (IS_OUT(s->ball.x, s->ball.y)) {
329 /* the ball bounced in the OUT area */
330 s->score_event = SCORE_EVENT_OUT;
331 s->sound_events |= SOUND_EVENT_OUT;
332 s->status = "out!";
333 } else if (GROUND_IS_VALID(s->ball.last_hit_by, s->ball.x, s->ball.y)) {
334 if (s->ball.ground_hit) {
335 s->score_event = SCORE_EVENT_GROUND_VALID;
336 s->sound_events |= SOUND_EVENT_OUT;
337 s->status = "did not catch the ball!";
338 } else {
339 /* first ground hit in valid area */
340 s->ball.ground_hit = true;
342 } else {
343 /* ball hit somewhere invalid */
344 s->score_event = SCORE_EVENT_GROUND_INVALID;
345 s->sound_events |= SOUND_EVENT_OUT;
346 s->status = "fault!";
351 if (s->score_event != SCORE_UNDECIDED) {
352 /* we have some scoring to do */
353 if (s->score_time == 0) {
354 /* schedule scoring in the future */
355 s->score_time = s->time + SCORING_DELAY;
356 s->referee = REFEREE_OUT;
357 } else if (s->time >= s->score_time) {
358 /* time has ran out - score now */
359 switch (score_game(s)) {
360 case WINNER_PLAYER1:
361 s->status = "player 1 scores";
362 s->referee = REFEREE_PLAYER1;
363 break;
364 case WINNER_PLAYER2:
365 s->status = "player 2 scores";
366 s->referee = REFEREE_PLAYER2;
367 break;
368 default:
369 assert(0);
370 break;
372 s->score_time = 0;
374 strcpy(s->game_score_str, format_game(s));
375 strcpy(s->sets_score_str, format_sets(s));
376 s->text_changed = true;
378 game_setup_serve(s);
379 if (s->location->max_visitors > 100) {
380 s->sound_events |= SOUND_EVENT_APPLAUSE;
382 /*FIXME n-gram predictor broken
383 s->history_size = 0;
384 s->history_is_locked = 0;
385 s->ngram_prediction = 0.0;*/
387 } else {
388 /* score is still undecided - do the racket swing thing */
389 for (p=1; p<=2; p++) {
390 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) {
391 /* RACKET HIT */
392 if (!s->ball.ground_hit && s->ball.move_x != 0.0) {
393 s->status = "volley!";
394 } else {
395 s->status = format_status(s);
397 switch (PLAYER(s, p).desire) {
398 case DESIRE_NORMAL:
399 /* normal swing */
400 s->ball.move_x = 2.7 + 2.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
401 s->ball.move_z = 1.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
402 break;
403 case DESIRE_TOPSPIN:
404 /* top spin */
405 s->ball.move_x = 1.1 + 2.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
406 s->ball.move_z = 2.5*PLAYER(s, p).power/PLAYER_POWER_MAX;
407 break;
408 case DESIRE_SMASH:
409 /* smash */
410 s->ball.move_x = 4.0 + 3.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
411 s->ball.move_z = 1.1*PLAYER(s, p).power/PLAYER_POWER_MAX;
412 break;
414 s->ball.move_y = get_move_y( s, p);
415 s->sound_events |= SOUND_EVENT_RACKET;
416 s->ball.ground_hit = false;
417 s->ball.inhibit_gravity = false;
418 s->ball.last_hit_by = p;
419 if (p==1) {
420 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.3);
421 } else {
422 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.7);
423 s->ball.move_x *= -1;
429 #if 0
430 FIXME n-gram predictor broken
431 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
432 if( IS_OUT_X(s->ball.x)) {
433 /*if( !s->history_is_locked && s->referee != REFEREE_OUT) {
434 s->history[s->history_size] = (int)(NGRAM_STEPS*s->ball.y/HEIGHT);
435 s->history_size++;
436 if( s->history_size == 3) {
437 s->ngram[s->history[0]][s->history[1]][s->history[2]] += 10;
438 s->ngram_prediction = ngram_predictor( s);
439 s->history[0] = s->history[1];
440 s->history[1] = s->history[2];
441 s->history_size--;
443 s->history_is_locked = true;
446 } else {
447 /*s->history_is_locked = false;*/
449 #endif
451 SDL_PumpEvents();
452 keys = SDL_GetKeyState(NULL);
454 if( s->time%50==0) {
455 if (keys['r']) {
456 if (s->rain == 0) {
457 play_sample_background(SOUND_RAIN);
459 s->rain += 10;
461 if (keys['t']) {
462 s->fog++;
464 if (keys['1']) {
465 s->wind++;
467 if (keys['2']) {
468 s->wind--;
472 if(!(SDL_GetTicks() < fading_start+FADE_DURATION) && !s->is_over) {
473 for (p=1; p<=MAXPLAYERS; p++) {
474 if( PLAYER(s, p).type == PLAYER_TYPE_HUMAN) {
475 input_human(s, p);
476 } else {
477 input_ai(s, p);
482 /* Maemo: The "F6" button is the "Fullscreen" button */
483 /*if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);*/
484 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
486 /* Maemo: The "F4" button is the "Open menu" button */
487 if( keys['p'] || keys[SDLK_F4]) {
488 while( keys['p'] || keys[SDLK_F4]) {
489 SDL_PollEvent( &e);
490 keys = SDL_GetKeyState( NULL);
491 SDL_Delay( 10);
493 while( (keys['p'] || keys[SDLK_F4]) == 0) {
494 SDL_PollEvent( &e);
495 keys = SDL_GetKeyState( NULL);
496 SDL_Delay( 10);
498 while( keys['p'] || keys[SDLK_F4]) {
499 SDL_PollEvent( &e);
500 keys = SDL_GetKeyState( NULL);
501 SDL_Delay( 10);
503 s->was_stopped = true;
506 if( keys[SDLK_ESCAPE] || keys['q']) return true;
508 limit_value( &PLAYER(s, 1).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
509 limit_value( &PLAYER(s, 2).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
511 if (s->ball.move_x > 0 && s->wind > 0) {
512 s->ball.x += absf(s->wind)/2;
513 } else if (s->ball.move_x < 0 && s->wind < 0) {
514 s->ball.x -= absf(s->wind)/2;
517 s->ball.z += s->ball.move_z;
518 if (!s->ball.inhibit_gravity) {
519 s->ball.move_z += GRAVITY;
522 s->ball.x += s->ball.move_x;
523 s->ball.y += s->ball.move_y;
525 for (p=1; p<=MAXPLAYERS; p++) {
526 if (PLAYER(s, p).use_power) {
527 PLAYER(s, p).power = maxf(0.0, minf(PLAYER(s, p).power*PLAYER(s, p).power_down_factor, PLAYER_POWER_MAX));
531 return false;
534 void render( GameState* s) {
535 int x, y, b;
536 unsigned int i;
537 float zoom;
538 float rotate;
539 #ifdef EXTENDED_REFEREE
540 int t=1000;
541 #endif
542 if (s->sound_events != 0) {
543 if (s->sound_events & SOUND_EVENT_GROUND) {
544 play_sample(SOUND_GROUND);
546 if (s->sound_events & SOUND_EVENT_OUT) {
547 play_sample(SOUND_OUT);
549 if (s->sound_events & SOUND_EVENT_APPLAUSE) {
550 play_sample(SOUND_APPLAUSE);
552 if (s->sound_events & SOUND_EVENT_RACKET) {
553 play_sample(SOUND_RACKET);
555 s->sound_events = 0;
557 if( s->winner != WINNER_NONE) {
558 if( !s->is_over) {
559 start_fade();
560 s->is_over = true;
562 clear_screen();
563 store_screen();
564 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);
565 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
566 font_draw_string(FONT_LARGE, s->game_score_str, (WIDTH-font_get_string_width(FONT_LARGE, s->game_score_str))/2, HEIGHT/2 + 30);
567 updatescr();
568 return;
570 if (s->displayed_court_type != s->location->court_type || s->text_changed || s->old_referee != s->referee) {
571 clear_screen();
572 fill_image(s->location->court_type, 120, 120, 400, 250);
573 show_image(GR_COURT, 0, 0, 255);
574 font_draw_string(FONT_XLARGE, s->game_score_str, 14, 14);
575 font_draw_string(FONT_XLARGE, s->sets_score_str, (WIDTH-font_get_string_width(FONT_XLARGE, s->sets_score_str))-14, 14);
576 if (s->location->has_referee) {
577 #ifdef EXTENDED_REFEREE
578 switch (s->referee) {
579 case REFEREE_NORMAL:
580 t = 1000;
581 break;
582 case REFEREE_OUT:
583 t = 200;
584 break;
585 case REFEREE_PLAYER1:
586 case REFEREE_PLAYER2:
587 t = 400;
588 break;
590 t = (s->time/t)%4;
591 switch (t) {
592 case 0:
593 t=0;
594 break;
595 case 1:
596 t=1;
597 break;
598 case 2:
599 t=0;
600 break;
601 case 3:
602 t=2;
603 break;
605 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
606 if (voice_finished_flag == 0) {
607 show_sprite(GR_TALK, (s->time/150)%2, 2, 280, 45, 255);
609 #else
610 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
611 #endif
613 s->displayed_court_type = s->location->court_type;
614 s->text_changed = false;
615 s->old_referee = s->referee;
616 store_screen();
619 /* show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
620 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);*/
621 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);
622 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);
624 rectangle(10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
625 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
627 rectangle(10, HEIGHT-30, (int)(PLAYER(s, 1).power), 10, 200, 200, 200);
628 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, (int)(PLAYER(s, 2).power), 10, 200, 200, 200);
630 if( s->ball.move_x > 0) {
631 b = (s->time/100)%BALL_STATES;
632 } else if( s->ball.move_x < 0) {
633 b = BALL_STATES-1-(s->time/100)%BALL_STATES;
634 } else {
635 b = 0;
638 rotate = 0.0;
639 zoom = maxf(0.5, minf(1.0, (float)(30.-(s->ball.z))/10.));
640 show_image_rotozoom(GR_SHADOW, s->ball.x, s->ball.y+3, rotate, zoom);
642 rotate = (-s->ball.move_x * (float)((float)s->time/10.));
643 zoom = 1.0 + maxf(0.0, (s->ball.z-10.)/100.);
644 show_image_rotozoom(GR_BALL, s->ball.x, s->ball.y - s->ball.z, rotate, zoom);
646 /* Player 1's mouse rectangle */
647 if (PLAYER(s, 1).input != NULL && PLAYER(s, 1).input->type == INPUT_TYPE_MOUSE) {
648 rectangle(PLAYER(s, 1).x-2+5, PLAYER(s, 1).input->my-2, 4, 4, 255, 255, 255);
649 rectangle(PLAYER(s, 1).x-1+5, PLAYER(s, 1).input->my-1, 2, 2, 0, 0, 0);
652 /* Player 2's mouse rectangle */
653 if (PLAYER(s, 2).input != NULL && PLAYER(s, 2).input->type == INPUT_TYPE_MOUSE) {
654 rectangle(PLAYER(s, 2).x-2+5, PLAYER(s, 2).input->my-2, 4, 4, 255, 255, 255);
655 rectangle(PLAYER(s, 2).x-1+5, PLAYER(s, 2).input->my-1, 2, 2, 0, 0, 0);
658 font_draw_string(FONT_MEDIUM, s->status, (WIDTH-font_get_string_width(FONT_MEDIUM, s->status))/2, HEIGHT-50);
660 for (i=0; i<s->rain; i++) {
661 x = rand()%WIDTH;
662 y = rand()%HEIGHT;
663 draw_line_faded(x, y, x+10+s->wind*5, y+30, 0, 0, 255, 100, 200, 255);
665 if (s->rain) {
667 * Cheap-ish update of the whole screen. This can
668 * probably be optimized.
670 update_rect(0, 0, WIDTH, HEIGHT);
673 #ifdef DEBUG
674 line_horiz( PLAYER(s, 1).y, 255, 0, 0);
675 line_horiz( PLAYER(s, 2).y, 0, 0, 255);
676 line_horiz( s->ball.y, 0, 255, 0);
678 line_vert( PLAYER(s, 1).x, 255, 0, 0);
679 line_vert( PLAYER(s, 2).x, 0, 0, 255);
680 line_vert( s->ball.x, 0, 255, 0);
682 line_horiz( GAME_Y_MIN, 100, 100, 100);
683 line_horiz( GAME_Y_MAX, 100, 100, 100);
684 #endif
685 switch (s->fog) {
686 default:
687 case 4:
688 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150-s->windtime/200, 0);
689 case 3:
690 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100-s->windtime/150, 20);
691 case 2:
692 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180-s->windtime/180, 80);
693 case 1:
694 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200-s->windtime/100, 0);
695 case 0:
696 break;
698 if (s->night) {
699 show_image(GR_NIGHT, 0, 0, 255);
702 updatescr();
705 void limit_value( float* value, float min, float max) {
706 if( *value < min) {
707 *value = min;
708 } else if( *value > max) {
709 *value = max;
713 float get_move_y( GameState* s, unsigned char player) {
714 float pct, dest, x_len, y_len;
715 float py, by, pa, move_x;
717 py = (player==1)?(PLAYER(s, 1).y):(PLAYER(s, 2).y);
718 by = s->ball.y - s->ball.z;
719 pa = RACKET_Y_MID*2;
720 move_x = s->ball.move_x;
722 /* -1.0 .. 1.0 for racket hit position */
723 pct = maxf(-1.0, minf(1.0, (by-py)/(pa/2)));
725 /* Y destination for ball */
726 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
728 /* lengths for the ball's journey */
729 if( player == 1) {
730 x_len = GAME_X_MAX - s->ball.x;
731 } else {
732 x_len = s->ball.x - GAME_X_MIN;
734 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
736 /* return the should-be value for move_y */
737 return (y_len*move_x)/(x_len);
740 void input_human(GameState* s, int player) {
741 bool hit, topspin, smash;
742 float move_y;
744 /* For mouse input, hand the player coordinates to the InputDevice */
745 if (PLAYER(s, player).input->type == INPUT_TYPE_MOUSE) {
746 PLAYER(s, player).input->player_x = (int)(PLAYER(s, player).x);
747 PLAYER(s, player).input->player_y = (int)(PLAYER(s, player).y);
750 move_y = PLAYER_MOVE_Y*input_device_get_axis(PLAYER(s, player).input, INPUT_AXIS_Y);
752 hit = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_HIT);
753 topspin = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_TOPSPIN);
754 smash = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_SMASH);
756 if (move_y != 0) {
757 if (absf(move_y) > absf(move_y*PLAYER(s, player).accelerate)) {
758 move_y *= PLAYER(s, player).accelerate;
760 PLAYER(s, player).y += move_y;
761 PLAYER(s, player).accelerate *= PLAYER_ACCEL_INCREASE;
762 } else {
763 PLAYER(s, player).accelerate = PLAYER_ACCEL_DEFAULT;
766 if(hit || topspin || smash) {
767 PLAYER(s, player).desire = (topspin)?(DESIRE_TOPSPIN):(DESIRE_NORMAL);
768 PLAYER(s, player).desire = (smash)?(DESIRE_SMASH):(PLAYER(s, player).desire);
770 PLAYER(s, player).power = maxf(10.0, minf(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
771 PLAYER(s, player).use_power = false;
772 } else {
773 PLAYER(s, player).use_power = true;
777 void input_ai(GameState* s, int player) {
778 float fact = 1.7;
779 float target;
780 int ball_approaching = 0;
782 if ((PLAYER(s, player).x < GAME_X_MID && s->ball.move_x <= 0) ||
783 (PLAYER(s, player).x > GAME_X_MID && s->ball.move_x >= 0)) {
784 ball_approaching = 1;
787 /* FIXME - this is broken since the new physics model has been introduced */
789 if (absf(PLAYER(s, player).y - (s->ball.y-s->ball.z)) > RACKET_Y_MID*5) {
790 fact = 3.5;
793 if(1) {
794 if( PLAYER(s, player).desire == DESIRE_NORMAL && !IS_NEAR_Y_AI(PLAYER(s, player).y, (s->ball.y-s->ball.z)) && ball_approaching) {
795 if( PLAYER(s, player).y < (s->ball.y-s->ball.z)) {
796 PLAYER(s, player).y += minf(2*fact, (s->ball.y-s->ball.z) - PLAYER(s, player).y);
797 } else if( PLAYER(s, player).y > (s->ball.y-s->ball.z)) {
798 PLAYER(s, player).y -= minf(2*fact, PLAYER(s, player).y - (s->ball.y-s->ball.z));
802 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.) {
803 PLAYER(s, player).use_power = true;
804 } else if (ball_approaching) {
805 PLAYER(s, player).power = maxf(10., minf(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
806 PLAYER(s, player).use_power = false;
808 } else if( s->ngram_prediction > 0.0) {
809 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
810 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
812 if( PLAYER(s, player).desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( PLAYER(s, player).y, target)) {
813 if( PLAYER(s, player).y < target) {
814 PLAYER(s, player).y += minf(fact, (target-PLAYER(s, player).y)/40.0);
815 } else if( PLAYER(s, player).y > target) {
816 PLAYER(s, player).y -= minf(fact, (PLAYER(s, player).y-target)/40.0);
822 void game_setup_serve( GameState* s) {
823 s->ball.z = 10.0;
824 s->ball.y = GAME_Y_MID;
825 s->ball.move_x = 0.0;
826 s->ball.move_y = 0.0;
827 s->ball.move_z = 0.0;
828 s->ball.last_hit_by = -1;
829 s->ball.inhibit_gravity = true;
831 if( s->serving_player == 1) {
832 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
833 } else {
834 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
838 float ngram_predictor( GameState* s) {
839 unsigned int count = 0;
840 unsigned long sum = 0;
841 int x, y, z;
842 float result;
844 if( s->history_size < 3) {
845 return 0.0;
848 x = s->history[1];
849 y = s->history[2];
851 for( z = 0; z<NGRAM_STEPS; z++) {
852 count += s->ngram[x][y][z];
853 sum += z * s->ngram[x][y][z];
856 result = ((float)(sum))/((float)(count));
857 #ifdef DEBUG
858 printf( "predicting next = %.2f\n", result);
859 #endif
861 return result;
864 int score_game(GameState* s) {
865 Player *last_hit_by = NULL;
866 Player *other = NULL;
868 Player* winner = NULL;
869 Player* loser = NULL;
871 /* determine "last hit by" and "other" */
872 if (s->ball.last_hit_by == 1) {
873 last_hit_by = &(PLAYER(s, 1));
874 other = &(PLAYER(s, 2));
875 } else {
876 last_hit_by = &(PLAYER(s, 2));
877 other = &(PLAYER(s, 1));
880 switch (s->score_event) {
881 case SCORE_EVENT_NET:
882 winner = other;
883 break;
884 case SCORE_EVENT_OUT:
885 case SCORE_EVENT_GROUND_INVALID:
886 case SCORE_EVENT_OFFSCREEN:
887 case SCORE_EVENT_GROUND_VALID:
888 if (s->ball.ground_hit) {
889 winner = last_hit_by;
890 } else {
891 winner = other;
893 break;
894 default:
895 break;
898 /* determine loser based on winner */
899 if (winner == last_hit_by) {
900 loser = other;
901 } else {
902 loser = last_hit_by;
905 /* we cannot be in an "impossibly high" set */
906 assert(s->current_set < SETS_TO_WIN*2-1);
908 winner->game++;
909 if( loser->game < winner->game-1) {
910 if( winner->game >= 4) {
911 winner->game = loser->game = 0;
912 winner->sets[s->current_set]++;
914 /* serving is changed when the "game" is over */
915 s->serving_player = (s->serving_player==1)?(2):(1);
917 #ifdef HAVE_VOICE_FILES
918 /* speak the current score */
919 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);
920 #endif
922 /* scoring the set.. */
923 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
924 winner->sets[s->current_set] == 7) {
925 s->current_set++;
926 s->winner = game_get_winner( s);
931 /* forget this event - we've handled it */
932 s->score_event = SCORE_UNDECIDED;
934 if (winner == &PLAYER(s, 1)) {
935 return WINNER_PLAYER1;
936 } else {
937 return WINNER_PLAYER2;
941 char* format_sets( GameState* s) {
942 static char sets[100];
943 static char tmp[100];
944 int i, max = s->current_set;
946 sets[0] = '\0';
948 if( s->winner != WINNER_NONE) {
949 max--;
951 for( i=0; i<=max; i++) {
952 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
953 strcat( sets, tmp);
956 sets[strlen(sets)-2] = '\0';
958 return sets;
961 char* format_game( GameState* s) {
962 static char game[100];
963 static const int game_scoring[] = { 0, 15, 30, 40 };
965 if( PLAYER(s, 1).game < 4 && PLAYER(s, 2).game < 4) {
966 #ifdef HAVE_VOICE_FILES
967 if (PLAYER(s, 1).game > 0 || PLAYER(s, 2).game > 0) {
968 if (PLAYER(s, 1).game == PLAYER(s, 2).game) {
969 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_ALL);
970 } else {
971 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
974 #endif
975 sprintf( game, "%d - %d", game_scoring[PLAYER(s, 1).game], game_scoring[PLAYER(s, 2).game]);
976 } else if( PLAYER(s, 1).game > PLAYER(s, 2).game) {
977 #ifdef HAVE_VOICE_FILES
978 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
979 #endif
980 strcpy( game, "advantage player 1");
981 } else if( PLAYER(s, 1).game < PLAYER(s, 2).game) {
982 #ifdef HAVE_VOICE_FILES
983 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
984 #endif
985 strcpy( game, "advantage player 2");
986 } else {
987 #ifdef HAVE_VOICE_FILES
988 voice_say_list(1, VOICE_DEUCE);
989 #endif
990 strcpy( game, "deuce");
993 return game;
996 char* format_status( GameState* s) {
997 static char status[100];
998 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
1000 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]);
1002 return status;
1005 int game_get_winner( GameState* s) {
1006 unsigned int i;
1007 int sets[2] = {0};
1009 for( i=0; i<s->current_set; i++) {
1010 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
1011 sets[0]++;
1012 } else {
1013 sets[1]++;
1017 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
1018 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
1020 return WINNER_NONE;