棒取りゲームを作ってみた

を見て、考えた棒取りゲームの必勝法をプログラムにしてみようと思い立った。
以下ソース

#!/usr/bin/ruby

class BokeshiField
    def initialize(arr)
        @state=[]
        arr.each{|n|
            @state=@state+[[true]*n]
        }
    end

    def state
        @state.map{|row|
            row.dup
        }
    end

    def print_field
        puts "  01234567890123456789"
        @state.each_index{|i|
            print "#{i} "
            @state[i].each{|x|
                print x ? "|" : "+"
            }
            print "\n"
        }
    end

    def outoffield(arow,acol)
        if arow<0 || @state.length<=arow
            puts "Bad row number"
            true
        elsif acol<0 || @state[arow].length<=acol
            puts "Bad column number"
            true
        else
            false
        end
    end

    def delrange(arow,acol1,acol2)
        if outoffield(arow,acol1) || outoffield(arow,acol2) 
            return false
        end
        if acol1>acol2
            tmp=acol1
            acol1=acol2
            acol2=tmp
        end

        if @state[arow][acol1..acol2].inject(true){|x,y|x & y}
            for i in acol1 .. acol2
                @state[arow][i]=false
            end
            return true
        else
            puts "Bad number"
            return false
        end
    end

    def endgame
        b=false
        @state.each{|row|
            row.each{|x|
                b=b|x
            }
        }
        !b
    end
end

class BokeshiCPU
    def hand(field)
        state=bar_position_length(field.state)

        case count_gt1(state)
        when 0
            return [state[0][0],state[0][1]]
        when 1
            state.each{|x|
                if x[2]>1
                    l=state.length%2>0?x[2]-1:x[2]
                    return [x[0],x[1],x[1]+l-1]
                end
            }
        else
            r=0
            state.each{|x|
                r=r^x[2]
            }

            if r==0
                return rand_hand(state)
            else
                hb=highest_bit(r)

                state.each{|x|
                    if x[2]&hb != 0
                        l=x[2]-(x[2]^r)
                        return [x[0],x[1],x[1]+l-1]
                    end
                }
            end
        end
    end

    def bar_position_length(state)
        arr=[]
        state.each_index{|i|
            row=state[i]

            n=0
            while n<row.length
                b=search_from(row,n,true)
                n=search_from(row,b,false)
                if n>b
                    arr=arr+[[i,b,n-b]]
                end
            end
        }
        arr
    end

    def search_from(arr,from,val)
        n=from
        while n<arr.length && arr[n]!=val
            n=n+1
        end
        n
    end

    def count_gt1(s)
        c=0
        s.each{|x|
            if x[2]>1
                c=c+1
            end
        }
        c
    end

    def highest_bit(n)
        mask=-1
        while mask&n != 0
            mask=mask*2
        end
        mask=mask/2
        mask & n
    end

    def rand_hand(s)
        n=rand(s.length)
        m=rand(s[n][2])
        [s[n][0],s[n][1]+m]   # 1 本だけ消して相手のミスを待つ

        #l=rand(s[n][2])
        #[s[n][0],s[n][1]+m,s[n][1]+l]   # 複数の棒を消させたい場合
    end
end

class BokeshiGame
    def initialize(players=[:human,:cpu],bars=[3,5,7])
        @fld=BokeshiField.new(bars)
        @cpu=BokeshiCPU.new
        @players=players
        @playerid=0

        puts "init"
        @fld.print_field
    end

    def game_start
        while(true)
            if @players[@playerid]==:human
                puts "your hand"
                m=gets.split.map{|s|s.to_i}
                if m.length<2
                    puts "Bad numbers"
                    next
                end
            else
                puts "cpu's hand"
                m=@cpu.hand(@fld)
            end

            if m.length==2
                suc=@fld.delrange(m[0],m[1],m[1])
            else
                suc=@fld.delrange(m[0],m[1],m[2])
            end

            if suc
                if @fld.endgame
                    show_end
                    break
                end
                @playerid=1-@playerid
                @fld.print_field
            end
        end
    end

    def show_end
        if @players[1-@playerid]==:human
            puts "you win"
        else
            puts "cpu win"
        end
    end
end

game=BokeshiGame.new([:human,:cpu],[1,2,3,4,5])
game.game_start