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

**URL:** <https://rubytalk.org/t/quiz-lost-cities-51/21600>\
**Category:** ruby-talk\
**Created:** [17 October 2005 08:00 UTC](https://rubytalk.org/t/quiz-lost-cities-51/21600 "2005-10-17T08:00:44Z")\
**Posts on this page:** 6\
**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:** [17 October 2005 08:00 UTC](https://rubytalk.org/t/quiz-lost-cities-51/21600/1 "2005-10-17T08:00:44Z")

</div>

Just had 10 minutes to look at this, and created a quick test harness to  
compare players before I go poking around trying to figure out what kind  
of logic to use.

Pass in the list of ruby files containing players, optionally preceeded  
with a count for the number of games to play, and also a random seed (to  
keep consistent results between runs).

All players will be given the same game to play with (the same set of  
random seeds will be used).

Make sure you call "super" in your player's init to get it setup  
properly.

Hope this encourages more entries.

require 'lost\_cities'  
require 'nano/enumerable/each\_combination'

class Player  
&nbsp;&nbsp;@@players = []  
&nbsp;&nbsp;def self.inherited( subclass )  
&nbsp;&nbsp;&nbsp;&nbsp;@@players \<\< subclass unless subclass == SocketPlayer  
&nbsp;&nbsp;end  
&nbsp;&nbsp;def self.players  
&nbsp;&nbsp;&nbsp;&nbsp;@@players  
&nbsp;&nbsp;end  
end  
class Game  
&nbsp;&nbsp;&nbsp;&nbsp;public :score  
end

count = 10  
count = ARGV.shift.to\_i if /^\d\*$/=== ARGV[0]  
srand(ARGV.shift.to\_i) if /^\d\*$/=== ARGV[0]

ARGV.each { |file| require file } if ARGV.length \> 0

RecordStruct = Struct.new( :type, :wins, :scores )  
records = Hash.new { |h,k| h[k] = RecordStruct.new(k.to\_s, 0, []) }

seeds = Array.new(count) { rand }

play\_proc = lambda do |players|  
&nbsp;&nbsp;&nbsp;&nbsp;p "Playing #{players.inspect}"  
&nbsp;&nbsp;&nbsp;&nbsp;players.map! { |x| x.new }  
&nbsp;&nbsp;&nbsp;&nbsp;seeds.each do |seed|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;srand(seed)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;game = Game.new(\*players)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;until game.over?  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;game.rotate\_player  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;game.play  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;game.draw  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scores = players.map{ |player| game.score(player.lands)  
}.zip(players.map {|player| player.class})  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scores = scores.sort\_by { |a,b| a }  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scores.each { |a,b| records[b].scores \<\< a }  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;records[scores[-1][1]].wins += 1  
&nbsp;&nbsp;&nbsp;&nbsp;end  
end

#~ if(Player.players.size \> 1)  
&nbsp;&nbsp;&nbsp;&nbsp;#~ Player.players.each\_combination(2, &play\_proc)  
#~ else  
&nbsp;&nbsp;&nbsp;&nbsp;Player.players.map{ |x| [x,x] }.each(&play\_proc)  
#~ end

puts  
printf("%-30s %4s %5s %5s %5s", "Class", "Wins", "Avg.", "Min.",  
"Max.")  
puts  
records.values.sort\_by {|r| -r.wins}.each { |record|  
&nbsp;&nbsp;&nbsp;&nbsp;printf("%-30s %4d %2.2f %2.2f %2.2f",  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;record.type,  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;record.wins,  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;record.scores.inject(0) { |s,x| s + x }  
/record.scores.size.to\_f,  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;record.scores.min,  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;record.scores.max)  
}

> **···**
>
> #####################################################################################  
> This email has been scanned by MailMarshal, an email content filter.  
> #####################################################################################

---

<div class="post-metadata">

**Author:** ![Bob\_Showalter](https://avatars.discourse-cdn.com/v4/letter/b/c57346/32.png) [@Bob\_Showalter](https://rubytalk.org/u/Bob_Showalter)\
**Post date:** [17 October 2005 14:24 UTC](https://rubytalk.org/t/quiz-lost-cities-51/21600/2 "2005-10-17T14:24:46Z")

</div>

Daniel Sheppard wrote:

> Just had 10 minutes to look at this, and created a quick test harness to  
> compare players before I go poking around trying to figure out what kind  
> of logic to use.

Daniel, this is awesome! I'm learning a ton just studying your code and James'.

There's a small bug in James' lost\_cities.rb that affects your program. Game.new does not clear the player's hand (it does clear everything else). So if a player object is reused to play a second game, he ends up with 16 cards in his hand, and the game is messed up.

As a quick fix, I just added:

&nbsp;&nbsp;&nbsp;players.map { |x| x.hand.clear }

before the call to Game.new() in your code.

---

<div class="post-metadata">

**Author:** ![Bob\_Showalter](https://avatars.discourse-cdn.com/v4/letter/b/c57346/32.png) [@Bob\_Showalter](https://rubytalk.org/u/Bob_Showalter)\
**Post date:** [17 October 2005 15:44 UTC](https://rubytalk.org/t/quiz-lost-cities-51/21600/3 "2005-10-17T15:44:28Z")

</div>

Daniel Sheppard wrote:

> Just had 10 minutes to look at this, and created a quick test harness to  
> compare players before I go poking around trying to figure out what kind  
> of logic to use.

OK, one more minor glitch with the harness. The random number seeding doesn't work correctly. I get the exact same deck with every game.

> seeds = Array.new(count) { rand }
> 
> play\_proc = lambda do |players|  
> &nbsp;&nbsp;&nbsp;&nbsp;p "Playing #{players.inspect}"  
> &nbsp;&nbsp;&nbsp;&nbsp;players.map! { |x| x.new }  
> &nbsp;&nbsp;&nbsp;&nbsp;seeds.each do |seed|  
> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;srand(seed)

seeds is intialized with a list of nubers 0 \<= n \< 1. These are then later passed to Kernel.srand. However, srand's argument is converted using to\_i, so all these calls are equivalent to srand(0).

My fix is:

&nbsp;&nbsp;&nbsp;seeds = Array.new(count) { rand(1 \<\< (8 \* 1.size))}

I'm trying to generate random seeds over the range of a Fixnum. Is there a more appropriate way to do that?

---

<div class="post-metadata">

**Author:** ![Bob\_Showalter](https://avatars.discourse-cdn.com/v4/letter/b/c57346/32.png) [@Bob\_Showalter](https://rubytalk.org/u/Bob_Showalter)\
**Post date:** [17 October 2005 15:15 UTC](https://rubytalk.org/t/quiz-lost-cities-51/21600/4 "2005-10-17T15:15:39Z")

</div>

I've found another glitch that affects the automation of players with Daniel's harness.

In James' dumb\_player.rb, the following line captures the cards in the players hand:

&nbsp;&nbsp;if @plays.nil? and @data =~ /Hand: (.+?)\s\*$/

The regex captures up to the end of the line containing "Hand:". When you play this in the client-server mode, each line passed to show() end with a newline (since :show is delegated to :puts), and so the regex works.

When you play this under Daniel's harness, the lines passed to show() don't have newlines added, since each player directly calls the opponent's show(). The last card is immediately followed by the next line starting with "Score:", and so the regex captures extraneous "cards" (and the eighth card is incorrectly parsed as "8VScore:", for example).

One way to resolve this is to change the dumb\_player.rb line:

&nbsp;&nbsp;&nbsp;@data \<\< game\_data

To:

&nbsp;&nbsp;&nbsp;@data \<\< game\_data.chomp \<\< "\n"

That way, you get the same data when you play through the client-server mode or through the harness.

I wonder how this would best be handled inside lost\_cities.rb or the harness itself?

---

<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:** [17 October 2005 19:04 UTC](https://rubytalk.org/t/quiz-lost-cities-51/21600/5 "2005-10-17T19:04:58Z")

</div>

Yes, Game.new() expects to be given fresh players. Good catch.

James Edward Gray II

> **···**
>
> On Oct 17, 2005, at 9:24 AM, Bob Showalter wrote:
> 
> > Daniel Sheppard wrote:
> > 
> > > Just had 10 minutes to look at this, and created a quick test harness to  
> > > compare players before I go poking around trying to figure out what kind  
> > > of logic to use.
> > 
> > Daniel, this is awesome! I'm learning a ton just studying your code and James'.
> > 
> > There's a small bug in James' lost\_cities.rb that affects your program. Game.new does not clear the player's hand (it does clear everything else). So if a player object is reused to play a second game, he ends up with 16 cards in his hand, and the game is messed up.
> > 
> > As a quick fix, I just added:
> > 
> > &nbsp;&nbsp;players.map { |x| x.hand.clear }
> > 
> > before the call to Game.new() in your code.

---

<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:** [17 October 2005 19:06 UTC](https://rubytalk.org/t/quiz-lost-cities-51/21600/6 "2005-10-17T19:06:53Z")

</div>

Let's not go changing the protocol on people. It's expected to be a line oriented network protocol, so each line should end in a "\n".

James Edward Gray II

> **···**
>
> On Oct 17, 2005, at 10:15 AM, Bob Showalter wrote:
> 
> > I've found another glitch that affects the automation of players with Daniel's harness.
> > 
> > In James' dumb\_player.rb, the following line captures the cards in the players hand:
> > 
> > if @plays.nil? and @data =~ /Hand: (.+?)\s\*$/
> > 
> > The regex captures up to the end of the line containing "Hand:". When you play this in the client-server mode, each line passed to show() end with a newline (since :show is delegated to :puts), and so the regex works.
> > 
> > When you play this under Daniel's harness, the lines passed to show() don't have newlines added, since each player directly calls the opponent's show(). The last card is immediately followed by the next line starting with "Score:", and so the regex captures extraneous "cards" (and the eighth card is incorrectly parsed as "8VScore:", for example).
> > 
> > One way to resolve this is to change the dumb\_player.rb line:
> > 
> > &nbsp;&nbsp;@data \<\< game\_data
> > 
> > To:
> > 
> > &nbsp;&nbsp;@data \<\< game\_data.chomp \<\< "\n"
> > 
> > That way, you get the same data when you play through the client-server mode or through the harness.
> > 
> > I wonder how this would best be handled inside lost\_cities.rb or the harness itself?
