Add rotozoom support; more realistic ball and shadow
[tennix.git] / game.c
blob453edb06034a96c890188bed6d69d526f1166450
2 /**
4 * Tennix! SDL Port
5 * Copyright (C) 2003, 2007, 2008, 2009 Thomas Perl <thp@thpinfo.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
22 **/
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <math.h>
28 #include <assert.h>
30 #include "tennix.h"
31 #include "game.h"
32 #include "graphics.h"
33 #include "input.h"
34 #include "sound.h"
37 GameState *gamestate_new() {
38 int x, y, z;
39 GameState *s;
41 GameState template = {
42 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, 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, 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 (fabsf(s->ball.move_z) > 0.3) {
185 s->sound_events |= SOUND_EVENT_GROUND;
186 sample_volume_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, MAX(0.0, MIN(1.0, fabsf(s->ball.move_z)/2)));
187 pan_sample_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, MAX(0.0, MIN(1.0, 0.5+0.8*(s->ball.x-GAME_X_MID)/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 && 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 += fabsf(s->wind)/2;
408 } else if (s->ball.move_x < 0 && s->wind < 0) {
409 s->ball.x -= fabsf(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 = MAX(0, MIN(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 #ifdef EXTENDED_REFEREE
433 int t=1000;
434 #endif
435 if (s->sound_events != 0) {
436 if (s->sound_events & SOUND_EVENT_GROUND) {
437 play_sample(SOUND_GROUND);
439 if (s->sound_events & SOUND_EVENT_OUT) {
440 play_sample(SOUND_OUT);
442 if (s->sound_events & SOUND_EVENT_APPLAUSE) {
443 play_sample(SOUND_APPLAUSE);
445 if (s->sound_events & SOUND_EVENT_RACKET) {
446 play_sample(SOUND_RACKET);
448 s->sound_events = 0;
450 if( s->winner != WINNER_NONE) {
451 if( !s->is_over) {
452 start_fade();
453 s->is_over = true;
455 clear_screen();
456 store_screen();
457 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);
458 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
459 font_draw_string(FONT_LARGE, s->game_score_str, (WIDTH-font_get_string_width(FONT_LARGE, s->game_score_str))/2, HEIGHT/2 + 30);
460 updatescr();
461 return;
463 if (s->displayed_court_type != s->location->court_type || s->text_changed || s->old_referee != s->referee) {
464 clear_screen();
465 fill_image(s->location->court_type, 120, 120, 400, 250);
466 show_image(GR_COURT, 0, 0, 255);
467 font_draw_string(FONT_XLARGE, s->game_score_str, 14, 14);
468 font_draw_string(FONT_XLARGE, s->sets_score_str, (WIDTH-font_get_string_width(FONT_XLARGE, s->sets_score_str))-14, 14);
469 if (s->location->has_referee) {
470 #ifdef EXTENDED_REFEREE
471 switch (s->referee) {
472 case REFEREE_NORMAL:
473 t = 1000;
474 break;
475 case REFEREE_OUT:
476 t = 200;
477 break;
478 case REFEREE_PLAYER1:
479 case REFEREE_PLAYER2:
480 t = 400;
481 break;
483 t = (s->time/t)%4;
484 switch (t) {
485 case 0:
486 t=0;
487 break;
488 case 1:
489 t=1;
490 break;
491 case 2:
492 t=0;
493 break;
494 case 3:
495 t=2;
496 break;
498 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
499 if (voice_finished_flag == 0) {
500 show_sprite(GR_TALK, (s->time/150)%2, 2, 280, 45, 255);
502 #else
503 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
504 #endif
506 s->displayed_court_type = s->location->court_type;
507 s->text_changed = false;
508 s->old_referee = s->referee;
509 store_screen();
512 /* show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
513 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);*/
514 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);
515 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);
517 rectangle(10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
518 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
520 rectangle(10, HEIGHT-30, PLAYER(s, 1).power, 10, 200, 200, 200);
521 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, PLAYER(s, 2).power, 10, 200, 200, 200);
523 if( s->ball.move_x > 0) {
524 b = (s->time/100)%BALL_STATES;
525 } else if( s->ball.move_x < 0) {
526 b = BALL_STATES-1-(s->time/100)%BALL_STATES;
527 } else {
528 b = 0;
531 show_image_rotozoom(GR_SHADOW, s->ball.x, s->ball.y+3, 0.0, MAX(0.5, MIN(1.0, (30-s->ball.z)/10)));
533 show_image_rotozoom(GR_BALL, s->ball.x, s->ball.y - s->ball.z, -s->ball.move_x * (s->time/10), 1.0+MAX(0.0, (s->ball.z-10)/100));
535 /* Player 1's mouse rectangle */
536 if (!(PLAYER(s, 1).mouse_locked)) {
537 rectangle(PLAYER(s, 1).x-2+5, PLAYER(s, 1).mouse_y-2, 4, 4, 255, 255, 255);
538 rectangle(PLAYER(s, 1).x-1+5, PLAYER(s, 1).mouse_y-1, 2, 2, 0, 0, 0);
542 font_draw_string(FONT_MEDIUM, s->status, (WIDTH-font_get_string_width(FONT_MEDIUM, s->status))/2, HEIGHT-50);
544 for (i=0; i<s->rain; i++) {
545 x = rand()%WIDTH;
546 y = rand()%HEIGHT;
547 draw_line_faded(x, y, x+10+s->wind*5, y+30, 0, 0, 255, 100, 200, 255);
549 if (s->rain) {
551 * Cheap-ish update of the whole screen. This can
552 * probably be optimized.
554 update_rect(0, 0, WIDTH, HEIGHT);
557 #ifdef DEBUG
558 line_horiz( PLAYER(s, 1).y, 255, 0, 0);
559 line_horiz( PLAYER(s, 2).y, 0, 0, 255);
560 line_horiz( s->ball.y, 0, 255, 0);
562 line_vert( PLAYER(s, 1).x, 255, 0, 0);
563 line_vert( PLAYER(s, 2).x, 0, 0, 255);
564 line_vert( s->ball.x, 0, 255, 0);
566 line_horiz( GAME_Y_MIN, 100, 100, 100);
567 line_horiz( GAME_Y_MAX, 100, 100, 100);
568 #endif
569 switch (s->fog) {
570 default:
571 case 4:
572 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150-s->windtime/200, 0);
573 case 3:
574 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100-s->windtime/150, 20);
575 case 2:
576 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180-s->windtime/180, 80);
577 case 1:
578 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200-s->windtime/100, 0);
579 case 0:
580 break;
582 if (s->night) {
583 show_image(GR_NIGHT, 0, 0, 255);
586 updatescr();
589 void limit_value( float* value, float min, float max) {
590 if( *value < min) {
591 *value = min;
592 } else if( *value > max) {
593 *value = max;
597 float get_move_y( GameState* s, unsigned char player) {
598 float pct, dest, x_len, y_len;
599 float py, by, pa, move_x;
601 py = (player==1)?(PLAYER(s, 1).y):(PLAYER(s, 2).y);
602 by = s->ball.y - s->ball.z;
603 pa = RACKET_Y_MID*2;
604 move_x = s->ball.move_x;
606 /* -1.0 .. 1.0 for racket hit position */
607 pct = MAX(-1.0, MIN(1.0, (by-py)/(pa/2)));
609 /* Y destination for ball */
610 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
612 /* lengths for the ball's journey */
613 if( player == 1) {
614 x_len = GAME_X_MAX - s->ball.x;
615 } else {
616 x_len = s->ball.x - GAME_X_MIN;
618 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
620 /* return the should-be value for move_y */
621 return (y_len*move_x)/(x_len);
624 void input_human(GameState* s, int player) {
625 bool hit, topspin, smash;
626 float move_y;
628 move_y = PLAYER_MOVE_Y*input_device_get_axis(PLAYER(s, player).input, INPUT_AXIS_Y);
630 hit = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_HIT);
631 topspin = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_TOPSPIN);
632 smash = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_SMASH);
634 if (move_y != 0) {
635 if (fabsf(move_y) > fabsf(move_y*PLAYER(s, player).accelerate)) {
636 move_y *= PLAYER(s, player).accelerate;
638 PLAYER(s, player).y += move_y;
639 PLAYER(s, player).accelerate *= PLAYER_ACCEL_INCREASE;
640 } else {
641 PLAYER(s, player).accelerate = PLAYER_ACCEL_DEFAULT;
644 if(hit || topspin || smash) {
645 PLAYER(s, player).desire = (topspin)?(DESIRE_TOPSPIN):(DESIRE_NORMAL);
646 PLAYER(s, player).desire = (smash)?(DESIRE_SMASH):(PLAYER(s, player).desire);
648 PLAYER(s, player).power = MAX(10, MIN(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
649 PLAYER(s, player).use_power = false;
650 } else {
651 PLAYER(s, player).use_power = true;
655 void input_ai(GameState* s, int player) {
656 float fact = 1.7;
657 float target;
658 int ball_approaching = 0;
660 if ((PLAYER(s, player).x < GAME_X_MID && s->ball.move_x <= 0) ||
661 (PLAYER(s, player).x > GAME_X_MID && s->ball.move_x >= 0)) {
662 ball_approaching = 1;
665 /* FIXME - this is broken since the new physics model has been introduced */
667 if( fabsf( PLAYER(s, player).y - (s->ball.y-s->ball.z)) > RACKET_Y_MID*5) {
668 fact = 3.5;
671 if(1) {
672 if( PLAYER(s, player).desire == DESIRE_NORMAL && !IS_NEAR_Y_AI(PLAYER(s, player).y, (s->ball.y-s->ball.z)) && ball_approaching) {
673 if( PLAYER(s, player).y < (s->ball.y-s->ball.z)) {
674 PLAYER(s, player).y += fmin( 2*fact, (s->ball.y-s->ball.z) - PLAYER(s, player).y);
675 } else if( PLAYER(s, player).y > (s->ball.y-s->ball.z)) {
676 PLAYER(s, player).y -= fmin( 2*fact, PLAYER(s, player).y - (s->ball.y-s->ball.z));
680 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) {
681 PLAYER(s, player).use_power = true;
682 } else if (ball_approaching) {
683 PLAYER(s, player).power = MAX(10, MIN(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
684 PLAYER(s, player).use_power = false;
686 } else if( s->ngram_prediction > 0.0) {
687 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
688 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
690 if( PLAYER(s, player).desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( PLAYER(s, player).y, target)) {
691 if( PLAYER(s, player).y < target) {
692 PLAYER(s, player).y += fmin( fact, (target-PLAYER(s, player).y)/40.0);
693 } else if( PLAYER(s, player).y > target) {
694 PLAYER(s, player).y -= fmin( fact, (PLAYER(s, player).y-target)/40.0);
700 void game_setup_serve( GameState* s) {
701 s->ball.z = 10.0;
702 s->ball.y = GAME_Y_MID;
703 s->ball.move_x = 0.0;
704 s->ball.move_y = 0.0;
705 s->ball.move_z = 0.0;
706 s->ball.last_hit_by = -1;
707 s->ball.inhibit_gravity = true;
709 if( s->serving_player == 1) {
710 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
711 } else {
712 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
716 float ngram_predictor( GameState* s) {
717 unsigned int count = 0;
718 unsigned long sum = 0;
719 int x, y, z;
720 float result;
722 if( s->history_size < 3) {
723 return 0.0;
726 x = s->history[1];
727 y = s->history[2];
729 for( z = 0; z<NGRAM_STEPS; z++) {
730 count += s->ngram[x][y][z];
731 sum += z * s->ngram[x][y][z];
734 result = ((float)(sum))/((float)(count));
735 #ifdef DEBUG
736 printf( "predicting next = %.2f\n", result);
737 #endif
739 return result;
742 int score_game(GameState* s) {
743 Player *last_hit_by = NULL;
744 Player *other = NULL;
746 Player* winner = NULL;
747 Player* loser = NULL;
749 /* determine "last hit by" and "other" */
750 if (s->ball.last_hit_by == 1) {
751 last_hit_by = &(PLAYER(s, 1));
752 other = &(PLAYER(s, 2));
753 } else {
754 last_hit_by = &(PLAYER(s, 2));
755 other = &(PLAYER(s, 1));
758 switch (s->score_event) {
759 case SCORE_EVENT_NET:
760 winner = other;
761 break;
762 case SCORE_EVENT_OUT:
763 case SCORE_EVENT_GROUND_INVALID:
764 case SCORE_EVENT_OFFSCREEN:
765 case SCORE_EVENT_GROUND_VALID:
766 if (s->ball.ground_hit) {
767 winner = last_hit_by;
768 } else {
769 winner = other;
771 break;
772 default:
773 break;
776 /* determine loser based on winner */
777 if (winner == last_hit_by) {
778 loser = other;
779 } else {
780 loser = last_hit_by;
783 /* we cannot be in an "impossibly high" set */
784 assert(s->current_set < SETS_TO_WIN*2-1);
786 winner->game++;
787 if( loser->game < winner->game-1) {
788 if( winner->game >= 4) {
789 winner->game = loser->game = 0;
790 winner->sets[s->current_set]++;
792 /* serving is changed when the "game" is over */
793 s->serving_player = (s->serving_player==1)?(2):(1);
795 #ifdef HAVE_VOICE_FILES
796 /* speak the current score */
797 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);
798 #endif
800 /* scoring the set.. */
801 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
802 winner->sets[s->current_set] == 7) {
803 s->current_set++;
804 s->winner = game_get_winner( s);
809 /* forget this event - we've handled it */
810 s->score_event = SCORE_UNDECIDED;
812 if (winner == &PLAYER(s, 1)) {
813 return WINNER_PLAYER1;
814 } else {
815 return WINNER_PLAYER2;
819 char* format_sets( GameState* s) {
820 static char sets[100];
821 static char tmp[100];
822 int i, max = s->current_set;
824 sets[0] = '\0';
826 if( s->winner != WINNER_NONE) {
827 max--;
829 for( i=0; i<=max; i++) {
830 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
831 strcat( sets, tmp);
834 sets[strlen(sets)-2] = '\0';
836 return sets;
839 char* format_game( GameState* s) {
840 static char game[100];
841 static const int game_scoring[] = { 0, 15, 30, 40 };
843 if( PLAYER(s, 1).game < 4 && PLAYER(s, 2).game < 4) {
844 #ifdef HAVE_VOICE_FILES
845 if (PLAYER(s, 1).game > 0 || PLAYER(s, 2).game > 0) {
846 if (PLAYER(s, 1).game == PLAYER(s, 2).game) {
847 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_ALL);
848 } else {
849 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
852 #endif
853 sprintf( game, "%d - %d", game_scoring[PLAYER(s, 1).game], game_scoring[PLAYER(s, 2).game]);
854 } else if( PLAYER(s, 1).game > PLAYER(s, 2).game) {
855 #ifdef HAVE_VOICE_FILES
856 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
857 #endif
858 strcpy( game, "advantage player 1");
859 } else if( PLAYER(s, 1).game < PLAYER(s, 2).game) {
860 #ifdef HAVE_VOICE_FILES
861 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
862 #endif
863 strcpy( game, "advantage player 2");
864 } else {
865 #ifdef HAVE_VOICE_FILES
866 voice_say_list(1, VOICE_DEUCE);
867 #endif
868 strcpy( game, "deuce");
871 return game;
874 char* format_status( GameState* s) {
875 static char status[100];
876 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
878 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]);
880 return status;
883 int game_get_winner( GameState* s) {
884 unsigned int i;
885 int sets[2] = {0};
887 for( i=0; i<s->current_set; i++) {
888 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
889 sets[0]++;
890 } else {
891 sets[1]++;
895 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
896 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
898 return WINNER_NONE;