data2csrc determines variable names, shell compatibility
[tennix.git] / game.c
bloba84cd5a07f4f4b32e226e9c49336912f8fa90de7
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 { 0, 0, 0.0, 0.0, 0.0 },
36 { 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 },
42 true,
43 "welcome to tennix " VERSION,
44 { 0 },
45 REFEREE_NORMAL
48 strcpy( s.score_str, "00:00");
50 Uint32 ot = SDL_GetTicks();
51 Uint32 nt;
52 Uint32 dt = GAME_TICKS;
53 Uint32 diff;
54 Uint32 accumulator = 0;
55 bool quit = false;
57 if( singleplayer) {
58 #ifdef DEBUG
59 s.player1.type = PLAYER_TYPE_AI;
60 #endif
61 s.player2.type = PLAYER_TYPE_AI;
64 game_setup_serve( &s);
65 sound_audience();
67 while( !quit) {
68 nt = SDL_GetTicks();
69 diff = nt-ot;
70 if( diff > 2000) {
71 diff = 0;
74 accumulator += diff;
75 ot = nt;
77 while( accumulator >= dt) {
78 quit = step( &s);
79 s.time += dt;
80 accumulator -= dt;
82 if( s.was_stopped) {
83 ot = SDL_GetTicks();
84 s.was_stopped = false;
88 render( &s);
93 bool step( GameState* s) {
94 Uint8 *keys;
95 SDL_Event e;
97 if( get_phase( s) < 1.0) {
98 if( !s->ground.jump) {
99 sound_ground();
101 if( IS_OUT_Y( s->ball.y)) {
102 /* out - responsibilities stay the same */
103 s->status = "out -- don't hit the ball!";
104 sound_out();
105 s->referee = REFEREE_OUT;
106 } else {
107 /* not out - responsibilities change */
108 s->player1.responsible = !(s->player2.responsible = !s->player2.responsible);
109 s->status = "";
110 s->referee = REFEREE_NORMAL;
113 s->ground.jump = 3;
114 s->ground.x = s->ball.x;
115 s->ground.y = s->ball.y;
116 } else {
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";
130 } else {
131 s->status = "player 2 scores";
133 s->referee = REFEREE_PLAYER2;
134 } else {
135 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
136 s->status = "player scores";
137 } else {
138 s->status = "player 1 scores";
140 s->referee = REFEREE_PLAYER1;
143 game_setup_serve( s);
144 sound_applause();
145 SDL_Delay( 500);
146 start_fade();
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;
155 } else {
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();
162 sound_racket();
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;
167 } else {
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();
174 sound_racket();
179 /* Update ball_dest for debugging purposes */
180 get_move_y( s, 1);
181 get_move_y( s, 2);
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--;
189 SDL_PollEvent( &e);
190 keys = SDL_GetKeyState( NULL);
192 if( !is_fading()) {
193 if( s->player1.type == PLAYER_TYPE_HUMAN) {
194 input_human( &s->player1, keys['w'], keys['s'], keys['d']);
195 } else {
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']);
201 } else {
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);
215 return false;
218 void render( GameState* s) {
219 clearscr();
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);
235 } else {
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);
244 #ifdef DEBUG
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);
258 #endif
260 updatescr();
263 void limit_value( float* value, float min, float max) {
264 if( *value < min) {
265 *value = min;
266 } else if( *value > max) {
267 *value = max;
271 float get_phase( GameState* s) {
272 float pos, fract;
273 float x, min, max, direction;
275 x = s->ball.x;
276 min = GAME_X_MIN;
277 max = GAME_X_MAX;
278 direction = s->ball.move_x;
280 pos = (direction>0)?(1-GROUND_PHASE):(GROUND_PHASE);
282 fract = (x-min)/(max-min);
284 if( fract < pos) {
285 fract = fract/pos;
286 return fabsf( cosf(PI*fract/2))*PHASE_AMP*s->ball.jump;
287 } else {
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);
298 by = s->ball.y;
299 pa = RACKET_Y_MID*2;
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);
308 if( player == 1) {
309 s->player1.ball_dest = dest;
310 } else {
311 s->player2.ball_dest = dest;
314 /* lengths for the ball's journey */
315 if( player == 1) {
316 x_len = fabsf(GAME_X_MAX - s->ball.x);
317 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
318 } else {
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) {
328 if( up) {
329 player->y -= 6;
332 if( down) {
333 player->y += 6;
336 if( hit && !player->state) {
337 player->state = PLAYER_STATE_MAX;
341 void input_ai( Player* player, Ball* ball, Player* opponent) {
342 float fact = 1.5;
343 float target;
345 if( fabsf( player->y - ball->y) > RACKET_Y_MID*5) {
346 fact = 4;
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);
371 } else {/*
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) {
381 s->ball.jump = 7.5;
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;
390 } else {
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);