/* This is "apool.c", main-fragment of the pool (billiards)-program
                   
                     "Another Pool GL".

   Copyright (C) 1995,2002 by Gerrit Jahn (http://www.planetjahn.de)

	 This file ist part of Another Pool / Another Pool GL (apool, apoolGL).

   "Another Pool" is free software; you can redistribute it 
   and/or modify it under the terms of the GNU General Public License 
   as published by the Free Software Foundation; either version 2 of 
   the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU CC; see the file COPYING.  If not, write to
   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* ------------------------------ apool.c -------------------------------- */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include <getopt.h>
#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include "apool.h"

int SCREENRESX=1024, SCREENRESY=768, GLOPT;

double ALPHA, BETA, GAMMA, DELTA, ETA, BANDES, BANDEREF, BANDEZ,
 SPINC, MASS_WHITE;
double CURVES, CURVEP; /* eigentlich Konstanten, werden es später wieder !!! */
int TIME_STEP;
int first_hit, bande_hit, col_in, last_foul, freeball, extra_shot, ende,
  STEP, CLEV, cur=1, new_col, c_player=-1, anstoss, demo = 0,
  undo_freeball, undo_extra_shot, undo_cur, last_anstoss=0,
  last_pocketed_balls, undo_ply_stat[2],undo_ply_wait[2],undo_ply_points[2],
  undo_ply_color[2], undo_ply_hole_black[2],undo_ply_speed[2];
double undo_spd, undo_wink, undo_ez;
struct vect3 undo_e;

struct ball k[BALLS];
struct player ply[2];
FILE *datei;
struct vect3 undo_pos[BALLS], undo_rot[BALLS]; 
unsigned char undo_stat[BALLS], undo_nopaint[BALLS], undo_no[BALLS], undo_col[BALLS], undo_pocket[BALLS]; 
int undo_gamemode;
struct statistic stats[2] = {{{0,0,0,0},0,0,0,0},{{0,0,0,0},0,0,0,-1}};

