/*
 * Atom-4 game engine
 * Implementation file
 * ---------------------------------------------------------------------------
 * $Id: game.cc,v 1.22 2003/04/08 09:56:28 hsteoh Exp hsteoh $
 *
 * DESIGN
 *
 * This game engine is supposed to be UI-driven; so it is basically designed
 * for an event-driven interface. The UI, whether the testing ncurses-based
 * UI or the anticipated X11 UI, will be sending events to class atom4 and
 * checking its state to know what to do next, etc.. Basically, this class
 * implements the game rules.
 *
 * NOTES:
 * - celltype assignments:
 *	a	black
 *	b	red
 *	c	green
 *	d	yellow
 *	e	blue
 *	f	purple
 *	g	aqua
 *	h	white
 *   These are basically based on the bit-pattern of the binary representation
 *   of (cell-'a').
 */

#include "game.h"


/*
 * class atom4
 */

void atom4::notify_move(int player, elist<boardchange> &changes) {
  elistiter<atom4notifier*> it;

  for (it=notifylist.headp(); it; it++) {
    (*it)->notify_move(this, player, changes);
  }
}

void atom4::notify_clear() {
  elistiter<atom4notifier*> it;

  for (it=notifylist.headp(); it; it++) {
    (*it)->notify_clear(this);
  }
}

atom4::~atom4() {
  // needed 'cos this is a virtual dtor to allow proper cleanup of derived
  // objects.
}

void atom4::add_notifier(atom4notifier *callback) {
  notifylist.append(callback);
}

void atom4::remove_notifier(atom4notifier *callback) {
  elistiter<atom4notifier*> it, prev;

  prev.invalidate();
  for (it=notifylist.headp(); it; it++) {
    if (*it == callback) {
      notifylist.remove(prev);
    }
    prev=it;
  }
}



/*
 * class atom4local
 */


// TESTING: sequence of colors each player gets to play
// (Interleaved between two players)
celltype atom4local::colorseq[COLORSEQ_LEN] = {
  'b', 'd', 'c', 'g', 'e', 'f'
};

// Returns 0 for normal operations; 1 if a winning position is detected.
int atom4local::splash(int bx, int by, color4 actor,
                       elist<boardchange> &changes) {
  board->compute_splash(bx, by, actor, changes);
  board->applychanges(changes, NULL);

  return board->check_win(changes, actor.propagator(), WIN_ROW);
}

void atom4local::reset_scores() {
  int i;

  for (i=0; i<NUM_PLAYERS; i++) {
    scores[i] = 0;
  }

  cur_round = 1;
  win_player = -1;
  cur_player = start_player = 1;
}

void atom4local::reset_board() {
  int vplayer=start_player, hplayer=next_player(start_player);
  int x,y;

  // Setup initial game position
  board->clear();
  x = board->width()/2 - 1;
  y = board->height()/2 - 1;

  // Starting color
  cur_color = (cur_round-1) % COLORSEQ_LEN;

  // Complementary setting
  board->setcell(x,y,   colorseq[(cur_color+2) % COLORSEQ_LEN]);
  board->setcell(x+1,y, colorseq[(cur_color+5) % COLORSEQ_LEN]);

  // Pyramid setting
//  board->setcell(x,y, 'a');
//  board->ngbr_coor(x,y,2,x,y); board->setcell(x,y, 'b');
//  board->ngbr_coor(x,y,4,x,y); board->setcell(x,y, 'c');

//  // Diamond configuration
//  board->setcell(x,y, plchar(hplayer));
//  board->ngbr_coor(x,y,1,x,y); board->setcell(x,y, plchar(vplayer));
//  board->ngbr_coor(x,y,3,x,y); board->setcell(x,y, plchar(hplayer));
//  board->ngbr_coor(x,y,4,x,y); board->setcell(x,y, plchar(vplayer));
}

atom4local::atom4local(unsigned int width, unsigned int height) {
  board = new board4(width,height);

  reset_scores();
  reset_board();
}

atom4local::~atom4local() {
  delete board;
}

void atom4local::reset() {
  reset_scores();
  reset_board();
  notify_clear();
}

atom4::mode_t atom4local::game_mode() {
  return atom4::DUAL;
}

int atom4local::local_playernum() {
  return cur_player;
}

int atom4local::is_local_turn() {
  // We're running in DUAL mode, so it's always the local player's turn until
  // the game is over.
  return win_player==-1;
}

int atom4local::current_player() {
  return (win_player==-1) ? cur_player : -1;
}

celltype atom4local::current_tile() {
  return colorseq[cur_color];
}

int atom4local::move(int player, int x, int y) {
  if (!valid_player(player)) return 0;	// sanity check

  if (win_player!=-1 || player!=cur_player || !check_legal(x,y)) {
    return 0;				// illegal move
  } else {
    elist<boardchange> changes;
    int move_player = cur_player;

    if (splash(x,y,colorseq[cur_color], changes)) {
      win_player = player;
      scores[player-1]++;
    } else {
      if (board->is_full()) {
        win_player = STALEMATE;		// no more moves left
      } else {
        // Next player's turn
        cur_player = next_player(cur_player);
        cur_color = next_color(cur_color);
      }
    }
    notify_move(move_player, changes);
    return 1;
  }
}

// Check if given move is legal.
int atom4local::check_legal(int x, int y) {
  return board->check_legal(x,y);
}

int atom4local::round_over() {
  return (win_player!=-1);
}

int atom4local::winner() {
  return win_player;
}

int atom4local::current_round() {
  return cur_round;
}

int atom4local::newround() {
  if (win_player != -1) {
    win_player=-1;			// start next round
    cur_round++;
    start_player=next_player(start_player);	// other player gets to start
    cur_player=start_player;

    reset_board();			// setup board for next round
    notify_clear();
    return 1;
  } else {
    return 0;
  }
}

int atom4local::score(int player) {
  return (valid_player(player)) ? scores[player-1] : 0;
}

board4 *atom4local::get_board() {
  return board;
}

unsigned int atom4local::board_width() {
  return board->width();
}

unsigned int atom4local::board_height() {
  return board->height();
}

