From 29b42aa2e8b622ab4e2a9d742a376d4daf40a74e Mon Sep 17 00:00:00 2001 From: Benjamin Jones Date: Sun, 21 Aug 2022 14:39:47 -0700 Subject: [PATCH] Create ConditionalMoveTreeDisplay in React. --- setup-jest.js | 1 + src/views/Game/ConditionalMoveTreeDisplay.tsx | 119 ++++++++++++++++++ src/views/Game/PlayControls.test.tsx | 45 ++++++- src/views/Game/PlayControls.tsx | 4 +- 4 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 src/views/Game/ConditionalMoveTreeDisplay.tsx diff --git a/setup-jest.js b/setup-jest.js index 87bc05ba20..e60d7910d7 100644 --- a/setup-jest.js +++ b/setup-jest.js @@ -1,5 +1,6 @@ global.window = window global.$ = require('jquery'); +global.jQuery = require("jquery"); // Goban stuff HTMLCanvasElement.prototype.getContext = () => { diff --git a/src/views/Game/ConditionalMoveTreeDisplay.tsx b/src/views/Game/ConditionalMoveTreeDisplay.tsx new file mode 100644 index 0000000000..a9345aec7e --- /dev/null +++ b/src/views/Game/ConditionalMoveTreeDisplay.tsx @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012-2022 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 { GoConditionalMove } from "goban"; +import { useGoban } from "./goban_context"; +import { generateGobanHook } from "./GameHooks"; + +interface ConditionalMoveTreeDisplayProps { + tree: GoConditionalMove; + cpath: string; +} + +export function ConditionalMoveTreeDisplay({ tree, cpath }: ConditionalMoveTreeDisplayProps) { + const player_move: string = tree.move; + + const goban = useGoban(); + const opponent_color = goban.conditional_starting_color; + const player_color = opponent_color === "black" ? "white" : "black"; + const selected_path = useCurrentConditionalpath(goban); + const player_move_selected = player_move && cpath + player_move === selected_path; + + if (!cpath) { + return ( +
+ {Object.keys(tree.children).map((opponent_move: string) => { + const child_tree = tree.getChild(opponent_move); + return ( + + ); + })} +
+ ); + } + + return ( + + ); +} + +interface MoveEntryProps { + color: "black" | "white" | "invalid"; + cpath: string; +} + +const useCurrentConditionalpath = generateGobanHook( + (goban) => goban.getCurrentConditionalPath(), + ["conditional-moves.updated"], +); + +function MoveEntry({ color, cpath }: MoveEntryProps) { + const goban = useGoban(); + const mv = goban.engine.decodeMoves(cpath.slice(-2))[0]; + const selected_cpath = useCurrentConditionalpath(goban); + const selected = cpath === selected_cpath; + + const cb = () => { + goban.jumpToLastOfficialMove(); + console.log(cpath); + goban.followConditionalPath(cpath); + goban.redraw(); + }; + + if (!mv) { + return; + } + + return ( + + + {goban.engine.prettyCoords(mv.x, mv.y)} + + ); +} + +function DeleteButton({ cpath }: { cpath: string }) { + const goban = useGoban(); + const cb = () => { + goban.jumpToLastOfficialMove(); + goban.deleteConditionalPath(cpath); + goban.redraw(); + }; + return ; +} diff --git a/src/views/Game/PlayControls.test.tsx b/src/views/Game/PlayControls.test.tsx index 680d548b17..8ff2cca9a1 100644 --- a/src/views/Game/PlayControls.test.tsx +++ b/src/views/Game/PlayControls.test.tsx @@ -1,4 +1,4 @@ -import { Goban } from "goban"; +import { Goban, GoConditionalMove } from "goban"; import { PlayControls } from "./PlayControls"; import { render, screen } from "@testing-library/react"; import * as React from "react"; @@ -204,3 +204,46 @@ test("Renders Pass if it is the user's turn", () => { expect(screen.getByText("Pass")).toBeDefined(); }); + +/** + * ``` + * A19 B18 + * ├── cc + * ├── dd ee + * │ └── ff gg + * └── hh ii + * jj kk + * ``` + */ +function makeConditionalMoveTree() { + return GoConditionalMove.decode([ + null, + { + aa: ["bb", { cc: [null, {}], dd: ["ee", { ff: ["gg", {}] }], hh: ["ii", {}] }], + jj: ["kk", {}], + }, + ]); +} + +test("Renders conditional moves", () => { + const goban = new Goban({ + game_id: 1234, + moves: [], + players: { + // Since three moves have been played, it's white's turn + white: { id: 123, username: "test_user" }, + black: { id: 456, username: "test_user2" }, + }, + }); + goban.setMode("conditional"); + goban.setConditionalTree(makeConditionalMoveTree()); + + render( + + + , + ); + + expect(screen.getByText("Conditional Move Planner")).toBeDefined(); + expect(screen.getByText("A19")).toBeDefined(); +}); diff --git a/src/views/Game/PlayControls.tsx b/src/views/Game/PlayControls.tsx index 1f046da77c..55edff9dd4 100644 --- a/src/views/Game/PlayControls.tsx +++ b/src/views/Game/PlayControls.tsx @@ -37,7 +37,6 @@ import { PlayerCacheEntry } from "player_cache"; import { Link } from "react-router-dom"; import { Resizable } from "Resizable"; import { ChatMode } from "./GameChat"; -import { PersistentElement } from "PersistentElement"; import { toast } from "toast"; import { errorAlerter } from "misc"; import { close_all_popovers } from "popover"; @@ -52,6 +51,7 @@ import { import { useGoban } from "./goban_context"; import { is_valid_url } from "url_validation"; import { enableTouchAction } from "./touch_actions"; +import { ConditionalMoveTreeDisplay } from "./ConditionalMoveTreeDisplay"; interface PlayControlsProps { // Cancel buttons are in props because the Cancel Button is placed below @@ -505,7 +505,7 @@ export function PlayControls({ {_("Current Move")} - + )}