/*
 * SnakeC3 by Jocke "Firetech" Andersson
 *
 * This version is Windows only, mostly because of windows.h... :(
 * (I'll add some Definition hacks to make it work in Linux too, in some distant future)
 *
 * Compilers that work (are tested):
 *  - MinGW (GCC for windows)
 *  - Borland C++ Compiler (BCC)
 *  - Microsoft Visual C++ 6.0
 *
 * Licence: Free (as in freedom) to use, abuse and/or modify (and redistribute).
 */

#include <iostream>
#include <time.h>
#include <windows.h>
#include <conio.h>
#include "Snake.h"
using namespace std;

const char vers[] = "3.2.1.8";

bool dualplayer = false;
char screen[76] [21], dir[2];
const char wormchr = 'o', wormhead = '\x94', applechr = '\x0F', crapchr = '\xCF', flin[] =
     "                                                                              \n";
int growincs[2], sleeps, sleeptime, score[2], lscore[2];

snake * head[2];

//function prototypes
void clean_draw( bool restart = true );
void clearscr( void );
void endgame( bool dieend = false, int p = 0, int special = 0 );
void getpos( int & x, int & y );
void gotopos( int x, int y );
int incplayer( int p = 0 );
char pause( bool gamepause = false, int time = 0, bool press_any = true );
void rand_apple( bool player_ate = false, int p = 0 );
int random( int lowest_number, int highest_number );
void setcolor( unsigned short color = 7, unsigned short bkg = 0 );
void showabout( bool shutdown = false );
bool showhelp( bool startup = false );

int main( void )
{
  bool cont[2];
  int p, op, pstat;
  srand( time( NULL ) );
  do
  {
    cont[0] = false;
    cont[1] = showhelp( true );
    if ( cont[1] )
    {
      clean_draw();
    }
    while ( cont[1] )
    {
      Sleep( sleeptime );
      if ( kbhit() )
      {
        switch ( tolower( getch() ) )
        {
          case 0: //Borland-style (BCC)
          case 224: //MinGW-style
            switch ( getch() )
            {
              case 72:
                dir[0] = ( dir[0] != 'd' ? 'u' : 'd' );
              break;
              case 80:
                dir[0] = ( dir[0] != 'u' ? 'd' : 'u' );
              break;
              case 75:
                dir[0] = ( dir[0] != 'r' ? 'l' : 'r' );
              break;
              case 77:
                dir[0] = ( dir[0] != 'l' ? 'r' : 'l' );
              break;
              default:
              break;
            }
          break;
          case 'w':
            dir[1] = ( dir[1] != 'd' ? 'u' : 'd' );
          break;
          case 's':
            dir[1] = ( dir[1] != 'u' ? 'd' : 'u' );
          break;
          case 'a':
            dir[1] = ( dir[1] != 'r' ? 'l' : 'r' );
          break;
          case 'd':
            dir[1] = ( dir[1] != 'l' ? 'r' : 'l' );
          break;

          case '+':
            sleeptime = ( sleeptime <= 5 ? 5 : sleeptime - 5 );
          break;
          case '-':
            sleeptime = ( sleeptime >= 100 ? 100 : sleeptime + 5 );
          break;

          case 'p':
            pause( true );
            clean_draw( false );
          break;
          case 'e':
            endgame();
            cont[0] = true;
            cont[1] = false;
          break;
          case 'r':
            clean_draw( false );
          break;
          case 'i':
            showabout();
            clean_draw( false );
          break;
          case 'h':
            showhelp();
            clean_draw( false );
          break;
          case 'q':
            endgame();
            showabout( true );
            cont[0] = false;
            cont[1] = false;
          break;
          default:
          break;
        }
      }
      if ( ( cont[1] ) && ( ++sleeps >= 5 ) )
      {
        sleeps = 0;
        cont[1] = false;
        if ( dualplayer )
        {
          p = random( 0, 1 );
          op = ( p == 0 ? 1 : 0 );
          pstat = incplayer( p );
          if ( pstat == -1 )
          {
            pstat = incplayer( op );
            if ( pstat == -1 )
              cont[1] = true;
            else
              endgame( true, op, pstat );
          }
          else
            endgame( true, p, pstat );
        }
        else
        {
          pstat = incplayer( 0 );
          if ( pstat == -1 )
            cont[1] = true;
          else
            endgame( true, 0, pstat);
        }
        if ( !cont[1] )
	        cont[0] = true;
      }
    }
  }
  while ( cont[0] );
  return 0;
}

