/*4il.c (4 in line) - compiled under Borland C++ 5.0.
 *Version : Beta
 *Author : Cheok Yan Cheng
 *Date started coding : 11/10/2000
 *Date finished coding : 13/10/2000
 */

/*If you had encounted any problem, bugs or comment on this source code
 *you are welcomed to contact me by my e-mail : yccheok@yahoo.com
 */

/*Some terms in this source code that you encounted may looked strange to you.
 *However, I have no time to include the explaination in detail.
 *If you really want to know, please contact me through e-mail.
 *Sorry for the inconvenience
 */

/*Ported to libcaca
 *Copyright (c) 2009-2014 Sam Hocevar <sam@hocevar.net>
*/

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "caca_conio.h"

#undef  max

#define X_BOARD 8
#define Y_BOARD 8
#define BOARD_SIZE  X_BOARD*Y_BOARD

#define BOOLEAN int
#define TRUE 1
#define FALSE 0

#define WIN 1
#define LOSE -1
#define DRAW 0
#define OK 2

#define COMPUTER 0
#define HUMAN 1
#define EMPTY -1
#define BASE -2

#define WIN_MESSAGE  "I Win! Press Any Key To Continue...\n"
#define LOSE_MESSAGE "You Win! Press Any Key To Continue...\n"
#define DRAW_MESSAGE "Draw! Press Any Key To Continue...\n"

BOOLEAN computerMove(void);
BOOLEAN doubleHead(int, int [Y_BOARD][X_BOARD]);
BOOLEAN humanMove(int);
BOOLEAN isFull(void);
BOOLEAN isWon(int, int [Y_BOARD][X_BOARD]);
BOOLEAN twoSnake(int, int, int [Y_BOARD][X_BOARD]);
int bestmove(void);
int max(int [X_BOARD]);
int status(void);
int xDoubleHead(int, int [Y_BOARD][X_BOARD]);
int xTwoSnake(int, int [Y_BOARD][X_BOARD]);
int xWon(int, int [Y_BOARD][X_BOARD]);
int y_Base(int, int [Y_BOARD][X_BOARD]);
void duplicate(int [Y_BOARD][X_BOARD], int [Y_BOARD][X_BOARD]);
void drawBoard(void);
void drawPiece(void);
void genNumWin(int [X_BOARD]);
void getHumanMove(void);
void init(void);
void makeMove(int, int, int [Y_BOARD][X_BOARD]);
void sorting(int n[X_BOARD]);

int move[X_BOARD] = {3, 4, 2, 5, 1, 6, 0, 7};
int col[X_BOARD]  = {-1,-1,-1,-1,-1,-1,-1,-1};
int square[Y_BOARD][X_BOARD] =
                        { {-1, -1, -1, -1, -1, -1, -1, -1},
                          {-1, -1, -1, -1, -1, -1, -1, -1},
                          {-1, -1, -1, -1, -1, -1, -1, -1},
                          {-1, -1, -1, -1, -1, -1, -1, -1},
                          {-1, -1, -1, -1, -1, -1, -1, -1},
                          {-1, -1, -1, -1, -1, -1, -1, -1},
                          {-1, -1, -1, -1, -1, -1, -1, -1},
                          {-2, -2, -2, -2, -2, -2, -2, -2},
                        };

BOOLEAN computerMove(void)
{
  int x_best;
  if (isFull())
    return FALSE;
  x_best = bestmove();
  gotoxy(52,2);
  printf("x:%d, y:%d\n" ,x_best+1 ,Y_BOARD-y_Base(x_best,square));
  makeMove(COMPUTER, x_best, square);
  return TRUE;
}

BOOLEAN doubleHead(int who, int xsquare[Y_BOARD][X_BOARD])
{
  int x,y;
  for(y=0; y<Y_BOARD; y++)
  {
    for(x=0; x<(X_BOARD-4); x++)
    {
      if( xsquare[y][x] == BASE &&
          xsquare[y][x+1] == who &&
          xsquare[y][x+2] == who &&
          xsquare[y][x+3] == who &&
          xsquare[y][x+4] == BASE )
      return TRUE;
    }
  }
  return FALSE;
}

