/*
2009.12.1 Chris Spencer (chrisspen@gmail.com)
A simple rock-paper-scissors framework.
Licensed under the Academic Free License version 3.0.
*/
var ROPASC = function(){
	
	ROCK = 0;
	PAPER = 1;
	SCISSORS = 2;
	
	Game = function(selector, BrainClassA, BrainClassB){
		var total=0;
		var human_wins=0;
		var computer_wins=0;
		var brain_a = new BrainClassA();
        var brain_b = (BrainClassB)?(new BrainClassB()):null;
		var history_a = new Array();
        var history_b = new Array();
		var uid = (''+(new Date()).getTime()+Math.random()).replace('.','');
        var against_human = (brain_b == null);
		
        var set_choice_markings = function(human_selection, computer_selection){
            $('#ropasc-'+uid+' .results td').html('&nbsp;');
            // Mark human selection.
            var sel = $('#ropasc-'+uid+' .results td[symbol='+human_selection+']');
            sel.html(sel.html()+'&nbsp;H');
            // Mark computer selection.
            var sel = $('#ropasc-'+uid+' .results td[symbol='+computer_selection+']');
            sel.html(sel.html()+'&nbsp;C');
        }
	    
        var getHistory = function(){ return selection_history; }
        
	    var create_ui = function(){
	        $(selector).append('<table id="ropasc-'+uid+'" class="ropasc-ui">'
	            +'<tr class="results"><td id="rock-choice" symbol="'+ROCK+'">&nbsp;</td><td id="paper-choice" symbol="'+PAPER+'">&nbsp;</td><td id="scissor-choice" symbol="'+SCISSORS+'">&nbsp;</td></tr>'
	            +'<tr class="buttons">'
	            +'<td class="rock"><a href="#" id="rock-button" symbol="'+ROCK+'">rock</a></td>'
	            +'<td class="paper"><a href="#" id="rock-paper" symbol="'+PAPER+'">paper</a></td>'
	            +'<td class="scissors"><a href="#" id="rock-scissors" symbol="'+SCISSORS+'">scissors</a></td></tr>'
	            +'<tr><th>total</th><th>wins</th><th>loses</th></tr>'
	            +'<tr class="status"><td class="total"></td><td class="human-wins"></td><td class="computer-wins"></td></tr></table>');
	        
	        // Bind events to brain.
	        $('#ropasc-'+uid+' .rock a').click(function(e){ return run_single_round(e); });
	        $('#ropasc-'+uid+' .paper a').click(function(e){ return run_single_round(e); });
	        $('#ropasc-'+uid+' .scissors a').click(function(e){ return run_single_round(e); });
	        $('#ropasc-'+uid+' .total').html(total);
	        $('#ropasc-'+uid+' .human-wins').html(human_wins);
	        $('#ropasc-'+uid+' .computer-wins').html(computer_wins);
	        
	    }
	    
	    var run_single_round = function(e){
	    
            // Collect selections.
            var computer_selection = brain_a.get_selection({opponent_history:history_b, our_history:history_a});
            if(against_human){
                var human_selection = parseInt($(e.target).attr('symbol'));
            }else{
                var human_selection = brain_b.get_selection({opponent_history:history_a, our_history:history_b});
            }
            history_a.push([computer_selection]);
            history_b.push([human_selection]);
            
            // Score game.
            var winner = 0; // -1=computer, 0=tie, +1=human
            if(human_selection == get_beating(computer_selection)){
                winner = +1;
                against_human && $('#ropasc-'+uid+' .human-wins').animate({ backgroundColor: "#afa" }, "fast").animate({ backgroundColor: "white" }, "fast");
            }else if(computer_selection == get_beating(human_selection)){
                winner = -1;
                against_human && $('#ropasc-'+uid+' .computer-wins').animate({ backgroundColor: "#faa" }, "fast").animate({ backgroundColor: "white" }, "fast");
            }
            
            // Publish selections.
            against_human && set_choice_markings(human_selection, computer_selection);
            
            // Publish score.
            total += 1;
            human_wins += (winner > 0);
            computer_wins += (winner < 0);
            if(against_human){
	            $('#ropasc-'+uid+' .total').html(total);
	            $('#ropasc-'+uid+' .human-wins').html(human_wins);
	            $('#ropasc-'+uid+' .computer-wins').html(computer_wins);
            }
            
            return false;
	    }
		
		against_human && create_ui();
		
		var api = {
		    run_single_round:run_single_round,
		    getTotal:function(){ return total; },
            getWinsA:function(){ return computer_wins; },
            getWinsB:function(){ return human_wins; }
		}
		return api;
	}
	
	Tournament = function(selector, BrainClasses){
	    // Scores every brain against every other brain.
	    var tbl = $('<table border="1"></table>').appendTo(selector);
        var tr = $('<tr></tr>').appendTo(tbl);
        $('<td></td>').appendTo(tr);
        for(var i=0; i<BrainClasses.length; i++){
            var name = (new BrainClasses[i]).abbrev;
            $('<th>'+name+'</th>').appendTo(tr);
        }
	    for(var i=0; i<BrainClasses.length; i++){ // Row.
            var tr = $('<tr></tr>').appendTo(tbl)
            var name = (new BrainClasses[i]).abbrev;
            $('<th>'+name+'</th>').appendTo(tr);
	        for(var j=0; j<i+1; j++){ // Column.
	            // Run several games between two brains.
	            var g = new Game(null, BrainClasses[i], BrainClasses[j]);
	            for(var gi=0; gi<1000; gi++){
	                g.run_single_round();
	            }
	            $('<td id="score-'+i+'-'+j+'">+'+g.getWinsA()+'/-'+g.getWinsB()+'</td>').appendTo(tr);
	        }
	    }
	    
	    var api = {
	    }
	    return api;
	}
	
	random_weighted_selection = function(weights){
	    // Returns a random weighted selection.
	    // e.g. weights = {a:123, b:34, c:8987}
		var sum_of_weight = 0;
		var weights = [weights[0],weights[1],weights[2]];
		for(var i=0; i<weights.length; i++){
		    sum_of_weight += weights[i];
		}
		var rnd = Math.random()*sum_of_weight;
        for(var i=0; i<weights.length; i++){
		    if(rnd < weights[i]) return i;
		    rnd -= weights[i];
		}
	}
	
	get_beating = function(i){
	    // Returns the selection that beats the given selection.
	    return (i+1) % 3;
	}
	
	/* Define Computer Opponents. */
    
    RandomBrain = function(){
        return {
            abbrev:'Rand',
            get_selection:function(game){
                return random_weighted_selection({0:1,1:1,2:1});
            }
        }
        
    }
	
	FrequencyBrain = function(){
		// Counts the selections made the opponent and weights
		// itself to select the choice that beats the opponent's
		// most likely selection.
		
		var opponent_selections = {0:1, 1:1, 2:1}; //selection:count 
		
		return {
		    abbrev:'Freq',
		    get_selection:function(game){
		        var opponent_history = game.opponent_history;
		        if(!opponent_history.length){
		            return random_weighted_selection(opponent_selections);
		        }
		        
		        // Update internal model of opponent.
		        var last_opponent_selection = opponent_history[opponent_history.length-1];
		        opponent_selections[last_opponent_selection] += 1;
		        
		        // We assume the opponent is likely to choose the next
		        // selection in proportion to their past selections. So we
		        // choose the selection that beats their most frequent
		        // (and therefore likely) selection.
		        // Note: Easily beatin by the opponent simply sequentially
		        // iterating through selections.
		        return get_beating(random_weighted_selection(opponent_selections));
		        
		    }
		}
		
	}
        
    Conditional1Brain = function(){
        // Forms a conditional probability distribution by tracking
        // which selections the opponent made after its last selection
        // in the previous game, and weights itself to select the
        // choice that beats the opponent's most likely selection.
        
        var opponent_selections = {}; //{selection0:{selection1:count}}
        
        // Initialize event counts.
        for(var i=0; i<3; i++){
            opponent_selections[i] = {}
            for(var j=0; j<3; j++){
                opponent_selections[i][j] = 1;
            }
        }
        
        return {
            abbrev:'Cond1',
            get_selection:function(game){
                var opponent_history = game.opponent_history;
                if(opponent_history.length < 2){
                    return random_weighted_selection({0:1,1:1,2:1});
                }
                
                // Update internal model of opponent.
                var last0 = opponent_history[opponent_history.length-2];
                var last1 = opponent_history[opponent_history.length-1];
                opponent_selections[last0][last1] += 1;
                
                return get_beating(random_weighted_selection(opponent_selections[last1]));
            }
        }
    }
		
	Conditional2Brain = function(){
		// Forms a conditional probability distribution by tracking
		// which selections the opponent made after our own selection
		// in the previous game, and weights itself to select the
		// choice that beats the opponent's most likely selection.
        
        var opponent_selections = {}; //{our_selection:{opp_selection:count}}
        
        // Initialize event counts.
        for(var i=0; i<3; i++){
            opponent_selections[i] = {}
            for(var j=0; j<3; j++){
                opponent_selections[i][j] = 1;
            }
        }
        
        return {
            abbrev:'Cond2',
            get_selection:function(game){
                var opponent_history = game.opponent_history;
                var our_history = game.our_history;
                if(opponent_history.length < 2){
                    return random_weighted_selection({0:1,1:1,2:1});
                }
                
                // Update internal model of opponent.
                var our_last = our_history[our_history.length-2];
                var their_last = opponent_history[opponent_history.length-1];
                opponent_selections[our_last][their_last] += 1;
                
                var our_current = our_history[our_history.length-1];
                return get_beating(random_weighted_selection(opponent_selections[our_current]));
            }
        }
	}
	
	// Expose API.
	return {
	    Game:Game,
	    Tournament:Tournament,
	    RandomBrain:RandomBrain,
		FrequencyBrain:FrequencyBrain,
		Conditional1Brain:Conditional1Brain,
        Conditional2Brain:Conditional2Brain,
		random_weighted_selection:random_weighted_selection
	};
	
}();