void init_table( void ) 
/* Hier wird alles, was es so an Variablen bezüglich Lage der Kugeln gibt, 
   neu initialisiert. init_table() wird immer beim Neustart aufgerufen */
 { 
  int i, j, l=0, nfull=1, nhalf=9;
  FILE *dat;
  static int poly_x[4], poly_y[4];
  double dummy, dummy2[4];
  struct vect3 n;
  frames_per_second=0.0;
  for(i=0;i<5;i++) 
   for(j=-i;j<=i;j+=2)		/* Lage der Kugeln am Anfang */
    { 
     k[l].p.x = 0.75+((i)*XAB/DIFFX) + 0.25 * RND / DIFFX;
     k[l].p.y = 0.25+j/2.0*((2.0*RADIUS+2.0/DIFFX)) + 0.25 * RND / DIFFX;
     k[l].p.z = RADIUS;
     k[l].v.x = k[l].v.y = k[l].v.z = k[l].e.x = k[l].e.y = k[l].e.z = k[l].col = k[l].no = 0.0; 
     if( l != WHITE )  { k[l].rot.x = 360.0*rand()/(RAND_MAX); k[l].rot.y = 360.0*rand()/(RAND_MAX); k[l].rot.z = 0.0; }
     else   { k[l].rot.y = 90.0; k[l].rot.x = 0; k[l].rot.z = 0;}
     k[l].stat = ONTABLE; k[l++].m = 1.0;
     calc_rotation(l);
    }
  k[BLACK].col = COL_BLACK; l = 1;
  k[WHITE-5].col = COL_YELLOW; /* Kugeln in der Ecke haben unterschiedliche "Art" */
  k[WHITE-1].col = COL_RED;
  do			/* Farbe der Kugeln, zufallsgesteuert */
   { 
    while( (i = 15.0*rand()/(RAND_MAX) ) == BLACK );
    if( !k[i].col ) { k[i].col = COL_RED; l++; }
   }
  while( l < (BALLS-2)/2 );
  for( i=0;i<WHITE;i++ ) if( !k[i].col ) k[i].col = COL_YELLOW;
  for( l=0;l<BALLS;l++ )
   {
   clear_rotation_matrix(l);
   if( k[l].col == COL_RED ) k[l].no = nfull++;
   if( k[l].col == COL_YELLOW ) k[l].no = nhalf++;
   if( k[l].col == COL_BLACK ) k[l].no = 8;
   if( k[l].col == COL_WHITE ) k[l].no = 0;
   }
  k[WHITE].stat = DELETED; /* Weiße ist anfangs NICHT auf dem Tisch */
  k[WHITE].m = MASS_WHITE;
  k[WHITE].col = COL_WHITE;
  k[WHITE].p.x = 0.25;
  k[WHITE].p.y = 0.25;
  for( i=0;i<2;i++)
   { ply[i].stat = ply[i].wait = ply[i].col = ply[i].hole_black = 0; }
  first_hit = bande_hit = anstoss = 1;
  if( !(dat = fopen("table.dat","r")) ) 
    {
      close_graphics();
      printf("error: can't find the file 'table.dat'. \n");
      printf("create this file using 'apool -init 50' ...\n\n");
      exit(0);
    }
  for(i=0;i<6;i++) /* Teilkreise der Taschen malen */
    {
      double RADIUSL;
      fscanf(dat,"%lg",&RADIUSL );
      for(j=0;j<2;j++) fscanf(dat,"%lg",&dummy2[j]);
      posl[i].r = RADIUSL;
      posl[i].p.x = dummy2[0] / DIFFX;
      posl[i].p.y = dummy2[1] / DIFFX;
    }
  counter=0;
  for(i=0;i<6;i++)  /* Banden initialisieren*/
    {
      for(j=0;j<4;j++)
	{ 
	  fscanf(dat,"%lg",&dummy); poly_x[j] = LEFT + (int)dummy; 
	  fscanf(dat,"%lg",&dummy); poly_y[j] = UP + (int)dummy; 
	}
      for( j=1;j<4;j++) /* Punkte den Banden zuweisen und Norm.vektoren berech. */
	{
	  banpixel[counter].p0.x = poly_x[j-1]-LEFT;
	  banpixel[counter].p0.y = poly_y[j-1]-UP;
	  banpixel[counter].p1.x = poly_x[j]-LEFT;
	  banpixel[counter].p1.y = poly_y[j]-UP;
	  ban[counter].p0.x = (poly_x[j-1]-LEFT)/DIFFX;
	  ban[counter].p0.y = (poly_y[j-1]-UP)/DIFFX;
	  ban[counter].p1.x = (poly_x[j]-LEFT)/DIFFX;
	  ban[counter].p1.y = (poly_y[j]-UP)/DIFFX;
	  /* "Den" zum "Richtungs-Vektor" der Bande senkrechten Vektor bestimmen */
	  n.y = banpixel[counter].p1.x-banpixel[counter].p0.x;   
	  n.x =  - ( banpixel[counter].p1.y-banpixel[counter].p0.y );   
	  /* Normieren */
	  banpixel[counter].n.x = n.x / ( dummy = BETR( n ) );
	  banpixel[counter].n.y = n.y / dummy;
	  ban[counter].n.x = n.x / ( dummy = BETR( n ) );
	  ban[counter].n.y = n.y / dummy;
	  n.x = ban[counter].p1.x - ban[counter].p0.x;
	  n.y = ban[counter].p1.y - ban[counter].p0.y;
	  ban[counter].v.x = n.x / ( dummy = BETR(n) );
	  ban[counter].v.y = n.y / dummy;
	  counter++;
	}
    }
  fclose(dat);
  calc_hot_spot();
  calc_e_winkel();
  ply[0].speed = ply[1].speed = spd = 0.75; 
  sprintf(current_message, "target mode - move mouse to change the position of the target ball");
  sprintf(current_message2,"F1: help; left button: cue, right button speed, ...");
  gl_init_lists();
  gl_plotall(-1);
  speed(spd, 1.0, 0.0, 0.0 );
  freeball = extra_shot = last_foul = col_in = 0;
  if( demo ) msg("press any key to stop demo");
 }
 
void save_current_position( void )
 { /* sichert die aktuelle Position der Kugeln, für undo() */
  int i;
  for( i=0;i<BALLS;i++ ) 
    { 
    undo_pos[i].x = k[i].p.x; 
    undo_pos[i].y = k[i].p.y; 
    undo_pos[i].z = k[i].p.z; 
    undo_stat[i] = k[i].stat; 
    undo_rot[i].x = k[i].rot.x;
    undo_rot[i].y = k[i].rot.y;
    undo_rot[i].z = k[i].rot.z;
    undo_col[i] = k[i].col;
    undo_pocket[i] = k[i].pocket;
    undo_nopaint[i] = k[i].nopaint;
    undo_no[i] = k[i].no;
    }
  undo_gamemode = gamemode;
  for(i=0;i<=1;i++)
   {
   undo_ply_stat[i] = ply[i].stat;
   undo_ply_wait[i] = ply[i].wait;
   undo_ply_points[i] = ply[i].points;
   undo_ply_color[i] = ply[i].col;
   undo_ply_hole_black[i] = ply[i].hole_black;
   undo_ply_speed[i] = ply[i].speed;
   }
  undo_cur = cur;
  undo_freeball = freeball;
  undo_extra_shot = extra_shot;
  undo_spd = spd;
  undo_wink = alph;
  undo_e.x = k[WHITE].e.x;
  undo_e.y = k[WHITE].e.y;
  undo_ez = k[WHITE].e.z;
  undo_target.x = target.x;
  undo_target.y = target.y;
 }
 
