Implement night for world map + automatic night mode
[tennix.git] / game.c
blobaef581516964f0f872b616ddd4ab8523c601dcdb
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 { GAME_X_MIN-RACKET_X_MID*2, GAME_Y_MID, 0, POWER_UP_FACTOR, POWER_DOWN_FACTOR, true, 0, DESIRE_NORMAL, PLAYER_TYPE_HUMAN, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true },
46 { 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 SOUND_MAX,
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.5) {
185 s->ball.move_z *= -s->ball.restitution;
186 } else {
187 s->ball.move_z = 0;
189 s->ball.z = 0;
191 /* only make the sound if it's visibly bouncing hard */
192 if (!IS_OFFSCREEN(s->ball.x, s->ball.y) && fabsf(s->ball.move_z) > .5) {
193 s->play_sound = SOUND_GROUND;
197 if (NET_COLLISION_BALL(s->ball)) {
198 /* the net blocks movement of the ball */
199 s->ball.move_x = 0;
200 s->ball.move_y = 0;
201 while (NET_COLLISION_BALL(s->ball)) {
202 /* make sure the ball appears OUTSIDE of the net */
203 if (s->ball.x >= (NET_X+NET_WIDTH/2)) {
204 s->ball.x += 1;
205 } else {
206 s->ball.x -= 1;
211 /* see if we have something to score */
212 if (s->score_event == SCORE_UNDECIDED) {
213 if (NET_COLLISION_BALL(s->ball)) {
214 /* the ball "fell" into the net */
215 s->score_event = SCORE_EVENT_NET;
216 s->play_sound = SOUND_OUT;
217 s->status = "net!";
218 } else if (IS_OFFSCREEN(s->ball.x, s->ball.y)) {
219 /* ball flew offscreen */
220 s->score_event = SCORE_EVENT_OFFSCREEN;
221 s->play_sound = SOUND_OUT;
222 s->status = "out!";
223 } else if (ground_event) {
224 /* the ball hit the ground on the screen */
225 if (IS_OUT(s->ball.x, s->ball.y)) {
226 /* the ball bounced in the OUT area */
227 s->score_event = SCORE_EVENT_OUT;
228 s->play_sound = SOUND_OUT;
229 s->status = "out!";
230 } else if (GROUND_IS_VALID(s->ball.last_hit_by, s->ball.x, s->ball.y)) {
231 if (s->ball.ground_hit) {
232 s->score_event = SCORE_EVENT_GROUND_VALID;
233 s->play_sound = SOUND_OUT;
234 s->status = "did not catch the ball!";
235 } else {
236 /* first ground hit in valid area */
237 s->ball.ground_hit = true;
239 } else {
240 /* ball hit somewhere invalid */
241 s->score_event = SCORE_EVENT_GROUND_INVALID;
242 s->play_sound = SOUND_OUT;
243 s->status = "fault!";
248 if (s->score_event != SCORE_UNDECIDED) {
249 /* we have some scoring to do */
250 if (s->score_time == 0) {
251 /* schedule scoring in the future */
252 s->score_time = s->time + SCORING_DELAY;
253 s->referee = REFEREE_OUT;
254 } else if (s->time >= s->score_time) {
255 /* time has ran out - score now */
256 switch (score_game(s)) {
257 case WINNER_PLAYER1:
258 s->status = "player 1 scores";
259 s->referee = REFEREE_PLAYER1;
260 break;
261 case WINNER_PLAYER2:
262 s->status = "player 2 scores";
263 s->referee = REFEREE_PLAYER2;
264 break;
265 default:
266 assert(0);
267 break;
269 s->score_time = 0;
271 strcpy(s->game_score_str, format_game(s));
272 strcpy(s->sets_score_str, format_sets(s));
273 s->text_changed = true;
275 game_setup_serve(s);
276 if (s->location->max_visitors > 100) {
277 s->play_sound = SOUND_APPLAUSE;
279 /*FIXME n-gram predictor broken
280 s->history_size = 0;
281 s->history_is_locked = 0;
282 s->ngram_prediction = 0.0;*/
284 } else {
285 /* score is still undecided - do the racket swing thing */
286 for (p=1; p<=2; p++) {
287 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) {
288 /* RACKET HIT */
289 if (!s->ball.ground_hit && s->ball.move_x != 0.0) {
290 s->status = "volley!";
291 } else {
292 s->status = format_status(s);
294 switch (PLAYER(s, p).desire) {
295 case DESIRE_NORMAL:
296 /* normal swing */
297 s->ball.move_x = 2.7 + 2.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
298 s->ball.move_z = 1.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
299 break;
300 case DESIRE_TOPSPIN:
301 /* top spin */
302 s->ball.move_x = 1.1 + 2.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
303 s->ball.move_z = 2.5*PLAYER(s, p).power/PLAYER_POWER_MAX;
304 break;
305 case DESIRE_SMASH:
306 /* smash */
307 s->ball.move_x = 4.0 + 3.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
308 s->ball.move_z = 1.1*PLAYER(s, p).power/PLAYER_POWER_MAX;
309 break;
311 s->ball.move_y = get_move_y( s, p);
312 s->play_sound = SOUND_RACKET;
313 s->ball.ground_hit = false;
314 s->ball.inhibit_gravity = false;
315 s->ball.last_hit_by = p;
316 if (p==1) {
317 pan_sample(SOUND_RACKET, 0.4);
318 } else {
319 pan_sample(SOUND_RACKET, 0.6);
320 s->ball.move_x *= -1;
326 #if 0
327 FIXME n-gram predictor broken
328 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
329 if( IS_OUT_X(s->ball.x)) {
330 /*if( !s->history_is_locked && s->referee != REFEREE_OUT) {
331 s->history[s->history_size] = (int)(NGRAM_STEPS*s->ball.y/HEIGHT);
332 s->history_size++;
333 if( s->history_size == 3) {
334 s->ngram[s->history[0]][s->history[1]][s->history[2]] += 10;
335 s->ngram_prediction = ngram_predictor( s);
336 s->history[0] = s->history[1];
337 s->history[1] = s->history[2];
338 s->history_size--;
340 s->history_is_locked = true;
343 } else {
344 /*s->history_is_locked = false;*/
346 #endif
348 SDL_PollEvent(&e);
349 keys = SDL_GetKeyState(NULL);
350 switch(e.type) {
351 case SDL_JOYAXISMOTION:
352 if (e.jaxis.axis == JOYSTICK_Y_AXIS) {
353 s->joystick_y = JOYSTICK_PERCENTIZE(e.jaxis.value);
354 } else if (e.jaxis.axis == JOYSTICK_X_AXIS) {
355 s->joystick_x = JOYSTICK_PERCENTIZE(e.jaxis.value);
357 break;
358 case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN:
359 if (e.jbutton.button == JOYSTICK_BUTTON_A) {
360 s->joystick_a = (e.jbutton.state == SDL_PRESSED);
362 break;
365 if( s->time%50==0) {
366 if (keys['r']) {
367 if (s->rain == 0) {
368 play_sample_background(SOUND_RAIN);
370 s->rain += 10;
372 if (keys['t']) {
373 s->fog++;
375 if (keys['1']) {
376 s->wind++;
378 if (keys['2']) {
379 s->wind--;
383 if(!(SDL_GetTicks() < fading_start+FADE_DURATION) && !s->is_over) {
384 if( PLAYER(s, 1).type == PLAYER_TYPE_HUMAN) {
385 input_human( &PLAYER(s, 1),
386 keys['w'] || keys[SDLK_UP] || s->joystick_y < -JOYSTICK_TRESHOLD,
387 keys['s'] || keys[SDLK_DOWN] || s->joystick_y > JOYSTICK_TRESHOLD,
388 keys['d'] || keys[SDLK_SPACE] || keys[SDLK_LCTRL] || keys[SDLK_RETURN] || s->joystick_a,
389 keys['e'],
390 keys['f'],
391 #ifdef ENABLE_MOUSE
392 true,
393 #else
394 false,
395 #endif
397 } else {
398 input_ai( &PLAYER(s, 1), &s->ball, &PLAYER(s, 2), s);
401 if( PLAYER(s, 2).type == PLAYER_TYPE_HUMAN) {
402 input_human( &PLAYER(s, 2), keys['o'], keys['l'], keys['k'], keys['i'], keys['j'], false, s);
403 } else {
404 input_ai( &PLAYER(s, 2), &s->ball, &PLAYER(s, 1), s);
408 /* Maemo: The "F6" button is the "Fullscreen" button */
409 /*if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);*/
410 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
412 /* Maemo: The "F4" button is the "Open menu" button */
413 if( keys['p'] || keys[SDLK_F4]) {
414 while( keys['p'] || keys[SDLK_F4]) {
415 SDL_PollEvent( &e);
416 keys = SDL_GetKeyState( NULL);
417 SDL_Delay( 10);
419 while( (keys['p'] || keys[SDLK_F4]) == 0) {
420 SDL_PollEvent( &e);
421 keys = SDL_GetKeyState( NULL);
422 SDL_Delay( 10);
424 while( keys['p'] || keys[SDLK_F4]) {
425 SDL_PollEvent( &e);
426 keys = SDL_GetKeyState( NULL);
427 SDL_Delay( 10);
429 s->was_stopped = true;
432 if( keys[SDLK_ESCAPE] || keys['q']) return true;
434 limit_value( &PLAYER(s, 1).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
435 limit_value( &PLAYER(s, 2).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
437 if (s->ball.move_x > 0 && s->wind > 0) {
438 s->ball.x += fabsf(s->wind)/2;
439 } else if (s->ball.move_x < 0 && s->wind < 0) {
440 s->ball.x -= fabsf(s->wind)/2;
443 s->ball.z += s->ball.move_z;
444 if (!s->ball.inhibit_gravity) {
445 s->ball.move_z += GRAVITY;
448 s->ball.x += s->ball.move_x;
449 s->ball.y += s->ball.move_y;
451 for (p=1; p<=MAXPLAYERS; p++) {
452 if (PLAYER(s, p).use_power) {
453 PLAYER(s, p).power = MAX(0, MIN(PLAYER(s, p).power*PLAYER(s, p).power_down_factor, PLAYER_POWER_MAX));
457 return false;
460 void render( GameState* s) {
461 int x, y, b;
462 unsigned int i;
463 #ifdef EXTENDED_REFEREE
464 int t=1000;
465 #endif
466 if (s->play_sound != SOUND_MAX) {
467 play_sample(s->play_sound);
468 s->play_sound = SOUND_MAX;
470 if( s->winner != WINNER_NONE) {
471 if( !s->is_over) {
472 start_fade();
473 s->is_over = true;
475 clear_screen();
476 store_screen();
477 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);
478 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
479 font_draw_string( GR_DKC2_FONT, s->game_score_str, (WIDTH-font_get_string_width( GR_DKC2_FONT, s->game_score_str))/2, HEIGHT/2 + 30, s->time/20, ANIMATION_WAVE | ANIMATION_BUNGEE);
480 updatescr();
481 return;
483 if (s->displayed_court_type != s->location->court_type || s->text_changed || s->old_referee != s->referee) {
484 clear_screen();
485 fill_image(s->location->court_type, 120, 120, 400, 250);
486 show_image(GR_COURT, 0, 0, 255);
487 font_draw_string( GR_DKC2_FONT, s->game_score_str, 14, 14, 0, ANIMATION_NONE);
488 font_draw_string( GR_DKC2_FONT, s->sets_score_str, (WIDTH-font_get_string_width( GR_DKC2_FONT, s->sets_score_str))-14, 14, 0, ANIMATION_NONE);
489 if (s->location->has_referee) {
490 #ifdef EXTENDED_REFEREE
491 switch (s->referee) {
492 case REFEREE_NORMAL:
493 t = 1000;
494 break;
495 case REFEREE_OUT:
496 t = 200;
497 break;
498 case REFEREE_PLAYER1:
499 case REFEREE_PLAYER2:
500 t = 400;
501 break;
503 t = (s->time/t)%4;
504 switch (t) {
505 case 0:
506 t=0;
507 break;
508 case 1:
509 t=1;
510 break;
511 case 2:
512 t=0;
513 break;
514 case 3:
515 t=2;
516 break;
518 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
519 if (voice_finished_flag == 0) {
520 show_sprite(GR_TALK, (s->time/150)%2, 2, 280, 45, 255);
522 #else
523 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
524 #endif
526 s->displayed_court_type = s->location->court_type;
527 s->text_changed = false;
528 s->old_referee = s->referee;
529 store_screen();
532 /* show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
533 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);*/
534 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);
535 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);
537 rectangle(10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
538 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
540 rectangle(10, HEIGHT-30, PLAYER(s, 1).power, 10, 200, 200, 200);
541 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, PLAYER(s, 2).power, 10, 200, 200, 200);
543 if( s->ball.move_x > 0) {
544 b = (s->time/100)%BALL_STATES;
545 } else if( s->ball.move_x < 0) {
546 b = BALL_STATES-1-(s->time/100)%BALL_STATES;
547 } else {
548 b = 0;
550 show_image(GR_SHADOW, s->ball.x-BALL_X_MID, s->ball.y-4, 255);
551 show_sprite(GR_BALL, b, BALL_STATES, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID-s->ball.z, 255);
553 /* Player 1's mouse rectangle */
554 if (!(PLAYER(s, 1).mouse_locked)) {
555 rectangle(PLAYER(s, 1).x-2+5, PLAYER(s, 1).mouse_y-2, 4, 4, 255, 255, 255);
556 rectangle(PLAYER(s, 1).x-1+5, PLAYER(s, 1).mouse_y-1, 2, 2, 0, 0, 0);
560 font_draw_string( GR_DKC2_FONT, s->status, (WIDTH-font_get_string_width( GR_DKC2_FONT, s->status))/2, HEIGHT-50, s->time/30, ANIMATION_WAVE);
562 for (i=0; i<s->rain; i++) {
563 x = rand()%WIDTH;
564 y = rand()%HEIGHT;
565 draw_line_faded(x, y, x+10+s->wind*5, y+30, 0, 0, 255, 100, 200, 255);
567 if (s->rain) {
569 * Cheap-ish update of the whole screen. This can
570 * probably be optimized.
572 update_rect(0, 0, WIDTH, HEIGHT);
575 #ifdef DEBUG
576 line_horiz( PLAYER(s, 1).y, 255, 0, 0);
577 line_horiz( PLAYER(s, 2).y, 0, 0, 255);
578 line_horiz( s->ball.y, 0, 255, 0);
580 line_vert( PLAYER(s, 1).x, 255, 0, 0);
581 line_vert( PLAYER(s, 2).x, 0, 0, 255);
582 line_vert( s->ball.x, 0, 255, 0);
584 line_horiz( GAME_Y_MIN, 100, 100, 100);
585 line_horiz( GAME_Y_MAX, 100, 100, 100);
586 #endif
587 switch (s->fog) {
588 default:
589 case 4:
590 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150-s->windtime/200, 0);
591 case 3:
592 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100-s->windtime/150, 20);
593 case 2:
594 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180-s->windtime/180, 80);
595 case 1:
596 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200-s->windtime/100, 0);
597 case 0:
598 break;
600 if (s->night) {
601 show_image(GR_NIGHT, 0, 0, 255);
604 updatescr();
607 void limit_value( float* value, float min, float max) {
608 if( *value < min) {
609 *value = min;
610 } else if( *value > max) {
611 *value = max;
615 float get_move_y( GameState* s, unsigned char player) {
616 float pct, dest, x_len, y_len;
617 float py, by, pa, move_x;
619 py = (player==1)?(PLAYER(s, 1).y):(PLAYER(s, 2).y);
620 by = s->ball.y - s->ball.z;
621 pa = RACKET_Y_MID*2;
622 move_x = s->ball.move_x;
624 /* -1.0 .. 1.0 for racket hit position */
625 pct = (by-py)/(pa/2);
626 limit_value( &pct, -1.0, 1.0);
628 /* Y destination for ball */
629 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
631 /* lengths for the ball's journey */
632 if( player == 1) {
633 x_len = fabsf(GAME_X_MAX - s->ball.x);
634 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
635 } else {
636 x_len = s->ball.x - GAME_X_MIN;
637 y_len = by - dest + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
640 /* return the should-be value for move_y */
641 return (y_len*move_x)/(x_len);
644 void input_human( Player* player, bool up, bool down, bool hit, bool topspin, bool smash, bool use_mouse, GameState* s) {
645 int diff = PLAYER_MOVE_Y;
646 int mb;
649 * Only use mouse control if the user isn't pressing any buttons
650 * this way, keyboard control still works when mouse control is
651 * enabled.
653 if (use_mouse && (down || up || hit)) {
655 * this is here so if the user decides to play
656 * with keyboard controls, we will lock the
657 * mouse and disable displaying the mouse cursor
659 player->mouse_locked = true;
661 if (use_mouse && !down && !up && !hit) {
662 mb = SDL_GetMouseState(&(player->mouse_x), &(player->mouse_y));
663 if (mb&SDL_BUTTON(SDL_BUTTON_LEFT)) {
664 if (player->mouse_y < player->y) {
665 down = false;
666 up = true;
667 diff = (player->y-player->mouse_y<diff)?(player->y-player->mouse_y):(diff);
668 } else if (player->mouse_y > player->y) {
669 up = false;
670 down = true;
671 diff = (player->mouse_y-player->y<diff)?(player->mouse_y-player->y):(diff);
673 player->mouse_locked = false;
674 } else if (!player->mouse_locked) {
675 hit = true;
679 if (fabsf(s->joystick_y) > JOYSTICK_TRESHOLD) {
680 diff = PLAYER_MOVE_Y*fabsf(s->joystick_y)/40.0;
681 if (diff > PLAYER_MOVE_Y) {
682 diff = PLAYER_MOVE_Y;
686 if (up) {
687 player->y -= fminf(diff, diff*player->accelerate);
688 player->accelerate *= PLAYER_ACCEL_INCREASE;
689 } else if (down) {
690 player->y += fminf(diff, diff*player->accelerate);
691 player->accelerate *= PLAYER_ACCEL_INCREASE;
692 } else {
693 player->accelerate = PLAYER_ACCEL_DEFAULT;
696 if(hit || topspin || smash) {
697 player->desire = (topspin)?(DESIRE_TOPSPIN):(DESIRE_NORMAL);
698 player->desire = (smash)?(DESIRE_SMASH):(player->desire);
700 player->power = MAX(10, MIN(player->power*player->power_up_factor, PLAYER_POWER_MAX));
701 player->use_power = false;
702 } else {
703 player->use_power = true;
707 void input_ai( Player* player, Ball* ball, Player* opponent, GameState* s) {
708 float fact = 1.7;
709 float target;
710 int ball_approaching = 0;
712 if ((player->x < opponent->x && ball->move_x <= 0) ||
713 (player->x > opponent->y && ball->move_x >= 0)) {
714 ball_approaching = 1;
717 /* FIXME - this is broken since the new physics model has been introduced */
719 if( fabsf( player->y - (ball->y-ball->z)) > RACKET_Y_MID*5) {
720 fact = 3.5;
723 if(1) {
724 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI(player->y, (ball->y-ball->z)) && ball_approaching) {
725 if( player->y < (ball->y-ball->z)) {
726 player->y += fmin( 2*fact, (ball->y-ball->z) - player->y);
727 } else if( player->y > (ball->y-ball->z)) {
728 player->y -= fmin( 2*fact, player->y - (ball->y-ball->z));
732 if (IS_NEAR_Y(player->y, (ball->y-ball->z)) && IS_NEAR_X_AI(player->x, ball->x) && player->power > 90) {
733 player->use_power = true;
734 } else if (ball_approaching) {
735 player->power = MAX(10, MIN(player->power*player->power_up_factor, PLAYER_POWER_MAX));
736 player->use_power = false;
738 } else if( s->ngram_prediction > 0.0) {
739 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
740 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
742 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
743 if( player->y < target) {
744 player->y += fmin( fact, (target-player->y)/40.0);
745 } else if( player->y > target) {
746 player->y -= fmin( fact, (player->y-target)/40.0);
752 void game_setup_serve( GameState* s) {
753 s->ball.z = 10.0;
754 s->ball.y = GAME_Y_MID;
755 s->ball.move_x = 0.0;
756 s->ball.move_y = 0.0;
757 s->ball.move_z = 0.0;
758 s->ball.last_hit_by = -1;
759 s->ball.inhibit_gravity = true;
761 if( s->serving_player == 1) {
762 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
763 } else {
764 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
768 float ngram_predictor( GameState* s) {
769 unsigned int count = 0;
770 unsigned long sum = 0;
771 int x, y, z;
772 float result;
774 if( s->history_size < 3) {
775 return 0.0;
778 x = s->history[1];
779 y = s->history[2];
781 for( z = 0; z<NGRAM_STEPS; z++) {
782 count += s->ngram[x][y][z];
783 sum += z * s->ngram[x][y][z];
786 result = ((float)(sum))/((float)(count));
787 #ifdef DEBUG
788 printf( "predicting next = %.2f\n", result);
789 #endif
791 return result;
794 int score_game(GameState* s) {
795 Player *last_hit_by = NULL;
796 Player *other = NULL;
798 Player* winner = NULL;
799 Player* loser = NULL;
801 /* determine "last hit by" and "other" */
802 if (s->ball.last_hit_by == 1) {
803 last_hit_by = &(PLAYER(s, 1));
804 other = &(PLAYER(s, 2));
805 } else {
806 last_hit_by = &(PLAYER(s, 2));
807 other = &(PLAYER(s, 1));
810 switch (s->score_event) {
811 case SCORE_EVENT_NET:
812 winner = other;
813 break;
814 case SCORE_EVENT_OUT:
815 case SCORE_EVENT_GROUND_INVALID:
816 case SCORE_EVENT_OFFSCREEN:
817 case SCORE_EVENT_GROUND_VALID:
818 if (s->ball.ground_hit) {
819 winner = last_hit_by;
820 } else {
821 winner = other;
823 break;
824 default:
825 break;
828 /* determine loser based on winner */
829 if (winner == last_hit_by) {
830 loser = other;
831 } else {
832 loser = last_hit_by;
835 /* we cannot be in an "impossibly high" set */
836 assert(s->current_set < SETS_TO_WIN*2-1);
838 winner->game++;
839 if( loser->game < winner->game-1) {
840 if( winner->game >= 4) {
841 winner->game = loser->game = 0;
842 winner->sets[s->current_set]++;
844 /* serving is changed when the "game" is over */
845 s->serving_player = (s->serving_player==1)?(2):(1);
847 #ifdef HAVE_VOICE_FILES
848 /* speak the current score */
849 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);
850 #endif
852 /* scoring the set.. */
853 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
854 winner->sets[s->current_set] == 7) {
855 s->current_set++;
856 s->winner = game_get_winner( s);
861 /* forget this event - we've handled it */
862 s->score_event = SCORE_UNDECIDED;
864 if (winner == &PLAYER(s, 1)) {
865 return WINNER_PLAYER1;
866 } else {
867 return WINNER_PLAYER2;
871 char* format_sets( GameState* s) {
872 static char sets[100];
873 static char tmp[100];
874 int i, max = s->current_set;
876 sets[0] = '\0';
878 if( s->winner != WINNER_NONE) {
879 max--;
881 for( i=0; i<=max; i++) {
882 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
883 strcat( sets, tmp);
886 sets[strlen(sets)-2] = '\0';
888 return sets;
891 char* format_game( GameState* s) {
892 static char game[100];
893 static const int game_scoring[] = { 0, 15, 30, 40 };
895 if( PLAYER(s, 1).game < 4 && PLAYER(s, 2).game < 4) {
896 #ifdef HAVE_VOICE_FILES
897 if (PLAYER(s, 1).game > 0 || PLAYER(s, 2).game > 0) {
898 if (PLAYER(s, 1).game == PLAYER(s, 2).game) {
899 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_ALL);
900 } else {
901 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
904 #endif
905 sprintf( game, "%d - %d", game_scoring[PLAYER(s, 1).game], game_scoring[PLAYER(s, 2).game]);
906 } else if( PLAYER(s, 1).game > PLAYER(s, 2).game) {
907 #ifdef HAVE_VOICE_FILES
908 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
909 #endif
910 strcpy( game, "advantage player 1");
911 } else if( PLAYER(s, 1).game < PLAYER(s, 2).game) {
912 #ifdef HAVE_VOICE_FILES
913 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
914 #endif
915 strcpy( game, "advantage player 2");
916 } else {
917 #ifdef HAVE_VOICE_FILES
918 voice_say_list(1, VOICE_DEUCE);
919 #endif
920 strcpy( game, "deuce");
923 return game;
926 char* format_status( GameState* s) {
927 static char status[100];
928 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
930 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]);
932 return status;
935 int game_get_winner( GameState* s) {
936 unsigned int i;
937 int sets[2] = {0};
939 for( i=0; i<s->current_set; i++) {
940 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
941 sets[0]++;
942 } else {
943 sets[1]++;
947 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
948 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
950 return WINNER_NONE;