Please Forward: Ruby Quiz Submission

From: Wolfgang Seeberg <wolfgang.seeberg@yahoo.com>
Date: April 17, 2007 11:53:11 AM CDT
To: submission@rubyquiz.com
Subject: Please Forward: Ruby Quiz Submission

# Usage: ruby -s q120.rb [-maxfinger=7] [-depth=50]
# works for maxfinger<=9.
# finds a forced loss for maxfinger=4 at "1111".
# uses complete retroanalysis, i.e. one can in principle
# play "as good as God" (K. Thompson) with its output.
class Finger
    Maxscore = 99999
    Thresh = (0.9 * Maxscore).to_i

    def initialize(mf = nil, d = nil)
        @maxfinger = (mf || 4).to_i
        @depth = (d || 30).to_i
        @movehash = {}
        @scorehash = {}
    end

    def main()
        scanall()
        condense()
        @depth.times do
            findbadnews()
            findgoodnews()
        end
        showresults()
    end

    def showresults()
        puts <<EOS

The two leftmost digits always belong to the player who is to make
a move. The "Moves" columns show the resulting positions after
that player has moved. All positions are "normalized", i.e. the
left hand has never more fingers up than the right hand.

EOS
        printf "Pos. Score Moves\n"
        list = @scorehash.sort
        list.each do | k, v |
            if (v == @movehash[k])
                printf "%04d %s ", k, "=" * 6
            else
                printf "%04d %6d ", k, v
            end
            @movehash[k].each do |item|
                printf " %04d", item
            end
            printf "\n"
        end
    end

    def d1(x, n)
        if x < 100
            s = sprintf("%02d", x)
        else
            s = sprintf("%04d", x)
        end
        return s[n - 1, 1].to_i
    end

    def left(x)
        return x / 100
    end

    def right(x)
        return x % 100
    end

    def n2(x)
        if (d1(x, 2) < d1(x, 1))
            return 10 * d1(x, 2) + d1(x, 1)
        else
            return x
        end
    end

    # in many places the code requires normalized positions.
    def normalize(x)
        return n2(left(x)) * 100 + n2(right(x))
    end

    def add(a, b)
        if (a <= 0 || b <= 0)
            return nil
        elsif (a + b > @maxfinger)
            return 0
        else
            return a + b
        end
    end

    def pushadd(x, i, j, k, hash, list)
        a = add(d1(x, i), d1(x, j))
        return list if a == nil
        mv = normalize(a * 1000 + 100 * d1(x, k) + left(x))
        if (!(hash.has_key? mv))
            hash[mv] = 1
            list << mv
        end
        return list
    end

    # allowed: 11->02
    # forbidden: 12->21
    def clap(a, b, n)
        if (a + n <= @maxfinger && b >= n)
            r = n2(10 * (a + n) + b - n)
            if r != (10 * a + b) then
                return r
            end
        end
        return nil
    end

    def pushclap(normalpos, n, hash, list)
        c = clap(d1(normalpos, 1), d1(normalpos, 2), n)
        return list if c == nil
        mv = right(normalpos) * 100 + c
        if (mv != right(normalpos) * 100 && !(hash.has_key? mv))
            list << mv
            hash[mv] = 1
        end
        return list
    end

    def moves(normalpos)
        list =
        hash = {}
        list = pushadd(normalpos, 1, 3, 4, hash, list)
        list = pushadd(normalpos, 2, 3, 4, hash, list)
        list = pushadd(normalpos, 1, 4, 3, hash, list)
        list = pushadd(normalpos, 2, 4, 3, hash, list)
        for i in 1 ..@maxfinger - 1
            list = pushclap(normalpos, i, hash, list)
        end
        return list
    end

    def scan1(x)
        normalpos = normalize(x)
        m = moves(normalpos)
        if (m.size > 0)
            @movehash[normalpos] = m * 1
            @scorehash[normalpos] = m * 1
        end
    end

    def scanall()
        for i in 1 .. @maxfinger
            for j in 0 ..i
                for k in 1 .. @maxfinger
                    for m in 0 .. k
                        scan1(i * 1000 + j * 100 + k * 10 + m)
                    end
                end
            end
        end
    end

    def condense()
        @scorehash.each_key do | i |
            list = @scorehash[i]
            list.each do | item |
                if item < 100
                    @scorehash[i] = Maxscore
                    break
                end
            end
        end
    end

    def findbadnews()
        @scorehash.each_key do | i |
            list = @scorehash[i]
            if list.class.to_s != 'Array'
                list = [list * 1]
            end
            m = Maxscore + 1
            list.each do | ak |
                if (ak > Thresh)
                    m = 0
                    break
                elsif ((@scorehash.has_key? ak) &&
                        @scorehash[ak].class.to_s == "Fixnum" &&
                        @scorehash[ak] > Thresh)
                    m = [m, @scorehash[ak]].min()
                else
                    m = 0
                    break
                end
            end
            # all moves lead to winning positions for the opponent.
            if (m > 0)
                @scorehash[i] = -m + 1
            end
        end
    end

    def findgoodnews()
        @scorehash.each_key do | i |
            list = @scorehash[i]
            if list.class.to_s != 'Array'
                next
            end
            m = Maxscore + 1
            list.each do | ak |
                # a definite loss for the opponent.
                if ((@scorehash.has_key? ak) &&
                    @scorehash[ak].class.to_s == "Fixnum" &&
                    @scorehash[ak] < -Thresh.to_i)
                    m = [m, @scorehash[ak]].min()
                end
            end
            if (m < 0)
                @scorehash[i] = -m - 1
            end
        end
    end
end # class Finger

Finger.new($maxfinger, $depth).main()

#-------------------- End ---------------------------

__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com

···

Begin forwarded message: