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
},
38 { GAME_X_MAX
+RACKET_X_MID
*2, GAME_Y_MID
, 0, 0, 0, DESIRE_NORMAL
, PLAYER_TYPE_HUMAN
},
43 "welcome to tennix " VERSION
,
48 strcpy( s
.score_str
, "00:00");
50 Uint32 ot
= SDL_GetTicks();
52 Uint32 dt
= GAME_TICKS
;
54 Uint32 accumulator
= 0;
59 s
.player1
.type
= PLAYER_TYPE_AI
;
61 s
.player2
.type
= PLAYER_TYPE_AI
;
64 game_setup_serve( &s
);
77 while( accumulator
>= dt
) {
84 s
.was_stopped
= false;
93 bool step( GameState
* s
) {
97 if( get_phase( s
) < 1.0) {
98 if( !s
->ground
.jump
) {
101 if( IS_OUT_Y( s
->ball
.y
)) {
102 /* out - responsibilities stay the same */
103 s
->status
= "out -- don't hit the ball!";
105 s
->referee
= REFEREE_OUT
;
107 /* not out - responsibilities change */
108 s
->player1
.responsible
= !(s
->player2
.responsible
= !s
->player2
.responsible
);
110 s
->referee
= REFEREE_NORMAL
;
114 s
->ground
.x
= s
->ball
.x
;
115 s
->ground
.y
= s
->ball
.y
;
117 if( s
->ground
.jump
&& !(s
->time
%5)) s
->ground
.jump
--;
120 if( IS_OUT_X(s
->ball
.x
) || IS_OFFSCREEN_Y(s
->ball
.y
)) {
121 if( IS_OFFSCREEN( s
->ball
.x
, s
->ball
.y
)) {
122 s
->player1_serves
= s
->player1
.responsible
;
124 (s
->player1
.responsible
)?(s
->player2
.score
++):(s
->player1
.score
++);
125 sprintf( s
->score_str
, "%02d:%02d", s
->player1
.score
, s
->player2
.score
);
127 if( s
->player1
.responsible
) {
128 if( s
->player1
.type
== PLAYER_TYPE_HUMAN
&& s
->player2
.type
== PLAYER_TYPE_AI
) {
129 s
->status
= "computer scores";
131 s
->status
= "player 2 scores";
133 s
->referee
= REFEREE_PLAYER2
;
135 if( s
->player1
.type
== PLAYER_TYPE_HUMAN
&& s
->player2
.type
== PLAYER_TYPE_AI
) {
136 s
->status
= "player scores";
138 s
->status
= "player 1 scores";
140 s
->referee
= REFEREE_PLAYER1
;
143 game_setup_serve( s
);
147 s
->was_stopped
= true;
150 if( IS_OUT_X(s
->ball
.x
)) {
151 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
) {
152 s
->ball
.x
= GAME_X_MIN
;
153 if( s
->player1
.state
== PLAYER_STATE_MAX
) {
154 s
->ball
.move_x
= PLAYER_POWERSHOT
;
156 s
->ball
.move_x
= 2.5 + 2.0*s
->player1
.state
/PLAYER_STATE_MAX
;
158 s
->ball
.move_y
= get_move_y( s
, 1);
159 s
->player2
.responsible
= !(s
->player1
.responsible
= 1);
160 s
->ball
.jump
+= 1.0-2.0*(s
->player1
.state
<5);
161 sound_applause_stop();
163 } 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
) {
164 s
->ball
.x
= GAME_X_MAX
;
165 if( s
->player2
.state
== PLAYER_STATE_MAX
) {
166 s
->ball
.move_x
= -PLAYER_POWERSHOT
;
168 s
->ball
.move_x
= -(2.5 + 2.0*s
->player2
.state
/PLAYER_STATE_MAX
);
170 s
->ball
.move_y
= get_move_y( s
, 2);
171 s
->player1
.responsible
= !(s
->player2
.responsible
= 1);
172 s
->ball
.jump
+= 1.0-2.0*(s
->player2
.state
<5);
173 sound_applause_stop();
179 /* Update ball_dest for debugging purposes */
183 s
->ball
.x
+= s
->ball
.move_x
;
184 s
->ball
.y
+= s
->ball
.move_y
;
186 if( s
->player1
.state
) s
->player1
.state
--;
187 if( s
->player2
.state
) s
->player2
.state
--;
190 keys
= SDL_GetKeyState( NULL
);
193 if( s
->player1
.type
== PLAYER_TYPE_HUMAN
) {
194 input_human( &s
->player1
, keys
['w'], keys
['s'], keys
['d']);
196 input_ai( &s
->player1
, &s
->ball
, &s
->player2
);
199 if( s
->player2
.type
== PLAYER_TYPE_HUMAN
) {
200 input_human( &s
->player2
, keys
['o'], keys
['l'], keys
['k']);
202 input_ai( &s
->player2
, &s
->ball
, &s
->player1
);
206 if( keys
['f']) SDL_WM_ToggleFullScreen( screen
);
207 if( keys
['y']) SDL_SaveBMP( screen
, "screenshot.bmp");
209 if( keys
[SDLK_ESCAPE
] || keys
['q']) return true;
211 limit_value( &s
->player1
.y
, PLAYER_Y_MIN
, PLAYER_Y_MAX
);
212 limit_value( &s
->player2
.y
, PLAYER_Y_MIN
, PLAYER_Y_MAX
);
213 limit_value( &s
->ball
.jump
, BALL_JUMP_MIN
, BALL_JUMP_MAX
);
218 void render( GameState
* s
) {
220 show_image( GR_COURT
, 0, 0, 255);
221 show_sprite( GR_REFEREE
, s
->referee
, 4, 250, 10, 255);
222 show_image( GR_SHADOW
, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
+ get_phase( s
) - BALL_Y_MID
, 255);
224 show_sprite( GR_RACKET
, (!s
->player1
.state
), 4, s
->player1
.x
-RACKET_X_MID
, s
->player1
.y
-RACKET_Y_MID
, 255);
225 show_sprite( GR_RACKET
, (!s
->player2
.state
)+2, 4, s
->player2
.x
-RACKET_X_MID
, s
->player2
.y
-RACKET_Y_MID
, 255);
227 if( s
->ground
.jump
) {
228 show_sprite( GR_GROUND
, s
->ground
.jump
-1, 3, s
->ground
.x
- BALL_X_MID
, s
->ground
.y
- BALL_Y_MID
, 128);
231 if( s
->ball
.move_x
> 0) {
232 show_sprite( GR_BALL
, (s
->time
/500)%4, 4, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
-BALL_Y_MID
, 255);
233 } else if( s
->ball
.move_x
< 0) {
234 show_sprite( GR_BALL
, 3-(s
->time
/500)%4, 4, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
-BALL_Y_MID
, 255);
236 show_sprite( GR_BALL
, 0, 4, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
-BALL_Y_MID
, 255);
240 font_draw_string( GR_DKC2_FONT
, s
->score_str
, (WIDTH
-font_get_string_width( GR_DKC2_FONT
, s
->score_str
))-14, 14, 0, ANIMATION_NONE
);
242 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
);
245 line_horiz( s
->player1
.y
, 255, 0, 0);
246 line_horiz( s
->player2
.y
, 0, 0, 255);
247 line_horiz( s
->ball
.y
, 0, 255, 0);
249 line_vert( s
->player1
.x
, 255, 0, 0);
250 line_vert( s
->player2
.x
, 0, 0, 255);
251 line_vert( s
->ball
.x
, 0, 255, 0);
253 line_horiz( s
->player1
.ball_dest
, 255, 0, 255);
254 line_horiz( s
->player2
.ball_dest
, 0, 255, 255);
256 line_horiz( GAME_Y_MIN
, 100, 100, 100);
257 line_horiz( GAME_Y_MAX
, 100, 100, 100);
263 void limit_value( float* value
, float min
, float max
) {
266 } else if( *value
> max
) {
271 float get_phase( GameState
* s
) {
273 float x
, min
, max
, direction
;
278 direction
= s
->ball
.move_x
;
280 pos
= (direction
>0)?(1-GROUND_PHASE
):(GROUND_PHASE
);
282 fract
= (x
-min
)/(max
-min
);
286 return fabsf( cosf(PI
*fract
/2))*PHASE_AMP
*s
->ball
.jump
;
288 fract
= (pos
-fract
)/(1-pos
);
289 return fabsf( sinf(PI
*fract
/2))*PHASE_AMP
*s
->ball
.jump
;
293 float get_move_y( GameState
* s
, unsigned char player
) {
294 float pct
, dest
, x_len
, y_len
;
295 float py
, by
, pa
, move_x
;
297 py
= (player
==1)?(s
->player1
.y
):(s
->player2
.y
);
300 move_x
= s
->ball
.move_x
;
302 /* -1.0 .. 1.0 for racket hit position */
303 pct
= (by
-py
)/(pa
/2);
304 limit_value( &pct
, -1.0, 1.0);
306 /* Y destination for ball */
307 dest
= GAME_Y_MID
+ pct
*(GAME_Y_MAX
-GAME_Y_MIN
);
309 s
->player1
.ball_dest
= dest
;
311 s
->player2
.ball_dest
= dest
;
314 /* lengths for the ball's journey */
316 x_len
= fabsf(GAME_X_MAX
- s
->ball
.x
);
317 y_len
= dest
- by
+ MOVE_Y_SEED
-rand()%MOVE_Y_SEED
*2;
319 x_len
= s
->ball
.x
- GAME_X_MIN
;
320 y_len
= by
- dest
+ MOVE_Y_SEED
-rand()%MOVE_Y_SEED
*2;
323 /* return the should-be value for move_y */
324 return (y_len
*move_x
)/(x_len
);
327 void input_human( Player
* player
, bool up
, bool down
, bool hit
) {
336 if( hit
&& !player
->state
) {
337 player
->state
= PLAYER_STATE_MAX
;
341 void input_ai( Player
* player
, Ball
* ball
, Player
* opponent
) {
345 if( fabsf( player
->y
- ball
->y
) > RACKET_Y_MID
*5) {
349 target
= GAME_Y_MID
+ (opponent
->ball_dest
- GAME_Y_MID
)/10;
351 if( player
->responsible
) {
352 if( player
->desire
== DESIRE_NORMAL
&& !IS_NEAR_Y_AI( player
->y
, ball
->y
)) {
353 if( player
->y
< ball
->y
) {
354 player
->y
+= fmin( 2*fact
, ball
->y
- player
->y
);
355 } else if( player
->y
> ball
->y
) {
356 player
->y
-= fmin( 2*fact
, player
->y
- ball
->y
);
360 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) {
361 player
->state
= PLAYER_STATE_MAX
;
363 } else if( ball
->move_x
== 0) {
364 if( player
->desire
== DESIRE_NORMAL
&& !IS_NEAR_Y_AI( player
->y
, target
)) {
365 if( player
->y
< target
) {
366 player
->y
+= fmin( fact
, (target
-player
->y
)/40.0);
367 } else if( player
->y
> target
) {
368 player
->y
-= fmin( fact
, (player
->y
-target
)/40.0);
372 if( player->desire == DESIRE_NORMAL) {
373 if( !IS_NEAR_Y_AI( player->y, target)) {
374 player->y += (target - player->y)/40.0;
380 void game_setup_serve( GameState
* s
) {
382 s
->ball
.y
= GAME_Y_MID
;
383 s
->ball
.move_x
= 0.0;
384 s
->ball
.move_y
= 0.0;
386 if( s
->player1_serves
) {
387 s
->player1
.responsible
= true;
388 s
->player1
.ball_dest
= 0.0;
389 s
->ball
.x
= GAME_X_MIN
-RACKET_X_MID
*1.5;
391 s
->player1
.responsible
= false;
392 s
->player2
.ball_dest
= 0.0;
393 s
->ball
.x
= GAME_X_MAX
+RACKET_X_MID
*1.5;
396 s
->player2
.responsible
= !(s
->player1
.responsible
);