BOOLEAN humanMove(int x)
{
  gotoxy(1,20);
  if ((x < 0) || x >= X_BOARD)
    return FALSE;
  if (y_Base(x, square) == -1) /*indicate no base at that x-coulomb*/
    return FALSE;
  if (isFull()) /*the board is full*/
    return FALSE;
  gotoxy(52,5);
  printf("x:%d, y:%d\n" ,x+1 ,Y_BOARD-y_Base(x,square));
  makeMove(HUMAN,x,square);
  return TRUE;
}

BOOLEAN isFull()
{
  int x;
  for(x=0; x<X_BOARD; x++)
  {
    if(square[0][x] == EMPTY || square[0][x] == BASE)
      return FALSE;
  }
  return TRUE;
}

BOOLEAN isWon(int who, int xsquare[Y_BOARD][X_BOARD])
{
  int x,y;
  for(x=0; x<X_BOARD; x++)
  {
    for(y=0; y<Y_BOARD; y++)
    {
      /*horizontal position*/
      if( (x+3)<X_BOARD &&
          xsquare[y][x]   == who &&
          xsquare[y][x+1] == who &&
          xsquare[y][x+2] == who &&
          xsquare[y][x+3] == who
        )
        return TRUE;

      /*vertical position*/
      if( (y+3)<Y_BOARD &&
          xsquare[y][x]   == who &&
          xsquare[y+1][x] == who &&
          xsquare[y+2][x] == who &&
          xsquare[y+3][x] == who
        )
        return TRUE;

      /*downstair diagonal position*/
      if( (x+3)<X_BOARD             &&
          (y+3)<Y_BOARD             &&
          xsquare[y][x]      == who &&
          xsquare[y+1][x+1]  == who &&
          xsquare[y+2][x+2]  == who &&
          xsquare[y+3][x+3]  == who
        )
        return TRUE;

      /*upstair diagonal position*/
      if( (x+3)<X_BOARD             &&
          (y-3)>=0                  &&
          xsquare[y][x]      == who &&
          xsquare[y-1][x+1]  == who &&
          xsquare[y-2][x+2]  == who &&
          xsquare[y-3][x+3]  == who
        )
        return TRUE;
    }
  }
  return FALSE;
}

BOOLEAN twoSnake(int who, int x, int xsquare[Y_BOARD][X_BOARD])
{
  int xxsquare[Y_BOARD][X_BOARD];
  int n[Y_BOARD] = {0,0,0,0,0,0,0,0};
  int i;

  for(i=0; i<Y_BOARD; i++)
  {
    if(xsquare[i][x] == BASE || xsquare[i][x] == EMPTY)
    {
      duplicate(xxsquare, xsquare);
      xxsquare[i][x] = who;

      if(isWon(who, xxsquare))
        n[i] = TRUE;
    }
  }

  for(i=0; i<(Y_BOARD-1); i++)
  {
    if( n[i] == TRUE && n[i+1] == TRUE )
      return TRUE;
  }
  return FALSE;
}

