// ObjectWindows - (C) Copyright 1992 by Borland International

#include <windows.h>
#include <fstream.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <dos.h>
#include <time.h>
#include <ctype.h>

#include "wchess.h"
#include "wcdefs.h"
#include "externs.h"

/*
 *  Global declarations
 */

char *INIFile = "wchess.ini";
BOOL InLibrary;  		// True if program is in the opening library
BOOL MultiMove,      // True if multimove mode
     AutoPlay,		   // True if AutoPlay mode
	  SingleStep;		// Single Step mode, used for debugging purposes
LEVELTYPE Level;
double AverageTime = 5.0;
PIECETYPE Pieces[8]  = { rook, knight, bishop, queen,
                              king, bishop, knight, rook};
BYTE MaxLevel;
BOOL Turned;
BOOL UseLib;
LIBTYPE Openings;
COLORTYPE ProgramColor;
int MoveNo;
int PVTable[2][7][0x78];
MOVETYPE PlayerMove;
BOOL Logging;
NODEVAL Nodes;             // Number of analysed nodes
CLOCKTYPE ChessClock;
extern int LegalMoves;
LINETYPE HintLine;            /*  suggested hint line  */
MAXTYPE HintEvalu;            /*  Evaluation for hintline  */
enum ANALYSISCONTROL {Start, Return, Continue};

int OpCount, LibNo;

static DEPTHTYPE LibDepth;
static BOOL Found;
static DEPTHTYPE dep;

ofstream *OutputFile;

typedef enum {readmove, checkmove, gamemove} CONTROLVAR;
static CONTROLVAR Control;



void InsertPiece(PIECETYPE p, COLORTYPE c, SQUARETYPE sq)
{
    Board[sq].piece = p;
    Board[sq].color = c;
}


void ClearPVTable()
{
    COLORTYPE color;
    PIECETYPE piece;
    SQUARETYPE square;

    for (color = white; color <= black; ((int)color)++)
        for (piece = king; piece <= pawn; ((int)piece)++)
            for (square = 0; square <= 0x77; square++)
                PVTable[color][piece][square] = 0;
}


void ResetGame()
{
	ClearBoard();
   Running = FALSE;
   for (int i = 0; i < 8; i++)
      {
        InsertPiece(Pieces[i], white, i);
        InsertPiece(pawn, white, i + 0x10);
        InsertPiece(pawn, black, i + 0x60);
        InsertPiece(Pieces[i], black, i + 0x70);
      }
   CalcPieceTab();
   Player = white;
   ClearDisplay();
   InitDisplay();
   ColorToPlay(Player);
   Opponent = black;
}


void NewGame()
{
	SingleStep = InLibrary = FALSE;
   GameOver = FALSE;
   ResetGame();
   PrintCurLevel();
   ResetMoves();
   if (!*Openings)
         UseLib = 0;
   else
      UseLib = 200;
   MovTab[-1].content = king;
   InitChessTime();
   ProgramColor = white;
   MoveNo = 0;
   ClearHint();
   ClearPVTable();
   PlayerMove = ZeroMove;
   if (Logging && !AutoPlay)
      *OutputFile << "\n No  Player Program      Hint      Value Level       Nodes    Time\n";
   InitNode(&Nodes);
   ChessClock.totaltime = 0.0;
   Control = (AutoPlay) ? gamemove : readmove;
}


void ResetNewPos(void)
{
    ResetMoves();
    CalcPieceTab();
    UseLib = FALSE;
    Running = FALSE;
    ClearHint();
}


void ResetOpening()
{
   const char *libfilename = "opening.lib";
   ifstream fin(libfilename, ios::in | ios::binary);

   if (!fin)
      {
      MessageBox(NULL,"Cannot find Openings Library", "Error",
                 MB_ICONHAND | MB_OK);
		Openings = new unsigned char;
		*Openings = 0;
      return;
      }
   Openings = new unsigned char[32000];
   fin.read(Openings, 32000);
   fin.close();
   *Openings = 0xFF;
}


