Skip to content

Commit

Permalink
Merge pull request #2372 from online-go/anti-grief
Browse files Browse the repository at this point in the history
Anti griefing features
  • Loading branch information
anoek authored Sep 21, 2023
2 parents 714bb51 + 06eaf4b commit 5b0f46d
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 7 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 7 additions & 2 deletions src/lib/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand All @@ -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;
Expand Down
34 changes: 34 additions & 0 deletions src/views/Game/AntiGrief.styl
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/


.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;
}
}
283 changes: 283 additions & 0 deletions src/views/Game/AntiGrief.tsx
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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(
<div>
{_(
"You have left a live game. If you do not return you will forfeit the match.",
)}
</div>,
);
last_toast = t;

const game_id = live_game_id; // capture the game id
t.on("close", () => {
last_toast = null;
browserHistory.push(`/game/${game_id}`);
});
}
} catch (e) {
console.error(e);
}
}
browserHistory.listen((obj) => {
checkForLeavingLiveGame(obj?.location?.pathname);
});

export function AntiGrief(): JSX.Element {
checkForLeavingLiveGame(location?.pathname);

return (
<>
<AntiEscaping />
<AntiStalling />
</>
);
}
function AntiEscaping(): JSX.Element {
const user = useUser();
const goban = useGoban();
const [phase, setPhase] = React.useState(goban?.engine?.phase);
const [clock, setClock] = React.useState<JGOFClockWithTransmitting>(goban?.last_clock as any);
const [expiration, setExpiration] = React.useState<number>(null);
const [show, setShow] = React.useState(false);

React.useEffect(() => {
setShow(false);
setClock(goban?.last_clock as any);
goban.on("clock", setClock);

return () => {
goban.off("clock", setClock);
};
}, [goban, setClock]);

React.useEffect(() => {
setPhase(goban?.engine?.phase);
}, [goban?.engine?.phase]);

React.useEffect(() => {
const handleAutoResign = (data?: { player_id: number; expiration: number }) => {
setShow(false);
setExpiration(data?.expiration);
};
const handleClearAutoResign = () => {
setShow(false);
setExpiration(null);
};

goban.on("auto-resign", handleAutoResign);
goban.on("clear-auto-resign", handleClearAutoResign);

return () => {
goban.off("auto-resign", handleAutoResign);
goban.off("clear-auto-resign", handleClearAutoResign);
};
}, [goban]);

React.useEffect(() => {
if (expiration) {
const timer = setTimeout(() => {
setExpiration(null);
setShow(true);
}, 30 * 1000);
return () => {
clearTimeout(timer);
};
}
}, [expiration]);

if (phase !== "play") {
return null;
}

const time_control: JGOFTimeControl = goban?.engine?.time_control;

if (!time_control) {
return null;
}

if (time_control.speed === "correspondence") {
return null;
}

if (!clock || clock.pause_state) {
return null;
}

if (
user.id !== goban?.engine?.config?.black_player_id &&
user.id !== goban?.engine?.config?.white_player_id &&
!user.is_moderator
) {
return null;
}

if (!show) {
return null;
}

const my_color = user.id === goban?.engine?.config?.black_player_id ? "black" : "white";

return (
<Card className="AntiEscaping">
<div>
{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.",
)}
</div>
<div>
<button
className="danger"
onClick={() => goban?.sendPreventEscaping(my_color, false)}
>
{pgettext(
"This button is shown when one player has left the game, it allows the other player to end the game and claim victory",
"Claim victory",
)}
</button>

{/*
<button
className="danger"
onClick={() => goban?.sendPreventEscaping(my_color, true)}
>
{pgettext(
"This button is shown when one player has left the game, it allows the other player to end the game and claim victory",
"End game and don't rate",
)}
</button>
</div> <div>
*/}
<button onClick={() => goban?.pauseGame()}>{_("Pause game")}</button>
</div>
</Card>
);
}

function AntiStalling(): JSX.Element {
const user = useUser();
const goban = useGoban();
const [estimate, setEstimate] = React.useState(null);
const [phase, setPhase] = React.useState(goban?.engine?.phase);

React.useEffect(() => {
const onScoreEstimate = (estimate) => {
setEstimate(estimate);
};

onScoreEstimate(goban.config?.stalling_score_estimate);
goban.on("stalling_score_estimate", onScoreEstimate);

return () => {
goban.off("stalling_score_estimate", onScoreEstimate);
};
}, [goban, goban.config?.stalling_score_estimate]);

React.useEffect(() => {
setPhase(goban?.engine?.phase);
}, [goban?.engine?.phase]);

if (!estimate) {
return null;
}

if (phase !== "play" && phase !== "stone removal") {
return null;
}

if (
user.id !== goban?.engine?.config?.black_player_id &&
user.id !== goban?.engine?.config?.white_player_id &&
!user.is_moderator
) {
return null;
}

if (estimate.move_number + 1 < goban.engine?.cur_move?.move_number) {
// If we've placed a move since the estimate was made, we don't need to show it anymore
return null;
}

// capitalize first letter
const predicted_winner =
estimate.predicted_winner.charAt(0).toUpperCase() + estimate.predicted_winner.slice(1);
const win_rate = (
(estimate.win_rate > 0.5 ? estimate.win_rate : 1.0 - estimate.win_rate) * 100.0
).toFixed(1);

return (
<Card className="AntiStalling">
<div>
{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}%)
</div>
<div>
<button
className="danger"
onClick={() => goban?.sendPreventStalling(estimate.predicted_winner)}
>
{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",
"Accept predicted winner and end game",
)}
</button>
</div>
</Card>
);
}
3 changes: 3 additions & 0 deletions src/views/Game/PlayControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -269,6 +270,7 @@ export function PlayControls({
)}
</span>
)}

{((mode === "play" && phase === "stone removal") || null) && (
<span>{_("Stone Removal Phase")}</span>
)}
Expand Down Expand Up @@ -468,6 +470,7 @@ export function PlayControls({
</div>
</div>
)}
<AntiGrief />
{(mode === "conditional" || null) && (
<div className="conditional-move-planner">
<div className="buttons">
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5352,10 +5352,10 @@ glogg@^1.0.0:
dependencies:
sparkles "^1.0.0"

goban@=0.7.11:
version "0.7.11"
resolved "https://registry.yarnpkg.com/goban/-/goban-0.7.11.tgz#e8fedbc1605b65119f76e45b1b59512fdb97407e"
integrity sha512-49viNOEoE6fQvRGKTiCRKmvFtLYu3AMq9ipK4/TjwP42U8gNzyaoo+vygGUFIK0t/Jd1R7QRm65OeSKuxKArwA==
goban@=0.7.13:
version "0.7.13"
resolved "https://registry.yarnpkg.com/goban/-/goban-0.7.13.tgz#a17ca1ab1931dc8b313c72ca894cdfbea463a8e3"
integrity sha512-sKoHlvBiexrjYSDXPvmIeW/ciGJkOkuKOt2E2dLwmTFqF9yfUTcDGFXHTATDDLEJt8XrgLXCa1vD00pJYEtFlA==
dependencies:
eventemitter3 "^5.0.0"

Expand Down

0 comments on commit 5b0f46d

Please sign in to comment.