/*
 * ai.c
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more details see the file COPYING.
 */

#include <gnome.h>

#include "ai.h"

static guint8	history[MAX_HPOINTER][14];
static guint	vpointer;
static GRand	*rgen;

gint8	board0[SIZE][SIZE];
gint	vsquares[MAX_VPOINTER][2];
guint	move_count;
guint	whose_turn;
guint	bcount;
guint	wcount;


/* Initialization of data */
void
init (void)
{
  guint i, j;

  rgen = g_rand_new ();

  for (i = 0; i < SIZE; i++)
    for (j = 0; j < SIZE; j++)
      board0[i][j] = BLANK;

  board0[0][0]               = WHITE;
  board0[0][SIZE - 1]        = BLACK;
  board0[SIZE - 1][0]        = BLACK;
  board0[SIZE - 1][SIZE - 1] = WHITE;

  vpointer   = 0;
  move_count = 0;
  whose_turn = WHITE;
  bcount     = 2;
  wcount     = 2;
}

gboolean
is_valid (guint who, guint yx)
{
  gint oyx = yx / 256;
  gint nyx = yx % 256;
  
  if (oyx >= SIZE * SIZE || nyx >= SIZE * SIZE)
    return FALSE;
  if (board0[oyx / SIZE][oyx % SIZE] != (gint8) who)
    return FALSE;
  if (board0[nyx / SIZE][nyx % SIZE] != BLANK)
    return FALSE;
  if (oyx % SIZE > nyx % SIZE + 2 || oyx % SIZE < nyx % SIZE - 2 
      || oyx / SIZE > nyx / SIZE + 2 || oyx / SIZE < nyx / SIZE - 2)
    return FALSE;
  return TRUE;
}

void
move_board (guint yx)
{
  gint i, j;
  gint oyx = yx / 256, nyx = yx % 256;
  guint count = 0, status = 0, not_me = OTHER_PLAYER (whose_turn);

  for (i = nyx / SIZE - 1; i < nyx / SIZE + 2; i++)
    for (j = nyx % SIZE - 1; j < nyx % SIZE + 2; j++)
      if (i >= 0 && i < SIZE && j >= 0 && j < SIZE) {
	if (board0[i][j] == (gint8) whose_turn)
	  status = 1;
	else if (board0[i][j] == (gint8) not_me) {
	  board0[i][j] = (gint8) whose_turn;
	  history[move_count][4 + ++count] = (guint8) (i * SIZE + j);
	}
      }

  history[move_count][0]   = (guint8) whose_turn;
  history[move_count][1]   = (guint8) oyx;
  history[move_count][2]   = (guint8) nyx;
  history[move_count][3]   = (guint8) status;
  history[move_count++][4] = (guint8) count;
  board0[nyx / SIZE][nyx % SIZE]   = (gint8) whose_turn;
  if (!status)
    board0[oyx / SIZE][oyx % SIZE] = BLANK;

  if (whose_turn == WHITE) {
    wcount += count + status;
    bcount -= count;
  } else {
    bcount += count + status;
    wcount -= count;
  }

  whose_turn = OTHER_PLAYER (whose_turn);
}

/* Back to before board0 */
void
back (void)
{
  guint oyx, nyx, count, status;
  gint8 not_me;

  whose_turn = (guint) history[--move_count][0];
  oyx        = (guint) history[move_count][1];
  nyx        = (guint) history[move_count][2];
  status     = (guint) history[move_count][3];
  count      = (guint) history[move_count][4];

  if (whose_turn == WHITE) {
    wcount -= count + status;
    bcount += count;
  } else {
    bcount -= count + status;
    wcount += count;
  }

  board0[nyx / SIZE][nyx % SIZE]   = BLANK;
  if (!status)
    board0[oyx / SIZE][oyx % SIZE] = (gint8) whose_turn;

  not_me = (gint8) OTHER_PLAYER (whose_turn);
  while (count > 0) {
    oyx = (guint) history[move_count][4 + count--];
    board0[oyx / SIZE][oyx % SIZE] = not_me;
  }
}

guint
entry_valid_move0 (guint oyx, guint j)
{
  gint oy = oyx / SIZE;
  gint ox = oyx % SIZE;
  gint i, ny, nx;

  i = j;
  for (ny = oy - 2; ny < oy + 3; ny++)
    for (nx = ox - 2; nx < ox + 3; nx++)
      if (ny >= 0 && ny < SIZE && nx >= 0 && nx < SIZE) {
	if (is_valid (whose_turn, oyx * 256 + ny * SIZE + nx)) {
	  vsquares[++i][1] = oyx * 256 + ny * SIZE + nx;
	  move_board (vsquares[i][1]);
	  if (whose_turn == WHITE)
	    vsquares[i][0] = wcount - bcount;
	  else
	    vsquares[i][0] = bcount - wcount;
	  back ();
	}
      }
  
  return i;
}

guint
entry_valid_move (void)
{
  guint ox, oy, j = vpointer;

  for (oy = 0; oy < SIZE; oy++)
    for (ox = 0; ox < SIZE; ox++)
      j = entry_valid_move0 (oy * SIZE + ox, j);

  return j - vpointer;
}

/* Sort vsquares by small order */
static void
sort (guint l, guint r)
{
  guint i, j;
  gint v, w, x;

  i = l;
  j = r;
  x = vsquares[(l + r) / 2][0];
  do {
    while (vsquares[i][0] < x)
      i++;
    while (x < vsquares[j][0])
      j--;
    if (i < j) {
      v = vsquares[i][0];
      w = vsquares[i][1];
      vsquares[i][0] = vsquares[j][0];
      vsquares[i][1] = vsquares[j][1];
      vsquares[j][0] = v;
      vsquares[j][1] = w;
    }
    if (i <= j) {
      i++;
      j--;
    }
  }
  while (i <= j);

  if (l < j)
    sort (l, j);
  if (i < r)
    sort (i, r);
}

/* alpha-beta search */
static gint
search (guint n, gint a, gint b)
{
  guint i, j;
  gint aa, bb;

  if (!n) {			        /* end ? */
    if (whose_turn == WHITE)
      return wcount - bcount;
    return bcount - wcount;
  }

  aa = a;
  j = entry_valid_move ();
  if (!j) {			        /* end ? */
    aa = wcount - bcount;
    if (whose_turn == BLACK)
      aa = -aa;
    if (aa > 0)
      return aa + 1000;
    if (aa < 0)
      return aa - 1000;
    return 0;
  }

  i = vpointer;			        /* save vpointer    */
  vpointer = i + j + 1;		        /* new vpointer     */
  sort (i + 1, vpointer - 1);	        /* sort vsquares    */

  aa = a;
  j = i;
  while (aa < b && ++j < vpointer) {    /* evaluate squares */
    move_board (vsquares[j][1]);
    bb = -search (n - 1, -b, -aa);
    back ();
    if (bb > aa) {
      aa = bb;
      vsquares[i][1] = vsquares[j][1];	/* save this square */
    }
  }

  vpointer = i;			        /* pop vpointer      */
  return aa;
}

static gint
random_select (void)
{
  guint i, j;

  j = entry_valid_move ();
  i = vpointer + 1;
  if (j > i)
    i = g_rand_int_range (rgen, i, j + 1);

  vsquares[0][1] = vsquares[i][1];
  return 0;
}