static void StartUp()
{
   long color;
   randomize();

   Level = (LEVELTYPE)GetPrivateProfileInt("WCHESS", "Level",
      (int)easygame, INIFile);

   if (GetPrivateProfileString("WCHESS", "AverageTime", "5.0", buf,
      80, INIFile))
      sscanf(buf, "%lf", &AverageTime);

   if (GetPrivateProfileString("WCHESS", "WhiteSquare", "", buf, 80, INIFile))
      {
      sscanf(buf, "%ld", &color);
      WhiteSquareColors[0] = color & 0xffL;
      WhiteSquareColors[1] = (BYTE)((color & 0xff00L) >> 8);
      WhiteSquareColors[2] = (BYTE)((color & 0xff0000L) >> 16);
      }

   if (GetPrivateProfileString("WCHESS", "BlackSquare", "", buf, 80, INIFile))
      {
      sscanf(buf, "%ld", &color);
      BlackSquareColors[0] = color & 0xff;
      BlackSquareColors[1] = ((color & 0xff00L) >> 8);
      BlackSquareColors[2] = ((color & 0xff0000L) >> 16);
      }

   MaxLevel = GetPrivateProfileInt("WCHESS", "MaxLevel", MAXPLY, INIFile);
   SoundOn = (BOOL)GetPrivateProfileInt("WCHESS", "SoundOn", 1, INIFile);

   if (!SoundOn)  // defaults to checked at startup
      CheckMenuItem(MainMenu, IDM_SOUND, MF_UNCHECKED);
   CalcAttackTab();
   MultiMove = FALSE;
   AutoPlay = FALSE;
   Turned = FALSE;
   ResetOpening();
   OutputFile = new ofstream("Chess.log");
   if (!OutputFile)
      {
      MessageBox(NULL, "Unable to open log file.\nLogging disabled", "OWL Chess", MB_OK | MB_ICONEXCLAMATION);
      Logging = FALSE;
      }
   else
      {
      *OutputFile << endl;
      *OutputFile << "            OWL CHESS by Borland International\n";
      *OutputFile << "            ==================================\n" << endl;
      }
}

const UNPLAYMARK = 0x3f;


/*
 *  Sets libno to the previous move in the block
 */

void PreviousLibNo(void)
{
    int n;

    n = 0;
    do
    {
        LibNo--;
        if (Openings[LibNo] >= 128) n++;
        if (Openings[LibNo] & 64) n--;
    } while (n);
}


/*
 *  Set libno to the first move in the block
 */

void FirstLibNo()
{
    while (!(Openings[LibNo - 1] & 64))
        PreviousLibNo();
}


/*
 *  set libno to the next move in the block.  Unplayable
 *  moves are skipped if skip is set
 */

void NextLibNo(short skip)
{
    int n;

    if (Openings[LibNo] >= 128) FirstLibNo();
    else
    {
        n = 0;
        do
        {
            if (Openings[LibNo] & 64) n++;
            if (Openings[LibNo] >= 128) n--;
            LibNo++;
        } while (n);
        if (skip && (Openings[LibNo] == UNPLAYMARK))
            FirstLibNo();
    }
}


/*
 *  find the node corresponding to the correct block
 */

static void FindNode(void)
{
    LibNo++;
    if (Depth > LibDepth)
    {
        Found = TRUE;
        return;
    }
    OpCount = -1;
    InitMovGen();
    do
    {
        OpCount++;
        MovGen();
    } while (Next.movpiece != empty && !EqMove(&Next, &MovTab[Depth]));
    if (Next.movpiece != empty)
    {
        while (((Openings[LibNo] & 63) != OpCount) &&
                (Openings[LibNo] < 128))
            NextLibNo(0);
        if ((Openings[LibNo] & 127) == 64 + OpCount)
        {
            MakeMove(&MovTab[Depth]);
            FindNode();
            TakeBackMove(&MovTab[Depth-1]);
        }
    }
}


/*
 *  Set LibNo to the block corresponding to the position
 */

void CalcLibNo(void)
{
    LibNo = 0;
    if (MoveNo < UseLib)
    {
        LibDepth = Depth;
        while (MovTab[Depth].movpiece != empty)
            TakeBackMove(&MovTab[Depth]);
        Found = FALSE;
        if (MovTab[Depth].content == king)
        {
            Depth++;
            FindNode();
            Depth--;
        }
        while(Depth < LibDepth)
            MakeMove(&MovTab[Depth + 1]);
        if (Found)
            UseLib = 200;
        else
        {
            UseLib = MoveNo;
            LibNo = 0;
        }
    }
}


/*
 *  find an opening move from the library
 */

static void FindOpeningMove(void)
{
    const unsigned char weight[7] = {7, 10, 12, 13, 14, 15, 16};
    unsigned char cnt, r, p, countp;

    r = random(16);   /*  calculate weighted random number in 0..16  */
    p = 0;
    while (r >= weight[p]) p++;
    for (countp = 1; countp <= p; countp++)  /* find corresponding node */
        NextLibNo(1);
    OpCount = Openings[LibNo] & 63;  /*  generate the move  */
    InitMovGen();
    for (cnt = 0; cnt <= OpCount; cnt++)
        MovGen();
                          /* store the move in mainline  */
    MainLine[0] = Next;
    MainLine[1] = ZeroMove;
    MainEvalu = 0;
    MaxDepth = 0;
    LegalMoves = 0;
    InitNode(&Nodes);
}



void OutputNode(NODEVAL *nodes)
{
   double nodereal;
   if (!Logging)
      return;
   char buf[20];
   nodereal = (nodes->nodebase * MAXINT) + nodes->nodeoffset;
   sprintf(buf, "%12.1f", nodereal);
   *OutputFile << buf;
}

static void ThinkAwhile();
void StartMove();

void ReturnAnalysis()
{
   int myx;
   char str[8];
   char buf[40];

   MovTab[0] = MainLine[0];   /*  copy the MainLine to HintLine  */
   for (myx = 1; myx <= MAXPLY; myx++)
       HintLine[myx - 1] = MainLine[myx];
   dep = MAXPLY;
   HintEvalu = MainEvalu;
   if (MovTab[0].movpiece == empty)
   {
       HintLine[0] = ZeroMove;   /*  No possible move  */
       if (AutoPlay)
       {
           NewGame();
           PrintBoard();
           StartMove();
       }
       return;
   }

//   FlashMove(&MovTab[Depth+1]);  /*  flash and perform the move  */
   DoSlideMove(MovTab[Depth+1]);
   EnterMove(&MovTab[Depth+1]);
   if (SoundOn)
      MessageBeep(0);
   StoreMoves();
   if (Logging && !AutoPlay)
      {
      sprintf(buf, "%3d. ",(MoveNo+1) / 2);
      *OutputFile << buf;
      strcpy(str, MoveStr(&MovTab[0]));
      if ((PlayerMove.movpiece == empty) && (Opponent == white))
          sprintf(buf, "%8.8s ", str);
      else
          sprintf(buf, "%s%8.8s",MoveStr(&PlayerMove), str);
      *OutputFile << buf;
      sprintf(buf, "    (%s)%9.2f%3d:%2d", MoveStr(&MainLine[1]),
           MainEvalu / 256.0, MaxDepth, LegalMoves);
      *OutputFile << buf;
      OutputNode(&Nodes);
      sprintf(buf, "%8.1lf\n", ChessClock.totaltime);
      *OutputFile << buf;
      }
   PlayerMove = ZeroMove;
   ColorToPlay(Player);
   if (AutoPlay)
   {
        if ((MoveNo >= 120) || (FiftyMoveCnt() >= 100) ||
            (Repetition(0) >= 3) || (MainEvalu <= -0x880))
            {
            NewGame();
            PrintBoard();
            }
        StartMove();
        return;
    }
   if (Level != easygame && !GameOver)
       ThinkAwhile();
}


/*
 *  Perform analysis in the opponents time of reflection.
 *  The program assumes that the opponent will perform the
 *  Hint move, and starts analysing on it for a counter move
 */


