【Ruby】将棋ウォーズの棋譜から自分の苦手な戦法を割り出す
みなさん、将棋ライフは楽しんでいるでしょうか。
現在自分はウォーズ2級で伸び悩んではやふた月。
漫然とさしててもしょうがないと思い、しっかりと自分の棋譜を分析することにした。
自分の戦法は嬉野流一本。効率よく勝率を上げるために相手の戦型ごとの勝率を出して、優先的に対策を立てるべき戦型を割り出すことに。
大まかに以下の流れ。
- ウォーズから棋譜リンク取得
- converterでkif形式に変換
- 相手の飛車の振り先を集計、分析
まずはウォーズから棋譜のリンクを取得する。 2019年8月までは棋譜取得ツールが使えたようだが、仕様変更で現在は利用不可だそう。 のでクローラーを自作。 注意点としてはウォーズにログイン済のユーザプロファイルが必要。 一度Chromeでウォーズにログインし、その際に利用したユーザプロファイルを指定する。
require 'selenium-webdriver' class ShogiWarsScraper def initialize(username) @username = username end def geturls(url) urllist = [] options = Selenium::WebDriver::Chrome::Options.new options.add_argument("--headless") options.add_argument("--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36") options.add_argument('--start-maximized') options.add_argument("--disable-dev-shm-usage") options.add_argument("--no-sandbox") options.add_argument("--disable-setuid-sandbox") #ウォーズに認証済のブラウザのプロファイルが必要。 options.add_argument("--user-data-dir=~/Library/Application Support/Google/Chrome/Profile 1") driver = Selenium::WebDriver.for :chrome, options: options driver.navigate.to(url) wait = Selenium::WebDriver::Wait.new(:timeout => 3000) # seconds elements = wait.until { driver.find_elements(:class, 'game_replay') } elements.each do |v| urllist << v.find_element(:xpath, ".//a")[:href] end driver.quit urllist end def makewarslist(max_page) #warsより棋譜リンクを取得。max 8 urllist = [] for i in 1..max_page do url = "https://shogiwars.heroz.jp/games/history?&locale=ja&user_id=#{@username}&page=#{i}" #s = ShogiWarsScraper.new urllist << self.geturls(url) sleep(2) end urllist.flatten!.each do |v| file = File.open("warsurllist.txt", "a") do |f| f.puts(v) end end end end
ウォーズの棋譜については、csa形式ライクな棋譜がページに埋め込まれている。 良さげな変換機がぱっと見で見つからなかったので、一旦webツール(http://swks.sakura.ne.jp/wars/kifusave/)を利用させてもらうことにする。
require 'selenium-webdriver' class ConverterScraper def doTranslate(url) begin transtool = "http://swks.sakura.ne.jp/wars/kifusave/" options = Selenium::WebDriver::Chrome::Options.new options.add_argument("--headless") options.add_argument("--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36") options.add_argument('--start-maximized') options.add_argument("--disable-dev-shm-usage") options.add_argument("--no-sandbox") options.add_argument("--disable-setuid-sandbox") options.add_argument("--user-data-dir=/Users/K/Library/Application Support/Google/Chrome/Profile 1") driver = Selenium::WebDriver.for :chrome, options: options driver.navigate.to(transtool) # URLを開く wait = Selenium::WebDriver::Wait.new(:timeout => 3000) # seconds element = wait.until { driver.find_element(:class, 'keyword2') } # XPathで指定 element.click element.send_keys(url) sendbutton = driver.find_element(:id, 'searchbtn1').click savebutton = driver.find_element(:class, 'btn1').click kif = driver.find_element(:xpath, '/html/body/pre').text driver.quit rescue end kif end end
これでようやくkifファイルができた。
詳細な分析はやねうら王を使わせてもらうので、kifを分類するために相手の戦型判断は相手の手の開始10手以内の飛車の振り先でのみ判定。
class Kifbunseki def initialize(pn, file) setPlayername(pn) setfile(file) end def setPlayername(pn) @playername = pn end def setfile(file) @filename = file end def getPlayerTeban @file = File.open(@filename, "r") array = @file.to_a sente = array[4].to_s gote = array[5].to_s if sente.include?(@playername) @teban = 0 elsif gote.include?(@playername) @teban = 1 else raise "Invalid PlayerName Error" end end def extractSashite(sentegote) #0:先手 1:後手 @file = File.open(@filename, "r") @sSashite =[] @gSashite =[] array = @file.to_a array -= array[0..6] a_teb =[] array.each do |v| teb = "" v = v.split("") v.each do |vv| teb += vv.match(/[^ -~。-゚]/).to_s.chomp end a_teb << teb end c = a_teb.count for num in 0..c do if num % 2 == 0 @sSashite << a_teb[num] else @gSashite << a_teb[num] end end if sentegote == 0 @sSashite else @gSashite end end def identifySenkei(teban, sashite) analyze_t = sashite[0..10] senkei = "" t = analyze_t.map do |v| v if v.include?("飛") end if teban == 1 #sente # case Boolean if t.include?("7二飛") then senkei = "sode" elsif t.include?("3二飛") then senkei = "sanken" elsif t.include?("4二飛") then senkei = "shiken" elsif t.include?("5二飛") then senkei = "nakabisya" elsif t.include?("2二飛") then senkei = "mukaibisya" elsif t.include?("6二飛") then senkei = "migishiken" else senkei = "ibisya" end else #gote # case t if t.include?("3八飛") then senkei = "sode" elsif t.include?("7八飛") then senkei = "sanken" elsif t.include?("6八飛") then senkei = "shiken" elsif t.include?("5八飛") then senkei = "nakabisya" elsif t.include?("8八飛") then senkei = "mukaibisya" elsif t.include?("4八飛") then senkei = "migishiken" else senkei = "ibisya" end end return senkei end def win?(sashite) if sashite.include?("投了") return 0 else return 1 end end end
実行結果はこんな感じ。
summary==========================
分析対象:119局
相手戦型:ibisya 勝利数:28 敗北数:36 勝率:43.75%
相手戦型:sode 勝利数:1 敗北数:3 勝率:25.0%
相手戦型:sanken 勝利数:11 敗北数:9 勝率:55.0%
相手戦型:shiken 勝利数:12 敗北数:5 勝率:70.59%
相手戦型:mukaibisya 勝利数:2 敗北数:4 勝率:33.33%
相手戦型:migishiken 勝利数:0 敗北数:1 勝率:0.0%
相手戦型:nakabisya 勝利数:3 敗北数:4 勝率:42.86%
相居飛車は得意だと思っていたが、んなことはなかったわ!