// APlayer.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_APlayer (APlayer.js) /////////////////////////////////

// Requires jde_ge_Engine (Engine.js)
// Requires jde_3t_Board (Board.js)
//
/*extern
	jde_ge_noValue,
*/
/*extern
	jde_3t_Board,
	jde_3t_boardCellState,
	jde_3t_boardState,
	jde_3t_BoardCoord,
*/
/*extern
	DEBUG_ASSERT_ERROR
*/


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// [enums] //////////////////
	
// <<enumeration>>GX_AStrategy //
var jde_3t_aStrategy = {
	ran : "RAN",
	heu : "HEU",
	fuz : "FUZ"
};
jde_3t_aStrategy.getRandomNonFuzzy = function () {
	var idx = Math.floor(Math.random() * 2);
	if(idx === 0) {
		return this.ran;
	}
	return this.heu;
};
jde_3t_aStrategy.getDefaultNonFuzzy = function () {
	return this.heu;
};
jde_3t_aStrategy.parse = function (strategy) {
	if(!(strategy === this.ran ||
			strategy === this.heu ||
			strategy === this.fuz)) {
		return jde_ge_noValue;
	}
	return strategy;
};
// NEEDS REVISION! ******************************************************
// Default: Match these!
jde_3t_aStrategy.getDefault = function () {
	return this.fuz;
};
jde_3t_aStrategy.getOptions = function () {
	var options = [];
	options[0] = {
		value : this.ran,
		text : "Random",
		selected : false
	};
	options[1] = {
		value : this.heu,
		text : "Heuristic",
		selected : false
	};
	options[2] = {
		value : this.fuz,
		text : "Fuzzy",
		selected : true
	};
	return options;
};
//


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// [types] //////////////////
	
// <<datatype>>GX_AThreshold //
function jde_3t_aThreshold() {
	return null;
}
jde_3t_aThreshold.getRandomIsOver = function (threshold) {
	return Math.random() >= threshold;
};
jde_3t_aThreshold.parse = function (threshold) {
	if(isNaN(threshold = parseFloat(threshold)) ||
			threshold < 0 || threshold > 1) {
		return jde_ge_noValue;
	}
	return threshold;
};
// NEEDS REVISION! ******************************************************
// Default: Match these!
jde_3t_aThreshold.getDefault = function () {
	return (7 / 10);
};
jde_3t_aThreshold.getOptions = function () {
	var options = [];
	for(var i=0; i<=10; i++) {
		options[i] = {
			value : "" + (i / 10),
			text : (i * 10) + "%",
			selected : (i === 7)
		};
	}
	return options;
};
//


////////////////////////////////////////////////////////////////
	/////////////////////////////
	// jde_3t_APlayer ///////////
	
