Removed powershot sound
[tennix.git] / game.c
blob4e41bccf3d21847b73859021b06cd127661a4af3
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 },
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 },
44 Uint32 ot = SDL_GetTicks();
45 Uint32 nt;
46 Uint32 dt = GAME_TICKS;
47 Uint32 diff;
48 Uint32 accumulator = 0;
49 bool quit = false;
51 if( singleplayer) {
52 #ifdef DEBUG
53 s.player1.type = PLAYER_TYPE_AI;
54 #endif
55 s.player2.type = PLAYER_TYPE_AI;
58 sound_audience();
60 while( !quit) {
61 nt = SDL_GetTicks();
62 diff = nt-ot;
63 if( diff > 2000) {
64 diff = 0;
67 accumulator += diff;
68 ot = nt;
70 while( accumulator >= dt) {
71 quit = step( &s);
72 s.time += dt;
73 accumulator -= dt;
75 if( s.was_stopped) {
76 ot = SDL_GetTicks();
77 s.was_stopped = false;
81 render( &s);
86 bool step( GameState* s) {
87 Uint8 *keys;
88 SDL_Event e;
90 if( get_phase( s) < 1.0) {
91 if( !s->ground.jump) {
92 sound_ground();
94 if( IS_OUT_Y( s->ball.y)) {
95 /* out - responsibilities stay the same */
96 sound_out();
97 } else {
98 /* not out - responsibilities change */
99 s->player1.responsible = !(s->player2.responsible = !s->player2.responsible);
102 s->ground.jump = 3;
103 s->ground.x = s->ball.x;
104 s->ground.y = s->ball.y;
105 } else {
106 if( s->ground.jump && !(s->time%5)) s->ground.jump--;
109 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
110 if( IS_OFFSCREEN( s->ball.x, s->ball.y) && s->player1.responsible) {
111 s->player2.score++;
112 sound_applause();
113 s->player2.responsible = !(s->player1.responsible = 1);
114 s->ball.x = GAME_X_MID;
115 s->ball.y = GAME_Y_MID;
116 s->ball.move_x = fabsf( s->ball.move_x);
117 s->player1.ball_dest = 0.0;
118 limit_value( &s->ball.move_x, s->ball.move_x, BALL_DEFAULT_SPEED);
119 s->ball.move_y = -0.1;
120 wait_keypress();
121 s->was_stopped = true;
122 sound_applause_stop();
125 if( IS_OFFSCREEN( s->ball.x, s->ball.y) && s->player2.responsible) {
126 s->player1.score++;
127 sound_applause();
128 s->player1.responsible = !(s->player2.responsible = 1);
129 s->ball.x = GAME_X_MID;
130 s->ball.y = GAME_Y_MID;
131 s->ball.move_x = -fabsf( s->ball.move_x);
132 s->player2.ball_dest = 0.0;
133 limit_value( &s->ball.move_x, -BALL_DEFAULT_SPEED, s->ball.move_x);
134 s->ball.move_y = 0.1;
135 wait_keypress();
136 s->was_stopped = true;
137 sound_applause_stop();
140 if( IS_OUT_X(s->ball.x)) {
141 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) {
142 s->ball.x = GAME_X_MIN;
143 if( s->player1.state == PLAYER_STATE_MAX) {
144 s->ball.move_x = PLAYER_POWERSHOT;
145 } else {
146 s->ball.move_x = 2.5 + 2.0*s->player1.state/PLAYER_STATE_MAX;
148 s->ball.move_y = get_move_y( s, 1);
149 s->player2.responsible = !(s->player1.responsible = 1);
150 s->ball.jump += 1.0-2.0*(s->player1.state<5);
151 sound_racket();
152 } else if( IS_NEAR_X( s->player2.x, s->ball.x) && IS_NEAR_Y( s->player2.y, s->ball.y) && s->player2.state) {
153 s->ball.x = GAME_X_MAX;
154 if( s->player2.state == PLAYER_STATE_MAX) {
155 s->ball.move_x = -PLAYER_POWERSHOT;
156 } else {
157 s->ball.move_x = -(2.5 + 2.0*s->player2.state/PLAYER_STATE_MAX);
159 s->ball.move_y = get_move_y( s, 2);
160 s->player1.responsible = !(s->player2.responsible = 1);
161 s->ball.jump += 1.0-2.0*(s->player2.state<5);
162 sound_racket();
167 #ifdef DEBUG
168 /* Update ball_dest for debugging purposes */
169 if( s->ball.move_x < 0) {
170 get_move_y( s, 1);
171 } else {
172 get_move_y( s, 2);
174 #endif
176 s->ball.x += s->ball.move_x;
177 s->ball.y += s->ball.move_y;
179 if( s->player1.state) s->player1.state--;
180 if( s->player2.state) s->player2.state--;
182 SDL_PollEvent( &e);
183 keys = SDL_GetKeyState( NULL);
185 if( s->player1.type == PLAYER_TYPE_HUMAN) {
186 input_human( &s->player1, keys['w'], keys['s'], keys['d']);
187 } else {
188 input_ai( &s->player1, &s->ball);
191 if( s->player2.type == PLAYER_TYPE_HUMAN) {
192 input_human( &s->player2, keys['o'], keys['l'], keys['k']);
193 } else {
194 input_ai( &s->player2, &s->ball);
197 if( keys['f']) SDL_WM_ToggleFullScreen( screen);
198 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
200 if( keys[SDLK_ESCAPE] || keys['q']) return true;
202 limit_value( &s->player1.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
203 limit_value( &s->player2.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
204 limit_value( &s->ball.jump, BALL_JUMP_MIN, BALL_JUMP_MAX);
206 return false;
209 void render( GameState* s) {
210 clearscr();
211 show_image( GR_COURT, 0, 0, 255);
212 show_image( GR_SHADOW, s->ball.x-BALL_X_MID, s->ball.y + get_phase( s) - BALL_Y_MID, 255);
214 show_sprite( GR_RACKET, (!s->player1.state), 4, s->player1.x-RACKET_X_MID, s->player1.y-RACKET_Y_MID, 255);
215 show_sprite( GR_RACKET, (!s->player2.state)+2, 4, s->player2.x-RACKET_X_MID, s->player2.y-RACKET_Y_MID, 255);
217 if( s->ground.jump) {
218 show_sprite( GR_GROUND, s->ground.jump-1, 3, s->ground.x - BALL_X_MID, s->ground.y - BALL_Y_MID, 128);
221 if( s->ball.move_x > 0) {
222 show_sprite( GR_BALL, (s->time/1000)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
223 } else {
224 show_sprite( GR_BALL, 3-(s->time/1000)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
227 show_digit( s->player1.score/10%10, 140*2, 14, 100);
228 show_digit( s->player1.score%10, 148*2, 14, 100);
229 show_digit( 10, 156*2, 14, 100);
230 show_digit( s->player2.score/10%10, 164*2, 14, 100);
231 show_digit( s->player2.score%10, 172*2, 14, 100);
233 #ifdef DEBUG
234 line_horiz( s->player1.y, 255, 0, 0);
235 line_horiz( s->player2.y, 0, 0, 255);
236 line_horiz( s->ball.y, 0, 255, 0);
238 line_vert( s->player1.x, 255, 0, 0);
239 line_vert( s->player2.x, 0, 0, 255);
240 line_vert( s->ball.x, 0, 255, 0);
242 line_horiz( s->player1.ball_dest, 255, 0, 255);
243 line_horiz( s->player2.ball_dest, 0, 255, 255);
245 line_horiz( GAME_Y_MIN, 100, 100, 100);
246 line_horiz( GAME_Y_MAX, 100, 100, 100);
247 #endif
249 updatescr();
252 void limit_value( float* value, float min, float max) {
253 if( *value < min) {
254 *value = min;
255 } else if( *value > max) {
256 *value = max;
260 float get_phase( GameState* s) {
261 float pos, fract;
262 float x, min, max, direction;
264 x = s->ball.x;
265 min = GAME_X_MIN;
266 max = GAME_X_MAX;
267 direction = s->ball.move_x;
269 pos = (direction>0)?(1-GROUND_PHASE):(GROUND_PHASE);
271 fract = (x-min)/(max-min);
273 if( fract < pos) {
274 fract = fract/pos;
275 return fabsf( cosf(PI*fract/2))*PHASE_AMP*s->ball.jump;
276 } else {
277 fract = (pos-fract)/(1-pos);
278 return fabsf( sinf(PI*fract/2))*PHASE_AMP*s->ball.jump;
282 float get_move_y( GameState* s, unsigned char player) {
283 float pct, dest, x_len, y_len;
284 float py, by, pa, move_x;
286 py = (player==1)?(s->player1.y):(s->player2.y);
287 by = s->ball.y;
288 pa = RACKET_Y_MID*2;
289 move_x = s->ball.move_x;
291 /* -1.0 .. 1.0 for racket hit position */
292 pct = (by-py)/(pa/2);
293 limit_value( &pct, -1.0, 1.0);
295 /* Y destination for ball */
296 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
297 if( player == 1) {
298 s->player1.ball_dest = dest;
299 } else {
300 s->player2.ball_dest = dest;
303 /* lengths for the ball's journey */
304 if( player == 1) {
305 x_len = fabsf(GAME_X_MAX - s->ball.x);
306 y_len = dest - by;
307 } else {
308 x_len = s->ball.x - GAME_X_MIN;
309 y_len = by - dest;
312 /* return the should-be value for move_y */
313 return (y_len*move_x)/(x_len);
316 void input_human( Player* player, bool up, bool down, bool hit) {
317 if( up) {
318 player->y -= 6;
321 if( down) {
322 player->y += 6;
325 if( hit && !player->state) {
326 player->state = PLAYER_STATE_MAX;
330 void input_ai( Player* player, Ball* ball) {
331 float fact;
333 if( player->responsible) {
334 if( fabsf( player->y - ball->y) > RACKET_Y_MID*5) {
335 fact = 4;
336 } else {
337 fact = 1.5;
340 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, ball->y)) {
341 if( player->y < ball->y) {
342 player->y += fmin( 2*fact, ball->y - player->y);
343 } else if( player->y > ball->y) {
344 player->y -= fmin( 2*fact, player->y - ball->y);
348 if( IS_NEAR_X_AI( player->x, ball->x) && !player->state && rand()%4) {
349 player->state = PLAYER_STATE_MAX;