cm-engine-runner 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.htaccess +3 -0
- package/README.md +30 -0
- package/assets/books/openings.bin +0 -0
- package/engines/stockfish-v10-niklasf.js +37 -0
- package/favicon.ico +0 -0
- package/index.html +36 -0
- package/package.json +33 -0
- package/postinstall.js +10 -0
- package/src/cm-engine-runner/EngineRunner.js +35 -0
- package/src/cm-engine-runner/PolyglotRunner.js +54 -0
- package/src/cm-engine-runner/StockfishRunner.js +124 -0
package/favicon.ico
ADDED
|
Binary file
|
package/index.html
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport"
|
|
6
|
+
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
|
7
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
8
|
+
<title>cm-engine-runner</title>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<h1>cm-engine-runner</h1>
|
|
12
|
+
<p>Logs are in the developer console</p>
|
|
13
|
+
<script type="module" crossorigin="anonymous">
|
|
14
|
+
import {PolyglotRunner} from "./src/cm-engine-runner/PolyglotRunner.js"
|
|
15
|
+
import {StockfishRunner} from "./src/cm-engine-runner/StockfishRunner.js"
|
|
16
|
+
|
|
17
|
+
const polyglotRunner = new PolyglotRunner({bookUrl: "./assets/books/openings.bin", responseDelay: 0, debug: true})
|
|
18
|
+
const startingPosition = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
|
19
|
+
const startingFewMoves = "rnbqkbnr/pppppp2/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 1"
|
|
20
|
+
console.log("polyglot", startingPosition, await polyglotRunner.calculateMove(startingPosition))
|
|
21
|
+
console.log("polyglot", startingFewMoves, await polyglotRunner.calculateMove(startingFewMoves))
|
|
22
|
+
|
|
23
|
+
const positionEndGame = "8/8/8/2k5/4K3/8/8/8"
|
|
24
|
+
console.log("polyglot", positionEndGame, await polyglotRunner.calculateMove(positionEndGame))
|
|
25
|
+
|
|
26
|
+
const stockfishRunner = new StockfishRunner({workerUrl: "./engines/stockfish-v10-niklasf.js", responseDelay: 0, debug: true})
|
|
27
|
+
console.log("stockfish", await stockfishRunner.calculateMove(startingFewMoves))
|
|
28
|
+
const postitionBlackWillWin = "4k3/3r4/8/8/8/8/6K1/8 w - - 0 1"
|
|
29
|
+
console.log("stockfish", await stockfishRunner.calculateMove(postitionBlackWillWin, {level: 1}))
|
|
30
|
+
console.log("stockfish", await stockfishRunner.calculateMove(postitionBlackWillWin, {level: 10}))
|
|
31
|
+
|
|
32
|
+
const positionManyPawns = "ppppkppp/pppppppp/pppppppp/pppppppp/8/5N2/8/RNBQKB1R b KQ - 0 1"
|
|
33
|
+
console.log("stockfish", await stockfishRunner.calculateMove(positionManyPawns, {level: 3}))
|
|
34
|
+
</script>
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cm-engine-runner",
|
|
3
|
+
"description": "abstraction layer to run chess engines, supports opening books",
|
|
4
|
+
"version": "1.0.11",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"postinstall": "node postinstall.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/shaack/cm-chess-engine-adapter.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"chess",
|
|
16
|
+
"engine",
|
|
17
|
+
"chessmail",
|
|
18
|
+
"es6"
|
|
19
|
+
],
|
|
20
|
+
"author": "shaack.com",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/shaack/cm-chess-engine-adapter/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/shaack/cm-chess-engine-adapter#readme",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"teevi": "^2.1.10"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"cm-polyglot": "^1.0.6",
|
|
31
|
+
"modrator": "^1.2.1"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author and copyright: Stefan Haack (https://shaack.com)
|
|
3
|
+
* License: MIT, see file 'LICENSE'
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ModRator = require("modrator/src/ModRator.js")
|
|
7
|
+
const modRator = new ModRator(__dirname)
|
|
8
|
+
|
|
9
|
+
modRator.addToLibrary("cm-polyglot")
|
|
10
|
+
modRator.addToLibrary("cm-polyglot", "src", "stakelbase")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author and copyright: Stefan Haack (https://shaack.com)
|
|
3
|
+
* Repository: https://github.com/shaack/cm-engine-runner
|
|
4
|
+
* License: MIT, see file 'LICENSE'
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const ENGINE_STATE = {
|
|
8
|
+
LOADING: 1,
|
|
9
|
+
LOADED: 2,
|
|
10
|
+
READY: 3,
|
|
11
|
+
THINKING: 4
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class EngineRunner {
|
|
15
|
+
|
|
16
|
+
constructor(props) {
|
|
17
|
+
this.props = {
|
|
18
|
+
debug: false,
|
|
19
|
+
responseDelay: 1000 // https://www.reddit.com/r/ProgrammerHumor/comments/6xwely/from_the_apple_chess_engine_code/
|
|
20
|
+
// https://opensource.apple.com/source/Chess/Chess-347/Sources/MBCEngine.mm.auto.html
|
|
21
|
+
}
|
|
22
|
+
Object.assign(this.props, props)
|
|
23
|
+
this.engineState = ENGINE_STATE.LOADING
|
|
24
|
+
this.initialization = this.init()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
init() {
|
|
28
|
+
return Promise.reject("you have to implement `init()` in the EngineRunner")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
calculateMove(fen, props = {}) {
|
|
32
|
+
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author and copyright: Stefan Haack (https://shaack.com)
|
|
3
|
+
* Repository: https://github.com/shaack/cm-engine-runner
|
|
4
|
+
* License: MIT, see file 'LICENSE'
|
|
5
|
+
*/
|
|
6
|
+
import {ENGINE_STATE, EngineRunner} from "./EngineRunner.js"
|
|
7
|
+
import {Polyglot} from "../../lib/cm-polyglot/Polyglot.js"
|
|
8
|
+
|
|
9
|
+
export class PolyglotRunner extends EngineRunner {
|
|
10
|
+
|
|
11
|
+
constructor(props) {
|
|
12
|
+
super(props)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
init() {
|
|
16
|
+
this.polyglot = new Polyglot(this.props.bookUrl)
|
|
17
|
+
this.polyglot.initialisation.then(() => {
|
|
18
|
+
this.engineState = ENGINE_STATE.READY
|
|
19
|
+
return Promise.resolve()
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
calculateMove(fen, props = {}) {
|
|
24
|
+
this.engineState = ENGINE_STATE.THINKING
|
|
25
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
26
|
+
setTimeout(async () => {
|
|
27
|
+
resolve()
|
|
28
|
+
}, this.props.responseDelay)
|
|
29
|
+
})
|
|
30
|
+
const calculationPromise = new Promise(async (resolve) => {
|
|
31
|
+
const moves = await this.polyglot.getMovesFromFen(fen)
|
|
32
|
+
if(this.props.debug) {
|
|
33
|
+
console.log(fen, "moves found in opening book", moves)
|
|
34
|
+
}
|
|
35
|
+
// handle propability
|
|
36
|
+
const propabilityMatrix = []
|
|
37
|
+
for (const move of moves) {
|
|
38
|
+
for (let i = 0; i < (move.probability * 10); i++) {
|
|
39
|
+
propabilityMatrix.push(move)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// propability weighted random
|
|
43
|
+
const luckyIndex = Math.floor(Math.random() * propabilityMatrix.length)
|
|
44
|
+
resolve(propabilityMatrix[luckyIndex])
|
|
45
|
+
})
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
Promise.all([this.initialization, timeoutPromise, calculationPromise]).then((values) => {
|
|
48
|
+
this.engineState = ENGINE_STATE.READY
|
|
49
|
+
resolve(values[2])
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author and copyright: Stefan Haack (https://shaack.com)
|
|
3
|
+
* Repository: https://github.com/shaack/cm-engine-runner
|
|
4
|
+
* License: MIT, see file 'LICENSE'
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {ENGINE_STATE, EngineRunner} from "./EngineRunner.js"
|
|
8
|
+
|
|
9
|
+
const LEVEL_DEPTH = {
|
|
10
|
+
1: 1,
|
|
11
|
+
2: 2,
|
|
12
|
+
3: 3,
|
|
13
|
+
4: 4,
|
|
14
|
+
5: 7,
|
|
15
|
+
6: 10,
|
|
16
|
+
7: 13,
|
|
17
|
+
8: 16,
|
|
18
|
+
9: 19,
|
|
19
|
+
10: 22
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class StockfishRunner extends EngineRunner {
|
|
23
|
+
|
|
24
|
+
constructor(props) {
|
|
25
|
+
super(props)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
init() {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const listener = (event) => {
|
|
31
|
+
this.workerListener(event)
|
|
32
|
+
}
|
|
33
|
+
if (this.engineWorker) {
|
|
34
|
+
this.engineWorker.removeEventListener("message", listener)
|
|
35
|
+
this.engineWorker.terminate()
|
|
36
|
+
}
|
|
37
|
+
this.engineWorker = new Worker(this.props.workerUrl)
|
|
38
|
+
this.engineWorker.addEventListener("message", listener)
|
|
39
|
+
|
|
40
|
+
this.uciCmd('uci')
|
|
41
|
+
this.uciCmd('ucinewgame')
|
|
42
|
+
this.uciCmd('isready')
|
|
43
|
+
resolve()
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
workerListener(event) {
|
|
48
|
+
if(this.props.debug) {
|
|
49
|
+
if(event.type === "message") {
|
|
50
|
+
console.log(" msg", event.data)
|
|
51
|
+
} else {
|
|
52
|
+
console.log(event)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const line = event.data
|
|
56
|
+
if (line === 'uciok') {
|
|
57
|
+
this.engineState = ENGINE_STATE.LOADED
|
|
58
|
+
} else if (line === 'readyok') {
|
|
59
|
+
this.engineState = ENGINE_STATE.READY
|
|
60
|
+
} else {
|
|
61
|
+
let match = line.match(/^info .*\bscore (\w+) (-?\d+)/)
|
|
62
|
+
if (match) {
|
|
63
|
+
const score = parseInt(match[2], 10)
|
|
64
|
+
let tmpScore
|
|
65
|
+
if (match[1] === 'cp') {
|
|
66
|
+
tmpScore = (score / 100.0).toFixed(1)
|
|
67
|
+
} else if (match[1] === 'mate') {
|
|
68
|
+
tmpScore = '#' + Math.abs(score)
|
|
69
|
+
}
|
|
70
|
+
this.score = tmpScore
|
|
71
|
+
}
|
|
72
|
+
// match = line.match(/^bestmove ([a-h][1-8])([a-h][1-8])([qrbk])?/) // ponder is not always included
|
|
73
|
+
match = line.match(/^bestmove ([a-h][1-8])([a-h][1-8])([qrbk])?( ponder ([a-h][1-8])?([a-h][1-8])?)?/)
|
|
74
|
+
if (match) {
|
|
75
|
+
this.engineState = ENGINE_STATE.READY
|
|
76
|
+
if (match[4] !== undefined) {
|
|
77
|
+
this.ponder = {from: match[5], to: match[6]}
|
|
78
|
+
} else {
|
|
79
|
+
this.ponder = undefined
|
|
80
|
+
}
|
|
81
|
+
const move = {from: match[1], to: match[2], promotion: match[3], score: this.score, ponder: this.ponder}
|
|
82
|
+
this.moveResponse(move)
|
|
83
|
+
} else {
|
|
84
|
+
match = line.match(/^info .*\bdepth (\d+) .*\bnps (\d+)/)
|
|
85
|
+
if (match) {
|
|
86
|
+
this.engineState = ENGINE_STATE.THINKING
|
|
87
|
+
this.search = 'Depth: ' + match[1] + ' Nps: ' + match[2]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
calculateMove(fen, props = { level: 4 }) {
|
|
94
|
+
this.engineState = ENGINE_STATE.THINKING
|
|
95
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
96
|
+
setTimeout(async () => {
|
|
97
|
+
resolve()
|
|
98
|
+
}, this.props.responseDelay)
|
|
99
|
+
})
|
|
100
|
+
const calculationPromise = new Promise ((resolve) => {
|
|
101
|
+
setTimeout(() => {
|
|
102
|
+
this.uciCmd('position fen ' + fen)
|
|
103
|
+
this.uciCmd('go depth ' + (LEVEL_DEPTH[props.level]))
|
|
104
|
+
this.moveResponse = (move) => {
|
|
105
|
+
resolve(move)
|
|
106
|
+
}
|
|
107
|
+
}, this.props.responseDelay)
|
|
108
|
+
})
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
Promise.all([this.initialisation, timeoutPromise, calculationPromise]).then((values) => {
|
|
111
|
+
this.engineState = ENGINE_STATE.READY
|
|
112
|
+
resolve(values[2])
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
uciCmd(cmd) {
|
|
118
|
+
if(this.props.debug) {
|
|
119
|
+
console.log(" uci ->", cmd)
|
|
120
|
+
}
|
|
121
|
+
this.engineWorker.postMessage(cmd)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
}
|