@sabaki/shudan
Version:
A highly customizable, low-level Preact Goban component.
436 lines (399 loc) • 14.7 kB
JavaScript
const { createElement: h, Component } = require("react");
const { render } = require("react-dom");
const Board = require("@sabaki/go-board");
const { Goban } = require("..");
const chineseCoord = [
"一",
"二",
"三",
"四",
"五",
"六",
"七",
"八",
"九",
"十",
"十一",
"十二",
"十三",
"十四",
"十五",
"十六",
"十七",
"十八",
"十九",
];
const signMap = [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[-1, -1, -1, -1, 1, 1, 1, -1, 0, -1, 1, -1, -1, 0, -1, 1, 1, -1, 0],
[-1, 1, -1, 0, -1, -1, -1, -1, -1, -1, 1, -1, 0, -1, -1, 1, -1, 0, -1],
[],
[],
[],
[],
[],
[],
[],
];
const paintMap = [
[-1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1],
[-1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1, 1],
[-1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1],
[-1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[-1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1],
[-1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, 0, 1, 1, 1, 1],
[-1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1],
[-1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 0, 1, -1, -1, -1, -1, -1, -1, 1],
[-1, -1, -1, -1, 1, 1, 1, 0, -1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1],
[-1, -1, -1, 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, -1],
[-1, -1, -1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, -1],
[-1, 1, -1, 0, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, -1],
[],
[],
[],
[],
[],
[],
[],
].map((row) => row.map((sign) => ((Math.random() * 2 + 1) / 3) * sign));
const heatMap = (() => {
let _ = null;
let O = (strength, text) => ({ strength, text });
let O1 = O(1, "20%\n111");
let O5 = O(5, "67%\n2315");
let O9 = O(9, "80%\n13.5k");
return [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
];
})();
const markerMap = (() => {
let _ = null;
let O = { type: "circle" };
let X = { type: "cross" };
let T = { type: "triangle" };
let Q = { type: "square" };
let $ = { type: "point" };
let S = { type: "loader" };
let L = (label) => ({ type: "label", label });
let A = L("a");
let B = L("b");
let C = L("c");
let longLabel = L("Long\nlabel with linebreak");
return [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
];
})();
const ghostStoneMap = (() => {
let _ = null;
let O = (t) => ({ sign: -1, type: t });
let X = (t) => ({ sign: 1, type: t });
let o = (t) => ({ sign: -1, type: t, faint: true });
let x = (t) => ({ sign: 1, type: t, faint: true });
let [Xg, xg] = [X, x].map((f) => f("good"));
let [Xb, xb] = [X, x].map((f) => f("bad"));
let [Xi, xi] = [X, x].map((f) => f("interesting"));
let [Xd, xd] = [X, x].map((f) => f("doubtful"));
return [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
];
})();
const createTwoWayCheckBox =
(component) =>
({ stateKey, text }) =>
h(
"label",
{
style: {
display: "flex",
alignItems: "center",
},
},
h("input", {
style: { marginRight: ".5em" },
type: "checkbox",
checked: component.state[stateKey],
onClick: () =>
component.setState((s) => ({ [stateKey]: !s[stateKey] })),
}),
h("span", { style: { userSelect: "none" } }, text)
);
class App extends Component {
constructor(props) {
super(props);
this.state = {
board: new Board(signMap),
vertexSize: 24,
showCoordinates: false,
alternateCoordinates: false,
showCorner: false,
showDimmedStones: false,
fuzzyStonePlacement: false,
animateStonePlacement: false,
showPaintMap: false,
showHeatMap: false,
showMarkerMap: false,
showGhostStones: false,
showLines: false,
showSelection: false,
isBusy: false,
};
this.CheckBox = createTwoWayCheckBox(this);
}
render() {
let {
vertexSize,
showCoordinates,
alternateCoordinates,
showCorner,
showDimmedStones,
fuzzyStonePlacement,
animateStonePlacement,
showPaintMap,
showHeatMap,
showMarkerMap,
showGhostStones,
showLines,
showSelection,
} = this.state;
return h(
"section",
{
style: {
display: "grid",
gridTemplateColumns: "15em auto",
gridColumnGap: "1em",
},
},
h(
"form",
{
style: {
display: "flex",
flexDirection: "column",
},
},
h(
"p",
{ style: { margin: "0 0 .5em 0" } },
"Size: ",
h(
"button",
{
type: "button",
onClick: (evt) => {
this.setState((s) => ({
vertexSize: Math.max(s.vertexSize - 4, 4),
}));
},
},
"-"
),
" ",
h(
"button",
{
type: "button",
title: "Reset",
onClick: (evt) => {
this.setState({ vertexSize: 24 });
},
},
"•"
),
" ",
h(
"button",
{
type: "button",
onClick: (evt) => {
this.setState((s) => ({ vertexSize: s.vertexSize + 4 }));
},
},
"+"
)
),
h(
"p",
{ style: { margin: "0 0 .5em 0" } },
"Stones: ",
h(
"button",
{
type: "button",
title: "Reset",
onClick: (evt) => {
this.setState({ board: new Board(signMap) });
},
},
"•"
)
),
h(this.CheckBox, {
stateKey: "showCoordinates",
text: "Show coordinates",
}),
h(this.CheckBox, {
stateKey: "alternateCoordinates",
text: "Alternate coordinates",
}),
h(this.CheckBox, {
stateKey: "showCorner",
text: "Show lower right corner only",
}),
h(this.CheckBox, {
stateKey: "showDimmedStones",
text: "Dim dead stones",
}),
h(this.CheckBox, {
stateKey: "fuzzyStonePlacement",
text: "Fuzzy stone placement",
}),
h(this.CheckBox, {
stateKey: "animateStonePlacement",
text: "Animate stone placement",
}),
h(this.CheckBox, { stateKey: "showMarkerMap", text: "Show markers" }),
h(this.CheckBox, {
stateKey: "showGhostStones",
text: "Show ghost stones",
}),
h(this.CheckBox, { stateKey: "showPaintMap", text: "Show paint map" }),
h(this.CheckBox, { stateKey: "showHeatMap", text: "Show heat map" }),
h(this.CheckBox, { stateKey: "showLines", text: "Show lines" }),
h(this.CheckBox, { stateKey: "showSelection", text: "Show selection" }),
h(this.CheckBox, { stateKey: "isBusy", text: "Busy" })
),
h(
"div",
{},
h(Goban, {
innerProps: {
onContextMenu: (evt) => evt.preventDefault(),
},
vertexSize,
animate: true,
busy: this.state.isBusy,
rangeX: showCorner ? [8, 18] : undefined,
rangeY: showCorner ? [12, 18] : undefined,
coordX: alternateCoordinates ? (i) => chineseCoord[i] : undefined,
coordY: alternateCoordinates ? (i) => i + 1 : undefined,
signMap: this.state.board.signMap,
showCoordinates,
fuzzyStonePlacement,
animateStonePlacement,
paintMap: showPaintMap && paintMap,
heatMap: showHeatMap && heatMap,
markerMap: showMarkerMap && markerMap,
ghostStoneMap: showGhostStones && ghostStoneMap,
lines: showLines
? [
{ type: "line", v1: [15, 6], v2: [12, 15] },
{ type: "arrow", v1: [10, 4], v2: [5, 7] },
]
: [],
dimmedVertices: showDimmedStones
? [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
]
: [],
selectedVertices: showSelection
? [
[],
[],
[],
[],
[],
]
: [],
onVertexMouseUp: (evt, [x, y]) => {
let sign = evt.button === 0 ? 1 : -1;
let newBoard = this.state.board.makeMove(sign, [x, y]);
this.setState({ board: newBoard });
},
}),
alternateCoordinates &&
h(
"style",
{},
`.shudan-coordx span {
font-size: .45em;
}`
)
)
);
}
}
render(h(App), document.getElementById("root"));