static void ThinkAwhile()
{
extern HCURSOR hArrowCursor;

   SetClassWord(hWndMain, GCW_HCURSOR, WORD( hArrowCursor ));
   SetCursor(hArrowCursor);
   SetMenu(hWndMain, MainMenu);
    if ((HintLine[0].movpiece == empty) || MultiMove)
        return;
    Analysis = FALSE;
    Opan = TRUE;
    AdjustMoves();          /*  Setup surroundings as if the  */
    MovTab[Depth+1] = HintLine[0];  /*  Opponent had performed  */
    MakeMove(&MovTab[Depth+1]);        /*  The hint move  */
    StoreMoves();
    AdjustMoves();
    Depth = 0;          /*  analyse until something is entered from by */
    FindMove(MaxLevel); /*  the user */
    Depth = -1;
    Opan = FALSE;
    if (Analysis)          /*  If the Opponent did make the  */
    {                      /*  Hint move then go and  */
        ReturnAnalysis();  /*  perform the counter move  */
        return;
    }
    TakeBackMove(&MovTab[Depth]);  /*  restore the surroundings  */
    if (GotValidMove)
      {
      AdjustMoves();
      EnterKeyMove();
      StoreMoves();
      SetMenu(hWndMain, ThinkMenu);
      SetClassWord(hWndMain, GCW_HCURSOR, WORD( hWaitCursor ));
      SetCursor(hWaitCursor);
      }
    return;
}


void StartMove()
{
   MSG msg;
   StartAnalysis();
   AdjustMoves();
   CalcLibNo();            /*  Try to find a move in the opening library  */
   Depth = 0;
   if (LibNo > 0)
   {
       OpeningLibMsg();
       InLibrary = TRUE;
       FindOpeningMove();
       if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
         {
            if (msg.message == WM_COMMAND && msg.wParam == CM_STOP)
               return;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
         }
   }
   else
   {
       if (InLibrary)
       {
           InLibrary = FALSE;
           ClearMessage();
       }
       FindMove(MaxLevel);
   }
   Depth = -1;
   ReturnAnalysis();
}


void ProgramMove()
{
   do
      {
      GotValidMove = FALSE;
      ColorToPlay(Player);
      StartMove();
      }
   while (GotValidMove);
}


void Talk()
{
   StartUp();
   NewGame();
}


BOOL Redo()
{
//   MakeMove(&MovTab[Depth+1]);
   EnterMove(&MovTab[Depth+1]);
   ClearHint();
//   UpdateBoard();
   ClearBestLine();
   ColorToPlay(Player);
   if (Depth >= -1)
      return FALSE;  // cannot redo again
   return TRUE;  // can redo again
}

BOOL Undo()
{
// TakeBackMove(&MovTab[Depth]);
   extern void RemoveMove(MOVETYPE *);
   RemoveMove(&MovTab[Depth]);
   ClearHint();
//   UpdateBoard();
   ClearBestLine();
   ColorToPlay(Player);
   if (MovTab[Depth].movpiece == empty)
      return FALSE;  // Can't undo anymore
   return TRUE;  // Can still undo
}

void QuitProgram()
{
   long color;

   if (Logging)
      OutputFile->close();
   color = RGB(WhiteSquareColors[0], WhiteSquareColors[1],
      WhiteSquareColors[2]);
   sprintf(buf, "%ld", color);
   WritePrivateProfileString("WCHESS", "WhiteSquare", buf, INIFile);
   color = RGB(BlackSquareColors[0], BlackSquareColors[1],
      BlackSquareColors[2]);
   sprintf(buf, "%ld", color);
   WritePrivateProfileString("WCHESS", "BlackSquare", buf, INIFile);
   sprintf(buf, "%d", Level);
   WritePrivateProfileString("WCHESS", "Level", buf, INIFile);
   sprintf(buf, "%lf", AverageTime);
   WritePrivateProfileString("WCHESS", "AverageTime", buf, INIFile);
   sprintf(buf, "%d", (int)MaxLevel);
   WritePrivateProfileString("WCHESS", "MaxLevel", buf, INIFile);
   sprintf(buf, "%d", (int)SoundOn);
   WritePrivateProfileString("WCHESS", "SoundOn", buf, INIFile);
}


void FindHintMove()
{
    /*  If hintline is empty the get the move from the
        opening library or perform a 1 - ply search  */
    if (HintLine[0].movpiece == empty)
    {
        AdjustMoves();
        CalcLibNo();
        Depth = 0;
        if (LibNo > 0)
            FindOpeningMove();
        else
        {
            Analysis = 1;
            Opan = 0;
            FindMove(1);
        }
        Depth = -1;
        HintLine[0] = MainLine[0];
        HintLine[1] = ZeroMove;
        HintEvalu = -MainEvalu;
    }
}

