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 (
-
- {bots_list().map((bot, idx) => (
-
- {bot.username} ({rankString(getUserRating(bot).rank)})
-
- ))}
-
-
-
-
-
+
({
+ ...css,
+ width: "20rem",
+ }),
+ }}
+ value={selected_bot_value}
+ isSearchable={false}
+ minMenuHeight={400}
+ maxMenuHeight={400}
+ menuPlacement="auto"
+ onChange={(opt) => {
+ if (opt) {
+ this.upstate("conf.bot_id", opt.id);
+ }
+ }}
+ isOptionDisabled={(option) => {
+ return option.disabled !== undefined;
+ }}
+ options={[
+ {
+ options: available_bots,
+ },
+ ]}
+ components={{
+ Option: RenderBotOption,
+ SingleValue: RenderBotValue,
+ }}
+ />
@@ -1973,8 +2039,9 @@ export function createDemoBoard(
/>,
);
}
-export function challengeComputer() {
- return challenge(undefined, null, true);
+
+export function challengeComputer(settings?: ChallengeModalConfig) {
+ return challenge(undefined, null, true, settings);
}
export function challengeRematch(
goban: GobanRenderer,
@@ -2096,7 +2163,7 @@ function isStandardBoardSize(board_size: string): boolean {
return board_size in standard_board_sizes;
}
-interface ChallengeModalConfig {
+export interface ChallengeModalConfig {
challenge: {
min_ranking?: number;
max_ranking?: number;
@@ -2225,3 +2292,54 @@ export function rejectionDetailsToMessage(details: RejectionDetails): string | u
return undefined;
}
}
+
+const RenderBotOption = (props: {
+ data: Bot & { disabled?: string };
+ innerProps: any;
+ innerRef: any;
+ isFocused: boolean;
+ isSelected: boolean;
+}) => {
+ const opt = props.data;
+ //console.log(opt.username, props.isSelected);
+ return (
+
+
+
+
+ {opt.username} ({rankString(opt.ranking || 0)})
+
+
+
+
+
+
+
+
+ {props.data.disabled ? props.data.disabled : ""}
+
+
+ );
+};
+
+const RenderBotValue = (props: any) => {
+ const opt = props.data;
+ return (
+
+
+ {opt.username} ({rankString(opt.ranking || 0)})
+
+ );
+};
diff --git a/src/lib/bots.ts b/src/lib/bots.ts
index c5813ecbfe..eaeba7d226 100644
--- a/src/lib/bots.ts
+++ b/src/lib/bots.ts
@@ -33,6 +33,7 @@ interface Events {
}
export interface Bot extends User {
config: BotConfig;
+ disabled?: string; // if not undefined, the string describes why
}
export const bot_event_emitter = new EventEmitter();
diff --git a/src/views/Play/CustomGames.tsx b/src/views/Play/CustomGames.tsx
index f8f4a3dbae..8abe461bfb 100644
--- a/src/views/Play/CustomGames.tsx
+++ b/src/views/Play/CustomGames.tsx
@@ -431,15 +431,6 @@ export function CustomGames(): JSX.Element {
>
- {
- challenge(undefined, undefined, undefined, undefined, undefined);
- }}
- >
- {_("Create a custom game")}
-
{_("Hide custom games")}
-
{
- challenge(undefined, undefined, true, undefined, undefined);
+ challenge(undefined, undefined, undefined, undefined, undefined);
}}
>
- {_("Play a custom computer game")}
+ {_("Create a custom game")}
diff --git a/src/views/Play/QuickMatch.styl b/src/views/Play/QuickMatch.styl
index 4330fe5390..07fec2a394 100644
--- a/src/views/Play/QuickMatch.styl
+++ b/src/views/Play/QuickMatch.styl
@@ -80,35 +80,6 @@
margin-right: 0.5rem;
}
- .finding-game-container {
- // flex-shrink: 0;
- // flex-grow: 0;
- // flex-basis: 2.5rem;
- font-size: 1.3rem;
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: row;
- justify-content: space-around;
- align-items: center;
- // align-content: center;
- padding-bottom: 2.5rem;
- padding-top: 1rem;
-
- .spinner {
- margin: 0;
- height: 20px;
- width: 20px;
- margin-left: 1rem;
- margin-right: 1rem;
- }
-
- button {
- margin-left: 2rem;
- font-size: 1.1rem;
- }
- }
-
.automatch-row {
display: flex;
justify-content: space-around;
@@ -179,14 +150,6 @@
}
}
- .automatch-settings-corr {
- flex: 0;
- text-align: justify;
- font-size: 1rem;
- padding: 0.5rem;
- padding-left: 3rem;
- }
-
.custom-game-row {
display: flex;
justify-content: space-around;
@@ -279,20 +242,6 @@
font-size: 0.9rem;
}
- .PlayButton-container {
- width: 100%;
- }
-
- .play-button {
- // margin-top: 3rem;
- width: 100%;
- height: 6rem;
- font-size: 1.5rem;
- line-height: 1.5rem;
- text-align: center;
- margin-bottom: 0;
- }
-
.game-speed-option-container {
// margin-top: 1rem;
margin-bottom: 2rem;
@@ -401,18 +350,6 @@
padding-top: 0.5rem;
}
- .opponent-rank-range {
- // display: flex;
- text-align: center;
-
- // justify-content: space-around;
- select {
- text-align: center;
- margin-left: 0.5rem;
- margin-right: 0.5rem;
- }
- }
-
.opponent-rank-range-description {
font-size: 0.9rem;
themed: color shade1;
@@ -421,25 +358,6 @@
padding-left: 0.5rem;
margin-bottom: 0.5rem;
}
-
- .computer-select {
- display: flex;
- align-items: center;
- justify-content: space-between;
- flex-wrap: wrap;
-
- select {
- flex-grow: 1;
- min-width: 10rem;
- max-width: 10rem;
- width: 10rem;
- overflow: hidden;
- }
-
- .fa {
- padding-left: 1rem;
- }
- }
}
}
@@ -524,33 +442,6 @@
margin-bottom: 0.5rem;
}
- .computer-select {
- .disabled .option-label {
- themed: color shade2;
- }
-
- .option-label {
- display: flex;
- justify-content: space-between;
- gap: 0.5rem;
-
- > span {
- display: flex;
- align-items: center;
- gap: 0.5rem;
- }
- }
-
- > div {
- width: 100%;
- max-width: 12rem;
- }
-
- &.error {
- border: 1px solid red;
- }
- }
-
.size-button {
margin-right: 0.2rem;
margin-left: 0.2rem;
@@ -577,6 +468,66 @@
.activity.popular:after {
themed: color, shade1;
}
+
+ .opponent-rank-range {
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ gap: 0.5rem;
+ }
+}
+
+.PlayButton-container {
+ display: flex;
+ justify-content: space-between;
+ gap: 2rem;
+
+ .play-button {
+ // margin-top: 3rem;
+ width: 100%;
+ height: 6rem;
+ font-size: 1.5rem;
+ line-height: 1.5rem;
+ text-align: center;
+ margin-bottom: 0;
+ }
+
+ .finding-game-container {
+ // flex-shrink: 0;
+ // flex-grow: 0;
+ // flex-basis: 2.5rem;
+ font-size: 1.3rem;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ align-items: center;
+ // align-content: center;
+ padding-bottom: 2.5rem;
+ padding-top: 1rem;
+
+ .spinner {
+ margin: 0;
+ height: 20px;
+ width: 20px;
+ margin-left: 1rem;
+ margin-right: 1rem;
+ }
+
+ button {
+ margin-left: 2rem;
+ font-size: 1.1rem;
+ }
+ }
+
+ .automatch-settings-corr {
+ flex: 0;
+ text-align: justify;
+ font-size: 1rem;
+ padding: 0.5rem;
+ padding-left: 3rem;
+ }
}
@media (max-width: hamburger-cutoff) {
@@ -591,14 +542,6 @@
.speed-options {
margin-bottom: 0;
}
-
- .computer-select {
- select {
- min-width: calc(100vw - 6rem) !important;
- max-width: calc(100vw - 6rem) !important;
- width: calc(100vw - 6rem) !important;
- }
- }
}
.BoardSize-header {
@@ -613,6 +556,7 @@
right: 0;
z-index: z.dock;
themed: background-color, bg;
+ gap: 1rem;
.play-button {
margin: 0;
@@ -665,10 +609,4 @@
}
}
}
-
- .computer-select {
- > div {
- max-width: auto;
- }
- }
}
\ No newline at end of file
diff --git a/src/views/Play/QuickMatch.tsx b/src/views/Play/QuickMatch.tsx
index b0e16b4d42..e3245fbed8 100644
--- a/src/views/Play/QuickMatch.tsx
+++ b/src/views/Play/QuickMatch.tsx
@@ -22,6 +22,7 @@ import moment from "moment";
import {
AutomatchPreferences,
+ JGOFTimeControl,
JGOFTimeControlSpeed,
shortDurationString,
Size,
@@ -34,23 +35,13 @@ import { alert } from "@/lib/swal_config";
import { useRefresh, useUser } from "@/lib/hooks";
//import { Toggle } from "@/components/Toggle";
import { MiniGoban } from "@/components/MiniGoban";
-import { rankString } from "@/lib/rank_utils";
-import { errorAlerter, uuid } from "@/lib/misc";
+import { uuid } from "@/lib/misc";
import { LoadingButton } from "@/components/LoadingButton";
-import { post } from "@/lib/requests";
-import { browserHistory } from "@/lib/ogsHistory";
-import {
- ChallengeDetails,
- RejectionDetails,
- rejectionDetailsToMessage,
-} from "@/components/ChallengeModal";
-import { notification_manager, NotificationManagerEvents } from "@/components/Notifications";
+import { challengeComputer, ChallengeModalConfig } from "@/components/ChallengeModal";
import { socket } from "@/lib/sockets";
-import { sfx } from "@/lib/sfx";
import { Link } from "react-router-dom";
-import Select, { components } from "react-select";
+import Select from "react-select";
import { SPEED_OPTIONS } from "./SPEED_OPTIONS";
-import { PlayerIcon } from "@/components/PlayerIcon";
import { useHaveActiveGameSearch } from "./hooks";
moment.relativeTimeThreshold("m", 56);
@@ -139,57 +130,6 @@ const RenderOptionWithDescription = (props: {
);
};
-const RenderBotOption = (props: {
- data: Bot & { disabled?: string };
- innerProps: any;
- innerRef: any;
- isFocused: boolean;
- isSelected: boolean;
-}) => {
- const opt = props.data;
- //console.log(opt.username, props.isSelected);
- return (
-
-
-
-
- {opt.username} ({rankString(opt.ranking || 0)})
-
-
-
-
-
-
-
-
- {props.data.disabled ? props.data.disabled : ""}
-
-
- );
-};
-
-const RenderBotValue = (props: any) => {
- const opt = props.data;
- return (
-
-
- {opt.username} ({rankString(opt.ranking || 0)})
-
- );
-};
-
const select_styles = {
menu: ({ ...css }) => ({
...css,
@@ -214,7 +154,6 @@ export function QuickMatch(): JSX.Element {
const [time_control_system, setTimeControlSystem] =
preferences.usePreference("automatch.time-control");
const [opponent, setOpponent] = preferences.usePreference("automatch.opponent");
- const [selected_bot, setSelectedBot] = preferences.usePreference("automatch.bot");
const [lower_rank_diff, setLowerRankDiff] = preferences.usePreference(
"automatch.lower-rank-diff",
);
@@ -223,8 +162,6 @@ export function QuickMatch(): JSX.Element {
);
const [correspondence_spinner, setCorrespondenceSpinner] = React.useState(false);
- const [bot_spinner, setBotSpinner] = React.useState(false);
- const cancel_bot_game = React.useRef<() => void>(() => {});
const [game_clock, setGameClock] = preferences.usePreference("automatch.game-clock");
const have_active_game_search = useHaveActiveGameSearch();
@@ -389,158 +326,64 @@ export function QuickMatch(): JSX.Element {
multiple_speeds,
]);
- const playComputer = React.useCallback(() => {
- const settings = {
- rank: user.ranking,
- width: parseInt(board_size),
- height: parseInt(board_size),
- ranked: true,
- handicap: handicaps === "disabled" ? false : true,
- system: time_control_system,
- speed: game_speed,
- [time_control_system]: SPEED_OPTIONS[board_size][game_speed][time_control_system],
- };
- const [options, message] = getAcceptableTimeSetting(selected_bot, settings);
- if (!options) {
- console.error("Failed to find acceptable time setting", message);
- void alert.fire(_("Please select a bot"));
- return;
+ const time_control: JGOFTimeControl =
+ time_control_system === "fischer"
+ ? {
+ system: "fischer",
+ speed: game_speed,
+ initial_time: SPEED_OPTIONS[board_size][game_speed].fischer.initial_time,
+ time_increment: SPEED_OPTIONS[board_size][game_speed].fischer.time_increment,
+ max_time: SPEED_OPTIONS[board_size][game_speed].fischer.initial_time * 10,
+ pause_on_weekends: false,
+ }
+ : {
+ system: "byoyomi",
+ speed: game_speed,
+ main_time: SPEED_OPTIONS[board_size][game_speed].byoyomi!.main_time,
+ period_time: SPEED_OPTIONS[board_size][game_speed].byoyomi!.period_time,
+ periods: SPEED_OPTIONS[board_size][game_speed].byoyomi!.periods,
+ pause_on_weekends: false,
+ };
+
+ const playComputerX = React.useCallback(() => {
+ // Try to guess whether the player wants a ranked game or not by looking at
+ // the past challenges.
+ let ranked = data.get(`challenge.challenge.${game_speed}`)?.game?.ranked;
+ if (ranked === undefined) {
+ ranked =
+ data.get(`challenge.challenge.blitz`)?.game?.ranked ??
+ data.get(`challenge.challenge.rapid`)?.game?.ranked ??
+ data.get(`challenge.challenge.live`)?.game?.ranked ??
+ true;
}
- const challenge: ChallengeDetails = {
- initialized: false,
- min_ranking: -99,
- max_ranking: 99,
- challenger_color: "automatic",
- rengo_auto_start: 0,
- game: {
- name: _("Quick Match"),
- rules: "chinese",
- ranked: true,
- width: board_size === "9x9" ? 9 : board_size === "13x13" ? 13 : 19,
- height: board_size === "9x9" ? 9 : board_size === "13x13" ? 13 : 19,
- handicap: handicaps === "disabled" ? 0 : -1,
- komi_auto: "automatic",
- disable_analysis: false,
- initial_state: null,
- private: false,
- rengo: false,
- rengo_casual_mode: false,
- pause_on_weekends: true,
- time_control: time_control_system,
- time_control_parameters:
- time_control_system === "fischer"
- ? {
- system: "fischer",
- speed: game_speed,
- initial_time:
- SPEED_OPTIONS[board_size as any][game_speed].fischer.initial_time,
- time_increment:
- SPEED_OPTIONS[board_size as any][game_speed].fischer
- .time_increment,
- max_time:
- SPEED_OPTIONS[board_size as any][game_speed].fischer
- .initial_time * 10,
- pause_on_weekends: true,
- }
- : {
- system: "byoyomi",
- speed: game_speed,
- main_time:
- SPEED_OPTIONS[board_size as any][game_speed].byoyomi!.main_time,
- periods:
- SPEED_OPTIONS[board_size as any][game_speed].byoyomi!.periods,
- period_time:
- SPEED_OPTIONS[board_size as any][game_speed].byoyomi!.period_time,
- periods_min:
- SPEED_OPTIONS[board_size as any][game_speed].byoyomi!.periods,
- periods_max:
- SPEED_OPTIONS[board_size as any][game_speed].byoyomi!.periods,
- pause_on_weekends: true,
- },
+ const settings: ChallengeModalConfig = {
+ challenge: {
+ challenger_color: "automatic",
+ invite_only: false,
+ game: {
+ width: parseInt(board_size),
+ height: parseInt(board_size),
+ ranked,
+ handicap: handicaps === "disabled" ? 0 : -1,
+ time_control,
+ rules: "japanese",
+ komi_auto: "automatic",
+ disable_analysis: false,
+ initial_state: null,
+ private: false,
+ },
+ },
+ conf: {
+ restrict_rank: false,
},
+ time_control,
};
- const bot_id = selected_bot;
- if (!bot_id) {
- void alert.fire(_("Please select a bot"));
- return;
- }
+ console.log(settings);
- setBotSpinner(true);
- post(`players/${bot_id}/challenge`, challenge)
- .then((res) => {
- const challenge_id = res.challenge;
-
- const game_id = typeof res.game === "object" ? res.game.id : res.game;
- let keepalive_interval: ReturnType | undefined;
-
- const checkForReject = (
- notification: NotificationManagerEvents["notification"],
- ) => {
- console.log("challenge rejection check notification:", notification);
- if (notification.type === "gameOfferRejected") {
- /* non checked delete to purge old notifications that
- * could be around after browser refreshes, connection
- * drops, etc. */
- notification_manager.deleteNotification(notification);
- if (notification.game_id === game_id) {
- onRejected(notification.message, notification.rejection_details);
- }
- }
- };
-
- const active_check = () => {
- keepalive_interval = setInterval(() => {
- socket.send("challenge/keepalive", {
- challenge_id: challenge_id,
- game_id: game_id,
- });
- }, 1000);
- socket.send("game/connect", { game_id: game_id });
- socket.on(`game/${game_id}/gamedata`, onGamedata);
- };
-
- const onGamedata = () => {
- off();
- alert.close();
- //sfx.play("game_accepted");
- sfx.play("game_started", 3000);
- //sfx.play("setup-bowl");
- browserHistory.push(`/game/${game_id}`);
- };
-
- const onRejected = (message?: string, details?: RejectionDetails) => {
- off();
- alert.close();
- void alert.fire({
- text:
- (details && rejectionDetailsToMessage(details)) ||
- message ||
- _("Game offer was rejected"),
- });
- };
-
- const off = () => {
- clearTimeout(keepalive_interval);
- socket.send("game/disconnect", { game_id: game_id });
- socket.off(`game/${game_id}/gamedata`, onGamedata);
- //socket.off(`game/${game_id}/rejected`, onRejected);
- notification_manager.event_emitter.off("notification", checkForReject);
- cancel_bot_game.current = () => {};
- setBotSpinner(false);
- };
-
- cancel_bot_game.current = off;
-
- notification_manager.event_emitter.on("notification", checkForReject);
- active_check();
- })
- .catch((err) => {
- setBotSpinner(false);
- errorAlerter(err);
- });
- }, [selected_bot, board_size, handicaps, game_speed, time_control_system, refresh]);
+ challengeComputer(settings);
+ }, [board_size, handicaps, time_control_system, game_speed]);
const play = React.useCallback(() => {
if (data.get("user").anonymous) {
@@ -548,29 +391,15 @@ export function QuickMatch(): JSX.Element {
return;
}
- if (opponent === "bot") {
- playComputer();
- } else {
- doAutomatch();
- }
- }, [doAutomatch, playComputer]);
+ doAutomatch();
+ }, [doAutomatch]);
const dismissCorrespondenceSpinner = React.useCallback(() => {
setCorrespondenceSpinner(false);
}, []);
- /*
- const newComputerGame = React.useCallback(() => {
- if (bot_count() === 0) {
- void alert.fire(_("Sorry, all bots seem to be offline, please try again later."));
- return;
- }
- challengeComputer();
- }, []);
- */
-
const automatch_search_active =
- !!automatch_manager.active_live_automatcher || correspondence_spinner || bot_spinner;
+ !!automatch_manager.active_live_automatcher || correspondence_spinner;
function isSizeActive(size: Size) {
if (game_clock === "multiple") {
@@ -690,8 +519,6 @@ export function QuickMatch(): JSX.Element {
return (a.ranking || 0) - (b.ranking || 0);
});
- const selected_bot_value = available_bots.find((b) => b.id === selected_bot) || undefined;
-
/* Filter available quick matches to the applicable ones for button highlighting */
const available_human_match_count_by_size: { [size: string]: number } = {
"9x9": 0,
@@ -815,6 +642,18 @@ export function QuickMatch(): JSX.Element {
return " activity ";
}
+ const lower_rank_diff_options = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0].map((v) => ({
+ value: v.toString(),
+ label: v === 0 ? "0" : `- ${v}`,
+ description: v === 0 ? llm_pgettext("Player is the same rank as you", "Your rank") : "",
+ }));
+
+ const upper_rank_diff_options = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((v) => ({
+ value: v.toString(),
+ label: v === 0 ? "0" : `+ ${v}`,
+ description: v === 0 ? llm_pgettext("Player is the same rank as you", "Your rank") : "",
+ }));
+
return (
<>
@@ -1050,126 +889,6 @@ export function QuickMatch(): JSX.Element {
{/* Opponent */}
-
-
- {_("Opponent")}
-
-
-
-
{
- if (automatch_search_active) {
- return;
- }
- setOpponent("human");
- }}
- >
-
- {pgettext("Play a human opponent", "Human")}
-
-
- setLowerRankDiff(parseInt(ev.target.value))}
- disabled={automatch_search_active}
- >
- {user.anonymous ? (
- {"30k"}
- ) : (
- [9, 8, 7, 6, 5, 4, 3, 2, 1, 0].map((v) => (
-
- {/*rankString(user.ranking - v) */}- {v}
-
- ))
- )}
-
- {" - "}
- setUpperRankDiff(parseInt(ev.target.value))}
- disabled={automatch_search_active}
- >
- {user.anonymous ? (
- {"9d"}
- ) : (
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((v) => (
-
- {/*rankString(user.ranking + v)*/}+ {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"
- : "")
- }
- >
- {
- if (opt) {
- setSelectedBot(opt.id);
- }
- }}
- isOptionDisabled={(option) => {
- return option.disabled !== undefined;
- }}
- options={[
- {
- options: available_bots,
- },
- ]}
- components={{
- Option: RenderBotOption,
- SingleValue: RenderBotValue,
- }}
- />
-
-
-
-
-
- {/* Play Button */}
{_("Handicap")}
@@ -1200,82 +919,176 @@ export function QuickMatch(): JSX.Element {
}}
/>
+
-
- {automatch_manager.active_live_automatcher && (
-
-
-
- {pgettext("Cancel automatch", "Searching for game...")}
-
-
-
- )}
+ {/* Play Button */}
+
+
+
{_("Opponent rank")}
+
+
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")}
-
-
-
- )}
+ {/*
+ setLowerRankDiff(parseInt(ev.target.value))}
+ disabled={automatch_search_active}
+ >
+ {user.anonymous ? (
+ {"30k"}
+ ) : (
+ [9, 8, 7, 6, 5, 4, 3, 2, 1, 0].map((v) => (
+
+ - {v}
+
+ ))
+ )}
+
+ */}
- {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".',
- )}
-
-
-
- {_(
- pgettext(
- "Dismiss the 'finding correspondence automatch' message",
- "Got it",
- ),
- )}
-
-
-
- )}
- {user.anonymous && (
-
- {_("Please sign in to play")}
-
- {_("Register for Free")}
- {" | "}
- {_("Sign in")}
-
-
- )}
+ {" - "}
- {!automatch_search_active && !user.anonymous && (
- o.value === upper_rank_diff.toString(),
+ )}
+ isSearchable={false}
+ isDisabled={automatch_search_active}
+ onChange={(opt) => {
+ if (opt) {
+ setUpperRankDiff(parseInt(opt.value));
+ }
+ }}
+ options={[
+ {
+ options: upper_rank_diff_options,
+ },
+ ]}
+ components={{
+ Option: RenderOptionWithDescription,
+ }}
+ />
+
+ {/*
+ setUpperRankDiff(parseInt(ev.target.value))}
+ disabled={automatch_search_active}
>
- {_("Play")}
-
- )}
+ {user.anonymous ? (
+ {"9d"}
+ ) : (
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((v) => (
+
+ + {v}
+
+ ))
+ )}
+
+ */}
+
+
+
+ {/* Bot */}
+ {user.anonymous && (
+
+ {_("Please sign in to play")}
+
+ {_("Register for Free")}
+ {" | "}
+ {_("Sign in")}
+
+
+ )}
+
+ {!automatch_search_active && !user.anonymous && (
+
+ {_("Play Computer")}
+
+ )}
+
+ {/* 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".',
+ )}
+
+
+
+ {_(
+ pgettext(
+ "Dismiss the 'finding correspondence automatch' message",
+ "Got it",
+ ),
+ )}
+
+
+
+ )}
+ {user.anonymous && (
+
+ {_("Please sign in to play")}
+
+ {_("Register for Free")}
+ {" | "}
+ {_("Sign in")}
+
+
+ )}
+
+ {!automatch_search_active && !user.anonymous && (
+
+ {_("Play Human")}
+
+ )}
+
>
);
}