Code viewer for World: Two Player Draughts
/*
=============================== Two Player Draughts World ===============================

Written by Graham Edmond Bartley for CA318: Advanced Algorithms and AI Search

----------- General Description -----------
This world models a two player game of English Draughts and all input to control it comes from the keyboard.
English draughts rules are as follows:
    - 8 x 8 board size
    - 12 white draughts
    - 12 black draughts
    - black goes first
    - "men" draughts can only move diagonally one square forward
    - "king" draughts can only move diagonally one square forward or backward
    - "men" draughts become "kings" when they reach the furthest line forward they can go
    - capture enemy pieces by "jumping" over them
    - multiple captures can be executed in a row if possible to do so
    - game ends when one player runs out of draughts or can no longer make a valid move

----------- Controls -----------
    UP ARROW        moves the current selector up one space
    DOWN ARROW      moves the current selector down one space
    LEFT ARROW      moves the current selector left one space
    RIGHT ARROW     moves the current selector right one space
    NUMPAD 1        selects the draught at the current selector location
    NUMPAD 2        moves the selected draught to the current selector location /
                    captures the enemy draught at the current selector location and places the selected draught appropriately

----------- Features & Issues -----------
Features:
    - 8 x 8 size draughts board
    - 12 white draughts & 12 black draughts
    - black goes first
    - world implements turn changes and tracks number of turns
    - player 1 selector is only shown on player 1's turn and likewise for player 2
    - "men" draughts can only move diagonally one square forward
    - "king" draughts can only move diagonally one square forward or backward
    - "men" draughts become "kings" when they reach the furthest line forward they can go
    - "king" draughts have a different texture
    - capture enemy pieces by selecting to move onto them, world will place you as if you jumped over them
    - selectors and draughts cannot move out of bounds (unless no longer in play)
    - when draughts are captured, they are placed on either side of the board in a line
    - the line of captured draughts wraps around so as not to go off screen
    - if all draughts of a particular colour have been captured, the game will end and a message will say that the player with the opposite colour will have won the game

Issues:
    - does not implement the functionality of allowing for multiple valid captures in one turn.
    - will not directly terminate if no more valid moves can be made but will eventually terminate by time out (after ~16.6 mins total runtime)

----------- Image Sources -----------
kings:              https://cdn.pixabay.com/photo/2012/04/18/00/41/chess-36306_960_720.png
border:             https://s-media-cache-ak0.pinimg.com/236x/62/15/13/6215134941540ac55f842953fc3fcd88.jpg
world image:        https://cnet3.cbsistatic.com/img/vifGx5HKGIWwXx1l7XkPm2ZOd9w=/770x433/2007/07/20/407755d8-f4d7-11e2-8c7c-d4ae52e62bcc/checkers_board.jpg
draughts:           https://www.colourbox.com/preview/2253423-draughts-pieces-isolated-on-white-background.jpg
space skybox:       http://en.spaceengine.org/forum/21-514-1

*/

// World must define these:
const CLOCKTICK = 100; // speed of run - move things every n milliseconds
const MAXSTEPS = 10000; // length of a run before timing out (16.6 mins)

//---- global constants: -------------------------------------------------------

const gridsize = 10; // number of squares along side of world       

const NOBOXES = (gridsize - 2) * (gridsize - 2);
// density of WhiteDraught - number of internal boxes

const squaresize = 1000; // size of square in pixels
const MAXPOS = gridsize * squaresize; // length of one side in pixels 

const SKYCOLOR = 0xddffdd; // a number, not a string 
const BLANKCOLOR = SKYCOLOR; // make objects this color until texture arrives (from asynchronous file read)

const show3d = false; // Switch between 3d and 2d view (both using Three.js) 

const startRadiusConst = MAXPOS * 0.8; // distance from centre to start the camera at
const skyboxConst = MAXPOS * 3; // where to put skybox 
const maxRadiusConst = MAXPOS * 10; // maximum distance from camera we will render things  

//--- Mind can pick one of these actions -----------------

const ACTION_LEFT = 0;
const ACTION_RIGHT = 1;
const ACTION_UP = 2;
const ACTION_DOWN = 3;
const ACTION_STAYSTILL = 4;
const ACTION_ASELECTDRAUGHT = 5;
const ACTION_AMAKEMOVE = 6;
const ACTION_ESELECTDRAUGHT = 7;
const ACTION_EMAKEMOVE = 8;

// in initial view, (smaller-larger) on i axis is aligned with (left-right)
// in initial view, (smaller-larger) on j axis is aligned with (away from you - towards you)

