diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 42a9f85cf8..cac7699074 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -23,6 +23,7 @@ "Baduk", "badukpop", "benjito", + "bezier", "bitfield", "boardsize", "byoyomi", diff --git a/src/components/ChallengeModal/ChallengeModal.styl b/src/components/ChallengeModal/ChallengeModal.styl index 211f14f209..9bb8610d4a 100644 --- a/src/components/ChallengeModal/ChallengeModal.styl +++ b/src/components/ChallengeModal/ChallengeModal.styl @@ -131,4 +131,64 @@ display: inline-flex; align-items: middle; } + + .ogs-react-select__menu { + right: 0; + width: 20rem; + max-width: 90vw; + + .ogs-react-select__group-heading { + text-transform: none; + font-size: 0.9rem; + } + + .option { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + + &.focused { + themed-important: background-color shade3; + } + + &.selected { + themed-important: background-color shade3; + } + + themed: color fg; + } + + .option-label { + font-weight: bold; + padding-right: 0.5rem; + padding-left: 0.5rem; + } + + .option-description { + font-size: 0.9rem; + font-style: italic; + themed: color shade1; + padding-right: 0.5rem; + padding-left: 0.5rem; + } + } + + .option-label { + font-weight: bold; + padding-right: 0.5rem; + padding-left: 0.5rem; + } + + .option-description { + font-size: 0.9rem; + font-style: italic; + themed: color shade1; + padding-right: 0.5rem; + padding-left: 0.5rem; + } + + .option-label, .ogs-react-select__single-value, .option-label > span { + display: flex !important; + align-items: center; + gap: 0.5rem; + } } diff --git a/src/components/ChallengeModal/ChallengeModal.tsx b/src/components/ChallengeModal/ChallengeModal.tsx index cfc1e08922..3f5a7e1636 100644 --- a/src/components/ChallengeModal/ChallengeModal.tsx +++ b/src/components/ChallengeModal/ChallengeModal.tsx @@ -19,13 +19,14 @@ import * as React from "react"; import * as data from "@/lib/data"; import * as player_cache from "@/lib/player_cache"; +import Select, { components } from "react-select"; import { OgsResizeDetector } from "@/components/OgsResizeDetector"; import { browserHistory } from "@/lib/ogsHistory"; -import { _, pgettext, interpolate } from "@/lib/translate"; +import { _, pgettext, interpolate, llm_pgettext } from "@/lib/translate"; import { post, del } from "@/lib/requests"; import { Modal, openModal } from "@/components/Modal"; import { socket } from "@/lib/sockets"; -import { rankString, getUserRating, amateurRanks, allRanks } from "@/lib/rank_utils"; +import { rankString, amateurRanks, allRanks } from "@/lib/rank_utils"; import { CreatedChallengeInfo, RuleSet } from "@/lib/types"; import { errorLogger, errorAlerter, rulesText, dup } from "@/lib/misc"; import { PlayerIcon } from "@/components/PlayerIcon"; @@ -41,7 +42,7 @@ import { notification_manager, NotificationManagerEvents, } from "@/components/Notifications/NotificationManager"; -import { one_bot, bot_count, bots_list } from "@/lib/bots"; +import { Bot, one_bot, bot_count, bots_list, getAcceptableTimeSetting } from "@/lib/bots"; import { goban_view_mode } from "@/views/Game/util"; import { GobanRenderer, @@ -61,6 +62,7 @@ import { saveTimeControlSettings, updateSystem, } from "@/components/TimeControl/TimeControlUpdates"; +import { SPEED_OPTIONS } from "@/views/Play/SPEED_OPTIONS"; export type ChallengeDetails = rest_api.ChallengeDetails; @@ -984,6 +986,59 @@ export class ChallengeModalBody extends React.Component< // game name and privacy basicSettings = () => { + const user = data.get("user"); + let available_bots = bots_list().filter((b) => b.id > 0); + const board_size = `${this.state.challenge.game.width}x${this.state.challenge.game.height}`; + console.log(board_size, this.state.challenge.game.speed, this.state.time_control.system); + console.log(this.state.challenge.game.speed); + + available_bots = available_bots.filter((b) => { + console.log( + this.state, + this.state.challenge.game.width, + this.state.challenge.game.height, + this.state.time_control.speed, + this.state.time_control.system, + ); + + const settings = { + rank: user.ranking, + width: this.state.challenge.game.width, + height: this.state.challenge.game.height, + ranked: true, + handicap: this.state.challenge.game.handicap, + system: this.state.time_control.system, + speed: this.state.time_control.speed, + [this.state.time_control.system]: (SPEED_OPTIONS as any)[board_size][ + this.state.time_control.speed + ][this.state.time_control.system], + }; + const [options, message] = getAcceptableTimeSetting(b, settings); + if (!options) { + b.disabled = message || undefined; + } else if (options && options._config_version && options._config_version === 0) { + b.disabled = llm_pgettext( + "Bot is not configured correctly", + "Bot is not configured correctly", + ); + } else { + b.disabled = undefined; + } + + return true; + }); + + available_bots.sort((a, b) => { + if (a.disabled && !b.disabled) { + return 1; + } + if (b.disabled && !a.disabled) { + return -1; + } + return (a.ranking || 0) - (b.ranking || 0); + }); + const selected_bot_value = available_bots.find((b) => b.id === this.state.conf.bot_id); + const mode = this.props.mode; return (
- -   - - - + setLowerRankDiff(parseInt(ev.target.value))} - disabled={automatch_search_active} - > - {user.anonymous ? ( - - ) : ( - [9, 8, 7, 6, 5, 4, 3, 2, 1, 0].map((v) => ( - - )) - )} - - {" - "} - -
-
{_("Rank range")}
-
-
{ - if (automatch_search_active || game_clock === "multiple") { - return; - } - setOpponent("bot"); - }} - > -
- {pgettext("Play a computer opponent", "Computer")} -
-
0 && - opponent === "bot" && - (!selected_bot || - !selected_bot_value || - selected_bot_value.disabled) - ? "error" - : "") - } - > - o.value === lower_rank_diff.toString(), + )} + isSearchable={false} + isDisabled={automatch_search_active} + onChange={(opt) => { + if (opt) { + setLowerRankDiff(parseInt(opt.value)); + } + }} + options={[ + { + options: lower_rank_diff_options, + }, + ]} + components={{ + Option: RenderOptionWithDescription, + }} + /> - {bot_spinner && ( -
-
- cancel_bot_game.current()} - > - {_("Cancel")} - -
-
- )} + {/* + + */} - {correspondence_spinner && ( -
-
{_("Finding you a game...")}
-
- {_( - 'This can take several minutes. You will be notified when your match has been found. To view or cancel your automatch requests, please see the list below labeled "Your Automatch Requests".', - )} -
-
- -
-
- )} - {user.anonymous && ( -
- {_("Please sign in to play")} -
- {_("Register for Free")} - {" | "} - {_("Sign in")} -
-
- )} + {" - "} - {!automatch_search_active && !user.anonymous && ( -
+ +
+ {/* Bot */} + {user.anonymous && ( +
+ {_("Please sign in to play")} +
+ {_("Register for Free")} + {" | "} + {_("Sign in")} +
+
+ )} + + {!automatch_search_active && !user.anonymous && ( + + )} + + {/* Human */} + {automatch_manager.active_live_automatcher && ( +
+ + {pgettext("Cancel automatch", "Searching for game...")} + +
+ )} + + {correspondence_spinner && ( +
+
{_("Finding you a game...")}
+
+ {_( + 'This can take several minutes. You will be notified when your match has been found. To view or cancel your automatch requests, please see the list below labeled "Your Automatch Requests".', + )} +
+
+ +
+
+ )} + {user.anonymous && ( +
+ {_("Please sign in to play")} +
+ {_("Register for Free")} + {" | "} + {_("Sign in")} +
+
+ )} + + {!automatch_search_active && !user.anonymous && ( + + )} +
); }