gint
computer_move (guint level)
{
  vpointer = 0;
  if (move_count < BEST)
    vsquares[0][0] = random_select ();
  else if (move_count < BEST * 3)
    vsquares[0][0] = search (level + 1, -32767, 32767);
  else if (SIZE * SIZE - bcount - wcount < BEST * 2)
    vsquares[0][0] = search (level + 2, -32767, 32767);
  else if (SIZE * SIZE - bcount - wcount < BEST * 3)
    vsquares[0][0] = search (level + 1, -32767, 32767);
  else
    vsquares[0][0] = search (level, -32767, 32767);

  return vsquares[0][1];
}


-------------------------------------------------------------------


/*
 * ai.h
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more details see the file COPYING.
 */

#define SIZE			7
#define BEST			4
#define MAX_DEPTH		5
#define MAX_MOVE_COUNT		100
#define MAX_VPOINTER		MAX_DEPTH * 120
#define MAX_HPOINTER		MAX_MOVE_COUNT

#define BLACK			49
#define WHITE			79
#define BLANK			1

#define OUTSIDE			(BLACK + WHITE - 1)
#define OTHER_PLAYER(who)	(OUTSIDE - who + 1)

void init (void);

gboolean is_valid (guint who, guint yx);

void move_board (guint yx);

void back (void);

guint entry_valid_move0 (guint oyx, guint j);

guint entry_valid_move (void);

gint computer_move (guint level);


-------------------------------------------------------------------


/*
 * gataxx.c
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more details see the file COPYING.
 */


#include "config.h"
#include <gnome.h>
#include <glib.h>
#include <gconf/gconf-client.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>

#include "games-stock.h"

#include "ai.h"
#include "properties.h"
#include "gataxx.h"

#ifdef GGZ_CLIENT
#include <ggzmod.h>
#include <ggz-gtk.h>
#include "ggz-network.h"
extern int fd;
#endif

static const gchar *strs[] = {
  N_("Dark's move"),			/* 0 */
  N_("Light's move"),			/* 1 */
  N_("Count overflow!"),	        /* 2 */
  NULL,				        /* 3 */
  N_("Light player wins!"),		/* 4 */
  N_("Dark player wins!"),		/* 5 */
  N_("The game was a draw."),		/* 6 */
  N_("Now thinking."),			/* 7 */
  N_("not iagno's file"),		/* 8 */
  N_("Invalid move."),			/* 9 */
  N_("Welcome to Gataxx!")	        /* 10 */
};

static const gchar *keys[MAX_KEYS] = {
  "/apps/gataxx/black_level",		/* 0 */
  "/apps/gataxx/white_level",		/* 1 */
  "/apps/gataxx/none_animation",	/* 2 */
  "/apps/gataxx/use_quick_moves",	/* 3 */
  "/apps/gataxx/hide_valid_squares",	/* 4 */
  "/apps/gataxx/hide_last_move",	/* 5 */
  "/apps/gataxx/hide_final_results",	/* 6 */
  "/apps/gataxx/hide_toolbar",		/* 7 */
  "/apps/gataxx/tileset",		/* 8 */
  "/apps/gataxx/bg_color_name",		/* 9 */
  "/apps/gataxx/board_width",		/* 10 */
  "/apps/gataxx/board_height",		/* 11 */
  "gataxx",				/* 12 Window title */
  N_("Gataxx Preferences")		/* 13 Properties dialog title */
};

static guint 	kifu[MAX_MOVE_COUNT + 1][2];
static gint 	old_yx = -1;
static guint	t_computer_level[2];
static gboolean t_hide_valid_squares;
static gboolean t_hide_last_move;
static guint 	computer_id = 0;
static guint 	computer_id1 = 0;


extern gint8	pixmaps[MAX_SIZE][MAX_SIZE];
extern gint8	board[MAX_SIZE][MAX_SIZE];

extern GtkWidget *window;
extern GtkWidget *file_open_dialog;
extern GtkWidget *file_save_dialog;
extern GtkWidget *counter;
extern GtkWidget *score[4];
extern GtkWidget *entry;

extern guint	tile_width;
extern guint	tile_height;
extern guint	computer_level[2];
extern gboolean	hide_valid_squares;
extern gboolean	hide_last_move;
extern gboolean	hide_final_results;
extern guint	computer_speed;
extern gboolean game_in_progress;
extern gboolean gameover;
extern gboolean flip_tiles;
extern guint 	flip_final_id;
extern gboolean play_sound;
extern guint	t_move_count;
extern guint	t_whose_turn;
extern guint	t_bcount;
extern guint	t_wcount;
extern gboolean ggz_network_mode;


extern gint8 	board0[SIZE][SIZE];
extern gint	vsquares[MAX_VPOINTER][2];
extern guint	move_count;
extern guint	whose_turn;
extern guint	bcount;
extern guint	wcount;


static gboolean move (guint yx);

static void
copy_board (void)
{
  guint i, j;

  for (i = 0; i < SIZE; i++)
    for (j = 0; j < SIZE; j++)
      board[i][j] = board0[i][j];

  t_move_count = move_count;
  t_whose_turn = whose_turn;
  t_bcount     = bcount;
  t_wcount     = wcount;
}

static void
gui_hint (gint best_yx, gint score)
{
  gint oyx = best_yx / 256, nyx = best_yx % 256;
  gchar message[16];

  message[0] = (gchar) (oyx % SIZE + 0x41);
  message[1] = (gchar) (oyx / SIZE + 0x31);
  message[2] = 0x2D;
  message[3] = (gchar) (nyx % SIZE + 0x41);
  message[4] = (gchar) (nyx / SIZE + 0x31);
  message[5] = 0x20;
  sprintf (&message[6], _("%d"), score);

  gui_entry (message);
}

static void
set_valid_squares (void)
{
  gint i, yx;

  for (yx = 0; yx < SIZE * SIZE; yx++)
    if (board[yx / SIZE][yx % SIZE] == -BLANK)
      board[yx / SIZE][yx % SIZE] = BLANK;
  if (!hide_valid_squares) {
    i = entry_valid_move0 (old_yx, 0);
    while (i > 0) {
      yx = vsquares[i--][1] % 256;
      board[yx / SIZE][yx % SIZE] = -BLANK;
    }
  }

  flip_tiles = TRUE;
};

static void
set_last_move (void)
{
  guint yx;

  if (move_count < 1 || move_count > MAX_MOVE_COUNT - MAX_DEPTH)
    return;

  yx = kifu[move_count - 1][1] % 256;
  board[yx / SIZE][yx % SIZE] =
	  (gint8) kifu[move_count - 1][0] * ((!hide_last_move)? -1: 1);
}

static gboolean
check_game_over (void)
{
  gui_entry (" ");
  gtk_entry_set_position ((GtkEntry *) entry, 0);
  set_last_move ();

  if (entry_valid_move ()) {
    gui_message ((gchar *) strs[PLAYER2NUMBER (whose_turn)]);
    continue_timer ();
    return FALSE;
  }

  if (wcount > bcount)
    gui_message ((gchar *) strs[4]);
  if (bcount > wcount)
    gui_message ((gchar *) strs[5]);
  if (wcount == bcount)
    gui_message ((gchar *) strs[6]);

  t_whose_turn     = whose_turn = 0;
  game_in_progress = FALSE;
  gameover         = TRUE;
  if (!hide_final_results)
    flip_final_id = g_timeout_add (computer_speed, flip_final_results, NULL);

  return TRUE;
}

