5 * Copyright (C) 2003, 2007, 2008, 2009 Thomas Perl <thp@thpinfo.com>
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,
38 GameState
*gamestate_new() {
42 GameState
template = {
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
,
82 s
= (GameState
*)malloc(sizeof(GameState
));
83 if (s
== NULL
) abort();
85 memcpy(s
, &template, sizeof(GameState
));
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;
102 int gamestate_save(GameState
* s
, const char* filename
)
106 InputDevice
* input_devices
[MAXPLAYERS
];
110 char tmp
[MAXPATHLEN
];
112 assert(getcwd(tmp
, MAXPATHLEN
) == tmp
);
113 assert(chdir(getenv("HOME")) == 0);
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");
129 /* Save data for later recovery + clear */
130 location
= s
->location
;
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) {
143 /* Restore process-specific data */
144 s
->location
= location
;
146 for (i
=1; i
<=MAXPLAYERS
; i
++) {
147 PLAYER(s
, i
).input
= input_devices
[i
-1];
153 assert(chdir(tmp
) == 0);
160 GameState
* gamestate_load(const char* filename
)
165 char tmp
[MAXPATHLEN
];
167 assert(getcwd(tmp
, MAXPATHLEN
) == tmp
);
168 assert(chdir(getenv("HOME")) == 0);
171 fp
= fopen(filename
, "r");
174 s
= (GameState
*)malloc(sizeof(GameState
));
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" */
189 assert(chdir(tmp
) == 0);
196 void gameloop(GameState
*s
) {
197 Uint32 ot
= SDL_GetTicks();
199 Uint32 dt
= GAME_TICKS
;
201 Uint32 accumulator
= 0;
205 #ifdef ENABLE_FPS_LIMIT
206 Uint32 ft
, frames
; /* frame timer and frames */
209 strcpy(s
->game_score_str
, format_game(s
));
210 strcpy(s
->sets_score_str
, format_sets(s
));
211 s
->text_changed
= true;
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
240 while( accumulator
>= dt
) {
243 s
->windtime
+= s
->wind
*dt
;
246 if( s
->was_stopped
) {
248 s
->was_stopped
= false;
251 if (s
->timelimit
!= 0 && s
->time
>= s
->timelimit
) {
255 #ifdef ENABLE_FPS_LIMIT
256 while (frames
*1000.0/((float)(SDL_GetTicks()-ft
+1))>(float)(DEFAULT_FPS
)) {
265 for (p
=1; p
<=MAXPLAYERS
; p
++) {
266 input_device_part_game(PLAYER(s
, p
).input
);
272 stop_sample(SOUND_AUDIENCE
);
273 stop_sample(SOUND_RAIN
);
276 bool step( GameState
* s
) {
279 bool ground_event
= false;
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
;
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) {
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
;
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
;
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
;
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!";
339 /* first ground hit in valid area */
340 s
->ball
.ground_hit
= true;
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
)) {
361 s
->status
= "player 1 scores";
362 s
->referee
= REFEREE_PLAYER1
;
365 s
->status
= "player 2 scores";
366 s
->referee
= REFEREE_PLAYER2
;
374 strcpy(s
->game_score_str
, format_game(s
));
375 strcpy(s
->sets_score_str
, format_sets(s
));
376 s
->text_changed
= true;
379 if (s
->location
->max_visitors
> 100) {
380 s
->sound_events
|= SOUND_EVENT_APPLAUSE
;
382 /*FIXME n-gram predictor broken
384 s->history_is_locked = 0;
385 s->ngram_prediction = 0.0;*/
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
) {
392 if (!s
->ball
.ground_hit
&& s
->ball
.move_x
!= 0.0) {
393 s
->status
= "volley!";
395 s
->status
= format_status(s
);
397 switch (PLAYER(s
, p
).desire
) {
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
;
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
;
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
;
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
;
420 pan_sample_group(SOUND_RACKET_FIRST
, SOUND_RACKET_LAST
, 0.3);
422 pan_sample_group(SOUND_RACKET_FIRST
, SOUND_RACKET_LAST
, 0.7);
423 s
->ball
.move_x
*= -1;
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);
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];
443 s->history_is_locked = true;
447 /*s->history_is_locked = false;*/
452 keys
= SDL_GetKeyState(NULL
);
457 play_sample_background(SOUND_RAIN
);
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
) {
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
]) {
490 keys
= SDL_GetKeyState( NULL
);
493 while( (keys
['p'] || keys
[SDLK_F4
]) == 0) {
495 keys
= SDL_GetKeyState( NULL
);
498 while( keys
['p'] || keys
[SDLK_F4
]) {
500 keys
= SDL_GetKeyState( NULL
);
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
));
534 void render( GameState
* s
) {
539 #ifdef EXTENDED_REFEREE
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
);
557 if( s
->winner
!= WINNER_NONE
) {
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);
570 if (s
->displayed_court_type
!= s
->location
->court_type
|| s
->text_changed
|| s
->old_referee
!= s
->referee
) {
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
) {
585 case REFEREE_PLAYER1
:
586 case REFEREE_PLAYER2
:
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);
610 show_sprite( GR_REFEREE
, s
->referee
, 4, 250, 10, 255);
613 s
->displayed_court_type
= s
->location
->court_type
;
614 s
->text_changed
= false;
615 s
->old_referee
= s
->referee
;
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
;
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
++) {
663 draw_line_faded(x
, y
, x
+10+s
->wind
*5, y
+30, 0, 0, 255, 100, 200, 255);
667 * Cheap-ish update of the whole screen. This can
668 * probably be optimized.
670 update_rect(0, 0, WIDTH
, HEIGHT
);
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);
688 fill_image_offset(GR_FOG2
, 0, 0, WIDTH
, HEIGHT
, -s
->time
/150-s
->windtime
/200, 0);
690 fill_image_offset(GR_FOG
, 0, 0, WIDTH
, HEIGHT
, -s
->time
/100-s
->windtime
/150, 20);
692 fill_image_offset(GR_FOG2
, 0, 0, WIDTH
, HEIGHT
, -s
->time
/180-s
->windtime
/180, 80);
694 fill_image_offset(GR_FOG
, 0, 0, WIDTH
, HEIGHT
, s
->time
/200-s
->windtime
/100, 0);
699 show_image(GR_NIGHT
, 0, 0, 255);
705 void limit_value( float* value
, float min
, float max
) {
708 } else if( *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
;
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 */
730 x_len
= GAME_X_MAX
- s
->ball
.x
;
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
;
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
);
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
;
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;
773 PLAYER(s
, player
).use_power
= true;
777 void input_ai(GameState
* s
, int player
) {
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) {
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
) {
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;
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;
844 if( s
->history_size
< 3) {
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
));
858 printf( "predicting next = %.2f\n", 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));
876 last_hit_by
= &(PLAYER(s
, 2));
877 other
= &(PLAYER(s
, 1));
880 switch (s
->score_event
) {
881 case SCORE_EVENT_NET
:
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
;
898 /* determine loser based on winner */
899 if (winner
== last_hit_by
) {
905 /* we cannot be in an "impossibly high" set */
906 assert(s
->current_set
< SETS_TO_WIN
*2-1);
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
);
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) {
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
;
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
;
948 if( s
->winner
!= WINNER_NONE
) {
951 for( i
=0; i
<=max
; i
++) {
952 sprintf( tmp
, "%d:%d, ", PLAYER(s
, 1).sets
[i
], PLAYER(s
, 2).sets
[i
]);
956 sets
[strlen(sets
)-2] = '\0';
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
);
971 voice_say_list(2, VOICE_LOVE_IN
+ 2*(PLAYER(s
, 1).game
), VOICE_LOVE_OUT
+ 2*(PLAYER(s
, 2).game
));
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
);
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
);
985 strcpy( game
, "advantage player 2");
987 #ifdef HAVE_VOICE_FILES
988 voice_say_list(1, VOICE_DEUCE
);
990 strcpy( game
, "deuce");
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
]);
1005 int game_get_winner( GameState
* s
) {
1009 for( i
=0; i
<s
->current_set
; i
++) {
1010 if( PLAYER(s
, 1).sets
[i
] > PLAYER(s
, 2).sets
[i
]) {
1017 if( sets
[0] == SETS_TO_WIN
) return WINNER_PLAYER1
;
1018 if( sets
[1] == SETS_TO_WIN
) return WINNER_PLAYER2
;