/****************************************************************************************************************
 *
 *  Copyright (c) 1992 by Antoine Dumesnil de Maricourt. All rights reserved.
 *
 *  This program is distributed in the hope that it will be useful.
 *  Use and copying of this software and preparation of derivative works
 *  based upon this software are permitted, so long as the following
 *  conditions are met:
 *       o credit to the authors is acknowledged following current
 *         academic behaviour
 *       o no fees or compensation are charged for use, copies, or
 *         access to this software
 *       o this copyright notice is included intact.
 *  This software is made available AS IS, and no warranty is made about 
 *  the software or its performance. 
 * 
 *  Bug descriptions, use reports, comments or suggestions are welcome.
 *  Send them to    dumesnil@etca.fr   or to:
 *       
 *       Antoine de Maricourt
 *       ETCA CREA-SP
 *       16 bis, avenue Prieur de la Cote d'Or
 *       94114 Arcueil Cedex
 *       France
 */

#include "xgoban.h"

static int  size = 19;
static int  board[21][21];
static int  tmp_board[21][21];

static int  x_ko = 0;
static int  y_ko = 0;
static int  last_player;

static long mark = 0;
static long marks[21][21];

/*******************************************************************************************************
 *
 *   Recopie le goban <src> dans <dst>. Utile pour faire la difference de deux positions et
 *   etablir les proprietes AddBlack, AddWhite et AddEmtpy.
 */
	
static void copyBoard (src, dst)
int src[21][21];
int dst[21][21];
{
  int x;
  int y;
  
  for (x = 0; x < size + 2; x++)
    for (y = 0; y < size + 2; y++)
      dst[x][y] = src[x][y];
}

/*******************************************************************************************************
 *
 *   Compte les libertes d'une chaine.
 */

static int countLiberty (board, x, y, color)
int  board[21][21];
int  x;
int  y;
int  color;
{
  if (marks[x][y] != mark) {
    marks[x][y] = mark;

    if (board[x][y] == SG_EmptyPoint) return 1;
    if (board[x][y] == SG_Border    ) return 0;
    if (board[x][y] != color        ) return 0;

    return (countLiberty (board, x - 1, y, color) +
	    countLiberty (board, x + 1, y, color) +
	    countLiberty (board, x, y - 1, color) +
	    countLiberty (board, x, y + 1, color));
  }
  
  return 0;
}

/*******************************************************************************************************
 *
 *   Supprime une chaine du goban.
 */

static void removeString (board, x, y, color)
int  board[21][21];
int  x;
int  y;
int  color;
{
  if (board[x][y] == color) {
    board[x][y] = SG_EmptyPoint;

    removeString (board, x + 1, y, color);
    removeString (board, x - 1, y, color);
    removeString (board, x, y + 1, color);
    removeString (board, x, y - 1, color);
  }
}

/*******************************************************************************************************
 *
 *   Fait la difference entre deux positions et construit la liste des pierres a poser ou enlever.
 *   On distingue EB et EW pour que cette propriete soit reversible.
 */

static void getSetup (dst, old, new)
unsigned char **dst;
int             old[21][21];
int             new[21][21];
{
  int            x;
  int            y;
  unsigned char  setup[361 * 2];
  unsigned char *ptr = setup;

  for (x = 1; x < size + 1; x++)
    for (y = 1; y < size + 1; y++)
      if (new[x][y] != old[x][y])
	if (old[x][y] == SG_BlackStone) {
	  *ptr++ = SG_SETEB | ((unsigned char) x & 0x3f);
	  *ptr++ = SG_SETEB | ((unsigned char) y & 0x3f);
	}
  
	else if (old[x][y] == SG_WhiteStone) {
	  *ptr++ = SG_SETEW | ((unsigned char) x & 0x3f);
	  *ptr++ = SG_SETEW | ((unsigned char) y & 0x3f);
	}
  
  for (x = 1; x < size + 1; x++)
    for (y = 1; y < size + 1; y++)
      if (new[x][y] != old[x][y])
	if (new[x][y] == SG_BlackStone) {
	  *ptr++ = SG_SETB | ((unsigned char) x & 0x3f);
	  *ptr++ = SG_SETB | ((unsigned char) y & 0x3f);
	}
  
  for (x = 1; x < size + 1; x++)
    for (y = 1; y < size + 1; y++)
      if (new[x][y] != old[x][y])
	if (new[x][y] == SG_WhiteStone) {
	  *ptr++ = SG_SETW | ((unsigned char) x & 0x3f);
	  *ptr++ = SG_SETW | ((unsigned char) y & 0x3f);
	}
  
  if (ptr != setup) {
    *ptr++ = SG_EOS;
    SG_strcpy (dst, setup, SG_EOS);
  }
}