// contents of a grid square

const GRID_BLANK = 0;
const GRID_WALL = 1;

// who's turn is it?

const AGENT_TURN = 0;
const ENEMY_TURN = 1;

//enemy goes first
var turn = ENEMY_TURN;

//keeps track of the number of draughts that have been captured
var capturedWhiteDraughts = 0;
var capturedBlackDraughts = 0;

//---- start of World class -------------------------------------------------------

function World() {

    //-----Draught class---------
    function Draught (obj, i, j) {
        this.obj = obj;
        this.i = i;
        this.j = j;
        this.isKing = false;
    }
    
    // most of World can be private 
    // regular "var" syntax means private variables:

    var BOXHEIGHT; // 3d or 2d box height 


    var GRID = new Array(gridsize); // can query GRID about whether squares are occupied, will in fact be initialised as a 2D array   
    var WALLS = new Array(4 * gridsize); // need to keep handles to wall and WhiteDraught objects so can find them later to paint them 
    
    var theagent, theenemy;

    //12 of each colour draught
    var whitedraughts = new Array(12);
    var blackdraughts = new Array(12);

    //agent position on squares
    var ai, aj;
  
  //enemy position on squares
  var ei, ej;
  
  //position of selected agent draught
  var selectedAgentDraughti = -1;
  var selectedAgentDraughtj = -1;

  //position of selected enemy draught
  var selectedEnemyDraughti = -1;
  var selectedEnemyDraughtj = -1;

  //keeps track of where to put captured draughts
  var capturedWhiteDraughtj = 0;
  var capturedBlackDraughtj = 0;

  var capturedBlackDraughti = 11;
  var capturedWhiteDraughti = -2;
  
    var turns;

    var self = this; // needed for private fn to call public fn - see below  

    // regular "function" syntax means private functions:

    function initGrid() {
        for (var i = 0; i < gridsize; i++) {
            GRID[i] = new Array(gridsize); // each element is an array 

            for (var j = 0; j < gridsize; j++) {
                GRID[i][j] = GRID_BLANK;
            }
        }
    }

    function occupied(i, j) // is this square occupied
    { // variable objects 
        if (GRID[i][j] == GRID_WALL) return true; // fixed objects     

        return false;
    }

    //returns true if i,j is out of bounds
    function isOutOfBounds(i, j) {
        return ((i < 1 || i > 8) || (j < 1 || j > 8));
    }

    // logically, coordinates are: y=0, x and z all positive (no negative)    
    // logically my dimensions are all positive 0 to MAXPOS
    // to centre everything on origin, subtract (MAXPOS/2) from all dimensions 

    function translate(x) {
        return (x - (MAXPOS / 2));
    }

    //--- skybox ----------------------------------------------------------------------------------------------


    function initSkybox() {
        // x,y,z positive and negative faces have to be in certain order in the array 

        var materialArray = [
            (new THREE.MeshBasicMaterial({
                map: THREE.ImageUtils.loadTexture("/uploads/starter/sky_pos_z.jpg"),
                side: THREE.BackSide
            })),
            (new THREE.MeshBasicMaterial({
                map: THREE.ImageUtils.loadTexture("/uploads/starter/sky_neg_z.jpg"),
                side: THREE.BackSide
            })),
            (new THREE.MeshBasicMaterial({
                map: THREE.ImageUtils.loadTexture("/uploads/starter/sky_pos_y.jpg"),
                side: THREE.BackSide
            })),
            (new THREE.MeshBasicMaterial({
                map: THREE.ImageUtils.loadTexture("/uploads/starter/sky_neg_y.jpg"),
                side: THREE.BackSide
            })),
            (new THREE.MeshBasicMaterial({
                map: THREE.ImageUtils.loadTexture("/uploads/starter/sky_pos_x.jpg"),
                side: THREE.BackSide
            })),
            (new THREE.MeshBasicMaterial({
                map: THREE.ImageUtils.loadTexture("/uploads/starter/sky_neg_x.jpg"),
                side: THREE.BackSide
            })),
        ];

        var skyGeometry = new THREE.CubeGeometry(skyboxConst, skyboxConst, skyboxConst);
        var skyMaterial = new THREE.MeshFaceMaterial(materialArray);
        var theskybox = new THREE.Mesh(skyGeometry, skyMaterial);
        threeworld.scene.add(theskybox); // We are inside a giant cube
    }

    // --- asynchronous load textures from file ----------------------------------------
    // loader return can call private function

    function loadTextures() {

        //border texture
        var loader1 = new THREE.TextureLoader();
        loader1.load('/uploads/bartleg3/wood.jpg', function(thetexture) {
            thetexture.minFilter = THREE.LinearFilter;
            paintWalls(new THREE.MeshBasicMaterial({
                map: thetexture
            }));
        });

        //white draught "man" texture
        var loader2 = new THREE.TextureLoader();
        loader2.load('/uploads/bartleg3/whitedraught.jpg', function(thetexture) {
            thetexture.minFilter = THREE.LinearFilter;
            paintWhiteDraughts(new THREE.MeshBasicMaterial({
                map: thetexture
            }));
        });

        //black draught "man" texture
        var loader3 = new THREE.TextureLoader();
        loader3.load('/uploads/bartleg3/blackdraught.jpg', function(thetexture) {
            thetexture.minFilter = THREE.LinearFilter;
            paintBlackDraughts(new THREE.MeshBasicMaterial({
                map: thetexture
            }));
        });

        //white draught "king" texture
        var loader4 = new THREE.TextureLoader();
        loader4.load('/uploads/bartleg3/whiteking.png', function(thetexture) {
            thetexture.minFilter = THREE.LinearFilter;
            paintWhiteKingDraughts(new THREE.MeshBasicMaterial({
                map: thetexture
            }));
        });

        //black draught "king" texture
        var loader5 = new THREE.TextureLoader();
        loader5.load('/uploads/bartleg3/blackking.png', function(thetexture) {
            thetexture.minFilter = THREE.LinearFilter;
            paintBlackKingDraughts(new THREE.MeshBasicMaterial({
                map: thetexture
            }));
        });

        //player 2 selector texture
        var loader6 = new THREE.TextureLoader();
        loader6.load('/uploads/bartleg3/p2.png', function(thetexture) {
            thetexture.minFilter = THREE.LinearFilter;
            theagent.material = new THREE.MeshBasicMaterial({
                map: thetexture
            });
        });
    
        //player 1 selector texture
        var loader7 = new THREE.TextureLoader();
        loader7.load('/uploads/bartleg3/p1.png', function(thetexture) {
            thetexture.minFilter = THREE.LinearFilter;
            theenemy.material = new THREE.MeshBasicMaterial({
                map: thetexture
            });
        });
    }

    // --- add fixed objects ---------------------------------------- 

    function initLogicalWalls() // set up logical walls in data structure, whether doing graphical run or not   
    {
        for (var i = 0; i < gridsize; i++)
            for (var j = 0; j < gridsize; j++)
                if ((i === 0) || (i == gridsize - 1) || (j === 0) || (j == gridsize - 1)) {
                    GRID[i][j] = GRID_WALL;
                }
    }

    function initThreeWalls() // graphical run only, set up blank boxes, painted later    
    {
        var t = 0;
        for (var i = 0; i < gridsize; i++)
            for (var j = 0; j < gridsize; j++)
                if (GRID[i][j] == GRID_WALL) {
                    var shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
                    var thecube = new THREE.Mesh(shape);
                    thecube.material.color.setHex(BLANKCOLOR);

                    thecube.position.x = translate(i * squaresize); // translate my simple (i,j) block-numbering coordinates to three.js (x,y,z) coordinates 
                    thecube.position.z = translate(j * squaresize);
                    thecube.position.y = 0;

                    threeworld.scene.add(thecube);
                    WALLS[t] = thecube; // save it for later
                    t++;
                }
    }

    function paintWalls(material) {
        for (var i = 0; i < WALLS.length; i++) {
            if (WALLS[i]) WALLS[i].material = material;
        }
    }
  
    // --- enemy functions -----------------------------------

    function drawEnemy() // given ei, ej, draw it 
    {
        var x = translate(ei * squaresize);
        var z = translate(ej * squaresize);
        var y = 0;

        theenemy.position.x = x;
        theenemy.position.y = y;
        theenemy.position.z = z;
        threeworld.scene.add(theenemy);
    }


    function initLogicalEnemy() {
        ei = 1;
        ej = 1;
    }


    function initThreeEnemy() {
        var shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
        theenemy = new THREE.Mesh(shape);
        theenemy.material.color.setHex(BLANKCOLOR);
        drawEnemy();
    }

    function moveLogicalEnemy(a) {
        var i = ei;
        var j = ej;

        if (a == ACTION_LEFT) {
            i--;
        } else if (a == ACTION_RIGHT) {
            i++;
        } else if (a == ACTION_UP) {
            j--;
        } else if (a == ACTION_DOWN) {
            j++;
        } else if (a == ACTION_ESELECTDRAUGHT) {
            enemySelectDraught();
        } else if (a == ACTION_EMAKEMOVE) {
            enemyMakeMove();
        }

        if (!occupied(i, j)) {
            ei = i;
            ej = j;
        }
    }

    //selects the draught on the space currently occupied by the enemy
    function enemySelectDraught() {
        //if there's a black draught on this space, select it
        if (isBlackDraught(ei, ej)) {
            selectedEnemyDraughti = ei;
            selectedEnemyDraughtj = ej;
        }
    }

    //move the enemy's selected draught to the enemy's selected space
    function enemyMakeMove() {
        //if the enemy has selected a draught to move
        if (selectedEnemyDraughti != -1 && selectedEnemyDraughtj != -1) {
            var draught = blackdraughts[getBlackDraught(selectedEnemyDraughti, selectedEnemyDraughtj)];

            //variables to hold new coords
            var newi = -1;
            var newj = -1;

            //if the draught being moved is not a king
            if (!draught.isKing) {
                /*if the space the enemy is moving to doesn't have a black draught on it
                and the space is one space forward diagonal of the current space of the draught it is moving*/
                if (!isBlackDraught(ei, ej) && ((ei == (selectedEnemyDraughti + 1) || ei == (selectedEnemyDraughti - 1)) && ej == (selectedEnemyDraughtj + 1))) {
                    //if the enemy is capturing a white draught
                    if (isWhiteDraught(ei, ej)) {

                        //check which i direction to move in
                        if (ei == (selectedEnemyDraughti + 1)) {
                            newi = ei + 1;
                        } else {
                            newi = ei - 1;
                        }

                        //always forward since not a king
                        newj = ej + 1;

                        //if not moving out of bounds or onto another draught
                        if (!isOutOfBounds(newi, newj) && !isWhiteDraught(newi, newj) && !isBlackDraught(newi, newj)) {
                            //capture the white draught
                            removeWhiteDraught(getWhiteDraught(ei, ej));

                            draught.i = newi;
                            draught.j = newj;

                            //if this black draught has reached the other side, make it a King and load it's new texture
                            if (draught.j == 8) {
                                draught.isKing = true;
                                loadTextures();
                            }

                            //reset selection
                            selectedEnemyDraughti = -1;
                            selectedEnemyDraughtj = -1;

                            //turn change
                            turn = AGENT_TURN;
                            turns++;             
                        }
                    } else {
                        draught.i = ei;
                        draught.j = ej;

                        //if this black draught has reached the other side, make it a King and load it's new texture
                        if (draught.j == 8) {
                            draught.isKing = true;
                            loadTextures();
                        }

                        //reset selection
                        selectedEnemyDraughti = -1;
                        selectedEnemyDraughtj = -1;

                        //turn change
                        turn = AGENT_TURN;
                        turns++;
                    }
                }
            //if the draught being moved is a king
            } else {
                /*if the space the enemy is moving to doesn't have a black draught on it
                and the space is one space forward or backward diagonal of the current space of the draught it is moving*/
                if (!isBlackDraught(ei, ej) && ((ei == (selectedEnemyDraughti + 1) || ei == (selectedEnemyDraughti - 1)) && (ej == (selectedEnemyDraughtj + 1) || ej == (selectedEnemyDraughtj - 1)))) {
                    //if the enemy is capturing a white draught
                    if (isWhiteDraught(ei, ej)) {

                        //check which i direction to move in
                        if (ei == (selectedEnemyDraughti + 1)) {
                            newi = ei + 1;
                        } else {
                            newi = ei - 1;
                        }

                        //check which j direction to move in
                        if (ej == (selectedEnemyDraughtj + 1)) {
                            newj = ej + 1;
                        } else {
                            newj = ej - 1;
                        }

                        //if not moving out of bounds or onto another draught
                        if (!isOutOfBounds(newi, newj) && !isWhiteDraught(newi, newj) && !isBlackDraught(newi, newj)) {
                            //capture the white draught
                            removeWhiteDraught(getWhiteDraught(ei, ej));

                            draught.i = newi;
                            draught.j = newj;

                            //reset selection
                            selectedEnemyDraughti = -1;
                            selectedEnemyDraughtj = -1;

                            //turn change
                            turn = AGENT_TURN;
                            turns++;
                        }
                    } else {
                        draught.i = ei;
                        draught.j = ej;

                        //reset selection
                        selectedEnemyDraughti = -1;
                        selectedEnemyDraughtj = -1;

                        //turn change
                        turn = AGENT_TURN;
                        turns++;
                    }
                }            
            }
        }
    }

    // --- agent functions -----------------------------------


    function drawAgent() // given ai, aj, draw it 
    {
        var x = translate(ai * squaresize);
        var z = translate(aj * squaresize);
        var y = 0;

        theagent.position.x = x;
        theagent.position.y = y;
        theagent.position.z = z;
        threeworld.scene.add(theagent);

        threeworld.follow.copy(theagent.position); // follow vector = agent position (for camera following agent)
    }

    function initLogicalAgent() {
        ai = 8;
        aj = 8;
    }

    function initThreeAgent() {
        var shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
        theagent = new THREE.Mesh(shape);
        theagent.material.color.setHex(BLANKCOLOR);
        drawAgent();
    }

    // this is called by the infrastructure that gets action a from the Mind
    function moveLogicalAgent(a) {
        var i = ai;
        var j = aj;

        if (a == ACTION_LEFT) {
            i--;
        } else if (a == ACTION_RIGHT) {
            i++;
        } else if (a == ACTION_UP) {
            j--;
        } else if (a == ACTION_DOWN) {
            j++;
        } else if (a == ACTION_ASELECTDRAUGHT) {
            agentSelectDraught();
        } else if (a == ACTION_AMAKEMOVE) {
            agentMakeMove();
        }

        if (!occupied(i, j)) {
            ai = i;
            aj = j;
        }
    }
  
    //selects the draught the agent is on
    function agentSelectDraught() {
        //if there's a white draught on this space, select it
        if (isWhiteDraught(ai, aj)) {
            selectedAgentDraughti = ai;
            selectedAgentDraughtj = aj;
        }
    }
  
    //move the selected draught to the selected space
    function agentMakeMove() {
        /*if you have selected a draught to be moved*/
        if (selectedAgentDraughti != -1 && selectedAgentDraughtj != -1) {
            var draught = whitedraughts[getWhiteDraught(selectedAgentDraughti, selectedAgentDraughtj)];
            
            //variables to hold new coords
            var newi = -1;
            var newj = -1;

            //if the draught being moved is not a king
            if (!draught.isKing) {
                /*if the space you're moving to doesn't have a white draught on it,
                and the space is one space forward diagonal of the current space of the draught you are moving*/
                if (!isWhiteDraught(ai, aj) && ((ai == (selectedAgentDraughti + 1) || ai == (selectedAgentDraughti - 1)) && aj == (selectedAgentDraughtj - 1))) {
                    //if you are capturing a black draught
                    if (isBlackDraught(ai, aj)) {

                        //check which i direction to move in
                        if (ai == (selectedAgentDraughti + 1)) {
                            newi = ai + 1;
                        } else {
                            newi = ai - 1;
                        }

                        //j direction will always be forward because it's not a king
                        newj = aj - 1;

                        //if not moving out of bounds or onto another draught
                        if (!isOutOfBounds(newi, newj) && !isWhiteDraught(newi, newj) && !isBlackDraught(newi, newj)) {
                            //capture the black draught
                            removeBlackDraught(getBlackDraught(ai, aj));

                            draught.i = newi;
                            draught.j = newj;

                            //if this white draught has reached the other side, make it a King and load it's new texture
                            if (draught.j == 1) {
                                draught.isKing = true;
                                loadTextures();
                            }

                            //reset selection
                            selectedAgentDraughti = -1;
                            selectedAgentDraughtj = -1;

                            //turn change
                            turn = ENEMY_TURN;
                            turns++;
                        }
                    } else {
                        draught.i = ai;
                        draught.j = aj;

                        //if this white draught has reached the other side, make it a King and load it's new texture
                        if (draught.j == 1) {
                            draught.isKing = true;
                            loadTextures();
                        }

                        //reset selection
                        selectedAgentDraughti = -1;
                        selectedAgentDraughtj = -1;

                        //turn change
                        turn = ENEMY_TURN;
                        turns++;
                    }
                }
            //if the draught being moved is a king
            } else {
                /*if the space you're moving to doesn't have a white draught on it,
                and the space is one space forward or backward diagonal of the current space of the draught you are moving*/
                if (!isWhiteDraught(ai, aj) && ((ai == (selectedAgentDraughti + 1) || ai == (selectedAgentDraughti - 1)) && (aj == (selectedAgentDraughtj - 1) || (aj == (selectedAgentDraughtj + 1))))) {
                    //if you are capturing a black draught
                    if (isBlackDraught(ai, aj)) {

                        //check which i direction to move in
                        if (ai == (selectedAgentDraughti + 1)) {
                            newi = ai + 1;
                        } else {
                            newi = ai - 1;
                        }

                        //check which j direction to move in
                        if (aj == (selectedAgentDraughtj - 1)) {
                            newj = aj - 1;
                        } else {
                            newj = aj + 1;
                        }

                        //if not moving out of bounds or onto another draught
                        if (!isOutOfBounds(newi, newj) && !isWhiteDraught(newi, newj) && !isBlackDraught(newi, newj)) {
                            //capture the black draught
                            removeBlackDraught(getBlackDraught(ai, aj));

                            draught.i = newi;
                            draught.j = newj;

                            //reset selection
                            selectedAgentDraughti = -1;
                            selectedAgentDraughtj = -1;

                            //turn change
                            turn = ENEMY_TURN;
                            turns++;
                        }
                    } else {
                        draught.i = ai;
                        draught.j = aj;

                        //reset selection
                        selectedAgentDraughti = -1;
                        selectedAgentDraughtj = -1;

                        //turn change
                        turn = ENEMY_TURN;
                        turns++;
                    }
                }
            }
        }
    }

    // user control 
    // Note that this.takeAction(a) is constantly running at same time, redrawing the screen.
    function keyHandler(e) {
        //if it's the agent's turn, respond to their input
        if (turn == AGENT_TURN) {
            if (e.keyCode == 37) moveLogicalAgent(ACTION_LEFT);
            if (e.keyCode == 38) moveLogicalAgent(ACTION_UP);
            if (e.keyCode == 39) moveLogicalAgent(ACTION_RIGHT);
            if (e.keyCode == 40) moveLogicalAgent(ACTION_DOWN);
            if (e.keyCode == 97) moveLogicalAgent(ACTION_ASELECTDRAUGHT);
            if (e.keyCode == 98) moveLogicalAgent(ACTION_AMAKEMOVE);
        }

        //if it's the enemy's turn, respond to their input
        if (turn == ENEMY_TURN) {
            if (e.keyCode == 37) moveLogicalEnemy(ACTION_LEFT);
            if (e.keyCode == 38) moveLogicalEnemy(ACTION_UP);
            if (e.keyCode == 39) moveLogicalEnemy(ACTION_RIGHT);
            if (e.keyCode == 40) moveLogicalEnemy(ACTION_DOWN);
            if (e.keyCode == 97) moveLogicalEnemy(ACTION_ESELECTDRAUGHT);
            if (e.keyCode == 98) moveLogicalEnemy(ACTION_EMAKEMOVE);
        }
    }

    // --- status: -----------------------------------

    function updateStatusBefore(a)
    // this is called before anyone has moved on this step, agent has just proposed an action
    // update status to show old state and proposed move 
    {
        //if even turn number, then it's agent turn
        if (turns % 2 == 0) {
            var status = " Turn: <b> " + turns + " </b>&nbsp;Colour: <b>White</b>&nbsp;&nbsp;&nbsp;Controls: <b>Arrow Keys</b> - Move Selector, <b>NUMPAD 1</b> - Select Draught, <b>NUMPAD 2</b> - Make Move";
        //otherwise it's enemy turn
        } else {
            var status = " Turn: <b> " + turns + " </b>&nbsp;Colour: <b>Black</b>&nbsp;&nbsp;&nbsp;Controls: <b>Arrow Keys</b> - Move Selector, <b>NUMPAD 1</b> - Select Draught, <b>NUMPAD 2</b> - Make Move";
        }

        $("#user_span3").html(status);
    }

    //--- draught functions --------------------------

    //initializes all white draughts in their starting positions on the board
    function initLogicalWhiteDraughts() {
        whitedraughts[0] = new Draught(null, 1, 8);
        whitedraughts[1] = new Draught(null, 3, 8);
        whitedraughts[2] = new Draught(null, 5, 8);
        whitedraughts[3] = new Draught(null, 7, 8);
        whitedraughts[4] = new Draught(null, 2, 7);
        whitedraughts[5] = new Draught(null, 4, 7);
        whitedraughts[6] = new Draught(null, 6, 7);
        whitedraughts[7] = new Draught(null, 8, 7);
        whitedraughts[8] = new Draught(null, 1, 6);
        whitedraughts[9] = new Draught(null, 3, 6);
        whitedraughts[10] = new Draught(null, 5, 6);
        whitedraughts[11] = new Draught(null, 7, 6);
    }

    //initializes the Three objects for all white draughts and draws them in the world
    function initThreeWhiteDraughts() {
        for (var i = 0; i < whitedraughts.length; i++) {
            var shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
            whitedraughts[i].obj = new THREE.Mesh(shape);
            whitedraughts[i].obj.material.color.setHex(BLANKCOLOR);
        }

        drawWhiteDraughts();
    }

    //paints all white draughts with "men" status
    function paintWhiteDraughts(material) {
        for (var i = 0; i < whitedraughts.length; i++) {
            if (whitedraughts[i].obj && !whitedraughts[i].isKing) whitedraughts[i].obj.material = material;
        }
    }

    //paints all white draughts with "King" status
    function paintWhiteKingDraughts(material) {
        for (var i = 0; i < whitedraughts.length; i++) {
            if (whitedraughts[i].obj && whitedraughts[i].isKing) whitedraughts[i].obj.material = material;
        }
    }

    //draws all white draughts in their current position
    function drawWhiteDraughts() {
        for (var i = 0; i < whitedraughts.length; i++) {

            var x = translate(whitedraughts[i].i * squaresize);
            var z = translate(whitedraughts[i].j * squaresize);
            var y = 0;

            whitedraughts[i].obj.position.x = x;
            whitedraughts[i].obj.position.y = y;
            whitedraughts[i].obj.position.z = z;

            threeworld.scene.add(whitedraughts[i].obj);
        }
    }

    //initializes all black draughts in their starting positions on the board
    function initLogicalBlackDraughts() {
        blackdraughts[0] = new Draught(null, 2, 3);
        blackdraughts[1] = new Draught(null, 4, 3);
        blackdraughts[2] = new Draught(null, 6, 3);
        blackdraughts[3] = new Draught(null, 8, 3);
        blackdraughts[4] = new Draught(null, 1, 2);
        blackdraughts[5] = new Draught(null, 3, 2);
        blackdraughts[6] = new Draught(null, 5, 2);
        blackdraughts[7] = new Draught(null, 7, 2);
        blackdraughts[8] = new Draught(null, 2, 1);
        blackdraughts[9] = new Draught(null, 4, 1);
        blackdraughts[10] = new Draught(null, 6, 1);
        blackdraughts[11] = new Draught(null, 8, 1);
    }

    //initializes the Three objects for all black draughts and draws them in the world
    function initThreeBlackDraughts() {
        for (var i = 0; i < blackdraughts.length; i++) {
            var shape = new THREE.BoxGeometry(squaresize, BOXHEIGHT, squaresize);
            blackdraughts[i].obj = new THREE.Mesh(shape);
            blackdraughts[i].obj.material.color.setHex(BLANKCOLOR);
        }

        drawBlackDraughts();
    }

    //paints all black draughts with "men" status
    function paintBlackDraughts(material) {
        for (var i = 0; i < blackdraughts.length; i++) {
            if (blackdraughts[i].obj && !blackdraughts[i].isKing) blackdraughts[i].obj.material = material;
        }
    }

    //paints all black draughts with "King" status
    function paintBlackKingDraughts(material) {
        for (var i = 0; i < blackdraughts.length; i++) {
            if (blackdraughts[i].obj && blackdraughts[i].isKing) blackdraughts[i].obj.material = material;
        }
    }

    //draws all black draughts in their current position
    function drawBlackDraughts() {
        for (var i = 0; i < blackdraughts.length; i++) {
            var x = translate(blackdraughts[i].i * squaresize);
            var z = translate(blackdraughts[i].j * squaresize);
            var y = 0;

            blackdraughts[i].obj.position.x = x;
            blackdraughts[i].obj.position.y = y;
            blackdraughts[i].obj.position.z = z;

            threeworld.scene.add(blackdraughts[i].obj);
        }
    }

    //returns true if a white draught exists at i,j
    function isWhiteDraught(i, j) {
        for (var k = 0; k < whitedraughts.length; k++) {
            if (whitedraughts[k].i == i) {
                if (whitedraughts[k].j == j) {
                    return true;
                }
            }
        }

        return false;
    }

    //returns true if a black draught exists at i,j
    function isBlackDraught(i, j) {
        for (var k = 0; k < blackdraughts.length; k++) {
            if (blackdraughts[k].i == i) {
                if (blackdraughts[k].j == j) {
                    return true;
                }
            }
        }

        return false;
    }

    //returns the index of a white draught existing at i,j
    function getWhiteDraught(i, j) {
        for (var k = 0; k < whitedraughts.length; k++) {
            if (whitedraughts[k].i == i) {
                if (whitedraughts[k].j == j) {
                    return k;
                }
            }
        }
    }

    //returns the index of a black draught existing at i,j
    function getBlackDraught(i, j) {
        for (var k = 0; k < blackdraughts.length; k++) {
            if (blackdraughts[k].i == i) {
                if (blackdraughts[k].j == j) {
                    return k;
                }
            }
        }
    }

    //removes a black draught, located at index, from the board
    function removeBlackDraught(index) {
        //if out of bounds, move to next column
        if (capturedBlackDraughtj > 9) {
            capturedBlackDraughtj = 0;
            capturedBlackDraughti = 12;
        }

        blackdraughts[index].i = capturedBlackDraughti;
        blackdraughts[index].j = capturedBlackDraughtj;

        capturedBlackDraughtj++;
        capturedBlackDraughts++;
    }

    //removes a white draught, located at index, from the board
    function removeWhiteDraught(index) {
        //if out of bounds, move to next column
        if (capturedWhiteDraughtj > 9) {
            capturedWhiteDraughtj = 0;
            capturedWhiteDraughti = -3;
        }

        whitedraughts[index].i = capturedWhiteDraughti;
        whitedraughts[index].j = capturedWhiteDraughtj;

        capturedWhiteDraughtj++;
        capturedWhiteDraughts++;
    }

    //--- public functions / interface / API ----------------------------------------------------------

    this.endCondition; // If set to true, run will end. 

    this.newRun = function() {

        // (subtle bug) must reset variables like these inside newRun (in case do multiple runs)

        this.endCondition = false;
        var agentWon = false;
        turns = 1;

        // for all runs:

        initGrid();
        initLogicalWalls();
        initLogicalWhiteDraughts();
        initLogicalBlackDraughts();
        initLogicalAgent();
        initLogicalEnemy();

        // for graphical runs only:

        if (true) {
            if (show3d) {
                BOXHEIGHT = squaresize;
                threeworld.init3d(startRadiusConst, maxRadiusConst, SKYCOLOR);
            } else {
                BOXHEIGHT = 1;
                threeworld.init2d(startRadiusConst, maxRadiusConst, SKYCOLOR);
            }

            initSkybox();

            // Set up objects first:
            initThreeWalls();
            initThreeWhiteDraughts();
            initThreeBlackDraughts();
            initThreeAgent();
            initThreeEnemy();

            // Then paint them with textures - asynchronous load of textures from files. 
            // The texture file loads return at some unknown future time in some unknown order.
            // Because of the unknown order, it is probably best to make objects first and later paint them, rather than have the objects made when the file reads return.
            // It is safe to paint objects in random order, but might not be safe to create objects in random order. 

            loadTextures();

            //handle keypresses
            document.onkeydown = keyHandler;
        }

    };

    this.getState = function() {
        var x = [ai, aj];
        return (x);
    };

    this.takeAction = function(a) {

        if (true)
            updateStatusBefore(a); // show status line before moves 

        //input from mind
        moveLogicalAgent(a);

        if (true) {
            //if it's the agent's turn, don't show the enemy
            if (turn == AGENT_TURN) {
                threeworld.scene.remove(theenemy);
                drawAgent();
            //otherwise, it's the enemy's turn so don't show the agent
            } else {
                threeworld.scene.remove(theagent);
                drawEnemy();
            }
            drawWhiteDraughts();
            drawBlackDraughts();
        }

        //if all enemy draughts have been taken
        if (capturedBlackDraughts == 12)
        {
            agentWon = true;
            this.endCondition = true;
        }

        //if all agent draughts have been taken
        if (capturedWhiteDraughts == 12) {
            agentWon = false;
            this.endCondition = true;
        }

    };

    this.endRun = function() {
        if (true) {
            if (this.endCondition) {
                if (agentWon) {
                    $("#user_span6").html("<br></br>&nbsp; <font color=green> <B> Player 2 has won the game! </B> </font>   ");
                } else {
                    $("#user_span6").html("<br></br>&nbsp; <font color=green> <B> Player 1 has won the game! </B> </font>   ");
                }
            }
            else
                $("#user_span6").html(" &nbsp; <font color=red> <B> Time has run out!. </B> </font>   ");
        }
    };

    this.getScore = function() {
        return turns;
    };

}

//---- end of World class -------------------------------------------------------

//end of Two Player Draughts World