557ad134c1ab113932d94df43f9f15b2ef5eb78b
[tennix.git] / game.c
blob557ad134c1ab113932d94df43f9f15b2ef5eb78b
2 /**
4 * Tennix! SDL Port
5 * Copyright (C) 2003, 2007, 2008, 2009 Thomas Perl <thp@thpinfo.com>
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 <stdlib.h>
26 #include <string.h>
27 #include <assert.h>
28 #include <unistd.h>
30 #include "tennix.h"
31 #include "game.h"
32 #include "graphics.h"
33 #include "input.h"
34 #include "sound.h"
35 #include "util.h"
38 GameState *gamestate_new() {
39 GameState *s;
41 GameState template = {
42 NULL,
43 -1,
44 { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6, false, -1, false },
46 { NULL, -1, GAME_X_MIN-RACKET_X_MID*2, GAME_Y_MID, 0.0, POWER_UP_FACTOR, POWER_DOWN_FACTOR, true, 0, DESIRE_NORMAL, PLAYER_TYPE_AI, 0, {0}, PLAYER_ACCEL_DEFAULT },
47 { NULL, -1, GAME_X_MAX+RACKET_X_MID*2, GAME_Y_MID, 0.0, POWER_UP_FACTOR, POWER_DOWN_FACTOR, true, 0, DESIRE_NORMAL, PLAYER_TYPE_AI, 0, {0}, PLAYER_ACCEL_DEFAULT },
51 REFEREE_NORMAL,
53 WINNER_NONE,
54 SOUND_EVENT_NONE, /* sound events */
57 false,
59 SCORE_UNDECIDED,
61 EVENTCOUNTER_GAMESTATE_START,
62 EVENTCOUNTER_GAMESTATE_START,
63 STATUSMSG_WELCOME,
66 s = (GameState*)malloc(sizeof(GameState));
67 if (s == NULL) abort();
69 memcpy(s, &template, sizeof(GameState));
71 game_setup_serve(s);
73 return s;
77 int gamestate_save(GameState* s, const char* filename)
79 Location *location;
80 InputDevice* input_devices[MAXPLAYERS];
81 int i, result = 0;
82 FILE* fp = NULL;
83 #ifndef WIN32
84 char tmp[MAXPATHLEN];
86 assert(getcwd(tmp, MAXPATHLEN) == tmp);
87 assert(chdir(getenv("HOME")) == 0);
88 #endif
90 /**
91 * Process-specific data (pointers to data
92 * structures only of interest to this process)
93 * has to be "removed" before saving.
95 * It can later be restored (in gamestate_load).
96 **/
98 fp = fopen(filename, "w");
99 if (fp == NULL) {
100 return -1;
103 /* Save data for later recovery + clear */
104 location = s->location;
105 s->location = NULL;
106 for (i=1; i<=MAXPLAYERS; i++) {
107 input_devices[i-1] = PLAYER(s, i).input;
108 PLAYER(s, i).input = NULL;
111 if (fwrite(s, sizeof(GameState), 1, fp) != 1) {
112 result = -2;
115 /* Restore process-specific data */
116 s->location = location;
117 for (i=1; i<=MAXPLAYERS; i++) {
118 PLAYER(s, i).input = input_devices[i-1];
121 fclose(fp);
123 #ifndef WIN32
124 assert(chdir(tmp) == 0);
125 #endif
127 return result;
131 GameState* gamestate_load(const char* filename)
133 FILE* fp;
134 GameState* s = NULL;
135 #ifndef WIN32
136 char tmp[MAXPATHLEN];
138 assert(getcwd(tmp, MAXPATHLEN) == tmp);
139 assert(chdir(getenv("HOME")) == 0);
140 #endif
142 fp = fopen(filename, "r");
144 if (fp != NULL) {
145 s = (GameState*)malloc(sizeof(GameState));
146 if (s != NULL) {
147 if (fread(s, sizeof(GameState), 1, fp) == 1) {
148 /* Restore/create process-specific data */
149 /* FIXME: s->location, players' "input" */
150 } else {
151 free(s);
152 s = NULL;
155 fclose(fp);
158 #ifndef WIN32
159 assert(chdir(tmp) == 0);
160 #endif
162 return s;
166 void gameloop(GameState *s) {
167 Uint32 ot = SDL_GetTicks();
168 Uint32 nt;
169 Uint32 dt = GAME_TICKS;
170 Uint32 diff;
171 Uint32 accumulator = 0;
172 bool quit = false;
173 int p;
174 RenderState r = {
175 SOUND_EVENT_NONE,
176 REFEREE_COUNT,
177 EVENTCOUNTER_RENDERSTATE_START,
178 EVENTCOUNTER_RENDERSTATE_START,
179 STATUSMSG_NONE,
180 NULL,
181 NULL,
182 NULL,
185 /* Catch-up with existing sound events */
186 r.sound_events = s->sound_events;
188 #ifdef ENABLE_FPS_LIMIT
189 Uint32 ft, frames; /* frame timer and frames */
190 #endif
192 if (s->rain > 0) {
193 play_sample_background(SOUND_RAIN);
195 if (s->location->max_visitors > 100) {
196 play_sample_loop(SOUND_AUDIENCE);
199 for (p=1; p<=MAXPLAYERS; p++) {
200 input_device_join_game(PLAYER(s, p).input, s, p);
203 #ifdef ENABLE_FPS_LIMIT
204 frames = 0;
205 ft = SDL_GetTicks();
206 #endif
207 while( !quit) {
208 nt = SDL_GetTicks();
209 diff = nt-ot;
210 if( diff > 2000) {
211 diff = 0;
214 accumulator += diff;
215 ot = nt;
217 while( accumulator >= dt) {
218 quit = step(s);
219 s->time += dt;
220 accumulator -= dt;
223 #ifdef ENABLE_FPS_LIMIT
224 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
225 SDL_Delay(10);
227 frames++;
228 #endif
230 render(s, &r);
233 for (p=1; p<=MAXPLAYERS; p++) {
234 input_device_part_game(PLAYER(s, p).input);
237 clear_screen();
238 rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
239 store_screen();
241 stop_sample(SOUND_AUDIENCE);
242 stop_sample(SOUND_RAIN);
245 bool step( GameState* s) {
246 Uint8 *keys;
247 bool ground_event = false;
248 int p;
250 if (s->ball.z < 0) {
251 /* ground event */
252 ground_event = true;
254 s->referee = REFEREE_NORMAL;
256 /* bounce from the ground */
257 if (fabsf(s->ball.move_z) > 0.3) {
258 s->sound_events ^= SOUND_EVENT_GROUND;
259 sample_volume_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, fmaxf(0.0, fminf(1.0, fabsf(s->ball.move_z)/2)));
260 pan_sample_group(SOUND_GROUND_FIRST, SOUND_GROUND_LAST, fmaxf(0.0, fminf(1.0, (s->ball.x)/WIDTH)));
261 s->ball.move_z *= -s->ball.restitution;
262 } else {
263 s->ball.move_z = 0;
265 s->ball.z = 0;
268 if (NET_COLLISION_BALL(s->ball)) {
269 /* the net blocks movement of the ball */
270 while (NET_COLLISION_BALL(s->ball)) {
271 /* make sure the ball appears OUTSIDE of the net */
272 if (s->ball.move_x < 0) {
273 s->ball.x += 1;
274 } else {
275 s->ball.x -= 1;
278 s->ball.move_x = 0;
279 s->ball.move_y = 0;
282 /* see if we have something to score */
283 if (s->score_event == SCORE_UNDECIDED) {
284 if (NET_COLLISION_BALL(s->ball)) {
285 /* the ball "fell" into the net */
286 s->score_event = SCORE_EVENT_NET;
287 s->sound_events ^= SOUND_EVENT_OUT;
288 s->status_message = STATUSMSG_NET;
289 } else if (IS_OFFSCREEN(s->ball.x, s->ball.y)) {
290 /* ball flew offscreen */
291 s->score_event = SCORE_EVENT_OFFSCREEN;
292 s->sound_events ^= SOUND_EVENT_OUT;
293 s->status_message = STATUSMSG_OUT;
294 } else if (ground_event) {
295 /* the ball hit the ground on the screen */
296 if (IS_OUT(s->ball.x, s->ball.y)) {
297 /* the ball bounced in the OUT area */
298 s->score_event = SCORE_EVENT_OUT;
299 s->sound_events ^= SOUND_EVENT_OUT;
300 s->status_message = STATUSMSG_OUT;
301 } else if (GROUND_IS_VALID(s->ball.last_hit_by, s->ball.x, s->ball.y)) {
302 if (s->ball.ground_hit) {
303 s->score_event = SCORE_EVENT_GROUND_VALID;
304 s->sound_events ^= SOUND_EVENT_OUT;
305 s->status_message = STATUSMSG_DIDNTCATCH;
306 } else {
307 /* first ground hit in valid area */
308 s->ball.ground_hit = true;
310 } else {
311 /* ball hit somewhere invalid */
312 s->score_event = SCORE_EVENT_GROUND_INVALID;
313 s->sound_events ^= SOUND_EVENT_OUT;
314 s->status_message = STATUSMSG_FAULT;
319 if (s->score_event != SCORE_UNDECIDED) {
320 /* we have some scoring to do */
321 if (s->score_time == 0) {
322 /* schedule scoring in the future */
323 s->score_time = s->time + SCORING_DELAY;
324 s->referee = REFEREE_OUT;
325 } else if (s->time >= s->score_time) {
326 /* time has ran out - score now */
327 switch (score_game(s)) {
328 case WINNER_PLAYER1:
329 s->status_message = STATUSMSG_P1SCORES;
330 s->referee = REFEREE_PLAYER1;
331 break;
332 case WINNER_PLAYER2:
333 s->status_message = STATUSMSG_P2SCORES;
334 s->referee = REFEREE_PLAYER2;
335 break;
336 default:
337 assert(0);
338 break;
340 s->score_time = 0;
342 game_setup_serve(s);
343 if (s->location->max_visitors > 100) {
344 s->sound_events ^= SOUND_EVENT_APPLAUSE;
347 } else {
348 /* score is still undecided - do the racket swing thing */
349 for (p=1; p<=2; p++) {
350 if (IS_NEAR_X(PLAYER(s, p).x, s->ball.x) && IS_NEAR_Y(PLAYER(s, p).y, s->ball.y-s->ball.z) && PLAYER(s, p).use_power && PLAYER(s, p).power > 30.0 && s->ball.last_hit_by != p) {
351 /* RACKET HIT */
352 if (!s->ball.ground_hit && s->ball.move_x != 0.0) {
353 s->status_message = STATUSMSG_VOLLEY;
354 } else {
355 s->status_message = STATUSMSG_DEFAULT;
357 switch (PLAYER(s, p).desire) {
358 case DESIRE_NORMAL:
359 /* normal swing */
360 s->ball.move_x = 2.7 + 2.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
361 s->ball.move_z = 1.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
362 break;
363 case DESIRE_TOPSPIN:
364 /* top spin */
365 s->ball.move_x = 1.1 + 2.2*PLAYER(s, p).power/PLAYER_POWER_MAX;
366 s->ball.move_z = 2.5*PLAYER(s, p).power/PLAYER_POWER_MAX;
367 break;
368 case DESIRE_SMASH:
369 /* smash */
370 s->ball.move_x = 4.0 + 3.0*PLAYER(s, p).power/PLAYER_POWER_MAX;
371 s->ball.move_z = 1.1*PLAYER(s, p).power/PLAYER_POWER_MAX;
372 break;
374 s->ball.move_y = get_move_y( s, p);
375 s->sound_events ^= SOUND_EVENT_RACKET;
376 s->ball.ground_hit = false;
377 s->ball.inhibit_gravity = false;
378 s->ball.last_hit_by = p;
379 if (p==1) {
380 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.3);
381 } else {
382 pan_sample_group(SOUND_RACKET_FIRST, SOUND_RACKET_LAST, 0.7);
383 s->ball.move_x *= -1;
389 SDL_PumpEvents();
390 keys = SDL_GetKeyState(NULL);
392 if( s->time%50==0) {
393 if (keys['r']) {
394 if (s->rain == 0) {
395 play_sample_background(SOUND_RAIN);
397 s->rain += 10;
399 if (keys['t']) {
400 s->fog++;
402 if (keys['1']) {
403 s->wind++;
405 if (keys['2']) {
406 s->wind--;
410 if(!(SDL_GetTicks() < fading_start+FADE_DURATION) && s->winner == WINNER_NONE) {
411 for (p=1; p<=MAXPLAYERS; p++) {
412 if( PLAYER(s, p).type == PLAYER_TYPE_HUMAN) {
413 input_human(s, p);
414 } else {
415 input_ai(s, p);
420 /* Maemo: The "F6" button is the "Fullscreen" button */
421 /*if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);*/
422 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
424 /* Maemo: The "F4" button is the "Open menu" button */
425 if(keys[SDLK_ESCAPE] || keys['q']
426 || keys['p'] || keys[SDLK_F4]) {
427 return true;
430 limit_value( &PLAYER(s, 1).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
431 limit_value( &PLAYER(s, 2).y, PLAYER_Y_MIN, PLAYER_Y_MAX);
433 if (s->ball.move_x > 0 && s->wind > 0) {
434 s->ball.x += fabsf(s->wind)/2;
435 } else if (s->ball.move_x < 0 && s->wind < 0) {
436 s->ball.x -= fabsf(s->wind)/2;
439 s->ball.z += s->ball.move_z;
440 if (!s->ball.inhibit_gravity) {
441 s->ball.move_z += GRAVITY;
444 s->ball.x += s->ball.move_x;
445 s->ball.y += s->ball.move_y;
447 for (p=1; p<=MAXPLAYERS; p++) {
448 if (PLAYER(s, p).use_power) {
449 PLAYER(s, p).power = fmaxf(0.0, fminf(PLAYER(s, p).power*PLAYER(s, p).power_down_factor, PLAYER_POWER_MAX));
453 return false;
456 void render(const GameState* s, RenderState* r) {
457 int x, y, b;
458 unsigned int i;
459 float zoom;
460 float rotate;
461 int t=1000;
462 soundevent_t sounds;
464 /* The bits in sound_events flip when the sound should play */
465 if ((sounds = (r->sound_events ^ s->sound_events)) != 0) {
466 if (sounds & SOUND_EVENT_GROUND) {
467 play_sample(SOUND_GROUND);
469 if (sounds & SOUND_EVENT_OUT) {
470 play_sample(SOUND_OUT);
472 if (sounds & SOUND_EVENT_APPLAUSE) {
473 play_sample(SOUND_APPLAUSE);
475 if (sounds & SOUND_EVENT_RACKET) {
476 play_sample(SOUND_RACKET);
478 r->sound_events = s->sound_events;
481 if( s->winner != WINNER_NONE) {
482 clear_screen();
483 rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
484 store_screen();
485 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);
486 /*sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
487 font_draw_string(FONT_LARGE, s->game_score_str, (WIDTH-font_get_string_width(FONT_LARGE, s->game_score_str))/2, HEIGHT/2 + 30);*/
488 updatescr();
489 return;
491 if ((r->referee != s->referee) ||
492 (r->ec_game != s->ec_game) ||
493 (r->ec_sets != s->ec_sets) ||
494 (r->status_message != s->status_message)) {
495 /* Update status message text */
496 if (r->status_message != s->status_message) {
497 r->text_status = format_status(s);
498 r->status_message = s->status_message;
500 /* Update game status text */
501 if (r->ec_game != s->ec_game) {
502 r->text_game = format_game(s);
503 r->ec_game = s->ec_game;
505 /* Update set status text */
506 if (r->ec_sets != s->ec_sets) {
507 r->text_sets = format_sets(s);
508 if (r->status_message == STATUSMSG_DEFAULT) {
509 r->text_status = format_status(s);
511 r->ec_sets = s->ec_sets;
513 clear_screen();
514 rectangle(0, 0, WIDTH, HEIGHT, 80, 80, 80);
515 fill_image(s->location->court_type, 120, 120, 400, 250);
516 show_image(GR_COURT, 0, 0, 255);
517 font_draw_string(FONT_XLARGE, r->text_game, 14, 14);
518 font_draw_string(FONT_XLARGE, r->text_sets,
519 (WIDTH-font_get_string_width(FONT_XLARGE, r->text_sets))-14, 14);
520 if (s->location->has_referee) {
521 switch (s->referee) {
522 case REFEREE_NORMAL:
523 t = 1000;
524 break;
525 case REFEREE_OUT:
526 t = 200;
527 break;
528 case REFEREE_PLAYER1:
529 case REFEREE_PLAYER2:
530 t = 400;
531 break;
533 t = (s->time/t)%4;
534 switch (t) {
535 case 0:
536 t=0;
537 break;
538 case 1:
539 t=1;
540 break;
541 case 2:
542 t=0;
543 break;
544 case 3:
545 t=2;
546 break;
548 show_sprite( GR_REFEREE, s->referee*3+t, 12, 250, 10, 255);
549 if (voice_finished_flag == 0) {
550 show_sprite(GR_TALK, (s->time/150)%2, 2, 280, 45, 255);
553 r->referee = s->referee;
554 store_screen();
557 /* show_sprite( GR_RACKET, (!PLAYER(s, 1).state), 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
558 show_sprite( GR_RACKET, (!PLAYER(s, 2).state)+2, 4, PLAYER(s, 2).x-RACKET_X_MID, PLAYER(s, 2).y-RACKET_Y_MID, 255);*/
559 show_sprite( GR_RACKET, !PLAYER(s, 1).use_power, 4, PLAYER(s, 1).x-RACKET_X_MID, PLAYER(s, 1).y-RACKET_Y_MID, 255);
560 show_sprite( GR_RACKET, !PLAYER(s, 2).use_power + 2, 4, PLAYER(s, 2).x-RACKET_X_MID, PLAYER(s, 2).y-RACKET_Y_MID, 255);
562 rectangle(10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
563 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, PLAYER_POWER_MAX, 10, 50, 50, 50);
565 rectangle(10, HEIGHT-30, (int)(PLAYER(s, 1).power), 10, 200, 200, 200);
566 rectangle(WIDTH-PLAYER_POWER_MAX-10, HEIGHT-30, (int)(PLAYER(s, 2).power), 10, 200, 200, 200);
568 if( s->ball.move_x > 0) {
569 b = (s->time/100)%BALL_STATES;
570 } else if( s->ball.move_x < 0) {
571 b = BALL_STATES-1-(s->time/100)%BALL_STATES;
572 } else {
573 b = 0;
576 rotate = 0.0;
577 zoom = fmaxf(0.5, fminf(1.0, (float)(30.-(s->ball.z))/10.));
578 show_image_rotozoom(GR_SHADOW, s->ball.x, s->ball.y+3, rotate, zoom);
580 rotate = (-s->ball.move_x * (float)((float)s->time/10.));
581 zoom = 1.0 + fmaxf(0.0, (s->ball.z-10.)/100.);
582 show_image_rotozoom(GR_BALL, s->ball.x, s->ball.y - s->ball.z, rotate, zoom);
584 /* Player 1's mouse rectangle */
585 if (PLAYER(s, 1).input != NULL && PLAYER(s, 1).input->type == INPUT_TYPE_MOUSE) {
586 rectangle(PLAYER(s, 1).x-2+5, PLAYER(s, 1).input->my-2, 4, 4, 255, 255, 255);
587 rectangle(PLAYER(s, 1).x-1+5, PLAYER(s, 1).input->my-1, 2, 2, 0, 0, 0);
590 /* Player 2's mouse rectangle */
591 if (PLAYER(s, 2).input != NULL && PLAYER(s, 2).input->type == INPUT_TYPE_MOUSE) {
592 rectangle(PLAYER(s, 2).x-2+5, PLAYER(s, 2).input->my-2, 4, 4, 255, 255, 255);
593 rectangle(PLAYER(s, 2).x-1+5, PLAYER(s, 2).input->my-1, 2, 2, 0, 0, 0);
596 font_draw_string(FONT_MEDIUM, r->text_status,
597 (WIDTH-font_get_string_width(FONT_MEDIUM, r->text_status))/2, HEIGHT-50);
599 for (i=0; i<s->rain; i++) {
600 x = rand()%WIDTH;
601 y = rand()%HEIGHT;
602 draw_line_faded(x, y, x+10+s->wind*5, y+30, 0, 0, 255, 100, 200, 255);
604 if (s->rain) {
606 * Cheap-ish update of the whole screen. This can
607 * probably be optimized.
609 update_rect(0, 0, WIDTH, HEIGHT);
612 #ifdef DEBUG
613 line_horiz( PLAYER(s, 1).y, 255, 0, 0);
614 line_horiz( PLAYER(s, 2).y, 0, 0, 255);
615 line_horiz( s->ball.y, 0, 255, 0);
617 line_vert( PLAYER(s, 1).x, 255, 0, 0);
618 line_vert( PLAYER(s, 2).x, 0, 0, 255);
619 line_vert( s->ball.x, 0, 255, 0);
621 line_horiz( GAME_Y_MIN, 100, 100, 100);
622 line_horiz( GAME_Y_MAX, 100, 100, 100);
623 #endif
624 switch (s->fog) {
625 default:
626 case 4:
627 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150, 0);
628 case 3:
629 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100, 20);
630 case 2:
631 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180, 80);
632 case 1:
633 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200, 0);
634 case 0:
635 break;
637 if (s->night) {
638 show_image(GR_NIGHT, 0, 0, 255);
641 updatescr();
644 void limit_value( float* value, float min, float max) {
645 if( *value < min) {
646 *value = min;
647 } else if( *value > max) {
648 *value = max;
652 float get_move_y( GameState* s, unsigned char player) {
653 float pct, dest, x_len, y_len;
654 float py, by, pa, move_x;
656 py = (player==1)?(PLAYER(s, 1).y):(PLAYER(s, 2).y);
657 by = s->ball.y - s->ball.z;
658 pa = RACKET_Y_MID*2;
659 move_x = s->ball.move_x;
661 /* -1.0 .. 1.0 for racket hit position */
662 pct = fmaxf(-1.0, fminf(1.0, (by-py)/(pa/2)));
664 /* Y destination for ball */
665 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
667 /* lengths for the ball's journey */
668 if( player == 1) {
669 x_len = GAME_X_MAX - s->ball.x;
670 } else {
671 x_len = s->ball.x - GAME_X_MIN;
673 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
675 /* return the should-be value for move_y */
676 return (y_len*move_x)/(x_len);
679 void input_human(GameState* s, int player) {
680 bool hit, topspin, smash;
681 float move_y;
683 /* For mouse input, hand the player coordinates to the InputDevice */
684 if (PLAYER(s, player).input->type == INPUT_TYPE_MOUSE) {
685 PLAYER(s, player).input->player_x = (int)(PLAYER(s, player).x);
686 PLAYER(s, player).input->player_y = (int)(PLAYER(s, player).y);
689 move_y = PLAYER_MOVE_Y*input_device_get_axis(PLAYER(s, player).input, INPUT_AXIS_Y);
691 hit = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_HIT);
692 topspin = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_TOPSPIN);
693 smash = input_device_get_key(PLAYER(s, player).input, INPUT_KEY_SMASH);
695 if (move_y != 0) {
696 if (fabsf(move_y) > fabsf(move_y*PLAYER(s, player).accelerate)) {
697 move_y *= PLAYER(s, player).accelerate;
699 PLAYER(s, player).y += move_y;
700 PLAYER(s, player).accelerate *= PLAYER_ACCEL_INCREASE;
701 } else {
702 PLAYER(s, player).accelerate = PLAYER_ACCEL_DEFAULT;
705 if(hit || topspin || smash) {
706 PLAYER(s, player).desire = (topspin)?(DESIRE_TOPSPIN):(DESIRE_NORMAL);
707 PLAYER(s, player).desire = (smash)?(DESIRE_SMASH):(PLAYER(s, player).desire);
709 PLAYER(s, player).power = fmaxf(10.0, fminf(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
710 PLAYER(s, player).use_power = false;
711 } else {
712 PLAYER(s, player).use_power = true;
716 void input_ai(GameState* s, int player) {
717 float fact = 1.7;
718 int ball_approaching = 0;
720 if ((PLAYER(s, player).x < GAME_X_MID && s->ball.move_x <= 0) ||
721 (PLAYER(s, player).x > GAME_X_MID && s->ball.move_x >= 0)) {
722 ball_approaching = 1;
725 /* FIXME - this is broken since the new physics model has been introduced */
727 if (fabsf(PLAYER(s, player).y - (s->ball.y-s->ball.z)) > RACKET_Y_MID*5) {
728 fact = 3.5;
731 if(1) {
732 if( PLAYER(s, player).desire == DESIRE_NORMAL && !IS_NEAR_Y_AI(PLAYER(s, player).y, (s->ball.y-s->ball.z)) && ball_approaching) {
733 if( PLAYER(s, player).y < (s->ball.y-s->ball.z)) {
734 PLAYER(s, player).y += fminf(2*fact, (s->ball.y-s->ball.z) - PLAYER(s, player).y);
735 } else if( PLAYER(s, player).y > (s->ball.y-s->ball.z)) {
736 PLAYER(s, player).y -= fminf(2*fact, PLAYER(s, player).y - (s->ball.y-s->ball.z));
740 if (IS_NEAR_Y(PLAYER(s, player).y, (s->ball.y-s->ball.z)) && IS_NEAR_X_AI(PLAYER(s, player).x, s->ball.x) && PLAYER(s, player).power > 90.) {
741 PLAYER(s, player).use_power = true;
742 } else if (ball_approaching) {
743 PLAYER(s, player).power = fmaxf(10., fminf(PLAYER(s, player).power*PLAYER(s, player).power_up_factor, PLAYER_POWER_MAX));
744 PLAYER(s, player).use_power = false;
749 void game_setup_serve( GameState* s) {
750 s->ball.z = 10.0;
751 s->ball.y = GAME_Y_MID;
752 s->ball.move_x = 0.0;
753 s->ball.move_y = 0.0;
754 s->ball.move_z = 0.0;
755 s->ball.last_hit_by = -1;
756 s->ball.inhibit_gravity = true;
758 if( s->serving_player == 1) {
759 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
760 } else {
761 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
765 int score_game(GameState* s) {
766 Player *last_hit_by = NULL;
767 Player *other = NULL;
769 Player* winner = NULL;
770 Player* loser = NULL;
772 /* determine "last hit by" and "other" */
773 if (s->ball.last_hit_by == 1) {
774 last_hit_by = &(PLAYER(s, 1));
775 other = &(PLAYER(s, 2));
776 } else {
777 last_hit_by = &(PLAYER(s, 2));
778 other = &(PLAYER(s, 1));
781 switch (s->score_event) {
782 case SCORE_EVENT_NET:
783 winner = other;
784 break;
785 case SCORE_EVENT_OUT:
786 case SCORE_EVENT_GROUND_INVALID:
787 case SCORE_EVENT_OFFSCREEN:
788 case SCORE_EVENT_GROUND_VALID:
789 if (s->ball.ground_hit) {
790 winner = last_hit_by;
791 } else {
792 winner = other;
794 break;
795 default:
796 break;
799 /* determine loser based on winner */
800 if (winner == last_hit_by) {
801 loser = other;
802 } else {
803 loser = last_hit_by;
806 /* we cannot be in an "impossibly high" set */
807 assert(s->current_set < SETS_TO_WIN*2-1);
809 winner->game++;
810 s->ec_game++;
811 if( loser->game < winner->game-1) {
812 if( winner->game >= 4) {
813 winner->game = loser->game = 0;
814 winner->sets[s->current_set]++;
815 s->ec_sets++;
817 /* serving is changed when the "game" is over */
818 s->serving_player = (s->serving_player==1)?(2):(1);
820 #ifdef HAVE_VOICE_FILES
821 /* speak the current score */
822 voice_say_list(4, VOICE_ZERO_IN + (PLAYER(s, 1).sets[s->current_set])*2, VOICE_TO, VOICE_ZERO_OUT + (PLAYER(s, 2).sets[s->current_set])*2, VOICE_IN_THE_FIRST_SET+s->current_set);
823 #endif
825 /* scoring the set.. */
826 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
827 winner->sets[s->current_set] == 7) {
828 s->current_set++;
829 s->winner = game_get_winner( s);
834 /* forget this event - we've handled it */
835 s->score_event = SCORE_UNDECIDED;
837 if (winner == &PLAYER(s, 1)) {
838 return WINNER_PLAYER1;
839 } else {
840 return WINNER_PLAYER2;
844 const char* format_sets(const GameState* s) {
845 static char sets[100];
846 static char tmp[100];
847 int i, max = s->current_set;
849 sets[0] = '\0';
851 if( s->winner != WINNER_NONE) {
852 max--;
854 for( i=0; i<=max; i++) {
855 sprintf( tmp, "%d:%d, ", PLAYER(s, 1).sets[i], PLAYER(s, 2).sets[i]);
856 strcat( sets, tmp);
859 sets[strlen(sets)-2] = '\0';
861 return sets;
864 const char* format_game(const GameState* s) {
865 static char game[100];
866 static const int game_scoring[] = { 0, 15, 30, 40 };
868 if( PLAYER(s, 1).game < 4 && PLAYER(s, 2).game < 4) {
869 #ifdef HAVE_VOICE_FILES
870 if (PLAYER(s, 1).game > 0 || PLAYER(s, 2).game > 0) {
871 if (PLAYER(s, 1).game == PLAYER(s, 2).game) {
872 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_ALL);
873 } else {
874 voice_say_list(2, VOICE_LOVE_IN + 2*(PLAYER(s, 1).game), VOICE_LOVE_OUT + 2*(PLAYER(s, 2).game));
877 #endif
878 sprintf( game, "%d - %d", game_scoring[PLAYER(s, 1).game], game_scoring[PLAYER(s, 2).game]);
879 } else if( PLAYER(s, 1).game > PLAYER(s, 2).game) {
880 #ifdef HAVE_VOICE_FILES
881 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_ONE);
882 #endif
883 strcpy( game, "advantage player 1");
884 } else if( PLAYER(s, 1).game < PLAYER(s, 2).game) {
885 #ifdef HAVE_VOICE_FILES
886 voice_say_list(1, VOICE_ADVANTAGE_PLAYER_TWO);
887 #endif
888 strcpy( game, "advantage player 2");
889 } else {
890 #ifdef HAVE_VOICE_FILES
891 voice_say_list(1, VOICE_DEUCE);
892 #endif
893 strcpy( game, "deuce");
896 return game;
899 const char* format_status(const GameState* s) {
900 static char status[100];
901 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
903 switch (s->status_message) {
904 case STATUSMSG_NONE:
905 return "";
906 case STATUSMSG_WELCOME:
907 return "welcome to tennix " VERSION;
908 case STATUSMSG_DEFAULT:
909 sprintf(status, "%d:%d in %s set",
910 PLAYER(s, 1).sets[s->current_set],
911 PLAYER(s, 2).sets[s->current_set],
912 set_names[s->current_set]);
913 return status;
914 case STATUSMSG_NET:
915 return "net!";
916 case STATUSMSG_OUT:
917 return "out!";
918 case STATUSMSG_FAULT:
919 return "fault!";
920 case STATUSMSG_P1SCORES:
921 return "player 1 scores.";
922 case STATUSMSG_P2SCORES:
923 return "player 2 scores.";
924 case STATUSMSG_VOLLEY:
925 return "volley!";
926 case STATUSMSG_DIDNTCATCH:
927 return "did not catch the ball.";
928 default:
929 return "";
933 int game_get_winner( GameState* s) {
934 unsigned int i;
935 int sets[2] = {0};
937 for( i=0; i<s->current_set; i++) {
938 if( PLAYER(s, 1).sets[i] > PLAYER(s, 2).sets[i]) {
939 sets[0]++;
940 } else {
941 sets[1]++;
945 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
946 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
948 return WINNER_NONE;