BigQueryで将棋の棋譜の氾濫を解決する

Tech KAYAC Advent Calendar 2017の13日目の記事をお届けします。 クライアントワーク事業部でサーバーサイドエンジニアをしている森です。よろしくお願いします。

過去最大の将棋の盛り上がり

今年は将棋がとてもとても盛り上がりましたね。

  • 最年少プロ棋士誕生
  • 藤井四段29連勝
  • 加藤一二三九段引退
  • 流行語大賞候補に藤井フィーバーとひふみんがノミネート
  • 羽生棋聖竜王位を獲得し永世七冠に

かつてこんなに将棋がニュースに出たことはあったでしょうか。

こうなってくると将棋人口が増えて来そうです。

将棋人口が増えた時の問題点

将棋人口が増えると何が困るでしょうか?

webのエンジニアなので将棋をwebに例えて考えてみました。

対局者:ユーザー
将棋盤・駒:UI・UX
指し手:アクセス
棋譜:ログ

という感じでしょうか。

将棋人口が増えると棋譜がとんでもないことになりそうです。 とは言え何時間もかけて指した棋譜捨ててしまうのは勿体無いですよね。

そんな勿体無いことせずに人の棋譜も含めて全て集めましょう!

どうやって棋譜を集めるか

棋譜≒ログということが分かったのでwebで一般的にログを扱うように棋譜を扱うことを考えてみます。 今回はtd-agent(fluentd)で受けてBigQueryに投入するというシステムにすることにしました。

なぜtd-agentを使うの?

様々な入力に柔軟に対応できるからです。例えば盤面を動画で撮影しながら指されたら自動的に棋譜データを送るとかも頑張ればできそうです。また、今回は出力にBigQueryを使用してますが状況によっては他のものに変更することも可能です。

なぜBigQueryを使うの?

BigQueryをデータベースとして使用することでかなりのデータ数まで耐えられます。

集めた棋譜はどうするの?

ログに入れても目的のデータが見つけられないと何のために保存しているかわからないので、効率的に表示することができるツールも作成しました。

システム

ログで記録する項目

項目名 詳細
game_code STRING ハッシュ値など対局ごとに固有の値を設定します
first_name STRING 先手対局者名
second_name STRING 後手対局者名
no INTEGER 〇〇手目
move STRING 指し手
position STRING 局面
is_first BOOLEAN 手番(先手:true 後手:false)
first_hand STRING 先手持ち駒
second_hand STRING 後手持ち駒
moved_at DATETIME 指した時刻

ログの送信形式

ログを送信するときは日本語名ではなく以下の表にマッピングさせます。

先手 後手
a A
b B
c C
d D
e E
f F
g G
h H
i I
j J
k K 成銀
l L 成桂
m M 成香
n N

また、局面は1一、1二、1三・・・2一、2二・・・9八、9九

という様に筋ごとに全て1行にまとめます

f:id:nitamago_monster:20171211011018p:plain:left

設定前の前提条件

  • BigQueryにアクセスする為のjsonキーを取得
  • BigQueryのテーブルを「ログで記録する項目」の様に作成
  • td-agentをインストール

設定

td-agentからBigQueryは幸いにもプラグインが用意されているのでそれを使います。 こちらの fluent-plugin-bigquery を使用しました。

td-agentを使用しているので受け口は柔軟に変えられます。今回はhttpで受けることにしました。

/etc/td-agent/td-agent.conf

<match kifulog.**>
  @type bigquery

  method insert
  <buffer time>
    flush_interval 0.1
    timekey 1d
  </buffer>
  auth_method json_key
  json_key /path/to/gcpkey.json
  auto_create_table true
  project project-name
  dataset kifulog
  table states$%Y%m%d
  schema_path /etc/td-agent/schema.json
</match>

<source>
  @type http
  port 8888
</source>

BigQueryはスキャンした量に対して課金され、毎回テーブルに対してフルスキャンがかかるので同じテーブルに全データを突っ込んでしまうととんでもない費用がかかってしまうことがあるので、日付ごとテーブルを分けて保存する様にしています。