void clean_draw( bool restart )
{
  char prc;
  if ( restart )
  {
    sleeptime = 30;
    for ( int l = 0; l < 21; l++ )
    {
      for ( int c = 0; c < 76; c++ )
      {
        screen[c] [l] = ' ';
      }
    }

    dir[0] = 'r';
    dir[1] = 'l';
    for ( int i = 0; i < 2; i++ )
    {
      head[i] = new snake( NULL, dir[i], ( 40 - ( i * 5 ) ), 10 ); //create player.
      growincs[i] = 4; //make him a bit longer after starting moving
      score[i] = 0;
      lscore[i] = 25;
    }
    rand_apple(); //randomize a new "apple"
  }

  screen[head[0]->x] [head[0]->y] = 'A';
  if ( dualplayer )
  {
    screen[head[1]->x] [head[1]->y] = 'B';
  }
  clearscr();
  setcolor( 9, 9 );

  cout << flin;
  for ( int l = 0; l < 21; l++ )
  {
    cout << " ";
    for ( int c = 0; c < 76; c++ )
    {
      prc = wormchr;
      switch ( screen[c] [l] )
      {
        case 'a':
          setcolor( 12 );
        break;
        case 'A':
          setcolor( 12 );
          prc = wormhead;
        break;
        case 'b':
          setcolor( 13 );
        break;
        case 'B':
          setcolor( 13 );
          prc = wormhead;
        break;
        case applechr:
          setcolor( 10 );
          prc = applechr;
        break;
        case 'c':
          setcolor( 6 );
          prc = crapchr;
        break;
        default:
          setcolor( 0 );
          prc = ' ';
      }
      cout << prc;
    }
    setcolor( 9, 9 );
    cout << " \n";
  }
  cout << flin;
  setcolor( 10 );
  if ( dualplayer )
  {
    cout << "Player 1: " << score[0] << "\nPlayer 2: " << score[1];
  }
  else
  {
    cout << "Score: " << score[0];
  }
}

void clearscr( void )
{
  system("cls");
}

void endgame( bool dieend, int p, int special )
{
  int op = ( p == 0 ? 1 : 0 ), wp, lp;
  clearscr();
  setcolor( 14 );
  cout << "Game Over\n=========\n\n";
  setcolor( 15 );
  if ( dualplayer )
  {
    if ( dieend )
    {
      score[p] -= 5;
      if ( special == 1 )
      {
        score[op] -= 5;
        cout << "Headon collision! Both players died.";
      }
      else if ( special == 2 )
      {
        cout << "Player " << p + 1 << " ate crap and died.";
      }
      else
      {
        cout << "Player " << p + 1 << " collided and died.";
      }
      cout << "\n\n";
    }
    if ( score[p] != score[op] )
    {
      wp = ( score[p] > score[op] ? p : op );
      lp = ( wp == 0 ? 1 : 0 );
      cout << "Player " << wp + 1 << " won with " << score[wp] << " point(s).\n";
      cout << "Player " << lp + 1 << " got " << score[lp] << " point(s).\n\n";
    }
    else
    {
      cout << "Tie. Both players got " << score[p] << " points.\n\n";
    }
  }
  else
  {
    if ( dieend )
    {
      if ( special == 2 )
      {
        cout << "You ate crap and died.";
      }
      else
      {
        cout << "You collided and died.";
      }
      cout << "\n\n";
    }
    cout << "You got " << score[0] << " points.\n\n";
  }
  setcolor();
  if ( dieend )
  {
    pause( false, 2 );
  }
  else
  {
    pause();
  }
}

void getpos( int & x, int & y )
{
  HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
  CONSOLE_SCREEN_BUFFER_INFO csbi;
  GetConsoleScreenBufferInfo( hConsole, & csbi );
  x = csbi.dwCursorPosition.X + 1;
  y = csbi.dwCursorPosition.Y + 1;
}

