Tennix 0.4.1
[tennix.git] / game.c
blob8bcb340b055f874d53515f611110d04d114f7581
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, false },
38 { GAME_X_MAX+RACKET_X_MID*2, GAME_Y_MID, 0, 0, 0, DESIRE_NORMAL, PLAYER_TYPE_HUMAN, false },
42 true,
43 "welcome to tennix " VERSION,
44 { 0 },
45 { 0 },
46 REFEREE_NORMAL,
48 WINNER_NONE,
49 false,
50 GR_CTT_GRASS
53 strcpy( s.game_score_str, format_game( &s));
54 strcpy( s.sets_score_str, format_sets( &s));
56 Uint32 ot = SDL_GetTicks();
57 Uint32 nt;
58 Uint32 dt = GAME_TICKS;
59 Uint32 diff;
60 Uint32 accumulator = 0;
61 bool quit = false;
63 if( singleplayer) {
64 #ifdef DEBUG
65 s.player1.type = PLAYER_TYPE_AI;
66 #endif
67 s.player2.type = PLAYER_TYPE_AI;
70 game_setup_serve( &s);
71 sound_audience();
73 while( !quit) {
74 nt = SDL_GetTicks();
75 diff = nt-ot;
76 if( diff > 2000) {
77 diff = 0;
80 accumulator += diff;
81 ot = nt;
83 while( accumulator >= dt) {
84 quit = step( &s);
85 s.time += dt;
86 accumulator -= dt;
88 if( s.was_stopped) {
89 ot = SDL_GetTicks();
90 s.was_stopped = false;
94 render( &s);
99 bool step( GameState* s) {
100 Uint8 *keys;
101 SDL_Event e;
103 if( get_phase( s) < 1.0) {
104 if( !s->ground.jump) {
105 sound_ground();
107 if( IS_OUT_Y( s->ball.y)) {
108 /* out - responsibilities stay the same */
109 s->status = "out!";
110 sound_out();
111 s->referee = REFEREE_OUT;
112 } else {
113 /* not out - responsibilities change */
114 s->player1.responsible = !(s->player2.responsible = !s->player2.responsible);
115 s->status = format_status( s);
116 s->referee = REFEREE_NORMAL;
119 s->ground.jump = 3;
120 s->ground.x = s->ball.x;
121 s->ground.y = s->ball.y;
122 } else {
123 if( s->ground.jump && !(s->time%5)) s->ground.jump--;
126 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
127 if( IS_OFFSCREEN( s->ball.x, s->ball.y)) {
128 s->player1_serves = s->player1.responsible;
130 score_game( s, s->player2.responsible);
131 strcpy( s->game_score_str, format_game( s));
132 strcpy( s->sets_score_str, format_sets( s));
134 if( s->player1.responsible) {
135 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
136 s->status = "computer scores";
137 } else {
138 s->status = "player 2 scores";
140 s->referee = REFEREE_PLAYER2;
141 } else {
142 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
143 s->status = "player scores";
144 } else {
145 s->status = "player 1 scores";
147 s->referee = REFEREE_PLAYER1;
150 game_setup_serve( s);
151 sound_applause();
152 SDL_Delay( 500);
153 start_fade();
154 s->was_stopped = true;
157 if( IS_OUT_X(s->ball.x)) {
158 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 && s->referee != REFEREE_OUT) {
159 s->ball.x = GAME_X_MIN;
160 if( s->player1.state == PLAYER_STATE_MAX) {
161 s->ball.move_x = PLAYER_POWERSHOT;
162 } else {
163 s->ball.move_x = 2.5 + 2.0*s->player1.state/PLAYER_STATE_MAX;
165 s->ball.move_y = get_move_y( s, 1);
166 s->player2.responsible = !(s->player1.responsible = 1);
167 s->ball.jump += 1.0-2.0*(s->player1.state<5);
168 sound_applause_stop();
169 sound_racket();
170 } 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 && s->referee != REFEREE_OUT) {
171 s->ball.x = GAME_X_MAX;
172 if( s->player2.state == PLAYER_STATE_MAX) {
173 s->ball.move_x = -PLAYER_POWERSHOT;
174 } else {
175 s->ball.move_x = -(2.5 + 2.0*s->player2.state/PLAYER_STATE_MAX);
177 s->ball.move_y = get_move_y( s, 2);
178 s->player1.responsible = !(s->player2.responsible = 1);
179 s->ball.jump += 1.0-2.0*(s->player2.state<5);
180 sound_applause_stop();
181 sound_racket();
186 /* Update ball_dest for debugging purposes */
187 get_move_y( s, 1);
188 get_move_y( s, 2);
190 s->ball.x += s->ball.move_x;
191 s->ball.y += s->ball.move_y;
193 if( s->player1.state) s->player1.state--;
194 if( s->player2.state) s->player2.state--;
196 SDL_PollEvent( &e);
197 keys = SDL_GetKeyState( NULL);
199 if( keys['c'] && s->time%50==0) {
200 s->court_type++;
201 if( s->court_type > GR_CTT_LAST) {
202 s->court_type = GR_CTT_FIRST;
206 if( !is_fading() && !s->is_over) {
207 if( s->player1.type == PLAYER_TYPE_HUMAN) {
208 input_human( &s->player1, keys['w'], keys['s'], keys['d']);
209 } else {
210 input_ai( &s->player1, &s->ball, &s->player2);
213 if( s->player2.type == PLAYER_TYPE_HUMAN) {
214 input_human( &s->player2, keys['o'], keys['l'], keys['k']);
215 } else {
216 input_ai( &s->player2, &s->ball, &s->player1);
220 if( keys['f']) SDL_WM_ToggleFullScreen( screen);
221 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
223 if( keys[SDLK_ESCAPE] || keys['q']) return true;
225 limit_value( &s->player1.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
226 limit_value( &s->player2.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
227 limit_value( &s->ball.jump, BALL_JUMP_MIN, BALL_JUMP_MAX);
229 return false;
232 void render( GameState* s) {
233 if( s->winner != WINNER_NONE) {
234 if( !s->is_over) {
235 start_fade();
236 s->is_over = true;
238 clearscr();
239 show_sprite( GR_RACKET, 2*(s->winner-1), 4, WIDTH/2 - get_image_width( GR_RACKET)/8, HEIGHT/2 - get_image_height( GR_RACKET), 255);
240 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
241 font_draw_string( GR_DKC2_FONT, s->game_score_str, (WIDTH-font_get_string_width( GR_DKC2_FONT, s->game_score_str))/2, HEIGHT/2 + 30, s->time/20, ANIMATION_WAVE | ANIMATION_BUNGEE);
242 updatescr();
243 return;
245 clearscr();
246 fill_image( s->court_type, 120, 120, 400, 250);
247 show_image( GR_COURT, 0, 0, 255);
248 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
249 show_image( GR_SHADOW, s->ball.x-BALL_X_MID, s->ball.y + get_phase( s) - BALL_Y_MID, 255);
251 show_sprite( GR_RACKET, (!s->player1.state), 4, s->player1.x-RACKET_X_MID, s->player1.y-RACKET_Y_MID, 255);
252 show_sprite( GR_RACKET, (!s->player2.state)+2, 4, s->player2.x-RACKET_X_MID, s->player2.y-RACKET_Y_MID, 255);
254 if( s->ground.jump) {
255 show_sprite( GR_GROUND, s->ground.jump-1, 3, s->ground.x - BALL_X_MID, s->ground.y - BALL_Y_MID, 128);
258 if( s->ball.move_x > 0) {
259 show_sprite( GR_BALL, (s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
260 } else if( s->ball.move_x < 0) {
261 show_sprite( GR_BALL, 3-(s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
262 } else {
263 show_sprite( GR_BALL, 0, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
267 font_draw_string( GR_DKC2_FONT, s->game_score_str, 14, 14, 0, ANIMATION_NONE);
268 font_draw_string( GR_DKC2_FONT, s->sets_score_str, (WIDTH-font_get_string_width( GR_DKC2_FONT, s->sets_score_str))-14, 14, 0, ANIMATION_NONE);
270 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);
272 #ifdef DEBUG
273 line_horiz( s->player1.y, 255, 0, 0);
274 line_horiz( s->player2.y, 0, 0, 255);
275 line_horiz( s->ball.y, 0, 255, 0);
277 line_vert( s->player1.x, 255, 0, 0);
278 line_vert( s->player2.x, 0, 0, 255);
279 line_vert( s->ball.x, 0, 255, 0);
281 line_horiz( s->player1.ball_dest, 255, 0, 255);
282 line_horiz( s->player2.ball_dest, 0, 255, 255);
284 line_horiz( GAME_Y_MIN, 100, 100, 100);
285 line_horiz( GAME_Y_MAX, 100, 100, 100);
286 #endif
288 updatescr();
291 void limit_value( float* value, float min, float max) {
292 if( *value < min) {
293 *value = min;
294 } else if( *value > max) {
295 *value = max;
299 float get_phase( GameState* s) {
300 float pos, fract;
301 float x, min, max, direction;
303 x = s->ball.x;
304 min = GAME_X_MIN;
305 max = GAME_X_MAX;
306 direction = s->ball.move_x;
308 pos = (direction>0)?(1-GROUND_PHASE):(GROUND_PHASE);
310 fract = (x-min)/(max-min);
312 if( fract < pos) {
313 fract = fract/pos;
314 return fabsf( cosf(PI*fract/2))*PHASE_AMP*s->ball.jump;
315 } else {
316 fract = (pos-fract)/(1-pos);
317 return fabsf( sinf(PI*fract/2))*PHASE_AMP*s->ball.jump;
321 float get_move_y( GameState* s, unsigned char player) {
322 float pct, dest, x_len, y_len;
323 float py, by, pa, move_x;
325 py = (player==1)?(s->player1.y):(s->player2.y);
326 by = s->ball.y;
327 pa = RACKET_Y_MID*2;
328 move_x = s->ball.move_x;
330 /* -1.0 .. 1.0 for racket hit position */
331 pct = (by-py)/(pa/2);
332 limit_value( &pct, -1.0, 1.0);
334 /* Y destination for ball */
335 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
336 if( player == 1) {
337 s->player1.ball_dest = dest;
338 } else {
339 s->player2.ball_dest = dest;
342 /* lengths for the ball's journey */
343 if( player == 1) {
344 x_len = fabsf(GAME_X_MAX - s->ball.x);
345 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
346 } else {
347 x_len = s->ball.x - GAME_X_MIN;
348 y_len = by - dest + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
351 /* return the should-be value for move_y */
352 return (y_len*move_x)/(x_len);
355 void input_human( Player* player, bool up, bool down, bool hit) {
356 if( up) {
357 player->y -= 6;
360 if( down) {
361 player->y += 6;
364 if( hit) {
365 if( !player->state && !player->state_locked) {
366 player->state = PLAYER_STATE_MAX;
367 player->state_locked = true;
369 } else {
370 player->state_locked = false;
374 void input_ai( Player* player, Ball* ball, Player* opponent) {
375 float fact = 1.5;
376 float target;
378 if( fabsf( player->y - ball->y) > RACKET_Y_MID*5) {
379 fact = 4;
382 target = GAME_Y_MID + (opponent->ball_dest - GAME_Y_MID)/10;
384 if( player->responsible) {
385 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, ball->y)) {
386 if( player->y < ball->y) {
387 player->y += fmin( 2*fact, ball->y - player->y);
388 } else if( player->y > ball->y) {
389 player->y -= fmin( 2*fact, player->y - ball->y);
393 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) {
394 player->state = PLAYER_STATE_MAX;
396 } else if( ball->move_x == 0) {
397 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
398 if( player->y < target) {
399 player->y += fmin( fact, (target-player->y)/40.0);
400 } else if( player->y > target) {
401 player->y -= fmin( fact, (player->y-target)/40.0);
404 } else {/*
405 if( player->desire == DESIRE_NORMAL) {
406 if( !IS_NEAR_Y_AI( player->y, target)) {
407 player->y += (target - player->y)/40.0;
413 void game_setup_serve( GameState* s) {
414 s->ball.jump = 7.5;
415 s->ball.y = GAME_Y_MID;
416 s->ball.move_x = 0.0;
417 s->ball.move_y = 0.0;
419 if( s->player1_serves) {
420 s->player1.responsible = true;
421 s->player1.ball_dest = 0.0;
422 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
423 } else {
424 s->player1.responsible = false;
425 s->player2.ball_dest = 0.0;
426 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
429 s->player2.responsible = !(s->player1.responsible);
432 void score_game( GameState* s, bool player1_scored) {
433 Player* winner = (player1_scored)?(&(s->player1)):(&(s->player2));
434 Player* loser = (player1_scored)?(&(s->player2)):(&(s->player1));
436 if( s->current_set >= SETS_TO_WIN*2-1) {
437 return;
440 winner->game++;
441 if( loser->game < winner->game-1) {
442 if( winner->game >= 4) {
443 winner->game = loser->game = 0;
444 winner->sets[s->current_set]++;
445 /* scoring the set.. */
446 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
447 winner->sets[s->current_set] == 7) {
448 s->current_set++;
449 s->winner = game_get_winner( s);
455 char* format_sets( GameState* s) {
456 static char sets[100];
457 static char tmp[100];
458 int i, max = s->current_set;
460 sets[0] = '\0';
462 if( s->winner != WINNER_NONE) {
463 max--;
465 for( i=0; i<=max; i++) {
466 sprintf( tmp, "%d:%d, ", s->player1.sets[i], s->player2.sets[i]);
467 strcat( sets, tmp);
470 sets[strlen(sets)-2] = '\0';
472 return sets;
475 char* format_game( GameState* s) {
476 static char game[100];
477 static const int game_scoring[] = { 0, 15, 30, 40 };
479 if( s->player1.game < 4 && s->player2.game < 4) {
480 sprintf( game, "%d - %d", game_scoring[s->player1.game], game_scoring[s->player2.game]);
481 } else if( s->player1.game > s->player2.game) {
482 strcpy( game, "advantage player 1");
483 } else if( s->player1.game < s->player2.game) {
484 strcpy( game, "advantage player 2");
485 } else {
486 strcpy( game, "deuce");
489 return game;
492 char* format_status( GameState* s) {
493 static char status[100];
494 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
496 sprintf( status, "%d:%d in %s set", s->player1.sets[s->current_set], s->player2.sets[s->current_set], set_names[s->current_set]);
498 return status;
501 int game_get_winner( GameState* s) {
502 int i;
503 int sets[2] = {0};
505 for( i=0; i<s->current_set; i++) {
506 if( s->player1.sets[i] > s->player2.sets[i]) {
507 sets[0]++;
508 } else {
509 sets[1]++;
513 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
514 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
516 return WINNER_NONE;