HTML5とCSS3で将棋の局面図作った

# はじめに

太古からブログなどで将棋の局面図を貼りたいという需要はあった。
当時のメインストリームは減色pngだ。
特に8色に完璧に調整された減色pngは
下手なベクターオブジェクトより軽いうえに
アンチエイリアスも美しい。
そして昨今ではその下手なベクターであるSVG形式もブラウザで扱えるようになってきた。
が、結局はバイナリなのでテキストでなんとかならないものか。
誰しもが思うことである。

# Gridレイアウトの本気を見せる

display: grid;を使えば[Excel方眼紙も作れる](https://qiita.com/tibigame/items/2b8df68e0e07eb3ec89f)ことは既に示した。
なら将棋もいけるのではないか。

![root.png](https://qiita-image-store.s3.amazonaws.com/0/133885/5b72780d-c19c-4c59-6c4d-205ef796e049.png)

局面図を「ヘッダー」「フッター」「盤面」「手駒左」「手駒右」の5つの領域に分ける。

```html
<figure class="shogi black-side">
	<figcaption>第30期 竜王戦七番勝負 第四局</figcaption>
	<div class="board">
	</div>
	<div class="hands-black">
		<span class="name">渡辺明</span>
		<span class="piece">銀桂香歩五</span>
	</div>
	<div class="hands-white">
		<span class="name">羽生善治</span>
		<span class="piece">金二歩</span>
	</div>
	<p class="info">△６六飛まで</p>
</figure>
```

```css
figure.shogi {
    width: 20rem;
    display: grid;
    grid-template-columns: 2rem 16rem 2rem;
    grid-template-rows: max-content 16rem max-content;
    grid-template-areas:
        "header header header"
        "hands-left board hands-right"
        "footer footer footer";
    justify-items: center;
    align-items: center;
    font-size: 1rem; 
}
figure.shogi > figcaption {
    height: 2em;
    margin-top: 0.5em;
    margin-bottom: 0.3em;
    grid-area: header;
}
figure.shogi > p.info {
    height: 2em;
    margin-top: 0.3em;
    margin-bottom: 0.5em;
    grid-area: footer;
}
figure.shogi > div.board {
    width: 100%;
    height: 100%;
    grid-area: board;
}
figure.shogi.black-side > div.hands-black {
    letter-spacing: 0.1em;
    margin-right: 20%;
    height: 100%;
    writing-mode: vertical-rl;
    grid-area: hands-right;
}
figure.shogi.black-side > div.hands-white {
    letter-spacing: 0.1em;
    margin-left: 20%;
    height: 100%;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    grid-area: hands-left;
}
figure.shogi.white-side > div.hands-black {
    letter-spacing: 0.1em;
    margin-left: 20%;
    height: 100%;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    grid-area: hands-left;
}
figure.shogi.white-side > div.hands-white {
    letter-spacing: 0.1em;
    margin-right: 20%;
    height: 100%;
    writing-mode: vertical-rl;
    grid-area: hands-right;
}
figure.shogi > div.hands-black  > span.name::before {
    content: '▲';
    margin-bottom: 0.2em;
}
figure.shogi > div.hands-white  > span.name::before {
    content: '△';
    margin-bottom: 0.2em;
}
figure.shogi > div.hands-black  > span.piece::before {
    content: '持駒';
    margin-top: 0.6em;
    margin-bottom: 0.4em;
}
figure.shogi > div.hands-white  > span.piece::before {
    content: '持駒';
    margin-top: 0.6em;
    margin-bottom: 0.4em;
}
```

左の駒台の文字は180度回転され、
ルートのclassがblack-sideかwhite-sideかによって
左右どちらに配置するかを分岐するコードになっている。

問題の盤面は……

```html
<div class="board">
	<div class="line board-border">&nbsp;</div>
	<div class="line area_f2">&nbsp;</div>
	<div class="line area_f4">&nbsp;</div>
	<div class="line area_f6">&nbsp;</div>
	<div class="line area_f8">&nbsp;</div>
	<div class="line area_r2">&nbsp;</div>
	<div class="line area_r4">&nbsp;</div>
	<div class="line area_r6">&nbsp;</div>
	<div class="line area_r8">&nbsp;</div>
	<div class="num rank1">一</div>
	<div class="num rank2">二</div>
	<div class="num rank3">三</div>
	<div class="num file1">１</div>
	<div class="num file2">２</div>
	<div class="num file3">３</div>
	<div class="sq93 white">歩</div>
	<div class="sq66 white strong">飛</div>
    (途中略)
</div>
```

```css
figure.shogi > div.board {
    width: 100%;
    height: 100%;
    display: grid;
    grid-template-columns: 0.5fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 0.5fr;
    grid-template-rows: 0.5fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 0.5fr;
    grid-template-areas:
        "a top9 top8 top7 top6 top5 top4 top3 top2 top1 b"
        "left1 sq91 sq81 sq71 sq61 sq51 sq41 sq31 sq21 sq11 right1"
        "left2 sq92 sq82 sq72 sq62 sq52 sq42 sq32 sq22 sq12 right2"
        "left3 sq93 sq83 sq73 sq63 sq53 sq43 sq33 sq23 sq13 right3"
        "left4 sq94 sq84 sq74 sq64 sq54 sq44 sq34 sq24 sq14 right4"
        "left5 sq95 sq85 sq75 sq65 sq55 sq45 sq35 sq25 sq15 right5"
        "left6 sq96 sq86 sq76 sq66 sq56 sq46 sq36 sq26 sq16 right6"
        "left7 sq97 sq87 sq77 sq67 sq57 sq47 sq37 sq27 sq17 right7"
        "left8 sq98 sq88 sq78 sq68 sq58 sq48 sq38 sq28 sq18 right8"
        "left9 sq99 sq89 sq79 sq69 sq59 sq49 sq39 sq29 sq19 right9"
	"c bottom9 bottom8 bottom7 bottom6 bottom5 bottom4 bottom3 bottom2 bottom1 d";
    justify-items: center;
    align-items: center;
    grid-area: board;
}
figure.shogi.black-side > div.board > div.rank1{
    grid-area: right1;
}
figure.shogi.black-side > div.board > div.rank2{
    grid-area: right2;
}
figure.shogi.black-side > div.board > div.file1{
    grid-area: top1;
}
figure.shogi.black-side > div.board > div.file2{
    grid-area: top2;
}
figure.shogi.white-side > div.board > div.rank1{
    grid-area: left9;
}
figure.shogi.white-side > div.board > div.rank2{
    grid-area: left8;
}
figure.shogi.white-side > div.board > div.file1{
    grid-area: bottom9;
}
figure.shogi.white-side > div.board > div.file2{
    grid-area: bottom8;
}
figure.shogi > div.board > div.line {
    height: 100%;
    width: 100%;
    border: 0.05rem solid #000000ff;
    background: #ffffff00;
}
figure.shogi > div.board > div.board-border {
    grid-column: 2 / -2;
    grid-row: 2 / -2;
}
figure.shogi > div.board > div.area_f2 {
    grid-column: 9 / 10;
    grid-row: 2 / -2;
}
figure.shogi > div.board > div.area_f4 {
    grid-column: 7 / 8;
    grid-row: 2 / -2;
}
figure.shogi > div.board > div.area_f6 {
    grid-column: 5 / 6;
    grid-row: 2 / -2;
}
figure.shogi > div.board > div.area_f8 {
    grid-column: 3 / 4;
    grid-row: 2 / -2;
}
figure.shogi > div.board > div.area_r2 {
    grid-row: 9 / 10;
    grid-column: 2 / -2;
}
figure.shogi > div.board > div.area_r4 {
    grid-row: 7 / 8;
    grid-column: 2 / -2;
}
figure.shogi > div.board > div.area_r6 {
    grid-row: 5 / 6;
    grid-column: 2 / -2;
}
figure.shogi > div.board > div.area_r8 {
    grid-row: 3 / 4;
    grid-column: 2 / -2;
}
figure.shogi > div.board > div {
    background: #ffffff00;
}
figure.shogi.black-side > div.board > div.black {
    margin-top: 10%;
}
figure.shogi.black-side > div.board > div.white {
    margin-bottom: 10%;
    transform: rotate(180deg);
}
figure.shogi.white-side > div.board > div.black {
    margin-bottom: 10%;
    transform: rotate(180deg);
}
figure.shogi.white-side > div.board > div.white {
    margin-top: 10%;
}
figure.shogi.black-side > div.board > div.sq11{
    grid-area: sq11;
}
figure.shogi.black-side > div.board > div.sq12{
    grid-area: sq12;
}
figure.shogi.black-side > div.board > div.sq13{
    grid-area: sq13;
}
figure.shogi >  div.board > div.strong {
    font-weight: bold;
}
```

升目を描くにはどうすればよいか。
81個の要素を無理矢理作って順番に埋めていく。
遂に「dense」プロパティが活躍するときが来たか！？と思ったが、
流石に空白升が多い中でその実装はないだろう。
代案として2,4,6,8列の縦横の長方形の枠を描いて外枠描けば
9*9升が描けるというアルゴリズムにした。
駒は&lt;div class="sq66 white strong"&gt;飛&lt;/div&gt;のように
位置とどちらの駒かと強調表示のクラスを指定するだけで良い。

レンダリングはこんな感じ。
![screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/133885/aa0baaed-cab1-b221-3cb5-f019735906d6.png)
2つの局面図を描くときの差分は最上位ノードのクラスが
「black-side」か「white-side」かの一箇所だけである。

GitHubにコードは挙げておいた。
https://github.com/tibigame/Shogi-Kyokumen-CSS
サイズとかをちょくちょく変えたい場合はscssとか使えばすっきり書けるかも。

Redux使えばイベント起動で局面動かすことはわけないだろうし、
それをReactで差分更新して
思考スクリプトはES2017で書いたのをBabelでトランスコードして
手元のnode.js使ってデバッグと開発するとかを暇な人がやれば
ブラウザ上でクライアントのCPU使って動く将棋ソフトも作れるだろう。