First cut of single-player mode support (Player 2 = AI)
[tennix.git] / game.c
blob1a8dcefe388029ded7a506af0277e4f6937cea6c
2 /**
4 * Tennix! SDL Port
5 * Copyright (C) 2003, 2007 Thomas Perl <thp@perli.net>
6 *
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,
20 * MA 02110-1301, USA.
22 **/
24 #include <stdio.h>
25 #include <math.h>
27 #include "tennix.h"
28 #include "game.h"
29 #include "graphics.h"
30 #include "input.h"
31 #include "sound.h"
33 void game( bool singleplayer) {
34 GameState s = {
35 { GAME_X_MID, GAME_Y_MID, BALL_DEFAULT_SPEED, 0.2, 7.5, 0.0, 0.0 },
36 { 0, 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 },
42 singleplayer,
45 Uint32 ot = SDL_GetTicks();
46 Uint32 nt;
47 Uint32 dt = GAME_TICKS;
48 Uint32 diff;
49 Uint32 accumulator = 0;
50 bool quit = false;
52 sound_audience();
54 while( !quit) {
55 nt = SDL_GetTicks();
56 diff = nt-ot;
57 if( diff > 2000) {
58 diff = 0;
61 accumulator += diff;
62 ot = nt;
64 while( accumulator >= dt) {
65 quit = step( &s);
66 s.time += dt;
67 accumulator -= dt;
69 if( s.was_stopped) {
70 ot = SDL_GetTicks();
71 s.was_stopped = false;
75 render( &s);
80 bool step( GameState* s) {
81 Uint8 *keys;
82 SDL_Event e;
83 float fact;
85 if( get_phase( s) < 1.0) {
86 if( !s->ground.jump) {
87 sound_ground();
89 if( IS_OUT_Y( s->ball.y)) {
90 /* out - responsibilities stay the same */
91 sound_out();
92 } else {
93 /* not out - responsibilities change */
94 s->player1.responsible = !(s->player2.responsible = !s->player2.responsible);
97 s->ground.jump = 3;
98 s->ground.x = s->ball.x;
99 s->ground.y = s->ball.y;
100 } else {
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) {
106 s->player2.score++;
107 sound_applause();
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);
112 s->ball.dest1 = 0.0;
113 limit_value( &s->ball.move_x, s->ball.move_x, BALL_DEFAULT_SPEED);
114 s->ball.move_y = -0.1;
115 wait_keypress();
116 s->was_stopped = true;
117 sound_applause_stop();
120 if( IS_OFFSCREEN( s->ball.x, s->ball.y) && s->player2.responsible) {
121 s->player1.score++;
122 sound_applause();
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);
127 s->ball.dest2 = 0.0;
128 limit_value( &s->ball.move_x, -BALL_DEFAULT_SPEED, s->ball.move_x);
129 s->ball.move_y = 0.1;
130 wait_keypress();
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;
140 } else {
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;
151 } else {
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);
162 #ifdef DEBUG
163 /* Update dest1/dest2 for debugging purposes (visualisation) */
164 if( s->ball.move_x < 0) {
165 get_move_y( s, 1);
166 } else {
167 get_move_y( s, 2);
169 #endif
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--;
177 SDL_PollEvent( &e);
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) {
189 fact = 4;
190 } else {
191 fact = 1.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;
206 } else {
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);
223 return false;
226 void render( GameState* s) {
227 clearscr();
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);
240 } else {
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);
250 #ifdef DEBUG
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);
264 #endif
266 updatescr();
269 void limit_value( float* value, float min, float max) {
270 if( *value < min) {
271 *value = min;
272 } else if( *value > max) {
273 *value = max;
277 float get_phase( GameState* s) {
278 float pos, fract;
279 float x, min, max, direction;
281 x = s->ball.x;
282 min = GAME_X_MIN;
283 max = GAME_X_MAX;
284 direction = s->ball.move_x;
286 pos = (direction>0)?(1-GROUND_PHASE):(GROUND_PHASE);
288 fract = (x-min)/(max-min);
290 if( fract < pos) {
291 fract = fract/pos;
292 return fabsf( cosf(PI*fract/2))*PHASE_AMP*s->ball.jump;
293 } else {
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);
304 by = s->ball.y;
305 pa = RACKET_Y_MID*2;
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);
314 if( player == 1) {
315 s->ball.dest1 = dest;
316 } else {
317 s->ball.dest2 = dest;
320 /* lengths for the ball's journey */
321 if( player == 1) {
322 x_len = fabsf(GAME_X_MAX - s->ball.x);
323 y_len = dest - by;
324 } else {
325 x_len = s->ball.x - GAME_X_MIN;
326 y_len = by - dest;
329 /* return the should-be value for move_y */
330 return (y_len*move_x)/(x_len);