From b6dd13b5849fce7706973f358910ada7241f7239 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Mon, 9 Dec 2024 14:05:09 -0700 Subject: [PATCH] Update to support rapid time controls --- example_config.json5 | 29 ++++++++++++++++++-- schema/Config.schema.json | 26 ++++++++++++++++-- src/Bot.ts | 2 +- src/Game.ts | 35 +++++++++++++++-------- src/PvOutputParser.ts | 8 +++--- src/config.ts | 39 +++++++++++++++++++++++--- src/main.ts | 58 +++++++++++++++++++++++++++++++++++---- src/pools.ts | 7 ++++- src/socket.ts | 2 +- src/types.ts | 4 ++- 10 files changed, 178 insertions(+), 32 deletions(-) diff --git a/example_config.json5 b/example_config.json5 index 8b22be0d..474d7587 100644 --- a/example_config.json5 +++ b/example_config.json5 @@ -219,12 +219,35 @@ /** * Allowed blitz times for the bot. Blitz is disabled by default, but you - * can enable it by providing accetpable time settings. + * can enable it by providing acceptable time settings. * * @default null */ // allowed_blitz_settings: null, + /** Allowed rapid game times for bot. */ + /* + allowed_rapid_settings: { + simple: { + per_move_time_range: [5, 30], + }, + + byoyomi: { + main_time_range: [0, 3600], + period_time_range: [5, 30], + periods_range: [1, 10], + }, + + fischer: { + initial_time_range: [5, 600], + max_time_range: [5, 7200], + time_increment_range: [3, 30], + }, + + concurrent_games: 3, + }, + */ + /** Allowed live game times for bot. */ /* allowed_live_settings: { @@ -239,7 +262,8 @@ }, fischer: { - max_time_range: [30, 600], + initial_time_range: [10, 3600], + max_time_range: [5, 7200], time_increment_range: [10, 300], }, @@ -261,6 +285,7 @@ }, fischer: { + initial_time_range: [86400, 604800], max_time_range: [86400, 604800], time_increment_range: [43200, 604800], }, diff --git a/schema/Config.schema.json b/schema/Config.schema.json index f7d43463..90d5babd 100644 --- a/schema/Config.schema.json +++ b/schema/Config.schema.json @@ -111,6 +111,17 @@ "description": "Allowed blitz times for the bot. Blitz is disabled by default, but you can enable it by providing acceptable time settings.", "default": null }, + "allowed_rapid_settings": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/definitions/TimeControlRanges" + } + ], + "description": "Allowed rapid game times for bot." + }, "allowed_live_settings": { "anyOf": [ { @@ -552,16 +563,26 @@ "fischer": { "type": "object", "properties": { - "max_time_range": { + "initial_time_range": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2, - "description": "Range of acceptable main times in seconds.", + "description": "Range of acceptable initial times in seconds.", "default": "[30, 600] for live games, [86400, 604800] for correspondence games" }, + "max_time_range": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2, + "description": "Range of acceptable max times in seconds.", + "default": "[5, 7200] for live games, [86400, 604800] for correspondence games" + }, "time_increment_range": { "type": "array", "items": { @@ -574,6 +595,7 @@ } }, "required": [ + "initial_time_range", "max_time_range", "time_increment_range" ], diff --git a/src/Bot.ts b/src/Bot.ts index 07bfcc17..05f5f6c8 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -4,7 +4,7 @@ import { ignore_promise } from "./util"; import * as split2 from "split2"; import { Move } from "./types"; -import { decodeMoves } from "goban/src/GoMath"; +import { decodeMoves } from "goban-engine"; import { config, BotConfig } from "./config"; import { PvOutputParser } from "./PvOutputParser"; import { socket } from "./socket"; diff --git a/src/Game.ts b/src/Game.ts index a8ab1ce7..194f7b00 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -1,6 +1,6 @@ -import { decodeMoves } from "goban/src/GoMath"; -import { GoEngineConfig } from "goban/src/GoEngine"; -import { GameChatAnalysisMessage } from "goban/src/protocol"; +import { decodeMoves } from "goban-engine"; +import { GobanEngineConfig } from "goban-engine"; +import { protocol } from "goban-engine"; import { move2gtpvertex, ignore_promise } from "./util"; import { Move } from "./types"; import { Bot } from "./Bot"; @@ -23,7 +23,7 @@ export class Game extends EventEmitter { connect_timeout: ReturnType; game_id: number; - state: GoEngineConfig; + state: GobanEngineConfig; opponent_evenodd: null | number; greeted: boolean; startup_timestamp: number; @@ -387,6 +387,7 @@ export class Game extends EventEmitter { const promises: Promise[] = []; this.verbose("Releasing bot(s)"); + this.verbose("Bot count available: " + bot_pools.main.countAvailable()); if (this.bot) { const bot = this.bot; @@ -394,14 +395,26 @@ export class Game extends EventEmitter { const using_opening_bot = this.using_opening_bot; promises.push( new Promise((resolve, _reject) => { + console.log("Will release bots in ", bot.bot_config.release_delay, "ms"); setTimeout(() => { - bot.off("chat"); - if (using_opening_bot) { - bot_pools.opening.release(bot); - } else { - bot_pools.main.release(bot); + try { + console.log("Releasing bot"); + bot.off("chat"); + if (using_opening_bot) { + console.log("Releasing opening bot"); + bot_pools.opening.release(bot); + } else { + console.log("Releasing main bot"); + bot_pools.main.release(bot); + console.log( + "released, Bot count available: " + + bot_pools.main.countAvailable(), + ); + } + resolve(); + } catch (e) { + console.error("Error releasing bot", e); } - resolve(); }, bot.bot_config.release_delay); }), ); @@ -780,7 +793,7 @@ export class Game extends EventEmitter { return `${color} ${player.username} [${this.state.width}x${this.state.height}] ${handicap}`; } sendChat( - msg: string | TranslatableString | GameChatAnalysisMessage, + msg: string | TranslatableString | protocol.GameChatAnalysisMessage, move_number?: number, channel: "main" | "malkovich" = "main", ): void { diff --git a/src/PvOutputParser.ts b/src/PvOutputParser.ts index 71b706d1..2b2c805d 100644 --- a/src/PvOutputParser.ts +++ b/src/PvOutputParser.ts @@ -1,6 +1,6 @@ -import { char2num, num2char } from "goban/src/GoMath"; +import { char2num, num2char } from "goban-engine"; //import { gtpchar2num } from "./Bot"; -import { GameChatAnalysisMessage } from "goban/src/protocol"; +import { protocol } from "goban-engine"; import { Game } from "./Game"; import { trace } from "./trace"; @@ -50,7 +50,7 @@ export class PvOutputParser { name: string, pv_move: string, engine_analysis: EngineAnalysis, - ): GameChatAnalysisMessage { + ): protocol.GameChatAnalysisMessage { return { type: "analysis", engine_analysis: engine_analysis, @@ -134,7 +134,7 @@ export class PvOutputParser { LEELA : this.postPvToChatSingleLine, */ - let message: GameChatAnalysisMessage = null; + let message: protocol.GameChatAnalysisMessage | null = null; switch (this.detected_engine) { case "katago": { diff --git a/src/config.ts b/src/config.ts index 54988ad6..13e6dfb6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -104,6 +104,10 @@ export interface Config { */ allowed_blitz_settings?: null | TimeControlRanges; + /** Allowed rapid game times for bot. + */ + allowed_rapid_settings?: null | TimeControlRanges; + /** Allowed live game times for bot. * * @default {"per_move_time_range": [10, 300], "main_time_range": [0, 3600], "periods_range": [1, 10]} @@ -201,7 +205,7 @@ export interface Config { /** Config version for internal use * @hidden */ - _config_version?: number; + _config_version?: 2; } export interface TimeControlRanges { @@ -233,9 +237,14 @@ export interface TimeControlRanges { /** Time control settings for fischer clocks */ fischer?: { - /** Range of acceptable main times in seconds. + /** Range of acceptable initial times in seconds. * @default [30, 600] for live games, [86400, 604800] for correspondence games */ + initial_time_range: [number, number]; + + /** Range of acceptable max times in seconds. + * @default [5, 7200] for live games, [86400, 604800] for correspondence games + */ max_time_range: [number, number]; /** range of acceptable times for the time increment @@ -382,6 +391,26 @@ function defaults(): Config { status_update_frequency: 60000, allowed_time_control_systems: ["fischer", "byoyomi", "simple"], allowed_blitz_settings: null, + + allowed_rapid_settings: { + simple: { + per_move_time_range: [5, 30], + }, + + byoyomi: { + main_time_range: [0, 3600], + period_time_range: [5, 30], + periods_range: [1, 10], + }, + + fischer: { + initial_time_range: [5, 600], + max_time_range: [5, 7200], + time_increment_range: [3, 30], + }, + + concurrent_games: 3, + }, allowed_live_settings: { simple: { per_move_time_range: [10, 300], @@ -394,7 +423,8 @@ function defaults(): Config { }, fischer: { - max_time_range: [30, 600], + initial_time_range: [10, 3600], + max_time_range: [5, 7200], time_increment_range: [10, 300], }, @@ -412,6 +442,7 @@ function defaults(): Config { }, fischer: { + initial_time_range: [86400, 604800], max_time_range: [86400, 604800], time_increment_range: [43200, 604800], }, @@ -657,7 +688,7 @@ function load_config_or_throw(): Config { //console.info(yargs.argv); //console.info(with_defaults); - with_defaults._config_version = 1; + with_defaults._config_version = 2; return sanity_check_and_patch_config(with_defaults); } diff --git a/src/main.ts b/src/main.ts index 06ec4f30..04209987 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,7 +8,7 @@ import { trace } from "./trace"; import { post, api1 } from "./util"; import { Game, handleChatLine } from "./Game"; import { bot_pools } from "./pools"; -import { JGOFTimeControl } from "goban/src/JGOF"; +import { JGOFTimeControl, protocol } from "goban-engine"; import { Speed } from "./types"; //process.title = `gtp2ogs ${config.bot_command.join(" ")}`; @@ -39,6 +39,8 @@ interface RejectionDetails { | "ranked_not_allowed" | "blitz_not_allowed" | "too_many_blitz_games" + | "rapid_not_allowed" + | "too_many_rapid_games" | "live_not_allowed" | "too_many_live_games" | "correspondence_not_allowed" @@ -125,7 +127,12 @@ class Main { config.bot_id = this.bot_id; trace.info("Bot is username: ", this.bot_username); trace.info("Bot is user id: ", this.bot_id); - socket.send("bot/config", config); + const config_v2: protocol.BotConfigV2 = { + hidden: false, + ...config, + _config_version: 2, + } as protocol.BotConfigV2; + socket.send("bot/config", config_v2); }, ); }); @@ -135,7 +142,11 @@ class Main { config.username = this.bot_username; if (socket.connected) { - socket.send("bot/config", config); + socket.send("bot/config", { + hidden: false, + ...config, + _config_version: 2, + } as protocol.BotConfigV2); } }); @@ -220,11 +231,12 @@ class Main { } dumpStatus() { const blitz_count = this.countGames("blitz"); + const rapid_count = this.countGames("rapid"); const live_count = this.countGames("live"); const corr_count = this.countGames("correspondence"); trace.info( - `Status: playing ${blitz_count} blitz, ${live_count} live, ${corr_count} correspondence games`, + `Status: playing ${blitz_count} blitz, ${rapid_count} rapid, ${live_count} live, ${corr_count} correspondence games`, ); let str = "Bot status: "; @@ -244,12 +256,14 @@ class Main { private sendStatusUpdate() { const update = { ongoing_blitz_count: this.countGames("blitz"), + ongoing_rapid_count: this.countGames("rapid"), ongoing_live_count: this.countGames("live"), ongoing_correspondence_count: this.countGames("correspondence"), }; if ( update["ongoing_blitz_count"] !== this.last_status_update["ongoing_blitz_count"] || + update["ongoing_rapid_count"] !== this.last_status_update["ongoing_rapid_count"] || update["ongoing_live_count"] !== this.last_status_update["ongoing_live_count"] || update["ongoing_correspondence_count"] !== this.last_status_update["ongoing_correspondence_count"] @@ -532,6 +546,28 @@ class Main { } break; + case "rapid": + if (!config.allowed_rapid_settings?.concurrent_games) { + return { + message: `This bot does not play rapid games.`, + rejection_code: "rapid_not_allowed", + details: {}, + }; + } + if (count >= (config.allowed_rapid_settings?.concurrent_games || 0)) { + return { + message: `This bot is already playing ${count} of ${ + config.allowed_rapid_settings?.concurrent_games || 0 + } allowed rapid games.`, + rejection_code: "too_many_rapid_games", + details: { + count, + allowed: config.allowed_rapid_settings?.concurrent_games || 0, + }, + }; + } + break; + case "live": if (!config.allowed_live_settings?.concurrent_games) { return { @@ -601,6 +637,18 @@ class Main { } settings = config.allowed_blitz_settings; break; + + case "rapid": + if (!config.allowed_rapid_settings) { + return { + message: `This bot does not play rapid games.`, + rejection_code: "rapid_not_allowed", + details: {}, + }; + } + settings = config.allowed_rapid_settings; + break; + case "live": if (!config.allowed_live_settings) { return { @@ -754,7 +802,7 @@ class Main { } checkGamesPerPlayer(player_id: number): RejectionDetails | undefined { trace.log("Max games per player: ", config.max_games_per_player); - config; + //config; if (config.max_games_per_player) { const game_count = Object.keys(this.connected_games).filter((game_id) => { const state = this.connected_games[game_id]?.state; diff --git a/src/pools.ts b/src/pools.ts index 511d3973..a102e60b 100644 --- a/src/pools.ts +++ b/src/pools.ts @@ -120,7 +120,7 @@ export class BotPoolManager extends EventEmitter implements BotManagerIn bot.setGame(null); if (this.queue.length > 0) { for (const pass of ["board_size", "any"]) { - for (const target_speed of ["blitz", "live", "correspondence"]) { + for (const target_speed of ["blitz", "rapid", "live", "correspondence"]) { for (let i = 0; i < this.queue.length; i++) { const [speed, width, height, resolve] = this.queue[i]; @@ -131,6 +131,9 @@ export class BotPoolManager extends EventEmitter implements BotManagerIn pass === "any"; if (speed === target_speed && pass_check) { + this.log( + `Releasing bot back to the ${speed} pool, queue length: ${this.queue.length}`, + ); this.queue.splice(i, 1); resolve(bot); return; @@ -229,6 +232,8 @@ export class PersistentBotManager extends EventEmitter implements BotMan } release(bot: Bot): void { + trace.info("[persistent] Releasing bot, queue length: " + this.queue.length); + if (bot.persistent_idle_timeout) { clearTimeout(bot.persistent_idle_timeout); } diff --git a/src/socket.ts b/src/socket.ts index 5fd64ec2..2c0a743f 100644 --- a/src/socket.ts +++ b/src/socket.ts @@ -1,5 +1,5 @@ import { config } from "./config"; -import { GobanSocket } from "goban/src/GobanSocket"; +import { GobanSocket } from "goban-engine"; import { trace } from "./trace"; global.performance = global.performance || (Date as any); diff --git a/src/types.ts b/src/types.ts index cd0ea0ba..50d0205e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { JGOFTimeControlSpeed } from "goban-engine"; + export interface Move { x?: number; y?: number; @@ -6,4 +8,4 @@ export interface Move { pass?: boolean; } -export type Speed = "blitz" | "live" | "correspondence"; +export type Speed = JGOFTimeControlSpeed;