Skip to content

Commit

Permalink
Create ConditionalMoveTreeDisplay in React.
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminpjones committed Aug 21, 2022
1 parent 1001c55 commit 29b42aa
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 3 deletions.
1 change: 1 addition & 0 deletions setup-jest.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
global.window = window
global.$ = require('jquery');
global.jQuery = require("jquery");

// Goban stuff
HTMLCanvasElement.prototype.getContext = () => {
Expand Down
119 changes: 119 additions & 0 deletions src/views/Game/ConditionalMoveTreeDisplay.tsx
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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 (
<div className="conditional-move-tree-container">
{Object.keys(tree.children).map((opponent_move: string) => {
const child_tree = tree.getChild(opponent_move);
return (
<ConditionalMoveTreeDisplay
tree={child_tree}
cpath={opponent_move}
key={opponent_move}
/>
);
})}
</div>
);
}

return (
<ul className="tree">
<li className="move-row">
<MoveEntry color={opponent_color} cpath={cpath} />
{player_move && <MoveEntry color={player_color} cpath={cpath + player_move} />}
{player_move_selected && <DeleteButton cpath={cpath + player_move} />}
{Object.keys(tree.children).map((opponent_move: string) => {
const child_tree = tree.getChild(opponent_move);
const child_cpath = cpath + player_move + opponent_move;
return (
<ConditionalMoveTreeDisplay
tree={child_tree}
cpath={child_cpath}
key={opponent_move}
/>
);
})}
</li>
</ul>
);
}

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 (
<span className={`entry ${selected ? "selected" : ""}`} onClick={cb}>
<span className={`stone ${color}`}></span>
<span>{goban.engine.prettyCoords(mv.x, mv.y)}</span>
</span>
);
}

function DeleteButton({ cpath }: { cpath: string }) {
const goban = useGoban();
const cb = () => {
goban.jumpToLastOfficialMove();
goban.deleteConditionalPath(cpath);
goban.redraw();
};
return <i className="fa fa-times delete-move" onClick={cb} />;
}
45 changes: 44 additions & 1 deletion src/views/Game/PlayControls.test.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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(
<WrapTest goban={goban}>
<PlayControls {...PLAY_CONTROLS_DEFAULTS} mode="conditional" />
</WrapTest>,
);

expect(screen.getByText("Conditional Move Planner")).toBeDefined();
expect(screen.getByText("A19")).toBeDefined();
});
4 changes: 2 additions & 2 deletions src/views/Game/PlayControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand Down Expand Up @@ -505,7 +505,7 @@ export function PlayControls({
<span className="move-current" onClick={goban_jumpToLastOfficialMove}>
{_("Current Move")}
</span>
<PersistentElement elt={conditional_move_tree.current as HTMLElement} />
<ConditionalMoveTreeDisplay tree={goban.conditional_tree} cpath="" />
</div>
</div>
)}
Expand Down

0 comments on commit 29b42aa

Please sign in to comment.