add pnacl support for ceval

pull/2295/head
Niklas Fiekas 2016-10-01 18:03:33 +02:00 committed by Thibault Duplessis
parent 1739bab7f0
commit 142195269d
7 changed files with 194 additions and 144 deletions

4
.gitmodules vendored
View File

@ -44,3 +44,7 @@
path = public/vendor/Sunsetter8
url = https://github.com/niklasf/Sunsetter8
branch = js
[submodule "public/vendor/stockfish.pexe"]
path = public/vendor/stockfish.pexe
url = https://github.com/niklasf/stockfish.pexe
branch = ddugovic

1
public/vendor/stockfish.pexe vendored 160000

@ -0,0 +1 @@
Subproject commit 6e07387f0adef9d521dd2eb4b1400031d4591e14

View File

@ -2,8 +2,8 @@ var m = require('mithril');
var makePool = require('./cevalPool');
var dict = require('./cevalDict');
var util = require('../util');
var stockfishWorker = require('./stockfishWorker');
var sunsetterWorker = require('./sunsetterWorker');
var stockfishProtocol = require('./stockfishProtocol');
var sunsetterProtocol = require('./sunsetterProtocol');
module.exports = function(possible, variant, emit) {
@ -16,13 +16,22 @@ module.exports = function(possible, variant, emit) {
var allowed = m.prop(true);
var enabled = m.prop(possible() && allowed() && lichess.storage.get(storageKey) === '1');
var started = false;
var engine = variant.key !== 'crazyhouse' ? stockfishWorker : sunsetterWorker;
var pool = makePool({
minDepth: minDepth,
maxDepth: maxDepth,
variant: variant
}, engine, nbWorkers);
var pool;
if (variant.key !== 'crazyhouse') {
pool = makePool(stockfishProtocol, {
asmjs: '/assets/vendor/stockfish.js/stockfish.js',
pnacl: '/assets/vendor/stockfish.pexe/nacl/stockfish.nmf'
}, {
minDepth: minDepth,
maxDepth: maxDepth,
variant: variant,
});
} else {
pool = makePool(sunsetterProtocol, {
asmjs: '/assets/vendor/Sunsetter8/sunsetter.js'
});
}
// adjusts maxDepth based on nodes per second
var npsRecorder = (function() {

View File

@ -1,7 +1,65 @@
var m = require('mithril');
module.exports = function(opts, makeWorker, nb) {
function makeHelper(makeWorker, terminateWorker, poolOpts, makeProtocol, protocolOpts) {
var worker, protocol, api;
var boot = function () {
worker = makeWorker(poolOpts);
protocol = makeProtocol(api, protocolOpts);
worker.addEventListener('message', function(e) {
protocol.received(e.data);
}, true);
};
var stop = function() {
var stopped = m.deferred(false);
setTimeout(function () {
stopped.reject();
}, 1000);
return protocol.stop(stopped);
};
api = {
send: function(text) {
worker.postMessage(text);
},
start: function (work) {
stop().then(function () {
protocol.start(work);
}, function () {
terminateWorker(worker);
boot();
});
},
stop: stop
};
boot();
return api;
}
function makeWebWorker(makeProtocol, poolOpts, protocolOpts) {
return makeHelper(function () {
return new Worker(poolOpts.asmjs);
}, function (worker) {
worker.terminate();
}, poolOpts, makeProtocol, protocolOpts);
}
function makePNaClModule(makeProtocol, poolOpts, protocolOpts) {
return makeHelper(function () {
var module = document.createElement('embed');
module.setAttribute('src', poolOpts.pnacl);
module.setAttribute('type', 'application/x-pnacl');
module.setAttribute('width', '0');
module.setAttribute('height', '0');
document.body.appendChild(module);
return module;
}, function () {}, poolOpts, makeProtocol, protocolOpts);
}
module.exports = function(makeProtocol, poolOpts, protocolOpts) {
var workers = [];
var token = -1;
@ -12,9 +70,13 @@ module.exports = function(opts, makeWorker, nb) {
};
var initWorkers = function() {
if (!workers.length)
for (var i = 1; i <= nb; i++)
workers.push(makeWorker(opts, 'W' + i));
if (workers.length) return;
if (poolOpts.pnacl && navigator.mimeTypes['application/x-pnacl'])
workers.push(makePNaClModule(makeProtocol, poolOpts, protocolOpts));
else
for (var i = 1; i <= 4; i++)
workers.push(makeWebWorker(makeProtocol, poolOpts, protocolOpts));
}
var stopAll = function() {

View File

@ -0,0 +1,75 @@
var m = require('mithril');
var legacyVariantMap = {
fromPosition: 'Chess960',
chess960: 'Chess960',
atomic: 'Atomic',
horde: 'Horde',
crazyhouse: 'House',
kingOfTheHill: 'KingOfTheHill',
racingKings: 'Race',
threeCheck: '3Check',
antichess: 'Anti'
};
module.exports = function(worker, opts) {
var work = null;
var stopped = m.deferred();
var legacyVariant = legacyVariantMap[opts.variant.key];
if (legacyVariant) worker.send('setoption name UCI_' + legacyVariant + ' value true');
else worker.send('uci');
// TODO: Modern variant selector
var processOutput = function(text) {
if (text.indexOf('bestmove ') === 0) {
stopped.resolve(true);
return;
}
if (!work) return;
if (/currmovenumber|lowerbound|upperbound/.test(text)) return;
var matches = text.match(/depth (\d+) .*score (cp|mate) ([-\d]+) .*nps (\d+) .*pv (.+)/);
if (!matches) return;
var depth = parseInt(matches[1]);
if (depth < opts.minDepth) return;
var cp, mate;
if (matches[2] === 'cp') cp = parseFloat(matches[3]);
else mate = parseFloat(matches[3]);
if (work.ply % 2 === 1) {
if (matches[2] === 'cp') cp = -cp;
else mate = -mate;
}
var best = matches[5].split(' ')[0];
work.emit({
work: work,
eval: {
depth: depth,
cp: cp,
mate: mate,
best: best,
nps: parseInt(matches[4])
},
name: name
});
};
return {
start: function(w) {
work = w;
worker.send(['position', 'fen', work.initialFen, 'moves'].concat(work.moves).join(' '));
worker.send('go depth ' + work.maxDepth);
},
stop: function(s) {
if (!work) s.resolve(true);
else {
work = null;
stopped = s;
worker.send('stop');
}
return s.promise;
},
received: processOutput
};
};

View File

@ -1,86 +0,0 @@
var m = require('mithril');
var variantMap = {
fromPosition: 'Chess960',
chess960: 'Chess960',
atomic: 'Atomic',
horde: 'Horde',
crazyhouse: 'House',
kingOfTheHill: 'KingOfTheHill',
racingKings: 'Race',
threeCheck: '3Check',
antichess: 'Anti'
};
module.exports = function(opts, name) {
var instance = null;
var busy = false;
var stopping = false;
var send = function(text) {
instance.postMessage(text);
};
var processOutput = function(text, work) {
if (text.indexOf('bestmove ') === 0) {
busy = false;
stopping = false;
return;
}
if (stopping) return;
if (/currmovenumber|lowerbound|upperbound/.test(text)) return;
var matches = text.match(/depth (\d+) .*score (cp|mate) ([-\d]+) .*nps (\d+) .*pv (.+)/);
if (!matches) return;
var depth = parseInt(matches[1]);
if (depth < opts.minDepth) return;
var cp, mate;
if (matches[2] === 'cp') cp = parseFloat(matches[3]);
else mate = parseFloat(matches[3]);
if (work.ply % 2 === 1) {
if (matches[2] === 'cp') cp = -cp;
else mate = -mate;
}
var best = matches[5].split(' ')[0];
work.emit({
work: work,
eval: {
depth: depth,
cp: cp,
mate: mate,
best: best,
nps: parseInt(matches[4])
},
name: name
});
};
var reboot = function() {
if (instance) instance.terminate();
instance = new Worker('/assets/vendor/stockfish.js/stockfish.js');
busy = false;
stopping = false;
var uciVariant = variantMap[opts.variant.key];
if (uciVariant) send('setoption name UCI_' + uciVariant + ' value true');
else send('uci'); // send something to warm up
};
reboot();
return {
start: function(work) {
if (busy) reboot();
busy = true;
send(['position', 'fen', work.initialFen, 'moves'].concat(work.moves).join(' '));
send('go depth ' + work.maxDepth);
instance.onmessage = function(msg) {
processOutput(msg.data, work);
};
},
stop: function() {
if (!busy) return;
stopping = true;
send('stop');
}
};
};

View File

@ -1,10 +1,11 @@
var m = require('mithril');
module.exports = function(opts, name) {
module.exports = function(worker, opts) {
var instance = null;
var busy = false;
var stopping = false;
var work = null;
var stopped = m.deferred();
worker.send('xboard');
// Sunsetter always plays only moves right away. Count the number of played
// moves to show the correct mate in #n.
@ -13,24 +14,19 @@ module.exports = function(opts, name) {
var aiMoves = 0;
var best;
var send = function(text) {
instance.postMessage(text);
};
var processOutput = function(text, work) {
var processOutput = function(text) {
if (text === 'tellics stopped') {
busy = false;
stopping = false;
aiMoves = 0;
best = undefined;
stopped.resolve(true);
return;
}
if (stopping) return;
if (!work) return;
if (text.indexOf('move ') == 0) {
aiMoves++;
best = text.split(' ')[1];
if (!stopping) send('analyze');
if (work) worker.send('analyze');
return;
}
@ -42,14 +38,13 @@ module.exports = function(opts, name) {
cp = parseInt(matches[2], 10);
if (!aiMoves) {
best = matches[5];
if (depth < opts.minDepth) return;
}
} else {
matches = text.match(/Found move:\s+([a-h1-8=@PNBRQK]+)\s+([-+]?\d+)\s.*/);
if (matches) {
cp = parseInt(matches[2], 10);
if (!aiMoves) best = matches[1];
if (!stopping) send('analyze');
if (work) worker.send('analyze');
} else {
return;
}
@ -81,40 +76,30 @@ module.exports = function(opts, name) {
});
};
var reboot = function() {
if (instance) instance.terminate();
instance = new Worker('/assets/vendor/Sunsetter8/sunsetter.js');
busy = false;
stopping = false;
aiMoves = 0;
send('xboard');
};
reboot();
return {
start: function(work) {
if (busy) reboot();
busy = true;
send('reset ' + opts.variant.key);
send('setboard ' + work.initialFen);
send('easy');
send('force');
start: function(w) {
work = w;
worker.send('reset crazyhouse');
worker.send('setboard ' + work.initialFen);
worker.send('easy');
worker.send('force');
for (var i = 0; i < work.moves.length; i++) {
send(work.moves[i]);
worker.send(work.moves[i]);
}
send('go');
instance.onmessage = function(msg) {
processOutput(msg.data, work);
};
worker.send('go');
},
stop: function() {
if (!busy) return;
stopping = true;
aiMoves = 0;
best = undefined;
send('exit');
send('tellics stopped');
}
stop: function(s) {
if (!work) s.resolve(true);
else {
stopped = s;
work = null;
aiMoves = 0;
best = undefined;
worker.send('exit');
worker.send('tellics stopped');
}
return s.promise;
},
received: processOutput
};
};