From: James Edward Gray II Date: 2005-10-19T07:18:25+09:00 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__