Properly extracted chess server

This commit is contained in:
2ManyProjects 2025-02-20 20:55:03 -06:00
parent 1e42cf8ba2
commit a3789e6d74
12 changed files with 304618 additions and 45 deletions

View file

@ -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';
}
}

View file

@ -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) => {

View file

@ -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();
}
};

View file

@ -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",

View file

@ -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
View file

View 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

View file

@ -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

View file

@ -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)

View file

@ -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

File diff suppressed because it is too large Load diff