void undo( void )
 { /* naja, was wohl */
  int i;
  for( i=0;i<BALLS;i++ )
   {
    k[i].p.x = undo_pos[i].x; 
    k[i].p.y = undo_pos[i].y; 
    k[i].p.z = undo_pos[i].z; 
    k[i].stat = undo_stat[i] & (0xff ^ GL_LISTED_NOT_MOVING); /* damit nicht die alte gl-DisplayList zum Malen */
    k[i].rot.x = undo_rot[i].x;                                 /* benutzt, sondern alle gemalt wird */
    k[i].rot.y = undo_rot[i].y;
    k[i].rot.z = undo_rot[i].z;
    k[i].col = undo_col[i];
    k[i].pocket = undo_pocket[i];
    k[i].nopaint = undo_nopaint[i];
    k[i].no = undo_no[i];
    k[i].stat |= MOVING;
   }
/*   gamemode = undo_gamemode; */
  for(i=0;i<=1;i++)
   {
   ply[i].stat = undo_ply_stat[i];
   ply[i].wait = undo_ply_wait[i];
   ply[i].points = undo_ply_points[i];
   ply[i].col = undo_ply_color[i];
   ply[i].hole_black = undo_ply_hole_black[i];
   ply[i].speed = undo_ply_speed[i];
   }
  cur = undo_cur;
  spd = undo_spd;
  freeball = undo_freeball;
  extra_shot = undo_extra_shot;
  wink( alph = undo_wink );
  k[WHITE].e.x = undo_e.x;
  k[WHITE].e.y = undo_e.y;
  k[WHITE].e.z = undo_e.z;
 }

void test_for_play_on_black( void )
/* Wenn alle Kugeln einer Farbe verschwunden sind, wird auf die Schwarze
   gespielt. Wann das der Fall ist, und wer Schwarz spielen muß, ermittelt
   dies Prozedur */
 {
  int i, j;
  for( j=0;j<2;j++ )
   if( (ply[j].col != COL_BLACK) && (ply[j].col) ) 
    {
     ply[j].hole_black = 0;
     for(i=0;i<WHITE;i++)
      if( (k[i].stat & DELETED) && (k[i].col == ply[j].col) ) ply[j].hole_black++;
     if( ply[j].hole_black == (BALLS-2)/2 ) 
      {
       ply[j].col = COL_BLACK;
        /* ply[j].hole_black = last_hole gegenüber; */ /* SPÄTER !!! */
      }
    }
 } 
 
char *str_player( char *out2, int cur )
 { 
  sprintf(out2, "Player %d%s", cur+1, (c_player==cur) ? " (Computer)" : "");
  return out2;
 }

int test_if_game_is_over( void ) 
 { /* Wie der Name schon sagt, testet, ob Spiel zu Ende */
 char out[80], out2[30];
 int loser;
 if( k[BLACK].stat & DELETED )
  {
   if( !(ply[cur].stat & FOUL_ANY_FOUL) ) 
    {        /* Schwarze wurde KORREKT eingelocht */
     loser = 1 - cur;
     sprintf(out,"%s wins!", str_player( out2, cur) );
     ply[cur].points += 1;
     stats[cur].wins += 1;
     stats[cur].pots += 1;
    }
   else
    { 
     loser = cur;
     sprintf(out,"black ball illegaly pocketed; %s loses!",
      str_player( out2, cur ) );
     ply[cur].points += 10000;
     stats[cur].losses += 1;
     cur = 1 - cur;
    }
   gl_plotall(-1);
   err(out);
   if( loser != last_anstoss ) last_anstoss = loser;
   if( demo || c_player == loser ) { msg("wait..."); wait_user_time( 5.0 ); }
   else 
     {
       msg("press button for new game");
       SDL_GL_SwapBuffers();
       wait_for_click();
     }
   init_table();
   return 1;
  }
 return 0;
}
 
