Properly extracted chess server
This commit is contained in:
parent
1e42cf8ba2
commit
a3789e6d74
12 changed files with 304618 additions and 45 deletions
|
|
@ -9,6 +9,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|||
const __dirname = dirname(__filename);
|
||||
|
||||
function getEnginePath() {
|
||||
console.log("getEnginePath")
|
||||
// First check if path is provided through environment variable
|
||||
if (process.env.FAIRY_STOCKFISH_PATH) {
|
||||
return process.env.FAIRY_STOCKFISH_PATH;
|
||||
|
|
@ -19,15 +20,16 @@ function getEnginePath() {
|
|||
|
||||
// Navigate up to Assets/ChessEngines
|
||||
const engineBaseDir = path.join(currentDir, '..', '..');
|
||||
// console.log(engineBaseDir + '/Fairy-Stockfish' + '/src' + '/stockfish')
|
||||
|
||||
// Determine OS and set appropriate path
|
||||
if (os.platform() === 'win32') {
|
||||
// Windows path
|
||||
console.log(engineBaseDir + '/ChessEngines' + '/stockfish' + '/stockfish.exe')
|
||||
return engineBaseDir + '/ChessEngines' + '/stockfish' + '/stockfish.exe'
|
||||
} else {
|
||||
//res://Assets/ChessEngines/Fairy-Stockfish/src/stockfish
|
||||
// Unix-like systems (Linux, macOS)
|
||||
console.log(engineBaseDir + '/ChessEngines'+ '/Fairy-Stockfish' + '/src' + '/stockfish')
|
||||
return engineBaseDir + '/ChessEngines'+ '/Fairy-Stockfish' + '/src' + '/stockfish';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ class ChessEngine extends EventEmitter {
|
|||
start() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
console.log()
|
||||
this.engine = spawn(this.enginePath);
|
||||
|
||||
this.engine.stdout.on('data', (data) => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,65 @@ import express from 'express';
|
|||
import ffish from 'ffish';
|
||||
import ChessEngine from './engine.js';
|
||||
import { getEnginePath, verifyEnginePath } from './engine-path.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import os from 'os';
|
||||
|
||||
function getLogPath() {
|
||||
if (process.platform === 'linux') {
|
||||
// Use ~/.local/share/ChessBuilder/logs
|
||||
return path.join(os.homedir(), '.local', 'share', 'ChessBuilder', 'logs');
|
||||
} else if (process.platform === 'win32') {
|
||||
// Use %APPDATA%\ChessBuilder\logs
|
||||
return path.join(os.homedir(), 'AppData', 'Roaming', 'ChessBuilder', 'logs');
|
||||
} else {
|
||||
// macOS: ~/Library/Logs/ChessBuilder
|
||||
return path.join(os.homedir(), 'Library', 'Logs', 'ChessBuilder');
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure log directory exists
|
||||
const logDir = getLogPath();
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
|
||||
const logFile = path.join(logDir, 'chess-server.log');
|
||||
console.log(`Logging to: ${logFile}`);
|
||||
|
||||
// Create a write stream for logging
|
||||
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
||||
|
||||
// Redirect console.log and console.error to both console and file
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
console.log = (...args) => {
|
||||
const message = args.map(arg =>
|
||||
typeof arg === 'object' ? JSON.stringify(arg) : arg
|
||||
).join(' ') + '\n';
|
||||
|
||||
logStream.write(`[${new Date().toISOString()}] ${message}`);
|
||||
originalConsoleLog.apply(console, args);
|
||||
};
|
||||
|
||||
console.error = (...args) => {
|
||||
const message = args.map(arg =>
|
||||
typeof arg === 'object' ? JSON.stringify(arg) : arg
|
||||
).join(' ') + '\n';
|
||||
|
||||
logStream.write(`[${new Date().toISOString()}] ERROR: ${message}`);
|
||||
originalConsoleError.apply(console, args);
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
fs.writeFileSync(logFile, `Server started at ${new Date().toISOString()}\n`, { flag: 'a' });
|
||||
fs.writeFileSync(logFile, `Process ID: ${process.pid}\n`, { flag: 'a' });
|
||||
fs.writeFileSync(logFile, `Working directory: ${process.cwd()}\n`, { flag: 'a' });
|
||||
fs.writeFileSync(logFile, `Node version: ${process.version}\n`, { flag: 'a' });
|
||||
} catch (error) {
|
||||
console.error('Failed to write startup log:', error);
|
||||
}
|
||||
|
||||
const app = express();
|
||||
const port = 27531;
|
||||
|
|
@ -17,12 +76,14 @@ const CHECK_INTERVAL = 5000;
|
|||
ffish.onRuntimeInitialized = async () => {
|
||||
try {
|
||||
isReady = true;
|
||||
console.log('Fairy-Chess library ready');
|
||||
console.log('Fairy-Chess library ready asd');
|
||||
|
||||
// Get and verify engine path
|
||||
const enginePath = getEnginePath();
|
||||
console.log('ENGINE PATH', enginePath);
|
||||
|
||||
// Initialize Stockfish
|
||||
fs.chmodSync(enginePath, '755');
|
||||
engine = new ChessEngine(enginePath);
|
||||
await engine.start();
|
||||
console.log('Chess engine initialized at:', enginePath, engine.isReady);
|
||||
|
|
@ -35,7 +96,7 @@ ffish.onRuntimeInitialized = async () => {
|
|||
engine.sendCommand('setoption name UCI_LimitStrength value true');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Initialization error:', error);
|
||||
console.log('Initialization error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -369,14 +430,17 @@ app.post('/shutdown', (req, res) => {
|
|||
process.on('SIGTERM', () => {
|
||||
console.log('Shutting down...');
|
||||
closeServer()
|
||||
logStream.end();
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Received SIGINT, shutting down...');
|
||||
closeServer()
|
||||
logStream.end();
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
lastResponse = new Date().getTime()
|
||||
startIdleMonitor();
|
||||
console.log(`Fairy-Chess server running on port ${port}`);
|
||||
});
|
||||
|
|
@ -388,6 +452,7 @@ function startIdleMonitor() {
|
|||
|
||||
if (timeSinceLastResponse > SERVER_WAIT_THRESHOLD) {
|
||||
console.log(`Server idle for ${timeSinceLastResponse/1000} seconds. Shutting down...`);
|
||||
console.log(currentTime, lastResponse, timeSinceLastResponse, SERVER_WAIT_THRESHOLD)
|
||||
closeServer();
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -574,11 +574,6 @@
|
|||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
|
||||
},
|
||||
"stockfish": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stockfish/-/stockfish-16.0.0.tgz",
|
||||
"integrity": "sha512-uu4AqsE2x0tD+SsYzZy4UtqeH4U9zDi1V8loDFDbMwCtvumGXF9j81Pg1ZmIkUgZhwQqPxQAv8IN5oy6hZa7Bw=="
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"start": "node index.js",
|
||||
"test": "node test.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
|
@ -13,7 +14,6 @@
|
|||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"express": "^4.21.2",
|
||||
"ffish": "^0.7.7",
|
||||
"stockfish": "^16.0.0"
|
||||
"ffish": "^0.7.7"
|
||||
}
|
||||
}
|
||||
0
Assets/test/simple.txt
Normal file
0
Assets/test/simple.txt
Normal file
|
|
@ -2,26 +2,117 @@
|
|||
extends Node
|
||||
|
||||
var server_path: String = ""
|
||||
var log_dir: String = ""
|
||||
var log_string: String = ""
|
||||
var running := false
|
||||
var server_process_id: int = -50
|
||||
|
||||
var server_url = "http://localhost:27531"
|
||||
|
||||
func write_log(message: String):
|
||||
# First check if path is valid
|
||||
# print("Attempting to write to: ", log_dir)
|
||||
|
||||
var file = FileAccess.open(log_dir, FileAccess.WRITE_READ)
|
||||
var error = FileAccess.get_open_error()
|
||||
|
||||
if error != OK:
|
||||
# print("Failed to open log file. Error code: ", error)
|
||||
return
|
||||
|
||||
if file == null:
|
||||
# print("File handle is null")
|
||||
return
|
||||
|
||||
var timestamp = Time.get_datetime_string_from_system()
|
||||
var log_message = "[" + timestamp + "] " + message + "\n"
|
||||
log_string += log_message
|
||||
print("Writing message: ", log_message)
|
||||
file.store_string(log_string)
|
||||
|
||||
var write_error = FileAccess.get_open_error()
|
||||
if write_error != OK:
|
||||
print("Failed to write to log file. Error code: ", write_error)
|
||||
|
||||
file.close()
|
||||
func _ready():
|
||||
# Get the path to the server directory relative to the project
|
||||
server_path = ProjectSettings.globalize_path("res://Assets/ChessEngines/fairy-chess-server")
|
||||
# server_path = OS.get_executable_path().get_base_dir().path_join("Assets/ChessEngines/fairy-chess-server")
|
||||
# server_path = "res://Assets/ChessEngines/fairy-chess-server"
|
||||
# server_path = ProjectSettings.globalize_path("res://Assets/ChessEngines/fairy-chess-server")
|
||||
# if OS.has_feature("editor"):
|
||||
# server_path = ProjectSettings.globalize_path("res://Assets/ChessEngines/fairy-chess-server")
|
||||
# else:
|
||||
# # Running from an exported project.
|
||||
# # `path` will contain the absolute path to `hello.txt` next to the executable.
|
||||
# # This is *not* identical to using `ProjectSettings.globalize_path()` with a `res://` path,
|
||||
# # but is close enough in spirit.
|
||||
# server_path = OS.get_executable_path().get_base_dir().path_join("Assets/ChessEngines/fairy-chess-server")
|
||||
|
||||
server_path = extract_server_files() + "/ChessEngines/fairy-chess-server"
|
||||
setup_logging()
|
||||
check_server_files(server_path)
|
||||
start_server()
|
||||
get_tree().set_auto_accept_quit(false)
|
||||
|
||||
func write_log_dir_contents(path: String):
|
||||
var dir = DirAccess.open(path)
|
||||
if dir:
|
||||
write_log("Directory contents of: " + path)
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
while file_name != "":
|
||||
write_log(" - " + file_name)
|
||||
file_name = dir.get_next()
|
||||
else:
|
||||
write_log("ERROR: Could not open directory: " + path)
|
||||
func check_server_files(path: String):
|
||||
write_log("Checking server files...")
|
||||
write_log(path)
|
||||
|
||||
var index_path = path.path_join("index.js")
|
||||
if FileAccess.file_exists(index_path):
|
||||
write_log("index.js exists")
|
||||
var file = FileAccess.open(index_path, FileAccess.READ)
|
||||
if file:
|
||||
write_log("index.js contents:")
|
||||
# write_log(file.get_as_text())
|
||||
else:
|
||||
write_log("ERROR: Could not read index.js")
|
||||
else:
|
||||
write_log("ERROR: index.js does not exist at path: " + index_path)
|
||||
|
||||
func get_globalDir() -> String:
|
||||
|
||||
if OS.get_name() == "Linux":
|
||||
return OS.get_environment("HOME").path_join(".local/share/ChessBuilder")
|
||||
elif OS.get_name() == "Windows":
|
||||
return OS.get_environment("APPDATA").path_join("Roaming/ChessBuilder")
|
||||
else: # macOS
|
||||
return OS.get_environment("HOME").path_join("Library/ChessBuilder")
|
||||
|
||||
func setup_logging():
|
||||
var l_dir = get_globalDir() + "/logs"
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
DirAccess.make_dir_recursive_absolute(l_dir)
|
||||
|
||||
log_dir = l_dir.path_join("godot-chess.log")
|
||||
write_log("ServerManager initialized")
|
||||
|
||||
func _exit_tree():
|
||||
stop_server()
|
||||
get_tree().quit()
|
||||
|
||||
func _notification(what):
|
||||
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
||||
stop_server()
|
||||
get_tree().quit()
|
||||
func start_server() -> bool:
|
||||
if running:
|
||||
return true
|
||||
|
||||
print("Starting chess server...", server_path)
|
||||
write_log("Starting chess server... " + server_path)
|
||||
|
||||
# Make sure we're in the correct directory
|
||||
|
||||
|
|
@ -32,52 +123,71 @@ func start_server() -> bool:
|
|||
http_request.request_completed.connect(self._on_init_request_completed)
|
||||
http_request.request(server_url + "/health")
|
||||
|
||||
# var http = HTTPClient.new()
|
||||
# var err = http.connect_to_host("localhost", 27531)
|
||||
# print("is_server_running", err, OK)
|
||||
# if err != OK:
|
||||
# server_process_id = OS.create_process("node", [server_path + "/index.js"])
|
||||
|
||||
# if server_process_id <= 0:
|
||||
# printerr("Failed to start server")
|
||||
# return false
|
||||
|
||||
|
||||
# if exit_code != 0:
|
||||
# printerr("Failed to start server: ", output)
|
||||
# return false
|
||||
|
||||
return true
|
||||
|
||||
func _on_init_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||
print("*****************_on_init_request_completed************")
|
||||
write_log("*****************_on_init_request_completed************")
|
||||
var json = JSON.new()
|
||||
json.parse(body.get_string_from_utf8())
|
||||
var response = json.get_data()
|
||||
# Will print the user agent string used by the HTTPRequest node (as recognized by httpbin.org).
|
||||
print(response)
|
||||
if response == null and result != HTTPRequest.RESULT_SUCCESS:
|
||||
print("HTTP Request failed")
|
||||
# Will write_log the user agent string used by the HTTPRequest node (as recognized by httpbin.org).
|
||||
write_log("Init request completed")
|
||||
write_log("Result: " + str(response))
|
||||
if response == null:
|
||||
write_log("HTTP Request failed, starting server process")
|
||||
write_log(server_path + "/index.js")
|
||||
server_process_id = OS.create_process("node", [server_path + "/index.js"])
|
||||
|
||||
write_log("SERVER PATH " + server_path)
|
||||
if server_process_id <= 0:
|
||||
printerr("Failed to start server")
|
||||
write_log("ERROR: Failed to start server, process ID: " + str(server_process_id))
|
||||
|
||||
return false
|
||||
|
||||
running = true
|
||||
print("Chess server started")
|
||||
write_log("Chess server started with PID: " + str(server_process_id))
|
||||
return
|
||||
running = true
|
||||
if response.status != "ok":
|
||||
if response and response.status != "ok":
|
||||
print("Server error : ", response_code, json.parse(body.get_string_from_utf8()),)
|
||||
return
|
||||
|
||||
static func copy_directory_recursively(p_from: String, p_to: String) -> void:
|
||||
# Create target directory if it doesn't exist
|
||||
if not DirAccess.dir_exists_absolute(p_to):
|
||||
DirAccess.make_dir_recursive_absolute(p_to)
|
||||
|
||||
# Open source directory
|
||||
var dir = DirAccess.open(p_from)
|
||||
if dir:
|
||||
# Begin listing directory contents
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
|
||||
# Iterate through directory contents
|
||||
while file_name != "":
|
||||
# write_log(file_name + " isDIr? " + str(dir.current_is_dir()))
|
||||
if dir.current_is_dir():
|
||||
# Recursively copy subdirectories
|
||||
copy_directory_recursively(p_from.path_join(file_name), p_to.path_join(file_name))
|
||||
else:
|
||||
# Copy files
|
||||
dir.copy(p_from.path_join(file_name), p_to.path_join(file_name))
|
||||
file_name = dir.get_next()
|
||||
|
||||
func extract_server_files() -> String:
|
||||
write_log("Extracting server files from PCK...")
|
||||
var dir = get_globalDir() + "/Assets";
|
||||
copy_directory_recursively("res://Assets/", dir)
|
||||
return dir
|
||||
|
||||
|
||||
func stop_server():
|
||||
if not running:
|
||||
return
|
||||
|
||||
print("Stopping chess server...")
|
||||
write_log("Stopping chess server...")
|
||||
# Send a stop request to the server
|
||||
var headers = ["Content-Type: application/json"]
|
||||
var http = HTTPClient.new()
|
||||
|
|
@ -85,7 +195,7 @@ func stop_server():
|
|||
http.request(HTTPClient.METHOD_POST, "/shutdown", headers, data)
|
||||
|
||||
running = false
|
||||
print("Chess server stopped")
|
||||
write_log("Chess server stopped")
|
||||
|
||||
func is_server_running() -> bool:
|
||||
return running
|
||||
|
|
@ -209,7 +209,7 @@ func _on_bestmove_completed(result: int, response_code: int, headers: PackedStri
|
|||
var response = json.get_data()
|
||||
# Will print the user agent string used by the HTTPRequest node (as recognized by httpbin.org).
|
||||
print(response)
|
||||
if result != HTTPRequest.RESULT_SUCCESS:
|
||||
if response == null or result != HTTPRequest.RESULT_SUCCESS:
|
||||
print("HTTP Request failed")
|
||||
return
|
||||
|
||||
|
|
@ -239,4 +239,3 @@ func _on_request_completed(result: int, response_code: int, headers: PackedStrin
|
|||
if response.status != "ok":
|
||||
print("Server error:", response_code, json.parse(body.get_string_from_utf8()),)
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func _ready() -> void:
|
|||
|
||||
|
||||
func unhandledInput(event: InputEvent) -> void:
|
||||
print("StateMachine received input:", event)
|
||||
# print("StateMachine received input:", event)
|
||||
state.handleInput(event)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
name="Linux"
|
||||
platform="Linux"
|
||||
runnable=true
|
||||
advanced_options=false
|
||||
advanced_options=true
|
||||
dedicated_server=false
|
||||
custom_features=""
|
||||
export_filter="all_resources"
|
||||
include_filter=""
|
||||
include_filter="Assets/*"
|
||||
exclude_filter=""
|
||||
export_path="build/Linux/ChessBuilder.x86_64"
|
||||
encryption_include_filters=""
|
||||
|
|
|
|||
304401
extension_api.json
Normal file
304401
extension_api.json
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue