// Board.js
// 2006-12-31
/*
(C)2008 Julio Di Egidio
http://julio.diegidio.name
mailto:julio@diegidio.name

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
////////////////////////////////////////////////////////////////
// jde_3t_Board (Board.js) /////////////////////////////////////

// Requires jde_ge_Engine (Engine.js)
//
/*extern
	jde_ge_noValue
*/


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// [enums] //////////////////
	
// <<enumeration>>GX_BoardCellState //
var jde_3t_boardCellState = {
	e : "E",
	x : "X",
	o : "O"
};

// <<enumeration>>GX_BoardState //
var jde_3t_boardState = {
	moveX : "MVX",
	moveO : "MVO",
	winX : "WNX",
	winO : "WNO",
	draw : "DRW"
};
jde_3t_boardState.isMove = function (boardState) {
	return boardState === this.moveX ||
		boardState === this.moveO;
};
jde_3t_boardState.isWin = function (boardState) {
	return boardState === this.winX ||
		boardState === this.winO;
};
jde_3t_boardState.isDraw = function (boardState) {
	return boardState === this.draw;
};


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// [types] //////////////////
	
// <<datatype>>GX_BoardCoord //
function jde_3t_BoardCoord(row, col) {
	this.row = row;
	this.col = col;
}
jde_3t_BoardCoord.prototype.toIdx = function () {
	return this.col + 3 * this.row;
};
jde_3t_BoardCoord.parse = function (boardCoord) {
	if(typeof(boardCoord) === "undefined" ||
			boardCoord === null ||
			isNaN(boardCoord.row = Math.round(boardCoord.row)) ||
			isNaN(boardCoord.col = Math.round(boardCoord.col)) ||
			boardCoord.row < 0 || boardCoord.row > 2 ||
			boardCoord.col < 0 || boardCoord.col > 2) {
		return jde_ge_noValue;
	}
	return new jde_3t_BoardCoord(boardCoord.row, boardCoord.col);
};
jde_3t_BoardCoord.getBuilderName = function (boardCoord) {
	return "new jde_3t_BoardCoord(" +
		(boardCoord.row) + ", " +
		(boardCoord.col) + ")";
};

// <<datatype>>GX_BoardWinCoords //
function jde_3t_BoardWinCoords(winCoords) {
	var getBoardCoord = function (idx) {
		return new jde_3t_BoardCoord(
			winCoords[idx].row,
			winCoords[idx].col);
	};
	var boardWinCoords = [];
	for(var i=0; i<winCoords.length; i++) {
		boardWinCoords[i] = getBoardCoord(i);
	}
	return boardWinCoords;
}

// <<datatype>>GX_BoardMove //
function jde_3t_BoardMove(boardState, boardCoord) {
	this.boardState = boardState;
	this.boardCoord = boardCoord;
}


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// jde_3t_Board /////////////
	