void rules( void )
/* Die Regeln, nach einem Stoß wird überprüft, ob keine Fouls aufgetreten
   sind. Sollte dies so sein, so erfolgen die "Strafen". Ruft die obigen
   Test-Prozeduren, das Menu und den Stoß auf ... */
 {
 int spielende=0, color; 
 char out[30];
 mouse_off();
 init_table();
 ply[0].points = ply[0].points = 0;
 do
  {
   first_hit = bande_hit =  0;
   if( ply[cur].stat & FOUL_WHITE_POCKETED ) /* hier kommen die Fouls... */
    {
     foul("white ball pocketed, foul!", 2500);
     last_foul = 1;
     stats[cur].fouls.whited += 1;
    }
   else if( ply[cur].stat & FOUL_NO_TOUCH )
    {
     foul("no ball or no rail touched, foul!", 2500);
     last_foul = 1;
     stats[cur].fouls.notouch += 1;
    }
   else if( ply[cur].stat & FOUL_WRONG_COLOR_POCKETED)
    {
     if( !freeball )
      {
       foul("wrong color pocketed, foul!", 2500);
       last_foul = 1;
       stats[cur].fouls.wrongcp += 1;
      }
     else { ply[1-cur].wait = 1; }
    }
   else if( ply[cur].stat & FOUL_WRONG_COLOR_TOUCHED )
    {
     if( !freeball )
      {
       foul("wrong color touched first, foul!", 2500);
       last_foul = 1;
       stats[cur].fouls.wrongct += 1;
      }
     else { extra_shot = 1; }
    }
   else if( !(ply[cur].stat & FOUL_ANY_FOUL) ) /* kein Foul gemacht */
    {
    if( ply[cur].stat & FOUL_CORRECT_POT )
     {
      last_foul = 0; /* Kugel eingelocht -> Yo! */
      stats[cur].pots += last_pocketed_balls;
     }
    else /* keine Kugel eingelocht */
     {
      stats[cur].nopots += 1;
      if( freeball && (ply[1-cur].wait == 1) ) { extra_shot = 1; ply[1-cur].wait = 0; }
      else cur = 1 - cur;
     }
    }
   if( col_in && last_foul ) { ply[cur].col = ply[1-cur].col = 0; }
   if( last_foul ) 
    { 
     ply[cur].wait = 1; cur = 1 - cur;
     if( ply[cur].col == COL_RED ) color = 4;
     else if( ply[cur].col == COL_YELLOW ) color = 11;
     else color = 6;
     sprintf(out,"Player %d!",cur+1);
     err2("Free ball for", out); 
     freeball = 1; 
    }
   else if( extra_shot )
    {
     ply[cur].wait = 0;
     freeball = 0;
     if( ply[cur].col == COL_RED ) color = 4;
     else if( ply[cur].col == COL_YELLOW ) color = 11;
     else color = 6;
     sprintf(out, "Player %d", cur+1);
     err2("Extra shot for", out);
    }
   else freeball = 0;
   new_col = col_in = last_foul = extra_shot = last_pocketed_balls = 0;
   ply[0].stat = ply[1].stat = 0;  /* eigentlich nur ply[cur].stat */
   if( demo ) c_player = cur; 
   if( c_player != cur )  spd = ply[cur].speed;
   if( !( spielende = menu() ) )  
    {
    save_current_position(); 
    gl_stoss(); /* HIER ROLLEN ERST D. KUGELN */
    }
   if( !test_if_game_is_over() ) test_for_play_on_black();   
   if( new_col & NEW_COL_NEW )
    {
    if( (new_col & NEW_COL_DOUBLE) /*&& (ply[cur].stat & FOUL_CORRECT_POT)*/ )
     ply[0].col = ply[1].col = 0;
    }
   if( (!first_hit) || (!bande_hit) ) ply[cur].stat |= FOUL_NO_TOUCH;
  }
 while( !spielende );
 }

void stop_it( void )
 { /* beendet Programm und gibt Statistiken und Stand aus */
 int i;
 close_graphics();
 if( datei ) fclose(datei);
 MSG;
 for( i=0;i<2;i++ )
  printf("\n	Player %d has won %d and lost %d game%s",i+1, 
   (int)(ply[i].points)%10000, (int)(ply[i].points)/10000,
   ( ((int)(ply[i].points)/10000) !=1 ) ? "s" : "");
 printf("\n\n");
 exit( 0 );
 }
 