void gotopos( int x, int y )
{
  COORD scrn;
  HANDLE hOuput = GetStdHandle(STD_OUTPUT_HANDLE);
  scrn.X = x - 1;
  scrn.Y = y - 1;
  SetConsoleCursorPosition(hOuput,scrn);
}


int incplayer( int p )
{
  int op = ( p == 0 ? 1 : 0 ), px = head[p]->x, py = head[p]->y, ex = -1, ey = -1;
  int retval = -1;
  char pc = ( p == 0 ? 'a' : 'b' );
  if ( --lscore[p] <= 0 )
  {
    score[p] ++;
    lscore[p] = 25;
    setcolor( 10 );
    if ( dualplayer )
    {
      switch ( p )
      {
        case 0:
          gotopos( 1, 24 );
          cout << "Player 1: " << score[0];
        break;
        case 1:
          gotopos( 1, 25 );
          cout << "Player 2: " << score[1];
        break;
      }
    }
    else
    {
      gotopos( 1, 24 );
      cout << "Score: " << score[0];
    }
  }

  if ( growincs[p] <= 0 )
  {
    head[p] = movesnake( head[p], dir[p], ex, ey );
    growincs[p] = 0;
  }
  else
  {
    head[p] = new snake( head[p], dir[p] );
    growincs[p] --;
  }

  if ( ( dualplayer ) && ( head[p]->x == head[op]->x ) && ( head[p]->y == head[op]->y ) )
  {
    if ( ( ( dir[p] == 'u' ) && ( dir[op] == 'd' ) ) || ( ( dir[p] == 'd' ) && ( dir[op] == 'u' ) )
         || ( ( dir[p] == 'r' ) && ( dir[op] == 'l' ) ) || ( ( dir[p] == 'l' ) && ( dir[op] == 'r' ) ) )
         {
           retval = 1;
    }
    else
    {
      retval = 0;
    }
  }
  else if ( tolower( screen[head[p]->x] [head[p]->y] ) == 'c' )
  {
    retval = 2;
  }
  else if ( ( head[p]->y <= -1 ) || ( head[p]->y >= 21 ) || ( head[p]->x <= -1 ) || ( head[p]->x >= 76 )
       || ( ( tolower( screen[head[p]->x] [head[p]->y] ) >= 'a' ) && ( tolower( screen[head[p]->x] [head[p]->y] ) <= 'b' ) ) )
       {
         retval = 0;
  }
  else
  {
    if ( screen[head[p]->x] [head[p]->y] == applechr )
    {
      rand_apple( true, p );
      if ( ( ex >= 0 ) && ( ey >= 0 ) )
      {
        screen[ex] [ey] = 'c';
        gotopos( ex + 2, ey + 2 );
        setcolor( 6 );
        cout << crapchr;
      }
    }
    else if ( ( ex >= 0 ) && ( ey >= 0 ) )
    {
      screen[ex] [ey] = ' ';
      gotopos( ex + 2, ey + 2 );
      cout << ' ';
    }
    screen[px] [py] = pc;
    screen[head[p]->x] [head[p]->y] = toupper( pc );
    setcolor( 12 + p );
    gotopos( px + 2, py + 2 );
    cout << wormchr;
    gotopos( head[p]->x + 2, head[p]->y + 2 );
    cout << wormhead;
  }
  return retval;
}

char pause( bool gamepause, int time, bool press_any )
{
  char gchar;
  if ( gamepause )
  {
    setcolor( 2 );
    gotopos( 34, 11 );
    cout << "Game paused.";
    gotopos( 26, 13 );
  }
  else
  {
    setcolor();
  }
  if ( time > 0 )
  {
    int i, tx = 0, ty = 0;
    getpos( tx, ty );
    for ( i = time; i >= 1; i-- )
    {
      cout << "Wait " << i << " second(s)...                    ";
      gotopos( tx, ty );
      Sleep( 1000 );
    }
  }
  if ( press_any )
  {
    cout << "Press any key to continue...";
    gchar = getch();
  }
  sleeps = 0;
  return gchar;
}

