Smoothen player racket movement (accelerate)
[tennix.git] / game.c
blob51e5d7ff47e2a35fd3757ead8001d934d9a27058
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, 0, 0, PLAYER_ACCEL_DEFAULT },
38 { GAME_X_MAX+RACKET_X_MID*2, GAME_Y_MID, 0, 0, 0, DESIRE_NORMAL, PLAYER_TYPE_HUMAN, false, 0, 0, PLAYER_ACCEL_DEFAULT },
42 true,
43 "welcome to tennix " VERSION,
44 { 0 },
45 { 0 },
46 REFEREE_NORMAL,
48 WINNER_NONE,
49 false,
50 GR_CTT_GRASS,
51 -1,
52 { 0 },
54 false,
55 { { { 0 } } },
56 0.0,
57 SOUND_MAX,
58 0.0,
59 0.0,
63 strcpy( s.game_score_str, format_game( &s));
64 strcpy( s.sets_score_str, format_sets( &s));
66 Uint32 ot = SDL_GetTicks();
67 Uint32 nt;
68 Uint32 dt = GAME_TICKS;
69 Uint32 diff;
70 Uint32 accumulator = 0;
71 bool quit = false;
72 int x, y, z;
74 #ifdef ENABLE_FPS_LIMIT
75 Uint32 ft, frames; /* frame timer and frames */
76 #endif
78 if( singleplayer) {
79 #ifdef DEBUG
80 s.player1.type = PLAYER_TYPE_AI;
81 #endif
82 s.player2.type = PLAYER_TYPE_AI;
85 game_setup_serve( &s);
87 /* smoothen n-gram */
88 for( x = 0; x<NGRAM_STEPS; x++) {
89 for( y = 0; y<NGRAM_STEPS; y++) {
90 for( z = 0; z<NGRAM_STEPS; z++) {
91 s.ngram[x][y][z] = 1;
96 #ifdef ENABLE_FPS_LIMIT
97 frames = 0;
98 ft = SDL_GetTicks();
99 #endif
100 while( !quit) {
101 nt = SDL_GetTicks();
102 diff = nt-ot;
103 if( diff > 2000) {
104 diff = 0;
107 accumulator += diff;
108 ot = nt;
110 while( accumulator >= dt) {
111 quit = step( &s);
112 s.time += dt;
113 accumulator -= dt;
115 if( s.was_stopped) {
116 ot = SDL_GetTicks();
117 s.was_stopped = false;
121 #ifdef ENABLE_FPS_LIMIT
122 while (frames*1000.0/((float)(SDL_GetTicks()-ft+1))>(float)(DEFAULT_FPS)) {
123 SDL_Delay(10);
125 frames++;
126 #endif
128 render( &s);
133 bool step( GameState* s) {
134 Uint8 *keys;
135 SDL_Event e;
137 if( get_phase( s) < 1.0) {
138 if( !s->ground.jump) {
139 s->play_sound = SOUND_GROUND;
141 if( IS_OUT_Y( s->ball.y)) {
142 /* out - responsibilities stay the same */
143 s->status = "out!";
144 s->play_sound = SOUND_OUT;
145 s->referee = REFEREE_OUT;
146 } else {
147 /* not out - responsibilities change */
148 s->player1.responsible = !(s->player2.responsible = !s->player2.responsible);
149 s->status = format_status( s);
150 s->referee = REFEREE_NORMAL;
153 s->ground.jump = 3;
154 s->ground.x = s->ball.x;
155 s->ground.y = s->ball.y;
156 } else {
157 if( s->ground.jump && !(s->time%5)) s->ground.jump--;
160 if( IS_OUT_X(s->ball.x) || IS_OFFSCREEN_Y(s->ball.y)) {
161 if( IS_OFFSCREEN( s->ball.x, s->ball.y)) {
162 s->player1_serves = s->player1.responsible;
164 score_game( s, s->player2.responsible);
165 strcpy( s->game_score_str, format_game( s));
166 strcpy( s->sets_score_str, format_sets( s));
168 if( s->player1.responsible) {
169 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
170 s->status = "computer scores";
171 } else {
172 s->status = "player 2 scores";
174 s->referee = REFEREE_PLAYER2;
175 } else {
176 if( s->player1.type == PLAYER_TYPE_HUMAN && s->player2.type == PLAYER_TYPE_AI) {
177 s->status = "player scores";
178 } else {
179 s->status = "player 1 scores";
181 s->referee = REFEREE_PLAYER1;
184 game_setup_serve( s);
185 s->play_sound = SOUND_APPLAUSE;
186 SDL_Delay( 500);
187 start_fade();
188 s->was_stopped = true;
189 s->history_size = 0;
190 s->history_is_locked = 0;
191 s->ngram_prediction = 0.0;
192 #ifdef DEBUG
193 printf( "-- game reset --\n");
194 #endif
197 if( IS_OUT_X(s->ball.x)) {
198 if( !s->history_is_locked && s->referee != REFEREE_OUT) {
199 s->history[s->history_size] = (int)(NGRAM_STEPS*s->ball.y/HEIGHT);
200 s->history_size++;
201 if( s->history_size == 3) {
202 s->ngram[s->history[0]][s->history[1]][s->history[2]] += 10;
203 #ifdef DEBUG
204 printf( "history: %d, %d, %d\n", s->history[0], s->history[1], s->history[2]);
205 #endif
206 s->ngram_prediction = ngram_predictor( s);
207 s->history[0] = s->history[1];
208 s->history[1] = s->history[2];
209 s->history_size--;
211 s->history_is_locked = true;
213 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) {
214 s->ball.x = GAME_X_MIN;
215 if( s->player1.state == PLAYER_STATE_MAX) {
216 s->ball.move_x = PLAYER_POWERSHOT;
217 } else {
218 s->ball.move_x = 2.5 + 2.0*s->player1.state/PLAYER_STATE_MAX;
220 s->ball.move_y = get_move_y( s, 1);
221 s->player2.responsible = !(s->player1.responsible = 1);
222 s->ball.jump += 1.0-2.0*(s->player1.state<5);
223 s->play_sound = SOUND_RACKET;
224 pan_sample(SOUND_RACKET, 0.4);
225 } 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) {
226 s->ball.x = GAME_X_MAX;
227 if( s->player2.state == PLAYER_STATE_MAX) {
228 s->ball.move_x = -PLAYER_POWERSHOT;
229 } else {
230 s->ball.move_x = -(2.5 + 2.0*s->player2.state/PLAYER_STATE_MAX);
232 s->ball.move_y = get_move_y( s, 2);
233 s->player1.responsible = !(s->player2.responsible = 1);
234 s->ball.jump += 1.0-2.0*(s->player2.state<5);
235 s->play_sound = SOUND_RACKET;
236 pan_sample(SOUND_RACKET, 0.6);
239 } else {
240 s->history_is_locked = false;
243 /* Update ball_dest for debugging purposes */
244 get_move_y( s, 1);
245 get_move_y( s, 2);
247 s->ball.x += s->ball.move_x;
248 s->ball.y += s->ball.move_y;
250 if( s->player1.state) s->player1.state--;
251 if( s->player2.state) s->player2.state--;
253 SDL_PollEvent( &e);
254 keys = SDL_GetKeyState( NULL);
255 switch(e.type) {
256 case SDL_JOYAXISMOTION:
257 if (e.jaxis.axis == JOYSTICK_Y_AXIS) {
258 s->joystick_y = JOYSTICK_PERCENTIZE(e.jaxis.value);
259 } else if (e.jaxis.axis == JOYSTICK_X_AXIS) {
260 s->joystick_x = JOYSTICK_PERCENTIZE(e.jaxis.value);
262 break;
263 case SDL_JOYBUTTONUP: case SDL_JOYBUTTONDOWN:
264 if (e.jbutton.button == JOYSTICK_BUTTON_A) {
265 s->joystick_a = (e.jbutton.state == SDL_PRESSED);
267 break;
270 if( keys['c'] && s->time%50==0) {
271 s->court_type++;
272 if( s->court_type > GR_CTT_LAST) {
273 s->court_type = GR_CTT_FIRST;
277 if( !is_fading() && !s->is_over) {
278 if( s->player1.type == PLAYER_TYPE_HUMAN) {
279 input_human( &s->player1,
280 keys['w'] || keys[SDLK_UP] || s->joystick_y < -JOYSTICK_TRESHOLD,
281 keys['s'] || keys[SDLK_DOWN] || s->joystick_y > JOYSTICK_TRESHOLD,
282 keys['d'] || keys[SDLK_SPACE] || keys[SDLK_LCTRL] || s->joystick_a,
283 ENABLE_MOUSE,
285 } else {
286 input_ai( &s->player1, &s->ball, &s->player2, s);
289 if( s->player2.type == PLAYER_TYPE_HUMAN) {
290 input_human( &s->player2, keys['o'], keys['l'], keys['k'], false, s);
291 } else {
292 input_ai( &s->player2, &s->ball, &s->player1, s);
296 if( keys['f']) SDL_WM_ToggleFullScreen( screen);
297 if( keys['y']) SDL_SaveBMP( screen, "screenshot.bmp");
299 if( keys['p']) {
300 while( keys['p']) {
301 SDL_PollEvent( &e);
302 keys = SDL_GetKeyState( NULL);
303 SDL_Delay( 10);
305 while( keys['p'] == 0) {
306 SDL_PollEvent( &e);
307 keys = SDL_GetKeyState( NULL);
308 SDL_Delay( 10);
310 while( keys['p']) {
311 SDL_PollEvent( &e);
312 keys = SDL_GetKeyState( NULL);
313 SDL_Delay( 10);
315 s->was_stopped = true;
318 if( keys[SDLK_ESCAPE] || keys['q']) return true;
320 limit_value( &s->player1.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
321 limit_value( &s->player2.y, PLAYER_Y_MIN, PLAYER_Y_MAX);
322 limit_value( &s->ball.jump, BALL_JUMP_MIN, BALL_JUMP_MAX);
324 return false;
327 void render( GameState* s) {
328 if (s->play_sound != SOUND_MAX) {
329 play_sample(s->play_sound);
330 s->play_sound = SOUND_MAX;
332 if( s->winner != WINNER_NONE) {
333 if( !s->is_over) {
334 start_fade();
335 s->is_over = true;
337 clear_screen();
338 store_screen();
339 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);
340 sprintf( s->game_score_str, "player %d wins the match with %s", s->winner, format_sets( s));
341 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);
342 updatescr();
343 return;
345 if (s->old_court_type != s->court_type) {
346 clear_screen();
347 fill_image(s->court_type, 120, 120, 400, 250);
348 show_image(GR_COURT, 0, 0, 255);
349 s->old_court_type = s->court_type;
350 store_screen();
352 show_sprite( GR_REFEREE, s->referee, 4, 250, 10, 255);
353 show_image( GR_SHADOW, s->ball.x-BALL_X_MID, s->ball.y + get_phase( s) - BALL_Y_MID, 255);
355 show_sprite( GR_RACKET, (!s->player1.state), 4, s->player1.x-RACKET_X_MID, s->player1.y-RACKET_Y_MID, 255);
356 show_sprite( GR_RACKET, (!s->player2.state)+2, 4, s->player2.x-RACKET_X_MID, s->player2.y-RACKET_Y_MID, 255);
358 if( s->ground.jump) {
359 show_sprite( GR_GROUND, s->ground.jump-1, 3, s->ground.x - BALL_X_MID, s->ground.y - BALL_Y_MID, 128);
362 if( s->ball.move_x > 0) {
363 show_sprite( GR_BALL, (s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
364 } else if( s->ball.move_x < 0) {
365 show_sprite( GR_BALL, 3-(s->time/500)%4, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
366 } else {
367 show_sprite( GR_BALL, 0, 4, s->ball.x-BALL_X_MID, s->ball.y-BALL_Y_MID, 255);
371 font_draw_string( GR_DKC2_FONT, s->game_score_str, 14, 14, 0, ANIMATION_NONE);
372 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);
374 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);
376 #ifdef DEBUG
377 line_horiz( s->player1.y, 255, 0, 0);
378 line_horiz( s->player2.y, 0, 0, 255);
379 line_horiz( s->ball.y, 0, 255, 0);
381 line_vert( s->player1.x, 255, 0, 0);
382 line_vert( s->player2.x, 0, 0, 255);
383 line_vert( s->ball.x, 0, 255, 0);
385 line_horiz( s->player1.ball_dest, 255, 0, 255);
386 line_horiz( s->player2.ball_dest, 0, 255, 255);
388 line_horiz( GAME_Y_MIN, 100, 100, 100);
389 line_horiz( GAME_Y_MAX, 100, 100, 100);
390 #endif
392 updatescr();
395 void limit_value( float* value, float min, float max) {
396 if( *value < min) {
397 *value = min;
398 } else if( *value > max) {
399 *value = max;
403 float get_phase( GameState* s) {
404 float pos, fract;
405 float x, min, max, direction;
407 x = s->ball.x;
408 min = GAME_X_MIN;
409 max = GAME_X_MAX;
410 direction = s->ball.move_x;
412 pos = (direction>0)?(1-GROUND_PHASE):(GROUND_PHASE);
414 fract = (x-min)/(max-min);
416 if( fract < pos) {
417 fract = fract/pos;
418 return fabsf( cosf(PI*fract/2))*PHASE_AMP*s->ball.jump;
419 } else {
420 fract = (pos-fract)/(1-pos);
421 return fabsf( sinf(PI*fract/2))*PHASE_AMP*s->ball.jump;
425 float get_move_y( GameState* s, unsigned char player) {
426 float pct, dest, x_len, y_len;
427 float py, by, pa, move_x;
429 py = (player==1)?(s->player1.y):(s->player2.y);
430 by = s->ball.y;
431 pa = RACKET_Y_MID*2;
432 move_x = s->ball.move_x;
434 /* -1.0 .. 1.0 for racket hit position */
435 pct = (by-py)/(pa/2);
436 limit_value( &pct, -1.0, 1.0);
438 /* Y destination for ball */
439 dest = GAME_Y_MID + pct*(GAME_Y_MAX-GAME_Y_MIN);
440 if( player == 1) {
441 s->player1.ball_dest = dest;
442 } else {
443 s->player2.ball_dest = dest;
446 /* lengths for the ball's journey */
447 if( player == 1) {
448 x_len = fabsf(GAME_X_MAX - s->ball.x);
449 y_len = dest - by + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
450 } else {
451 x_len = s->ball.x - GAME_X_MIN;
452 y_len = by - dest + MOVE_Y_SEED-rand()%MOVE_Y_SEED*2;
455 /* return the should-be value for move_y */
456 return (y_len*move_x)/(x_len);
459 void input_human( Player* player, bool up, bool down, bool hit, bool use_mouse, GameState* s) {
460 int diff = PLAYER_MOVE_Y;
461 int mb;
463 if (use_mouse) {
464 mb = SDL_GetMouseState(&(player->mouse_x), &(player->mouse_y));
465 if (mb&SDL_BUTTON(SDL_BUTTON_LEFT)) {
466 hit = true;
468 if (player->mouse_y < player->y) {
469 down = false;
470 up = true;
471 diff = (player->y-player->mouse_y<diff)?(player->y-player->mouse_y):(diff);
472 } else if (player->mouse_y > player->y) {
473 up = false;
474 down = true;
475 diff = (player->mouse_y-player->y<diff)?(player->mouse_y-player->y):(diff);
479 if (fabsf(s->joystick_y) > JOYSTICK_TRESHOLD) {
480 diff = PLAYER_MOVE_Y*fabsf(s->joystick_y)/40.0;
481 if (diff > PLAYER_MOVE_Y) {
482 diff = PLAYER_MOVE_Y;
486 if (up) {
487 player->y -= fminf(diff, diff*player->accelerate);
488 player->accelerate *= PLAYER_ACCEL_INCREASE;
489 } else if (down) {
490 player->y += fminf(diff, diff*player->accelerate);
491 player->accelerate *= PLAYER_ACCEL_INCREASE;
492 } else {
493 player->accelerate = PLAYER_ACCEL_DEFAULT;
496 if( hit) {
497 if( !player->state && !player->state_locked) {
498 player->state = PLAYER_STATE_MAX;
499 player->state_locked = true;
501 } else {
502 player->state_locked = false;
506 void input_ai( Player* player, Ball* ball, Player* opponent, GameState* s) {
507 float fact = 1.7;
508 float target;
510 if( fabsf( player->y - ball->y) > RACKET_Y_MID*5) {
511 fact = 3.5;
514 target = GAME_Y_MID + (opponent->ball_dest - GAME_Y_MID)/5;
516 if( player->responsible) {
517 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, ball->y)) {
518 if( player->y < ball->y) {
519 player->y += fmin( 2*fact, ball->y - player->y);
520 } else if( player->y > ball->y) {
521 player->y -= fmin( 2*fact, player->y - ball->y);
525 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) {
526 player->state = PLAYER_STATE_MAX;
528 } else if( ball->move_x == 0) {
529 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
530 if( player->y < target) {
531 player->y += fmin( fact, (target-player->y)/40.0);
532 } else if( player->y > target) {
533 player->y -= fmin( fact, (player->y-target)/40.0);
536 } else if( s->ngram_prediction > 0.0) {
537 target = s->ngram_prediction*((float)HEIGHT)/((float)(NGRAM_STEPS));
538 target = GAME_Y_MID + (target-GAME_Y_MID)*1.5;
540 if( player->desire == DESIRE_NORMAL && !IS_NEAR_Y_AI( player->y, target)) {
541 if( player->y < target) {
542 player->y += fmin( fact, (target-player->y)/40.0);
543 } else if( player->y > target) {
544 player->y -= fmin( fact, (player->y-target)/40.0);
547 } else {/*
548 if( player->desire == DESIRE_NORMAL) {
549 if( !IS_NEAR_Y_AI( player->y, target)) {
550 player->y += (target - player->y)/40.0;
556 void game_setup_serve( GameState* s) {
557 s->ball.jump = 7.5;
558 s->ball.y = GAME_Y_MID;
559 s->ball.move_x = 0.0;
560 s->ball.move_y = 0.0;
562 if( s->player1_serves) {
563 s->player1.responsible = true;
564 s->player1.ball_dest = 0.0;
565 s->ball.x = GAME_X_MIN-RACKET_X_MID*1.5;
566 } else {
567 s->player1.responsible = false;
568 s->player2.ball_dest = 0.0;
569 s->ball.x = GAME_X_MAX+RACKET_X_MID*1.5;
572 s->player2.responsible = !(s->player1.responsible);
575 float ngram_predictor( GameState* s) {
576 unsigned int count = 0;
577 unsigned long sum = 0;
578 int x, y, z;
579 float result;
581 if( s->history_size < 3) {
582 return 0.0;
585 x = s->history[1];
586 y = s->history[2];
588 for( z = 0; z<NGRAM_STEPS; z++) {
589 count += s->ngram[x][y][z];
590 sum += z * s->ngram[x][y][z];
593 result = ((float)(sum))/((float)(count));
594 #ifdef DEBUG
595 printf( "predicting next = %.2f\n", result);
596 #endif
598 return result;
601 void score_game( GameState* s, bool player1_scored) {
602 Player* winner = (player1_scored)?(&(s->player1)):(&(s->player2));
603 Player* loser = (player1_scored)?(&(s->player2)):(&(s->player1));
605 if( s->current_set >= SETS_TO_WIN*2-1) {
606 return;
609 winner->game++;
610 if( loser->game < winner->game-1) {
611 if( winner->game >= 4) {
612 winner->game = loser->game = 0;
613 winner->sets[s->current_set]++;
614 /* scoring the set.. */
615 if( (winner->sets[s->current_set] == 6 && loser->sets[s->current_set] < 5) ||
616 winner->sets[s->current_set] == 7) {
617 s->current_set++;
618 s->winner = game_get_winner( s);
624 char* format_sets( GameState* s) {
625 static char sets[100];
626 static char tmp[100];
627 int i, max = s->current_set;
629 sets[0] = '\0';
631 if( s->winner != WINNER_NONE) {
632 max--;
634 for( i=0; i<=max; i++) {
635 sprintf( tmp, "%d:%d, ", s->player1.sets[i], s->player2.sets[i]);
636 strcat( sets, tmp);
639 sets[strlen(sets)-2] = '\0';
641 return sets;
644 char* format_game( GameState* s) {
645 static char game[100];
646 static const int game_scoring[] = { 0, 15, 30, 40 };
648 if( s->player1.game < 4 && s->player2.game < 4) {
649 sprintf( game, "%d - %d", game_scoring[s->player1.game], game_scoring[s->player2.game]);
650 } else if( s->player1.game > s->player2.game) {
651 strcpy( game, "advantage player 1");
652 } else if( s->player1.game < s->player2.game) {
653 strcpy( game, "advantage player 2");
654 } else {
655 strcpy( game, "deuce");
658 return game;
661 char* format_status( GameState* s) {
662 static char status[100];
663 static const char* set_names[] = { "first", "second", "third", "fourth", "fifth" };
665 sprintf( status, "%d:%d in %s set", s->player1.sets[s->current_set], s->player2.sets[s->current_set], set_names[s->current_set]);
667 return status;
670 int game_get_winner( GameState* s) {
671 int i;
672 int sets[2] = {0};
674 for( i=0; i<s->current_set; i++) {
675 if( s->player1.sets[i] > s->player2.sets[i]) {
676 sets[0]++;
677 } else {
678 sets[1]++;
682 if( sets[0] == SETS_TO_WIN) return WINNER_PLAYER1;
683 if( sets[1] == SETS_TO_WIN) return WINNER_PLAYER2;
685 return WINNER_NONE;