// <<class>>GX_Board //
function jde_3t_Board() {

	// Check required
	if(typeof(jde_ge_noValue) === "undefined") {
		return null;
	}
	
	
	/////////////////////
	// Private ////////////
	
	//
	//   R  ,  C  |  which  |  r  ,  c
	// -----------|---------|-----------
	//   n  , NaN |  isRow  |  R  ,  i
	//  NaN ,  n  |  isCol  |  i  ,  C
	//   n  ,  n  | isDiag0 |  i  ,  i
	//  NaN , NaN | isDiag2 |  i  , 2-i
	// -----------|---------|-----------
	//
	
	var _cells = [],
		_state = jde_ge_noValue,
		_winCoords = jde_ge_noValue,
		_moves = [];
	
	for(var i=0; i<9; i++) {
		_cells[i] = jde_ge_noValue;
	}
	
	
	function StateRC(R, C) {
		this.r = R;
		this.c = C;
	}
	
	function StateBag(state, RCs) {
		this.state = state;
		this.RCs = RCs;
	}
	
	
	function getNextStateBag() {
		var rcToIdx = function (r, c) { return c + 3 * r; };

		var found;
		
		/* Check win */
		
		var RCs = [];
		
		var winCtrlCells = [];
		winCtrlCells[0] = _cells[rcToIdx(0, 0)];
		winCtrlCells[1] = _cells[rcToIdx(1, 1)];
		winCtrlCells[2] = _cells[rcToIdx(2, 2)];
		
		found = false;
			// Check rows
		for(var r=0; 1 && r<3; r++) {
			if(winCtrlCells[r] !== jde_3t_boardCellState.e) {
				var cell_r_0 = _cells[rcToIdx(r, 0)];
				var cell_r_1 = _cells[rcToIdx(r, 1)];
				var cell_r_2 = _cells[rcToIdx(r, 2)];
				if(cell_r_0 === cell_r_1 && cell_r_1 === cell_r_2) {
					RCs[RCs.length] = new StateRC(r, NaN);
					found = true;
				}
			}
		}
			// Check cols
		for(var c=0; 1 && c<3; c++) {
			if(winCtrlCells[c] !== jde_3t_boardCellState.e) {
				var cell_0_c = _cells[rcToIdx(0, c)];
				var cell_1_c = _cells[rcToIdx(1, c)];
				var cell_2_c = _cells[rcToIdx(2, c)];
				if(cell_0_c === cell_1_c && cell_1_c === cell_2_c) {
					RCs[RCs.length] = new StateRC(NaN, c);
					found = true;
				}
			}
		}
			// Check diags
		if(1) {
			if(winCtrlCells[1] !== jde_3t_boardCellState.e) {
				// Check diag0
				var cell_0_0 = _cells[rcToIdx(0, 0)];
				var cell_1_1 = _cells[rcToIdx(1, 1)];
				var cell_2_2 = _cells[rcToIdx(2, 2)];
				if(cell_0_0 === cell_1_1 && cell_1_1 === cell_2_2) {
					RCs[RCs.length] = new StateRC(1, 1);
					found = true;
				}
				// Check diag2
				var cell_0_2 = _cells[rcToIdx(0, 2)];
				//var cell_1_1 = _cells[rcToIdx(1, 1)];
				var cell_2_0 = _cells[rcToIdx(2, 0)];
				if(cell_0_2 === cell_1_1 && cell_1_1 === cell_2_0) {
					RCs[RCs.length] = new StateRC(NaN, NaN);
					found = true;
				}
			}
		}
			//
		if(found) {	// Set WIN!
			return new StateBag(_state === jde_3t_boardState.moveX ?
				jde_3t_boardState.winX : jde_3t_boardState.winO, RCs);
		}

		/* Check draw */
		
		found = true;
			//
		for(var r=0; found && r<3; r++) {
			for(var c=0; found && c<3; c++) {
				var cell_r_c = _cells[rcToIdx(r, c)];
				found = cell_r_c !== jde_3t_boardCellState.e;
			}
		}
			//
		if(found) {	// Set DRAW!
			return new StateBag(jde_3t_boardState.draw);
		}
		
		
		/* Not found. */
		
		return new StateBag(_state === jde_3t_boardState.moveX ?
			jde_3t_boardState.moveO : jde_3t_boardState.moveX);
	}
	
	
	function fillWinCoords(RCs) {
		_winCoords = [];
		
		for(var idx=0; idx<RCs.length; idx++) {
			var R = RCs[idx].r,
				C = RCs[idx].c;
			
			for(var i=0; i<3; i++) {
				var winCoord;
				
				if(isNaN(R) && isNaN(C)) {	// isDiag2
					winCoord = new jde_3t_BoardCoord(i, 2-i);
				}
				else if(isNaN(R)) {			// isCol
					winCoord = new jde_3t_BoardCoord(i, C);
				}
				else if(isNaN(C)) {			// isRow
					winCoord = new jde_3t_BoardCoord(R, i);
				}
				else {						// isDiag0
					winCoord = new jde_3t_BoardCoord(i, i);
				}
				
				var found = false;
				
				for(var j=0; !found && j<_winCoords.length; j++) {
					found = winCoord.row === _winCoords[j].row &&
						winCoord.col === _winCoords[j].col;
				}
				
				if(!found) {
					_winCoords[_winCoords.length] = winCoord;
				}
			}
		}
	}
	
	
	/////////////////////
	// Public ////////////
	
	this.reset = function () {
		for(var i=0; i<9; i++) {
			_cells[i] = jde_3t_boardCellState.e;
		}
		
		_state = jde_3t_boardState.moveX;
		
		_winCoords = jde_ge_noValue;
		
		_moves.length = 0;
	};
	
	this.move = function (boardCoord) {
		if(!jde_3t_boardState.isMove(_state)) {
			return jde_ge_noValue;
		}
		
		boardCoord = jde_3t_BoardCoord.parse(boardCoord);
		if(boardCoord === jde_ge_noValue) {
			return jde_ge_noValue;
		}

		var idx = boardCoord.toIdx();
		if(_cells[idx] !== jde_3t_boardCellState.e) {
			return jde_ge_noValue;
		}

		_moves[_moves.length] =
			new jde_3t_BoardMove(_state, boardCoord);
		
		_cells[idx] = _state === jde_3t_boardState.moveX ?
			jde_3t_boardCellState.x : jde_3t_boardCellState.o;
		
		var nextBag = getNextStateBag();
		
		_state = nextBag.state;
		
		if(jde_3t_boardState.isWin(_state)) {
			fillWinCoords(nextBag.RCs);
		}
		
		return _state;
	};

	this.undo = function () {
		if(_moves.length === 0) {
			return jde_ge_noValue;
		}
		
		var boardMove = _moves[_moves.length - 1];
		
		var idx = boardMove.boardCoord.toIdx();
		_cells[idx] = jde_3t_boardCellState.e;
		
		_state = boardMove.boardState;
		
		_winCoords = jde_ge_noValue;
		
		_moves.length -= 1;
		
		return _state;
	};
	

	this.getCells = function () {
		var boardCells = [];
		for(var i=0; i<9; i++) {
			boardCells[i] = _cells[i];
		}
		return boardCells;
	};

	this.getCell = function (boardCoord) {
		boardCoord = jde_3t_BoardCoord.parse(boardCoord);
		if(boardCoord === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		var idx = boardCoord.toIdx();
		return _cells[idx];
	};

	this.getState = function () {
		return _state;
	};

	this.getLastMove = function () {
		if(_moves.length === 0) {
			return jde_ge_noValue;
		}
		return _moves[_moves.length - 1];
	};

	this.getWinCoords = function () {
		if(_winCoords === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return new jde_3t_BoardWinCoords(_winCoords);
	};
	
}

