From: Daniel Sheppard Date: 2005-10-19T09:18:22+09:00 Subject: Re: [SOLUTION] Lost Cities (#51) 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. #####################################################################################