Fix sound panning for ground hit
[tennix.git] / game.c
blob60041a47e1801a07bd35fdc7e14e962114375274
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>
29 #include "tennix.h"
30 #include "game.h"
31 #include "graphics.h"
32 #include "input.h"
33 #include "sound.h"
34 #include "util.h"
37 GameState *gamestate_new() {
38 int x, y, z;
39 GameState *s;
41 GameState template = {
42 NULL,
43 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6, false, -1, false },
45 { 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 },
46 { 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 },
51 "welcome to tennix " VERSION,
52 { 0 },
53 { 0 },
54 REFEREE_NORMAL,
56 WINNER_NONE,
57 false,
58 GR_COUNT,
59 { 0 },
61 false,
62 { { { 0 } } },
63 0.0,
64 0, /* sound events */
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 if (s->location->max_visitors > 100) {
120 play_sample_loop(SOUND_AUDIENCE);
122 /* Reset the court type, so it is redrawn on first display */
123 s->displayed_court_type = GR_COUNT;
125 #ifdef ENABLE_FPS_LIMIT
126 frames = 0;
127 ft = SDL_GetTicks();
128 #endif
129 while( !quit) {
130 nt = SDL_GetTicks();
131 diff = nt-ot;
132 if( diff > 2000) {
133 diff = 0;
136 accumulator += diff;
137 ot = nt;
139 while( accumulator >= dt) {
140 quit = step(s);
141 s->time += dt;
142 s->windtime += s->wind*dt;
143 accumulator -= dt;
145 if( s->was_stopped) {
146 ot = SDL_GetTicks();
147 s->was_stopped = false;
150 if (s->timelimit != 0 && s->time >= s->timelimit) {
151 quit = 1;
154 #ifdef ENABLE_FPS_LIMIT
155 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
156 SDL_Delay(10);
158 frames++;
159 #endif
161 render(s);
164 clear_screen();
165 store_screen();
167 stop_sample(SOUND_AUDIENCE);
168 stop_sample(SOUND_RAIN);
171 bool step( GameState* s) {
172 Uint8 *keys;
173 SDL_Event e;
174 bool ground_event = false;
175 int p;
177 if (s->ball.z < 0) {
178 /* ground event */
179 ground_event = true;
181 s->referee = REFEREE_NORMAL;
183 /* bounce from the ground */
184 if (absf(s->ball.move_z) > 0.3) {
185 s->sound_events |= SOUND_EVENT_GROUND;
186 sample_volume_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, maxf(0.0, minf(1.0, absf(s->ball.move_z)/2)));
187 pan_sample_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, maxf(0.0, minf(1.0, (s->ball.x)/WIDTH)));
188 s->ball.move_z *= -s->ball.restitution;
189 } else {
190 s->ball.move_z = 0;
192 s->ball.z = 0;
195 if (NET_COLLISION_BALL(s->ball)) {
196 /* the net blocks movement of the ball */
197 while (NET_COLLISION_BALL(s->ball)) {
198 /* make sure the ball appears OUTSIDE of the net */
199 if (s->ball.move_x < 0) {
200 s->ball.x += 1;
201 } else {
202 s->ball.x -= 1;
205 s->ball.move_x = 0;
206 s->ball.move_y = 0;
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->sound_events |= SOUND_EVENT_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->sound_events |= SOUND_EVENT_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->sound_events |= SOUND_EVENT_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->sound_events |= SOUND_EVENT_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->sound_events |= SOUND_EVENT_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 if (s->location->max_visitors > 100) {
275 s->sound_events |= SOUND_EVENT_APPLAUSE;
277 /*FIXME n-gram predictor broken
278 s->history_size = 0;
279 s->history_is_locked = 0;
280 s->ngram_prediction = 0.0;*/
282 } else {
283 /* score is still undecided - do the racket swing thing */
284 for (p=1; p<=2; p++) {
285 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) {
286 /* RACKET HIT */
287 if (!s->ball.ground_hit && s->ball.move_x != 0.0) {
288 s->status = "volley!";
289 } else {
290 s->status = format_status(s);
292 switch (PLAYER(s, p).desire) {
293 case DESIRE_NORMAL:
294 /* normal swing */
295 s->ball.move_x = 2.7 + 2.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
296 s->ball.move_z = 1.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
297 break;
298 case DESIRE_TOPSPIN:
299 /* top spin */
300 s->ball.move_x = 1.1 + 2.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
301 s->ball.move_z = 2.5*PLAYER(s, p).power/PLAYER_POWER_MAX;
302 break;
303 case DESIRE_SMASH:
304 /* smash */
305 s->ball.move_x = 4.0 + 3.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
306 s->ball.move_z = 1.1*PLAYER(s, p).power/PLAYER_POWER_MAX;
307 break;
309 s->ball.move_y = get_move_y( s, p);
310 s->sound_events |= SOUND_EVENT_RACKET;
311 s->ball.ground_hit = false;
312 s->ball.inhibit_gravity = false;
313 s->ball.last_hit_by = p;
314 if (p==1) {
315 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.3);
316 } else {
317 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.7);
318 s->ball.move_x *= -1;
324 #if 0
325 FIXME n-gram predictor broken
326 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
327 if( IS_OUT_X(s->ball.x)) {
328 /*if( !s->history_is_locked && s->referee != REFEREE_OUT) {
329 s->history[s->history_size] = (int)(NGRAM_STEPS*s->ball.y/HEIGHT);
330 s->history_size++;
331 if( s->history_size == 3) {
332 s->ngram[s->history[0]][s->history[1]][s->history[2]] += 10;
333 s->ngram_prediction = ngram_predictor( s);
334 s->history[0] = s->history[1];
335 s->history[1] = s->history[2];
336 s->history_size--;
338 s->history_is_locked = true;
341 } else {
342 /*s->history_is_locked = false;*/
344 #endif
346 SDL_PumpEvents();
347 keys = SDL_GetKeyState(NULL);
349 if( s->time%50==0) {
350 if (keys['r']) {
351 if (s->rain == 0) {
352 play_sample_background(SOUND_RAIN);
354 s->rain += 10;
356 if (keys['t']) {
357 s->fog++;
359 if (keys['1']) {
360 s->wind++;
362 if (keys['2']) {
363 s->wind--;
367 if(!(SDL_GetTicks() < fading_start+FADE_DURATION) && !s->is_over) {
368 for (p=1; p<=MAXPLAYERS; p++) {
369 if( PLAYER(s, p).type == PLAYER_TYPE_HUMAN) {
370 input_human(s, p);
371 } else {
372 input_ai(s, p);
377 /* Maemo: The "F6" button is the "Fullscreen" button */
378 /*if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);*/
379 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
381 /* Maemo: The "F4" button is the "Open menu" button */
382 if( keys['p'] || keys[SDLK_F4]) {
383 while( keys['p'] || keys[SDLK_F4]) {
384 SDL_PollEvent( &e);
385 keys = SDL_GetKeyState( NULL);
386 SDL_Delay( 10);
388 while( (keys['p'] || keys[SDLK_F4]) == 0) {
389 SDL_PollEvent( &e);
390 keys = SDL_GetKeyState( NULL);
391 SDL_Delay( 10);
393 while( keys['p'] || keys[SDLK_F4]) {
394 SDL_PollEvent( &e);
395 keys = SDL_GetKeyState( NULL);
396 SDL_Delay( 10);
398 s->was_stopped = true;
401 if( keys[SDLK_ESCAPE] || keys['q']) return true;
403 limit_value( &PLAYER(s, 1).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
404 limit_value( &PLAYER(s, 2).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
406 if (s->ball.move_x > 0 && s->wind > 0) {
407 s->ball.x += absf(s->wind)/2;
408 } else if (s->ball.move_x < 0 && s->wind < 0) {
409 s->ball.x -= absf(s->wind)/2;
412 s->ball.z += s->ball.move_z;
413 if (!s->ball.inhibit_gravity) {
414 s->ball.move_z += GRAVITY;
417 s->ball.x += s->ball.move_x;
418 s->ball.y += s->ball.move_y;
420 for (p=1; p<=MAXPLAYERS; p++) {
421 if (PLAYER(s, p).use_power) {
422 PLAYER(s, p).power = maxf(0.0, minf(PLAYER(s, p).power*PLAYER(s, p).power_down_factor, PLAYER_POWER_MAX));
426 return false;
429 void render( GameState* s) {
430 int x, y, b;
431 unsigned int i;
432 float zoom;
433 float rotate;
434 #ifdef EXTENDED_REFEREE
435 int t=1000;
436 #endif
437 if (s->sound_events != 0) {
438 if (s->sound_events & SOUND_EVENT_GROUND) {
439 play_sample(SOUND_GROUND);
441 if (s->sound_events & SOUND_EVENT_OUT) {
442 play_sample(SOUND_OUT);
444 if (s->sound_events & SOUND_EVENT_APPLAUSE) {
445 play_sample(SOUND_APPLAUSE);
447 if (s->sound_events & SOUND_EVENT_RACKET) {
448 play_sample(SOUND_RACKET);
450 s->sound_events = 0;
452 if( s->winner != WINNER_NONE) {
453 if( !s->is_over) {
454 start_fade();
455 s->is_over = true;
457 clear_screen();
458 store_screen();
459 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);
460 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
461 font_draw_string(FONT_LARGE, s->game_score_str, (WIDTH-font_get_string_width(FONT_LARGE, s->game_score_str))/2, HEIGHT/2 + 30);
462 updatescr();
463 return;
465 if (s->displayed_court_type != s->location->court_type || s->text_changed || s->old_referee != s->referee) {
466 clear_screen();
467 fill_image(s->location->court_type, 120, 120, 400, 250);
468 show_image(GR_COURT, 0, 0, 255);
469 font_draw_string(FONT_XLARGE, s->game_score_str, 14, 14);
470 font_draw_string(FONT_XLARGE, s->sets_score_str, (WIDTH-font_get_string_width(FONT_XLARGE, s->sets_score_str))-14, 14);
471 if (s->location->has_referee) {
472 #ifdef EXTENDED_REFEREE
473 switch (s->referee) {
474 case REFEREE_NORMAL:
475 t = 1000;
476 break;
477 case REFEREE_OUT:
478 t = 200;
479 break;
480 case REFEREE_PLAYER1:
481 case REFEREE_PLAYER2:
482 t = 400;
483 break;
485 t = (s->time/t)%4;
486 switch (t) {
487 case 0:
488 t=0;
489 break;
490 case 1:
491 t=1;
492 break;
493 case 2:
494 t=0;
495 break;
496 case 3:
497 t=2;
498 break;
500 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
501 if (voice_finished_flag == 0) {
502 show_sprite(GR_TALK, (s->time/150)%2, 2, 280, 45, 255);
504 #else
505 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
506 #endif
508 s->displayed_court_type = s->location->court_type;
509 s->text_changed = false;
510 s->old_referee = s->referee;
511 store_screen();
514 /* show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
515 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);*/
516 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);
517 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);
519 rectangle(10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
520 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
522 rectangle(10, HEIGHT-30, (int)(PLAYER(s, 1).power), 10, 200, 200, 200);
523 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, (int)(PLAYER(s, 2).power), 10, 200, 200, 200);
525 if( s->ball.move_x > 0) {
526 b = (s->time/100)%BALL_STATES;
527 } else if( s->ball.move_x < 0) {
528 b = BALL_STATES-1-(s->time/100)%BALL_STATES;
529 } else {
530 b = 0;
533 rotate = 0.0;
534 zoom = maxf(0.5, minf(1.0, (float)(30.-(s->ball.z))/10.));
535 show_image_rotozoom(GR_SHADOW, s->ball.x, s->ball.y+3, rotate, zoom);
537 rotate = (-s->ball.move_x * (float)((float)s->time/10.));
538 zoom = 1.0 + maxf(0.0, (s->ball.z-10.)/100.);
539 show_image_rotozoom(GR_BALL, s->ball.x, s->ball.y - s->ball.z, rotate, zoom);
541 /* Player 1's mouse rectangle */
542 if (!(PLAYER(s, 1).mouse_locked)) {
543 rectangle(PLAYER(s, 1).x-2+5, PLAYER(s, 1).mouse_y-2, 4, 4, 255, 255, 255);
544 rectangle(PLAYER(s, 1).x-1+5, PLAYER(s, 1).mouse_y-1, 2, 2, 0, 0, 0);
548 font_draw_string(FONT_MEDIUM, s->status, (WIDTH-font_get_string_width(FONT_MEDIUM, s->status))/2, HEIGHT-50);
550 for (i=0; i<s->rain; i++) {
551 x = rand()%WIDTH;
552 y = rand()%HEIGHT;
553 draw_line_faded(x, y, x+10+s->wind*5, y+30, 0, 0, 255, 100, 200, 255);
555 if (s->rain) {
557 * Cheap-ish update of the whole screen. This can
558 * probably be optimized.
560 update_rect(0, 0, WIDTH, HEIGHT);
563 #ifdef DEBUG
564 line_horiz( PLAYER(s, 1).y, 255, 0, 0);
565 line_horiz( PLAYER(s, 2).y, 0, 0, 255);
566 line_horiz( s->ball.y, 0, 255, 0);
568 line_vert( PLAYER(s, 1).x, 255, 0, 0);
569 line_vert( PLAYER(s, 2).x, 0, 0, 255);
570 line_vert( s->ball.x, 0, 255, 0);
572 line_horiz( GAME_Y_MIN, 100, 100, 100);
573 line_horiz( GAME_Y_MAX, 100, 100, 100);
574 #endif
575 switch (s->fog) {
576 default:
577 case 4:
578 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150-s->windtime/200, 0);
579 case 3:
580 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100-s->windtime/150, 20);
581 case 2:
582 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180-s->windtime/180, 80);
583 case 1:
584 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200-s->windtime/100, 0);
585 case 0:
586 break;
588 if (s->night) {
589 show_image(GR_NIGHT, 0, 0, 255);
592 updatescr();
595 void limit_value( float* value, float min, float max) {
596 if( *value < min) {
597 *value = min;
598 } else if( *value > max) {
599 *value = max;
603 float get_move_y( GameState* s, unsigned char player) {
604 float pct, dest, x_len, y_len;
605 float py, by, pa, move_x;
607 py = (player==1)?(PLAYER(s, 1).y):(PLAYER(s, 2).y);
608 by = s->ball.y - s->ball.z;
609 pa = RACKET_Y_MID*2;
610 move_x = s->ball.move_x;
612 /* -1.0 .. 1.0 for racket hit position */
613 pct = maxf(-1.0, minf(1.0, (by-py)/(pa/2)));
615 /* Y destination for ball */
616 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
618 /* lengths for the ball's journey */
619 if( player == 1) {
620 x_len = GAME_X_MAX - s->ball.x;
621 } else {
622 x_len = s->ball.x - GAME_X_MIN;
624 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
626 /* return the should-be value for move_y */
627 return (y_len*move_x)/(x_len);
630 void input_human(GameState* s, int player) {
631 bool hit, topspin, smash;
632 float move_y;
634 move_y = PLAYER_MOVE_Y*input_device_get_axis(PLAYER(s, player).input, INPUT_AXIS_Y);
636 hit = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_HIT);
637 topspin = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_TOPSPIN);
638 smash = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_SMASH);
640 if (move_y != 0) {
641 if (absf(move_y) > absf(move_y*PLAYER(s, player).accelerate)) {
642 move_y *= PLAYER(s, player).accelerate;
644 PLAYER(s, player).y += move_y;
645 PLAYER(s, player).accelerate *= PLAYER_ACCEL_INCREASE;
646 } else {
647 PLAYER(s, player).accelerate = PLAYER_ACCEL_DEFAULT;
650 if(hit || topspin || smash) {
651 PLAYER(s, player).desire = (topspin)?(DESIRE_TOPSPIN):(DESIRE_NORMAL);
652 PLAYER(s, player).desire = (smash)?(DESIRE_SMASH):(PLAYER(s, player).desire);
654 PLAYER(s, player).power = maxf(10.0, minf(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
655 PLAYER(s, player).use_power = false;
656 } else {
657 PLAYER(s, player).use_power = true;
661 void input_ai(GameState* s, int player) {
662 float fact = 1.7;
663 float target;
664 int ball_approaching = 0;
666 if ((PLAYER(s, player).x < GAME_X_MID && s->ball.move_x <= 0) ||
667 (PLAYER(s, player).x > GAME_X_MID && s->ball.move_x >= 0)) {
668 ball_approaching = 1;
671 /* FIXME - this is broken since the new physics model has been introduced */
673 if (absf(PLAYER(s, player).y - (s->ball.y-s->ball.z)) > RACKET_Y_MID*5) {
674 fact = 3.5;
677 if(1) {
678 if( PLAYER(s, player).desire == DESIRE_NORMAL && !IS_NEAR_Y_AI(PLAYER(s, player).y, (s->ball.y-s->ball.z)) && ball_approaching) {
679 if( PLAYER(s, player).y < (s->ball.y-s->ball.z)) {
680 PLAYER(s, player).y += minf(2*fact, (s->ball.y-s->ball.z) - PLAYER(s, player).y);
681 } else if( PLAYER(s, player).y > (s->ball.y-s->ball.z)) {
682 PLAYER(s, player).y -= minf(2*fact, PLAYER(s, player).y - (s->ball.y-s->ball.z));
686 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.) {
687 PLAYER(s, player).use_power = true;
688 } else if (ball_approaching) {
689 PLAYER(s, player).power = maxf(10., minf(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
690 PLAYER(s, player).use_power = false;
692 } else if( s->ngram_prediction > 0.0) {
693 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
694 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
696 if( PLAYER(s, player).desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( PLAYER(s, player).y, target)) {
697 if( PLAYER(s, player).y < target) {
698 PLAYER(s, player).y += minf(fact, (target-PLAYER(s, player).y)/40.0);
699 } else if( PLAYER(s, player).y > target) {
700 PLAYER(s, player).y -= minf(fact, (PLAYER(s, player).y-target)/40.0);
706 void game_setup_serve( GameState* s) {
707 s->ball.z = 10.0;
708 s->ball.y = GAME_Y_MID;
709 s->ball.move_x = 0.0;
710 s->ball.move_y = 0.0;
711 s->ball.move_z = 0.0;
712 s->ball.last_hit_by = -1;
713 s->ball.inhibit_gravity = true;
715 if( s->serving_player == 1) {
716 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
717 } else {
718 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
722 float ngram_predictor( GameState* s) {
723 unsigned int count = 0;
724 unsigned long sum = 0;
725 int x, y, z;
726 float result;
728 if( s->history_size < 3) {
729 return 0.0;
732 x = s->history[1];
733 y = s->history[2];
735 for( z = 0; z<NGRAM_STEPS; z++) {
736 count += s->ngram[x][y][z];
737 sum += z * s->ngram[x][y][z];
740 result = ((float)(sum))/((float)(count));
741 #ifdef DEBUG
742 printf( "predicting next = %.2f\n", result);
743 #endif
745 return result;
748 int score_game(GameState* s) {
749 Player *last_hit_by = NULL;
750 Player *other = NULL;
752 Player* winner = NULL;
753 Player* loser = NULL;
755 /* determine "last hit by" and "other" */
756 if (s->ball.last_hit_by == 1) {
757 last_hit_by = &(PLAYER(s, 1));
758 other = &(PLAYER(s, 2));
759 } else {
760 last_hit_by = &(PLAYER(s, 2));
761 other = &(PLAYER(s, 1));
764 switch (s->score_event) {
765 case SCORE_EVENT_NET:
766 winner = other;
767 break;
768 case SCORE_EVENT_OUT:
769 case SCORE_EVENT_GROUND_INVALID:
770 case SCORE_EVENT_OFFSCREEN:
771 case SCORE_EVENT_GROUND_VALID:
772 if (s->ball.ground_hit) {
773 winner = last_hit_by;
774 } else {
775 winner = other;
777 break;
778 default:
779 break;
782 /* determine loser based on winner */
783 if (winner == last_hit_by) {
784 loser = other;
785 } else {
786 loser = last_hit_by;
789 /* we cannot be in an "impossibly high" set */
790 assert(s->current_set < SETS_TO_WIN*2-1);
792 winner->game++;
793 if( loser->game < winner->game-1) {
794 if( winner->game >= 4) {
795 winner->game = loser->game = 0;
796 winner->sets[s->current_set]++;
798 /* serving is changed when the "game" is over */
799 s->serving_player = (s->serving_player==1)?(2):(1);
801 #ifdef HAVE_VOICE_FILES
802 /* speak the current score */
803 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);
804 #endif
806 /* scoring the set.. */
807 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
808 winner->sets[s->current_set] == 7) {
809 s->current_set++;
810 s->winner = game_get_winner( s);
815 /* forget this event - we've handled it */
816 s->score_event = SCORE_UNDECIDED;
818 if (winner == &PLAYER(s, 1)) {
819 return WINNER_PLAYER1;
820 } else {
821 return WINNER_PLAYER2;
825 char* format_sets( GameState* s) {
826 static char sets[100];
827 static char tmp[100];
828 int i, max = s->current_set;
830 sets[0] = '\0';
832 if( s->winner != WINNER_NONE) {
833 max--;
835 for( i=0; i<=max; i++) {
836 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
837 strcat( sets, tmp);
840 sets[strlen(sets)-2] = '\0';
842 return sets;
845 char* format_game( GameState* s) {
846 static char game[100];
847 static const int game_scoring[] = { 0, 15, 30, 40 };
849 if( PLAYER(s, 1).game < 4 && PLAYER(s, 2).game < 4) {
850 #ifdef HAVE_VOICE_FILES
851 if (PLAYER(s, 1).game > 0 || PLAYER(s, 2).game > 0) {
852 if (PLAYER(s, 1).game == PLAYER(s, 2).game) {
853 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_ALL);
854 } else {
855 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
858 #endif
859 sprintf( game, "%d - %d", game_scoring[PLAYER(s, 1).game], game_scoring[PLAYER(s, 2).game]);
860 } else if( PLAYER(s, 1).game > PLAYER(s, 2).game) {
861 #ifdef HAVE_VOICE_FILES
862 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
863 #endif
864 strcpy( game, "advantage player 1");
865 } else if( PLAYER(s, 1).game < PLAYER(s, 2).game) {
866 #ifdef HAVE_VOICE_FILES
867 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
868 #endif
869 strcpy( game, "advantage player 2");
870 } else {
871 #ifdef HAVE_VOICE_FILES
872 voice_say_list(1, VOICE_DEUCE);
873 #endif
874 strcpy( game, "deuce");
877 return game;
880 char* format_status( GameState* s) {
881 static char status[100];
882 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
884 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]);
886 return status;
889 int game_get_winner( GameState* s) {
890 unsigned int i;
891 int sets[2] = {0};
893 for( i=0; i<s->current_set; i++) {
894 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
895 sets[0]++;
896 } else {
897 sets[1]++;
901 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
902 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
904 return WINNER_NONE;