diff --git a/package.json b/package.json
index fe1dc85436..238ff00a40 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
"express": "^4.17.1",
"express-http-proxy": "^1.6.0",
"fork-ts-checker-webpack-plugin": "^8.0.0",
- "goban": "=0.7.11",
+ "goban": "=0.7.13",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
"gulp-eslint-new": "^1.7.1",
diff --git a/src/lib/misc.ts b/src/lib/misc.ts
index ca17ea2a82..726ad1f935 100644
--- a/src/lib/misc.ts
+++ b/src/lib/misc.ts
@@ -155,8 +155,9 @@ export function uuid(): string {
});
}
export function getOutcomeTranslation(outcome: string) {
- /* Note: for the case statements, don't simply do `pgettext("Game outcome", outcome)`,
- * the system to parse out strings to translate needs the text. */
+ /* Note: Do not simply do `pgettext("Game outcome", outcome)`
+ * The translation system needs to read these strings to parse them out and
+ * prepare them for translating. */
switch (outcome) {
case "resign":
case "r":
@@ -178,6 +179,10 @@ export function getOutcomeTranslation(outcome: string) {
return pgettext("Game outcome", "Abandonment");
}
+ if (outcome.indexOf("Server Decision") === 0) {
+ return pgettext("Game outcome", "Server Decision") + " " + outcome.substring(16);
+ }
+
if (/[0-9.]+/.test(outcome)) {
const num: number = +outcome.match(/([0-9.]+)/)[1];
const rounded_num = Math.round(num * 2) / 2;
diff --git a/src/views/Game/AntiGrief.styl b/src/views/Game/AntiGrief.styl
new file mode 100644
index 0000000000..6b83d818b3
--- /dev/null
+++ b/src/views/Game/AntiGrief.styl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) Online-Go.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+
+.AntiStalling, .AntiEscaping {
+ display: inline-flex;
+ align-items: center;
+ justify-content: space-around;
+ flex-direction: column;
+ margin: 0.5rem;
+ width: calc(100% - 2.2rem) !important;
+ min-height: 8rem;
+ text-align: center;
+ font-size: 1.1rem;
+ border-radius: 0.5rem;
+
+ button {
+ white-space: nowrap !important;
+ }
+}
diff --git a/src/views/Game/AntiGrief.tsx b/src/views/Game/AntiGrief.tsx
new file mode 100644
index 0000000000..5b96c62910
--- /dev/null
+++ b/src/views/Game/AntiGrief.tsx
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) Online-Go.com
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import * as React from "react";
+import { Card } from "material";
+import { pgettext, _ } from "translate";
+import { useGoban } from "./goban_context";
+import { useUser } from "hooks";
+import { JGOFClockWithTransmitting, JGOFTimeControl } from "goban";
+import { browserHistory } from "../../ogsHistory";
+import { toast } from "toast";
+
+let on_game_page = false;
+let live_game = false;
+let live_game_id = 0;
+let live_game_phase = null;
+let last_toast = null;
+
+function checkForLeavingLiveGame(pathname: string) {
+ try {
+ const goban = window["global_goban"];
+ const was_on_page = on_game_page;
+ const was_live_game = live_game;
+
+ if (goban) {
+ const path = `/game/${goban.game_id}`;
+ if (pathname === path) {
+ on_game_page = true;
+ live_game = goban.engine.time_control.speed !== "correspondence";
+ live_game_id = goban.game_id;
+ live_game_phase = goban.engine?.phase;
+ if (last_toast) {
+ last_toast.close();
+ }
+ } else {
+ on_game_page = false;
+ }
+ }
+
+ if (was_on_page && !on_game_page && was_live_game && live_game_phase === "play") {
+ const t = toast(
+
+ {_(
+ "You have left a live game. If you do not return you will forfeit the match.",
+ )}
+
+ {pgettext(
+ "This message is shown when one player has left the game",
+ "Your opponent is no longer connected. You can wait and see if they come back, or end the game.",
+ )}
+
+ {pgettext(
+ "This message is shown when the server thinks the game is over, but one player is stalling the game by continually playing useless moves",
+ "Predicted winner: ",
+ )}{" "}
+ {_(predicted_winner)} ({win_rate}%)
+
+
+
+
+
+ );
+}
diff --git a/src/views/Game/PlayControls.tsx b/src/views/Game/PlayControls.tsx
index f110ae442b..2474b11abe 100644
--- a/src/views/Game/PlayControls.tsx
+++ b/src/views/Game/PlayControls.tsx
@@ -57,6 +57,7 @@ import { is_valid_url } from "url_validation";
import { enableTouchAction } from "./touch_actions";
import { ConditionalMoveTreeDisplay } from "./ConditionalMoveTreeDisplay";
import { useUser } from "hooks";
+import { AntiGrief } from "./AntiGrief";
import * as moment from "moment";
@@ -269,6 +270,7 @@ export function PlayControls({
)}
)}
+
{((mode === "play" && phase === "stone removal") || null) && (
{_("Stone Removal Phase")}
)}
@@ -468,6 +470,7 @@ export function PlayControls({
)}
+
{(mode === "conditional" || null) && (