@sabaki/shudan-goban
Version:
A highly customizable Preact Goban component.
321 lines (285 loc) • 12.4 kB
JavaScript
const {h, render, Component} = require('preact')
const {Goban} = require('..')
const chineseCoord = [
'一', '二', '三', '四', '五', '六', '七', '八', '九', '十',
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九'
]
const signMap = [
[0,0,0,-1,-1,-1,1,0,1,1,-1,-1,0,-1,0,-1,-1,1,0],
[0,0,-1,0,-1,1,1,1,0,1,-1,0,-1,-1,-1,-1,1,1,0],
[0,0,-1,-1,-1,1,1,0,0,1,1,-1,-1,1,-1,1,0,1,0],
[0,0,0,0,-1,-1,1,0,1,-1,1,1,1,1,1,0,1,0,0],
[0,0,0,0,-1,0,-1,1,0,0,1,1,0,0,0,1,1,1,0],
[0,0,-1,0,0,-1,-1,1,0,-1,-1,1,-1,-1,0,1,0,0,1],
[0,0,0,-1,-1,1,1,1,1,1,1,1,1,-1,-1,-1,1,1,1],
[0,0,-1,1,1,0,1,-1,-1,1,0,1,-1,0,1,-1,-1,-1,1],
[0,0,-1,-1,1,1,1,0,-1,1,-1,-1,0,-1,-1,1,1,1,1],
[0,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,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],
[1,1,1,1,-1,1,1,1,-1,1,0,1,-1,0,-1,1,-1,-1,0],
[0,1,-1,1,1,-1,-1,1,-1,1,1,1,-1,1,-1,1,1,-1,1],
[0,0,-1,1,0,0,1,1,-1,-1,0,1,-1,1,-1,1,-1,0,-1],
[0,0,1,0,1,0,1,1,1,-1,-1,1,-1,-1,1,-1,-1,-1,0],
[0,0,0,0,1,1,0,1,-1,0,-1,-1,1,1,1,1,-1,-1,-1],
[0,0,1,1,-1,1,1,-1,0,-1,-1,1,1,1,1,0,1,-1,1],
[0,0,0,1,-1,-1,-1,-1,-1,0,-1,-1,1,1,0,1,1,1,0]
]
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],
[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,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]
]
const heatMap = (() => {
let _ = null
let O = (strength, text) => ({strength, text})
return [
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,O(7),O(9, '80%\n13.5k'),_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,O(3),_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,O(2),_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,O(1, '20%\n111'),_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,O(5, '67%\n2315'),O(4),_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]
]
})()
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')
return [
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,O,O,O,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,X,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,X,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,X,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,T,T,T,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,$,$,$,_,_,_,_,_,_,_,_,_,_,_,S,S,S,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,Q,_,_,_,_,_,_,_,_,_,L('Long\nlabel')],
[_,_,_,_,_,_,_,_,Q,_,_,_,_,_,_,_,_,_,C],
[_,_,_,_,_,_,_,_,Q,_,_,_,_,_,_,_,_,_,B],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,A]
]
})()
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})
return [
[X(),x(),_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[O(),o(),_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[X('good'),x('good'),_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[X('interesting'),x('interesting'),_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[X('doubtful'),x('doubtful'),_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[X('bad'),x('bad'),_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_],
[_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_]
]
})()
const createTwoWayCheckBox = (state, setState) => (
({stateKey, text}) => h('label',
{
style: {
display: 'flex',
alignItems: 'center'
}
},
h('input', {
style: {marginRight: '.5em'},
type: 'checkbox',
checked: state[stateKey],
onClick: () => setState(s => ({[stateKey]: !s[stateKey]}))
}),
h('span', {style: {userSelect: 'none'}}, text)
)
)
class App extends Component {
constructor(props) {
super(props)
this.state = {
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.state, this.setState.bind(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({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, {
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.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 ? [
[2, 14], [2, 13], [5, 13], [6, 13],
[9, 3], [9, 5], [10, 5], [14, 7],
[13, 13], [13, 14], [18, 13]
] : [],
selectedVertices: showSelection ? [
[9, 7]
] : [],
onVertexMouseUp: (evt, [x, y]) => {
let signMap = JSON.parse(JSON.stringify(this.state.signMap))
signMap[y][x] = Math.sign(Math.random() - .5) || 1
this.setState({signMap})
}
}),
alternateCoordinates && h('style', {}, `
.shudan-coordx span {
font-size: .45em;
}
`)
)
)
}
}
render(h(App), document.body)