static gboolean
t_computer_move (gpointer level)
{
  if (game_in_progress)
    move (computer_move (GPOINTER_TO_INT (level)));

  computer_id = 0;
  return FALSE;
}

static gboolean
check_computer_players (void)
{
  guint level;

  if (computer_id) {
    g_source_remove (computer_id);
    computer_id = 0;
  }

  level = computer_level[PLAYER2NUMBER (whose_turn)];
  if (level) {
    gui_entry ((gchar *) strs[7]);
    computer_id =
      g_timeout_add (computer_speed, (GSourceFunc) t_computer_move,
		     GINT_TO_POINTER (level));
  }

  return FALSE;
}

static gboolean
check_computer_level_changed (gpointer user_data)
{
  if (t_hide_last_move != hide_last_move) {
    t_hide_last_move = hide_last_move;
    set_last_move ();
    flip_tiles = TRUE;
  };
  if (t_computer_level[0] == computer_level[0]
      && t_computer_level[1] == computer_level[1])
    return TRUE;

  t_computer_level[0] = computer_level[0];
  t_computer_level[1] = computer_level[1];
  if (game_in_progress)
    check_computer_players ();

  return TRUE;
};

static void
set_computerid1 (void)
{
  t_computer_level[0]  = computer_level[0];
  t_computer_level[1]  = computer_level[1];
  t_hide_valid_squares = hide_valid_squares;
  t_hide_last_move     = hide_last_move;
  computer_id1 =
    g_timeout_add (500, (GSourceFunc) check_computer_level_changed, NULL);
};

static gboolean
check_valid_move (guint who, guint yx)
{
  if (computer_level[PLAYER2NUMBER (whose_turn)])
    return FALSE;
  if (who != whose_turn)
    return FALSE;

  return is_valid (who, yx);
}

static gboolean
move (guint yx)
{
  if (game_in_progress) {
    stop_timer ();
    kifu[move_count][0]     = whose_turn;
    kifu[move_count][1]     = yx;
    kifu[move_count + 1][0] = 0;
  }

  move_board (yx);
  if (move_count >= MAX_MOVE_COUNT - MAX_DEPTH) {
    gui_message ((gchar *) strs[2]);
    game_in_progress = FALSE;
    return FALSE;
  }

  copy_board ();
  gui_status ();
  check_game_over ();
  old_yx     = -1;
  flip_tiles = TRUE;
  play_sound = TRUE;
  if (!game_in_progress)
    return FALSE;

  return check_computer_players ();
}

static void
check_back (void)
{
  if (flip_final_id) {
    g_source_remove (flip_final_id);
    flip_final_id = 0;
  }
  if (computer_id) {
    g_source_remove (computer_id);
    computer_id = 0;
  }
  if (game_in_progress) {
    stop_timer ();
    game_in_progress = FALSE;
  }
}

void
init_new_game (void)
{
  check_back ();
  reset_timer ();
  init ();
  copy_board ();
  check_game_over ();
  redraw_board (NULL);
}

void
start_game (void)
{
  if (game_in_progress)
    return;
  game_in_progress = TRUE;
  start_timer();
  if (check_game_over ())
    return;

  kifu[move_count][0] = 0;
  flip_tiles          = TRUE;

  check_computer_players ();
}

#ifdef GGZ_CLIENT
int
get_move (void)
{
  int yx;

  if (ggz_read_int (fd, &yx) < 0)
    return ERROR_CANT_READ;
  if (yx < 0)
    return yx;
  if (!check_valid_move (whose_turn, (guint) yx))
    return ERROR_INVALID_MOVE;

  move ((guint) yx);

  return SERVER_OK;
}

int
get_sync (void)
{
  int i, who, yx, count;
  guint t_kifu[MAX_MOVE_COUNT + 1][2];
  gboolean err;

  if (ggz_read_int (fd, &count) < 0 || count < 0 || count > MAX_MOVE_COUNT)
    return ERROR_CANT_READ;

  for (i = 0; i < count; i++) {
    if (ggz_read_int (fd, &who) < 0 || ggz_read_int (fd, &yx) < 0)
      return ERROR_CANT_READ;
    t_kifu[i][0] = (guint) who;
    t_kifu[i][1] = (guint) yx;
  }
  t_kifu[i][0] = 0;

  if (ggz_read_int (fd, &who) < 0)
    return ERROR_CANT_READ;

  i = 0;
  err = (move_count != (guint) count);
  while (!err && i < count) {
    err = (kifu[i][0] != t_kifu[i][0] || kifu[i][1] != t_kifu[i][1]);
    i++;
  }
  if (!err)
    err = (whose_turn != (guint) who);
  if (!err)
    return SERVER_OK;

/* auto recovery */
  stop_timer ();
  init ();
  i = 0;
  while (t_kifu[i][0]) {
    kifu[i][0] = t_kifu[i][0];
    kifu[i][1] = t_kifu[i][1];
    whose_turn = kifu[i][0];
    move_board (kifu[i++][1]);
  }
  kifu[i][0] = 0;

  copy_board ();
  check_game_over ();
  redraw_board (NULL);
  return SERVER_OK;
}
#endif

static void
on_new_activate (GtkMenuItem *menuitem, gpointer user_data)
{
#ifdef GGZ_CLIENT
  if (ggz_network_mode)
    return on_again_activate (menuitem, user_data);
#endif

  init_new_game ();
  start_game ();
}

static void
on_head_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  check_back ();
  if (move_count < 1)
    return;

  init ();
  copy_board ();
  check_game_over ();
  redraw_board (NULL);
}

static void
on_back_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  check_back ();
  if (move_count < 1)
    return;

  back ();
  copy_board ();
  check_game_over ();
  gui_status ();
  flip_tiles = TRUE;
  play_sound = TRUE;
}

static gboolean
check_forward (void)
{
  if (game_in_progress)
    return FALSE;
  if (move_count >= MAX_MOVE_COUNT)
    return FALSE;
  if (!kifu[move_count][0])
    return FALSE;

  return TRUE;
}

static void
on_forward_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  if (!check_forward ())
    return;

  move (kifu[move_count][1]);
}

static void
on_last_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  if (!check_forward ())
    return;

  while (kifu[move_count][0]) {
    whose_turn = kifu[move_count][0];
    move_board (kifu[move_count][1]);
  }

  copy_board ();
  check_game_over ();
  redraw_board (NULL);
}

static void
on_hint_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  gint best_yx;
  
  if (move_count >= MAX_MOVE_COUNT)
    return;
  if (!entry_valid_move ())
    return;

  best_yx = computer_move (MAX_DEPTH);
  gui_hint (best_yx, vsquares[0][0]);
}

static void
on_redo_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  start_game ();
}

static void
read_kifu (int fd)
{
  gchar buf[12];
  guint i = 0;
  ssize_t err;

  err = read (fd, buf, 7);
  buf[7] = 0;
  if (!g_str_equal (buf, _("gataxx\n"))) {
    gui_message ((gchar *) strs[8]);
    return;
  }

  err = read (fd, buf, 11);
  while (err > 0) {
    kifu[i][0] = (buf[3] == 0x42) ? BLACK : WHITE;
    kifu[i][1] = (guint) ((buf[6] - 0x31) * SIZE + buf[5] - 0x41) * 256;
    kifu[i++][1] += (guint) ((buf[9] - 0x31) * SIZE + buf[8] - 0x41);
    err = read (fd, buf, 11);
  }

  kifu[i][0] = 0;
}

static void
on_open_clicked (GtkButton *button, gpointer user_data)
{
  gchar *filename;
  int fd;

  filename =
    gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_open_dialog));
  fd = open (filename, 0, 0644);
  if (fd) {
    read_kifu (fd);
    close (fd);
  }
  gtk_widget_destroy (file_open_dialog);
  g_free (filename);
  on_head_activate (NULL, NULL);
}

static void
on_open_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  game_in_progress = FALSE;
  reset_timer ();
  file_open_dialog =
    create_file_open_dialog ((GtkSignalFunc *) on_open_clicked);
  gtk_widget_show (file_open_dialog);
}

static void
write_kifu (int fd)
{
  gchar buf[12] = { 0, 0, 0x20, 0, 0x20, 0, 0, 0x2D, 0, 0, 0x0A, 0 };
  guint i = 0;
  ssize_t err;

  err = write (fd, _("gataxx\n"), 7);
  while (kifu[i][0] && err > 0) {
    buf[0] = (gchar) ((i + 1) / 10 + 0x30);
    buf[1] = (gchar) ((i + 1) % 10 + 0x30);
    buf[3] = (kifu[i][0] == BLACK) ? 0x42 : 0x57;
    buf[5] = (gchar) (kifu[i][1] / 256 % SIZE + 0x41);
    buf[6] = (gchar) (kifu[i][1] / 256 / SIZE + 0x31);
    buf[8] = (gchar) (kifu[i][1] % 256 % SIZE + 0x41);
    buf[9] = (gchar) (kifu[i++][1] % 256 / SIZE + 0x31);

    err = write (fd, buf, 11);
  }
}

static void
on_save_clicked (GtkButton *button, gpointer user_data)
{
  gchar *filename;
  int fd;

  filename =
    gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (file_save_dialog));
  fd = creat (filename, 0644);
  if (fd) {
    write_kifu (fd);
    close (fd);
  }

  gtk_widget_destroy (file_save_dialog);
  g_free (filename);
}

static void
on_save_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  GDate *date;
  gchar filename[17];

  date = g_date_new ();
  g_date_set_time_t (date, time (NULL));
  g_date_strftime (filename, 17, _("%y%m%d%H"), date);

  file_save_dialog =
    create_file_save_dialog ((GtkSignalFunc *) on_save_clicked);
  gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (file_save_dialog),
				     filename);
  gtk_widget_show (file_save_dialog);
  g_date_free (date);
}

static void
on_help_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  gnome_help_display ("gataxx.xml", NULL, NULL);
}

static void
on_about_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  const gchar *authors[] = {
    "Chris Rogers", "Sjoerd Langkemper",
    N_("Based on code from Iagno by Ian Peters"), NULL
  };
  const gchar *documenters[] = {"Aaron Weber", NULL};
  gchar *license = games_get_license (_("Gataxx"));
  
  gtk_show_about_dialog (GTK_WINDOW (window),
			 "name", _("Ataxx"),
			 "version", VERSION,
			 "copyright",
			 "Copyright \xc2\xa9 1999-2003 Chris Rogers\n"
			 "Copyright \xc2\xa9 2004-2005 Sjoerd Langkemper\n"
			 "Copyright \xc2\xa9 2005-2006 Callum McKenzie",
			 "license", license, "comments",
	    _("A disk-flipping game where you attempt to dominate the board."),
			 "authors", authors, "documenters", documenters,
			 "translator_credits", _("translator-credits"),
			 "logo-icon-name", "gnome-ataxx", "website",
			 "http://www.gnome.org/projects/gnome-games/",
			 "wrap-license", TRUE, NULL);
  
  g_free (license);
}

static gboolean
check_event_move (guint oyx, guint nyx)
{
  guint yx = oyx * 256 + nyx;
  
#ifdef GGZ_CLIENT
  if (ggz_network_mode) {
    send_my_move ((int) yx);
    return FALSE;
  }
#endif

  if (!game_in_progress)
    return FALSE;
  if (check_valid_move (whose_turn, yx))
    return move (yx);

  gui_message ((gchar *) strs[9]);
  return FALSE;
}

static gboolean
on_drawing_area_button_press_event (GtkWidget *widget,
				    GdkEventButton *event, gpointer user_data)
{
  guint x, y;

  if (event->button != 1)
    return FALSE;

  x = event->x / (tile_width + GRIDWIDTH);
  y = event->y / (tile_height + GRIDWIDTH);
  if (board0[y][x] == (gint8) whose_turn) {
    old_yx = y * SIZE + x;
    set_valid_squares ();
    gui_message ((gchar *) strs[PLAYER2NUMBER (whose_turn)]);
    return FALSE;
  }

  return check_event_move (old_yx, y * SIZE + x);
}

static gboolean
on_entry_key_press_event (GtkWidget *widget,
			  GdkEventKey *event, gpointer user_data)
{
  gchar *ch;
  guint ox, oy, nx, ny;

  ch = (gchar *) gtk_entry_get_text (GTK_ENTRY (entry));
  ox = (guint) ((*ch++ & 0xdf) - 0x41);
  if (!*ch)
    return FALSE;
  oy = (guint) (*ch++ - 0x31);
  if (!*ch)
    return FALSE;
  if (*ch == 0x2D)
    ch++;
  nx = (guint) ((*ch++ & 0xdf) - 0x41);
  if (!*ch)
    return FALSE;
  ny = (guint) (*ch - 0x31);

  return check_event_move (oy * SIZE + ox, ny * SIZE + nx);
}

const GCallback callbacks[MAX_ACTIONS] = {
  NULL,
  (GCallback) on_new_activate,
#ifdef GGZ_CLIENT
  (GCallback) on_network_activate,
  (GCallback) on_leave_activate,
#else
  NULL,
  NULL,
#endif
  (GCallback) on_head_activate,
  (GCallback) on_back_activate,
  (GCallback) on_forward_activate,
  (GCallback) on_last_activate,
  (GCallback) on_hint_activate,
  (GCallback) on_redo_activate,
  (GCallback) on_open_activate,
  (GCallback) on_save_activate,
  (GCallback) on_quit_activate,
  NULL,
  (GCallback) on_properties_activate,
  NULL,
  (GCallback) on_help_activate,
  (GCallback) on_about_activate
};

const GtkSignalFunc signalfuncs[MAX_SIGNALFUNCS] = {
  (GtkSignalFunc) on_drawing_area_button_press_event,
  (GtkSignalFunc) on_entry_key_press_event,
};

int
main (int argc, char *argv[])
{
  GnomeProgram *program;

  gnome_score_init ("gataxx");

  bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
  textdomain (GETTEXT_PACKAGE);

  program =
    gnome_program_init ("gataxx", VERSION, LIBGNOMEUI_MODULE, argc, argv,
			GNOME_PARAM_APP_DATADIR, DATADIR, NULL);

  gconf_init (argc, argv, NULL);
  games_stock_init ();
  gtk_window_set_default_icon_name ("gnome-ataxx");

  init_interface (SIZE, MAX_DEPTH, (gchar **) keys,
		  (GCallback *) callbacks, (GtkSignalFunc *) signalfuncs);

  load_properties ();
  window = create_window ();
  reset_properties ();
  apply_changes ();
  set_computerid1 ();

  init_new_game ();
  gui_message ((gchar *) strs[10]);
  gtk_main ();

  g_object_unref (program);
  return 0;
}


-------------------------------------------------------------------


/*
 * gataxx.h
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more details see the file COPYING.
 */

#ifdef GGZ_CLIENT

#define NETWORK_ENGINE "Gataxx"
#define NETWORK_VERSION "1"

int get_move (void);

int get_sync (void);

void init_new_game (void);

void start_game (void);

#endif


-------------------------------------------------------------------


/* ggz-network.c
 *
 * Copyright (C) 2006 -  Andreas Røsdal <andrearo@pvv.ntnu.no>
 *
 * This game 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, 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 */

#include "config.h"
#include <gnome.h>

#include <ggzmod.h>
#include <ggz-embed.h>
#include <ggz-gtk.h>

#include "games-dlg-chat.h"
#include "games-dlg-players.h"

#include "properties.h"
#include "ggz-network.h"

#include "gataxx.h"

static const gchar *strs[] = {
  N_("Waiting for an opponent to join the game."),	/* 0 */
  N_("Can't read message!"),	/* 1 */
  N_("Can't send message!"),	/* 2 */
  N_("Incorrect opcode!"),	/* 3 */
  N_("Invalid move!"),		/* 4 */
  N_("Not your turn!"),		/* 5 */
  N_("Skip move!"),		/* 6 */
  N_("Not playing now!"),	/* 7 */
  N_("Not game over yet!"),	/* 8 */
  N_("Sync error!"),		/* 9 */
};

static GGZMod	 *ggzmod;
static int	 seat;
static int	 seats[2];
static char	 names[2][17];
static GtkWidget *ggzbox;
static GtkWidget *vpaned;
static GtkWidget *playerlist;
static GtkWidget *chat;

int	 	 fd;

extern GtkWidget *window;
extern GtkWidget *notebook;
extern GtkWidget *hpaned;
extern GtkWidget *properties_dialog;
extern gboolean  flip_tiles;
extern gboolean  game_in_progress;
extern gboolean  ggz_network_mode;


static void
gui_error (int err)
{
  gui_message ((gchar *) strs[-err]);
}

static int
get_seat (void)
{
  if (ggz_read_int (fd, &seat) < 0)
    return ERROR_CANT_READ;
  if (!seat)
    gui_error (WAITING);

  return SERVER_OK;
}

static int
get_players (void)
{
  int i;

  for (i = 0; i < 2; i++) {
    if (ggz_read_int (fd, &seats[i]) < 0)
      return ERROR_CANT_READ;
    if (seats[i] != GGZ_SEAT_OPEN
	&& ggz_read_string (fd, (char *) &names[i], 17) < 0)
      return ERROR_CANT_READ;
  }

  return SERVER_OK;
}

static int
get_gameover (void)
{
  int winner;

  if (ggz_read_int (fd, &winner) < 0)
    return ERROR_CANT_READ;

  return SERVER_OK;
}

static int
game_handle_io (GGZMod *mod)
{
  int err = 0, op = -1;

  fd = ggzmod_get_server_fd (mod);

  // Read the fd
  if (ggz_read_int (fd, &op) < 0) {
    gui_error (ERROR_CANT_READ);
    return FALSE;
  }

  switch (op) {
    case RVR_MSG_SEAT:		/* 0 */
      err = get_seat ();
      break;
    case RVR_MSG_PLAYERS:	/* 1 */
      err = get_players ();
      break;
    case RVR_MSG_MOVE:		/* 2 */
      err = get_move ();
      if (err == ERROR_NOT_PLAYING && game_in_progress) {
        stop_timer ();
	game_in_progress = FALSE;
      }
      break;
    case RVR_MSG_GAMEOVER:	/* 3 */
      err = get_gameover ();
      break;
    case RVR_MSG_START:		/* 5 */
      game_in_progress = FALSE;
      reset_timer ();
      start_game ();
      break;
    case RVR_MSG_SYNC:		/* 6 */
      err = get_sync ();
      break;
    case RVR_MSG_INIT:		/* 7 */
      init_new_game ();
      gui_error (WAITING);
      break;
    default:
      err = ERROR_OPCODE;
      break;
  }
  if (err < 0)
    gui_error (err);

  return TRUE;
}

static gboolean
handle_ggzmod (GIOChannel *channel, GIOCondition cond, gpointer data)
{
  GGZMod *mod = data;

  return (ggzmod_dispatch (mod) >= 0);
}

static gboolean
handle_game_server (GIOChannel *channel, GIOCondition cond, gpointer data)
{
  GGZMod *mod = data;

  return game_handle_io (mod);
}

static void
handle_ggzmod_server (GGZMod *mod, GGZModEvent e, const void *data)
{
  const int *fd = data;
  GIOChannel *channel;

  ggzmod_set_state (mod, GGZMOD_STATE_PLAYING);
  channel = g_io_channel_unix_new (*fd);
  g_io_add_watch (channel, G_IO_IN, handle_game_server, mod);
}

static void
network_init (void)
{
  GIOChannel *channel;
  
  if (!ggzmod_is_ggz_mode ())
    return;

  ggzmod = ggzmod_new (GGZMOD_GAME);
  ggzmod_set_handler (ggzmod, GGZMOD_EVENT_SERVER, handle_ggzmod_server);
  if (ggzmod_connect (ggzmod) != 0)
    return;

  channel = g_io_channel_unix_new (ggzmod_get_fd (ggzmod));
  g_io_add_watch (channel, G_IO_IN, handle_ggzmod, ggzmod);

  init_player_list (ggzmod);
  init_chat (ggzmod);
}

/****************************************************************************
  Callback function that's called by the library when a connection is
  established (or lost) to the GGZ server.  The server parameter gives
  the server (or NULL).
****************************************************************************/
static void
ggz_connected (GGZServer *server)
{
  /* Nothing useful to do... */
}

/****************************************************************************
  Callback function that's called by the library when we launch a game.  This
  means we now have a connection to a gnect server so handling can be given
  back to the regular gnect code.
****************************************************************************/
static void
ggz_game_launched (void)
{
  network_init ();

  gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), MAIN_PAGE);
  init_new_game ();

  vpaned = gtk_vpaned_new ();
  gtk_paned_pack2 (GTK_PANED (hpaned), vpaned, TRUE, TRUE);
  gtk_widget_show (vpaned);
  playerlist = create_playerlist_widget ();
  gtk_paned_pack1 (GTK_PANED (vpaned), playerlist, TRUE, TRUE);
  gtk_widget_show (playerlist);
  chat = create_chat_widget ();
  gtk_paned_pack2 (GTK_PANED (vpaned), chat, TRUE, TRUE);
  gtk_widget_show (chat);
}

