Make sure FPS limit in the main menu is always correct
[tennix.git] / game.c
bloba62af3b2fe7cd4b70c09dec1ff719ce2c4a1d34d
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 false
70 s = (GameState*)malloc(sizeof(GameState));
71 if (s == NULL) abort();
73 memcpy(s, &template, sizeof(GameState));
75 game_setup_serve(s);
77 /* smoothen n-gram */
78 for( x = 0; x<NGRAM_STEPS; x++) {
79 for( y = 0; y<NGRAM_STEPS; y++) {
80 for( z = 0; z<NGRAM_STEPS; z++) {
81 s->ngram[x][y][z] = 1;
86 return s;
90 void gameloop(GameState *s) {
91 strcpy(s->game_score_str, format_game(s));
92 strcpy(s->sets_score_str, format_sets(s));
94 Uint32 ot = SDL_GetTicks();
95 Uint32 nt;
96 Uint32 dt = GAME_TICKS;
97 Uint32 diff;
98 Uint32 accumulator = 0;
99 bool quit = false;
101 #ifdef ENABLE_FPS_LIMIT
102 Uint32 ft, frames; /* frame timer and frames */
103 #endif
105 play_sample_loop(SOUND_AUDIENCE);
106 /* Reset the court type, so it is redrawn on first display */
107 s->old_court_type = -1;
109 #ifdef ENABLE_FPS_LIMIT
110 frames = 0;
111 ft = SDL_GetTicks();
112 #endif
113 while( !quit) {
114 nt = SDL_GetTicks();
115 diff = nt-ot;
116 if( diff > 2000) {
117 diff = 0;
120 accumulator += diff;
121 ot = nt;
123 while( accumulator >= dt) {
124 quit = step(s);
125 s->time += dt;
126 accumulator -= dt;
128 if( s->was_stopped) {
129 ot = SDL_GetTicks();
130 s->was_stopped = false;
134 #ifdef ENABLE_FPS_LIMIT
135 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
136 SDL_Delay(10);
138 frames++;
139 #endif
141 render(s);
144 clear_screen();
145 store_screen();
147 stop_sample(SOUND_AUDIENCE);
150 bool step( GameState* s) {
151 Uint8 *keys;
152 SDL_Event e;
154 if( get_phase( s) < 1.0) {
155 if( !s->ground.jump) {
156 s->play_sound = SOUND_GROUND;
158 if( IS_OUT_Y( s->ball.y)) {
159 /* out - responsibilities stay the same */
160 s->status = "out!";
161 s->play_sound = SOUND_OUT;
162 s->referee = REFEREE_OUT;
163 } else {
164 /* not out - responsibilities change */
165 s->player1.responsible = !(s->player2.responsible = !s->player2.responsible);
166 s->status = format_status( s);
167 s->referee = REFEREE_NORMAL;
170 s->ground.jump = 3;
171 s->ground.x = s->ball.x;
172 s->ground.y = s->ball.y;
173 } else {
174 if( s->ground.jump && !(s->time%5)) s->ground.jump--;
177 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
178 if( IS_OFFSCREEN( s->ball.x, s->ball.y)) {
179 s->player1_serves = s->player1.responsible;
181 score_game( s, s->player2.responsible);
182 strcpy( s->game_score_str, format_game( s));
183 strcpy( s->sets_score_str, format_sets( s));
185 if( s->player1.responsible) {
186 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
187 s->status = "computer scores";
188 } else {
189 s->status = "player 2 scores";
191 s->referee = REFEREE_PLAYER2;
192 } else {
193 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
194 s->status = "player scores";
195 } else {
196 s->status = "player 1 scores";
198 s->referee = REFEREE_PLAYER1;
201 game_setup_serve( s);
202 s->play_sound = SOUND_APPLAUSE;
203 SDL_Delay( 500);
204 s->was_stopped = true;
205 s->history_size = 0;
206 s->history_is_locked = 0;
207 s->ngram_prediction = 0.0;
208 #ifdef DEBUG
209 printf( "-- game reset --\n");
210 #endif
213 if( IS_OUT_X(s->ball.x)) {
214 if( !s->history_is_locked && s->referee != REFEREE_OUT) {
215 s->history[s->history_size] = (int)(NGRAM_STEPS*s->ball.y/HEIGHT);
216 s->history_size++;
217 if( s->history_size == 3) {
218 s->ngram[s->history[0]][s->history[1]][s->history[2]] += 10;
219 #ifdef DEBUG
220 printf( "history: %d, %d, %d\n", s->history[0], s->history[1], s->history[2]);
221 #endif
222 s->ngram_prediction = ngram_predictor( s);
223 s->history[0] = s->history[1];
224 s->history[1] = s->history[2];
225 s->history_size--;
227 s->history_is_locked = true;
229 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) {
230 s->ball.x = GAME_X_MIN;
231 if( s->player1.state == PLAYER_STATE_MAX) {
232 s->ball.move_x = PLAYER_POWERSHOT;
233 } else {
234 s->ball.move_x = 2.5 + 2.0*s->player1.state/PLAYER_STATE_MAX;
236 s->ball.move_y = get_move_y( s, 1);
237 s->player2.responsible = !(s->player1.responsible = 1);
238 s->ball.jump += 1.0-2.0*(s->player1.state<5);
239 s->play_sound = SOUND_RACKET;
240 pan_sample(SOUND_RACKET, 0.4);
241 } 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) {
242 s->ball.x = GAME_X_MAX;
243 if( s->player2.state == PLAYER_STATE_MAX) {
244 s->ball.move_x = -PLAYER_POWERSHOT;
245 } else {
246 s->ball.move_x = -(2.5 + 2.0*s->player2.state/PLAYER_STATE_MAX);
248 s->ball.move_y = get_move_y( s, 2);
249 s->player1.responsible = !(s->player2.responsible = 1);
250 s->ball.jump += 1.0-2.0*(s->player2.state<5);
251 s->play_sound = SOUND_RACKET;
252 pan_sample(SOUND_RACKET, 0.6);
255 } else {
256 s->history_is_locked = false;
259 SDL_PollEvent( &e);
260 keys = SDL_GetKeyState( NULL);
261 switch(e.type) {
262 case SDL_JOYAXISMOTION:
263 if (e.jaxis.axis == JOYSTICK_Y_AXIS) {
264 s->joystick_y = JOYSTICK_PERCENTIZE(e.jaxis.value);
265 } else if (e.jaxis.axis == JOYSTICK_X_AXIS) {
266 s->joystick_x = JOYSTICK_PERCENTIZE(e.jaxis.value);
268 break;
269 case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN:
270 if (e.jbutton.button == JOYSTICK_BUTTON_A) {
271 s->joystick_a = (e.jbutton.state == SDL_PRESSED);
273 break;
276 if( s->time%50==0) {
278 * Maemo keys:
279 * F7 = "Decrease" key
280 * F8 = "Increase" key
282 if (keys['c'] || keys[SDLK_F8]) {
283 s->court_type++;
284 } else if (keys[SDLK_F7]) {
285 s->court_type--;
287 if (keys['r']) {
288 s->rain += 10;
290 if (keys['t']) {
291 s->fog++;
293 if (keys['n']) {
294 s->night = 1 - s->night;
296 if( s->court_type > GR_CTT_LAST) {
297 s->court_type = GR_CTT_FIRST;
298 } else if (s->court_type < GR_CTT_FIRST) {
299 s->court_type = GR_CTT_LAST;
303 if( !is_fading() && !s->is_over) {
304 if( s->player1.type == PLAYER_TYPE_HUMAN) {
305 input_human( &s->player1,
306 keys['w'] || keys[SDLK_UP] || s->joystick_y < -JOYSTICK_TRESHOLD,
307 keys['s'] || keys[SDLK_DOWN] || s->joystick_y > JOYSTICK_TRESHOLD,
308 keys['d'] || keys[SDLK_SPACE] || keys[SDLK_LCTRL] || keys[SDLK_RETURN] || s->joystick_a,
309 #ifdef ENABLE_MOUSE
310 true,
311 #else
312 false,
313 #endif
315 } else {
316 input_ai( &s->player1, &s->ball, &s->player2, s);
319 if( s->player2.type == PLAYER_TYPE_HUMAN) {
320 input_human( &s->player2, keys['o'], keys['l'], keys['k'], false, s);
321 } else {
322 input_ai( &s->player2, &s->ball, &s->player1, s);
326 /* Maemo: The "F6" button is the "Fullscreen" button */
327 if( keys['f'] || keys[SDLK_F6]) SDL_WM_ToggleFullScreen( screen);
328 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
330 /* Maemo: The "F4" button is the "Open menu" button */
331 if( keys['p'] || keys[SDLK_F4]) {
332 while( keys['p'] || keys[SDLK_F4]) {
333 SDL_PollEvent( &e);
334 keys = SDL_GetKeyState( NULL);
335 SDL_Delay( 10);
337 while( (keys['p'] || keys[SDLK_F4]) == 0) {
338 SDL_PollEvent( &e);
339 keys = SDL_GetKeyState( NULL);
340 SDL_Delay( 10);
342 while( keys['p'] || keys[SDLK_F4]) {
343 SDL_PollEvent( &e);
344 keys = SDL_GetKeyState( NULL);
345 SDL_Delay( 10);
347 s->was_stopped = true;
350 if( keys[SDLK_ESCAPE] || keys['q']) return true;
352 limit_value( &s->player1.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
353 limit_value( &s->player2.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
354 limit_value( &s->ball.jump, BALL_JUMP_MIN, BALL_JUMP_MAX);
356 /* Update ball_dest for debugging purposes */
357 get_move_y(s, 1);
358 get_move_y(s, 2);
360 s->ball.x += s->ball.move_x;
361 s->ball.y += s->ball.move_y;
363 if(s->player1.state) s->player1.state--;
364 if(s->player2.state) s->player2.state--;
366 return false;
369 void render( GameState* s) {
370 int i, x, y;
371 if (s->play_sound != SOUND_MAX) {
372 play_sample(s->play_sound);
373 s->play_sound = SOUND_MAX;
375 if( s->winner != WINNER_NONE) {
376 if( !s->is_over) {
377 start_fade();
378 s->is_over = true;
380 clear_screen();
381 store_screen();
382 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);
383 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
384 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);
385 updatescr();
386 return;
388 if (s->old_court_type != s->court_type) {
389 clear_screen();
390 fill_image(s->court_type, 120, 120, 400, 250);
391 show_image(GR_COURT, 0, 0, 255);
392 s->old_court_type = s->court_type;
393 store_screen();
395 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
396 show_image( GR_SHADOW, s->ball.x-BALL_X_MID, s->ball.y + get_phase( s) - BALL_Y_MID, 255);
398 show_sprite( GR_RACKET, (!s->player1.state), 4, s->player1.x-RACKET_X_MID, s->player1.y-RACKET_Y_MID, 255);
399 show_sprite( GR_RACKET, (!s->player2.state)+2, 4, s->player2.x-RACKET_X_MID, s->player2.y-RACKET_Y_MID, 255);
401 if( s->ball.move_x > 0) {
402 show_sprite( GR_BALL, (s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
403 } else if( s->ball.move_x < 0) {
404 show_sprite( GR_BALL, 3-(s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
405 } else {
406 show_sprite( GR_BALL, 0, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
409 /* Player 1's mouse rectangle */
410 if (!(s->player1.mouse_locked)) {
411 rectangle(s->player1.x-2+5, s->player1.mouse_y-2, 4, 4, 255, 255, 255);
412 rectangle(s->player1.x-1+5, s->player1.mouse_y-1, 2, 2, 0, 0, 0);
415 font_draw_string( GR_DKC2_FONT, s->game_score_str, 14, 14, 0, ANIMATION_NONE);
416 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);
418 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);
420 for (i=0; i<s->rain; i++) {
421 x = rand()%WIDTH;
422 y = rand()%HEIGHT;
423 draw_line_faded(x, y, x+10, y+30, 0, 0, 255, 100, 200, 255);
425 if (s->rain) {
427 * Cheap-ish update of the whole screen. This can
428 * probably be optimized.
430 update_rect(0, 0, WIDTH, HEIGHT);
433 #ifdef DEBUG
434 line_horiz( s->player1.y, 255, 0, 0);
435 line_horiz( s->player2.y, 0, 0, 255);
436 line_horiz( s->ball.y, 0, 255, 0);
438 line_vert( s->player1.x, 255, 0, 0);
439 line_vert( s->player2.x, 0, 0, 255);
440 line_vert( s->ball.x, 0, 255, 0);
442 line_horiz( s->player1.ball_dest, 255, 0, 255);
443 line_horiz( s->player2.ball_dest, 0, 255, 255);
445 line_horiz( GAME_Y_MIN, 100, 100, 100);
446 line_horiz( GAME_Y_MAX, 100, 100, 100);
447 #endif
448 switch (s->fog) {
449 default:
450 case 4:
451 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/150, 0);
452 case 3:
453 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, -s->time/100, 20);
454 case 2:
455 fill_image_offset(GR_FOG2, 0, 0, WIDTH, HEIGHT, -s->time/180, 80);
456 case 1:
457 fill_image_offset(GR_FOG, 0, 0, WIDTH, HEIGHT, s->time/200, 0);
458 case 0:
459 break;
461 if (s->night) {
462 show_image(GR_NIGHT, 0, 0, 255);
465 updatescr();
468 void limit_value( float* value, float min, float max) {
469 if( *value < min) {
470 *value = min;
471 } else if( *value > max) {
472 *value = max;
476 float get_phase( GameState* s) {
477 float pos, fract;
478 float x, min, max, direction;
480 x = s->ball.x;
481 min = GAME_X_MIN;
482 max = GAME_X_MAX;
483 direction = s->ball.move_x;
485 pos = (direction>0)?(1-GROUND_PHASE):(GROUND_PHASE);
487 fract = (x-min)/(max-min);
489 if( fract < pos) {
490 fract = fract/pos;
491 return fabsf( cosf(PI*fract/2))*PHASE_AMP*s->ball.jump;
492 } else {
493 fract = (pos-fract)/(1-pos);
494 return fabsf( sinf(PI*fract/2))*PHASE_AMP*s->ball.jump;
498 float get_move_y( GameState* s, unsigned char player) {
499 float pct, dest, x_len, y_len;
500 float py, by, pa, move_x;
502 py = (player==1)?(s->player1.y):(s->player2.y);
503 by = s->ball.y;
504 pa = RACKET_Y_MID*2;
505 move_x = s->ball.move_x;
507 /* -1.0 .. 1.0 for racket hit position */
508 pct = (by-py)/(pa/2);
509 limit_value( &pct, -1.0, 1.0);
511 /* Y destination for ball */
512 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
513 if( player == 1) {
514 s->player1.ball_dest = dest;
515 } else {
516 s->player2.ball_dest = dest;
519 /* lengths for the ball's journey */
520 if( player == 1) {
521 x_len = fabsf(GAME_X_MAX - s->ball.x);
522 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
523 } else {
524 x_len = s->ball.x - GAME_X_MIN;
525 y_len = by - dest + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
528 /* return the should-be value for move_y */
529 return (y_len*move_x)/(x_len);
532 void input_human( Player* player, bool up, bool down, bool hit, bool use_mouse, GameState* s) {
533 int diff = PLAYER_MOVE_Y;
534 int mb;
537 * Only use mouse control if the user isn't pressing any buttons
538 * this way, keyboard control still works when mouse control is
539 * enabled.
541 if (use_mouse && (down || up || hit)) {
543 * this is here so if the user decides to play
544 * with keyboard controls, we will lock the
545 * mouse and disable displaying the mouse cursor
547 player->mouse_locked = true;
549 if (use_mouse && !down && !up && !hit) {
550 mb = SDL_GetMouseState(&(player->mouse_x), &(player->mouse_y));
551 if (mb&SDL_BUTTON(SDL_BUTTON_LEFT)) {
552 if (player->mouse_y < player->y) {
553 down = false;
554 up = true;
555 diff = (player->y-player->mouse_y<diff)?(player->y-player->mouse_y):(diff);
556 } else if (player->mouse_y > player->y) {
557 up = false;
558 down = true;
559 diff = (player->mouse_y-player->y<diff)?(player->mouse_y-player->y):(diff);
561 player->mouse_locked = false;
562 } else if (!player->mouse_locked) {
563 hit = true;
567 if (fabsf(s->joystick_y) > JOYSTICK_TRESHOLD) {
568 diff = PLAYER_MOVE_Y*fabsf(s->joystick_y)/40.0;
569 if (diff > PLAYER_MOVE_Y) {
570 diff = PLAYER_MOVE_Y;
574 if (up) {
575 player->y -= fminf(diff, diff*player->accelerate);
576 player->accelerate *= PLAYER_ACCEL_INCREASE;
577 } else if (down) {
578 player->y += fminf(diff, diff*player->accelerate);
579 player->accelerate *= PLAYER_ACCEL_INCREASE;
580 } else {
581 player->accelerate = PLAYER_ACCEL_DEFAULT;
584 if( hit) {
585 if( !player->state && !player->state_locked) {
586 player->state = PLAYER_STATE_MAX;
587 player->state_locked = true;
589 } else {
590 player->state_locked = false;
594 void input_ai( Player* player, Ball* ball, Player* opponent, GameState* s) {
595 float fact = 1.7;
596 float target;
598 if( fabsf( player->y - ball->y) > RACKET_Y_MID*5) {
599 fact = 3.5;
602 target = GAME_Y_MID + (opponent->ball_dest - GAME_Y_MID)/5;
604 if( player->responsible) {
605 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, ball->y)) {
606 if( player->y < ball->y) {
607 player->y += fmin( 2*fact, ball->y - player->y);
608 } else if( player->y > ball->y) {
609 player->y -= fmin( 2*fact, player->y - ball->y);
613 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) {
614 player->state = PLAYER_STATE_MAX;
616 } else if( ball->move_x == 0) {
617 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
618 if( player->y < target) {
619 player->y += fmin( fact, (target-player->y)/40.0);
620 } else if( player->y > target) {
621 player->y -= fmin( fact, (player->y-target)/40.0);
624 } else if( s->ngram_prediction > 0.0) {
625 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
626 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
628 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
629 if( player->y < target) {
630 player->y += fmin( fact, (target-player->y)/40.0);
631 } else if( player->y > target) {
632 player->y -= fmin( fact, (player->y-target)/40.0);
635 } else {/*
636 if( player->desire == DESIRE_NORMAL) {
637 if( !IS_NEAR_Y_AI( player->y, target)) {
638 player->y += (target - player->y)/40.0;
644 void game_setup_serve( GameState* s) {
645 s->ball.jump = 7.5;
646 s->ball.y = GAME_Y_MID;
647 s->ball.move_x = 0.0;
648 s->ball.move_y = 0.0;
650 if( s->player1_serves) {
651 s->player1.responsible = true;
652 s->player1.ball_dest = 0.0;
653 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
654 } else {
655 s->player1.responsible = false;
656 s->player2.ball_dest = 0.0;
657 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
660 s->player2.responsible = !(s->player1.responsible);
663 float ngram_predictor( GameState* s) {
664 unsigned int count = 0;
665 unsigned long sum = 0;
666 int x, y, z;
667 float result;
669 if( s->history_size < 3) {
670 return 0.0;
673 x = s->history[1];
674 y = s->history[2];
676 for( z = 0; z<NGRAM_STEPS; z++) {
677 count += s->ngram[x][y][z];
678 sum += z * s->ngram[x][y][z];
681 result = ((float)(sum))/((float)(count));
682 #ifdef DEBUG
683 printf( "predicting next = %.2f\n", result);
684 #endif
686 return result;
689 void score_game( GameState* s, bool player1_scored) {
690 Player* winner = (player1_scored)?(&(s->player1)):(&(s->player2));
691 Player* loser = (player1_scored)?(&(s->player2)):(&(s->player1));
693 if( s->current_set >= SETS_TO_WIN*2-1) {
694 return;
697 winner->game++;
698 if( loser->game < winner->game-1) {
699 if( winner->game >= 4) {
700 winner->game = loser->game = 0;
701 winner->sets[s->current_set]++;
702 /* scoring the set.. */
703 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
704 winner->sets[s->current_set] == 7) {
705 s->current_set++;
706 s->winner = game_get_winner( s);
712 char* format_sets( GameState* s) {
713 static char sets[100];
714 static char tmp[100];
715 int i, max = s->current_set;
717 sets[0] = '\0';
719 if( s->winner != WINNER_NONE) {
720 max--;
722 for( i=0; i<=max; i++) {
723 sprintf( tmp, "%d:%d, ", s->player1.sets[i], s->player2.sets[i]);
724 strcat( sets, tmp);
727 sets[strlen(sets)-2] = '\0';
729 return sets;
732 char* format_game( GameState* s) {
733 static char game[100];
734 static const int game_scoring[] = { 0, 15, 30, 40 };
736 if( s->player1.game < 4 && s->player2.game < 4) {
737 sprintf( game, "%d - %d", game_scoring[s->player1.game], game_scoring[s->player2.game]);
738 } else if( s->player1.game > s->player2.game) {
739 strcpy( game, "advantage player 1");
740 } else if( s->player1.game < s->player2.game) {
741 strcpy( game, "advantage player 2");
742 } else {
743 strcpy( game, "deuce");
746 return game;
749 char* format_status( GameState* s) {
750 static char status[100];
751 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
753 sprintf( status, "%d:%d in %s set", s->player1.sets[s->current_set], s->player2.sets[s->current_set], set_names[s->current_set]);
755 return status;
758 int game_get_winner( GameState* s) {
759 int i;
760 int sets[2] = {0};
762 for( i=0; i<s->current_set; i++) {
763 if( s->player1.sets[i] > s->player2.sets[i]) {
764 sets[0]++;
765 } else {
766 sets[1]++;
770 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
771 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
773 return WINNER_NONE;