// <<class>>GX_APlayer //
function jde_3t_APlayer(board) {
	
	// Check required
	if(typeof(jde_ge_noValue) === "undefined") {
		return null;
	}
	// Check required
	if(typeof(jde_3t_Board) === "undefined") {
		return null;
	}
	
	
////////////////////////////////////////////////////////////////
	/////////////////////////////
	/* AI LOGIC */
	
	/////////////////////
	// Support ////////////
	
	function getWinMove(board, newCellState) {
		
		/* Check win */
		
			// Check rows
		for(var r=0; r<3; r++) {
			var cell_r_0 = board.getCell(new jde_3t_BoardCoord(r, 0));
			var cell_r_1 = board.getCell(new jde_3t_BoardCoord(r, 1));
			var cell_r_2 = board.getCell(new jde_3t_BoardCoord(r, 2));
			if(cell_r_0 === jde_3t_boardCellState.e &&
					newCellState === cell_r_1 && cell_r_1 === cell_r_2) {
				return new jde_3t_BoardCoord(r, 0);
			}
			if(cell_r_1 === jde_3t_boardCellState.e &&
					newCellState === cell_r_2 && cell_r_2 === cell_r_0) {
				return new jde_3t_BoardCoord(r, 1);
			}
			if(cell_r_2 === jde_3t_boardCellState.e &&
					newCellState === cell_r_0 && cell_r_0 === cell_r_1) {
				return new jde_3t_BoardCoord(r, 2);
			}
		}
			// Check cols
		for(var c=0; c<3; c++) {
			var cell_0_c = board.getCell(new jde_3t_BoardCoord(0, c));
			var cell_1_c = board.getCell(new jde_3t_BoardCoord(1, c));
			var cell_2_c = board.getCell(new jde_3t_BoardCoord(2, c));
			if(cell_0_c === jde_3t_boardCellState.e &&
					newCellState === cell_1_c && cell_1_c === cell_2_c) {
				return new jde_3t_BoardCoord(0, c);
			}
			if(cell_1_c === jde_3t_boardCellState.e &&
					newCellState === cell_2_c && cell_2_c === cell_0_c) {
				return new jde_3t_BoardCoord(1, c);
			}
			if(cell_2_c === jde_3t_boardCellState.e &&
					newCellState === cell_0_c && cell_0_c === cell_1_c) {
				return new jde_3t_BoardCoord(2, c);
			}
		}
			// Check diags
		if(1) {
				// Check diag0
			var cell_0_0 = board.getCell(new jde_3t_BoardCoord(0, 0));
			var cell_1_1 = board.getCell(new jde_3t_BoardCoord(1, 1));
			var cell_2_2 = board.getCell(new jde_3t_BoardCoord(2, 2));
			if(cell_0_0 === jde_3t_boardCellState.e &&
					newCellState === cell_1_1 && cell_1_1 === cell_2_2) {
				return new jde_3t_BoardCoord(0, 0);
			}
			if(cell_1_1 === jde_3t_boardCellState.e &&
					newCellState === cell_2_2 && cell_2_2 === cell_0_0) {
				return new jde_3t_BoardCoord(1, 1);
			}
			if(cell_2_2 === jde_3t_boardCellState.e &&
					newCellState === cell_0_0 && cell_0_0 === cell_1_1) {
				return new jde_3t_BoardCoord(2, 2);
			}
				// Check diag2
			var cell_0_2 = board.getCell(new jde_3t_BoardCoord(0, 2));
			//var cell_1_1 = board.getCell(new jde_3t_BoardCoord(1, 1));
			var cell_2_0 = board.getCell(new jde_3t_BoardCoord(2, 0));
			if(cell_0_2 === jde_3t_boardCellState.e &&
					newCellState === cell_1_1 && cell_1_1 === cell_2_0) {
				return new jde_3t_BoardCoord(0, 2);
			}
			if(cell_1_1 === jde_3t_boardCellState.e &&
					newCellState === cell_2_0 && cell_2_0 === cell_0_2) {
				return new jde_3t_BoardCoord(1, 1);
			}
			if(cell_2_0 === jde_3t_boardCellState.e &&
					newCellState === cell_0_2 && cell_0_2 === cell_1_1) {
				return new jde_3t_BoardCoord(2, 0);
			}
		}
		
		/* Not found */
		
		return jde_ge_noValue;
	}
	
	
	/////////////////////
	// Logics ////////////
	
	function getNextMove_RAN(board) {
		var emptyCount = 0;
		for(var r=0; r<3; r++) {
			for(var c=0; c<3; c++) {
				var cell_r_c = board.getCell(new jde_3t_BoardCoord(r, c));
				if(cell_r_c === jde_3t_boardCellState.e) {
					emptyCount++;
				}
			}
		}
		if(emptyCount === 0) {
			return jde_ge_noValue;
		}
		
		var ref = Math.floor(Math.random() * emptyCount);
		var idx = 0;
		for(var r=0; r<3; r++) {
			for(var c=0; c<3; c++) {
				var cell_r_c = board.getCell(new jde_3t_BoardCoord(r, c));
				if(cell_r_c === jde_3t_boardCellState.e) {
					if(idx === ref) {
						return new jde_3t_BoardCoord(r, c);
					}
					idx++;
				}
			}
		}
		
		return jde_ge_noValue;
	}
	
	
// NEEDS REVISION! ******************************************************
	function getNextMove_HEU(board, buGetNextMoveRef) {
		var emptyCount = 0;
		for(var r=0; r<3; r++) {
			for(var c=0; c<3; c++) {
				var cell_r_c = board.getCell(new jde_3t_BoardCoord(r, c));
				if(cell_r_c === jde_3t_boardCellState.e) {
					emptyCount++;
				}
			}
		}
		if(emptyCount === 0) {
			return jde_ge_noValue;
		}
		
		var boardState = board.getState();
		
		// 1. Search win			////////////////////////////
		var coordW = getWinMove(board,
			boardState === jde_3t_boardState.moveX ?
				jde_3t_boardCellState.x : jde_3t_boardCellState.o);
		if(coordW !== jde_ge_noValue) {
			return coordW;
		}
		
		// 2. Search lose			////////////////////////////
		var coordL = getWinMove(board,
			boardState === jde_3t_boardState.moveX ?
				jde_3t_boardCellState.o : jde_3t_boardCellState.x);
		if(coordL !== jde_ge_noValue) {
			return coordL;
		}
		
		// 3. Search center 		////////////////////////////
		var who_1_1 = board.getCell(new jde_3t_BoardCoord(1, 1));
		if(who_1_1 === jde_3t_boardCellState.e) {
			return new jde_3t_BoardCoord(1, 1);
		}
		
		// 4. Search random angle   ////////////////////////////
		var who_0_0 = board.getCell(new jde_3t_BoardCoord(0, 0));
		var who_0_2 = board.getCell(new jde_3t_BoardCoord(0, 2));
		var who_2_0 = board.getCell(new jde_3t_BoardCoord(2, 0));
		var who_2_2 = board.getCell(new jde_3t_BoardCoord(2, 2));
		var emptyAngles = [];
		if(who_0_0 === jde_3t_boardCellState.e) {
			emptyAngles[emptyAngles.length] = new jde_3t_BoardCoord(0, 0);
		}
		if(who_0_2 === jde_3t_boardCellState.e) {
			emptyAngles[emptyAngles.length] = new jde_3t_BoardCoord(0, 2);
		}
		if(who_2_0 === jde_3t_boardCellState.e) {
			emptyAngles[emptyAngles.length] = new jde_3t_BoardCoord(2, 0);
		}
		if(who_2_2 === jde_3t_boardCellState.e) {
			emptyAngles[emptyAngles.length] = new jde_3t_BoardCoord(2, 2);
		}
		if(emptyAngles.length !== 0) {
			var idxAngle = Math.floor(Math.random() * emptyAngles.length);
			return emptyAngles[idxAngle];
		}
		
		// Catch-all rule			////////////////////////////
		return buGetNextMoveRef(board);
	}
	
	
	/////////////////////
	// Private ////////////
	
	var _board = board;
	
	var _strategy,
		_fuzzyThreshold;
	
	var that = this;
	
	
	/////////////////////
	// Public ////////////
	
// NEEDS REVISION! ******************************************************
	var def_strategy = jde_3t_aStrategy.getDefault();
	var def_fuzzyThreshold = jde_3t_aThreshold.getDefault();
	
	
	this.reset = function () {
		_strategy = def_strategy;
		_fuzzyThreshold = def_fuzzyThreshold;
	};
	
	this.getNextMove = function (strategy) {
		if(!jde_3t_boardState.isMove(_board.getState())) {
			return jde_ge_noValue;
		}
		
		strategy = jde_3t_aStrategy.parse(strategy);
		if(strategy === jde_ge_noValue) {
			strategy = _strategy;
		}
		
		// (pre-)fuzzy
		if(strategy === jde_3t_aStrategy.fuz) {
			if(jde_3t_aThreshold.getRandomIsOver(_fuzzyThreshold)) {
				strategy = jde_3t_aStrategy.getRandomNonFuzzy();
			}
			else {
				strategy = jde_3t_aStrategy.getDefaultNonFuzzy();
			}
		}
		
		// random
		if(strategy === jde_3t_aStrategy.ran) {
			return getNextMove_RAN(_board);
		}
		// heuristic
		else if(strategy === jde_3t_aStrategy.heu) {
			return getNextMove_HEU(_board, getNextMove_RAN);
		}
		
		// (Should not reach here!)
		DEBUG_ASSERT_ERROR(); // Undef this!
	};
	
	
	this.getStrategy = function () {
		return _strategy;
	};
	this.setStrategy = function (strategy) {
		strategy = jde_3t_aStrategy.parse(strategy);
		if(strategy === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return (_strategy = strategy);
	};
	
	this.getFuzzyThreshold = function () {
		return _fuzzyThreshold;
	};
	this.setFuzzyThreshold = function (threshold) {
		threshold = jde_3t_aThreshold.parse(threshold);
		if(threshold === jde_ge_noValue) {
			return jde_ge_noValue;
		}
		return (_fuzzyThreshold = threshold);
	};
	
}

