Code viewer for World: Trapper Mind
// Cloned by Theo Delettre (21262281) on 1 Nov 2021 from World "Enemy Mind" by Theo Delettre 
// Please leave this clone trail here.


// Our trapper algorithm simply calculates the best single next move by seing how the agent would react
function trapperThink(ai, aj, ei, ej, GRID, TRAPGRID){
    //constants to simplify predicted agent movement
    const ACTION_LEFT 			= 0;		   
    const ACTION_RIGHT 			= 1;
    const ACTION_UP 			= 2;		 
    const ACTION_DOWN 			= 3;
    
    
    const gridsize = GRID.length;
    var open = [];
    
    // We start by exploring our starting position, where the enemy currently is
    open.push( new Pos(ei,ej) );
    
    // We still have the open set, but since we are only predicting 1 move now
    //  we only use it to store neighboring + current pos
    var curr = open[0];
    if(curr.h!==0){
        addNeighbors(curr.i,curr.j);
        curr = maxOpen();
    }
    
    // once we have decided the best next move we can just return that
    return {
        nexti: curr.i ,
        nextj: curr.j,
    };
    
    // For each possible next move from the agent, there are 2 moves the agent can do.
    //  We save the possible agent positions in the ANode class, as trees
    function ANode(hai,haj,weight,parent,depth,hei,hej) {
        // agent and enemy positions in this scenario
        this.hai = hai;
        this.haj = haj;
        this.hei = hei;
        this.hej = hej;
        
        // how likely the agent is to make this move based on 
        this.weight = weight;
        
        // current node depth in tree
        this.depth = depth;
        
        // parent node
        this.parent = parent;
        
        //Array of children nodes (next possible moves)
        this.children = [];
        
        // This adds the next moves to the current node's children based on the simple mind algorithm
        this.getChildren = function(){
            if ( this.hej < this.haj  ) {
                this.agentDirection(ACTION_UP, 2);
                this.agentDirection(ACTION_RIGHT, 1);
                this.agentDirection(ACTION_LEFT, 1);
            } else if ( this.hej > this.haj ) {
                this.agentDirection(ACTION_DOWN, 2);
                this.agentDirection(ACTION_RIGHT, 1);
                this.agentDirection(ACTION_LEFT, 1);
		    } else if ( this.hei < this.hai ){
                this.agentDirection(ACTION_RIGHT, 2);
                this.agentDirection(ACTION_UP, 1);
                this.agentDirection(ACTION_DOWN, 1);
		    } else if ( this.hei > this.hai ) {
		        this.agentDirection(ACTION_LEFT, 2);
                this.agentDirection(ACTION_UP, 1);
                this.agentDirection(ACTION_DOWN, 1);
		    }
        }
        
        this.agentDirection = function(dir, weight) {
            var newI = this.hai;
            var newJ = this.haj;

            switch(dir) {
                case ACTION_LEFT:
                    newI--;
                    break;
                case ACTION_RIGHT:
                    newI++;
                    break;
                case ACTION_UP:
                    newJ++;
                    break;
                case ACTION_DOWN:
                    newJ--;
                    break;
            }
            
            if(!occupied(newI, newJ, this.hei, this.hej))
                this.children.push(new ANode(newI, newJ, weight, this, this.depth+1, this.hei, this.hej));
        }
        
        // if wew have reached a depth of 2, stop going deeper (because 2 agent moves per enemy move)
        if (this.depth<2) this.getChildren();
        
        // reccursive function to obtain average heuristic for a given setup
        this.getAverage = function(){
            if(this.children.length === 0) {
                // if the agent has finished moving for the turn, return how close it is to a hole
                //console.log("       WE GOT ",this.hai, this.haj, "FOR A HEURISTIC OF ", TRAPGRID[this.hai][this.haj]);
                return TRAPGRID[this.hai][this.haj];
            } else {
                var totalH = 0;
                var totalWeight = 0;
                // if the agent still has moves left, calculate the weighted average of the next moves heuristic
                for (let k = 0; k < this.children.length ; k++ ) {
                    totalWeight+= this.children[k].weight;
                    totalH += this.children[k].weight * this.children[k].getAverage();
                }
                return totalH / totalWeight;
            }
        }
    }
    
    // Position of the enemy after a move
    function Pos(i,j) {
        this.i = i;
        this.j = j;
        
        // heuristic is the estimated distance of the agent after it has reacted to this move
        this.root = new ANode(ai,aj,1,null,0,this.i,this.j);
        this.dist = Math.abs(distance2D(ai,aj,this.i,this.j) - 1);
        this.h = this.root.getAverage();
    }
    
    function distance2D(ai,aj,bi,bj){
        return Math.abs(ai-bi)+Math.abs(aj-bj);
    }
    
    //adds all the neighbors
    function addNeighbors(x,y){
        for (var a = x-1; a <= x+1 ; a++ ) {
            for (var b = y-1; b <= y+1 ; b++ ) {
                //if(x >=0 && y>=0 && x<gridsize && y<gridsize) { //Not necessary since there should be a wall outline
                
                if(GRID[a][b] != 1 && GRID[a][b] != 2){
                    open.push( new Pos(a,b) );
                }
    	    }
        }
    }
    
    // Pick the move with the best heuristic. If multiple moves have the same heuristic, pick the that brings enemy closest to agent
    function maxOpen(){
        var maxPos = null;
        console.log("----- POSSIBLE MOVES FROM", ei, ej, " ------");
        for ( i = 0; i < open.length ; i++ ) {
            console.log(" E Coord:",open[i].i,open[i].j, "A coord:",ai,aj, "heuristic:",open[i].h, "Dist:", open[i].dist);
            if(maxPos===null){
                maxPos = open[i];
            } else if (open[i].h < maxPos.h || (open[i].h == maxPos.h && open[i].dist < maxPos.dist) ){
                maxPos = open[i];
            }
        }
        console.log("  CHOSEN E Coord:",maxPos.i,maxPos.j, "A coord:",ai,aj, "heuristic:",maxPos.h, "Dist:", maxPos.dist);
        return maxPos;
    }
	
	function occupied(i,j,hei,hej) {
	    if (GRID[i][j] == 1 || GRID[i][j] == 2) return true;
	    if (i == hei && j == hej) return true;
	    return false;
	}
}