FPS Limiting for the main menu display code
[tennix.git] / game.c
blob7aec483827817cdeecabd6d14573c76dd7bcce24
2 /**
4 * Tennix! SDL Port
5 * Copyright (C) 2003, 2007, 2008 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"
34 GameState *gamestate_new() {
35 int x, y, z;
36 GameState *s;
38 GameState template = {
39 { 0, 0, 0.0, 0.0, 0.0 },
40 { 0, 0, 0, 0, 0 },
41 { GAME_X_MIN-RACKET_X_MID*2, GAME_Y_MID, 0, 0, 1, DESIRE_NORMAL, PLAYER_TYPE_HUMAN, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true },
42 { GAME_X_MAX+RACKET_X_MID*2, GAME_Y_MID, 0, 0, 0, DESIRE_NORMAL, PLAYER_TYPE_AI, GAME_Y_MID, false, 0, {0}, 0, 0, PLAYER_ACCEL_DEFAULT, true },
46 true,
47 "welcome to tennix " VERSION,
48 { 0 },
49 { 0 },
50 REFEREE_NORMAL,
52 WINNER_NONE,
53 false,
54 GR_CTT_GRASS,
55 -1,
56 { 0 },
58 false,
59 { { { 0 } } },
60 0.0,
61 SOUND_MAX,
62 0.0,
63 0.0,
67 s = (GameState*)malloc(sizeof(GameState));
68 if (s == NULL) abort();
70 memcpy(s, &template, sizeof(GameState));
72 game_setup_serve(s);
74 /* smoothen n-gram */
75 for( x = 0; x<NGRAM_STEPS; x++) {
76 for( y = 0; y<NGRAM_STEPS; y++) {
77 for( z = 0; z<NGRAM_STEPS; z++) {
78 s->ngram[x][y][z] = 1;
83 return s;
87 void gameloop(GameState *s) {
88 strcpy(s->game_score_str, format_game(s));
89 strcpy(s->sets_score_str, format_sets(s));
91 Uint32 ot = SDL_GetTicks();
92 Uint32 nt;
93 Uint32 dt = GAME_TICKS;
94 Uint32 diff;
95 Uint32 accumulator = 0;
96 bool quit = false;
98 #ifdef ENABLE_FPS_LIMIT
99 Uint32 ft, frames; /* frame timer and frames */
100 #endif
102 play_sample_loop(SOUND_AUDIENCE);
103 /* Reset the court type, so it is redrawn on first display */
104 s->old_court_type = -1;
106 #ifdef ENABLE_FPS_LIMIT
107 frames = 0;
108 ft = SDL_GetTicks();
109 #endif
110 while( !quit) {
111 nt = SDL_GetTicks();
112 diff = nt-ot;
113 if( diff > 2000) {
114 diff = 0;
117 accumulator += diff;
118 ot = nt;
120 while( accumulator >= dt) {
121 quit = step(s);
122 s->time += dt;
123 accumulator -= dt;
125 if( s->was_stopped) {
126 ot = SDL_GetTicks();
127 s->was_stopped = false;
131 #ifdef ENABLE_FPS_LIMIT
132 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
133 SDL_Delay(10);
135 frames++;
136 #endif
138 render(s);
141 clear_screen();
142 store_screen();
144 stop_sample(SOUND_AUDIENCE);
147 bool step( GameState* s) {
148 Uint8 *keys;
149 SDL_Event e;
151 if( get_phase( s) < 1.0) {
152 if( !s->ground.jump) {
153 s->play_sound = SOUND_GROUND;
155 if( IS_OUT_Y( s->ball.y)) {
156 /* out - responsibilities stay the same */
157 s->status = "out!";
158 s->play_sound = SOUND_OUT;
159 s->referee = REFEREE_OUT;
160 } else {
161 /* not out - responsibilities change */
162 s->player1.responsible = !(s->player2.responsible = !s->player2.responsible);
163 s->status = format_status( s);
164 s->referee = REFEREE_NORMAL;
167 s->ground.jump = 3;
168 s->ground.x = s->ball.x;
169 s->ground.y = s->ball.y;
170 } else {
171 if( s->ground.jump && !(s->time%5)) s->ground.jump--;
174 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
175 if( IS_OFFSCREEN( s->ball.x, s->ball.y)) {
176 s->player1_serves = s->player1.responsible;
178 score_game( s, s->player2.responsible);
179 strcpy( s->game_score_str, format_game( s));
180 strcpy( s->sets_score_str, format_sets( s));
182 if( s->player1.responsible) {
183 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
184 s->status = "computer scores";
185 } else {
186 s->status = "player 2 scores";
188 s->referee = REFEREE_PLAYER2;
189 } else {
190 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
191 s->status = "player scores";
192 } else {
193 s->status = "player 1 scores";
195 s->referee = REFEREE_PLAYER1;
198 game_setup_serve( s);
199 s->play_sound = SOUND_APPLAUSE;
200 SDL_Delay( 500);
201 start_fade();
202 s->was_stopped = true;
203 s->history_size = 0;
204 s->history_is_locked = 0;
205 s->ngram_prediction = 0.0;
206 #ifdef DEBUG
207 printf( "-- game reset --\n");
208 #endif
211 if( IS_OUT_X(s->ball.x)) {
212 if( !s->history_is_locked && s->referee != REFEREE_OUT) {
213 s->history[s->history_size] = (int)(NGRAM_STEPS*s->ball.y/HEIGHT);
214 s->history_size++;
215 if( s->history_size == 3) {
216 s->ngram[s->history[0]][s->history[1]][s->history[2]] += 10;
217 #ifdef DEBUG
218 printf( "history: %d, %d, %d\n", s->history[0], s->history[1], s->history[2]);
219 #endif
220 s->ngram_prediction = ngram_predictor( s);
221 s->history[0] = s->history[1];
222 s->history[1] = s->history[2];
223 s->history_size--;
225 s->history_is_locked = true;
227 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) {
228 s->ball.x = GAME_X_MIN;
229 if( s->player1.state == PLAYER_STATE_MAX) {
230 s->ball.move_x = PLAYER_POWERSHOT;
231 } else {
232 s->ball.move_x = 2.5 + 2.0*s->player1.state/PLAYER_STATE_MAX;
234 s->ball.move_y = get_move_y( s, 1);
235 s->player2.responsible = !(s->player1.responsible = 1);
236 s->ball.jump += 1.0-2.0*(s->player1.state<5);
237 s->play_sound = SOUND_RACKET;
238 pan_sample(SOUND_RACKET, 0.4);
239 } 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) {
240 s->ball.x = GAME_X_MAX;
241 if( s->player2.state == PLAYER_STATE_MAX) {
242 s->ball.move_x = -PLAYER_POWERSHOT;
243 } else {
244 s->ball.move_x = -(2.5 + 2.0*s->player2.state/PLAYER_STATE_MAX);
246 s->ball.move_y = get_move_y( s, 2);
247 s->player1.responsible = !(s->player2.responsible = 1);
248 s->ball.jump += 1.0-2.0*(s->player2.state<5);
249 s->play_sound = SOUND_RACKET;
250 pan_sample(SOUND_RACKET, 0.6);
253 } else {
254 s->history_is_locked = false;
257 SDL_PollEvent( &e);
258 keys = SDL_GetKeyState( NULL);
259 switch(e.type) {
260 case SDL_JOYAXISMOTION:
261 if (e.jaxis.axis == JOYSTICK_Y_AXIS) {
262 s->joystick_y = JOYSTICK_PERCENTIZE(e.jaxis.value);
263 } else if (e.jaxis.axis == JOYSTICK_X_AXIS) {
264 s->joystick_x = JOYSTICK_PERCENTIZE(e.jaxis.value);
266 break;
267 case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN:
268 if (e.jbutton.button == JOYSTICK_BUTTON_A) {
269 s->joystick_a = (e.jbutton.state == SDL_PRESSED);
271 break;
274 if( s->time%50==0) {
276 * Maemo keys:
277 * F7 = "Decrease" key
278 * F8 = "Increase" key
280 if (keys['c'] || keys[SDLK_F8]) {
281 s->court_type++;
282 } else if (keys[SDLK_F7]) {
283 s->court_type--;
285 if( s->court_type > GR_CTT_LAST) {
286 s->court_type = GR_CTT_FIRST;
287 } else if (s->court_type < GR_CTT_FIRST) {
288 s->court_type = GR_CTT_LAST;
292 if( !is_fading() && !s->is_over) {
293 if( s->player1.type == PLAYER_TYPE_HUMAN) {
294 input_human( &s->player1,
295 keys['w'] || keys[SDLK_UP] || s->joystick_y < -JOYSTICK_TRESHOLD,
296 keys['s'] || keys[SDLK_DOWN] || s->joystick_y > JOYSTICK_TRESHOLD,
297 keys['d'] || keys[SDLK_SPACE] || keys[SDLK_LCTRL] || keys[SDLK_RETURN] || s->joystick_a,
298 #ifdef ENABLE_MOUSE
299 true,
300 #else
301 false,
302 #endif
304 } else {
305 input_ai( &s->player1, &s->ball, &s->player2, s);
308 if( s->player2.type == PLAYER_TYPE_HUMAN) {
309 input_human( &s->player2, keys['o'], keys['l'], keys['k'], false, s);
310 } else {
311 input_ai( &s->player2, &s->ball, &s->player1, s);
315 /* Maemo: The "F6" button is the "Fullscreen" button */
316 if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);
317 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
319 /* Maemo: The "F4" button is the "Open menu" button */
320 if( keys['p'] || keys[SDLK_F4]) {
321 while( keys['p'] || keys[SDLK_F4]) {
322 SDL_PollEvent( &e);
323 keys = SDL_GetKeyState( NULL);
324 SDL_Delay( 10);
326 while( (keys['p'] || keys[SDLK_F4]) == 0) {
327 SDL_PollEvent( &e);
328 keys = SDL_GetKeyState( NULL);
329 SDL_Delay( 10);
331 while( keys['p'] || keys[SDLK_F4]) {
332 SDL_PollEvent( &e);
333 keys = SDL_GetKeyState( NULL);
334 SDL_Delay( 10);
336 s->was_stopped = true;
339 if( keys[SDLK_ESCAPE] || keys['q']) return true;
341 limit_value( &s->player1.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
342 limit_value( &s->player2.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
343 limit_value( &s->ball.jump, BALL_JUMP_MIN, BALL_JUMP_MAX);
345 /* Update ball_dest for debugging purposes */
346 get_move_y(s, 1);
347 get_move_y(s, 2);
349 s->ball.x += s->ball.move_x;
350 s->ball.y += s->ball.move_y;
352 if(s->player1.state) s->player1.state--;
353 if(s->player2.state) s->player2.state--;
355 return false;
358 void render( GameState* s) {
359 if (s->play_sound != SOUND_MAX) {
360 play_sample(s->play_sound);
361 s->play_sound = SOUND_MAX;
363 if( s->winner != WINNER_NONE) {
364 if( !s->is_over) {
365 start_fade();
366 s->is_over = true;
368 clear_screen();
369 store_screen();
370 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);
371 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
372 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);
373 updatescr();
374 return;
376 if (s->old_court_type != s->court_type) {
377 clear_screen();
378 fill_image(s->court_type, 120, 120, 400, 250);
379 show_image(GR_COURT, 0, 0, 255);
380 s->old_court_type = s->court_type;
381 store_screen();
383 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
384 show_image( GR_SHADOW, s->ball.x-BALL_X_MID, s->ball.y + get_phase( s) - BALL_Y_MID, 255);
386 show_sprite( GR_RACKET, (!s->player1.state), 4, s->player1.x-RACKET_X_MID, s->player1.y-RACKET_Y_MID, 255);
387 show_sprite( GR_RACKET, (!s->player2.state)+2, 4, s->player2.x-RACKET_X_MID, s->player2.y-RACKET_Y_MID, 255);
389 if( s->ball.move_x > 0) {
390 show_sprite( GR_BALL, (s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
391 } else if( s->ball.move_x < 0) {
392 show_sprite( GR_BALL, 3-(s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
393 } else {
394 show_sprite( GR_BALL, 0, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
397 /* Player 1's mouse rectangle */
398 if (!(s->player1.mouse_locked)) {
399 rectangle(s->player1.x-2+5, s->player1.mouse_y-2, 4, 4, 255, 255, 255);
400 rectangle(s->player1.x-1+5, s->player1.mouse_y-1, 2, 2, 0, 0, 0);
403 font_draw_string( GR_DKC2_FONT, s->game_score_str, 14, 14, 0, ANIMATION_NONE);
404 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);
406 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);
408 #ifdef DEBUG
409 line_horiz( s->player1.y, 255, 0, 0);
410 line_horiz( s->player2.y, 0, 0, 255);
411 line_horiz( s->ball.y, 0, 255, 0);
413 line_vert( s->player1.x, 255, 0, 0);
414 line_vert( s->player2.x, 0, 0, 255);
415 line_vert( s->ball.x, 0, 255, 0);
417 line_horiz( s->player1.ball_dest, 255, 0, 255);
418 line_horiz( s->player2.ball_dest, 0, 255, 255);
420 line_horiz( GAME_Y_MIN, 100, 100, 100);
421 line_horiz( GAME_Y_MAX, 100, 100, 100);
422 #endif
424 updatescr();
427 void limit_value( float* value, float min, float max) {
428 if( *value < min) {
429 *value = min;
430 } else if( *value > max) {
431 *value = max;
435 float get_phase( GameState* s) {
436 float pos, fract;
437 float x, min, max, direction;
439 x = s->ball.x;
440 min = GAME_X_MIN;
441 max = GAME_X_MAX;
442 direction = s->ball.move_x;
444 pos = (direction>0)?(1-GROUND_PHASE):(GROUND_PHASE);
446 fract = (x-min)/(max-min);
448 if( fract < pos) {
449 fract = fract/pos;
450 return fabsf( cosf(PI*fract/2))*PHASE_AMP*s->ball.jump;
451 } else {
452 fract = (pos-fract)/(1-pos);
453 return fabsf( sinf(PI*fract/2))*PHASE_AMP*s->ball.jump;
457 float get_move_y( GameState* s, unsigned char player) {
458 float pct, dest, x_len, y_len;
459 float py, by, pa, move_x;
461 py = (player==1)?(s->player1.y):(s->player2.y);
462 by = s->ball.y;
463 pa = RACKET_Y_MID*2;
464 move_x = s->ball.move_x;
466 /* -1.0 .. 1.0 for racket hit position */
467 pct = (by-py)/(pa/2);
468 limit_value( &pct, -1.0, 1.0);
470 /* Y destination for ball */
471 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
472 if( player == 1) {
473 s->player1.ball_dest = dest;
474 } else {
475 s->player2.ball_dest = dest;
478 /* lengths for the ball's journey */
479 if( player == 1) {
480 x_len = fabsf(GAME_X_MAX - s->ball.x);
481 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
482 } else {
483 x_len = s->ball.x - GAME_X_MIN;
484 y_len = by - dest + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
487 /* return the should-be value for move_y */
488 return (y_len*move_x)/(x_len);
491 void input_human( Player* player, bool up, bool down, bool hit, bool use_mouse, GameState* s) {
492 int diff = PLAYER_MOVE_Y;
493 int mb;
496 * Only use mouse control if the user isn't pressing any buttons
497 * this way, keyboard control still works when mouse control is
498 * enabled.
500 if (use_mouse && (down || up || hit)) {
502 * this is here so if the user decides to play
503 * with keyboard controls, we will lock the
504 * mouse and disable displaying the mouse cursor
506 player->mouse_locked = true;
508 if (use_mouse && !down && !up && !hit) {
509 mb = SDL_GetMouseState(&(player->mouse_x), &(player->mouse_y));
510 if (mb&SDL_BUTTON(SDL_BUTTON_LEFT)) {
511 if (player->mouse_y < player->y) {
512 down = false;
513 up = true;
514 diff = (player->y-player->mouse_y<diff)?(player->y-player->mouse_y):(diff);
515 } else if (player->mouse_y > player->y) {
516 up = false;
517 down = true;
518 diff = (player->mouse_y-player->y<diff)?(player->mouse_y-player->y):(diff);
520 player->mouse_locked = false;
521 } else if (!player->mouse_locked) {
522 hit = true;
526 if (fabsf(s->joystick_y) > JOYSTICK_TRESHOLD) {
527 diff = PLAYER_MOVE_Y*fabsf(s->joystick_y)/40.0;
528 if (diff > PLAYER_MOVE_Y) {
529 diff = PLAYER_MOVE_Y;
533 if (up) {
534 player->y -= fminf(diff, diff*player->accelerate);
535 player->accelerate *= PLAYER_ACCEL_INCREASE;
536 } else if (down) {
537 player->y += fminf(diff, diff*player->accelerate);
538 player->accelerate *= PLAYER_ACCEL_INCREASE;
539 } else {
540 player->accelerate = PLAYER_ACCEL_DEFAULT;
543 if( hit) {
544 if( !player->state && !player->state_locked) {
545 player->state = PLAYER_STATE_MAX;
546 player->state_locked = true;
548 } else {
549 player->state_locked = false;
553 void input_ai( Player* player, Ball* ball, Player* opponent, GameState* s) {
554 float fact = 1.7;
555 float target;
557 if( fabsf( player->y - ball->y) > RACKET_Y_MID*5) {
558 fact = 3.5;
561 target = GAME_Y_MID + (opponent->ball_dest - GAME_Y_MID)/5;
563 if( player->responsible) {
564 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, ball->y)) {
565 if( player->y < ball->y) {
566 player->y += fmin( 2*fact, ball->y - player->y);
567 } else if( player->y > ball->y) {
568 player->y -= fmin( 2*fact, player->y - ball->y);
572 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) {
573 player->state = PLAYER_STATE_MAX;
575 } else if( ball->move_x == 0) {
576 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
577 if( player->y < target) {
578 player->y += fmin( fact, (target-player->y)/40.0);
579 } else if( player->y > target) {
580 player->y -= fmin( fact, (player->y-target)/40.0);
583 } else if( s->ngram_prediction > 0.0) {
584 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
585 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
587 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
588 if( player->y < target) {
589 player->y += fmin( fact, (target-player->y)/40.0);
590 } else if( player->y > target) {
591 player->y -= fmin( fact, (player->y-target)/40.0);
594 } else {/*
595 if( player->desire == DESIRE_NORMAL) {
596 if( !IS_NEAR_Y_AI( player->y, target)) {
597 player->y += (target - player->y)/40.0;
603 void game_setup_serve( GameState* s) {
604 s->ball.jump = 7.5;
605 s->ball.y = GAME_Y_MID;
606 s->ball.move_x = 0.0;
607 s->ball.move_y = 0.0;
609 if( s->player1_serves) {
610 s->player1.responsible = true;
611 s->player1.ball_dest = 0.0;
612 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
613 } else {
614 s->player1.responsible = false;
615 s->player2.ball_dest = 0.0;
616 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
619 s->player2.responsible = !(s->player1.responsible);
622 float ngram_predictor( GameState* s) {
623 unsigned int count = 0;
624 unsigned long sum = 0;
625 int x, y, z;
626 float result;
628 if( s->history_size < 3) {
629 return 0.0;
632 x = s->history[1];
633 y = s->history[2];
635 for( z = 0; z<NGRAM_STEPS; z++) {
636 count += s->ngram[x][y][z];
637 sum += z * s->ngram[x][y][z];
640 result = ((float)(sum))/((float)(count));
641 #ifdef DEBUG
642 printf( "predicting next = %.2f\n", result);
643 #endif
645 return result;
648 void score_game( GameState* s, bool player1_scored) {
649 Player* winner = (player1_scored)?(&(s->player1)):(&(s->player2));
650 Player* loser = (player1_scored)?(&(s->player2)):(&(s->player1));
652 if( s->current_set >= SETS_TO_WIN*2-1) {
653 return;
656 winner->game++;
657 if( loser->game < winner->game-1) {
658 if( winner->game >= 4) {
659 winner->game = loser->game = 0;
660 winner->sets[s->current_set]++;
661 /* scoring the set.. */
662 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
663 winner->sets[s->current_set] == 7) {
664 s->current_set++;
665 s->winner = game_get_winner( s);
671 char* format_sets( GameState* s) {
672 static char sets[100];
673 static char tmp[100];
674 int i, max = s->current_set;
676 sets[0] = '\0';
678 if( s->winner != WINNER_NONE) {
679 max--;
681 for( i=0; i<=max; i++) {
682 sprintf( tmp, "%d:%d, ", s->player1.sets[i], s->player2.sets[i]);
683 strcat( sets, tmp);
686 sets[strlen(sets)-2] = '\0';
688 return sets;
691 char* format_game( GameState* s) {
692 static char game[100];
693 static const int game_scoring[] = { 0, 15, 30, 40 };
695 if( s->player1.game < 4 && s->player2.game < 4) {
696 sprintf( game, "%d - %d", game_scoring[s->player1.game], game_scoring[s->player2.game]);
697 } else if( s->player1.game > s->player2.game) {
698 strcpy( game, "advantage player 1");
699 } else if( s->player1.game < s->player2.game) {
700 strcpy( game, "advantage player 2");
701 } else {
702 strcpy( game, "deuce");
705 return game;
708 char* format_status( GameState* s) {
709 static char status[100];
710 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
712 sprintf( status, "%d:%d in %s set", s->player1.sets[s->current_set], s->player2.sets[s->current_set], set_names[s->current_set]);
714 return status;
717 int game_get_winner( GameState* s) {
718 int i;
719 int sets[2] = {0};
721 for( i=0; i<s->current_set; i++) {
722 if( s->player1.sets[i] > s->player2.sets[i]) {
723 sets[0]++;
724 } else {
725 sets[1]++;
729 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
730 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
732 return WINNER_NONE;