/etc/td-agent/schema.json

[
  {
    "name": "game_code",
    "type": "STRING"
  },
  {
    "name": "first_name",
    "type": "STRING"
  },
  {
    "name": "second_name",
    "type": "STRING"
  },
  {
    "name": "no",
    "type": "INTEGER"
  },
  {
    "name": "move",
    "type": "STRING"
  },
  {
    "name": "position",
    "type": "STRING"
  },
  {
    "name": "is_first",
    "type": "BOOLEAN"
  },
  {
    "name": "first_hand",
    "type": "STRING"
  },
  {
    "name": "second_hand",
    "type": "STRING"
  },
  {
    "name": "moved_at",
    "type": "DATETIME"
  }
]

棋譜データを送る

curlで送ってみます。

curl -X POST -d 'json={"game_code":"ewqfnna","no":4, "first_name": "muutaro729", "second_name": "dcsyhi", "position": "G-H---h-gFCH--h-bfE--H--h-eD-H---h-dA--H--h-aD-H---h-dE-H--h--eFBH---hcfG-H---h-g", "first_hand": "", "second_hand": "", "is_first": false, "move": "5354H", "moved_at": "2017-12-11 12:00:00"}' http://localhost:8888/kifulog.http

この様な感じでBigQueryにデータを送ってみて入っていれば完了です。

目的の棋譜データを探す

棋譜データを保存しても目的のデータが探し出せなくては意味がないので、日付を指定してどの様な対局があるか、特定の囲いがあるかなどをキーとして検索できる様なkifulogというツールを作成しました。

github.com

コマンド

今回日付ごとにテーブルを分けている為クエリを実行する際には必ず期間を指定する様にしています。

対局一覧

指定日付の期間内での対局一覧を表示します

bundle exec ruby kifulog.rb game 20171201 20171207

出力

2017/11/18 10:00 > ewqfnna
2017/11/18 10:30 > inzqina
2017/11/19 11:00 > viziqfh

対局の表示

対局の詳細を表示します。

bundle exec ruby kifulog.rb show 20171201 20171207 ewqfnna

出力

CLIで操作することができます。

コマンドはこんな感じです

  • 数字を入力するとその局面に飛ぶ
  • nまたはnextと入力すると次の局面に移る
  • qまたはquitと入力すると終了

f:id:nitamago_monster:20171211011259g:plain

囲いの検索

指定の囲いを使用している対局を検索します。

bundle exec ruby kifulog.rb castle 20171201 20171207 mino

出力

ewqfnna : 30手目
zeiwzyq : 35手目
pzoqnae : 28手目

の様にgame_codeと何手目に最初に囲いが現れたかが表示されます。これで気になった対局を show コマンドで表示できます。

BigQueryではクエリに正規表現が使えることを活かしてこの様な正規表現でマッチさせています。局面を81文字で表すことにより正規表現での抽出がしやすくなっています。以下美濃囲いの場合の正規表現です(厳密な美濃囲いではなく近い状態で検索に引っかかる様にしています)

(.{45}D.{8}-EH.{6}FAH.{6}G-(H.{6}|-H.{5})|(.{6}h|.{5}h-)-g.{6}haf.{6}he-.{8}d)

BigQueryに投げているクエリとしてはこんな感じです。

"SELECT * FROM kifulog.states WHERE REGEXP_CONTAINS(position, '(.{45}D.{8}-EH.{6}FAH.{6}G-(H.{6}|-H.{5})|(.{6}h|.{5}h-)-g.{6}haf.{6}he-.{8}d)') AND _PARTITIONTIME BETWEEN TIMESTAMP('20171201') AND TIMESTAMP('20171207') LIMIT 1000"

最後に

いかがだったでしょうか。来年もこれまで以上に将棋が盛り上がっていくことを願っております。

カヤックでは無類の将棋好きも、そうでない方も募集しております!

次回はまっちーによるHoudiniの記事です。期待!!