596 lines
15 KiB
JavaScript
596 lines
15 KiB
JavaScript
// ECMAScript 5 strict mode
|
|
/* jshint globalstrict: true*/
|
|
/* jslint newcap: true */
|
|
/* global THREE, $, document, window, console */
|
|
/* global LOADING_BAR_SCALE,ROWS,COLS,PIECE_SIZE, BOARD_SIZE, FLOOR_SIZE, WIREFRAME, DEBUG, Cell, WHITE, BLACK, FEEDBACK, SHADOW */
|
|
/* global textures, geometries, removeLoader */
|
|
/* global initGUI, initInfo, addToPGN, displayCheck, newGame */
|
|
/* global initPieceFactory,initCellFactory,createCell,createPiece,createChessBoard, createFloor, createValidCellMaterial,createSelectedMaterial, validCellMaterial, selectedMaterial */
|
|
/*global Search,FormatSquare,GenerateMove,MakeMove,GetMoveSAN,MakeSquare,UnmakeMove, FormatMove, ResetGame, GetFen, GetMoveFromString, alert, InitializeFromFen, GenerateValidMoves */
|
|
/*global g_inCheck,g_board,g_pieceList, g_toMove, g_timeout:true,g_maxply:true */
|
|
/*global moveflagCastleKing, moveflagCastleQueen, moveflagEPC, moveflagPromotion, colorWhite*/
|
|
/*global moveflagPromoteQueen,moveflagPromoteRook,moveflagPromoteBishop,moveflagPromoteKnight*/
|
|
/*global piecePawn, pieceKnight, pieceBishop, pieceRook, pieceQueen, pieceKing */
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var camera;
|
|
// list of valid move after each move
|
|
// used mostly for cell highlighting
|
|
var validMoves = null;
|
|
// chess game variables
|
|
var g_allMoves = [];
|
|
// default promotion
|
|
var promotion = moveflagPromoteQueen;
|
|
|
|
var g_playerWhite = false;
|
|
var g_backgroundEngine;
|
|
|
|
// settings for AI level
|
|
var levels = [
|
|
{timeout:0,maxply:1},
|
|
{timeout:12,maxply:20},
|
|
{timeout:25,maxply:40},
|
|
{timeout:50,maxply:60},
|
|
{timeout:100,maxply:80},
|
|
{timeout:200,maxply:100},
|
|
{timeout:400,maxply:120},
|
|
{timeout:800,maxply:140},
|
|
{timeout:1600,maxply:160},
|
|
{timeout:3200,maxply:180}
|
|
];
|
|
|
|
|
|
(function() {
|
|
// general setup
|
|
var scene, renderer;
|
|
var cameraControls, effectController;
|
|
// for picking
|
|
var projector;
|
|
// 3D board representation
|
|
var chessBoard;
|
|
// for proper timing
|
|
var clock = new THREE.Clock();
|
|
|
|
var g_backgroundEngineValid = true;
|
|
|
|
// array for picking
|
|
var board3D = [];
|
|
|
|
|
|
// hold current selection
|
|
var selectedPiece = null;
|
|
var selectedCell = null;
|
|
|
|
// default values for AI level
|
|
g_timeout = 1600;
|
|
g_maxply = 49;
|
|
|
|
|
|
/*
|
|
* BASIC SETUP
|
|
*/
|
|
function init() {
|
|
// initialize everything for 3D
|
|
|
|
// CANVAS PARAMETERS
|
|
var canvasWidth = window.innerWidth;
|
|
var canvasHeight = window.innerHeight;
|
|
var canvasRatio = canvasWidth / canvasHeight;
|
|
|
|
// RENDERER
|
|
renderer = new THREE.WebGLRenderer( { antialias: true } );
|
|
renderer.gammaInput = true;
|
|
renderer.gammaOutput = true;
|
|
renderer.setSize(canvasWidth, canvasHeight);
|
|
|
|
if ( SHADOW ) {
|
|
renderer.shadowMapEnabled = true;
|
|
renderer.shadowMapType = THREE.PCFSoftShadowMap;
|
|
renderer.shadowMapCascade = true;
|
|
}
|
|
|
|
// black background
|
|
renderer.setClearColor( 0x000000, 1.0 );
|
|
document.body.appendChild( renderer.domElement );
|
|
|
|
// CAMERA
|
|
camera = new THREE.PerspectiveCamera( 45, canvasRatio, 1, 40000 );
|
|
// CONTROLS
|
|
cameraControls = new THREE.OrbitAndPanControls(camera, renderer.domElement);
|
|
// limitations
|
|
cameraControls.minPolarAngle = 0;
|
|
cameraControls.maxPolarAngle = 80 * Math.PI/180;
|
|
cameraControls.minDistance = 10;
|
|
cameraControls.maxDistance = 200;
|
|
cameraControls.userZoomSpeed = 1.0;
|
|
// default position behind white
|
|
// (might want to change that according to color selection)
|
|
camera.position.set( 0, 100, 100 );
|
|
|
|
|
|
// LIGHTING
|
|
var spotlight = new THREE.SpotLight( 0xFFFFFF, 1.0);
|
|
spotlight.position.set( 0, 300, 0 );
|
|
spotlight.angle = Math.PI / 2;
|
|
spotlight.exponent = 50.0;
|
|
spotlight.target.position.set( 0, 0, 0 );
|
|
|
|
if ( SHADOW ) {
|
|
spotlight.castShadow = true;
|
|
spotlight.shadowDarkness = 0.5;
|
|
//spotlight.shadowMapWidth = 4096; // yeah crazy testing
|
|
//spotlight.shadowMapHeight = 4096;
|
|
spotlight.shadowBias = -0.001;
|
|
}
|
|
|
|
|
|
var whiteLight = new THREE.PointLight( 0xFFEEDD, 0.2);
|
|
whiteLight.position.set(0,0,100);
|
|
var blackLight = new THREE.PointLight( 0xFFEEDD, 0.2);
|
|
blackLight.position.set(0,0,-100);
|
|
|
|
// generate createPiece and createCell functions
|
|
initPieceFactory();
|
|
initCellFactory();
|
|
|
|
// we let chessBoard in global scope to use it for picking
|
|
chessBoard = createChessBoard(BOARD_SIZE);
|
|
var floor = createFloor(FLOOR_SIZE,BOARD_SIZE);
|
|
|
|
//floor.position.y = -5*BOARD_SIZE/100;
|
|
floor.position.y = chessBoard.height;
|
|
|
|
// create and fill the scene with default stuff
|
|
scene = new THREE.Scene();
|
|
scene.add(floor);
|
|
scene.add(spotlight);
|
|
scene.add(whiteLight);
|
|
scene.add(blackLight);
|
|
scene.add(chessBoard);
|
|
|
|
// to make everything black in the background
|
|
scene.fog = new THREE.FogExp2( 0x000000, 0.001 );
|
|
// little reddish to fake a bit of bounce lighting
|
|
scene.add(new THREE.AmbientLight(0x330000));
|
|
|
|
// for picking
|
|
projector = new THREE.Projector();
|
|
|
|
// Menu
|
|
initGUI();
|
|
// Check feedback
|
|
initInfo();
|
|
|
|
createValidCellMaterial();
|
|
createSelectedMaterial();
|
|
|
|
// picking event
|
|
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
|
|
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
|
|
|
|
// avoid stretching
|
|
window.addEventListener('resize',onResize,false);
|
|
}
|
|
|
|
function onResize() {
|
|
var canvas = renderer.domElement;
|
|
var w = window.innerWidth;
|
|
var h = window.innerHeight;
|
|
renderer.setSize(w,h);
|
|
// have to change the projection
|
|
// else the image will be stretched
|
|
camera.aspect = w/h;
|
|
camera.updateProjectionMatrix();
|
|
}
|
|
|
|
function animate() {
|
|
window.requestAnimationFrame(animate);
|
|
render();
|
|
}
|
|
|
|
function render() {
|
|
var delta = clock.getDelta();
|
|
cameraControls.update(delta);
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
|
|
function UIPlayMove(move,silent) {
|
|
// we play the move here by
|
|
// adding it to the png list (for display)
|
|
addToPGN(move);
|
|
// and to the move list (for undos)
|
|
g_allMoves[g_allMoves.length] = move;
|
|
// committing the move
|
|
MakeMove(move);
|
|
// redrawing
|
|
// silent flag is used when simulating moves for loading PGN
|
|
if(!silent) {
|
|
redrawBoard();
|
|
}
|
|
}
|
|
|
|
function playMove(piece,cell) {
|
|
|
|
if (piece.cell === undefined || cell.name === undefined) {
|
|
return false;
|
|
}
|
|
// get the positions
|
|
var start = new Cell(piece.cell);
|
|
var end = new Cell(cell.name);
|
|
|
|
var startSquare = MakeSquare(start.y, start.x);
|
|
var endSquare = MakeSquare(end.y, end.x);
|
|
|
|
var move = null;
|
|
var testPromotion = false;
|
|
var p = g_board[startSquare];
|
|
|
|
if ( ((p & 0x7) === piecePawn) &&
|
|
(((start.y === 1) && g_playerWhite) ||
|
|
( (start.y === 6) && !g_playerWhite)) &&
|
|
(((p & 0x8) && g_playerWhite) ||
|
|
(!(p & 0x8) && !g_playerWhite))
|
|
) {
|
|
testPromotion = true;
|
|
}
|
|
|
|
// check if the move is valid
|
|
// validMoves is global and reevaluated after each move
|
|
for (var i = 0; i < validMoves.length; i++) {
|
|
if (testPromotion) {
|
|
// for promotion we one valid move per promotion type
|
|
// so we have to be more specific and create the entire move
|
|
// with its flag go get it back from validMoves.
|
|
// else it's alway a Rook promotion (flag 0x00).
|
|
if(validMoves[i] === GenerateMove(startSquare, endSquare, moveflagPromotion | promotion)) {
|
|
move = validMoves[i];
|
|
break;
|
|
}
|
|
} else {
|
|
// just checking start and end square allows to cover
|
|
// all other special moves like "en passant" capture and
|
|
// castling
|
|
if ( (validMoves[i] & 0xFF) == startSquare &&
|
|
((validMoves[i] >> 8) & 0xFF) == endSquare ) {
|
|
move = validMoves[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (!(start.x === end.x && start.y === end.y) && move !== null) {
|
|
|
|
// we send the move to our worker
|
|
if (InitializeBackgroundEngine()) {
|
|
g_backgroundEngine.postMessage(FormatMove(move));
|
|
}
|
|
|
|
// we play the actual move
|
|
UIPlayMove(move,false);
|
|
|
|
|
|
// make the engine play (setTimeOut is used probably to wait for the last postMessage to kick in)
|
|
// maybe creating a callback from the worker would be better (more reliable)
|
|
setTimeout(SearchAndRedraw, 0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* AI CONTROL
|
|
*/
|
|
function EnsureAnalysisStopped() {
|
|
if (g_backgroundEngine) {
|
|
g_backgroundEngine.terminate();
|
|
g_backgroundEngine = null;
|
|
}
|
|
}
|
|
|
|
function SearchAndRedraw() {
|
|
// the AI is triggered here
|
|
if (InitializeBackgroundEngine()) {
|
|
g_backgroundEngine.postMessage("search " + g_timeout + "," + g_maxply);
|
|
} else {
|
|
Search(FinishMove, g_maxply, null); // unasynchronous version fall back
|
|
}
|
|
}
|
|
|
|
function FinishMove(bestMove, value, timeTaken, ply) {
|
|
// used by the fallback Search
|
|
if (bestMove !== null) {
|
|
UIPlayMove(bestMove,false);
|
|
}
|
|
}
|
|
|
|
function InitializeBackgroundEngine() {
|
|
// we initialize the web worker here
|
|
if (!g_backgroundEngineValid) {
|
|
return false;
|
|
}
|
|
if (!g_backgroundEngine) {
|
|
g_backgroundEngineValid = true;
|
|
try {
|
|
g_backgroundEngine = new Worker("js/AI/garbochess.js");
|
|
g_backgroundEngine.onmessage = function (e) {
|
|
if (e.data.match("^pv") == "pv") {
|
|
// legacy
|
|
} else if (e.data.match("^message") == "message") {
|
|
// legacy
|
|
EnsureAnalysisStopped();
|
|
} else if (e.data.match("^console: ") == "console: ") {
|
|
// debugging
|
|
console.log(e.data.substr(9));
|
|
} else {
|
|
// we receive the move from the AI, we play it
|
|
UIPlayMove(GetMoveFromString(e.data), false);
|
|
}
|
|
};
|
|
g_backgroundEngine.error = function (e) {
|
|
alert("Error from background worker:" + e.message);
|
|
};
|
|
// set up the current board position
|
|
g_backgroundEngine.postMessage("position " + GetFen());
|
|
} catch (error) {
|
|
g_backgroundEngineValid = false;
|
|
}
|
|
}
|
|
// return false for fallback
|
|
return g_backgroundEngineValid;
|
|
}
|
|
|
|
|
|
/*
|
|
* BOARD
|
|
*/
|
|
|
|
|
|
function updateBoard3D() {
|
|
// list all the pieces
|
|
board3D = [];
|
|
for (var y = 0; y < ROWS; y++) {
|
|
for (var x = 0; x < COLS; x++) {
|
|
var piece = g_board[MakeSquare(y,x)];
|
|
var pieceColor = (piece & colorWhite) ? WHITE : BLACK;
|
|
var pieceName = null;
|
|
switch (piece & 0x7) {
|
|
case piecePawn:
|
|
pieceName = "pawn";
|
|
break;
|
|
case pieceKnight:
|
|
pieceName = "knight";
|
|
break;
|
|
case pieceBishop:
|
|
pieceName = "bishop";
|
|
break;
|
|
case pieceRook:
|
|
pieceName = "rook";
|
|
break;
|
|
case pieceQueen:
|
|
pieceName = "queen";
|
|
break;
|
|
case pieceKing:
|
|
pieceName = "king";
|
|
break;
|
|
}
|
|
|
|
if (pieceName !== null) {
|
|
board3D[x+y*COLS] = createPiece(pieceName,pieceColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function clearBoard() {
|
|
// remove all pieces from the board
|
|
var cell;
|
|
board3D.forEach(function(piece) {
|
|
scene.remove(piece);
|
|
cell = new Cell(piece.cell);
|
|
});
|
|
}
|
|
|
|
function fillBoard() {
|
|
// place all the pieces on the board
|
|
var cell;
|
|
board3D.forEach(function(piece,index) {
|
|
cell = new Cell(index);
|
|
piece.position = cell.getWorldPosition();
|
|
piece.cell = index;
|
|
scene.add(piece);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
function redrawBoard() {
|
|
validMoves = GenerateValidMoves();
|
|
clearBoard();
|
|
updateBoard3D();
|
|
fillBoard();
|
|
displayCheck();
|
|
}
|
|
|
|
|
|
/*
|
|
* PICKING
|
|
*/
|
|
function pickPiece(raycaster) {
|
|
var intersect = null;
|
|
var picked = null;
|
|
// intersect piece
|
|
var hitList = [];
|
|
var hit,piece;
|
|
for (var i in board3D) {
|
|
if ({}.hasOwnProperty.call(board3D, i)) {
|
|
piece = board3D[i];
|
|
intersect = raycaster.intersectObject( piece.children[0], true );
|
|
|
|
if (intersect.length > 0) {
|
|
hit = intersect[0];
|
|
if (( g_playerWhite && hit.object.parent.color === WHITE ) ||
|
|
(!g_playerWhite && hit.object.parent.color === BLACK ) ){
|
|
|
|
// only pick the right color
|
|
hitList.push(hit);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// find the closest
|
|
hitList.forEach(function(hit) {
|
|
if (picked === null || picked.distance > hit.distance) {
|
|
picked = hit;
|
|
}
|
|
});
|
|
|
|
|
|
if (picked) {
|
|
return picked.object.parent;
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
}
|
|
|
|
function pickCell(raycaster) {
|
|
// here we don't need to test the distance since you can't really
|
|
// intersect more than one cell at a time.
|
|
var intersect = raycaster.intersectObject( chessBoard, true );
|
|
if (intersect.length > 0) {
|
|
var pickedCell = intersect[0].object;
|
|
return pickedCell;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getRay(event) {
|
|
// get the raycaster object from the mouse position
|
|
var zoomLevel = window.devicePixelRatio | 1.0 ;
|
|
var canvas = renderer.domElement;
|
|
var canvasPosition = canvas.getBoundingClientRect();
|
|
var mouseX = event.clientX*zoomLevel - canvasPosition.left;
|
|
var mouseY = event.clientY*zoomLevel - canvasPosition.top;
|
|
|
|
var mouseVector = new THREE.Vector3(
|
|
2 * ( mouseX / canvas.width ) - 1,
|
|
1 - 2 * ( mouseY / canvas.height ));
|
|
|
|
return projector.pickingRay( mouseVector.clone(), camera );
|
|
}
|
|
|
|
function onDocumentMouseMove( event ) {
|
|
|
|
var canvas = renderer.domElement;
|
|
var raycaster = getRay(event);
|
|
var pickedPiece = pickPiece(raycaster);
|
|
var pickedCell = pickCell(raycaster);
|
|
|
|
|
|
canvas.style.cursor = "default";
|
|
// we are over one of our piece -> hand
|
|
if (pickedPiece !== null) {
|
|
canvas.style.cursor = "pointer";
|
|
}
|
|
|
|
// if a cell is selected, we unselect it by default
|
|
if (selectedCell !== null) {
|
|
selectedCell.material = selectedCell.baseMaterial;
|
|
}
|
|
|
|
// if a piece is selected and a cell is picked
|
|
if(selectedPiece !== null && pickedCell !== null) {
|
|
var start = new Cell(selectedPiece.cell);
|
|
var end = new Cell(pickedCell.name);
|
|
|
|
var move = null;
|
|
// we check if it would be a valid move
|
|
for (var i = 0; i < validMoves.length; i++) {
|
|
if ( (validMoves[i] & 0xFF) == MakeSquare(start.y, start.x) &&
|
|
((validMoves[i] >> 8) & 0xFF) == MakeSquare(end.y, end.x)
|
|
) {
|
|
move = validMoves[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// then if a piece was clicked and we are on a valide cell
|
|
// we highlight it and display a hand cursor
|
|
if (pickedCell !== null && move !==null) {
|
|
selectedCell = pickedCell;
|
|
selectedCell.baseMaterial = selectedCell.material;
|
|
selectedCell.material = validCellMaterial[selectedCell.color];
|
|
canvas.style.cursor = "pointer";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
function onDocumentMouseDown( event ) {
|
|
|
|
var canvas = renderer.domElement;
|
|
var raycaster = getRay(event);
|
|
|
|
var pickedPiece = pickPiece(raycaster);
|
|
var pickedCell = pickCell(raycaster);
|
|
|
|
if (selectedPiece !== null && pickedCell !== null) {
|
|
if(playMove(selectedPiece,pickedCell)) {
|
|
// a move is played, we reset everything
|
|
// any selectedPiece will disappear
|
|
// since we redraw everything
|
|
selectedPiece = null;
|
|
pickedPiece = null;
|
|
pickedCell = null;
|
|
}
|
|
}
|
|
|
|
// when a click happen, any selected piece gets unselected
|
|
if (selectedPiece !== null) {
|
|
selectedPiece.children[0].material = selectedPiece.baseMaterial;
|
|
//selectedPiece.children[1].material = selectedPiece.baseMaterial;
|
|
}
|
|
|
|
// then if a piece was clicked, we select it
|
|
selectedPiece = pickedPiece;
|
|
if (selectedPiece !== null) {
|
|
selectedPiece.baseMaterial = selectedPiece.children[0].material;
|
|
selectedPiece.children[0].material = selectedMaterial[selectedPiece.color];
|
|
//selectedPiece.children[1].material = selectedMaterial[selectedPiece.color];
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// all resources (meshs and textures) are loaded
|
|
function onLoaded () {
|
|
//bar.container.style.display = "none";
|
|
removeLoader();
|
|
|
|
init();
|
|
if (DEBUG) {
|
|
window.scene = scene;
|
|
window.renderer = renderer;
|
|
}
|
|
newGame(WHITE);
|
|
animate();
|
|
|
|
//setTimeout(loadFEN('8/Q5P1/8/8/8/8/8/2K1k3 w - -'),2000);
|
|
|
|
}
|
|
|
|
window.SearchAndRedraw = SearchAndRedraw;
|
|
window.onLoaded = onLoaded;
|
|
window.redrawBoard = redrawBoard;
|
|
window.EnsureAnalysisStopped = EnsureAnalysisStopped;
|
|
window.InitializeBackgroundEngine = InitializeBackgroundEngine;
|
|
window.UIPlayMove = UIPlayMove;
|
|
|
|
})(); |