/*******************************************************************************************************
 *
 *   Quelques fonctions pour editer des diagrames.
 */

void StartDiagram () 
{
  copyBoard (board, tmp_board);
}
 
void AddStone (x, y, color)
int x;
int y;
int color;
{
  board[x][y] = color;
}

unsigned char *EndDiagram ()
{
  unsigned char *ptr = NULL;

  getSetup (&ptr, tmp_board, board);
  copyBoard (tmp_board, board);

  return ptr;
}

/*******************************************************************************************************
 */

int Play (node)
SG_NodePtr    node;
{
  SG_PropertyPtr  property;
  SG_NodePtr      tmp;
  unsigned char  *ptr;
  int             x;
  int             y;
  int             color;
  int             prisoner;
  int             lib;

  if (node != NULL)
    switch (node->type) {

    case SG_EventType   :
      size     = 19;
      property = node->property;
      
      while (property != NULL) {
	if (property->id == SG_SiZe)
	  if (property->data.ivalue > 1 && property->data.ivalue < 20)
	    size = property->data.ivalue;

	property = property->next;
      }
      
      for (x = 0; x < size + 2; x++)
	for (y = 0; y < size + 2; y++)
	  board[x][y] = (x == 0 || y == 0 || x == size + 1 || y == size + 1) ? 
	    SG_Border : SG_EmptyPoint;
	
    case SG_DiagramType :
      
      node->num = 0;

      if (node->setup != NULL) {
	copyBoard (board, tmp_board);

	ptr = node->setup;
	
	while (*ptr != SG_EOS) {
	  color = (int) (*ptr   & 0xc0);
	  x     = (int) (*ptr++ & 0x3f);
	  y     = (int) (*ptr++ & 0x3f);

	  switch (color) {
	  case SG_SETB  : board[x][y] = SG_BlackStone; break;
	  case SG_SETW  : board[x][y] = SG_WhiteStone; break;
	  case SG_SETEB :
	  case SG_SETEW : board[x][y] = SG_EmptyPoint; break;
	  }

	}

	getSetup (&node->setup, tmp_board, board);
      }

      x_ko = y_ko = 0;
      break;
      
    case SG_MoveType :

      if (node->num == -1) {
	tmp = node;
	while (tmp->left != NULL) tmp = tmp->left;
	tmp = tmp->up;
	
	if (tmp != NULL)
	  node->num = tmp->num + 1;
      }

      x            = node->x;
      y            = node->y;
      color        = node->color;
      node->player = (node->color == SG_BlackStone) ? SG_WhiteStone : SG_BlackStone;

      if ((x_ko != 0 || y_ko != 0) && color != last_player)
	SG_MakeProperty (node, SG_ko, (last_player << 16) + (x_ko << 8) + y_ko);
      
      if (x == 0 && y == 0) {
	x_ko = y_ko = 0;
	return SG_OK;
      }

      if (board[x][y] != SG_EmptyPoint)
	return SG_PlayIllegal;
      
      if (x == x_ko && y == y_ko && color != last_player)
	return SG_PlayKo;

      board[x][y] = color;

      prisoner = 0;
      color    = (color == SG_BlackStone) ? SG_WhiteStone : SG_BlackStone;
      
      mark++; if (board[x + 1][y] == color && countLiberty (board, x + 1, y, color) == 0) prisoner |= 1;
      mark++; if (board[x - 1][y] == color && countLiberty (board, x - 1, y, color) == 0) prisoner |= 2;
      mark++; if (board[x][y + 1] == color && countLiberty (board, x, y + 1, color) == 0) prisoner |= 4;
      mark++; if (board[x][y - 1] == color && countLiberty (board, x, y - 1, color) == 0) prisoner |= 8;

      mark++; 
      if ((lib = countLiberty (board, x, y, board[x][y])) == 0 && prisoner == 0) {
	board[x][y] = SG_EmptyPoint;
	return SG_PlaySuicide;
      }

      x_ko = y_ko = 0;

      if (prisoner != 0) {
	copyBoard (board, tmp_board);

	if (prisoner & 1) removeString (board, x + 1, y, color);
	if (prisoner & 2) removeString (board, x - 1, y, color);
	if (prisoner & 4) removeString (board, x, y + 1, color);
	if (prisoner & 8) removeString (board, x, y - 1, color);

	getSetup (&node->setup, tmp_board, board);

	ptr = node->setup;

	prisoner = 0;
	while (*ptr != SG_EOS) {
	  prisoner++;
	  ptr += 2;
	}

	if (prisoner == 1 && lib == 0 &&
	    board[x + 1][y] != board[x][y] &&
	    board[x - 1][y] != board[x][y] &&
	    board[x][y + 1] != board[x][y] &&
	    board[x][y - 1] != board[x][y]) {

	  x_ko = (int) (*node->setup       & 0x3f);
	  y_ko = (int) (*(node->setup + 1) & 0x3f);

	  SG_MakeProperty (node, SG_KO, (x_ko << 8) + y_ko);
	}
      }
      
      last_player = (int) node->color;
      break;
    }

  return SG_OK;
}

