From dba3f4bb6a9539d486b1977e2722079647844281 Mon Sep 17 00:00:00 2001 From: Akita Noek Date: Mon, 17 Apr 2023 13:40:33 -0600 Subject: [PATCH] Add game and board size affinity for bots --- src/Bot.ts | 10 ++++++++++ src/Game.ts | 21 ++++++++++++++++++--- src/pools.ts | 53 +++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/Bot.ts b/src/Bot.ts index 5312f1f6..a33759a0 100644 --- a/src/Bot.ts +++ b/src/Bot.ts @@ -60,6 +60,11 @@ export class Bot extends EventEmitter { /** True if we are available for use by a Game. This flag is managed by the pool. */ available: boolean = true; + /* These are afinity fields used by our pool to try and select good bots to use */ + last_game_id: number = -1; + last_width: number = -1; + last_height: number = -1; + log: (...arr: any[]) => any; info: (...arr: any[]) => any; trace: (...arr: any[]) => any; @@ -561,6 +566,11 @@ export class Bot extends EventEmitter { this.katafischer = false; } + // Update our afinity fields + this.last_game_id = state.game_id; + this.last_width = state.width; + this.last_height = state.height; + if (state.width === state.height) { await this.command(`boardsize ${state.width}`); } else { diff --git a/src/Game.ts b/src/Game.ts index d873b386..164706cf 100644 --- a/src/Game.ts +++ b/src/Game.ts @@ -386,11 +386,21 @@ export class Game extends EventEmitter { config.opening_bot.number_of_opening_moves_to_play >= this.state.moves.length ) { this.verbose("Acquiring opening bot instance"); - this.bot = await bot_pools.opening.acquire(this.state.time_control.speed); + this.bot = await bot_pools.opening.acquire( + this.state.time_control.speed, + parseInt(this.state.width.toString()), + parseInt(this.state.height.toString()), + parseInt(this.state.game_id.toString()), + ); this.using_opening_bot = true; } else { this.verbose("Acquiring main bot instance"); - this.bot = await bot_pools.main.acquire(this.state.time_control.speed); + this.bot = await bot_pools.main.acquire( + this.state.time_control.speed, + parseInt(this.state.width.toString()), + parseInt(this.state.height.toString()), + parseInt(this.state.game_id.toString()), + ); this.using_opening_bot = false; } this.bot.setGame(this); @@ -415,7 +425,12 @@ export class Game extends EventEmitter { `[game ${this.game_id}] Acquiring ending bot: ${this.state.moves.length} moves played out of ${move_to_start_checking_ending_bot} necessary to begin consulting ending bot`, ); - this.ending_bot = await bot_pools.ending.acquire(this.state.time_control.speed); + this.ending_bot = await bot_pools.ending.acquire( + this.state.time_control.speed, + parseInt(this.state.width.toString()), + parseInt(this.state.height.toString()), + parseInt(this.state.game_id.toString()), + ); this.ending_bot.verbose(`[game ${this.game_id}] Acquired resign bot instance`); this.ending_bot.setGame(this); diff --git a/src/pools.ts b/src/pools.ts index 3a78b966..586c8eff 100644 --- a/src/pools.ts +++ b/src/pools.ts @@ -13,7 +13,7 @@ export class BotPool extends EventEmitter { pool_name: string; bot_config: BotConfig; instances: Bot[] = []; - queue: [Speed, (bot: Bot) => void][] = []; + queue: [Speed, number, number, (bot: Bot) => void][] = []; ready: Promise; log: (...arr: any[]) => any; verbose: (...arr: any[]) => any; @@ -70,18 +70,32 @@ export class BotPool extends EventEmitter { }); } - async acquire(speed: Speed): Promise { + async acquire(speed: Speed, width: number, height: number, game_id: number): Promise { trace.info(`Acquiring bot for ${speed} game`); await this.ready; - for (let i = 0; i < this.instances.length; i++) { - const bot = this.instances[i]; - if (bot.available) { - bot.available = false; - return bot; + for (const pass of ["game_id", "board_size", "any"]) { + for (let i = 0; i < this.instances.length; i++) { + const bot = this.instances[i]; + + /* We prioritize instances that have been playing this game, are already + * setup to play on this board size, or finally any that are available. + */ + const pass_check = + (pass === "game_id" && bot.last_game_id === game_id) || + (pass === "board_size" && + bot.last_width === width && + bot.last_height === height) || + pass === "any"; + + if (bot.available && pass_check) { + bot.available = false; + trace.info("Picked bot in " + pass + " pass"); + return bot; + } } } return new Promise((resolve) => { - this.queue.push([speed, resolve]); + this.queue.push([speed, width, height, resolve]); }); } @@ -92,13 +106,22 @@ export class BotPool extends EventEmitter { release(bot: Bot): void { bot.setGame(null); if (this.queue.length > 0) { - for (const target_speed of ["blitz", "live", "correspondence"]) { - for (let i = 0; i < this.queue.length; i++) { - const [speed, resolve] = this.queue[i]; - if (speed === target_speed) { - this.queue.splice(i, 1); - resolve(bot); - return; + for (const pass of ["board_size", "any"]) { + for (const target_speed of ["blitz", "live", "correspondence"]) { + for (let i = 0; i < this.queue.length; i++) { + const [speed, width, height, resolve] = this.queue[i]; + + const pass_check = + (pass === "board_size" && + bot.last_width === width && + bot.last_height === height) || + pass === "any"; + + if (speed === target_speed && pass_check) { + this.queue.splice(i, 1); + resolve(bot); + return; + } } } }