/****************************************************************************
  Callback function that's invoked when GGZ is exited.
****************************************************************************/
static void
ggz_closed (void)
{
  ggzmod_disconnect (ggzmod);
  ggzmod_free (ggzmod);
  
  gtk_widget_destroy (ggzbox);
  gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), MAIN_PAGE);

  ggz_network_mode = FALSE;
  load_properties ();
  reset_properties ();
  apply_changes ();
  init_new_game ();
}


void
on_network_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  if (properties_dialog)
    gtk_widget_destroy (properties_dialog);
  ggz_network_mode = TRUE;
  load_properties ();
  reset_properties ();
  apply_changes ();

  ggz_gtk_initialize (FALSE, (void *)ggz_connected,
		      (void *)ggz_game_launched, (void *)ggz_closed,
		      NETWORK_ENGINE, NETWORK_VERSION, "GNOME GGZ");
  ggz_embed_ensure_server ("GNOME GGZ", "games.gnome.org", 5688, _("Player"));

  ggzbox = ggz_gtk_create_main_area (window);
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), ggzbox, NULL);
  gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), NETWORK_PAGE);
  gtk_widget_show (ggzbox);
}

void
on_leave_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  ggz_embed_leave_table ();

  gtk_widget_destroy (chat);
  gtk_widget_destroy (playerlist);
  gtk_widget_destroy (vpaned);
  gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), NETWORK_PAGE);
}

void
on_again_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  if (ggz_write_int (fd, RVR_REQ_AGAIN) < 0)
    gui_error (ERROR_CANT_WRITE);
}

void
send_my_move (int yx)
{
  if (ggz_write_int (fd, RVR_REQ_MOVE) < 0 || ggz_write_int (fd, yx) < 0)
    gui_error (ERROR_CANT_WRITE);
}


-------------------------------------------------------------------


/*
 * File: server.c
 * Author: Ismael Orenstein
 * Project: GGZ Reversi game module
 * Date: 09/17/2000
 * Desc: main loop for the server
 * $Id: server.c,v 1.1 2006/09/05 16:36:27 andreasr Exp $
 *
 * Copyright (C) 2000 Ismael Orenstein.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include "config.h"
#include <gnome.h>
#include <ggzdmod.h>
#include <assert.h>

#include "server.h"

#define COMPUTER_LEVEL 3
#include "ai.h"

static const gchar *strs[] = {
  "Can't read message\n",	/* 0 */
  "Can't send message\n",	/* 1 */
  "Unknown opcode\n",		/* 2 */
  "Invalid move\n",		/* 3 */
  "Wrong turn\n",		/* 4 */
  "Skip move\n",		/* 5 */
  "Not playing error\n",	/* 6 */
  "Play again error\n"		/* 7 */
};

// Global game variables
static GGZdMod	*ggz;
static int	kifu[MAX_MOVE_COUNT + 1][2];
static int	state;

extern guint	move_count;
extern guint	whose_turn;
extern guint	bcount;
extern guint	wcount;

static int game_make_move (int player, int yx);

// Initializes everything
static void
game_init (GGZdMod *ggzdmod)
{
  ggz = ggzdmod;
  init ();
  kifu[0][0] = EMPTY;
  bcount = EMPTY;
  wcount = EMPTY;
  state = RVR_STATE_INIT;

  if (ggzdmod_get_seat (ggz, PLAYER2SEAT (BLACK)).type == GGZ_SEAT_BOT)
    bcount = 1;
  if (ggzdmod_get_seat (ggz, PLAYER2SEAT (WHITE)).type == GGZ_SEAT_BOT)
    wcount = 1;
}

static int
game_check_bot (void)
{
  int seat = PLAYER2SEAT (whose_turn);

  // Is the AI?
  if (ggzdmod_get_seat (ggz, seat).type != GGZ_SEAT_BOT)
    // It's not! Wait until the player sends the message...
    return SERVER_OK;

  // Yes! Wait for a second
  sleep (1);
  return game_make_move ((int)whose_turn, (int)computer_move (COMPUTER_LEVEL));
}

static int
game_start (void)
{
  int fd, seat;

  // Start game variables
  state = RVR_STATE_PLAYING;
  bcount = wcount = 2;
  
  for (seat = 0; seat < ggzdmod_get_num_seats (ggz); seat++) {
    fd = ggzdmod_get_seat (ggz, seat).fd;
    // Don't send anything if the player is a computer!
    if (fd != -1 && ggz_write_int (fd, RVR_MSG_START) < 0)
      return ERROR_CANT_WRITE;
  }

  ggzdmod_log (ggz, "Game has started!\n");
  return game_check_bot ();
}

// Puts human players score = 0
// When they send a REQ_AGAIN message, put it = 1
// When everyone equal = 1, then starts it all again
static int
game_play_again (int seat, int fd)
{
  ggzdmod_log (ggz, "%d wants to play again!\n", seat);

  // Check game is finished or not. 
  if (state != RVR_STATE_DONE && state != RVR_STATE_WAIT) {
    if (ggz_write_int (fd, RVR_MSG_MOVE) < 0
	|| ggz_write_int (fd, ERROR_PLAY_AGAIN) < 0)
      return ERROR_CANT_WRITE;
    return ERROR_PLAY_AGAIN;
  }

  if (state == RVR_STATE_DONE)
    game_init (ggz);

  if (SEAT2PLAYER (seat) == WHITE) {
    wcount = 1;
    if (fd != -1 && ggz_write_int (fd, RVR_MSG_INIT) < 0)
      return ERROR_CANT_WRITE;
  } else if (SEAT2PLAYER (seat) == BLACK) {
    bcount = 1;
    if (fd != -1 && ggz_write_int (fd, RVR_MSG_INIT) < 0)
      return ERROR_CANT_WRITE;
  }

  if (bcount * wcount)
    // Starts again
    return game_start ();

  state = RVR_STATE_WAIT;
  return SERVER_OK;
}

static int
game_check_over (void)
{
  int seat, fd, winner;
  GGZGameResult results[2];

  if (entry_valid_move ())
    return game_check_bot ();

  if (bcount > wcount) {
    results[PLAYER2SEAT (BLACK)] = GGZ_GAME_WIN;
    results[PLAYER2SEAT (WHITE)] = GGZ_GAME_LOSS;
    winner = BLACK;
  } else if (wcount > bcount) {
    results[PLAYER2SEAT (WHITE)] = GGZ_GAME_WIN;
    results[PLAYER2SEAT (BLACK)] = GGZ_GAME_LOSS;
    winner = WHITE;
  } else {
    winner = EMPTY;
    results[0] = results[1] = GGZ_GAME_TIE;
  }

  /* Report game to GGZ */
  ggzdmod_report_game (ggz, NULL, results, NULL);

  // Send message 
  for (seat = 0; seat < ggzdmod_get_num_seats (ggz); seat++) {
    fd = ggzdmod_get_seat (ggz, seat).fd;
    if (fd != -1 && (ggz_write_int (fd, RVR_MSG_GAMEOVER) < 0
		     || ggz_write_int (fd, winner) < 0))
      return ERROR_CANT_WRITE;;
  }

  // Ends everything
  whose_turn = EMPTY;
  state = RVR_STATE_DONE;
  ggzdmod_log (ggz, "Game is over. Waiting to see if we should play again\n");

  return GAME_OVER;
}