void rand_apple( bool player_ate, int p )
{
  int ax, ay;
  if ( player_ate )
  {
    score[p] += 10;
    growincs[p] += 3;
    setcolor( 14 );
    gotopos( 1, 24 );
    setcolor( 10 );
    if ( dualplayer )
    {
      cout << "Player 1: " << score[0] << "\nPlayer 2: " << score[1];
    }
    else
    {
      cout << "Score: " << score[0];
    }
  }
  do
  {
    ax = random( 0, 74 );
    ay = random( 0, 19 );
  }
  while ( screen[ax] [ay] != ' ' );
  screen[ax] [ay] = applechr;
  gotopos( ax + 2, ay + 2 );
  setcolor( 10 );
  cout << applechr;
}

int random( int lowest_number, int highest_number )
{
  int range = highest_number - lowest_number + 1;
  return lowest_number + int( range * rand() / ( RAND_MAX + 1.0 ) );
}

void setcolor( unsigned short color, unsigned short bkg )
{
  HANDLE hcon = GetStdHandle( STD_OUTPUT_HANDLE );
  SetConsoleTextAttribute( hcon, ( WORD )( ( bkg << 4 ) | color ) );
}

void showabout( bool shutdown )
{
  clearscr();
  setcolor( 14 );
  if ( shutdown )
  {
    cout << "Thanks for playing SnakeC3\n==========================";
  }
  else
  {
    cout << "About SnakeC3\n=============";
  }
  setcolor();
  cout << "\n\nSnake Concept: Phase 3\n\tPowered by CrowD-S " << vers << "\n\tAnd Snake.h " << shvers;
  cout << "\n\n(c) 2005 Jocke \"Firetech\" Andersson";
  cout << "\n\tThanks to \"Bloodyduck\" for helping with Snake.h\n\n";
  pause();
}

bool showhelp( bool startup )
{
  char gchar;
  clearscr();
  setcolor( 14 );
  if ( startup )
  {
    cout << "Welcome to SnakeC3\n==================\n\n";
    setcolor();
    int i = 0;
    do
    {
      if ( i >= 1 )
      {
        cout << "Invalid input!";
      }
      else
      {
        cout << "1 or 2 players, or quit?";
      }
      cout << " (1/2/Q) ";
      gchar = tolower( getche() );
      cout << endl;
      i++;
    }
    while ( ( gchar != '1' ) && ( gchar != '2' ) && ( gchar != 'q' ) );
    switch ( gchar )
    {
      case '1':
        dualplayer = false;
      break;
      case '2':
        dualplayer = true;
      break;
      case 'q':
        showabout( true );
        return false;
    }
  }
  else
  {
    cout << "SnakeC3 Help\n============\n";
  }

  cout << endl;
  setcolor( 15 );
  cout << "Keys\n----\n";
  setcolor();
  cout << "\x18,\x1B,\x19,\x1A\t:Control snake ";
  if ( dualplayer )
  {
    setcolor( 12 );
    cout << "1";
    setcolor();
    cout << "\nW,A,S,D\t:Control snake ";
    setcolor( 13 );
    cout << "2";
    setcolor();
  }
  cout << "\n   +\t:Increase game speed\n   -\t:Decrease game speed\n   P\t:Pause\n   E\t:End game\n   R\t:Redraw screen\n";
  cout << "   I\t:About information\n   H\t:Show this help\n   Q\t:Quit\n\n";
  setcolor( 15 );
  cout << "Scores\n------\n";
  setcolor();
  cout << "25 moves\t:+1 point\nTaking ";
  setcolor( 10 );
  cout << applechr;
  setcolor();
  cout << "\t:+10 points and +3 bodyparts";
  if ( dualplayer )
  {
    cout << "\nDying\t\t:-5 points";
  }
  cout << "\n\n";
  if ( startup )
  {
    switch ( pause() )
    {
      case 'i':
        showabout();
      break;
      case 'q':
        showabout( true );
        return false;
    }
  }
  else
  {
    pause();
  }
  if ( dualplayer )
  {
    cout << "\n\n";
    pause( false, 2, false );
  }
  return true;
}