int main( int argc, char *argv[])
 {
 time_t sys_time;
 char opt;
 opterr=0; /* Keine Fehlermeldung bei "falscher" Option ausgeben */
 if( !(datei = fopen("konst.dat","r") ) ) 
  { /* Datei für die (phys.) Konstanten */
  printf("can't open file 'konst.dat'\n");
  exit( 0 ); 
  }
 fscanf(datei,"%lg%*[^\n]%lg%*[^\n]%lg%*[^\n]%lg%*[^\n]%lg%*[^\n]",
  &ALPHA, &BETA, &GAMMA, &DELTA, &ETA);
 fscanf(datei,"%lg%*[^\n]%lg%*[^\n]%lg%*[^\n]%lg%*[^\n]",
  &BANDES, &BANDEREF, &BANDEZ, &MASS_WHITE);
 fscanf(datei,"%d%*[^\n]", &CLEV);
 fscanf(datei,"%lg%*[^\n]%lg%*[^\n]",&CURVES, &CURVEP);
 if( CLEV > 7 ) CLEV = 7; /* Computer-Level max 7 */
 fclose(datei);

 gamemode = SPECIAL_TARGET_MODE;
 simulation_flag=0;
 /* default Werte der Kommandozeilen-Optionen */
 shadows = PLANAR_SHADOWS; /* =1 */
 display_textures = 1;
 display_floor_textures = 0;
 new_game = 1;
 showfps = 1;
 geo_detail = DETAIL_MED;  /* =3 */
 txt_detail = TEXTURE_LOW; /* =1 */
 env_map = 0;
 ball_env_map = 0;
 GLOPT = 0; /* no fullscreen, start game in window mode */
 table_color = 0;

 while((opt = getopt (argc, argv, "x:y:g:t:hvf:Fd:e:s:T:b:")) != EOF)
  {
  switch(opt)
   {
   case 'x': SCREENRESX = atoi(optarg); break;
   case 'y': SCREENRESY = atoi(optarg); break;
   case 'g': geo_detail = atoi(optarg); break;
   case 't': txt_detail = atoi(optarg); break;
   case 'd': display_textures = atoi(optarg); break;
   case 'b': display_floor_textures = atoi(optarg); break;
   case 'e': env_map = atoi(optarg); break;
   case 'f': showfps = atoi(optarg); break;
   case 's': shadows = atoi(optarg); break;
   case 'F': GLOPT = 0x80000000; break;
   case 'T': new_game = atoi(optarg); break;
   default:
   case 'h': 
    printf("\nThis is Another Pool GL V%s - %s\n",VERSION,DATE);
    printf("(homepage: http://www.planetjahn.de)\n\n");
    printf("usage: apool [-hF] [-x width] [-y height] [-g dlevel] [-t dlevel] [-f dispfps]\n");
    printf("             [-e envmap] [-f dtext] [-T title] [-s smode] [-b ftext]\n\n");
    printf("-x width       sets screenwidth to <width> - if available [1024]\n");
    printf("-y height      sets screenheight to <height> - if available [768]\n");
    printf("-g dlevel      sets gemetric detail to <dlevel=1..5> [3]\n");
    printf("-t tlevel      sets texture detail to <tlevel=1/2> [1]\n");
    printf("-f dispfps     toggles display of framerate on/off (<dispfs=0/1>) [1]\n");
    printf("-e envmap      toggles environment mapping (table) on/off (<envmap=0/1>) [0]\n");
    printf("-d dtext       toggles display of textures on/off (<dtext=0/1>) [1]\n");
    printf("-b ftext       toggles display of floor textures on/off (<ftext=0/1>) [0]\n");
    printf("-F             starts Another Pool GL in fullscreen mode [off]\n");
    printf("-T title       show title screen <title=1/0> for yes/no [1]\n");
    printf("-s smode       sets shadow mode to <smode=0,1,2> (off,planar,stencil) [1]\n");
    printf("-h, ...        displays this messages\n\n");
    exit(0);
    break;
   }
  }
 init_graphics();  /* Graphik anschalten, OpenGL initialisieren */
 datei = NULL;
/* datei = fopen("xxx.xxx","w"); */
 init_timer();
 sys_time = time( NULL );
 srand( sys_time % RAND_MAX );
 rules(); /* Start des Spiels */
 stop_it();
 return 0;
 }

void calc_e_winkel( void )
 /* Als nächstes wird der "Einschußwinkel" der Mittellöcher berechnet, also
    der Winkel, "oberhalb des cos( dieses Winkels )" die Kugeln noch ins 
    Mittelloch versenkt werden können */
 {
 double dummy;
 dummy = (DIFFX/2 - banpixel[8].p0.x) - 0; /* wasndas?! */
 e_winkel = 90-180.0 / M_PI * 
 atan( ((banpixel[8].p0.y+PIXELRADIUS+2.0) - posl[2].m.y*(double)DIFFX) / dummy );
 }


