# \[SOLUTION\] Lost Cities (#51)

**URL:** <https://rubytalk.org/t/solution-lost-cities-51/21676>\
**Category:** ruby-talk\
**Created:** [19 October 2005 00:18 UTC](https://rubytalk.org/t/solution-lost-cities-51/21676 "2005-10-19T00:18:22Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![Daniel\_Sheppard](https://avatars.discourse-cdn.com/v4/letter/d/db5fbb/32.png) [@Daniel\_Sheppard](https://rubytalk.org/u/Daniel_Sheppard)\
**Post date:** [19 October 2005 00:18 UTC](https://rubytalk.org/t/solution-lost-cities-51/21676/1 "2005-10-19T00:18:22Z")

</div>

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:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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  
> > &nbsp;&nbsp;&nbsp;def self.card\_from\_string( card )  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value, land = card[0..-2], card[-1, 1].downcase  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Game::Card.new( value[0] == ?I ? value : value.to\_i,  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Game::LANDS.find { |l| l[0, 1] == land } )  
> > &nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;def initialize  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@piles = Hash.new do |piles, player|  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;piles[player] = Hash.new { |pile, land| pile[land] =  
> > Array.new }  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@deck\_size = 60  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@hand = nil
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@last\_dicard = nil
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@action = nil  
> > &nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;def show( game\_data )  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if game\_data =~ /^(Your?)(?: opponent)? (play|discard)s?  
> > the (\w+)/  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;card = self.class.card\_from\_string($3)  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if $2 == "play"  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if $1 == "You"  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@piles[:me][card.land] \<\< card  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@piles[:them][card.land] \<\< card  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@piles[:discards][card.land] \<\< card  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@last\_discard = nil if $1 == "Your"  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if game\_data =~ /^\s\*Deck:\s+#+\s+\((\d+)\)/  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@deck\_size = $1.to\_i  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if game\_data =~ /^\s\*Hand:((?:\s+\w+)+)/  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@hand = $1.strip.split.map { |c|  
> > self.class.card\_from\_string(c) }  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if game\_data.include?("Your play?")  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@action = :play\_card  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elsif game\_data.include?("Draw from?")  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@action = :draw\_card  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;def move  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send(@action)  
> > &nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;private
> > 
> > &nbsp;&nbsp;&nbsp;def play\_card  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;plays, discards = @hand.partition { |card| playable? card }
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if plays.empty?  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;discard\_card(discards)  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;risks = analyze\_risks(plays)  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;risk = risks.max { |a, b| a.last \<=\> b.last }
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return discard\_card(@hand) if risk.last \< 0
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;land = risks.max { |a, b| a.last \<=\> b.last }.first.land  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;play = plays.select { |card| card.land == land }.  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sort\_by { |c| c.value.is\_a?(String) ? 0 :  
> > c.value }.first  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"#{play.value}#{play.land[0, 1]}".sub("nv", "")  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;def discard\_card( choices )  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;discard = choices.sort\_by do |card|  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[ playable?(card) ? 1 : 0, playable?(card, :them) ? 1 : 0,  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;card.value.is\_a?(String) ? 0 : card.value ]  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end.first
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@last\_discard = discard  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"d#{discard.value}#{discard.land[0, 1]}".sub("nv", "")  
> > &nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;def draw\_card  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;want = @piles[:discards].find do |land, cards|  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;not @piles[:me][land].empty? and  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cards.last != @last\_discard and cards.any? { |card| playable?  
> > (card) }  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if want  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;want.first[0, 1]  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"n"  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;def analyze\_risks( plays )  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;plays.inject(Hash.new) do |risks, card|  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;risks[card] = 0
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;me\_total = ( @piles[:me][card.land] +  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;plays.select { |c| c.land == card.land }  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;).inject(0) do |total, c|  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if c.value.is\_a? String  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total + c.value  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;risks[card] += 20 - me\_total
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;them\_total = @piles[:them][card.land].inject(0) do |total, c|  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if c.value.is\_a? String  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;total + c.value  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;high = card.value.is\_a?(String) ? 2 : card.value  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;risks[card] += ( (high..10).inject { |sum, n| sum + n }  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- (me\_total + them\_total) ) / 2
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @piles[:me][card.land].empty?  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;lands\_played = @piles[:me].inject(0) do |count,  
> > (land, cards)|  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if cards.empty?  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count + 1  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;risks[card] -= (lands\_played + 1) \* 5  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;risks  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
> > &nbsp;&nbsp;&nbsp;end
> > 
> > &nbsp;&nbsp;&nbsp;def playable?( card, who = :me )  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@piles[who][card.land].empty? or  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@piles[who][card.land].last.value.is\_a?(String) or  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;( not card.value.is\_a?(String) and  
> > &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@piles[who][card.land].last.value \< card.value )  
> > &nbsp;&nbsp;&nbsp;end  
> > end
> > 
> > \_\_END\_\_
> 
> #####################################################################################  
> This email has been scanned by MailMarshal, an email content filter.  
> #####################################################################################

---

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [19 October 2005 13:11 UTC](https://rubytalk.org/t/solution-lost-cities-51/21676/2 "2005-10-19T13:11:50Z")

</div>

> 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.

I didn't test my player using your tester, so it may not work there.

> You player works successfully against the dumb\_player - I guess that's  
> how you tested it.

Na, I played against it.

> When you designed your game, you probably should have kept the player  
> intelligence separate from the player record keeping.

Well, I did design it for the players to be at opposite ends of a socket, communicating through a protocol.

I do agree that I made a few mistakes in the design though. Sorry about that.

James Edward Gray II

> **···**
>
> On Oct 18, 2005, at 7:18 PM, Daniel Sheppard wrote:

---

<div class="post-metadata">

**Author:** ![Anthony\_Moralez1](https://avatars.discourse-cdn.com/v4/letter/a/d78d45/32.png) [@Anthony\_Moralez1](https://rubytalk.org/u/Anthony_Moralez1)\
**Post date:** [19 October 2005 14:18 UTC](https://rubytalk.org/t/solution-lost-cities-51/21676/3 "2005-10-19T14:18:32Z")

</div>

Hi Daniel and James-

> 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:
> 
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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").

Thanks for those fixes. I found the other problem. Risk player keeps  
track of the discards and his last discard but never notices if he  
picks one up. Below are the fixes I used to get it to run. But I  
think I might have changed it's behavior.

> \> 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)  
> \>  
> \> 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

@piles[:discard][discard.land].delete 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 (not cards.find { |card| @my\_hand.include? card } ) and

> **···**
>
> On 10/18/05, Daniel Sheppard \<daniels@pronto.com.au\> wrote:
> 
> > \> -----Original Message-----  
> > \> cards.last != @last\_discard and cards.any? { |card| playable?  
> > \> (card) }  
> > \> end  
> > \> if want  
> > \> want.first[0, 1]  
> > \> else  
> > \> "n"  
> > \> end  
> > \> end

---

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [19 October 2005 14:40 UTC](https://rubytalk.org/t/solution-lost-cities-51/21676/4 "2005-10-19T14:40:03Z")

</div>

Oh, good find. That's a bug.

Actually, he never notices if ANYONE picks up a discard. Oops.

Attached is a fixed version, zipped to prevent wrapping issues.

My thanks to you both, for helping me fix this.

James Edward Gray II

[risk\_player.zip](https://rubytalk.org/uploads/short-url/ivJ3NqXplZjefRQE79owvLCebYQ.zip) (1.41 KB)

> **···**
>
> On Oct 19, 2005, at 9:18 AM, Anthony Moralez wrote:
> 
> > Thanks for those fixes. I found the other problem. Risk player keeps  
> > track of the discards and his last discard but never notices if he  
> > picks one up.
