漢字熟語陣取り合戦ゲームが面白そうだったので作ったらまあまあ面白くてもっと作ればもう少し面白くなりそうだと思いました

# 動機
東大王が出てる番組でこんなゲームをやっていました。

- 漢字が20個くらい並んでる。AチームとBチームで戦う。
- そこから熟語を見つけて答える。使った漢字は獲得できる。
- **相手が獲得した漢字も使える。** つまり、奪える。
- ただし、誰も獲得していない漢字(以降、未取得漢字)を1字以上含める必要がある。
- 1ターン1熟語を答えたら、次は相手のターン。

これの面白さは、熟語同士の漢字の被りにあります。例えば[低 気 圧 熱 帯]という漢字が未取得で、Aに「熱帯」「気圧」を取られても、Bは「熱帯**低**気圧」と答えれば得点差-4から+5の大逆転です。Aの獲得した熟語には**低**という穴があったんですね。これによってオセロのような「これは取れるんだけど泳がせといて後で一気に取り返そう」という駆け引きが生まれてきます。

できたのが[これ](https://mt-experience.github.io/kanjiCampounding/index.html)で、テスト用に漢字を[熱帯低気圧参謀本部]に固定したのが[こちら](https://mt-experience.github.io/kanjiCampounding/kanjiCampounding_fotest.html)です。HTMLとjsを使った静的なウェッブページです。ウェッブ関連の技術に疎いのですが、今回で少し慣れることができました。
作成の過程を振り返り、知見を得ます。

# 要件を決める
熟語陣取りのルールを決めてみます。なお、本記事では未取得漢字の表現が「手付かず」に揺れたりします。

- (A,B,手つかずの3エリアがある。)
- どのエリアからも選べるが、熟語は最低でも1字は未取得漢字から取る
- 未取得漢字がなくなったら終了。
- Aが無回答、次のBも無回答になったら終了。出尽くしたとみなす。
- 終了時に獲得漢字が多いほうの勝ち。
- 同じ漢字を複数回使うのはアリ。「十人十色」とかに対応するため。
- 同じ漢字を複数回使っても得点は増えない。「十人十色」は「十人色」の3点分。
- 回答に1回間違えたら相手に順番が渡る。

さらに面白さや便利さのために必要なことも考えました。

- 大きな漢字熟語辞書データがほしい。
  - マニアックな熟語をカバーできる。
  - 熟語をいっぱい知ってることが強さにつながり、競技性が高まる。
  - 漢字熟語辞典なんか落ちてるだろうか。
- 漢字の数は、番組準拠だと20個くらいがちょうどいい？
- 取ってくる熟語同士に被りがないと知ってる単語挙げてくだけになる。
  - 3個熟語を取ってくる。各々が漢字が被る4個の熟語を用意する、とか？


# 作業に取り掛かる - まずは辞書作成

## 辞典を探す
無料で使える漢字熟語辞典は見つけられませんでした。そこで、[**国立国語研究所(2004)『分類語彙表増補改訂版データベース』(ver.1.0)**](https://github.com/masayu-a/WLSP)から取ってくることにしました。[国立国語研究所コーパス開発センター](https://pj.ninjal.ac.jp/corpus_center/goihyo.html)が構築した「語を意味によって分類・整理した類義語集」だそうです。
正直テキトウに選びました。大きな辞書が欲しいという要望からすると、類義語集よりは網羅的な辞典か何かのデータが適切でしょうが、それも見つからなかったです。よって、成果物の熟語判定がガバっててもデータベースのせいではないです。結局36000語の熟語が手に入ったので十分だと思います。

## 類義語集を加工して熟語辞典を作る
<details><summary>データを見ます(クリックで表示)</summary>
  000001,00001,A,体,関係,事柄,事柄,1.1000,01,01,01,者（もの）,者,もの,のも
  025563,24339,A,体,活動,心,表情・態度,1.3030,09,02,03,嗤笑（ししょう）,嗤笑,ししょう,うょしし
  025564,24340,A,体,活動,心,表情・態度,1.3030,09,02,04,嘲笑,嘲笑,ちょうしょう,うょしうょち
  101041,96022,A,他,,動物の鳴き声,動物の鳴き声,4.5000,01,01,05,にゃーにゃー,にゃーにゃー,にゃあにゃあ,あゃにあゃに
</details>
あんま仕様みてないんですが、5列から7列に分類に関する情報が入っていますね。13列目の読みが付いてない単語を取ってきたいですね。

1. 文字コードをsjisからutf8にする。
2. 漢字のみ、2文字以上の単語を取得。
3. 重複を消す。純粋な辞典ではないため、重複があり得るかもしれない。
4. 手抜きするために "" と , を付けてjsの配列にしてしまう。

文字コードの変換はテキストエディタで行いました。また、データの加工はpowershellで行いました。psでcsv読むためには列名が必要なので、先頭にcol1...col16という文字を手で打ちました。

```powershell:漢字熟語を抜き出す.ps1
$csv = Import-Csv .\bunruidb_utf8.txt
$csv | %{$_.col14} >> dic.txt  # 単語だけ抜き出す。  
# 漢字のみで構成されたものを取り出す
(Get-Content .\dic.txt) | where{$_ -match "^([〇\u3400-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF])+$"} >> kanji.txt
#  2文字以上で重複なし # 副作用としてソートされる
(Get-Content .\kanji.txt) | where{$_.length -gt 1} | Sort-Object | Get-Unique >> jukugo.txt

# jsの配列を作るために。
(Get-Content .\jukugo.txt) | %{"'"+$_+"',"} >> jukugo.js

# https://so-zou.jp/software/tech/programming/tech/regular-expression/meta-character/variable-width-encoding.htm
#   "漢字" -match "^([〇\u3400-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF])+$"  => True
#   "あ漢字あ" -match "^([〇\u3400-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF])+$"  => False
#   "あ漢字" -match "^([〇\u3400-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF])+$"  => False
#   "漢 字" -match "^([〇\u3400-\u9FFF\uF900-\uFAFF]|[\uD840-\uD87F][\uDC00-\uDFFF])+$"  => False
# https://win.just4fun.biz/?PowerShell/PowerShell%E3%81%A7%E3%83%A6%E3%83%8B%E3%83%BC%E3%82%AF%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B
#   Get-Uniqueはsortした後じゃないと機能しない
```

jsの配列にするための=や[]も手打ちです。
本当はすべてをスクリプトで書いたほうが後々違う加工方法を試したいときとかに再現性があって良いのですが、横着しました。

<details><summary>漢字熟語データができました。(クリックで表示)</summary>
const fullJukugo = [
'亜鉛',
'亜寒帯',
'亜種',
...
'黎明',
'齟齬'
]
</details>
次はアプリケーションの実装です。

# 実装 ... 詳細は割愛

長くなりそうなので別記事に書こうかと思います。グローバルなオブジェクトだけ抜粋します。なんとなくどんな感じでデータを扱おうとしたか伝わればうれしいです。ここの部分は設計もコーディングも、技術が上達したら見直したいです。
ソースコードは、存在[します](https://github.com/mt-experience/kanjiCampounding)。

```js:グローバルなもの
// 熟語を20個取ってくる
const jukugos = randomArray(fullJukugo, 20)
// 重複のない文字にばらす
const kanjis = (function (){
    const chars = uniqueChars(jukugos.join(''))
    return randomArray(chars, chars.length)
  })();

const buttonIdPrefix = "kanji" // 漢字ボタンのIDの頭に付ける
const players = {  // q:リストを選択できるquery
    A:{name:"A", q:"#a-list"}, 
    B:{name:"B", q:"#b-list"},
	getByName: function(name){
	  if(this.A.name === name){ return this.A }
	  if(this.B.name === name){ return this.B }
	  return; // undefined
	},
	getOpponentByName: function(name){
	  if(this.A.name === name){ return this.B }
	  if(this.B.name === name){ return this.A }
	  return; // undefined
	},
  }
const fields = {A:"A", B:"B", Free:"Free"} // 獲得or手つかずの場
var selectedKanjiIds = [] // 選択されている漢字ボタンのID
```

しくじったのが

- 漢字1文字を1個のbuttonにして、そこに所有者などの情報を載せている。
  - デザインをまともにするなら、書き直すことになりそう。
- テキストエリアに漢字を1個ずつ出力している。
  - ID情報などが消えるので、漢字ボタンを押すたびに selectedKanjiIds にIDを入れている。
- 表現する意味合いが大きいbuttonにあまり情報は持たせたくない。
  - jsのオブジェクトで情報を管理して、DOMはそれを参照するというのが筋だと思う。

というところです。

# 振り返り
## やろうとしたこと
- 熟語陣取りの駆け引きの面白さを再現したかった。
- ブラウザとメモ帳だけで戦える、素のjsとHTMLでやりたかった。
- なるべく早く、作って公開したかった。モチベーションが冷めたり、記憶が古くなるのが嫌だった。

## 上手くできましたか？
- ブラウザとメモ帳で作った。
- 自由時間をゲーム以外に費やすのは10年ぶりだった(作ってるのはゲームだけど)。知識がなさすぎて3日くらいかかったけど、逆に完成がじっくり近づくのが楽しかった。
- 「取ってくる漢字に被りを意図的に増やす」が未実装。でも実装はそんなに難しくなさそう。
- 勝敗判定を実装していないので自分らで判定してください。正気か？
- 獲得文字数を表示していない。自分らで判定してください。これはわくわく感を増す演出ですが、1漢字を1ボタンにして横並びにしてるので、ヒストグラムばりに得点差が分かりやすいですね。

## 改善点
- デザインやばすぎ。二次元に並べて色とかで取得者を表示するとか？
  - 見てくれが悪いだけでなく、横一直線だと漢字間の距離が長くなるので、端の漢字ほど熟語を連想しにくい。
  - 漢字の位置が変わったりすると頭の中に保持しにくい？
  - 番組だと確か六角形で並べてて、横一直線の3倍は連想しやすかったと思う。
  - 六角形はゴチャつかずにまとまる、ちょうどいいマスの形だと思う。TV関係者は分かりやすい表現を心得ているんだろうと思う。
- 熟語の選出方法が楽しさや難易度を左右する。もっと考えよう。
  - せっかく分類情報が載ってるデータ使ってるんだから、それを元に連想しやすい=同じ分類の熟語を持ってきてもいいかも。
  - 「取ってくる漢字に被りを意図的に増やす」は実装した方がよかった。
     - [実装した。](https://qiita.com/unticrice/items/4557cb3611f42f259408)
- ソースコードが汚い。
  - 下手なりに「jsのオブジェクトで情報を管理して、DOMはそれを参照する」みたいな明確な方針を立てればよかった。
  - 実現可能時間内に実装することを優先した結果。ここは努力するとしか言えないので深く追求しない。
- (文書の書き方)
  - 箇条書きの項目同士が独立していない。同レベルの下の項目が上の項目の文脈を受け継いだり、受け継いでなかったりすると分かりにくい。
  - これは直さなきゃなあ(これが直上のことか、親の「ドキュメントの書き方」のことか分からないということ。)
     - 文脈を受け継ぎたいならレベルを下げるべき。(このように)

## いかがでしたか？
ところで、あとでやるって絶対やらないやつですよね。