* 将棋の棋譜相互変換、棋譜から戦術抽出、超弱いAIなどのライブラリ

[[file:https://www.shogi-extend.com/cpu-battle][file:https://raw.github.com/akicho8/bioshogi/master/bioshogi.png]]
[[https://www.shogi-extend.com/cpu-battle][デモ]]

#+BEGIN_SRC ruby
container = Container::Basic.start
[
  "7六歩", "8四歩", "7八金", "3二金",
  "2六歩", "8五歩", "7七角", "3四歩",
  "8八銀", "7七角成",
].each{|input|
  container.execute(input)
}
puts container.board
pp container.to_kif_a
# >>   9 8 7 6 5 4 3 2 1
# >> +---------------------------+
# >> |v香v桂v銀v金v玉 ・v銀v桂v香|一
# >> | ・v飛 ・ ・ ・ ・v金 ・ ・|二
# >> |v歩 ・v歩v歩v歩v歩 ・v歩v歩|三
# >> | ・ ・ ・ ・ ・ ・v歩 ・ ・|四
# >> | ・v歩 ・ ・ ・ ・ ・ ・ ・|五
# >> | ・ ・ 歩 ・ ・ ・ ・ 歩 ・|六
# >> | 歩 歩v馬 歩 歩 歩 歩 ・ 歩|七
# >> | ・ 銀 金 ・ ・ ・ ・ 飛 ・|八
# >> | 香 桂 ・ ・ 玉 金 銀 桂 香|九
# >> +---------------------------+
# >> ["▲7六歩(77)",
# >>  "▽8四歩(83)",
# >>  "▲7八金(69)",
# >>  "▽3二金(41)",
# >>  "▲2六歩(27)",
# >>  "▽8五歩(84)",
# >>  "▲7七角(88)",
# >>  "▽3四歩(33)",
# >>  "▲8八銀(79)",
# >>  "▽7七角成(22)"]
#+END_SRC

** サンプルコード

*** 初期配置文字列のパース

#+BEGIN_SRC ruby
info = Soldier.from_str("7六と")
info[:place].name # => "7六"
info[:promoted]   # => true
info[:piece].name # => "歩"
#+END_SRC

*** AI同士の対局 (ルールを守った上でランダムに指してみる)

#+BEGIN_SRC ruby
container = Container::Basic.start
loop do
  hand = container.current_player.brain.create_all_hands.sample
  container.execute(hand)
  captured_soldier = container.opponent_player.executor.captured_soldier
  if executor.captured_soldier && executor.captured_soldier.piece.key == :king
    break
  end
end

p container
# >> 78手目: ▽後手番
# >>   9 8 7 6 5 4 3 2 1
# >> +---------------------------+
# >> | 角v桂v銀 ・v金 ・ ・ ・v香|一
# >> |v香 ・ ・ ・ ・v銀 ・ ・ 歩|二
# >> |v歩v歩v飛v歩 圭 ・ ・ ・v桂|三
# >> | ・ ・v歩 ・v金 ・ ・v歩 ・|四
# >> | ・ ・ ・ ・ 角 歩 ・ 歩v歩|五
# >> | ・ ・ 歩 ・ ・v歩v歩 ・ ・|六
# >> | 歩 歩 ・ 歩 歩 ・ ・ ・ 銀|七
# >> | 香 ・ ・ 金 ・ 飛 金 ・ 香|八
# >> | ・ 銀 ・ 玉 ・ ・ ・ 桂 ・|九
# >> +---------------------------+
# >> ▲先手の持駒:歩 玉
# >> ▽後手の持駒:歩

puts container.to_ki2_a.group_by.with_index{|v, i|i / 8}.values.collect{|v|v.join(" ")}
# >> ▲3八金(39) ▽4六歩打 ▲1二歩打 ▽3六歩(35) ▲5三桂成(65)
# >> ▲4八玉 ▽5二金 ▲5六歩 ▽6二飛 ▲1六歩 ▽7二飛 ▲5九玉 ▽6二玉
# >> ▲2六歩 ▽4二金 ▲4八飛 ▽5四歩 ▲7八銀 ▽5一金 ▲7六歩 ▽5五歩
# >> ▲同歩 ▽3二金 ▲5八飛 ▽3四歩 ▲7七桂 ▽9二香 ▲1八飛 ▽4二金
# >> ▲6八金 ▽8二飛 ▲2八銀 ▽4四歩 ▲1五歩 ▽4三金 ▲1七銀 ▽2四歩
# >> ▲6五桂 ▽3三金 ▲9八香 ▽4二銀 ▲2八飛 ▽3一角 ▲2七飛 ▽5二玉
# >> ▲9九角 ▽4三金 ▲3九金 ▽4五歩 ▲8九銀 ▽1四歩 ▲5四歩 ▽1三桂
# >> ▲4六歩 ▽6二飛 ▲4五歩 ▽5四金 ▲5七歩打 ▽2二角 ▲1八香 ▽5三玉
# >> ▲2五歩 ▽7四歩 ▲6九玉 ▽5二飛 ▲2六飛 ▽4八歩打 ▲2八飛 ▽7二飛
# >> ▲4八飛 ▽5五角 ▲同角 ▽7三飛 ▲3六歩 ▽3五歩 ▲9一角打 ▽1五歩
# >> ▲3八金 ▽4六歩打 ▲1二歩打 ▽3六歩 ▲5三桂成
#+END_SRC

*** 第23期竜王戦七番勝負第6局の再生

    KIFファイルを読み込み

#+BEGIN_SRC ruby
info = Bioshogi::Parser.parse(Pathname("ryuou20101214.kif"))
pp info.pi.header
# >> {"対局ID"=>"333",
# >>  "開始日時"=>"2010/12/14 9:00",
# >>  "終了日時"=>"2010/12/15 19:13",
# >>  "表題"=>"竜王戦",
# >>  "棋戦"=>"第23期竜王戦七番勝負第6局",
# >>  "持ち時間"=>"各8時間",
# >>  "消費時間"=>"146▲479△471",
# >>  "場所"=>"岐阜・ホテルアソシア高山リゾート",
# >>  "手合割"=>"平手",
# >>  "先手"=>"羽生善治",
# >>  "後手"=>"渡辺 明"}
#+END_SRC

    棋譜を入力していく

#+BEGIN_SRC ruby
container = Container::Basic.start
info.pi.move_infos.each{|info|
  container.execute(info[:input])
}
#+END_SRC

    最終形

#+BEGIN_SRC ruby
puts container.inspect
# >> 147:▲先手番
# >>   9 8 7 6 5 4 3 2 1
# >> +---------------------------+
# >> | ・v桂 ・ ・ 馬 ・ ・v桂v香|一
# >> |v飛 ・ ・ ・ ・ と ・ ・ ・|二
# >> | ・ ・ ・ 全v歩 ・v玉 ・ ・|三
# >> | ・ ・ ・ ・ ・ ・v桂 ・v金|四
# >> | ・v歩 ・ ・ ・ 銀v歩v歩v歩|五
# >> |v歩 ・ 歩v角 ・ ・ ・ ・ ・|六
# >> | ・ 歩 銀v歩vと ・ ・ ・ ・|七
# >> | 歩 ・ 玉 香 ・ ・ ・ ・ 香|八
# >> | 香 桂 ・ ・ ・ ・ 飛 ・ ・|九
# >> +---------------------------+
# >> blackの持駒:歩三金
# >> whiteの持駒:金二歩三銀
#+END_SRC

    KIF形式の棋譜確認

#+BEGIN_SRC ruby
puts container.to_kif_a.group_by.with_index{|v, i|i / 8}.values.collect{|v|v.join(" ")}
# >> ▲7六歩(77) ▽8四歩(83) ▲7八金(69) ▽3二金(41) ▲2六歩(27) ▽8五歩(84) ▲7七角(88) ▽3四歩(33)
# >> ▲8八銀(79) ▽7七角成(22) ▲7七銀(88) ▽4二銀(31) ▲3八銀(39) ▽7二銀(71) ▲9六歩(97) ▽9四歩(93)
# >> ▲4六歩(47) ▽6四歩(63) ▲4七銀(38) ▽6三銀(72) ▲6八玉(59) ▽3三銀(42) ▲5八金(49) ▽5四銀(63)
# >> ▲3六歩(37) ▽4二玉(51) ▲7九玉(68) ▽6五歩(64) ▲5六銀(47) ▽5二金(61) ▲1六歩(17) ▽1四歩(13)
# >> ▲3七桂(29) ▽3一玉(42) ▲4七金(58) ▽4四歩(43) ▲2五歩(26) ▽4三金(52) ▲8八玉(79) ▽2二玉(31)
# >> ▲4八金(47) ▽4二金(43) ▲2九飛(28) ▽4三金(42) ▲1八香(19) ▽9二香(91) ▲2八飛(29) ▽4二金(43)
# >> ▲2六飛(28) ▽5二金(42) ▲2九飛(26) ▽4三金(52) ▲2八飛(29) ▽4二金(43) ▲2七飛(28) ▽5二金(42)
# >> ▲4五歩(46) ▽4三金(52) ▲4四歩(45) ▽4四金(43) ▲2九飛(27) ▽4三金(44) ▲4六角打 ▽9三香(92)
# >> ▲4五歩打 ▽4二金(43) ▲4七銀(56) ▽9二飛(82) ▲3五歩(36) ▽3五歩(34) ▲3五角(46) ▽6四角打
# >> ▲5六歩(57) ▽9五歩(94) ▲9五歩(96) ▽9六歩打 ▲5七角(35) ▽9五香(93) ▲9八歩打 ▽3四歩打
# >> ▲3六銀(47) ▽7四歩(73) ▲1五歩(16) ▽1五歩(14) ▲2四歩(25) ▽2四銀(33) ▲2五銀(36) ▽4六歩打
# >> ▲2四銀(25) ▽2四歩(23) ▲8三銀打 ▽5二飛(92) ▲7四銀成(83) ▽9一角(64) ▲2四飛(29) ▽2三金(32)
# >> ▲2六飛(24) ▽2五歩打 ▲2五桂(37) ▽2四歩打 ▲1二歩打 ▽1二玉(22) ▲8四角(57) ▽4七歩成(46)
# >> ▲4七金(48) ▽1四金(23) ▲9五角(84) ▽2五歩(24) ▲3六飛(26) ▽2三玉(12) ▲5五歩(56) ▽4五銀(54)
# >> ▲3九飛(36) ▽4六歩打 ▲3六金(47) ▽3六銀(45) ▲3六飛(39) ▽4七歩成(46) ▲6三全(74) ▽9二飛(52)
# >> ▲5一角成(95) ▽6九銀打 ▲4五銀打 ▽2二桂打 ▲4三歩打 ▽3三金(42) ▲3五歩打 ▽3五歩(34)
# >> ▲3九飛(36) ▽7八銀成(69) ▲7八玉(88) ▽5五角(91) ▲3四歩打 ▽3四桂(22) ▲4二歩成(43) ▽5七と(47)
# >> ▲6九香打 ▽6六歩(65) ▲6六歩(67) ▽6八歩打 ▲6八香(69) ▽6七歩打 ▲4四銀打 ▽6六角(55)
# >> ▲3三銀成(44) ▽3三玉(23)
#+END_SRC

    KI2形式の棋譜確認

#+BEGIN_SRC ruby
puts container.to_ki2_a.group_by.with_index{|v, i|i / 8}.values.collect{|v|v.join(" ")}
# >> ▲7六歩 ▽8四歩 ▲7八金 ▽3二金 ▲2六歩 ▽8五歩 ▲7七角 ▽3四歩
# >> ▲8八銀 ▽7七角成 ▲同銀 ▽4二銀 ▲3八銀 ▽7二銀 ▲9六歩 ▽9四歩
# >> ▲4六歩 ▽6四歩 ▲4七銀 ▽6三銀 ▲6八玉 ▽3三銀 ▲5八金 ▽5四銀
# >> ▲3六歩 ▽4二玉 ▲7九玉 ▽6五歩 ▲5六銀 ▽5二金 ▲1六歩 ▽1四歩
# >> ▲3七桂 ▽3一玉 ▲4七金 ▽4四歩 ▲2五歩 ▽4三金 ▲8八玉 ▽2二玉
# >> ▲4八金 ▽4二金 ▲2九飛 ▽4三金 ▲1八香 ▽9二香 ▲2八飛 ▽4二金
# >> ▲2六飛 ▽5二金 ▲2九飛 ▽4三金 ▲2八飛 ▽4二金 ▲2七飛 ▽5二金
# >> ▲4五歩 ▽4三金 ▲4四歩 ▽同金 ▲2九飛 ▽4三金 ▲4六角打 ▽9三香
# >> ▲4五歩打 ▽4二金 ▲4七銀 ▽9二飛 ▲3五歩 ▽同歩 ▲同角 ▽6四角打
# >> ▲5六歩 ▽9五歩 ▲同歩 ▽9六歩打 ▲5七角 ▽9五香 ▲9八歩打 ▽3四歩打
# >> ▲3六銀 ▽7四歩 ▲1五歩 ▽同歩 ▲2四歩 ▽同銀 ▲2五銀 ▽4六歩打
# >> ▲2四銀 ▽同歩 ▲8三銀打 ▽5二飛 ▲7四銀成 ▽9一角 ▲2四飛 ▽2三金
# >> ▲2六飛 ▽2五歩打 ▲同桂 ▽2四歩打 ▲1二歩打 ▽同玉 ▲8四角 ▽4七歩成
# >> ▲同金 ▽1四金 ▲9五角 ▽2五歩 ▲3六飛 ▽2三玉 ▲5五歩 ▽4五銀
# >> ▲3九飛 ▽4六歩打 ▲3六金 ▽同銀 ▲同飛 ▽4七歩成 ▲6三全 ▽9二飛
# >> ▲5一角成 ▽6九銀打 ▲4五銀打 ▽2二桂打 ▲4三歩打 ▽3三金 ▲3五歩打 ▽同歩
# >> ▲3九飛 ▽7八銀成 ▲同玉 ▽5五角 ▲3四歩打 ▽同桂 ▲4二歩成 ▽5七と
# >> ▲6九香打 ▽6六歩 ▲同歩 ▽6八歩打 ▲同香 ▽6七歩打 ▲4四銀打 ▽6六角
# >> ▲3三銀成 ▽同玉
#+END_SRC

*** 駒が動ける場所

#+BEGIN_SRC ruby
container = Container::Basic.start
player = container.player_at(:black)
player.soldier_create("5五馬")
player.soldiers.first.move_list.each{|place|
  player.soldier_create("#{place}馬")
}
puts container.board
# >>   9 8 7 6 5 4 3 2 1
# >> +---------------------------+
# >> | 馬 ・ ・ ・ ・ ・ ・ ・ 馬|一
# >> | ・ 馬 ・ ・ ・ ・ ・ 馬 ・|二
# >> | ・ ・ 馬 ・ ・ ・ 馬 ・ ・|三
# >> | ・ ・ ・ 馬 馬 馬 ・ ・ ・|四
# >> | ・ ・ ・ 馬 馬 馬 ・ ・ ・|五
# >> | ・ ・ ・ 馬 馬 馬 ・ ・ ・|六
# >> | ・ ・ 馬 ・ ・ ・ 馬 ・ ・|七
# >> | ・ 馬 ・ ・ ・ ・ ・ 馬 ・|八
# >> | 馬 ・ ・ ・ ・ ・ ・ ・ 馬|九
# >> +---------------------------+
#+END_SRC

*** 座標のパース

    Placeクラス経由で扱えばだいたいパース可

#+BEGIN_SRC ruby
Place["4三"].name   # => "4三"
Place["4三"].name  # => "4三"
Place["43"].name    # => "4三"
#+END_SRC

    内部では別の座標

#+BEGIN_SRC ruby
Place["4三"].to_xy  # => [5, 2]
#+END_SRC

    引数が配列だったときのみスルー

#+BEGIN_SRC ruby
Place[[5, 2]].to_xy # => [5, 2]
#+END_SRC

*** 駒の情報取得例

#+BEGIN_SRC ruby
pp Piece["飛"].to_h
# >> {name: "飛",
# >>  promoted_name: "龍",
# >>  basic_names: ["飛", "rook"],
# >>  promoted_names: ["龍", "ROOK", "竜"],
# >>  names: ["飛", "rook", "龍", "ROOK", "竜"],
# >>  key: :rook,
# >>  :promotable=>true,
# >>  basic_once_vectors: [],
# >>  basic_repeat_vectors: [nil, [0, -1], nil, [-1, 0], [1, 0], nil, [0, 1], nil],
# >>  promoted_once_vectors: # >>   [[-1, -1], [0, -1], [1, -1], [-1, 0], nil, [1, 0], [-1, 1], [0, 1], [1, 1]],
# >>  promoted_repeat_vectors: [nil, [0, -1], nil, [-1, 0], [1, 0], nil, [0, 1], nil]}
#+END_SRC

*** 盤面テキストのパース

#+BEGIN_SRC ruby
board = <<-EOT
+---------------------------+
| ・v桂 ・ ・ 馬 ・ ・v桂v香|
|v飛 ・ ・ ・ ・ と ・ ・ ・|
| ・ ・ ・ 全v歩 ・v玉 ・ ・|
| ・ ・ ・ ・ ・ ・v桂 ・v金|
| ・v歩 ・ ・ ・ 銀v歩v歩v歩|
|v歩 ・ 歩v角 ・ ・ ・ ・ ・|
| ・ 歩 銀v歩vと ・ ・ ・ ・|
| 歩 ・ 玉 香 ・ ・ ・ ・ 香|
| 香 桂 ・ ・ ・ ・ 飛 ・ ・|
+---------------------------+
EOT
BoardParser.parse(board)
# => {
  white: {
    "8一桂", "2一桂", "1一香", "9二飛", "5三歩", "3三玉", "3四桂", "1四金",
    "8五歩", "3五歩", "2五歩", "1五歩", "9六歩", "6六角", "6七歩", "5七と",
  },
  black: {
    "5一馬", "4二と", "6三全", "4五銀", "7六歩", "8七歩", "7七銀", "9八歩",
    "7八玉", "6八香", "1八香", "9九香", "8九桂", "3九飛",
  },
}
#+END_SRC

*** KIF形式の盤面表示と盤面の駒の確認

#+BEGIN_SRC ruby
container = Container::Basic.start
container.piece_plot
puts container.board

container.board["5五"]      # => nil
container.board["8八"].name # => "▲8八角"
container.board["2八"].name # => "▲2八飛"
container.board["5九"].name # => "▲5九玉"
# >>   9 8 7 6 5 4 3 2 1
# >> +---------------------------+
# >> |v香v桂v銀v金v玉v金v銀v桂v香|一
# >> | ・v飛 ・ ・ ・ ・ ・v角 ・|二
# >> |v歩v歩v歩v歩v歩v歩v歩v歩v歩|三
# >> | ・ ・ ・ ・ ・ ・ ・ ・ ・|四
# >> | ・ ・ ・ ・ ・ ・ ・ ・ ・|五
# >> | ・ ・ ・ ・ ・ ・ ・ ・ ・|六
# >> | 歩 歩 歩 歩 歩 歩 歩 歩 歩|七
# >> | ・ 角 ・ ・ ・ ・ ・ 飛 ・|八
# >> | 香 桂 銀 金 玉 金 銀 桂 香|九
# >> +---------------------------+
#+END_SRC

*** 5五将棋の例

#+BEGIN_SRC ruby
Dimension.wh_change([5, 5])
container = Container::Basic.start
soldiers = ["5五玉", "4五金", "3五銀", "2五角", "1五飛", "5四歩"]
container.players.each do |player|
  _soldiers = soldiers.collect{|s|
    s = Soldier.from_str(s)
    s.merge(place: s[:place].flip_if_white(player.location))
  }
  player.soldier_create(_soldiers)
end
container.piece_box_clear
p container
# >> 1手目: ▲先手番
# >>   5 4 3 2 1
# >> +---------------+
# >> |v飛v角v銀v金v玉|一
# >> | ・ ・ ・ ・v歩|二
# >> | ・ ・ ・ ・ ・|三
# >> | 歩 ・ ・ ・ ・|四
# >> | 玉 金 銀 角 飛|五
# >> +---------------+
# >> ▲先手の持駒:
# >> ▽後手の持駒:

container.execute("2四銀")
container.execute("4二銀")
container.execute("3四角")
container.execute("3二角")
container.execute("2三銀")
container.execute("4三銀")
container.execute("1二銀")
container.execute("同金")
container.execute("同角")
p container
# >> 10手目: ▽後手番
# >>   5 4 3 2 1
# >> +---------------+
# >> |v飛 ・ ・ ・v玉|一
# >> | ・ ・v角 ・ 角|二
# >> | ・v銀 ・ ・ ・|三
# >> | 歩 ・ ・ ・ ・|四
# >> | 玉 金 ・ ・ 飛|五
# >> +---------------+
# >> ▲先手の持駒:歩 金
# >> ▽後手の持駒:銀
#+END_SRC

*** NegaMax法のログの見方

    3x3の盤面で対角線上に歩がある場合の駆け引き

#+BEGIN_SRC ruby
Bioshogi.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
Dimension.wh_change([3, 3]) do
  container = Container::Basic.new
  container.board.placement_from_human("▲3三歩 △1一歩")
  puts container
  pp Diver::NegaAlphaDiver.run(player: container.player_at(:black), depth_max: 1)
end
# >> 1手目: ▲先手番
# >>   3 2 1
# >> +---------+
# >> | ・ ・v歩|一
# >> | ・ ・ ・|二
# >> | 歩 ・ ・|三
# >> +---------+
# >> ▲先手の持駒:
# >> ▽後手の持駒:
# >>    0  試指 ▲3二歩(33) (1/2)
# >> 葉 1      試指 ▽1二歩(11) (1/2)
# >> 葉 1      評価 ▽1二歩(11)    +0
# >> 葉 1      試指 ▽1二歩成(11) (2/2)
# >> 葉 1      評価 ▽1二歩成(11) +1100
# >> 葉 1      確定 ▽1二歩成(11) +1100 候補:[▽1二歩成(11)(1100) ▽1二歩(11)(0)]
# >>    0  評価 ▲3二歩(33) -1100
# >>    0  試指 ▲3二歩成(33) (2/2)
# >> 葉 1      試指 ▽1二歩(11) (1/2)
# >> 葉 1      評価 ▽1二歩(11) -1100
# >> 葉 1      試指 ▽1二歩成(11) (2/2)
# >> 葉 1      評価 ▽1二歩成(11)    +0
# >> 葉 1      確定 ▽1二歩成(11)    +0 候補:[▽1二歩成(11)(0) ▽1二歩(11)(-1100)]
# >>    0  評価 ▲3二歩成(33)    +0
# >>    0  確定 ▲3二歩成(33)    +0 候補:[▲3二歩成(33)(0) ▲3二歩(33)(-1100)]
# >> {:hand=>"▲3二歩成(33)",
# >>  :score=>0,
# >>  :level=>0,
# >>  :reading_hands=>["▲3二歩成(33)", "▽1二歩成(11)"]}
#+END_SRC

- 自分(先手)には「3二歩」「3二歩成」の二通りの手があることがわかり、どっちにするか問題。
- それぞれ指したとき、後手側になってみていちばん良くなる手を探す。
- 自分にとってはそれは負なのでマイナスとする
- 「3二歩」のとき後手は「1二歩」「1二歩成」の二通りを考えていて「1二歩成」の方が良いとわかる。+1100点。
- 自分にとってはそれは負なので「▲3二歩」なら -1100 点。
- 同様に「▲3二歩成」なら 0 点。
- -1100 になる手と、0点になる手なら当然0点になる「▲3二歩成」を指した方がいいという結果になる

*** ミクロコスモスの棋譜を読む

#+BEGIN_SRC ruby
puts Parser.file_parse("microcosmos.kif").to_bod
# >> 後手の持駒:飛二 角 銀 桂二 香 歩三
# >>   9 8 7 6 5 4 3 2 1
# >> +---------------------------+
# >> | ・ ・ ・ と と と と 杏 ・|一
# >> | ・v金 ・ ・ ・ ・ ・ ・ ・|二
# >> | ・v桂 ・ ・v歩v歩v歩v歩 ・|三
# >> | ・ ・ とv銀v金vと ・ ・ ・|四
# >> |v馬v圭 香 ・ ・ ・ ・ 銀 ・|五
# >> | ・ 歩 歩 ・ ・ ・ と ・ 香|六
# >> | ・ ・ ・ ・ ・ ・ ・ ・ ・|七
# >> | ・ ・ ・ ・ とv銀 歩 ・ ・|八
# >> | ・ ・ ・ ・ ・ ・ 金 金v玉|九
# >> +---------------------------+
# >> 先手の持駒:なし
# >> 手数=1525 ▲2九金打 まで
# >>
# >> 後手番
#+END_SRC

*** 詰みチェック

#+BEGIN_EXAMPLE
container = Container::Basic.new
container.placement_from_preset("平手")
brain = container.opponent_player.brain

require 'active_support/core_ext/benchmark'
Benchmark.ms { brain.iterative_deepening(depth_max_range: 0..0).none? }           # => 18.85599992237985
Benchmark.ms { container.opponent_player.create_all_hands(legal_only: true).any? } # => 0.674000009894371
Benchmark.ms { container.opponent_player.create_all_hands(legal_only: true).to_a } # => 19.73800011910498
#+END_EXAMPLE

** 仕様

*** 棋譜サフィックス語の解釈

    | コマンド | 意味               | 詳細                                                                       |
    |----------+--------------------+----------------------------------------------------------------------------|
    | 右       | 右の方のを選択     | 移動元を指定座標より右で絞る(龍馬は例外で指定座標を無視し左右の方向)       |
    | 左       | 左の方のを選択     | 移動元を指定座標より左で絞る(龍馬は例外で指定座標を無視し左右の方向)       |
    | 上       | 下の方のを上げる   | 移動元を指定座標より下で絞る                                               |
    | 引       | 上の方のを引く     | 移動元を指定座標より上で絞る。下げるから "下" と書いてしまいがちなので注意 |
    | 寄       | 横一列の中から選択 | 移動元を指定座標のY座標で絞る                                              |
    | 直       | 縦一列の中から選択 | 移動元を指定座標のX座標で絞る                                              |

    もっと簡単に

#+BEGIN_EXAMPLE
    ↓引く

                   右の方にあるやつ

●    ← 寄せる

         ↑もち上げる
↑
直
#+END_EXAMPLE

*** 棋譜の表記

    | 表記       | 意味                     |
    |------------+--------------------------|
    | 7六歩(77) | 7七の歩を7六に移動     |
    | 7六歩     | 7六歩(77) の省略形      |
    | 2二角成   | 2二に角が移動して成った |
    | 5五飛打   | 5五に持駒の飛車を打った |
    | 同歩       | 1手前の座標に歩を移動    |

*** 主な例外

    | 例外                 | 意味                                           | どんなときに起きる?                                                                    |
    |----------------------+------------------------------------------------+-----------------------------------------------------------------------------------------|
    | BioshogiError        | すべての例外の親                               |                                                                                         |
    | MustNotHappen        | ありえない処理                                 | ブログラムがバグっている                                                                |
    | AmbiguousFormatError | 指定座標に移動できる駒が多すぎる               | 初手 "▲5八金"                                                                         |
    | SyntaxDefact         | とりあえず表記が違う                           | 駒の配置時に "4二銀成" とした                                                          |
    | PieceNotFound        | 指定の名前の駒が存在しない                     | 龍のつもりで蛇と書いた                                                                  |
    | HoldPieceNotFound    | 打を明示したのにその駒を持っていない           | 飛車を持ってないのに初手 "▲55飛打"                                                     |
    | HoldPieceNotFound2   | 打の省略型かと思ったがそれも違う               | 飛車を持ってないのに初手 "▲55飛"                                                       |
    | PieceAlredyExist     | 自分の駒の上に自分の駒を初期配置               | 配置時に2連続で "9七歩"                                                                |
    | AlredyPromoted       | すでに成っている                               | "5五" の龍を "5一飛成"                                                                |
    | BeforePlaceNotFound  | 「同」に対する座標が不明                       | 初手 "同歩"                                                                             |
    | RuleError            | 反則系例外の親                                 | 二歩など                                                                                |
    | CommonError          | 黙認できる例外の親                             | いまのところ二歩と手番間違いの2つだけ                                                   |
    | SamePlaceDifferent   | 座標と「同」を同時に指定したが一致しない       | "同歩" だけでいいのに "2四同歩" と場所を明示したとき、その前の指し手が "2四○" でない |
    | TimeMinusError       | 消費時間がマイナスになっている                 | CSAの読み込みのとき不整合が起きやすい。1分の持ち時間なのに1手1分で2手指したときなど     |
    | FileFormatError      | ファイルのフォーマットがおかしい               | KIFファイルとして画像ファイルを読ませた                                                 |
    | KeyNotFound          | 手合割・囲い・戦型などのあるはずのデータがない | 手合割で「二枚落ち」と書くところを「飛車角落ち」と書いた                                |
    | BrainProcessingHeavy | 処理が重すぎる                                 | 反復深化深さ優先探索でのタイムリミットが短かすぎて指し手を生成できない                  |

    反則系 (RuleError のサブクラス)

    こちらはプログラムの不具合や、棋譜の入力ミスに起因するものが多いので例外を出す

    | 例外                            | 意味                                               | どんなときに起きる?               |
    |---------------------------------+----------------------------------------------------+------------------------------------|
    | NoPromotablePiece               | "成" "不成" は指定できない                         | 1三金不成、3三玉成               |
    | NotFoundOnBoard                 | 盤面に指定の駒がない                               | 2七に歩がないのに2六歩(27)とした |
    | SoldierWarpError     | 指定座標に移動できる駒に移動元の駒が含まれていない | 初手 "▲25歩(27)"                  |
    | DeadPieceRuleError              | 死に駒(行きどころのない駒)を作った                 | ▲1一桂                           |
    | PromotedPiecePutOnError         | 成った状態で打とうとした                           | 5五龍打                           |
    | PromotedPieceToNormalPiece      | 成駒を成ってない状態に戻そうとした                 | 5五龍を5六飛                     |
    | SamePlayerBattlerOverwrideError | 自分の駒の上に自分の駒を指した                     | 初手 "8八飛(28)"                  |
    | ReversePlayerPieceMoveError     | 相手の駒を動かそうとした                           | ▲の手番で初手 "3四歩"            |
    | MovableBattlerNotFound          | 指定座標に移動できる駒が一つもない                 | 平手で初手 "▲3四歩" や ▲22角成  |

    一部の例外を抑制できる系の反則

    これらは棋譜にでてくるので別扱いにする

    | 例外                     | 意味     | どんなときに起きる?     |
    |--------------------------+----------+--------------------------|
    | DoublePawnCommonError    | 二歩     | 歩がある縦列に歩を打った |
    | DifferentTurnCommonError | 手番違い | 平手で初手△3四歩       |

*** 表示座標系

    | 9    | 8 |    7 | 6 | 5 | 4 |    3 | 2 | 1    |    |
    |------+---+------+---+---+---+------+---+------+----|
    | 9一 |   |      |   |   |   |      |   | 1一 | 一 |
    |      |   |      |   |   |   |      |   |      | 二 |
    |      |   |      |   |   |   | 3三 |   | 1三 | 三 |
    |      |   |      |   |   |   |      |   |      | 四 |
    |      |   |      |   |   |   |      |   |      | 五 |
    |      |   |      |   |   |   |      |   |      | 六 |
    |      |   | 7七 |   |   |   |      |   |      | 七 |
    |      |   |      |   |   |   |      |   |      | 八 |
    | 9九 |   |      |   |   |   |      |   | 1九 | 九 |

*** コード座標系

    |   | 0   | 1 |   2 | 3 | 4 | 5 |   6 | 7 | 8   |
    |---+-----+---+-----+---+---+---+-----+---+-----|
    | 0 | 0,0 |   |     |   |   |   |     |   | 8,0 |
    | 1 |     |   |     |   |   |   |     |   |     |
    | 2 |     |   |     |   |   |   | 6,2 |   | 8,2 |
    | 3 |     |   |     |   |   |   |     |   |     |
    | 4 |     |   |     |   |   |   |     |   |     |
    | 5 |     |   |     |   |   |   |     |   |     |
    | 6 |     |   | 2,6 |   |   |   |     |   |     |
    | 7 |     |   |     |   |   |   |     |   |     |
    | 8 | 0,8 |   |     |   |   |   |     |   | 8,8 |

*** 棋譜のパース

    - "7六歩" の場合 "7六" と "歩" に分離する
    - "2二角成" の場合 "2二" と "角" と "成" に分離する
    - 同銀の場合、同がどこを差しているのか、前の座標を見る
    - "5八金右" の場合、5八から見て右下にある金が斜め上に上がったという意味なのでこの解釈が難しい
    - "4八" に金があった場合、"5八金右" は真横の金なのか、斜め下の金なのか、どっちだろう
    - ネット上にある棋譜はだいたい "7六歩(77)" の形式になっていて7七にあったことを明示しているのでがんばって推測しなくてもいい

*** 棋譜ファイルの形式についての考察

**** KIFフォーマット

#+BEGIN_EXAMPLE
# ----  Kifu for Windows V6.22 棋譜ファイル  ----
開始日時:2000/01/01 00:00:00
終了日時:2000/01/01 01:00:00
棋戦:(棋戦)
持ち時間:(持ち時間)
手合割:平手  
先手:(先手)
後手:(後手)
手数----指手---------消費時間--
*対局前コメント
   1 7六歩(77)   ( 0:10/00:00:10)
*コメント1
   2 3四歩(33)   ( 0:10/00:00:20)
   3 6六歩(67)   ( 0:10/00:00:30)
   4 8四歩(83)   ( 0:10/00:00:40)
*コメント2
   5 投了         ( 0:10/00:00:50)
まで4手で後手の勝ち
#+END_EXAMPLE

    - 移動元が明記されているためプログラム的に扱いやすい
    - 例えば "5八金右" は "5八金(49)" と表わすので「右」の方にある金を探さなくても済む
    - ヘッダーは KI2 と共通でよい
    - ヘッダーとコンテンツを分けるセパレーターは */^手数.*/* らしい。基本、これがあるかどうかで KIF or KIF2 の判別ができそう
    - コメントは *直前の指し手* に結び付いている
    - しかし対局前のコメントは *結び付く指し手がない* ため別データ扱いと考える方がよさそう
    - 「投了」は指し手とは別に管理した方がよい。指し手に「投了」が入ってくるとプログラムが複雑になるため
    - 「投了」の横の時間がある場合、それはどれだけ考えて投了したかを表す情報なので、見ないといけない。
    - 「投了」がないと将棋山脈ではKIFと見なされない。なお激指は「投了」を入れてくれない。
    - アスタリスクで始まるコメント部分には何を書いてもいいというのを利用して一手目の上に開始前メッセージがあるのがおかしい。結び付く手がない。開始前メッセージはヘッダーに入れる仕様だとよかった。
    - コメントには「#」と「*」の2つがあり、「#」はプログラム用のコメントで「*」は中継記者のコメント
    - 手合割の値の最後に謎の全角スペース2つあるのは謎。とくに気にしなくてもよいみたい
    - 棋譜部分の手数番号の前のインデントはなくてもよい
    - 激指(定跡道場4)は最後の指し手が「中断, 投了, 持将棋, 千日手, 詰み, 切れ負け」以外になっていると読み込めない。ここで何か判断しているようでもないので、この行は不要かもしれない。ただ投了までの時間を入れるにはこの行がいる。
    - 手数は必ず 1 から始まらないといけないっぽい (1以外から始まると多くのソフトがエラーになる)
    - 局面図を入れるときは、BOD を埋める。ただし「手数=数字」を入れると、エラーになるソフトもある
    - 局面図を入れるときの手番は「後手番」の行だけを入れる。※もともとBODに含まれている
    - 局面図を入れるときの手番と図面の間の空行は KIF のときは無くてよい (Kifu for Windows のフォーマットがそうだった)

**** KI2フォーマット

#+BEGIN_EXAMPLE
開始日時:2000/01/01 00:00
終了日時:2000/01/01 01:00
表題:(表題)
棋戦:(棋戦)
戦型:(戦型)
持ち時間:(持ち時間)
場所:(場所)
掲載:(掲載)
立会人:(立会人)
副立会人:(副立会人)
記録係:(記録係)
Web Page:(Web Page)
通算成績:(通算成績)
先手:(先手)
後手:(後手)

*対局前コメント
▲7六歩    △3四歩
*コメント1
▲6六歩△8四歩
*コメント2
まで4手で後手の勝ち
#+END_EXAMPLE

    - "5八金右" "同歩" など人が書いた風の棋譜になっている
    - ヘッダーとコンテンツを分けるセパレーターは *最初の空行* (2021-11-10 ではなかった)
    - 指し手は横に何個並んでもいいっぽい
    - 指し手のセパレータは *空白ではない* 。くっついている場合もあるので三角マークの前で区切る
    - なお *△* ではなく *▽* の場合もあるので *▲△▼▽* の4つに対応すること
    - *投了* がない (2021-11-10 ことはない)
    - "#" もない(?)
    - *まで○手で○手の勝ち* は必要みたい (2021-11-10 ない場合もある )

**** Kifu for iPhone のフォーマット

#+BEGIN_EXAMPLE
# Kifu for iPhone V4.50 棋譜ファイル
開始日時:2021/11/04 22:09:41
棋戦:R対局 早指し2(猶予1分)
▲7六歩△3四歩▲2六歩△4四歩
▲4八銀△5四歩▲投了
#+END_EXAMPLE

    - ずっとヘッダーとコンテンツを分けるセパレーターは最初の空行と信じていたがなんと空行がなかった
    - なので空行の有無で分けてはいけない
    - また投了 *投了* がないと思われていたが、あった

*** 英語表記対応表

    | 日本語   | 英語     |
    |----------+----------|
    | 歩       | pawn     |
    | 角       | bishop   |
    | 飛       | rook     |
    | 香       | lance    |
    | 桂       | knight   |
    | 銀       | silver   |
    | 金       | gold     |
    | 玉       | king     |
    | 成った   | promoted |
    | 盤面     | board    |
    | 座標     | place    |
    | 相対座標 | vector   |
    | 先手     | black    |
    | 後手     | white    |
    | 対局室   | container |

** コマンドラインツール

#+BEGIN_SRC shell
% bioshogi --help
Commands:
  bioshogi convert         # 棋譜フォーマット変換
  bioshogi help [COMMAND]  # Describe available commands or one specific command
  bioshogi input_checker    # 入力
  bioshogi piece           # 駒一覧
  bioshogi versus          # CPU同士の対戦

Options:
  [--debug], [--no-debug]
  [--quiet], [--no-quiet]
#+END_SRC

** 参考リンク集

   - 棋譜の形式について http://wiki.optus.nu/shogi/index.php?post=%B4%FD%C9%E8%A4%CE%B7%C1%BC%B0%A4%CB%A4%C4%A4%A4%A4%C6
   - 二歩 - Wikipedia http://ja.wikipedia.org/wiki/%E4%BA%8C%E6%AD%A9#cite_note-4
   - CC Resources for Shogi Applications | 将棋アプリ用クリエイティブコモンズ画像 http://mucho.girly.jp/bona/
   - 将棋所:USIプロトコルとは http://www.geocities.jp/shogidokoro/usi.html
   - CSA標準棋譜ファイル形式 http://www.computer-shogi.org/protocol/record_v22.html

** ライセンス

   - Ricty Diminished https://www.rs.tus.ac.jp/yyusa/ricty_diminished.html
   - 駒画像 Portella https://creativecommons.org/licenses/by-nc-sa/4.0/