stockfish for godot
| -rw-r--r-- | Main.gd | 2 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | addons/stockfish.gd/README.md | 2 | ||||
| -rw-r--r-- | addons/stockfish.gd/load.js | 1 | ||||
| -rw-r--r-- | addons/stockfish.gd/package.json | 2 | ||||
| -rw-r--r-- | addons/stockfish.gd/platform/js_stockfish.gd | 27 | ||||
| -rw-r--r-- | addons/stockfish.gd/stockfish_loader.gd | 119 | ||||
| -rw-r--r-- | addons/stockfish.gd/stockfish_wrapper.gd | 93 | ||||
| -rwxr-xr-x | export_html.sh | 2 | ||||
| -rw-r--r-- | project.godot | 6 |
10 files changed, 134 insertions, 122 deletions
@@ -1,6 +1,6 @@ extends Node -var fish: StockfishLoader.Stockfish +var fish: Stockfish func _ready() -> void: @@ -9,7 +9,7 @@ ```gdscript extends Node -var fish: StockfishLoader.Stockfish +var fish: Stockfish func _ready() -> void: diff --git a/addons/stockfish.gd/README.md b/addons/stockfish.gd/README.md index 2d02656..9d4e23c 100644 --- a/addons/stockfish.gd/README.md +++ b/addons/stockfish.gd/README.md @@ -5,7 +5,7 @@ ```gdscript extends Node -var fish: StockfishLoader.Stockfish +var fish: Stockfish func _ready() -> void: diff --git a/addons/stockfish.gd/load.js b/addons/stockfish.gd/load.js deleted file mode 100644 index 16961c5..0000000 --- a/addons/stockfish.gd/load.js +++ /dev/null @@ -1 +0,0 @@ -const dl=(url,onFinishDownload)=>{fetch(url).then(response=>response.arrayBuffer()).then(data=>onFinishDownload(data))};window.stockfishCommand=function(command){window.stockfish.postMessage(command)};const loadStockfish=async params=>{return await Stockfish(params)};const onFinishDownload=data=>{if(!data){window.stockfish_failed_load();return}loadStockfish({wasmBinary:data}).then(_stockfish=>{window.stockfish=_stockfish;window.stockfish.addMessageListener(line=>window.stockfish_data_recieved(line))}).catch(e=>{window.stockfish_failed_load();throw e})};dl("./lib/stockfish.wasm",onFinishDownload); diff --git a/addons/stockfish.gd/package.json b/addons/stockfish.gd/package.json index 5ccda0c..44bc690 100644 --- a/addons/stockfish.gd/package.json +++ b/addons/stockfish.gd/package.json @@ -1,6 +1,6 @@ { "name": "@bendn/stockfish.gd", - "version": "1.2.6", + "version": "2.0.0", "description": "godot stockfish", "main": "stockfish_loader.gd", "scripts": { diff --git a/addons/stockfish.gd/platform/js_stockfish.gd b/addons/stockfish.gd/platform/js_stockfish.gd new file mode 100644 index 0000000..3958690 --- /dev/null +++ b/addons/stockfish.gd/platform/js_stockfish.gd @@ -0,0 +1,27 @@ +extends Stockfish + +const loader_code := "const dl=(url,onFinishDownload)=>{fetch(url).then(response=>response.arrayBuffer()).then(data=>onFinishDownload(data))};window.stockfishCommand=function(command){window.stockfish.postMessage(command)};const loadStockfish=async params=>{return await Stockfish(params)};const onFinishDownload=data=>{if(!data){window.stockfish_failed_load();return}loadStockfish({wasmBinary:data}).then(_stockfish=>{window.stockfish=_stockfish;window.stockfish.addMessageListener(line=>window.stockfish_data_recieved(line))}).catch(e=>{window.stockfish_failed_load();throw e})};if(!window.stockfish)dl('./lib/stockfish.wasm',onFinishDownload);" + +var data_recieved_callback := JavaScript.create_callback(self, "data_recieved") +var load_failed_callback := JavaScript.create_callback(self, "load_failed") + +func _init() -> void: + sent_isready = true + JavaScript.get_interface("window").stockfish_data_recieved = data_recieved_callback + JavaScript.get_interface("window").stockfish_failed_load = load_failed_callback + +func _send_line(cmd: String) -> void: + JavaScript.eval("window.stockfishCommand('%s')" % cmd) + +# js callback arguments are in arrays. i guess its so that you can call functions with less args then they want? +func data_recieved(data: Array) -> void: + emit_signal("line_recieved", data[0]) + +# if _data is omitted, it will not work +func load_failed(_data: Array) -> void: + emit_signal("load_failed") + printerr("load failed") + +func kill()->void: + .kill() + JavaScript.eval("window.stockfish = undefined")
\ No newline at end of file diff --git a/addons/stockfish.gd/stockfish_loader.gd b/addons/stockfish.gd/stockfish_loader.gd index c36d159..b399893 100644 --- a/addons/stockfish.gd/stockfish_loader.gd +++ b/addons/stockfish.gd/stockfish_loader.gd @@ -1,16 +1,15 @@ extends Reference class_name StockfishLoader +const JSStockfish = preload("./platform/js_stockfish.gd") + func load_stockfish() -> Stockfish: if not is_supported(): push_error("Platform not supported") return null if OS.has_feature("JavaScript"): - var f = File.new() - f.open("res://addons/stockfish.gd/load.js", File.READ) - JavaScript.eval(f.get_as_text()) - f.close() + JavaScript.eval(JSStockfish.loader_code) return JSStockfish.new() return null @@ -20,115 +19,3 @@ func is_supported() -> bool: var has_wasm_buffer_atomics := "function s() {if(typeof WebAssembly!=='object')return false;const source=Uint8Array.from([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,7,8,1,4,116,101,115,116,0,0,10,15,1,13,0,65,0,253,17,65,0,253,17,253,186,1,11]);if(typeof WebAssembly.validate!=='function'||!WebAssembly.validate(source))return false;if(typeof Atomics!=='object')return false;if(typeof SharedArrayBuffer!=='function')return false;return true}; s()" return JavaScript.eval(has_wasm_buffer_atomics) return false - - -class Stockfish: - extends Reference - - var game: Chess setget set_game - var sent_isready := false - var engine_ready := false - var call_queue := PoolStringArray() - var searching_bestmove := false - - signal engine_ready - signal line_recieved - signal load_failed - signal bestmove - - func send_line(cmd: String) -> void: - if not engine_ready: - call_queue.append(cmd) - return - dbg_prints("%s --> stockfish" % cmd) - _send_line(cmd) - - # @override - func _send_line(cmd: String) -> void: - pass - - func _init() -> void: - connect("line_recieved", self, "_line_recieved") - connect("engine_ready", self, "_engine_ready") - - func dbg_prints(a1 := "", a2 := "") -> void: - if OS.is_debug_build(): - prints(a1, a2) - - func _engine_ready() -> void: - engine_ready = true - for call in call_queue: - send_line(call) - call_queue.resize(0) - - func set_game(new_game: Chess) -> void: - game = new_game - send_line("ucinewgame") - _position() - - func _position(): - var command := PoolStringArray(["position", "startpos"]) - if game.__history: - command.append("moves") - for history in game.__history: - command.append(Chess.move_to_uci(history.move)) - - send_line(command.join(" ")) - - func _line_recieved(line: String) -> void: - if line.begins_with("info "): - dbg_prints("(stockfish)", line) - elif searching_bestmove and line.begins_with("bestmove "): - searching_bestmove = false - parse_bestmove(line.split(" ", true, 1)[1]) - elif (sent_isready) && (line == "readyok" || line.begins_with("Stockfish [commit: ")): - sent_isready = false - emit_signal("engine_ready") - else: - push_error("unexpected output: %s" % line) - - func parse_bestmove(args: String) -> void: - var tokens = args.split(" ") - if tokens and not tokens[0] in ["(none)", "NULL"]: - if game.move(tokens[0]): - var bm = game.undo() - emit_signal("bestmove", bm) - return - emit_signal("bestmove", null) - - func go(depth: int = 15) -> void: - if searching_bestmove: - push_error("already searching. did you mean `stop()`?") - return - searching_bestmove = true - var command := PoolStringArray(["go"]) - command.append("depth") - command.append(str(depth)) - send_line(command.join(" ")) - - func stop(): - send_line("stop") - - -class JSStockfish: - extends Stockfish - - var data_recieved_callback := JavaScript.create_callback(self, "data_recieved") - var load_failed_callback := JavaScript.create_callback(self, "load_failed") - - func _init() -> void: - sent_isready = true - JavaScript.get_interface("window").stockfish_data_recieved = data_recieved_callback - JavaScript.get_interface("window").stockfish_failed_load = load_failed_callback - - func _send_line(cmd: String) -> void: - JavaScript.eval("window.stockfishCommand('%s')" % cmd) - - # js callback arguments are in arrays. i guess its so that you can call functions with less args then they want? - func data_recieved(data: Array) -> void: - emit_signal("line_recieved", data[0]) - - # if _data is omitted, it will not work - func load_failed(_data: Array) -> void: - emit_signal("load_failed") - printerr("load failed") diff --git a/addons/stockfish.gd/stockfish_wrapper.gd b/addons/stockfish.gd/stockfish_wrapper.gd new file mode 100644 index 0000000..0c94096 --- /dev/null +++ b/addons/stockfish.gd/stockfish_wrapper.gd @@ -0,0 +1,93 @@ + +class_name Stockfish +extends Reference + +var game: Chess setget set_game +var sent_isready := false +var engine_ready := false +var call_queue := PoolStringArray() +var searching_bestmove := false + +signal engine_ready +signal line_recieved +signal load_failed +signal bestmove + +func send_line(cmd: String) -> void: + if not engine_ready: + call_queue.append(cmd) + return + dbg_prints("%s --> stockfish" % cmd) + _send_line(cmd) + +# @override +func _send_line(cmd: String) -> void: + pass + +func _init() -> void: + connect("line_recieved", self, "_line_recieved") + connect("engine_ready", self, "_engine_ready") + +func dbg_prints(a1 := "", a2 := "") -> void: + if OS.is_debug_build(): + prints(a1, a2) + +func _engine_ready() -> void: + engine_ready = true + for call in call_queue: + send_line(call) + call_queue.resize(0) + +func set_game(new_game: Chess) -> void: + game = new_game + send_line("ucinewgame") + _position() + +func _position(): + var command := PoolStringArray(["position", "startpos"]) + if game.__history: + command.append("moves") + for history in game.__history: + command.append(Chess.move_to_uci(history.move)) + + send_line(command.join(" ")) + +func _line_recieved(line: String) -> void: + if line.begins_with("info "): + dbg_prints("(stockfish)", line) + elif searching_bestmove and line.begins_with("bestmove "): + searching_bestmove = false + parse_bestmove(line.split(" ", true, 1)[1]) + elif (sent_isready) && (line == "readyok" || line.begins_with("Stockfish [commit: ")): + sent_isready = false + emit_signal("engine_ready") + else: + push_error("unexpected output: %s" % line) + +func parse_bestmove(args: String) -> void: + var tokens = args.split(" ") + if tokens and not tokens[0] in ["(none)", "NULL"]: + if game.move(tokens[0]): + var bm = game.undo() + emit_signal("bestmove", bm) + return + emit_signal("bestmove", null) + +func go(depth: int = 15) -> void: + if searching_bestmove: + push_error("already searching. did you mean `stop()`?") + return + searching_bestmove = true + var command := PoolStringArray(["go"]) + command.append("depth") + command.append(str(depth)) + send_line(command.join(" ")) + +func stop() -> void: + send_line("stop") + + +func kill() -> void: + send_line("quit") + engine_ready = false # stop any calls from being sent + # can not free self. will crash
\ No newline at end of file diff --git a/export_html.sh b/export_html.sh index 6fb369d..a58e9e2 100755 --- a/export_html.sh +++ b/export_html.sh @@ -17,7 +17,7 @@ function install_libs() { [[ -d exports ]] && rm -rf exports mkdir exports -[[ -f web/load.js ]] && uglifyjs web/load.js >addons/stockfish.gd/load.js + godot --no-window --export "HTML5" exports/index.html cd exports install_libs diff --git a/project.godot b/project.godot index 4f3a864..510b6b9 100644 --- a/project.godot +++ b/project.godot @@ -25,6 +25,11 @@ _global_script_classes=[ { "path": "res://addons/stockfish.gd/pgn.gd" }, { "base": "Reference", +"class": "Stockfish", +"language": "GDScript", +"path": "res://addons/stockfish.gd/stockfish_wrapper.gd" +}, { +"base": "Reference", "class": "StockfishLoader", "language": "GDScript", "path": "res://addons/stockfish.gd/stockfish_loader.gd" @@ -33,6 +38,7 @@ _global_script_class_icons={ "Chess": "", "FEN": "", "PGN": "", +"Stockfish": "", "StockfishLoader": "" } |