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,
37 GameState
*gamestate_new() {
41 GameState
template = {
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
,
80 s
= (GameState
*)malloc(sizeof(GameState
));
81 if (s
== NULL
) abort();
83 memcpy(s
, &template, sizeof(GameState
));
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;
100 void gameloop(GameState
*s
) {
101 Uint32 ot
= SDL_GetTicks();
103 Uint32 dt
= GAME_TICKS
;
105 Uint32 accumulator
= 0;
108 #ifdef ENABLE_FPS_LIMIT
109 Uint32 ft
, frames
; /* frame timer and frames */
112 strcpy(s
->game_score_str
, format_game(s
));
113 strcpy(s
->sets_score_str
, format_sets(s
));
114 s
->text_changed
= true;
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
139 while( accumulator
>= dt
) {
142 s
->windtime
+= s
->wind
*dt
;
145 if( s
->was_stopped
) {
147 s
->was_stopped
= false;
150 if (s
->timelimit
!= 0 && s
->time
>= s
->timelimit
) {
154 #ifdef ENABLE_FPS_LIMIT
155 while (frames
*1000.0/((float)(SDL_GetTicks()-ft
+1))>(float)(DEFAULT_FPS
)) {
167 stop_sample(SOUND_AUDIENCE
);
168 stop_sample(SOUND_RAIN
);
171 bool step( GameState
* s
) {
174 bool ground_event
= false;
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
;
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 */
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)) {
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
;
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
;
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
;
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!";
236 /* first ground hit in valid area */
237 s
->ball
.ground_hit
= true;
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
)) {
258 s
->status
= "player 1 scores";
259 s
->referee
= REFEREE_PLAYER1
;
262 s
->status
= "player 2 scores";
263 s
->referee
= REFEREE_PLAYER2
;
271 strcpy(s
->game_score_str
, format_game(s
));
272 strcpy(s
->sets_score_str
, format_sets(s
));
273 s
->text_changed
= true;
276 if (s
->location
->max_visitors
> 100) {
277 s
->play_sound
= SOUND_APPLAUSE
;
279 /*FIXME n-gram predictor broken
281 s->history_is_locked = 0;
282 s->ngram_prediction = 0.0;*/
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
) {
289 if (!s
->ball
.ground_hit
&& s
->ball
.move_x
!= 0.0) {
290 s
->status
= "volley!";
292 s
->status
= format_status(s
);
294 switch (PLAYER(s
, p
).desire
) {
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
;
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
;
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
;
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
;
317 pan_sample(SOUND_RACKET
, 0.4);
319 pan_sample(SOUND_RACKET
, 0.6);
320 s
->ball
.move_x
*= -1;
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);
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];
340 s->history_is_locked = true;
344 /*s->history_is_locked = false;*/
349 keys
= SDL_GetKeyState(NULL
);
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
);
358 case SDL_JOYBUTTONUP
: case SDL_JOYBUTTONDOWN
:
359 if (e
.jbutton
.button
== JOYSTICK_BUTTON_A
) {
360 s
->joystick_a
= (e
.jbutton
.state
== SDL_PRESSED
);
368 play_sample_background(SOUND_RAIN
);
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
,
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
);
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
]) {
416 keys
= SDL_GetKeyState( NULL
);
419 while( (keys
['p'] || keys
[SDLK_F4
]) == 0) {
421 keys
= SDL_GetKeyState( NULL
);
424 while( keys
['p'] || keys
[SDLK_F4
]) {
426 keys
= SDL_GetKeyState( NULL
);
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
));
460 void render( GameState
* s
) {
463 #ifdef EXTENDED_REFEREE
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
) {
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
);
483 if (s
->displayed_court_type
!= s
->location
->court_type
|| s
->text_changed
|| s
->old_referee
!= s
->referee
) {
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
) {
498 case REFEREE_PLAYER1
:
499 case REFEREE_PLAYER2
:
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);
523 show_sprite( GR_REFEREE
, s
->referee
, 4, 250, 10, 255);
526 s
->displayed_court_type
= s
->location
->court_type
;
527 s
->text_changed
= false;
528 s
->old_referee
= s
->referee
;
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
;
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
++) {
565 draw_line_faded(x
, y
, x
+10+s
->wind
*5, y
+30, 0, 0, 255, 100, 200, 255);
569 * Cheap-ish update of the whole screen. This can
570 * probably be optimized.
572 update_rect(0, 0, WIDTH
, HEIGHT
);
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);
590 fill_image_offset(GR_FOG2
, 0, 0, WIDTH
, HEIGHT
, -s
->time
/150-s
->windtime
/200, 0);
592 fill_image_offset(GR_FOG
, 0, 0, WIDTH
, HEIGHT
, -s
->time
/100-s
->windtime
/150, 20);
594 fill_image_offset(GR_FOG2
, 0, 0, WIDTH
, HEIGHT
, -s
->time
/180-s
->windtime
/180, 80);
596 fill_image_offset(GR_FOG
, 0, 0, WIDTH
, HEIGHT
, s
->time
/200-s
->windtime
/100, 0);
601 show_image(GR_NIGHT
, 0, 0, 255);
607 void limit_value( float* value
, float min
, float max
) {
610 } else if( *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
;
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 */
633 x_len
= fabsf(GAME_X_MAX
- s
->ball
.x
);
634 y_len
= dest
- by
+ MOVE_Y_SEED
-rand()%MOVE_Y_SEED
*2;
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
;
649 * Only use mouse control if the user isn't pressing any buttons
650 * this way, keyboard control still works when mouse control is
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
) {
667 diff
= (player
->y
-player
->mouse_y
<diff
)?(player
->y
-player
->mouse_y
):(diff
);
668 } else if (player
->mouse_y
> player
->y
) {
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
) {
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
;
687 player
->y
-= fminf(diff
, diff
*player
->accelerate
);
688 player
->accelerate
*= PLAYER_ACCEL_INCREASE
;
690 player
->y
+= fminf(diff
, diff
*player
->accelerate
);
691 player
->accelerate
*= PLAYER_ACCEL_INCREASE
;
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;
703 player
->use_power
= true;
707 void input_ai( Player
* player
, Ball
* ball
, Player
* opponent
, GameState
* s
) {
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) {
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
) {
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;
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;
774 if( s
->history_size
< 3) {
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
));
788 printf( "predicting next = %.2f\n", 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));
806 last_hit_by
= &(PLAYER(s
, 2));
807 other
= &(PLAYER(s
, 1));
810 switch (s
->score_event
) {
811 case SCORE_EVENT_NET
:
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
;
828 /* determine loser based on winner */
829 if (winner
== last_hit_by
) {
835 /* we cannot be in an "impossibly high" set */
836 assert(s
->current_set
< SETS_TO_WIN
*2-1);
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
);
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) {
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
;
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
;
878 if( s
->winner
!= WINNER_NONE
) {
881 for( i
=0; i
<=max
; i
++) {
882 sprintf( tmp
, "%d:%d, ", PLAYER(s
, 1).sets
[i
], PLAYER(s
, 2).sets
[i
]);
886 sets
[strlen(sets
)-2] = '\0';
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
);
901 voice_say_list(2, VOICE_LOVE_IN
+ 2*(PLAYER(s
, 1).game
), VOICE_LOVE_OUT
+ 2*(PLAYER(s
, 2).game
));
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
);
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
);
915 strcpy( game
, "advantage player 2");
917 #ifdef HAVE_VOICE_FILES
918 voice_say_list(1, VOICE_DEUCE
);
920 strcpy( game
, "deuce");
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
]);
935 int game_get_winner( GameState
* s
) {
939 for( i
=0; i
<s
->current_set
; i
++) {
940 if( PLAYER(s
, 1).sets
[i
] > PLAYER(s
, 2).sets
[i
]) {
947 if( sets
[0] == SETS_TO_WIN
) return WINNER_PLAYER1
;
948 if( sets
[1] == SETS_TO_WIN
) return WINNER_PLAYER2
;