gekomad / Cinnamon

C++ UCI chess engine
GNU Lesser General Public License v3.0
31 stars 8 forks source link

is it possible to run it on nodejs #57

Closed invasionofsmallcubes closed 3 years ago

invasionofsmallcubes commented 3 years ago

Hi, is it possible to import the js version on nodejs? If yes, is there any documentation I could use?

What I tried is the following:

const cinnamon = require('./cinnamon_2.4')
cinnamonCommand = cinnamon.cwrap('command', 'string', ['string', 'string'])
cinnamonCommand("setMaxTimeMillsec", "1000")

and I get as error:

  var Module=typeof Module!=="undefined"?Module:{};var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_I
16:34:53 web.1   |  >  S_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path")
16:34:53 web.1   |  >  .dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){var ret=tryParseAsDataURI(filename);if(ret){return binary?ret:ret.toString()}if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(
16:34:53 web.1   |  >  process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);if(typeof module!=="undefined"){module["exports"]=Module}process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(
16:34:53 web.1   |  >  f){var data=tryParseAsDataURI(f);if(data){return intArrayToString(data)}return read(f)}}readBinary=function readBinary(f){var data;data=tryParseAsDataURI(f);if(data){return data}if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}i
16:34:53 web.1   |  >  f(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}
16:34:53 web.1   |  >  else{scriptDirectory=""}{read_=function(url){try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText}catch(err){var data=tryParseAsDataURI(url);if(data){return intArrayToString(data)}throw err}};if(ENVIRONMENT_IS_WORKER){readBinary=function(url){try{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}catch(err){var data=tryParseAsDataURI(url);if(data){return data}throw err}}
16:34:53 web.1   |  >  }readAsync=function(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}var data=tryParseAsDataURI(url);if(data){onload(data.buffer);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console
16:34:53 web.1   |  >  );for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;function alignMemory(size,factor){if(!factor)factor=STACK_ALIGN;return Math.ceil(size/factor)*factor}var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime;if(Module["noE
16:34:53 web.1   |  >  xitRuntime"])noExitRuntime=Module["noExitRuntime"];var WebAssembly={Memory:function(opts){this.buffer=new ArrayBuffer(opts["initial"]*65536)},Module:function(binary){},Instance:function(module,info){this.exports=(
16:34:53 web.1   |  TypeError: Cannot read properties of undefined (reading 'K')
16:34:53 web.1   |      at Module.stackSave (/Users/emanuele/personal/chat-vs-stockfish-backend/cinnamon_2.4.js:27:82799)
16:34:53 web.1   |      at ccall (/Users/emanuele/personal/chat-vs-stockfish-backend/cinnamon_2.4.js:27:1160)
16:34:53 web.1   |      at /Users/emanuele/personal/chat-vs-stockfish-backend/cinnamon_2.4.js:27:1587
16:34:53 web.1   |      at Object.<anonymous> (/Users/emanuele/personal/chat-vs-stockfish-backend/index.js:11:1)
16:34:53 web.1   |      at Module._compile (node:internal/modules/cjs/loader:1101:14)
16:34:53 web.1   |      at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
16:34:53 web.1   |      at Module.load (node:internal/modules/cjs/loader:981:32)
16:34:53 web.1   |      at Function.Module._load (node:internal/modules/cjs/loader:822:12)
16:34:53 web.1   |      at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:79:12)
16:34:53 web.1   |      at node:internal/main/run_main_module:17:47

if, instead, I just use:

const cinnamon = require('./cinnamon_2.4')
const cinnamonCommand = cinnamon.cwrap('command', 'string', ['string', 'string'])

then no error:

Cinnamon 2.4 UCI by Giuseppe Cannella
cinnamon_2.4.js:27
version compiled May 22 2021 with emscripten - Clang 12.0.0 (/startdir/llvm-project 52e240a0721e4120a7143f6f5bab4760d28d48e8)
cinnamon_2.4.js:27
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
cinnamon_2.4.js:27

Thank you

gekomad commented 3 years ago

Hi @invasionofsmallcubes this works for me:

$ nodejs -v
v10.19.0
$ nodejs
> const cinnamon = require('./cinnamon_2.4')
undefined
> Cinnamon 2.4 UCI by Giuseppe Cannella
version compiled May 22 2021 with emscripten - Clang 12.0.0 (/startdir/llvm-project 52e240a0721e4120a7143f6f5bab4760d28d48e8)
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

cinnamonCommand = cinnamon.cwrap('command', 'string', ['string', 'string'])
[Function]
> cinnamonCommand("setMaxTimeMillsec", "1000")
''
> cinnamonCommand("go","")
info depth 1 score cp 28 time 45 nodes 24 nps 533 pv e2e3
info depth 2 score cp 5 time 59 nodes 63 nps 1067 pv e2e3 e7e6
info depth 3 score cp 27 time 70 nodes 123 nps 1757 pv e2e3 e7e6 f1e2
info depth 4 score cp 5 time 103 nodes 387 nps 3757 pv e2e3 e7e6 f1e2 f8e7
info depth 5 score cp 15 time 150 nodes 762 nps 5080 pv e2e3 e7e6 f1c4 d8g5 e1f1
info depth 6 score cp 11 time 200 nodes 1911 nps 9555 pv e2e3 e7e6 b1c3 f8e7 d1g4 e8f8
info depth 7 score cp 13 time 264 nodes 3863 nps 14632 pv e2e3 e7e6 b1c3 b8c6 f1c4 d8g5 c3b5
info depth 8 score cp 5 time 398 nodes 16634 nps 41793 pv e2e3 e7e6 b1c3 b8c6 a2a3 f8c5 f1c4 a7a6
info depth 9 score cp 12 time 454 nodes 16783 nps 36966 pv e2e3 e7e6 b1c3 b8c6 a2a3 d8g5 g1f3 g5g4 f3d4
info depth 10 score cp 9 time 667 nodes 99989 nps 149908 pv e2e3 e7e6 b1c3 b8c6 a2a3 d7d5 f1e2 f8c5 d2d4 c5d6
info depth 11 score cp 13 time 961 nodes 154669 nps 160945 pv e2e3 e7e6 b1c3 b8c6 a2a3 a7a6 d1h5 d7d5 f1d3 d5d4 e3d4
bestmove e2e3
'e2e3'
> 
invasionofsmallcubes commented 3 years ago

Hi, sorry I was traveling. So yes, from interactive command line I don't have any issue.

➜  chat-vs-chess-backend git:(main) ✗ node -v
v16.9.1
➜  chat-vs-chess-backend git:(main) ✗ nodejs
zsh: command not found: nodejs
➜  chat-vs-chess-backend git:(main) ✗ node       
Welcome to Node.js v16.9.1.
Type ".help" for more information.
> const cinnamon = require('./cinnamon_2.4')
undefined
> Cinnamon 2.4 UCI by Giuseppe Cannella
version compiled May 22 2021 with emscripten - Clang 12.0.0 (/startdir/llvm-project 52e240a0721e4120a7143f6f5bab4760d28d48e8)
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

cinnamonCommand = cinnamon.cwrap('command', 'string_g', ['string', 'string'])
[Function (anonymous)]
> cinnamonCommand("setMaxTimeMillsec", "1000")
''
> cinnamonCommand("go","")
info depth 1 score cp 28 time 12 nodes 24 nps 2000 pv e2e3 
info depth 2 score cp 5 time 24 nodes 63 nps 2625 pv e2e3 e7e6 
info depth 3 score cp 27 time 38 nodes 123 nps 3236 pv e2e3 e7e6 f1e2 
info depth 4 score cp 5 time 76 nodes 387 nps 5092 pv e2e3 e7e6 f1e2 f8e7 
info depth 5 score cp 15 time 119 nodes 762 nps 6403 pv e2e3 e7e6 f1c4 d8g5 e1f1 
info depth 6 score cp 11 time 172 nodes 1911 nps 11110 pv e2e3 e7e6 b1c3 f8e7 d1g4 e8f8 
info depth 7 score cp 13 time 246 nodes 3863 nps 15703 pv e2e3 e7e6 b1c3 b8c6 f1c4 d8g5 c3b5 
info depth 8 score cp 5 time 379 nodes 16634 nps 43889 pv e2e3 e7e6 b1c3 b8c6 a2a3 f8c5 f1c4 a7a6 
info depth 9 score cp 12 time 437 nodes 16783 nps 38405 pv e2e3 e7e6 b1c3 b8c6 a2a3 d8g5 g1f3 g5g4 f3d4 
info depth 10 score cp 9 time 655 nodes 99989 nps 152654 pv e2e3 e7e6 b1c3 b8c6 a2a3 d7d5 f1e2 f8c5 d2d4 c5d6 
info depth 11 score cp 13 time 942 nodes 154669 nps 164192 pv e2e3 e7e6 b1c3 b8c6 a2a3 a7a6 d1h5 d7d5 f1d3 d5d4 e3d4 
bestmove e2e3
'e2e3'

but if I put in a file called index.js the following code:

const cinnamon = require('./cinnamon_2.4')
cinnamonCommand = cinnamon.cwrap('command', 'string_g', ['string', 'string'])
cinnamonCommand("setMaxTimeMillsec", "1000")

and then do node index.js then I get:

TypeError: Cannot read properties of undefined (reading 'K')
    at Module.stackSave (/Users/emanuele/personal/chat-vs-chess-backend/cinnamon_2.4.js:27:82799)
gekomad commented 3 years ago

hi, no problem

The engine takes a while to initialize itself but the setMaxTimeMillsec command appears to be sent immediately.

Sending all commands in node console the error is thrown before the engine is ready.

The code works on a html page but I don't know why it doesn't work on node, I'm no expert :(

$ nodejs
> const cinnamon = require('./cinnamon_2.4');console.log("A");cinnamonCommand = cinnamon.cwrap('command', 'string_g', ['string', 'string']);console.log("B");cinnamonCommand("setMaxTimeMillsec", "1000");console.log("C")
A
B
Thrown:
TypeError: Cannot read property 'K' of undefined
    at Module.stackSave (/home/geko/workspace/cinnamon_javascript_2.4/console/cinnamon_2.4.js:27:82799)
    at ccall (/home/geko/workspace/cinnamon_javascript_2.4/console/cinnamon_2.4.js:27:1160)
    at /home/geko/workspace/cinnamon_javascript_2.4/console/cinnamon_2.4.js:27:1587
> Cinnamon 2.4 UCI by Giuseppe Cannella
version compiled May 22 2021 with emscripten - Clang 12.0.0 (/startdir/llvm-project 52e240a0721e4120a7143f6f5bab4760d28d48e8)
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
invasionofsmallcubes commented 3 years ago

I'm not an expert either. I think I will use it from the browser at this point, it doesn't change my use case. I was just curious on why this difference between interactive nodejs and normal nodejs.

invasionofsmallcubes commented 3 years ago

I think I solved 90% of the issue. But I have a last question:

So this works because with emscripten you need to call it Module and work in the callback after initialization:

const Module = require('./public/cinnamon_2.4');

Module['onRuntimeInitialized'] = async function () {
  const cinnamonCommand = Module.cwrap('command', 'string_g', ['string', 'string'])
  await cinnamonCommand("setMaxTimeMillsec", "1000")
  const output = cinnamonCommand("go", "")
  console.log(output)
}

My problem now is I was expecting output to have the

info depth 1 score cp 28 time 12 nodes 24 nps 2000 pv e2e3 
info depth 2 score cp 5 time 24 nodes 63 nps 2625 pv e2e3 e7e6 
info depth 3 score cp 27 time 38 nodes 123 nps 3236 pv e2e3 e7e6 f1e2 
info depth 4 score cp 5 time 76 nodes 387 nps 5092 pv e2e3 e7e6 f1e2 f8e7 
info depth 5 score cp 15 time 119 nodes 762 nps 6403 pv e2e3 e7e6 f1c4 d8g5 e1f1 
info depth 6 score cp 11 time 172 nodes 1911 nps 11110 pv e2e3 e7e6 b1c3 f8e7 d1g4 e8f8 
info depth 7 score cp 13 time 246 nodes 3863 nps 15703 pv e2e3 e7e6 b1c3 b8c6 f1c4 d8g5 c3b5 
info depth 8 score cp 5 time 379 nodes 16634 nps 43889 pv e2e3 e7e6 b1c3 b8c6 a2a3 f8c5 f1c4 a7a6 
info depth 9 score cp 12 time 437 nodes 16783 nps 38405 pv e2e3 e7e6 b1c3 b8c6 a2a3 d8g5 g1f3 g5g4 f3d4 
info depth 10 score cp 9 time 655 nodes 99989 nps 152654 pv e2e3 e7e6 b1c3 b8c6 a2a3 d7d5 f1e2 f8c5 d2d4 c5d6 
info depth 11 score cp 13 time 942 nodes 154669 nps 164192 pv e2e3 e7e6 b1c3 b8c6 a2a3 a7a6 d1h5 d7d5 f1d3 d5d4 e3d4 
bestmove e2e3
'e2e3'

but instead has just 5955616.

So two questions:

Thanks.

invasionofsmallcubes commented 3 years ago

string_g should become string. The chess engine is perfectly functioning on nodejs, it would be nice to have a way to capture the reasoning but it's ok.