int bestmove()
{
  int xsquare[Y_BOARD][X_BOARD], n[X_BOARD], i, snake;

  gotoxy(1,19);
  textcolor(4);
  if(xWon(COMPUTER, square) != -1)
  {
    cprintf("Computer Previous Depth : +1\n");
    return xWon(COMPUTER, square);
  }
  if(xWon(HUMAN, square) != -1)
  {
    cprintf("Computer Previous Depth : -1\n");
    return xWon(HUMAN, square);
  }

  for(i=0; i<X_BOARD; i++)
  {
    if(y_Base(move[i], square) != -1 && col[move[i]] == COMPUTER)
    {
      duplicate(xsquare, square);
      makeMove(COMPUTER, move[i], xsquare);
      if(xWon(HUMAN, xsquare) == -1)
      {
        cprintf("Computer Previous Depth : +2\n");
        return move[i];
      }
    }
  }

  if(xDoubleHead(COMPUTER, square) != -1)
  {
    duplicate(xsquare, square);
    makeMove(COMPUTER, xDoubleHead(COMPUTER, xsquare), xsquare);
    if(xWon(HUMAN, xsquare) == -1)
    {
      cprintf("Computer Previous Depth : +3\n");
      return xDoubleHead(COMPUTER, square);
    }
  }

  if(xDoubleHead(HUMAN, square) != -1)
  {
    duplicate(xsquare, square);
    makeMove(COMPUTER, xDoubleHead(HUMAN, xsquare), xsquare);
    if(xWon(HUMAN, xsquare) == -1)
    {
      cprintf("Computer Previous Depth : -3\n");
      return xDoubleHead(HUMAN, square);
    }
  }

  snake =xTwoSnake(COMPUTER, square);
  if( snake != -1)
  {
    duplicate(xsquare, square);
    makeMove(COMPUTER, snake, xsquare);
    if(xWon(HUMAN, xsquare) == -1)
    {
      cprintf("Computer Previous Depth : +4\n");
      return snake;
    }
  }
  if(xTwoSnake(HUMAN, square) != -1)
  {
    duplicate(xsquare, square);
    makeMove(COMPUTER, xTwoSnake(HUMAN, xsquare), xsquare);
    if(xWon(HUMAN, xsquare) == -1)
    {
      cprintf("Computer Previous Depth : -4\n");
      return xTwoSnake(HUMAN, square);
    }
  }

  genNumWin(n);
  sorting(n);

  for(i=0; i<X_BOARD; i++)
  {
    if( y_Base (n[i], square) != -1)
    {
      duplicate(xsquare, square);
      makeMove(COMPUTER, n[i], xsquare);
      if(xWon(HUMAN, xsquare) == -1)
      {
        cprintf("Computer Previous Depth : +5\n");
        return n[i];
      }
    }
  }

  for(i=0; i<X_BOARD; i++)
  {
    if( y_Base (move[i], square) != -1)
    {
      cprintf("Computer Previous Depth : +0\n");
      return move[i];
    }
  }
  return -1;
}

int max(int n[X_BOARD])
{
  int i, big;
  big = 0;
  for(i=0; i<X_BOARD; i++)
  {
    if(n[i]>big)
      big = n[i];
  }
  return big;
}

int status()
{
  if (isWon(COMPUTER, square))
    return WIN;
  else
    if (isWon(HUMAN, square))
      return LOSE;
    else
      if (isFull())
        return DRAW;
      else
        return OK;
}

int xDoubleHead(int who, int xsquare[Y_BOARD][X_BOARD])
{
  int x;
  int xxsquare[Y_BOARD][X_BOARD];
  for(x=0; x<X_BOARD; x++)
  {
    if(y_Base(x,xsquare) != -1)
    {
      duplicate(xxsquare, xsquare);
      makeMove(who, x, xxsquare);
      if(doubleHead(who, xxsquare))
        return x;
    }
  }
  return -1;
}

int xTwoSnake(int who, int xsquare[Y_BOARD][X_BOARD])
{
  int x, dx;
  int xxsquare[Y_BOARD][X_BOARD];
  for(x=0; x<X_BOARD; x++)
  {
    if(y_Base(move[x],xsquare) != -1)
    {
      duplicate(xxsquare, xsquare);
      makeMove(who, move[x], xxsquare);
      for(dx=0; dx<X_BOARD; dx++)
      {
        if( twoSnake(who, move[dx], xxsquare) && col[move[dx]] != who)
        {
          if(who == COMPUTER)
            col[move[dx]] = who;
          return move[x];
        }
      }
    }
  }
  return -1;
}

