From 260e5c533206ecd8cc3ac552a758c70e842c20f4 Mon Sep 17 00:00:00 2001 From: ecrucru Date: Thu, 26 Oct 2017 23:07:53 +0200 Subject: [PATCH] Version 1.0.0 --- README.md | 53 ++++++ toledo-uci.js | 448 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 501 insertions(+) create mode 100644 README.md create mode 100644 toledo-uci.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..e71ab95 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ + +# Toledo NanoChess UCI + +Toledo NanoChess is originally a killer chess engine because it is the smallest one in the world ! The quality of the code within so few bytes is totally incredible. + +This UCI version is a derivative work of the library Toledo NanoChess released in 2013 for [JavaScript](http://nanochess.org/chess4.html). It has some benefits : + +- Play with a desktop application through the modern UCI protocol. +- Undo the last move without losing your game. +- With pyChess' slider, select the strength of the AI when you start a new game (6 is a good value). +- Toledo plays both White and Black. +- Play Chess960. +- Information about the analysis : processing time, nodes, nodes per second... + +Despite it was coded and tested in just two days, this UCI port doesn't aim to be the smallest one in the world... ;-) + + +## Install + +First, install NodeJS.org which is the host application to run the script. + +For pyChess, just add `toledo-uci.js` to the list of engines. + +For WinBoard, add this new line in the engine list : + +``` +"Toledo NanoChess UCI" -fcp 'C:\full-path-to\node.exe "C:\full-path-to\toledo-uci.js"' -fn "Toledo NanoChess UCI" -fUCI +``` + +For the command line, execute `node toledo-uci.js`. + + +## License + +Refer to the header of the main JavaScript file to know more about the license. + + +## Supported commands + +- `uci` +- `setoption name Skill Level value (number)` where *(number)* is a level between 1 and 20 +- `isready` +- `ucinewgame` +- `position startpos` +- `position startpos moves e2e4` with as many moves as needed +- `position fen (string)` where *(string)* is a complete position in FEN format +- `debug` displays Toledo NanoChess' internal variables +- `debug move (string)` where *(string)* is a move to perform written in UCI format (like `a2a1q`) +- `go` +- `go depth (number)` where *(number)* is the forced depth to reach (it temporarily overrides the current strength of the AI) +- `quit` + +Any other command is unsupported (like `stop`) or partially ignored (like `go infinite`). diff --git a/toledo-uci.js b/toledo-uci.js new file mode 100644 index 0000000..89989d3 --- /dev/null +++ b/toledo-uci.js @@ -0,0 +1,448 @@ +/*==================================================================================================== + * + * Toledo JavaScript Chess Engine + * (C) Copyright 2009-2013 Oscar Toledo G. [biyubi@gmail.com] + * http://nanochess.org/chess4.html + * + * Development of a UCI-compatible interface based on the release of 2013-05-10 + * (C) Copyright 2017 ecrucru + * https://github.com/ecrucru/toledo-uci/ + * https://github.com/ecrucru/anticrux/ (Reused parts of code) + * + * License : + * - Free for non commercial use + * - Contact the original author for any commercial use + * - Allowed derivative works with the mention of the authors + * - No guarantee + * + *==================================================================================================== + * + * z = constant equal to 15 to apply the bitmask 1111b on I[] to extract the piece and the player + * I[0..119] = board of 120 cells, I[21]=a8, I[98]=h1, + * I[] = tag | player | piece + * > tag = 10000b to mark the original position used in castling + * > player = 0000b for black, 1000b for white + * > piece = 001b for pawn, 010b for king, 011b for knight, 100b for bishop, 101b for rook, + * 110b for queen, 111b for invalid piece + * s = positional identifier in I[] of the selected cell on the board + * B = positional identifier in I[] of the source cell + * b = positional identifier in I[] of the target cell + * u = positional identifier in I[] of the target pawn which has activated an enpassant cell + * i = processed player|piece, it serves to promote a pawn to another piece + * y = current player, 0000b=white, 1000b=black, the sides are reversed with I[] to allow XOR + * + * Any name beginning with an underscore "_" is not part of the original library. + * + *==================================================================================================== + * + * List of interesting positions to validate the UCI output : + * - Normal move : + * - White : rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - + * - Black : rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - + * - Move with taken piece : + * - White : rnbqkbnr/ppp1pppp/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - + * - Black : rnbqkbnr/ppp1pppp/8/3P4/8/8/PPPP1PPP/RNBQKBNR b KQkq - + * - Promotion : + * - White : 1k6/7P/1K6/8/8/8/8/8 w - - + * - Black : 8/8/8/8/8/1k6/7p/1K6 b - - + * - En passant : + * - White : 1k6/8/1K6/6pP/8/8/8/8 w - g6 + * - Black : 1k6/8/1K6/8/5pP1/8/8/8 b - g3 + * - Short castling : + * - White : rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK2R w KQkq - + * - Black : rnbqk2r/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - + * - Long castling : + * - White : rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/R3KBNR w KQkq - + * - Black : r3kbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - + * +====================================================================================================*/ + + + +//==================================================================================================== +// Toledo NanoChess original library (with few changes) + +var B, i, y, u, b, I = [], G, x, z, M, l; + +function X(w, c, h, e, S, s) +{ + var t, o, L, E, d, O = e, // That's the letters of the original author's name :-) + N = -M * M, + K = 78 - h << x, + p, g, n, m, A, q, r, C, J, a = y ? -x : x; + y ^= 8; + G++; + _uciNodes++; + d = w || s && s >= h && X(0, 0, 0, 21, 0, 0) > M; + do + { + if (o = I[p = O]) + { + q = o & z ^ y; + if (q < 7) + { + A = q-- & 2 ? 8 : 4; + C = o - 9 & z ? [53, 47, 61, 51, 47, 47][q] : 57; + do + { + r = I[p += l[C]]; + if (!w | p == w) + { + g = q | p + a - S ? 0 : S; + if (!r & (!!q | A < 3 || !!g) || (r + 1 & z ^ y) > 9 && q | A > 2) + { + if (m = !(r - 2 & 7)) + return y ^= 8, I[G--] = O, K; + J = n = o & z; + E = I[p - a] & z; + t = q | E - 7 ? n : (n += 2, 6 ^ y); + while (n <= t) + { + L = r ? l[r & 7 | 32] - h - q : 0; + if (s) + L += (1 - q ? l[(p - p % x) / x + 37] - l[(O - O % x) / x + 37] + l[p % x + 38] * (q ? 1 : 2) - l[O % x + 38] + (o & 16) / 2 : !!m * 9) + (!q ? !(I[p - 1] ^ n) + !(I[p + 1] ^ n) + l[n & 7 | 32] - 99 + !!g * 99 + (A < 2) : 0) + !(E ^ y ^ 9); + if (s > h || 1 < s & s == h && L > z | d) + { + I[p] = n, I[O] = m ? (I[g] = I[m], I[m] = 0) : g ? I[g] = 0 : 0; + L -= X(s > h | d ? 0 : p, L - N, h + 1, I[G + 1], J = q | A > 1 ? 0 : p, s); + if (!(h || s - 1 | B - O | i - n | p - b | L < -M)) + return B = b, G--, u = J; + J = q - 1 | A < 7 || m || !s | d | r | o < z || X(0, 0, 0, 21, 0, 0) > M; + I[O] = o; + I[p] = r; + m ? (I[m] = I[g], I[g] = 0) : g ? I[g] = 9 ^ y : 0; + } + if (L > N || s > 1 && L == N && !h && Math.random() < 0.5) + { + I[G] = O; + if (s > 1) + { + if (h && c - L < 0) + return y ^= 8, G--, L; + if (!h) + i = n, B = O, b = p; + } + N = L; + } + n += J || (g = p, m = p < O ? g - 3 : g + 2, I[m] < z | I[m + O - p] || I[p += p - O]) ? 1 : 0; + } + } + } + } while (!r & q > 2 || (p = O, q | A > 2 | o > z & !r && ++C * --A)); + } + } + } while (++O > 98 ? O = 20 : e - O); + return y ^= 8, G--, N + M * M && N > -K + 1924 | d ? N : 0; +} + + + +//==================================================================================================== +// Wrapper to use it as an UCI-compatible engine + + +//-- Initialization +var _uciStrength = 2, + _uciNodes; + +var _fs = require('fs'); +_uciReset(); +process.title = 'Toledo NanoChess UCI'; +process.on('SIGINT', function() { + process.exit(0); +}); +process.stdin.on('readable', function() { + var _obj, _input, _k; + + //-- Input block of data + _obj = process.stdin.read(); + if (_obj === null) + return; + _input = _obj.toString(); + + //-- Simplifies the input + _input = _input.split("\r").join(''); + _input = _input.split("\t").join(' '); + _k = _input.length; + while (true) + { + _input = _input.split(' ').join(' '); + if (_input.length != _k) + _k = _input.length; + else + break; + } + + //-- Splits the input line by line + _input = _input.split("\n"); + for (_k=0 ; _k<_input.length ; _k++) + { + _line = _input[_k]; + if (_line.length === 0) + continue; + _uciProcess(_line); + } +}); + + +//-- Library +function _uciWrite(pLine) +{ + if (typeof _fs != 'undefined') + _fs.writeSync(1, pLine + "\r\n"); + else + console.log(pLine + "\r\n"); +} + +function _uciProcess(pLine) +{ + var _j, _tab, _fen, _list, _promo, + _match, _yold, _levelold, _B, _b, + _nodes, _start, _end, _nps; + + //-- Processes + _tab = pLine.split(' '); + for (_j=0 ; (_j<10) || (_j<_tab.length) ; _j++) + if (typeof _tab[_j] == 'undefined') + _tab[_j] = ''; + switch (_tab[0].toLowerCase()) + { + case 'quit': + process.exit(0); + break; + + case 'uci': + _uciWrite('id name Toledo NanoChess UCI'); + _uciWrite('id author More information at https://github.com/ecrucru/toledo-uci/'); + _uciWrite('option name Skill Level type spin default '+_uciStrength+' min 1 max 20'); + _uciWrite('option name UCI_Chess960 type check default false'); + _uciWrite('uciok'); + _uciWrite('copyprotection ok'); + break; + + case 'setoption': + _match = pLine.match(/^setoption name skill level value ([0-9]+)$/i); + if (_match !== null) + { + _uciStrength = Math.min(Math.max(parseInt(_match[1]), 2), 20); + _uciWrite('set info Level '+_uciStrength+' set'); + } + break; + + case 'isready': + _uciWrite('readyok'); + break; + + case 'ucinewgame': + _uciReset(); + break; + + case 'position': + { + //- Loads the initial position + if (_tab[1].toLowerCase() == 'fen') + { + _fen = ''; + for (_j=2 ; _j<_tab.length ; _j++) + { + if (_tab[_j] == 'moves') + break; + _fen += (_fen.length > 0 ? ' ' : '') + _tab[_j]; + } + if (!_uciLoadFen(_fen)) + { + _uciWrite('info string Error : invalid FEN ' + _fen); + return; + } + } + else if (_tab[1].toLowerCase() == 'startpos') + _uciLoadFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 0'); + else + return; + + // Proceeds with the additional moves + _b = false; + for (_j=2 ; _j<_tab.length ; _j++) + { + if (_tab[_j].length === 0) + continue; + if (_tab[_j] == 'moves') + { + _b = true; + continue; + } + if (_b && !_uciMove(_tab[_j])) + { + _uciWrite('info string Error : invalid move history'); + break; + } + } + break; + } + + case 'go': + { + //- Forced depth + _levelold = _uciStrength; + switch (_tab[1].toLowerCase()) + { + case '': + break; + case 'depth': + _uciStrength = parseInt(_tab[2]); + _uciStrength = (_uciStrength <= 0 ? _levelold : Math.max(2, _uciStrength)); + break; + default: + _uciWrite('info string Info : unrecognized parameter for the command '+_tab[0]); + break; + } + + //- Init + _yold = y; + _uciNodes = 0; + + //- Search + _start = Date.now(); + X(0, 0, 0, 21, u, _uciStrength); + _end = Date.now(); + _B = B; + _b = b; + _promo = (i != (I[B]&z)); + _nodes = _uciNodes; + _nps = (_end-_start === 0 ? 0 : Math.floor(1000*_nodes/(_end-_start))); + X(0, 0, 0, 21, u, 1); + + //- Output + if (y == _yold) + _uciWrite('bestmove 0000'); + else + { + _uciWrite('info depth '+_uciStrength+' time '+(_end-_start)+' nodes '+_nodes+' nps '+_nps+' pv 0000'); + _uciWrite('bestmove '+_uciIdToCoordinate(_B)+_uciIdToCoordinate(_b)+(_promo?' nbrq'[(i&z)^y]:'').trim()); + } + _uciStrength = _levelold; + break; + } + + case 'debug': + { + if (_tab[1] == 'move') + { + if (!_uciMove(_tab[2])) + _uciWrite('info string Error : invalid move'); + } + else + { + _list = ['B', 'i', 'y', 'u', 'b', 'I']; + for (_j=0 ; _j<_list.length ; _j++) + _uciWrite('info string '+_list[_j]+' = '+eval(_list[_j])); + } + break; + } + } +} + +function _uciReset() +{ + var _k; + + //-- Variables + B = i = y = u = b = 0; // That's the letters of the original author's email address :-) + I = []; + + //-- Constants + G = 120; + x = 10; + z = 15; + M = 10000; + l = [5, 3, 4, 6, 2, 4, 3, 5, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 13, 11, 12, 14, 10, 12, 11, 13, 0, 99, 0, 306, 297, 495, 846, -1, 0, 1, 2, 2, 1, 0, -1, -1, 1, -10, 10, -11, -9, 9, 11, 10, 20, -9, -11, -10, -20, -21, -19, -12, -8, 8, 12, 19, 21]; + + //-- Resets the board + for (_k=0 ; _k < 120 ; _k++) + I[_k] = ((_k >= 21) && (_k <= 98) && ((_k-20)%10>0) && ((_k-20)%10<9)) ? 0 : 7; +} + +function _uciLoadFen(pFen) +{ + var _list, _x, _y, _i, _car, _castB, _castW, _black; + + //-- Checks + if (pFen.length === 0) + return false; + + //-- Splits the input parameter + _list = pFen.trim().split(' '); + if (_list[0].split('/').length != 8) + return false; + + //-- Castling + _castB = _list[2].match(/k/) || _list[2].match(/q/); + _castW = _list[2].match(/K/) || _list[2].match(/Q/); + + //-- Loads the main position + _uciReset(); + _x = 0; + _y = 0; + for (_i=0 ; _i<_list[0].length ; _i++) + { + _car = _list[0].charAt(_i); + if ('12345678'.indexOf(_car) != -1) + _x += parseInt(_car); + else if (_car == '/') + { + _x = 0; + _y++; + } + else if ('prnbqk'.indexOf(_car.toLowerCase()) != -1) + { + if (_x > 7) + return false; + else + { + _black = (_car == _car.toLowerCase()); + I[21+10*_y+_x] = ((_black&&_castB)||(!_black&&_castW) ? 16 : 0) | (_black ? 0 : 8) | ' pknbrq'.indexOf(_car.toLowerCase()); + _x++; + } + } + else + return false; + } + + //-- Current player + y = (_list[1].toLowerCase() == 'b' ? 8 : 0); + + //-- En passant + u = (_list[3] == '-' ? 0 : _uciCoordinateToId(_list[3]) + (y==8 ? -10 : 10)); + + return true; +} + +function _uciCoordinateToId(pCoordinate) +{ + return 21 + 10*(8-parseInt(pCoordinate.substring(1))) + 'abcdefgh'.indexOf(pCoordinate.substring(0,1).toLowerCase()); +} + +function _uciIdToCoordinate(pId) +{ + var _x = (pId-21)%10, + _y = Math.floor((pId-21)/10); + return 'abcdefgh'[_x]+(8-_y); +} + +function _uciMove(pMove) +{ + var _idx, _y; + _y = y; + B = _uciCoordinateToId(pMove.substring(0,2)); + b = _uciCoordinateToId(pMove.substring(2,4)); + i = I[B] & z; + if ((i & 7) == 1 & (b < 29 | b > 90)) + { + _idx = 'qrbn'.indexOf(pMove.substring(4,5)); + if (_idx == -1) + _idx = 0; + i = (14 - _idx) ^ y; + } + X(0, 0, 0, 21, u, 1); + return (y != _y); +}