whisper.cat/weboasis/arcade/chess/js/gui.js
2023-10-05 23:28:32 +11:00

545 lines
12 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 SearchAndRedraw, UIPlayMove, camera, levels, g_allMoves:true, promotion:true, g_backgroundEngine:true, validMoves, InitializeBackgroundEngine, EnsureAnalysisStopped, newGame, redrawBoard, parsePGN, g_playerWhite:true */
/*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";
(function () {
// jQuery pgn textarea
var $pgn;
// list of moves in pgn format
var g_pgn = [];
// jQuery check feedback
var $info;
function initInfo() {
// create the DOM element
// to display Chc
$info = $("<div>")
.css("position","absolute")
.position({
of:$("body"),
my:"right top",
at:"right top"
})
.attr("id","info")
.appendTo($("body"))
.css("left","auto")
.css("right","0");
}
function initGUI() {
var $gui = $("<div>")
.css("position","absolute")
.position({
of:$("body"),
my:"left top",
at:"left top"
})
.width(150)
.attr("id","gui");
$("<p>")
.text("menu")
.appendTo($gui);
var $menudiv = $("<div>").appendTo($gui);
var $menu = $("<ul>").appendTo($menudiv);
makeButton("NewGame",newGameDialog,$menu);
makeButton("Undo",undo,$menu);
makeButton("Load",loadDialog,$menu);
makeButton("Save",save,$menu);
$("<label>")
.text("Promotion:")
.append(
$("<select>")
.width(140)
.append(
$("<option>")
.text("Queen"))
.append(
$("<option>")
.text("Rook"))
.append(
$("<option>")
.text("Bishop"))
.append(
$("<option>")
.text("Knight"))
.change( changePromo )
.appendTo($menudiv)
)
.appendTo($menudiv);
$pgn = $("<textarea>")
.attr("cols","16")
.attr("rows","10")
.attr("readonly","readonly")
.appendTo($menudiv);
$("body").append($gui);
$gui.accordion({
header: "p",
collapsible: true
});
}
function makeButton(name,callback,parent) {
var $item = $("<li>").appendTo(parent);
return $("<button>")
.button({
label:name
})
.width(140)
.click(callback)
.appendTo($item);
}
function newGameDialog() {
var id = "newgame";
var dialogColor = WHITE;
var dialogLevel = 0;
if ($("#"+id).length !== 0) {
return false;
}
var $newGame = $("<div>")
.attr("id",id)
.attr("title","New Game")
.appendTo($("body"));
// buttonset div
var $radio = $("<p>").appendTo($newGame);
// first button for white
$('<input type="radio" id="white" name="color" checked="checked">')
.click(function() {
dialogColor = WHITE;
})
.appendTo($radio);
$('<label for="white">White</label>').appendTo($radio);
// second button for black
$('<input type="radio" id="black" name="color"/>')
.click(function() {
dialogColor = BLACK;
})
.appendTo($radio);
$('<label for="black">Black</label>').appendTo($radio);
// initialize the buttonset
$radio.buttonset();
// level selector
var $label = $("<label>")
.text("AI Strength:");
var $levelSelect = $('<select>')
.css("display","block")
.appendTo($label)
.change(function(event) {
dialogLevel = $(event.currentTarget).val();
});
$("<p>").append($label).appendTo($newGame);
// add as much level configuration we have
for (var i = 0; i < levels.length; i++) {
$('<option>')
.val(i)
.text("level "+(i+1))
.appendTo($levelSelect);
}
$newGame.dialog({
close:function(event,ui) {
$newGame.remove();
},
buttons: {
"Start": function() {
newGame(dialogColor,dialogLevel);
$(this).remove();
}
}
});
}
/*
* GAME CONTROL
*/
function newGame(color,level) {
// change AI parameters according to level
if (levels[level] !== undefined) {
g_timeout = levels[level].timeout;
g_maxply = levels[level].maxply;
}
EnsureAnalysisStopped();
ResetGame();
if (InitializeBackgroundEngine()) {
g_backgroundEngine.postMessage("go");
}
g_allMoves = [];
clearPGN();
redrawBoard();
if (color === WHITE) {
g_playerWhite = true;
camera.position.x = 0;
camera.position.z = 100; // camera on white side
} else {
g_playerWhite = false;
SearchAndRedraw();
camera.position.x = 0;
camera.position.z = -100; // camera on black side
}
}
function changeStartPlayer(event) {
g_playerWhite = $(event.currentTarget).val() === "white";
redrawBoard();
}
function undo() {
if (g_allMoves.length === 0) {
return;
}
if (g_backgroundEngine !== null) {
g_backgroundEngine.terminate();
g_backgroundEngine = null;
}
UnmakeMove(g_allMoves[g_allMoves.length - 1]);
g_allMoves.pop();
g_pgn.pop();
g_pgn.pop();
updatePGN();
if (g_playerWhite !== Boolean(g_toMove) && g_allMoves.length !== 0) {
UnmakeMove(g_allMoves[g_allMoves.length - 1]);
g_allMoves.pop();
}
redrawBoard();
}
function loadDialog() {
var id = "loadGame";
if ($("#"+id).length !== 0) {
return false;
}
var $loadGame = $("<div>")
.attr("id",id)
.attr("title","Load Game")
.appendTo($("body"));
$('<input>')
.attr("type","file")
.change(function(evt) {
load(evt);
$loadGame.remove();
})
.appendTo($loadGame);
$loadGame
.dialog({
minWidth:420,
close:function(event,ui) {
$loadGame.remove();
}
});
}
function load(evt) {
//Retrieve the first (and only!) File from the FileList object
var file = evt.target.files[0];
if (file) {
var reader = new FileReader();
reader.onload = function(e) {
var contents = e.target.result;
loadPGN(contents);
};
reader.readAsText(file);
} else {
console.log("Failed to load file");
}
}
function loadFEN(fen) {
g_allMoves = [];
InitializeFromFen(fen);
EnsureAnalysisStopped();
InitializeBackgroundEngine();
g_playerWhite = !!g_toMove;
g_backgroundEngine.postMessage("position " + GetFen());
redrawBoard();
}
function loadPGN (pgn) {
var parsedPGN = parsePGN(pgn);
var fen = parsedPGN.fen;
var moves = parsedPGN.sequence;
g_allMoves = [];
clearPGN();
if (fen !== null) {
loadFEN(fen);
if (parsedPGN.startColor === BLACK) {
g_pgn.push("..");
}
} else {
ResetGame();
}
function Piece(flag,promo) {
this.flag = flag;
this.promo = promo;
}
moves.forEach(function(move) {
var i;
var formatedMove;
var vMoves = GenerateValidMoves();
var pieces = {
"P": new Piece(piecePawn,null),
"N": new Piece(pieceKnight,moveflagPromoteKnight),
"B": new Piece(pieceBishop,moveflagPromoteBishop),
"R": new Piece(pieceRook,moveflagPromoteRook),
"Q": new Piece(pieceQueen,moveflagPromoteQueen),
"K": new Piece(pieceKing,null)
};
// get the piece flag
var piece = pieces[move.piece].flag; // [P,N,B,R,Q,K]
// ge the color flag
var color = (move.color === WHITE) ? 0x8 : 0x0;
// get the from value
var startList = [];
// get all square that has this kind of piece
var pieceIdx = (color|piece) << 4;
while(g_pieceList[pieceIdx] !== 0) {
startList.push(new Cell(FormatSquare(g_pieceList[pieceIdx])));
pieceIdx++;
}
var from = move.from;
if (from !== undefined) {
// if we have a precision on the starting square like the columns
// or even the position directly
// We will filter the startList using it
for (i = startList.length - 1; i >= 0; i--) {
if( from.length === 1) {
// only the row is given
if (from.match(/[a-h]/) && startList[i].position.charAt(0) !== from) {
// different starting row
startList.splice(i,1);
} else if (from.match(/[1-8]/) && startList[i].position.charAt(1) !== from) {
// different starting line
startList.splice(i,1);
}
} else if (from.length === 2) {
// the starting coordinate is given
// this is then just an extra check
if (startList[i].position !== from) {
// different starting coordinate
startList.splice(i,1);
}
}
}
}
// here we should have a list of starting square
// only one should make a valid move
// paired with the provided destination
var end = new Cell(move.to);
var endSquare = MakeSquare(end.y, end.x);
var promotion = (move.promotion) ? pieces[move.promotion.substr(1)].promo : undefined; // remove the "="
// take formatedMove and endSquare in a closure
function checkMove(start) {
var startSquare = MakeSquare(start.y, start.x);
if (promotion !== undefined) {
// we have a promotion so we need to generate a
// specific move and check against it
if(vMoves[i] === GenerateMove(startSquare, endSquare, moveflagPromotion | promotion)) {
formatedMove = vMoves[i];
}
} else {
// just checking start and end square allows to cover
// all other special moves like "en passant" capture and
// castling
if ( (vMoves[i] & 0xFF) == startSquare &&
((vMoves[i] >> 8) & 0xFF) == endSquare ) {
formatedMove = vMoves[i];
}
}
}
// to get the move we will check withing all valide moves
// which one match the hints given by the pgn
for (i = 0; i < vMoves.length; i++) {
startList.forEach(checkMove);
if (formatedMove) break;
}
if(formatedMove) {
UIPlayMove(formatedMove,false);
} else {
console.log(move);
throw "Invalid PGN";
}
});
if (g_toMove === colorWhite) {
g_playerWhite = true;
camera.position.x = 0;
camera.position.z = 100;
} else {
g_playerWhite = false;
camera.position.x = 0;
camera.position.z = -100;
}
EnsureAnalysisStopped();
if (InitializeBackgroundEngine()) {
g_backgroundEngine.postMessage("position " + GetFen());
}
redrawBoard();
}
function clearPGN () {
$pgn.val("");
g_pgn = [];
}
function addToPGN(move) {
g_pgn.push(GetMoveSAN(move));
updatePGN();
}
function updatePGN() {
$pgn.val(getPGN());
$pgn.scrollTop($pgn[0].scrollHeight);
}
function getPGN() {
var str = "";
g_pgn.forEach(function(move,i) {
if(i%2 === 0) {
if (move === "..") {
str += ((i/2)+1)+"...";
} else {
str += ((i/2)+1)+". "+move;
}
} else {
str += " "+move+"\r\n";
}
});
return str;
}
function save() {
var filename = "chessSave.pgn";
var a = document.createElement("a");
if (typeof a.download === "undefined")
{
var str = 'data:text/html,' + encodeURIComponent("<p><a download='" + filename + "' href=\"data:application/json," +
encodeURIComponent(getPGN()) +
"\">Download link</a></p>");
window.open(str);
} else {
// auto download
var body = document.body;
a.textContent = filename;
a.href = "data:application/json," + encodeURIComponent(getPGN());
a.download = filename;
body.appendChild(a);
var clickEvent = document.createEvent("MouseEvent");
clickEvent.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(clickEvent);
body.removeChild(a);
}
}
function changePromo(event) {
var choice = $(event.currentTarget).val();
switch(choice) {
case "Queen":
promotion = moveflagPromoteQueen;
break;
case "Rook":
promotion = moveflagPromoteRook;
break;
case "Bishop":
promotion = moveflagPromoteBishop;
break;
case "Knight":
promotion = moveflagPromoteKnight;
break;
}
}
function displayCheck() {
if (validMoves.length === 0) {
$info.text(( g_inCheck ? 'Checkmate' : 'Stalemate' ));
} else if (g_inCheck) {
$info.text('Check');
} else {
$info.text('');
}
if ($info.text() !== '') {
$info.show("highlight",{},500);
} else {
$info.hide();
}
}
window.initGUI = initGUI;
window.initInfo = initInfo;
window.clearPGN = clearPGN;
window.addToPGN = addToPGN;
window.displayCheck = displayCheck;
window.newGame = newGame;
})();