int xWon(int who, int xsquare[Y_BOARD][X_BOARD])
{
  int x;
  int xxsquare[Y_BOARD][X_BOARD];
  for(x=0; x<X_BOARD; x++)
  {
    if(y_Base(x,xsquare) != -1)
    {
      duplicate(xxsquare, xsquare);
      makeMove(who, x, xxsquare);
      if(isWon(who, xxsquare))
        return x;
    }
  }
  return -1;
}

int y_Base(int x, int xsquare[Y_BOARD][X_BOARD])
{
  int y;
  for(y=0; y<Y_BOARD; y++)
  {
    if(xsquare[y][x] == BASE)
      return y;
  }
  return -1; /*indicate no base at that x*/
}

void duplicate(int xSquare[Y_BOARD][X_BOARD], int oSquare[Y_BOARD][X_BOARD])
{
  int x,y;
  for(x=0; x<X_BOARD; x++)
  {
    for(y=0; y<Y_BOARD; y++)
    {
      xSquare[y][x] = oSquare[y][x];
    }
  }
}

void drawBoard()
{
  textcolor(0);
  textbackground(7);
  clrscr();
  gotoxy(1,1);
  printf("+-----+-----+-----+-----+-----+-----+-----+-----+\n");
  printf("|     |     |     |     |     |     |     |     |\n");
  printf("+-----+-----+-----+-----+-----+-----+-----+-----+\n");
  printf("|     |     |     |     |     |     |     |     |\n");
  printf("+-----+-----+-----+-----+-----+-----+-----+-----+\n");
  printf("|     |     |     |     |     |     |     |     |\n");
  printf("+-----+-----+-----+-----+-----+-----+-----+-----+\n");
  printf("|     |     |     |     |     |     |     |     |\n");
  printf("+-----+-----+-----+-----+-----+-----+-----+-----+\n");
  printf("|     |     |     |     |     |     |     |     |\n");
  printf("+-----+-----+-----+-----+-----+-----+-----+-----+\n");
  printf("|     |     |     |     |     |     |     |     |\n");
  printf("+-----+-----+-----+-----+-----+-----+-----+-----+\n");
  printf("|     |     |     |     |     |     |     |     |\n");
  printf("+-----+-----+-----+-----+-----+-----+-----+-----+\n");
  printf("|     |     |     |     |     |     |     |     |\n");
  printf("+-----+-----+-----+-----+-----+-----+-----+-----+\n");
  printf("ARROW:move\tSPACE:select\tESC:exit\n");
  textcolor(1);
  gotoxy(44,20);
  cprintf("%-1s","4 In Line\n");
  gotoxy(44,21);
  cprintf("%-1s","ver Beta by Cheok Yan Cheng\n");
  gotoxy(44,22);
  cprintf("E-mail : yccheok@yahoo.com\n");
  gotoxy(44,23);
  cprintf("Web Site: www.geocities.com/yccheok\n");
  gotoxy(44,24);
  cprintf("Source code included!\n");
  gotoxy(1,22);
  printf("Human's Piece is O\n");
  gotoxy(1,23);
  printf("Computer's Piece is X\n");
  gotoxy(52,1);
  printf("Computer Move :\n");
  gotoxy(52,4);
  printf("Human Move :\n");
}

void drawPiece()
{
  int x,y;
  for(x=0; x<X_BOARD; x++)
  {
    for(y=0; y<Y_BOARD; y++)
    {
      if(square[y][x] == HUMAN)
      {
        gotoxy(x*6+4, y*2+2);
        textcolor(1);
        cprintf("O\n");
      }
      else
      if(square[y][x] == COMPUTER)
      {
        gotoxy(x*6+4, y*2+2);
        textcolor(4);
        cprintf("X\n");
      }
    }
  }
}

void genNumWin(int n[X_BOARD])
{
  int i, j, k;
  int xsquare[Y_BOARD][X_BOARD];
  int xxsquare[Y_BOARD][X_BOARD];

  for(i=0; i<X_BOARD; i++)
  {
    n[i]=0;
    if(y_Base(i, square) != -1)               /*has base exsit?*/
    {
      duplicate(xsquare, square);
      makeMove(COMPUTER, i, xsquare);

      for(j=0; j<X_BOARD; j++)
      {
        for(k=0; k<Y_BOARD; k++)
        {
          if(xsquare[k][j] == EMPTY || xsquare[k][j] == BASE)
          {
            duplicate(xxsquare, xsquare);
            xxsquare[k][j] = COMPUTER;
            if(isWon(COMPUTER, xxsquare))
              n[i]++;
          }
        }
      }
    }
  }
}

void getHumanMove()
{
  int x=3 ;
  int ch;
        while(TRUE)
  {
    gotoxy(x*6 +4, 2);
    ch = (int)getch();
    switch(ch)
    {
      case 75:/*LEFT*/
        if(x>0)
          x--;
        break;
      case 77:/*RIGHT*/
        if(x<(X_BOARD-1))
          x++;
        break;
      case 27:/*ESC*/
        textcolor(7);
        textbackground(0);
        clrscr();
        printf("Thank You For Playing 4 in line by Cheok Yan Cheng!\n");
        exit(0);
        break;
      case 32:/*SPACE*/
        if(humanMove(x))
        {
          drawPiece();
          return;
        }
        else
        {
          gotoxy(1,20);
          textcolor(4);
          cprintf("OOPs! Wrong Move! \n");
        }
    }
        }
}

void init()
{
  int x,y;
  for(x=0; x<X_BOARD; x++)
  {
    for(y=0; y<(Y_BOARD-1); y++)
    {
      square[y][x] = EMPTY;
    }
    square[7][x] = BASE;
    col[x] = -1;
  }
}

void makeMove(int who, int x, int xsquare[Y_BOARD][X_BOARD])
{
  int y;
  y = y_Base(x, xsquare);
  xsquare[y][x] = who;
  if(y>0)
    xsquare[y-1][x] = BASE;
}

void sorting(int n[])
{
  int i, j, alpha;
  int store[X_BOARD];

  for(j=0; j<X_BOARD; j++)
  {
    alpha = max(n);
    for(i=0; i<X_BOARD; i++)
    {
      if(n[move[i]] == alpha)
      {
        store[j] = move[i];
        n[move[i]] = -1;
        break;
      }
    }
  }
  for(i=0; i<X_BOARD; i++)
    n[i] = store[i];
}

int main(void)
{
  BOOLEAN myturn;
  myturn = TRUE;
  drawBoard();
  srand(time(NULL));
  rand();
  do
  {
    switch (status())
    {
      case WIN:
      case LOSE:
      case DRAW:
      init();
      drawBoard();
      if (myturn)
      {
        makeMove(COMPUTER,(2+rand()%4),square);
      }
      myturn = !myturn;
      drawPiece();
    }
    textcolor(4);
    gotoxy(1,20);
    cprintf("Your Turn, Please.\n");
    getHumanMove();
    gotoxy(1,20);
    textcolor(4);
    switch (status())
    {
      case WIN:
        cprintf(WIN_MESSAGE);
        getch();
      break;
      case LOSE:
        cprintf(LOSE_MESSAGE);
        getch();
      break;
      case DRAW:
        cprintf(DRAW_MESSAGE);
        getch();
        break;
      default:/*OK*/
        if(computerMove())
        {
          gotoxy(1,20);
          drawPiece();
          gotoxy(1,20);
          switch (status())
          {
            case WIN:
              cprintf(WIN_MESSAGE);
              getch();
            break;
            case LOSE:
              cprintf(LOSE_MESSAGE);
              getch();
            break;
            case DRAW:
              cprintf(DRAW_MESSAGE);
              getch();
            break;
          }
        }
    }
  }
  while(TRUE);
}