diff --git a/example_config.json5 b/example_config.json5 index fce76f81..a7bd7bd2 100644 --- a/example_config.json5 +++ b/example_config.json5 @@ -51,8 +51,6 @@ /** Send the principal variation (PV) values. Note that your bot must output this * data in a way that can be parsed. * - * See `pv_format` for more details on formatting and parsing PV values . - * * @default true */ // send_pv_data: true, @@ -192,21 +190,43 @@ /** Allowed live game times for bot. */ /* allowed_live_settings: { - per_move_time_range: [10, 300], - main_time_range: [0, 3600], - periods_range: [1, 10], + simple: { + per_move_time_range: [10, 300], + }, + + byoyomi: { + main_time_range: [0, 3600], + period_time_range: [10, 300], + periods_range: [1, 10], + }, + + fischer: { + max_time_range: [30, 600], + time_increment_range: [10, 300], + }, + + concurrent_games: 3, }, */ - /** Allowed correspondence game times for bot. - * - * @default {"per_move_time_range": [43200, 259200], "main_time_range": [0, 86400], "periods_range": [1, 10]} - */ + /** Allowed correspondence game times for bot. */ /* allowed_correspondence_settings: { - per_move_time_range: [43200, 259200], - main_time_range: [0, 86400], - periods_range: [1, 10], + simple: { + per_move_time_range: [43200, 604800], + }, + + byoyomi: { + main_time_range: [0, 604800], + period_time_range: [43200, 604800], + periods_range: [1, 10], + }, + + fischer: { + max_time_range: [86400, 604800], + time_increment_range: [43200, 604800], + }, + concurrent_games: 500, }, */ @@ -222,6 +242,11 @@ */ // allow_unranked: true, + /** Allowed ranked games + * @default true + */ + // allow_ranked: true, + /** +- the number of ranks allowed to play against this bot. Note that * ranked games are always limited to +-9. 0 to disable rank restrictions. * @default 0 @@ -233,23 +258,43 @@ */ // allow_handicap: true, + /** Allow handicap games for unranked games + * @default true + */ + // allow_unranked_handicap: true, + + /** Allowed komi range. Negative numbers indicate reverse Komi. + * @default [-99, 99] + */ + // allowed_komi_range: [-99, 99], + + /** Allowed komi range + * @default true + */ + // allowed_komi_range: true, + /** Minimum rank to accept games from * @default 0 * @minimum 0 * @maximum 35 */ - //min_rank: 0, + // min_rank: 0, /** Hide the bot from the public bot list * @default false */ - //hidden: false, + // hidden: false, + + /** Decline all new challenges. This implies hidden. + * @default false + */ + // decline_new_challenges: false, /** Used for debugging, will issue a showboard command when we've loaded * the board state into the bot * @default false */ - //showboard: false, + // showboard: false, /** If set, bot moves will be delayed when made before `min_move_time` ms. * This is primarily a user experience thing as can make players feel rushed @@ -259,6 +304,11 @@ */ // min_move_time: 1500, + /** Maximum amount of ongoing games to allow concurrently by the same player + * @default 1 + */ + // max_games_per_player: 1, + /** Enable verbose logging. * @values 0-2 * @default 0 diff --git a/schema/Config.schema.json b/schema/Config.schema.json index b9d2d594..aa227bf7 100644 --- a/schema/Config.schema.json +++ b/schema/Config.schema.json @@ -204,9 +204,27 @@ }, "allow_handicap": { "type": "boolean", - "description": "Allow handicap games", + "description": "Allow handicap games for ranked games", "default": true }, + "allow_unranked_handicap": { + "type": "boolean", + "description": "Allow handicap games for unranked games", + "default": true + }, + "allowed_komi_range": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2, + "description": "Allowed komi range. Negative numbers indicate reverse Komi.", + "default": [ + -99, + 99 + ] + }, "hidden": { "type": "boolean", "description": "Hide the bot from the public bot list", @@ -397,38 +415,101 @@ "TimeControlRanges": { "type": "object", "properties": { - "per_move_time_range": { - "type": "array", - "items": { - "type": "number" + "simple": { + "type": "object", + "properties": { + "per_move_time_range": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2, + "description": "Range of acceptable times per period in seconds", + "default": "[10, 300] for live, [43200, 259200] for correspondence" + } }, - "minItems": 2, - "maxItems": 2, - "description": "Range of acceptable times per move. This is: - The period time in byo-yomi - The time increment and minimum move time in Fischer - The time per move in simple time", - "default": "[10, 300] for live, [43200, 259200] for correspondence" + "required": [ + "per_move_time_range" + ], + "additionalProperties": false, + "description": "Time control settings for Simple clocks" }, - "main_time_range": { - "type": "array", - "items": { - "type": "number" + "byoyomi": { + "type": "object", + "properties": { + "main_time_range": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2, + "description": "Range of acceptable main times in seconds.", + "default": "[0, 3600] for live games, [0, 259200] for correspondence games" + }, + "period_time_range": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2, + "description": "Range of acceptable times per period in seconds", + "default": "[10, 300] for live, [43200, 259200] for correspondence" + }, + "periods_range": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2, + "description": "Range of acceptable number of periods.", + "default": [ + 1, + 10 + ] + } }, - "minItems": 2, - "maxItems": 2, - "description": "Range of acceptable main times in seconds. This is only applicable for byo-yomi", - "default": "[0, 3600] for live games, [0, 86400] for correspondence games" + "required": [ + "main_time_range", + "period_time_range", + "periods_range" + ], + "additionalProperties": false, + "description": "Time control settings for byo-yomi clocks" }, - "periods_range": { - "type": "array", - "items": { - "type": "number" + "fischer": { + "type": "object", + "properties": { + "max_time_range": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2, + "description": "Range of acceptable main times in seconds.", + "default": "[30, 600] for live games, [86400, 604800] for correspondence games" + }, + "time_increment_range": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2, + "description": "range of acceptable times for the time increment", + "default": "[10, 300] for live, [43200, 259200] for correspondence" + } }, - "minItems": 2, - "maxItems": 2, - "description": "Range of acceptable number of periods. This is only applicable for byo-yomi", - "default": [ - 1, - 10 - ] + "required": [ + "max_time_range", + "time_increment_range" + ], + "additionalProperties": false, + "description": "Time control settings for fischer clocks" }, "concurrent_games": { "type": "number", @@ -437,9 +518,6 @@ } }, "required": [ - "per_move_time_range", - "main_time_range", - "periods_range", "concurrent_games" ], "additionalProperties": false diff --git a/src/config.ts b/src/config.ts index a06b66e0..c3a2388f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -135,11 +135,21 @@ export interface Config { */ allowed_rank_range?: [number, number]; - /** Allow handicap games + /** Allow handicap games for ranked games * @default true */ allow_handicap?: boolean; + /** Allow handicap games for unranked games + * @default true + */ + allow_unranked_handicap?: boolean; + + /** Allowed komi range. Negative numbers indicate reverse Komi. + * @default [-99, 99] + */ + allowed_komi_range?: [number, number]; + /** Hide the bot from the public bot list * @default false */ @@ -189,24 +199,44 @@ export interface Config { } export interface TimeControlRanges { - /** Range of acceptable times per move. This is: - * - The period time in byo-yomi - * - The time increment and minimum move time in Fischer - * - The time per move in simple time - * - * @default [10, 300] for live, [43200, 259200] for correspondence - */ - per_move_time_range: [number, number]; + /** Time control settings for Simple clocks */ + simple?: { + /** Range of acceptable times per period in seconds + * @default [10, 300] for live, [43200, 259200] for correspondence + */ + per_move_time_range: [number, number]; + }; - /** Range of acceptable main times in seconds. This is only applicable for byo-yomi - * @default [0, 3600] for live games, [0, 86400] for correspondence games - */ - main_time_range: [number, number]; + /** Time control settings for byo-yomi clocks */ + byoyomi?: { + /** Range of acceptable main times in seconds. + * @default [0, 3600] for live games, [0, 259200] for correspondence games + */ + main_time_range: [number, number]; + + /** Range of acceptable times per period in seconds + * @default [10, 300] for live, [43200, 259200] for correspondence + */ + period_time_range: [number, number]; + + /** Range of acceptable number of periods. + * @default [1, 10] + */ + periods_range: [number, number]; + }; - /** Range of acceptable number of periods. This is only applicable for byo-yomi - * @default [1, 10] - */ - periods_range: [number, number]; + /** Time control settings for fischer clocks */ + fischer?: { + /** Range of acceptable main times in seconds. + * @default [30, 600] for live games, [86400, 604800] for correspondence games + */ + max_time_range: [number, number]; + + /** range of acceptable times for the time increment + * @default [10, 300] for live, [43200, 259200] for correspondence + */ + time_increment_range: [number, number]; + }; /** Concurrent games to allow for this speed bracket * @default 1, 3, 500 for blitz, live, correspondence respectively @@ -304,15 +334,38 @@ function defaults(): Config { allowed_time_control_systems: ["fischer", "byoyomi", "simple"], allowed_blitz_settings: null, allowed_live_settings: { - per_move_time_range: [10, 300], - main_time_range: [0, 3600], - periods_range: [1, 10], + simple: { + per_move_time_range: [10, 300], + }, + + byoyomi: { + main_time_range: [0, 3600], + period_time_range: [10, 300], + periods_range: [1, 10], + }, + + fischer: { + max_time_range: [30, 600], + time_increment_range: [10, 300], + }, + concurrent_games: 3, }, allowed_correspondence_settings: { - per_move_time_range: [43200, 259200], - main_time_range: [0, 86400 * 30], - periods_range: [1, 10], + simple: { + per_move_time_range: [43200, 604800], + }, + + byoyomi: { + main_time_range: [0, 604800], + period_time_range: [43200, 604800], + periods_range: [1, 10], + }, + + fischer: { + max_time_range: [86400, 604800], + time_increment_range: [43200, 604800], + }, concurrent_games: 500, }, @@ -321,6 +374,8 @@ function defaults(): Config { allow_unranked: true, allowed_rank_range: [0, 99], allow_handicap: true, + allow_unranked_handicap: true, + allowed_komi_range: [-99, 99], hidden: false, decline_new_challenges: false, min_move_time: 1500, diff --git a/src/main.ts b/src/main.ts index 95864ec8..d5463b5a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -48,10 +48,12 @@ interface RejectionDetails { | "period_time_out_of_range" | "periods_out_of_range" | "main_time_out_of_range" + | "max_time_out_of_range" | "per_move_time_out_of_range" | "player_rank_out_of_range" | "not_accepting_new_challenges" - | "too_many_games_for_player"; + | "too_many_games_for_player" + | "komi_out_of_range"; details: { [key: string]: any; }; @@ -282,11 +284,12 @@ class Main { this.checkTimeControl(notification.time_control) || this.checkConcurrentGames(notification.time_control.speed) || this.checkBoardSize(notification.width, notification.height) || - this.checkHandicap(notification.handicap) || + this.checkHandicap(notification.ranked, notification.handicap) || this.checkRanked(notification.ranked) || this.checkAllowedRank(notification.ranked, notification.min_ranking) || this.checkDeclineChallenges() || this.checkGamesPerPlayer(notification.user?.id) || + this.checkKomi(notification.komi) || undefined; if (this.checkWhitelist(notification.user)) { @@ -387,8 +390,8 @@ class Main { return undefined; } - checkHandicap(handicap: number): RejectionDetails | undefined { - if (!config.allow_handicap && handicap !== 0) { + checkHandicap(ranked: number, handicap: number): RejectionDetails | undefined { + if (!(ranked ? config.allow_handicap : config.allow_unranked_handicap) && handicap !== 0) { return { message: `This bot only plays games with no handicap.`, rejection_code: "handicap_not_allowed", @@ -536,15 +539,28 @@ class Main { switch (time_control.system) { case "fischer": if ( - time_control.time_increment < settings.per_move_time_range[0] || - time_control.time_increment > settings.per_move_time_range[1] + time_control.max_time < settings.fischer.max_time_range[0] || + time_control.max_time > settings.fischer.max_time_range[1] + ) { + return { + message: `Time increment is out of acceptable range`, + rejection_code: "max_time_out_of_range", + details: { + time_increment: time_control.time_increment, + range: settings.fischer.time_increment_range, + }, + }; + } + if ( + time_control.time_increment < settings.fischer.time_increment_range[0] || + time_control.time_increment > settings.fischer.time_increment_range[1] ) { return { message: `Time increment is out of acceptable range`, rejection_code: "time_increment_out_of_range", details: { time_increment: time_control.time_increment, - range: settings.per_move_time_range, + range: settings.fischer.time_increment_range, }, }; } @@ -552,41 +568,41 @@ class Main { case "byoyomi": if ( - time_control.period_time < settings.per_move_time_range[0] || - time_control.period_time > settings.per_move_time_range[1] + time_control.period_time < settings.byoyomi.period_time_range[0] || + time_control.period_time > settings.byoyomi.period_time_range[1] ) { return { message: `Period time is out of acceptable range`, rejection_code: "period_time_out_of_range", details: { period_time: time_control.period_time, - range: settings.per_move_time_range, + range: settings.byoyomi.period_time_range, }, }; } if ( - time_control.periods < settings.periods_range[0] || - time_control.periods > settings.periods_range[1] + time_control.periods < settings.byoyomi.periods_range[0] || + time_control.periods > settings.byoyomi.periods_range[1] ) { return { message: `Periods is out of acceptable range`, rejection_code: "periods_out_of_range", details: { periods: time_control.periods, - range: settings.periods_range, + range: settings.byoyomi.periods_range, }, }; } if ( - time_control.main_time < settings.main_time_range[0] || - time_control.main_time > settings.main_time_range[1] + time_control.main_time < settings.byoyomi.main_time_range[0] || + time_control.main_time > settings.byoyomi.main_time_range[1] ) { return { message: `Main time is out of acceptable range`, rejection_code: "main_time_out_of_range", details: { main_time: time_control.main_time, - range: settings.main_time_range, + range: settings.byoyomi.main_time_range, }, }; } @@ -594,15 +610,15 @@ class Main { case "simple": if ( - time_control.per_move < settings.per_move_time_range[0] || - time_control.per_move > settings.per_move_time_range[1] + time_control.per_move < settings.simple.per_move_time_range[0] || + time_control.per_move > settings.simple.per_move_time_range[1] ) { return { message: `Per move time is out of acceptable range`, rejection_code: "per_move_time_out_of_range", details: { per_move_time: time_control.per_move, - range: settings.per_move_time_range, + range: settings.simple.per_move_time_range, }, }; } @@ -668,6 +684,17 @@ class Main { } } } + checkKomi(komi: number): RejectionDetails | undefined { + if (komi < config.allowed_komi_range[0] || komi > config.allowed_komi_range[1]) { + return { + rejection_code: "komi_out_of_range", + details: { + allowed_komi_range: config.allowed_komi_range, + }, + message: `Komi is out of acceptable range`, + }; + } + } terminate() { clearTimeout(this.connect_timeout);