static int
game_check_move (int player, int yx)
{
  // Check if it's right time
  if (state != RVR_STATE_PLAYING)
    return ERROR_NOT_PLAYING;
  // Check if it's his turn
  if (player != whose_turn)
    return ERROR_WRONG_TURN;
  // Check if valid yx
  if (!is_valid (player, yx))
    return ERROR_INVALID_MOVE;

  return SERVER_OK;
}

static int
game_make_move (int player, int yx)
{
  int fd, seat;

  kifu[move_count][0]     = player;
  kifu[move_count][1]     = yx;
  kifu[move_count + 1][0] = 0;
  move_board ((guint) yx);

  for (seat = 0; seat < ggzdmod_get_num_seats (ggz); seat++) {
    fd = ggzdmod_get_seat (ggz, seat).fd;
    if (fd != -1 && (ggz_write_int (fd, RVR_MSG_MOVE) < 0
		     || ggz_write_int (fd, yx) < 0))
      return ERROR_CANT_WRITE;
  }

  // Check if game is over
  return game_check_over ();
}

static int
game_send_sync (int seat)
{
  int fd = ggzdmod_get_seat (ggz, seat).fd;
  int i;

  ggzdmod_log (ggz, "Handling sync for player %d", seat);

  // Send SYNC message and move_count
  if (ggz_write_int (fd, RVR_MSG_SYNC) < 0
      || ggz_write_int (fd, (int) move_count) < 0)
    return ERROR_CANT_WRITE;
  // Send kifu
  for (i = 0; i < move_count; i++) {
    if (ggz_write_int (fd, kifu[i][0]) < 0
	|| ggz_write_int (fd, kifu[i][1]) < 0)
      return ERROR_CANT_WRITE;
  }
  // Send current turn
  if (ggz_write_int (fd, (int) whose_turn) < 0)
    return ERROR_CANT_WRITE;

  // That's fine
  return SERVER_OK;
}

static int
game_handle_move (int seat, int *yx)
{
  int status, fd = ggzdmod_get_seat (ggz, seat).fd;

  // Get the yx from the message
  if (ggz_read_int (fd, yx) < 0)
    return ERROR_CANT_READ;

  ggzdmod_log (ggz, "Handling yx %d, for player %d\n", *yx, seat);

  // Check if it's valid
  status = game_check_move (SEAT2PLAYER (seat), *yx);
  if (status < 0) {
    if (ggz_write_int (fd, RVR_MSG_MOVE) < 0
	|| ggz_write_int (fd, status) < 0)
      return ERROR_CANT_WRITE;
    // If invalid yx, sends sync to the player
    return game_send_sync (seat);
  }

  // Make move if it's valid (also sends it to everyone);
  return game_make_move (SEAT2PLAYER (seat), *yx);
}

static int
seats_full (void)
{
  /* This calculation is a bit inefficient, but that's OK */
  return !ggzdmod_count_seats (ggz, GGZ_SEAT_OPEN)
    && !ggzdmod_count_seats (ggz, GGZ_SEAT_RESERVED)
    && !ggzdmod_count_seats (ggz, GGZ_SEAT_ABANDONED);
}

static int
seats_empty (void)
{
  /* This calculation is a bit inefficient, but that's OK */
  return !ggzdmod_count_seats (ggz, GGZ_SEAT_PLAYER)
    && !ggzdmod_count_spectators (ggz);
}

static int
game_send_seat (int seat)
{
  int fd = ggzdmod_get_seat (ggz, seat).fd;

  ggzdmod_log (ggz, "Sending player %d's his seat number\n", seat);

  if (ggz_write_int (fd, RVR_MSG_SEAT) < 0 || ggz_write_int (fd, seat) < 0)
    return ERROR_CANT_WRITE;

  return SERVER_OK;
}

static int
game_send_players (void)
{
  int i, j, fd;

  for (j = 0; j < ggzdmod_get_num_seats (ggz); j++) {
    fd = ggzdmod_get_seat (ggz, j).fd;
    if (fd == -1) {
      ggzdmod_log (ggz, "Bot seat\n");
      continue;
    }

    ggzdmod_log (ggz, "Sending player list to player %d", j);
    if (ggz_write_int (fd, RVR_MSG_PLAYERS) < 0)
      return ERROR_CANT_WRITE;

    for (i = 0; i < ggzdmod_get_num_seats (ggz); i++) {
      if (ggz_write_int (fd, ggzdmod_get_seat (ggz, i).type) < 0)
	return ERROR_CANT_WRITE;
      if (ggzdmod_get_seat (ggz, i).type != GGZ_SEAT_OPEN &&
	  ggz_write_string (fd, ggzdmod_get_seat (ggz, i).name) < 0)
	return ERROR_CANT_WRITE;
    }
  }

  return SERVER_OK;
}

static void
game_handle_player (GGZdMod *ggzdmod, GGZdModEvent event, const void *data)
{
  const int *seat_ptr = data;
  const int seat = *seat_ptr;
  int op, yx, err;
  int fd = ggzdmod_get_seat (ggzdmod, seat).fd;

  if (ggz_read_int (fd, &op) < 0)
    return;

  switch (op) {
    case RVR_REQ_MOVE:		/* 4 */
      err = game_handle_move (seat, &yx);
      break;
    case RVR_REQ_AGAIN:		/* 8 */
      err = game_play_again (seat, fd);
      break;
    default:
      err = ERROR_OPCODE;
      break;
  }

  if (err < 0)
    ggzdmod_log (ggzdmod, strs[-err - 1]);
}

static void
game_handle_ggz_seat (GGZdMod *ggzdmod, GGZdModEvent event, const void *data)
{
  const GGZSeat *old_seat = data;
  GGZSeat new_seat = ggzdmod_get_seat (ggzdmod, old_seat->num);
  GGZdModState new_state;

  /* Check the state. */
  if (seats_full ())
    new_state = GGZDMOD_STATE_PLAYING;
  else if (seats_empty ())
    new_state = GGZDMOD_STATE_DONE;
  else
    new_state = GGZDMOD_STATE_WAITING;

  // That's great!! Do stuff
  if (new_seat.type == GGZ_SEAT_PLAYER)
    game_send_seat (new_seat.num);
  game_send_players ();

  if (new_state == GGZDMOD_STATE_PLAYING && whose_turn != EMPTY)
    game_send_sync (new_seat.num);

  ggzdmod_set_state (ggzdmod, new_state);
}

// Handle server messages
static void
game_handle_ggz_state (GGZdMod *ggzdmod, GGZdModEvent event, const void *data)
{
  GGZdModState new_state = ggzdmod_get_state (ggzdmod);
  const GGZdModState *old_state_ptr = data;
  const GGZdModState old_state = *old_state_ptr;

  // Check if it's the right time to launch the game and if ggz could do taht
  if (old_state == GGZDMOD_STATE_CREATED)
    /*assert(rvr_game.state == RVR_STATE_INIT); */
    return;

  if (new_state == GGZDMOD_STATE_PLAYING)
    // Let's start the game!
    game_start ();
  else if (new_state == GGZDMOD_STATE_WAITING) {
    // That's great! Update the state
    // Now waiting for people to join
    state = RVR_STATE_WAIT;
    ggzdmod_log (ggzdmod, "Waiting for players");
  }
}

