Hmmm.... tried running your client through my tester, and it ends up
trying to draw cards from a pile that it can't, resulting in an infinite
loop of "There are no cards there" errors. This could be due to
line-wrapping in the email though - I already had to fix up this code:

       cards.last != @last_discard and cards.any? { |card| playable? 
(card) }

So that it was all on one line, otherwise it tries to call playable with
no args.

(to make your player work with my tester, I also changed all references
of "@hand" to "@my_hand").

You player works successfully against the dumb_player - I guess that's
how you tested it. But it doesn't play against itself or my player
(which I'll post in a couple of hours).

When you designed your game, you probably should have kept the player
intelligence separate from the player record keeping. I probably would
have also put the human-readable rendering of the output in the client
and made the server just spit out the data - but I'm guessing the
original focus of your game was PvP, and these suggestions only really
affect the design in the AI situation.

> -----Original Message-----
> From: James Edward Gray II [mailto:james / grayproductions.net] 
> Sent: Wednesday, 19 October 2005 8:18 AM
> To: ruby-talk ML
> Subject: Re: [SOLUTION] Lost Cities (#51)
> 
> Well, it's not brilliant yet, but I've run out of time to 
> keep tweaking the risk analysis.  Here's my passible first 
> crack at a solution.
> 
> Hopefully someone will step in with a player that slaughters him...
> 
> James Edward Gray II
> 
> #!/usr/local/bin/ruby -w
> 
> class RiskPlayer < Player
>    def self.card_from_string( card )
>      value, land = card[0..-2], card[-1, 1].downcase
>      Game::Card.new( value[0] == ?I ? value : value.to_i,
>                      Game::LANDS.find { |l| l[0, 1] == land } )
>    end
> 
>    def initialize
>      @piles = Hash.new do |piles, player|
>        piles[player] = Hash.new { |pile, land| pile[land] = 
> Array.new }
>      end
> 
>      @deck_size = 60
>      @hand      = nil
> 
>      @last_dicard = nil
> 
>      @action = nil
>    end
> 
>    def show( game_data )
>      if game_data =~ /^(Your?)(?: opponent)? (play|discard)s? 
> the (\w+)/
>        card = self.class.card_from_string($3)
>        if $2 == "play"
>          if $1 == "You"
>            @piles[:me][card.land] << card
>          else
>            @piles[:them][card.land] << card
>          end
>        else
>          @piles[:discards][card.land] << card
>        end
> 
>        @last_discard = nil if $1 == "Your"
>      end
> 
>      if game_data =~ /^\s*Deck:\s+#+\s+\((\d+)\)/
>        @deck_size = $1.to_i
>      end
>      if game_data =~ /^\s*Hand:((?:\s+\w+)+)/
>        @hand = $1.strip.split.map { |c| 
> self.class.card_from_string(c) }
>      end
> 
>      if game_data.include?("Your play?")
>        @action = :play_card
>      elsif game_data.include?("Draw from?")
>        @action = :draw_card
>      end
>    end
> 
>    def move
>      send(@action)
>    end
> 
>    private
> 
>    def play_card
>      plays, discards = @hand.partition { |card| playable? card }
> 
>      if plays.empty?
>        discard_card(discards)
>      else
>        risks   = analyze_risks(plays)
>        risk    = risks.max { |a, b| a.last <=> b.last }
> 
>        return discard_card(@hand) if risk.last < 0
> 
>        land    = risks.max { |a, b| a.last <=> b.last }.first.land
>        play    = plays.select { |card| card.land == land }.
>                        sort_by { |c| c.value.is_a?(String) ? 0 :  
> c.value }.first
>        "#{play.value}#{play.land[0, 1]}".sub("nv", "")
>      end
>    end
> 
>    def discard_card( choices )
>      discard = choices.sort_by do |card|
>        [ playable?(card) ? 1 : 0, playable?(card, :them) ? 1 : 0,
>          card.value.is_a?(String) ? 0 : card.value ]
>      end.first
> 
>      @last_discard = discard
>      "d#{discard.value}#{discard.land[0, 1]}".sub("nv", "")
>    end
> 
>    def draw_card
>      want = @piles[:discards].find do |land, cards|
>        not @piles[:me][land].empty? and
>        cards.last != @last_discard and cards.any? { |card| playable? 
> (card) }
>      end
>      if want
>        want.first[0, 1]
>      else
>        "n"
>      end
>    end
> 
>    def analyze_risks( plays )
>      plays.inject(Hash.new) do |risks, card|
>        risks[card] = 0
> 
>        me_total = ( @piles[:me][card.land] +
>                     plays.select { |c| c.land == card.land }
>                     ).inject(0) do |total, c|
>          if c.value.is_a? String
>            total
>          else
>            total + c.value
>          end
>        end
>        risks[card] += 20 - me_total
> 
>        them_total = @piles[:them][card.land].inject(0) do |total, c|
>          if c.value.is_a? String
>            total
>          else
>            total + c.value
>          end
>        end
>        high = card.value.is_a?(String) ? 2 : card.value
>        risks[card] += ( (high..10).inject { |sum, n| sum + n }
>                         - (me_total + them_total) ) / 2
> 
>        if @piles[:me][card.land].empty?
>          lands_played = @piles[:me].inject(0) do |count, 
> (land, cards)|
>            if cards.empty?
>              count
>            else
>              count + 1
>            end
>          end
> 
>          risks[card] -= (lands_played + 1) * 5
>        end
> 
>        risks
>      end
>    end
> 
>    def playable?( card, who = :me )
>      @piles[who][card.land].empty? or
>      @piles[who][card.land].last.value.is_a?(String) or
>      ( not card.value.is_a?(String) and
>        @piles[who][card.land].last.value < card.value )
>    end
> end
> 
> __END__
> 
> 
> 
> 
#####################################################################################
This email has been scanned by MailMarshal, an email content filter.
#####################################################################################