5 * Copyright (C) 2003, 2007 Thomas Perl <thp@perli.net>
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,
33 void game( bool singleplayer
) {
35 { 0, 0, 0.0, 0.0, 0.0 },
37 { GAME_X_MIN
-RACKET_X_MID
*2, GAME_Y_MID
, 0, 0, 1, DESIRE_NORMAL
, PLAYER_TYPE_HUMAN
, false },
38 { GAME_X_MAX
+RACKET_X_MID
*2, GAME_Y_MID
, 0, 0, 0, DESIRE_NORMAL
, PLAYER_TYPE_HUMAN
, false },
43 "welcome to tennix " VERSION
,
58 strcpy( s
.game_score_str
, format_game( &s
));
59 strcpy( s
.sets_score_str
, format_sets( &s
));
61 Uint32 ot
= SDL_GetTicks();
63 Uint32 dt
= GAME_TICKS
;
65 Uint32 accumulator
= 0;
71 s
.player1
.type
= PLAYER_TYPE_AI
;
73 s
.player2
.type
= PLAYER_TYPE_AI
;
76 game_setup_serve( &s
);
80 for( x
= 0; x
<NGRAM_STEPS
; x
++) {
81 for( y
= 0; y
<NGRAM_STEPS
; y
++) {
82 for( z
= 0; z
<NGRAM_STEPS
; z
++) {
98 while( accumulator
>= dt
) {
105 s
.was_stopped
= false;
114 bool step( GameState
* s
) {
118 if( get_phase( s
) < 1.0) {
119 if( !s
->ground
.jump
) {
122 if( IS_OUT_Y( s
->ball
.y
)) {
123 /* out - responsibilities stay the same */
126 s
->referee
= REFEREE_OUT
;
128 /* not out - responsibilities change */
129 s
->player1
.responsible
= !(s
->player2
.responsible
= !s
->player2
.responsible
);
130 s
->status
= format_status( s
);
131 s
->referee
= REFEREE_NORMAL
;
135 s
->ground
.x
= s
->ball
.x
;
136 s
->ground
.y
= s
->ball
.y
;
138 if( s
->ground
.jump
&& !(s
->time
%5)) s
->ground
.jump
--;
141 if( IS_OUT_X(s
->ball
.x
) || IS_OFFSCREEN_Y(s
->ball
.y
)) {
142 if( IS_OFFSCREEN( s
->ball
.x
, s
->ball
.y
)) {
143 s
->player1_serves
= s
->player1
.responsible
;
145 score_game( s
, s
->player2
.responsible
);
146 strcpy( s
->game_score_str
, format_game( s
));
147 strcpy( s
->sets_score_str
, format_sets( s
));
149 if( s
->player1
.responsible
) {
150 if( s
->player1
.type
== PLAYER_TYPE_HUMAN
&& s
->player2
.type
== PLAYER_TYPE_AI
) {
151 s
->status
= "computer scores";
153 s
->status
= "player 2 scores";
155 s
->referee
= REFEREE_PLAYER2
;
157 if( s
->player1
.type
== PLAYER_TYPE_HUMAN
&& s
->player2
.type
== PLAYER_TYPE_AI
) {
158 s
->status
= "player scores";
160 s
->status
= "player 1 scores";
162 s
->referee
= REFEREE_PLAYER1
;
165 game_setup_serve( s
);
169 s
->was_stopped
= true;
171 s
->history_is_locked
= 0;
172 s
->ngram_prediction
= 0.0;
173 printf( "-- game reset --\n");
176 if( IS_OUT_X(s
->ball
.x
)) {
177 if( !s
->history_is_locked
&& s
->referee
!= REFEREE_OUT
) {
178 s
->history
[s
->history_size
] = (int)(NGRAM_STEPS
*s
->ball
.y
/HEIGHT
);
179 /*if( s->ball.move_x < 0 || (s->ball.move_x == 0 && s->player1.responsible)) {
184 printf( " Storing: %d (at %d)\n", s->history[s->history_size], s->history_size);*/
186 if( s
->history_size
== 3) {
187 s
->ngram
[s
->history
[0]][s
->history
[1]][s
->history
[2]] += 10;
188 printf( "history: %d, %d, %d\n", s
->history
[0], s
->history
[1], s
->history
[2]);
189 s
->ngram_prediction
= ngram_predictor( s
);
190 s
->history
[0] = s
->history
[1];
191 s
->history
[1] = s
->history
[2];
194 s
->history_is_locked
= true;
196 if( s
->ball
.move_x
<= 0 && IS_NEAR_X( s
->player1
.x
, s
->ball
.x
) && IS_NEAR_Y( s
->player1
.y
, s
->ball
.y
) && s
->player1
.state
&& s
->referee
!= REFEREE_OUT
) {
197 s
->ball
.x
= GAME_X_MIN
;
198 if( s
->player1
.state
== PLAYER_STATE_MAX
) {
199 s
->ball
.move_x
= PLAYER_POWERSHOT
;
201 s
->ball
.move_x
= 2.5 + 2.0*s
->player1
.state
/PLAYER_STATE_MAX
;
203 s
->ball
.move_y
= get_move_y( s
, 1);
204 s
->player2
.responsible
= !(s
->player1
.responsible
= 1);
205 s
->ball
.jump
+= 1.0-2.0*(s
->player1
.state
<5);
206 sound_applause_stop();
208 } else if( s
->ball
.move_x
>= 0 && IS_NEAR_X( s
->player2
.x
, s
->ball
.x
) && IS_NEAR_Y( s
->player2
.y
, s
->ball
.y
) && s
->player2
.state
&& s
->referee
!= REFEREE_OUT
) {
209 s
->ball
.x
= GAME_X_MAX
;
210 if( s
->player2
.state
== PLAYER_STATE_MAX
) {
211 s
->ball
.move_x
= -PLAYER_POWERSHOT
;
213 s
->ball
.move_x
= -(2.5 + 2.0*s
->player2
.state
/PLAYER_STATE_MAX
);
215 s
->ball
.move_y
= get_move_y( s
, 2);
216 s
->player1
.responsible
= !(s
->player2
.responsible
= 1);
217 s
->ball
.jump
+= 1.0-2.0*(s
->player2
.state
<5);
218 sound_applause_stop();
223 s
->history_is_locked
= false;
226 /* Update ball_dest for debugging purposes */
230 s
->ball
.x
+= s
->ball
.move_x
;
231 s
->ball
.y
+= s
->ball
.move_y
;
233 if( s
->player1
.state
) s
->player1
.state
--;
234 if( s
->player2
.state
) s
->player2
.state
--;
237 keys
= SDL_GetKeyState( NULL
);
239 if( keys
['c'] && s
->time
%50==0) {
241 if( s
->court_type
> GR_CTT_LAST
) {
242 s
->court_type
= GR_CTT_FIRST
;
246 if( !is_fading() && !s
->is_over
) {
247 if( s
->player1
.type
== PLAYER_TYPE_HUMAN
) {
248 input_human( &s
->player1
, keys
['w'], keys
['s'], keys
['d']);
250 input_ai( &s
->player1
, &s
->ball
, &s
->player2
, s
);
253 if( s
->player2
.type
== PLAYER_TYPE_HUMAN
) {
254 input_human( &s
->player2
, keys
['o'], keys
['l'], keys
['k']);
256 input_ai( &s
->player2
, &s
->ball
, &s
->player1
, s
);
260 if( keys
['f']) SDL_WM_ToggleFullScreen( screen
);
261 if( keys
['y']) SDL_SaveBMP( screen
, "screenshot.bmp");
263 if( keys
[SDLK_ESCAPE
] || keys
['q']) return true;
265 limit_value( &s
->player1
.y
, PLAYER_Y_MIN
, PLAYER_Y_MAX
);
266 limit_value( &s
->player2
.y
, PLAYER_Y_MIN
, PLAYER_Y_MAX
);
267 limit_value( &s
->ball
.jump
, BALL_JUMP_MIN
, BALL_JUMP_MAX
);
272 void render( GameState
* s
) {
273 if( s
->winner
!= WINNER_NONE
) {
279 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);
280 sprintf( s
->game_score_str
, "player %d wins the match with %s", s
->winner
, format_sets( s
));
281 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
);
286 fill_image( s
->court_type
, 120, 120, 400, 250);
287 show_image( GR_COURT
, 0, 0, 255);
288 show_sprite( GR_REFEREE
, s
->referee
, 4, 250, 10, 255);
289 show_image( GR_SHADOW
, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
+ get_phase( s
) - BALL_Y_MID
, 255);
291 show_sprite( GR_RACKET
, (!s
->player1
.state
), 4, s
->player1
.x
-RACKET_X_MID
, s
->player1
.y
-RACKET_Y_MID
, 255);
292 show_sprite( GR_RACKET
, (!s
->player2
.state
)+2, 4, s
->player2
.x
-RACKET_X_MID
, s
->player2
.y
-RACKET_Y_MID
, 255);
294 if( s
->ground
.jump
) {
295 show_sprite( GR_GROUND
, s
->ground
.jump
-1, 3, s
->ground
.x
- BALL_X_MID
, s
->ground
.y
- BALL_Y_MID
, 128);
298 if( s
->ball
.move_x
> 0) {
299 show_sprite( GR_BALL
, (s
->time
/500)%4, 4, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
-BALL_Y_MID
, 255);
300 } else if( s
->ball
.move_x
< 0) {
301 show_sprite( GR_BALL
, 3-(s
->time
/500)%4, 4, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
-BALL_Y_MID
, 255);
303 show_sprite( GR_BALL
, 0, 4, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
-BALL_Y_MID
, 255);
307 font_draw_string( GR_DKC2_FONT
, s
->game_score_str
, 14, 14, 0, ANIMATION_NONE
);
308 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
);
310 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
);
313 line_horiz( s
->player1
.y
, 255, 0, 0);
314 line_horiz( s
->player2
.y
, 0, 0, 255);
315 line_horiz( s
->ball
.y
, 0, 255, 0);
317 line_vert( s
->player1
.x
, 255, 0, 0);
318 line_vert( s
->player2
.x
, 0, 0, 255);
319 line_vert( s
->ball
.x
, 0, 255, 0);
321 line_horiz( s
->player1
.ball_dest
, 255, 0, 255);
322 line_horiz( s
->player2
.ball_dest
, 0, 255, 255);
324 line_horiz( GAME_Y_MIN
, 100, 100, 100);
325 line_horiz( GAME_Y_MAX
, 100, 100, 100);
331 void limit_value( float* value
, float min
, float max
) {
334 } else if( *value
> max
) {
339 float get_phase( GameState
* s
) {
341 float x
, min
, max
, direction
;
346 direction
= s
->ball
.move_x
;
348 pos
= (direction
>0)?(1-GROUND_PHASE
):(GROUND_PHASE
);
350 fract
= (x
-min
)/(max
-min
);
354 return fabsf( cosf(PI
*fract
/2))*PHASE_AMP
*s
->ball
.jump
;
356 fract
= (pos
-fract
)/(1-pos
);
357 return fabsf( sinf(PI
*fract
/2))*PHASE_AMP
*s
->ball
.jump
;
361 float get_move_y( GameState
* s
, unsigned char player
) {
362 float pct
, dest
, x_len
, y_len
;
363 float py
, by
, pa
, move_x
;
365 py
= (player
==1)?(s
->player1
.y
):(s
->player2
.y
);
368 move_x
= s
->ball
.move_x
;
370 /* -1.0 .. 1.0 for racket hit position */
371 pct
= (by
-py
)/(pa
/2);
372 limit_value( &pct
, -1.0, 1.0);
374 /* Y destination for ball */
375 dest
= GAME_Y_MID
+ pct
*(GAME_Y_MAX
-GAME_Y_MIN
);
377 s
->player1
.ball_dest
= dest
;
379 s
->player2
.ball_dest
= dest
;
382 /* lengths for the ball's journey */
384 x_len
= fabsf(GAME_X_MAX
- s
->ball
.x
);
385 y_len
= dest
- by
+ MOVE_Y_SEED
-rand()%MOVE_Y_SEED
*2;
387 x_len
= s
->ball
.x
- GAME_X_MIN
;
388 y_len
= by
- dest
+ MOVE_Y_SEED
-rand()%MOVE_Y_SEED
*2;
391 /* return the should-be value for move_y */
392 return (y_len
*move_x
)/(x_len
);
395 void input_human( Player
* player
, bool up
, bool down
, bool hit
) {
405 if( !player
->state
&& !player
->state_locked
) {
406 player
->state
= PLAYER_STATE_MAX
;
407 player
->state_locked
= true;
410 player
->state_locked
= false;
414 void input_ai( Player
* player
, Ball
* ball
, Player
* opponent
, GameState
* s
) {
418 if( fabsf( player
->y
- ball
->y
) > RACKET_Y_MID
*5) {
422 target
= GAME_Y_MID
+ (opponent
->ball_dest
- GAME_Y_MID
)/5;
424 if( player
->responsible
) {
425 if( player
->desire
== DESIRE_NORMAL
&& !IS_NEAR_Y_AI( player
->y
, ball
->y
)) {
426 if( player
->y
< ball
->y
) {
427 player
->y
+= fmin( 2*fact
, ball
->y
- player
->y
);
428 } else if( player
->y
> ball
->y
) {
429 player
->y
-= fmin( 2*fact
, player
->y
- ball
->y
);
433 if( (ball
->move_x
!= 0 || IS_NEAR_Y_AI( player
->y
, ball
->y
)) && IS_NEAR_X_AI( player
->x
, ball
->x
) && !player
->state
&& rand()%4) {
434 player
->state
= PLAYER_STATE_MAX
;
436 } else if( ball
->move_x
== 0) {
437 if( player
->desire
== DESIRE_NORMAL
&& !IS_NEAR_Y_AI( player
->y
, target
)) {
438 if( player
->y
< target
) {
439 player
->y
+= fmin( fact
, (target
-player
->y
)/40.0);
440 } else if( player
->y
> target
) {
441 player
->y
-= fmin( fact
, (player
->y
-target
)/40.0);
444 } else if( s
->ngram_prediction
> 0.0) {
445 target
= s
->ngram_prediction
*((float)HEIGHT
)/((float)(NGRAM_STEPS
));
446 target
= GAME_Y_MID
+ (target
-GAME_Y_MID
)*1.5;
448 if( player
->desire
== DESIRE_NORMAL
&& !IS_NEAR_Y_AI( player
->y
, target
)) {
449 if( player
->y
< target
) {
450 player
->y
+= fmin( fact
, (target
-player
->y
)/40.0);
451 } else if( player
->y
> target
) {
452 player
->y
-= fmin( fact
, (player
->y
-target
)/40.0);
456 if( player->desire == DESIRE_NORMAL) {
457 if( !IS_NEAR_Y_AI( player->y, target)) {
458 player->y += (target - player->y)/40.0;
464 void game_setup_serve( GameState
* s
) {
466 s
->ball
.y
= GAME_Y_MID
;
467 s
->ball
.move_x
= 0.0;
468 s
->ball
.move_y
= 0.0;
470 if( s
->player1_serves
) {
471 s
->player1
.responsible
= true;
472 s
->player1
.ball_dest
= 0.0;
473 s
->ball
.x
= GAME_X_MIN
-RACKET_X_MID
*1.5;
475 s
->player1
.responsible
= false;
476 s
->player2
.ball_dest
= 0.0;
477 s
->ball
.x
= GAME_X_MAX
+RACKET_X_MID
*1.5;
480 s
->player2
.responsible
= !(s
->player1
.responsible
);
483 float ngram_predictor( GameState
* s
) {
484 unsigned int count
= 0;
485 unsigned long sum
= 0;
489 if( s
->history_size
< 3) {
496 for( z
= 0; z
<NGRAM_STEPS
; z
++) {
497 count
+= s
->ngram
[x
][y
][z
];
498 sum
+= z
* s
->ngram
[x
][y
][z
];
501 result
= ((float)(sum
))/((float)(count
));
502 printf( "predicting next = %.2f\n", result
);
507 void score_game( GameState
* s
, bool player1_scored
) {
508 Player
* winner
= (player1_scored
)?(&(s
->player1
)):(&(s
->player2
));
509 Player
* loser
= (player1_scored
)?(&(s
->player2
)):(&(s
->player1
));
511 if( s
->current_set
>= SETS_TO_WIN
*2-1) {
516 if( loser
->game
< winner
->game
-1) {
517 if( winner
->game
>= 4) {
518 winner
->game
= loser
->game
= 0;
519 winner
->sets
[s
->current_set
]++;
520 /* scoring the set.. */
521 if( (winner
->sets
[s
->current_set
] == 6 && loser
->sets
[s
->current_set
] < 5) ||
522 winner
->sets
[s
->current_set
] == 7) {
524 s
->winner
= game_get_winner( s
);
530 char* format_sets( GameState
* s
) {
531 static char sets
[100];
532 static char tmp
[100];
533 int i
, max
= s
->current_set
;
537 if( s
->winner
!= WINNER_NONE
) {
540 for( i
=0; i
<=max
; i
++) {
541 sprintf( tmp
, "%d:%d, ", s
->player1
.sets
[i
], s
->player2
.sets
[i
]);
545 sets
[strlen(sets
)-2] = '\0';
550 char* format_game( GameState
* s
) {
551 static char game
[100];
552 static const int game_scoring
[] = { 0, 15, 30, 40 };
554 if( s
->player1
.game
< 4 && s
->player2
.game
< 4) {
555 sprintf( game
, "%d - %d", game_scoring
[s
->player1
.game
], game_scoring
[s
->player2
.game
]);
556 } else if( s
->player1
.game
> s
->player2
.game
) {
557 strcpy( game
, "advantage player 1");
558 } else if( s
->player1
.game
< s
->player2
.game
) {
559 strcpy( game
, "advantage player 2");
561 strcpy( game
, "deuce");
567 char* format_status( GameState
* s
) {
568 static char status
[100];
569 static const char* set_names
[] = { "first", "second", "third", "fourth", "fifth" };
571 sprintf( status
, "%d:%d in %s set", s
->player1
.sets
[s
->current_set
], s
->player2
.sets
[s
->current_set
], set_names
[s
->current_set
]);
576 int game_get_winner( GameState
* s
) {
580 for( i
=0; i
<s
->current_set
; i
++) {
581 if( s
->player1
.sets
[i
] > s
->player2
.sets
[i
]) {
588 if( sets
[0] == SETS_TO_WIN
) return WINNER_PLAYER1
;
589 if( sets
[1] == SETS_TO_WIN
) return WINNER_PLAYER2
;