/*
 * JavaScript Mill 
 * Copyright 2003 by Thomas Meyer
 *
 * 2003/12/21   Version 1.0
 *
 */

     
     /* globals */
    // field states
    var EMPTY = 0;
    var HUMAN = 1;
    var ARTIL = 2;

    var MAXMARBLES = 3;

    // actions
    var ACTION_PUT = 1;
    var ACTION_MOVE = 2;
    
    
    function getPlayerName(inFieldState) {
	return (inFieldState == HUMAN) ? "Human" : "Artil";
	}
    
    function getOtherPlayer(inFieldState) {
	return (inFieldState == HUMAN) ? ARTIL : HUMAN;
	}
	
    
    /* class */
    // action describes either a "put" or a "move" action
    function action(who,what,fieldIndex1,fieldIndex2) {
    
	/* methods */
	this.action_toStr = action_toStr;
	
	this.who = who;
	this.what = what;
	this.fieldIndex1 = fieldIndex1;
	this.fieldIndex2 = fieldIndex2;
	this.artilValue = -Number.MAX_VALUE;
	this.humanValue = -Number.MAX_VALUE;
	}
    
    
    function action_toStr() {
	str = getPlayerName(this.who) + ": ";
	
	switch (this.what) {
	
	    case ACTION_PUT:
		str += "put " + this.fieldIndex1;
		break;
		
	    case ACTION_MOVE:
		str += "move " + this.fieldIndex1 + " to " + this.fieldIndex2;
		break;
	    }
	    
	return str;
	}
	

    /* class */
    // gameState describes a specific game situation
    function gameState(inBoardSize) {

	/* methods */
	this.put = put;
	this.move = move;
	this.endMove = endMove;
	this.getPossibleActions = getPossibleActions;
	this.performAction = performAction;
	this.getMaxInARow = getMaxInARow;
	this.reset = reset;
	this.clone = clone;
	this.gameState_toStr = gameState_toStr;
	
	this.boardSize = inBoardSize;
	
	// who's next
	this.nextPlayer = HUMAN;
	
	// array holding state of each field
	this.fields = new Array(this.boardSize * this.boardSize);
	
	// we have to initialize this array
	for(fieldIndex=0 ; fieldIndex<this.fields.length ; fieldIndex++)
	    this.fields[fieldIndex] = EMPTY;

	// array holding move counts
	this.moves = new Array();
	this.moves[HUMAN] = 0;
	this.moves[ARTIL] = 0;
	}
	
	
    
    /* gameState.clone */
    function clone() {
	that = new gameState(this.boardSize);
	
	that.boardSize = this.boardSize;
	that.nextPlayer = this.nextPlayer;
	
	// copy array
	for(fieldIndex=0 ; fieldIndex<this.fields.length ; fieldIndex++)
	    that.fields[fieldIndex] = this.fields[fieldIndex];
	    
	that.moves = new Array();
	that.moves[HUMAN] = this.moves[HUMAN];
	that.moves[ARTIL] = this.moves[ARTIL];
		
	return that;
	}
	
    
    /* gameState.gameState_toStr */
    function gameState_toStr() {
    	
	str = "";
	
	for(fieldIndex=0 ; fieldIndex<this.fields.length ; fieldIndex++)
	    str += this.fields[fieldIndex];
	    
	str += "\n";
	
	str += "Next player: " + getPlayerName(this.nextPlayer) + "\n";
	str += "Human moves: " + this.moves[HUMAN] + "\n";
	str += "Artil moves: " + this.moves[ARTIL];
	
	return str;
	}


    /* gameState.put */
    // lets the next player put a thing down on the board
    function put(inFieldIndex) {

	// each player may put only, say, 3 things on the board, and the field has to be empty
	if ( (this.moves[this.nextPlayer] >= MAXMARBLES) || (this.fields[inFieldIndex] != EMPTY) )
	    return false;

	// we may put the thing down
	this.fields[inFieldIndex] = this.nextPlayer;

	this.endMove();
	}


    /* gameState.move */
    function move(inFromField,inToField) {
	
	// to be able to move, each player has to have MAXMARBLES things on the board
	if ( (this.moves[this.nextPlayer] < MAXMARBLES) || (this.fields[inFromField] != this.nextPlayer) || (this.fields[inToField] != EMPTY) )
	    return false;

	// we may move the thing
	this.fields[inFromField] = EMPTY;
	this.fields[inToField] = this.nextPlayer;
	
	this.endMove();
	}
    

    /* gameState.endMove */
    function endMove() {
	    
	// count move
	this.moves[this.nextPlayer]++;
	
	// switch players
	this.nextPlayer = getOtherPlayer(this.nextPlayer);
	}


    /* gameState.getPossibleActions */
    function getPossibleActions() {
	
	theActions = new Array();
	actionCount = 0;
	
	if (this.moves[this.nextPlayer] < MAXMARBLES)
	
	    // list possible put actions
	    for(fieldIndex=0 ; fieldIndex<this.fields.length ; fieldIndex++) {
		if (this.fields[fieldIndex] == EMPTY) {
		    theActions[actionCount] = new action(this.nextPlayer,ACTION_PUT,fieldIndex,0);
		    actionCount++;
		    }
		}
	else {

	    // list possible move actions
	    for(srcFieldIdx=0 ; srcFieldIdx<this.fields.length ; srcFieldIdx++) {

		// do we have a moveable thing?
		if (this.fields[srcFieldIdx] == this.nextPlayer) {
		
		    // look for destination fields
		    for(dstFieldIdx=0 ; dstFieldIdx<this.fields.length ; dstFieldIdx++)
		    
			if (this.fields[dstFieldIdx] == EMPTY) {
			    theActions[actionCount] = new action(this.nextPlayer,ACTION_MOVE,srcFieldIdx,dstFieldIdx);
			    actionCount++;
			    }
			    
		    }
		}
	    }
	    
	return theActions;
	}
	
    
    /* gameState.performAction */
    function performAction(theAction) {

	switch (theAction.what) {
	
	    case ACTION_PUT:
		this.put(theAction.fieldIndex1);
		break;
		
	    case ACTION_MOVE:
		this.move(theAction.fieldIndex1,theAction.fieldIndex2);
		break;
	    }
	}
    
    
    /* gameState.evaluate */
    function getMaxInARow(who) {

	rows = new Array();
	cols = new Array();
	dpos = 0;
	dneg = 0;
	
	rowCounts = new Array();
	rowCountSize = 2 * this.boardSize + 2;
	
	for (i=0 ; i<rowCountSize ; i++)
	    rowCounts[i] = 0;
	    
	fieldIndex = 0;
	
	// init counts
	for (idx=0; idx<this.boardSize ; idx++) {
	    rows[idx] = 0;
	    cols[idx] = 0;
	    }
	
	// count
	for (y=0 ; y<this.boardSize ; y++)
	    for (x=0 ; x<this.boardSize ; x++) {
		if (this.fields[fieldIndex] == who) {
		    rowCounts[2+y]++;
		    rowCounts[2+this.boardSize+x]++;
		    
		    if (x == y)
			rowCounts[0]++;
			
		    if (x == (this.boardSize-1-y))
			rowCounts[1]++;
		    }
		else if (this.fields[fieldIndex] != EMPTY) {
		    rowCounts[2+y]--;
		    rowCounts[2+this.boardSize+x]--;
		    
		    if (x == y)
			rowCounts[0]--;
			
		    if (x == (this.boardSize-1-y))
			rowCounts[1]--;
		    }
		    
		fieldIndex++;
		}

	maxVal = 0;
	
	//str = "Player " + getPlayerName(who) + "\n";
	
	for (i=0 ; i<rowCounts.length ; i++)
	    if (rowCounts[i] > maxVal)
		maxVal = rowCounts[i];
		
	//    str += "row " + i + ": " + rowCounts[i] + "\n";

	return maxVal;
	//alert(str);
	}
	
	
    /* gameState.reset */
    function reset(inStartPlayer) {
    
	this.nextPlayer = inStartPlayer;
	
	this.moves[HUMAN] = 0;
	this.moves[ARTIL] = 0;
	
	for(fieldIndex=0 ; fieldIndex<this.fields.length ; fieldIndex++)
	    this.fields[fieldIndex] = EMPTY;
	}
	
	
    /* class */
    // board encapsulates the board state and image handling
    function board(inBoardSize,inImageNamePrefix,inImagePath,inPrefixEmpty,inPrefixHuman,inPrefixArtil,inSuffix) {

	/* methods */
	this.newGame = newGame;
	this.click = click;
	this.think = think;
	this.makeImagePath = makeImagePath;
	this.updateImage = updateImage;
	this.updateImages = updateImages;
	this.checkVictory = checkVictory;
	
	/* variables */
	// init new game state
	this.state = new gameState(inBoardSize);

	// prefix of the DOM image names
	this.imageNamePrefix = inImageNamePrefix;
	
	// path to images
	this.imagePath = inImagePath;

	// prefixes for field state images
	this.stateImagePrefix = new Array();
	this.stateImagePrefix[EMPTY] = inPrefixEmpty;
	this.stateImagePrefix[HUMAN] = inPrefixHuman;
	this.stateImagePrefix[ARTIL] = inPrefixArtil;

	// suffix after images
	this.imageSuffix = inSuffix;

	// preload images
	for(fieldIndex=0 ; fieldIndex<this.state.fields.length ; fieldIndex++) {
	    tempImg = new Image(); 
	    tempImg.src = this.makeImagePath(fieldIndex,HUMAN);

	    tempImg = new Image(); 
	    tempImg.src = this.makeImagePath(fieldIndex,ARTIL);
	    }
	    
	this.clickFieldNumber = 0;
	}
     
    
    /* board.newGame */
    function newGame(inStartPlayer) {
	this.state.reset(inStartPlayer);
	this.updateImages();
	}


    /* board.click */
    function click(inFieldNumber) {
		
    	inFieldIndex = inFieldNumber - 1;

	if (this.state.nextPlayer != HUMAN) {
	    alert("Der Computer ist am Zug.");
	    return;
	    }
	
	if (this.state.moves[HUMAN] < MAXMARBLES) {
	
	    // put action
	    if (this.state.fields[inFieldIndex] == EMPTY)
		this.state.put(inFieldIndex);
	    else {
		alert("Legen Sie " + MAXMARBLES + " Kugeln aufs Spielfeld.");
		return;
		}
	    }
	else
	    
	    // move action
	    if (this.state.fields[inFieldIndex] == HUMAN) {
		this.clickFieldNumber = inFieldNumber;
		return;
		}
	    else if (this.clickFieldNumber > 0) {
		    this.state.move(this.clickFieldNumber-1,inFieldIndex);
		    this.clickFieldNumber = 0;
		    }
	    else {
		alert("Versetzen Sie eine Kugel, indem Sie zuerst die Kugel anklicken und dann ein leeres Feld.");
		return;
		}
	    
	this.updateImages();
    
	if (!this.checkVictory())
	    window.setTimeout("millThink();",500);
	}


    function checkVictory() {
	artilCount = this.state.getMaxInARow(ARTIL);
	humanCount = this.state.getMaxInARow(HUMAN);

	if (artilCount == this.state.boardSize) {
	    alert("Tut mir leid, Sie haben verloren!");
	    this.newGame(HUMAN);
	    return true;
	    }
	    
	if (humanCount == this.state.boardSize) {
	    alert("Bravo, Sie haben gewonnen!");
	    this.newGame(ARTIL);
	    window.setTimeout("millThink();",1000);
	    return true;
	    }
	
	return false;
	}
	
	
    /* board.think */
    function think() {

	minHumanValue = Number.MAX_VALUE;
	
	// get all possible actions
	actions = this.state.getPossibleActions();

	// init list of winning actions
	bestActions = new Array();
	
	// perform all actions and calculate their values
	for (actionIndex=0 ; actionIndex < actions.length; actionIndex++) {

	    theState = this.state.clone();
	    theState.performAction(actions[actionIndex]);

	    actions[actionIndex].artilValue = theState.getMaxInARow(ARTIL);

	    if (actions[actionIndex].artilValue == this.state.boardSize) {
		size = bestActions.length;
		bestActions[size] = actions[actionIndex];
		}

	    actions[actionIndex].humanValue = theState.getMaxInARow(HUMAN);

	    if (actions[actionIndex].humanValue < minHumanValue)
		minHumanValue = actions[actionIndex].humanValue;
	    }
		
	if (bestActions.length > 0)
	    this.state.performAction(bestActions[Math.floor(Math.random()*(bestActions.length-1))]);
	else {
	
	    // init list of human value-minimizing actions
	    minActions = new Array();

	    // chose among human value minimizing actions
	    for (actionIndex=0 ; actionIndex < actions.length; actionIndex++)
		if (actions[actionIndex].humanValue == minHumanValue) {
		    size = minActions.length;
		    minActions[size] = actions[actionIndex];
		    }
		    	
	    this.state.performAction(minActions[Math.floor(Math.random()*(minActions.length-1))]);

	    }
	    
	this.updateImages();
	
	blue = this.checkVictory();
	}
	

    /* board.makeImagePath */
    function makeImagePath(inFieldIndex,inFieldState) {
	inFieldNumber = inFieldIndex + 1;
	return this.imagePath + this.stateImagePrefix[inFieldState] + inFieldNumber + this.imageSuffix;
	}
	
    
    /* board.updateImage */
    function updateImage(inFieldIndex) {
	inFieldNumber = inFieldIndex + 1;
	document.images[this.imageNamePrefix + inFieldNumber].src = this.makeImagePath(inFieldIndex,this.state.fields[inFieldIndex]);
	}
	
    
    /* board.updateImages */
    function updateImages() {
	for(fieldIndex=0 ; fieldIndex<this.state.fields.length ; fieldIndex++ )
	    this.updateImage(fieldIndex);
	}


    function millThink() {
	theBoard.think();
	}


    /* public global interface functions */
    
    var theBoard = null;
    
    function millInit(inBoardSize,inImageNamePrefix,inImagePath,inPrefixEmpty,inPrefixHuman,inPrefixArtil,inSuffix) {
	theBoard = new board(inBoardSize,inImageNamePrefix,inImagePath,inPrefixEmpty,inPrefixHuman,inPrefixArtil,inSuffix);
	}
    
    function millClick(inFieldNumber) {
	theBoard.click(inFieldNumber);
	}
    
