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 { GAME_X_MID
, GAME_Y_MID
, BALL_DEFAULT_SPEED
, 0.2, 7.5, 0.0, 0.0 },
37 { GAME_X_MIN
-RACKET_X_MID
*2, GAME_Y_MID
, 0, 0, 1 },
38 { GAME_X_MAX
+RACKET_X_MID
*2, GAME_Y_MID
, 0, 0, 0 },
45 Uint32 ot
= SDL_GetTicks();
47 Uint32 dt
= GAME_TICKS
;
49 Uint32 accumulator
= 0;
64 while( accumulator
>= dt
) {
71 s
.was_stopped
= false;
80 bool step( GameState
* s
) {
85 if( get_phase( s
) < 1.0) {
86 if( !s
->ground
.jump
) {
89 if( IS_OUT_Y( s
->ball
.y
)) {
90 /* out - responsibilities stay the same */
93 /* not out - responsibilities change */
94 s
->player1
.responsible
= !(s
->player2
.responsible
= !s
->player2
.responsible
);
98 s
->ground
.x
= s
->ball
.x
;
99 s
->ground
.y
= s
->ball
.y
;
101 if( s
->ground
.jump
&& !(s
->time
%5)) s
->ground
.jump
--;
104 if( IS_OUT_X(s
->ball
.x
) || IS_OFFSCREEN_Y(s
->ball
.y
)) {
105 if( IS_OFFSCREEN( s
->ball
.x
, s
->ball
.y
) && s
->player1
.responsible
) {
108 s
->player2
.responsible
= !(s
->player1
.responsible
= 1);
109 s
->ball
.x
= GAME_X_MID
;
110 s
->ball
.y
= GAME_Y_MID
;
111 s
->ball
.move_x
= fabsf( s
->ball
.move_x
);
113 limit_value( &s
->ball
.move_x
, s
->ball
.move_x
, BALL_DEFAULT_SPEED
);
114 s
->ball
.move_y
= -0.1;
116 s
->was_stopped
= true;
117 sound_applause_stop();
120 if( IS_OFFSCREEN( s
->ball
.x
, s
->ball
.y
) && s
->player2
.responsible
) {
123 s
->player1
.responsible
= !(s
->player2
.responsible
= 1);
124 s
->ball
.x
= GAME_X_MID
;
125 s
->ball
.y
= GAME_Y_MID
;
126 s
->ball
.move_x
= -fabsf( s
->ball
.move_x
);
128 limit_value( &s
->ball
.move_x
, -BALL_DEFAULT_SPEED
, s
->ball
.move_x
);
129 s
->ball
.move_y
= 0.1;
131 s
->was_stopped
= true;
132 sound_applause_stop();
135 if( IS_OUT_X(s
->ball
.x
)) {
136 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
) {
137 s
->ball
.x
= GAME_X_MIN
;
138 if( s
->player1
.state
== PLAYER_STATE_MAX
) {
139 s
->ball
.move_x
= PLAYER_POWERSHOT
;
141 s
->ball
.move_x
= 2.5 + 2.0*s
->player1
.state
/PLAYER_STATE_MAX
;
143 s
->ball
.move_y
= get_move_y( s
, 1);
144 s
->player2
.responsible
= !(s
->player1
.responsible
= 1);
145 s
->ball
.jump
+= 1.0-2.0*(s
->player1
.state
<5);
146 sound_racket( s
->player1
.state
== PLAYER_STATE_MAX
);
147 } else if( IS_NEAR_X( s
->player2
.x
, s
->ball
.x
) && IS_NEAR_Y( s
->player2
.y
, s
->ball
.y
) && s
->player2
.state
) {
148 s
->ball
.x
= GAME_X_MAX
;
149 if( s
->player2
.state
== PLAYER_STATE_MAX
) {
150 s
->ball
.move_x
= -PLAYER_POWERSHOT
;
152 s
->ball
.move_x
= -(2.5 + 2.0*s
->player2
.state
/PLAYER_STATE_MAX
);
154 s
->ball
.move_y
= get_move_y( s
, 2);
155 s
->player1
.responsible
= !(s
->player2
.responsible
= 1);
156 s
->ball
.jump
+= 1.0-2.0*(s
->player2
.state
<5);
157 sound_racket( s
->player2
.state
== PLAYER_STATE_MAX
);
163 /* Update dest1/dest2 for debugging purposes (visualisation) */
164 if( s
->ball
.move_x
< 0) {
171 s
->ball
.x
+= s
->ball
.move_x
;
172 s
->ball
.y
+= s
->ball
.move_y
;
174 if( s
->player1
.state
) s
->player1
.state
--;
175 if( s
->player2
.state
) s
->player2
.state
--;
178 keys
= SDL_GetKeyState( NULL
);
180 if( keys
['2']) s
->player1
.y
-=8;
181 if( keys
['w']) s
->player1
.y
-=6;
182 if( keys
['s']) s
->player1
.y
+=6;
183 if( keys
['x']) s
->player1
.y
+=8;
184 if( keys
['d'] && !s
->player1
.state
) s
->player1
.state
= PLAYER_STATE_MAX
;
186 if( s
->is_singleplayer
) {
187 if( s
->player2
.responsible
) {
188 if( fabsf( s
->player2
.y
- s
->ball
.y
) > RACKET_Y_MID
*5) {
194 if( !IS_NEAR_Y_AI( s
->player2
.y
, s
->ball
.y
)) {
195 if( s
->player2
.y
< s
->ball
.y
) {
196 s
->player2
.y
+= fmin( 2*fact
, s
->ball
.y
-s
->player2
.y
);
197 } else if( s
->player2
.y
> s
->ball
.y
) {
198 s
->player2
.y
-= fmin( 2*fact
, s
->player2
.y
-s
->ball
.y
);
202 if( IS_NEAR_X_AI( s
->player2
.x
, s
->ball
.x
) && !s
->player2
.state
&& rand()%4) {
203 s
->player2
.state
= PLAYER_STATE_MAX
;
207 if( keys
['9']) s
->player2
.y
-=8;
208 if( keys
['o']) s
->player2
.y
-=6;
209 if( keys
['l']) s
->player2
.y
+=6;
210 if( keys
['.']) s
->player2
.y
+=8;
211 if( keys
['k'] && !s
->player2
.state
) s
->player2
.state
= PLAYER_STATE_MAX
;
214 if( keys
['f']) SDL_WM_ToggleFullScreen( screen
);
215 if( keys
['y']) SDL_SaveBMP( screen
, "screenshot.bmp");
217 if( keys
[SDLK_ESCAPE
] || keys
['q']) return true;
219 limit_value( &s
->player1
.y
, PLAYER_Y_MIN
, PLAYER_Y_MAX
);
220 limit_value( &s
->player2
.y
, PLAYER_Y_MIN
, PLAYER_Y_MAX
);
221 limit_value( &s
->ball
.jump
, BALL_JUMP_MIN
, BALL_JUMP_MAX
);
226 void render( GameState
* s
) {
228 show_image( GR_COURT
, 0, 0, 255);
229 show_image( GR_SHADOW
, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
+ get_phase( s
) - BALL_Y_MID
, 255);
231 show_sprite( GR_RACKET
, (!s
->player1
.state
), 4, s
->player1
.x
-RACKET_X_MID
, s
->player1
.y
-RACKET_Y_MID
, 255);
232 show_sprite( GR_RACKET
, (!s
->player2
.state
)+2, 4, s
->player2
.x
-RACKET_X_MID
, s
->player2
.y
-RACKET_Y_MID
, 255);
234 if( s
->ground
.jump
) {
235 show_sprite( GR_GROUND
, s
->ground
.jump
-1, 3, s
->ground
.x
- BALL_X_MID
, s
->ground
.y
- BALL_Y_MID
, 128);
238 if( s
->ball
.move_x
> 0) {
239 show_sprite( GR_BALL
, (s
->time
/1000)%4, 4, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
-BALL_Y_MID
, 255);
241 show_sprite( GR_BALL
, 3-(s
->time
/1000)%4, 4, s
->ball
.x
-BALL_X_MID
, s
->ball
.y
-BALL_Y_MID
, 255);
244 show_digit( s
->player1
.score
/10%10, 140*2, 14, 100);
245 show_digit( s
->player1
.score
%10, 148*2, 14, 100);
246 show_digit( 10, 156*2, 14, 100);
247 show_digit( s
->player2
.score
/10%10, 164*2, 14, 100);
248 show_digit( s
->player2
.score
%10, 172*2, 14, 100);
251 line_horiz( s
->player1
.y
, 255, 0, 0);
252 line_horiz( s
->player2
.y
, 0, 0, 255);
253 line_horiz( s
->ball
.y
, 0, 255, 0);
255 line_vert( s
->player1
.x
, 255, 0, 0);
256 line_vert( s
->player2
.x
, 0, 0, 255);
257 line_vert( s
->ball
.x
, 0, 255, 0);
259 line_horiz( s
->ball
.dest1
, 255, 0, 255);
260 line_horiz( s
->ball
.dest2
, 0, 255, 255);
262 line_horiz( GAME_Y_MIN
, 100, 100, 100);
263 line_horiz( GAME_Y_MAX
, 100, 100, 100);
269 void limit_value( float* value
, float min
, float max
) {
272 } else if( *value
> max
) {
277 float get_phase( GameState
* s
) {
279 float x
, min
, max
, direction
;
284 direction
= s
->ball
.move_x
;
286 pos
= (direction
>0)?(1-GROUND_PHASE
):(GROUND_PHASE
);
288 fract
= (x
-min
)/(max
-min
);
292 return fabsf( cosf(PI
*fract
/2))*PHASE_AMP
*s
->ball
.jump
;
294 fract
= (pos
-fract
)/(1-pos
);
295 return fabsf( sinf(PI
*fract
/2))*PHASE_AMP
*s
->ball
.jump
;
299 float get_move_y( GameState
* s
, unsigned char player
) {
300 float pct
, dest
, x_len
, y_len
;
301 float py
, by
, pa
, move_x
;
303 py
= (player
==1)?(s
->player1
.y
):(s
->player2
.y
);
306 move_x
= s
->ball
.move_x
;
308 /* -1.0 .. 1.0 for racket hit position */
309 pct
= (by
-py
)/(pa
/2);
310 limit_value( &pct
, -1.0, 1.0);
312 /* Y destination for ball */
313 dest
= GAME_Y_MID
+ pct
*(GAME_Y_MAX
-GAME_Y_MIN
);
315 s
->ball
.dest1
= dest
;
317 s
->ball
.dest2
= dest
;
320 /* lengths for the ball's journey */
322 x_len
= fabsf(GAME_X_MAX
- s
->ball
.x
);
325 x_len
= s
->ball
.x
- GAME_X_MIN
;
329 /* return the should-be value for move_y */
330 return (y_len
*move_x
)/(x_len
);