// Cloned by Teacher World samples on 10 Dec 2017 from World "Checkers" by Adrien HARNAY
// Please leave this clone trail here.
// =============================================================================================
// Checkers World
// Adrien HARNAY
// Simulation of a Checkers Board where you can play with a "Mind" against the "World"
// =============================================================================================
// =============================================================================================
// Scoring:
// You either score 100 or 0 depending on whether you win the game or not
// =============================================================================================
// World must define these:
const CLOCKTICK = 1000; // speed of run - move things every n milliseconds
const MAXSTEPS = 1000000; // length of a run before final score
const SCREENSHOT_STEP = 1;
//---- global constants: -------------------------------------------------------
const gridsize = 8; // number of squares along side of world
const SKYCOLOR = 0xddffdd; // a number, not a string
const BLANKCOLOR = SKYCOLOR ; // make objects this color until texture arrives (from asynchronous file read)
const show3d = true; // Switch between 3d and 2d view (both using Three.js)
const squaresize = 30; // size of square in pixels
const MAXPOS = gridsize * squaresize; // length of one side in pixels
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
// 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 WHITE_CELL = 0;
const BLACK_CELL = 1;
const WHITE_PIECE = 2;
const BLACK_PIECE = 3;
const WORLD_COLOR = BLACK_PIECE;
const MIND_COLOR = WHITE_PIECE;
// textures just in case
var BLACK_CELL_OBJ = new Array(gridsize * gridsize / 2);
var blackCellObjCount = 0;
//---- start of World class -------------------------------------------------------
function World() {
// most of World can be private
// regular "var" syntax means private variables:
var BOXHEIGHT; // 3d or 2d box height
var BOARD = new Array(gridsize * gridsize); // can query BOARD about whether squares are occupied, will in fact be initialised as a 2D array
var GRAPH_BOARD = new Array (gridsize * gridsize);
var turn;
var winner;
var noMoreMoves;
var self = this; // needed for private fn to call public fn - see below
// regular "function" syntax means private functions:
function initBoard()
{
for (var i = 0; i < gridsize * 1; i += 2) {
BOARD[i] = WHITE_CELL;
BOARD[i + 1] = BLACK_PIECE;
BOARD[i + gridsize * 2] = WHITE_CELL;
BOARD[i + gridsize * 2 + 1] = BLACK_PIECE;
}
for (i = gridsize * 1; i < gridsize * 2; i += 2) {
BOARD[i] = BLACK_PIECE;
BOARD[i + 1] = WHITE_CELL;
}
for (i = gridsize * 3; i < gridsize * 4; i += 2) {
BOARD[i] = BLACK_CELL;
BOARD[i + 1] = WHITE_CELL;
}
for (i = gridsize * 4; i < gridsize * 5; i += 2) {
BOARD[i] = WHITE_CELL;
BOARD[i + 1] = BLACK_CELL;
}
for (i = gridsize * 5; i < gridsize * 6; i += 2) {
BOARD[i] = WHITE_PIECE;
BOARD[i + 1] = WHITE_CELL;
BOARD[i + gridsize * 2] = WHITE_PIECE;
BOARD[i + gridsize * 2 + 1] = WHITE_CELL;
}
for (i = gridsize * 6; i < gridsize * 7; i += 2) {
BOARD[i] = WHITE_CELL;
BOARD[i + 1] = WHITE_PIECE;
}
}
//--- skybox ----------------------------------------------------------------------------------------------
function initSkybox()
{
// x,y,z positive and negative faces have to be in certain order in the array
// mountain skybox, credit:
// http://stemkoski.github.io/Three.js/Skybox.html
var materialArray = [
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-xpos.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-xneg.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-ypos.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-yneg.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-zpos.png" ), side: THREE.BackSide } ) ),
( new THREE.MeshBasicMaterial ( { map: THREE.ImageUtils.loadTexture( "/uploads/starter/dawnmountain-zneg.png" ), 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
}
// Graphics functions
function paintBoard ( whiteCellTexture, blackCellTexture, whitePieceTexture, blackPieceTexture )
{
if (!whiteCellTexture || !blackCellTexture || !whitePieceTexture || !blackPieceTexture ) {
return;
}
for ( var i = 0; i < BLACK_CELL_OBJ.length; i++ )
{
BLACK_CELL_OBJ[i].material = blackCellTexture;
}
for ( i = 0; i < BOARD.length; i++ )
{
if (GRAPH_BOARD[i]) {
switch (BOARD[i]) {
case WHITE_CELL:
GRAPH_BOARD[i].material = whiteCellTexture;
break;
case BLACK_CELL:
GRAPH_BOARD[i].material = blackCellTexture;
break;
case WHITE_PIECE:
GRAPH_BOARD[i].material = whitePieceTexture;
break;
case BLACK_PIECE:
GRAPH_BOARD[i].material = blackPieceTexture;
break;
default:
}
}
}
}
function drawCellOrPiece(obj, x, z) {
obj.position.x = translate(x * squaresize);
obj.position.z = translate(z * squaresize);
obj.position.y = 0;
if ( true )
{
threeworld.scene.add(obj);
}
}
function loadTextures() {
var whiteCellTexture;
var blackCellTexture;
var whitePieceTexture;
var blackPieceTexture;
var loader1 = new THREE.TextureLoader();
loader1.load ( '/uploads/starter/latin.jpg', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
whiteCellTexture = new THREE.MeshBasicMaterial( { map: thetexture } );
paintBoard(whiteCellTexture, blackCellTexture, whitePieceTexture, blackPieceTexture);
} );
var loader2 = new THREE.TextureLoader();
loader2.load ( '/uploads/starter/door.jpg', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
blackCellTexture = new THREE.MeshBasicMaterial( { map: thetexture } );
paintBoard(whiteCellTexture, blackCellTexture, whitePieceTexture, blackPieceTexture);
} );
var loader3 = new THREE.TextureLoader();
loader3.load ( '/uploads/starter/pacman.jpg', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
whitePieceTexture = new THREE.MeshBasicMaterial( { map: thetexture } );
paintBoard(whiteCellTexture, blackCellTexture, whitePieceTexture, blackPieceTexture);
} );
var loader4 = new THREE.TextureLoader();
loader4.load ( '/uploads/starter/ghost.3.png', function ( thetexture ) {
thetexture.minFilter = THREE.LinearFilter;
blackPieceTexture = new THREE.MeshBasicMaterial( { map: thetexture } );
paintBoard(whiteCellTexture, blackCellTexture, whitePieceTexture, blackPieceTexture);
} );
}
function initGraphBoard()
{
var shape;
var thecube;
for (var i = 0; i < gridsize * 8 ; i++) {
shape = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
thecube = new THREE.Mesh( shape );
thecube.material.color.setHex( BLANKCOLOR );
GRAPH_BOARD[i] = thecube; // save it for later
drawCellOrPiece(thecube, getXFromI(i), getZFromI(i));
}
for (var i = 0; i < BLACK_CELL_OBJ.length ; i++) {
shape = new THREE.BoxGeometry( squaresize, BOXHEIGHT, squaresize );
thecube = new THREE.Mesh( shape );
thecube.material.color.setHex( BLANKCOLOR );
BLACK_CELL_OBJ[i] = thecube;
}
}
function updateStatusBefore()
{
var status = " Turn: <b> " + (turn === WHITE_PIECE ? 'PacMan (Mind)' : 'Pirates (World)') + " </b>";
$("#user_span3").html( status );
}
// win & turn functions
function changeTurn() {
turn = turn === WHITE_PIECE ? BLACK_PIECE : WHITE_PIECE;
}
function checkWin() {
var count = countPieces(BOARD);
if (count.black === 0) {
winner = 'PacMan (Mind)';
return true;
}
if (count.white === 0) {
winner = 'Pirates (World)';
return true;
}
if (noMoreMoves) {
if (count.black >= count.white) {
winner = 'Pirates (World)';
} else {
winner = 'PacMan (Mind)';
}
return true;
}
return false;
}
// utils random
function randomFloatAtoB ( A, B )
{
return ( A + ( Math.random() * (B-A) ) );
}
function randomIntAtoB ( A, B )
{
return ( Math.round ( randomFloatAtoB ( A, B ) ) );
}
// utils coordinate functions
function translate (pos)
{
return (pos - (MAXPOS / 2));
}
function isCorrectPos(pos) {
return pos >= 0 && pos <= 7;
}
function getXFromI(i) {
return i % gridsize;
}
function getZFromI(i) {
return Math.floor(i / gridsize);
}
function getIFromXZ(x, z) {
return z * gridsize + x;
}
// world move functions
function replaceEatenCell(playerColor, i, board, graphBoard) {
var pieceColor = (playerColor === WHITE_PIECE ? BLACK_PIECE : WHITE_PIECE);
board[i] = (board[i] === BLACK_CELL ? pieceColor : BLACK_CELL);
if (graphBoard) {
drawCellOrPiece(graphBoard[i], 1000000, 1000000);
graphBoard[i] = BLACK_CELL_OBJ[blackCellObjCount];
blackCellObjCount++;
drawCellOrPiece(graphBoard[i], getXFromI(i), getZFromI(i));
}
}
function getEatenCellI(startI, endI) {
var startX = getXFromI(startI);
var startZ = getZFromI(startI);
var endX = getXFromI(endI);
var endZ = getZFromI(endI);
var zDiff = endZ - startZ;
var xDiff = endX - startX;
if (!(zDiff === 2 || zDiff == -2)) {
return -1;
}
return getIFromXZ(endX - xDiff / 2, endZ - zDiff / 2);
}
function canMove(playerColor, startI, endI, board) {
var piece = board[startI];
var destination = board[endI];
var enemyColor = playerColor === WHITE_PIECE ? BLACK_PIECE : WHITE_PIECE;
var startX = getXFromI(startI);
var startZ = getZFromI(startI);
var endX = getXFromI(endI);
var endZ = getZFromI(endI);
if (destination !== BLACK_CELL
|| !isCorrectPos(endX) || !isCorrectPos(endZ)
|| (piece === BLACK_PIECE && (playerColor !== BLACK_PIECE || startI >= endI))
|| (piece === WHITE_PIECE && (playerColor !== WHITE_PIECE || startI <= endI))) {
return false;
}
var zDiff1 = piece === BLACK_PIECE ? 1 : -1;
var zDiff2 = piece === BLACK_PIECE ? 2 : -2;
if (!(endZ === startZ + zDiff1 && (endX === startX + 1 || endX === startX - 1))
&& !(endZ === startZ + zDiff2 && (endX === startX + 2 || endX === startX - 2))) {
return false;
}
if (endZ === startZ + zDiff2
&& ((endX === startX + 2 && board[getIFromXZ(startX + 1, startZ + zDiff1)] !== enemyColor)
|| (endX === startX - 2 && board[getIFromXZ(startX - 1, startZ + zDiff1)] !== enemyColor))) {
return false;
}
return true;
}
function move(playerColor, startI, endI, board, graphBoard, force) {
if (!force && !canMove(playerColor, startI, endI, board)) {
return false;
}
var eatenCellI = getEatenCellI(startI, endI);
if (eatenCellI !== -1) {
replaceEatenCell(playerColor, eatenCellI, board, graphBoard);
}
var tmpCell = board[startI];
board[startI] = board[endI];
board[endI] = tmpCell;
if (graphBoard) {
var tmpGraphCell = graphBoard[startI];
graphBoard[startI] = GRAPH_BOARD[endI];
graphBoard[endI] = tmpGraphCell;
drawCellOrPiece(graphBoard[startI], getXFromI(startI), getZFromI(startI));
drawCellOrPiece(graphBoard[endI], getXFromI(endI), getZFromI(endI));
changeTurn();
}
return true;
}
// heuristics
function countPieces(board) {
var count = {
black: 0,
white: 0,
};
for (var i = 0; i < board.length; i++) {
if (board[i] === BLACK_PIECE) {
count.black++;
} else if (board[i] === WHITE_PIECE) {
count.white++;
}
}
return count;
}
function getHeuristic(board) {
var count = countPieces(board);
return count.black - count.white;
}
// IA functions
function findNextPieceI(currI, board) {
for (var i = currI + 1; i < board.length; i++) {
if (board[i] === WORLD_COLOR) {
return i;
}
}
return -2;
}
function findRandomInBestSolutions(moves) {
if (moves.length === 1) {
return moves[0];
}
return moves[randomIntAtoB(0, moves.length - 1)];
}
function findBestMoveFromTab(moves) {
if (!moves || !moves.length) {
return null;
}
var bestScore = moves[0].score;
for (var i = 1; i < moves.length; i++) {
if (moves[i].score > bestScore) {
bestScore = moves[i].score;
}
}
var bestMoves = moves.filter(function (move) {
return move.score === bestScore;
});
return findRandomInBestSolutions(bestMoves);
}
function tryMove(board, moves, startI, endI) {
if (isCorrectPos(getXFromI(startI)) && isCorrectPos(getZFromI(startI))
&& isCorrectPos(getXFromI(endI)) && isCorrectPos(getZFromI(endI))
&& move(WORLD_COLOR, startI, endI, board)) {
moves.push({
startI: startI,
endI: endI,
score: getHeuristic(board),
});
move(WORLD_COLOR, endI, startI, board, null, true);
}
}
function findBestMoveForPiece(startI, board) {
var moves = [];
var startX = getXFromI(startI);
var startZ = getZFromI(startI);
var endZ = startZ + 1;
var endX = startX + 1;
var endI = getIFromXZ(endX, endZ);
tryMove(board, moves, startI, endI);
endX = startX - 1;
endI = getIFromXZ(endX, endZ);
tryMove(board, moves, startI, endI);
endZ = startZ + 2;
endX = startX + 2;
endI = getIFromXZ(endX, endZ);
tryMove(board, moves, startI, endI);
endX = startX - 2;
endI = getIFromXZ(endX, endZ);
tryMove(board, moves, startI, endI);
return findBestMoveFromTab(moves);
}
// --- play functions -----------------------------------
function playMind(a) {
if (a === -1) {
return -1;
}
var startX = a.startX;
var startZ = a.startZ;
var endX = a.endX;
var endZ = a.endZ;
if (startX === undefined || startZ === undefined || endX == undefined || endZ === undefined) {
alert("The mind is supposed to provide an object containing: startX, startZ, endX, endZ");
}
var startI = getIFromXZ(startX, startZ);
var endI = getIFromXZ(endX, endZ);
return move(MIND_COLOR, startI, endI, BOARD, GRAPH_BOARD);
}
function playWorld() {
var COPY_BOARD = BOARD.slice();
var currI = -1;
var possibleMoves = [];
var currBestMove;
var bestMoveOfAll;
while (currI !== -2) {
currI = findNextPieceI(currI, COPY_BOARD);
if (currI !== -2) {
currBestMove = findBestMoveForPiece(currI, COPY_BOARD);
if (currBestMove) {
possibleMoves.push(currBestMove);
}
}
}
bestMoveOfAll = findBestMoveFromTab(possibleMoves);
if (bestMoveOfAll) {
return move(WORLD_COLOR, bestMoveOfAll.startI, bestMoveOfAll.endI, BOARD, GRAPH_BOARD);
}
return -1;
}
//--- public functions / interface / API ----------------------------------------------------------
this.endCondition; // If set to true, run will end.
this.newRun = function()
{
this.endCondition = false;
turn = MIND_COLOR;
noMoreMoves = false;
initBoard();
if ( true )
{
if (show3d) {
BOXHEIGHT = squaresize;
threeworld.init3d(startRadiusConst, maxRadiusConst, SKYCOLOR);
}
else {
BOXHEIGHT = 1;
threeworld.init2d(startRadiusConst, maxRadiusConst, SKYCOLOR);
}
initSkybox();
initGraphBoard();
loadTextures();
}
};
this.getState = function()
{
return BOARD.slice();
};
this.takeAction = function ( a )
{
var firstTurn = true;
console.log(a);
if (true)
updateStatusBefore(a); // show status line before moves
setTimeout(function() { // wait for the textures to initialize
if (turn === MIND_COLOR) {
if (playMind(a) === -1) {
noMoreMoves = true;
}
} else if (turn === WORLD_COLOR) {
if (playWorld() === -1) {
noMoreMoves = true;
}
}
if (checkWin()) {
this.endCondition = true;
if ( true )
{
if ( this.endCondition )
$("#user_span6").html( " <font color=green> The winner is: <B>" + winner + "</B>. </font> " );
else
$("#user_span6").html( " <font color=red> <B> No more moves available. </B> </font> " );
}
}
firstTurn = false;
}, firstTurn ? 1000 : 0);
};
this.getScore = function()
{
return ((winner === 'PacMan (Mind)') * 100);
};
this.endRun = function()
{
if ( true )
{
if ( this.endCondition )
$("#user_span6").html( " <font color=green> The winner is: <B>" + winner + "</B>. </font> " );
else
$("#user_span6").html( " <font color=red> <B> No more moves available. </B> </font> " );
}
};
}
//---- end of World class -------------------------------------------------------