/*******************************************************************************************************
 */
	
void Unplay (node)
SG_NodePtr    node;
{
  SG_PropertyPtr  property;
  unsigned char  *ptr;
  int             x;
  int             y;
  int             color;

  if (node != NULL)
    switch (node->type) {
      
    case SG_EventType   :
      size     = 19;
      
      for (x = 0; x < size + 2; x++)
	for (y = 0; y < size + 2; y++)
	  board[x][y] = (x == 0 || y == 0 || x == size + 1 || y == size + 1) ? 
	    SG_Border : SG_EmptyPoint;

    case SG_DiagramType :
      
      if (node->setup != NULL) {
	
	ptr = node->setup;
	
	while (*ptr != SG_EOS) {
	  color = (int) (*ptr   & 0xc0);
	  x     = (int) (*ptr++ & 0x3f);
	  y     = (int) (*ptr++ & 0x3f);
	  
	  switch (color) {
	  case SG_SETEB : board[x][y] = SG_BlackStone; break;
	  case SG_SETEW : board[x][y] = SG_WhiteStone; break;
	  case SG_SETB  :
	  case SG_SETW  : board[x][y] = SG_EmptyPoint; break;
	  }
	}
      }
      break;
      
    case SG_MoveType :
      x_ko = y_ko = 0;
      last_player = SG_EmptyPoint;

      property = node->property;

      while (property != NULL) {
	if (property->id == SG_ko) {
	  last_player = (int) ( property->data.ivalue >> 16);
	  x_ko        = (int) ((property->data.ivalue >> 8) & 0x3f);
	  y_ko        = (int) ( property->data.ivalue       & 0x3f);
	  break;
	}

	property = property->next;
      }

      x = (int) node->x;
      y = (int) node->y;
      
      if (x == 0 && y == 0)
	break;

      board[x][y] = SG_EmptyPoint;
      
      if (node->setup != NULL) {
	ptr   = node->setup;
	color = (node->color == SG_BlackStone) ? SG_WhiteStone : SG_BlackStone;
	
	while (*ptr != SG_EOS) {
	  x = (int) (*ptr++ & 0x3f);
	  y = (int) (*ptr++ & 0x3f);
	  
	  board[x][y] = color;
	}
      }
      break;
    }
}

/*******************************************************************************************************
 */
