【Ruby】将棋ウォーズの棋譜から自分の苦手な戦法を割り出す

みなさん、将棋ライフは楽しんでいるでしょうか。

現在自分はウォーズ2級で伸び悩んではやふた月。

漫然とさしててもしょうがないと思い、しっかりと自分の棋譜を分析することにした。

自分の戦法は嬉野流一本。効率よく勝率を上げるために相手の戦型ごとの勝率を出して、優先的に対策を立てるべき戦型を割り出すことに。

 

大まかに以下の流れ。

  1. ウォーズから棋譜リンク取得
  2. converterでkif形式に変換
  3. 相手の飛車の振り先を集計、分析

まずはウォーズから棋譜のリンクを取得する。 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%

 

 

居飛車は得意だと思っていたが、んなことはなかったわ!