int
main (void)
{
  GGZdMod *ggzdmod = ggzdmod_new (GGZDMOD_GAME);

  /* game_init is called at the start of _each_ game, so we must do
   * ggz stuff here. */
  ggzdmod_set_handler (ggzdmod, GGZDMOD_EVENT_STATE,
		       (GGZdModHandler) game_handle_ggz_state);
  ggzdmod_set_handler (ggzdmod, GGZDMOD_EVENT_JOIN,
		       (GGZdModHandler) game_handle_ggz_seat);
  ggzdmod_set_handler (ggzdmod, GGZDMOD_EVENT_LEAVE,
		       (GGZdModHandler) game_handle_ggz_seat);
  ggzdmod_set_handler (ggzdmod, GGZDMOD_EVENT_SEAT,
		       (GGZdModHandler) game_handle_ggz_seat);
  ggzdmod_set_handler (ggzdmod, GGZDMOD_EVENT_PLAYER_DATA,
		       (GGZdModHandler) game_handle_player);

  game_init (ggzdmod);

  /* Connect to GGZ server; main loop */
  if (ggzdmod_connect (ggzdmod) < 0)
    return -1;
  ggzdmod_loop (ggzdmod);
  ggzdmod_disconnect (ggzdmod);
  ggzdmod_free (ggzdmod);

  return 0;
}


-------------------------------------------------------------------
gataxx-client.desc.in
-------------------------------------------------------------------


# Gataxx description file
[ModuleInfo]
Name = Gataxx
Author = The GNOME Project 
CommandLine = @prefix@/bin/gataxx
Frontend = gtk
Homepage = http://www.gnome.org/projects/gnome-games/
ProtocolEngine = Gataxx
ProtocolVersion = 1 
Version = @VERSION@


-------------------------------------------------------------------
gataxx-server.desc.in
-------------------------------------------------------------------


[GameInfo]
Author = The GNOME Project 
Description = A disk-flipping game where you attempt to dominate the board.
Homepage = http://www.gnome.org/projects/gnome-games/ 
Name = Gataxx
Version = @VERSION@ 

[LaunchInfo]
ExecutablePath = @prefix@/bin/gataxxd
# Set ExecutableArgs in the room file

[Protocol]
Engine = Gataxx
Version = 1

[TableOptions]
AllowLeave = 1
BotsAllowed = 1
PlayersAllowed = 2


-------------------------------------------------------------------
gataxx.room
-------------------------------------------------------------------


[RoomInfo]
# This is the short name for the room
Name = Gataxx 
# This is the long descriptive name for the room
Description = A disk-flipping game where you attempt to dominate the board.
# The gametype should match up to the Name of an added game
GameType = Gataxx 
# Any additional game arguments specific to this room
#ExecutableArgs = 
# These set maximum values for this room
MaxPlayers = 150
MaxTables = 45


-------------------------------------------------------------------
Makefile.am
-------------------------------------------------------------------


pixmapdir=$(datadir)/pixmaps/iagno
Gamesdir = $(datadir)/applications

INCLUDES =						\
	-I$(top_srcdir)/libgames-support		\
	$(WARN_CFLAGS)					\
	$(GNOME_GAMES_CFLAGS)				\
	-DDATADIR=\""$(datadir)"\"		   	\
	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"	\
	$(GGZMOD_INCLUDES) $(GGZ_GTK_INCLUDES) $(GGZCORE_INCLUDES)\
	$(LIBGGZ_INCLUDES) $(GGZCORE_INCLUDES)

if GGZ_SERVER
bin_PROGRAMS = gataxx gataxxd
gataxxd_SOURCES = server.h   \
		 server.c   \
		 ai.h  \
		 ai.c

gataxxd_LDFLAGS = @GGZDMOD_LDFLAGS@

gataxxd_LDADD = \
	$(GNOME_GAMES_LIBS)     \
	$(INTLLIBS) 		\
	@LIB_GGZDMOD@

else
bin_PROGRAMS = gataxx
endif

if GGZ_CLIENT
GGZ_FILES =	ggz-network.h \
		ggz-network.c \
		$(NULL)
endif

gataxx_SOURCES = \
	ai.c \
	ai.h \
	gataxx.c \
	gataxx.h \
	properties.c \
	properties.h \
	$(GGZ_FILES)

gataxx_LDFLAGS = $(GGZMOD_LDFLAGS)

gataxx_LDADD = \
	../libgames-support/libgames-support.la \
	$(GNOME_GAMES_LIBS) \
	$(INTLLIBS) \
	$(LIB_GGZMOD) \
	$(LIB_GGZ_GTK)

icon16dir = $(datadir)/icons/hicolor/16x16/apps/
icon16_DATA = 16x16/gnome-ataxx.png
icon22dir = $(datadir)/icons/hicolor/22x22/apps/
icon22_DATA = 22x22/gnome-ataxx.png
icon32dir = $(datadir)/icons/hicolor/32x32/apps/
icon32_DATA = 32x32/gnome-ataxx.png
icon48dir = $(datadir)/icons/hicolor/48x48/apps/
icon48_DATA = 48x48/gnome-ataxx.png
svgicondir = $(datadir)/icons/hicolor/scalable/apps/
svgicon_DATA = scalable/gnome-ataxx.svg

Games_in_files = gataxx.desktop.in
Games_DATA = $(Games_in_files:.desktop.in=.desktop)
@INTLTOOL_DESKTOP_RULE@

schemadir   = @GCONF_SCHEMA_FILE_DIR@
schema_in_files = gataxx.schemas.in
schema_DATA = $(schema_in_files:.schemas.in=.schemas)
@INTLTOOL_SCHEMAS_RULE@

EXTRA_DIST = \
	AUTHORS \
	gataxx-client.dsc.in \
	gataxx-server.dsc.in \
        gataxx.room          \
	gataxx.desktop.in 	\
	$(icon16_DATA)	\
	$(icon22_DATA)	\
	$(icon32_DATA)	\
	$(icon48_DATA)	\
	$(svgicon_DATA) \
	$(schema_in_files)

install-data-local:
if GCONF_SCHEMAS_INSTALL
	GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(top_builddir)/gataxx/$(schema_DATA)
endif
if GGZ_CLIENT
	$(GGZ_CONFIG) -D --install --modfile=gataxx-client.dsc --force
endif
uninstall-local:
if GGZ_CLIENT
	-$(GGZ_CONFIG) -D --remove --modfile=gataxx-client.dsc
endif

SUBDIRS= help

DISTCLEANFILES = $(Games_DATA) $(schema_DATA)

if GGZ_SERVER
ggzroom_DATA = gataxx.room
ggzroomdir = ${prefix}/etc/ggzd/rooms/

ggzgame_DATA = gataxx-server.dsc
ggzgamedir = ${prefix}/etc/ggzd/games/
endif
