[QUIZ] Paper Rock Scissors (#16)

R

Ruby Quiz

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.grayproductions.net/ruby_quiz/

3. Enjoy!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Alright generals, break out you copies of "The Art of War" and let's get a
little competition going!

Your task is to build a strategy for playing the game of Paper Rock Scissors
against all manner of opponents. The question here is if you can adapt to an
opponent's strategy and seize the advantage, while he is doing the same to you
of course.

If you're not familiar with this childhood game, it's very simple. Both players
choose one of three items at the same time: Paper, a Rock, or Scissors. A
"winner" is chosen by the following rules:

Paper covers a Rock. (Paper beats a Rock.)
Scissors cut Paper. (Scissors beat Paper.)
A Rock smashes Scissors. (A Rock beats Scissors.)
Anything else is a "draw".

Defining a player for straight forward. I'm providing a class you can just
inherit from:

class YourPlayer < Player
def initialize( opponent )
# optional
#
# called at the start of a match verses opponent
# opponent = String of opponent's class name
#
# Player's constructor sets @opponent
end

def choose
# required
#
# return your choice of :paper, :rock or :scissors
end

def result( you, them, win_lose_or_draw )
# optional
#
# called after each choice you make to give feedback
# you = your choice
# them = opponent's choice
# win_lose_or_draw = :win, :lose or :draw, your result
end
end

We'll need some rules for defining players, to make it easy for all our
strategies to play against each other:

* send in one file for each strategy
* a file should contain exactly one subclass of Player
* start the name of your subclass with your initials
* start the name of your files with your initials
* start any data files you write to disk with your initials

Those rules should help with testing how different algorithms perform against
each other.

Here are two dumb Players to practice with:

class JEGPaperPlayer < Player
def choose
:paper
end
end

class JEGQueuePlayer < Player
QUEUE = [ :rock,
:scissors,
:scissors ]

def initialize( opponent )
super

@index = 0
end

def choose
choice = QUEUE[@index]

@index += 1
@index = 0 if @index == QUEUE.size

choice
end
end

Here's how those two do against each other in a 1,000 game match:

JEGPaperPlayer vs. JEGQueuePlayer
JEGPaperPlayer: 334
JEGQueuePlayer: 666
JEGQueuePlayer Wins

Finally, here's the game engine that supports the players:

#!/usr/bin/env ruby

class Player
@@players = [ ]

def self.inherited( player )
@@players << player
end

def self.each_pair
(0...(@@players.size - 1)).each do |i|
((i + 1)...@@players.size).each do |j|
yield @@players, @@players[j]
end
end
end

def initialize( opponent )
@opponent = opponent
end

def choose
raise NoMethodError, "Player subclasses must override choose()."
end

def result( you, them, win_lose_or_draw )
# do nothing--sublcasses can override as needed
end
end

class Game
def initialize( player1, player2 )
@player1 = player1.new(player2.to_s)
@player2 = player2.new(player1.to_s)
@score1 = 0
@score2 = 0
end

def play( match )
match.times do
hand1 = @player1.choose
hand2 = @player2.choose
case hand1
when :paper
case hand2
when :paper
draw hand1, hand2
when :rock
win @player1, hand1, hand2
when :scissors
win @player2, hand1, hand2
else
raise "Invalid choice by #{@player2.class}."
end
when :rock
case hand2
when :paper
win @player2, hand1, hand2
when :rock
draw hand1, hand2
when :scissors
win @player1, hand1, hand2
else
raise "Invalid choice by #{@player2.class}."
end
when :scissors
case hand2
when :paper
win @player1, hand1, hand2
when :rock
win @player2, hand1, hand2
when :scissors
draw hand1, hand2
else
raise "Invalid choice by #{@player2.class}."
end
else
raise "Invalid choice by #{@player1.class}."
end
end
end

def results
match = "#{@player1.class} vs. #{@player2.class}\n" +
"\t#{@player1.class}: #{@score1}\n" +
"\t#{@player2.class}: #{@score2}\n"
if @score1 == @score2
match + "\tDraw\n"
elsif @score1 > @score2
match + "\t#{@player1.class} Wins\n"
else
match + "\t#{@player2.class} Wins\n"
end
end

private

def draw( hand1, hand2 )
@score1 += 0.5
@score2 += 0.5
@player1.result(hand1, hand2, :draw)
@player2.result(hand2, hand1, :draw)
end

def win( winner, hand1, hand2 )
if winner == @player1
@score1 += 1
@player1.result(hand1, hand2, :win)
@player2.result(hand2, hand1, :lose)
else
@score2 += 1
@player1.result(hand1, hand2, :lose)
@player2.result(hand2, hand1, :win)
end
end
end

match_game_count = 1000
if ARGV.size > 2 and ARGV[0] == "-m" and ARGV[1] =~ /^[1-9]\d*$/
ARGV.shift
match_game_count = ARGV.shift.to_i
end

ARGV.each do |p|
if test(?d, p)
Dir.foreach(p) do |file|
next if file =~ /^\./
next unless file =~ /\.rb$/
require File.join(p, file)
end
else
require p
end
end

Player.each_pair do |one, two|
game = Game.new one, two
game.play match_game_count
puts game.results
end

To use:

paper_rock_scissors.rb jeg_paper_player.rb jeg_queue_player.rb

Or you can point it at a directory and it will treat all the ".rb" files in
there as Players:

paper_rock_scissors.rb players/

You can also change the match game count:

paper_rock_scissors.rb -m 10000 players/
 
A

Avi Bryant

* a file should contain exactly one subclass of Player
* start the name of your subclass with your initials

Why use prefixes on the class names, rather than a module? Wouldn't it
be better for my subclass to be AJB::MyPlayer than AJBMyPlayer?

Avi
 
J

James Edward Gray II

Why use prefixes on the class names, rather than a module? Wouldn't it
be better for my subclass to be AJB::MyPlayer than AJBMyPlayer?

It's a fair style issue you raise and you're probably right. But, I
didn't think of it at the time and I do have reasons for you NOT to do
it that way. (Score print out, for one.) So, just consider it coding
to a specification. ;)

James Edward Gray II
 
B

Benedikt Huber

Here's how those two do against each other in a 1,000 game match:
JEGPaperPlayer vs. JEGQueuePlayer
JEGPaperPlayer: 334
JEGQueuePlayer: 666
JEGQueuePlayer Wins

I would suggest that you need at least 55% to win. (45%-55% is draw)
Otherwise, a strategy simply selecting a random choice will always have a
50% chance of winning - The results of a single run will be highly
unpredictable and change from time to time.
 
A

Avi Bryant

Well, how is this competition going to be scored? A strategy trying to
win the most number of matches will be somewhat different from one
trying to win by the largest margin.
 
A

Avi Bryant

Here's my submission. I take no credit for the basic idea, which is
shamelessly ripped from Dan Egnor's famous "Iocaine Powder" program;
mostly what I was trying to do was provide a clean Ruby implementation
of Dan's insights.

For reasons that will become clear later on, this submission actually
contains several subclasses of Player in the same file - if this is a
problem when running the competition, let me know, it's easy to work
around. However, the "real" player being submitted here is
AJBMetaPlayer.

The basic idea behind AJBMetaPlayer is this: it keeps an array of
other instances of Player subclasses. When it gets handed the
result(), it passes the information along to each of these players;
when it needs to choose(), it picks just one of the players and
returns its choice. It also keeps a running tally on each move of
which of the player instances would have won or lost if it had been
the one chosen for that move, and then uses that when picking a player
for next time. It will always pick the player that would have had the
best record so far, had it been chosen every time.

The interesting twist is that AJBMetaPlayer also makes use of two
PlayerDecorators, which present a Player interface but are wrapped
around existing players. One of these is the Inverter, which reverses
the results that are provided to it: a Player instance wrapped in an
Inverter will see the world as your opponent sees it. The other is
the Defeater, which shifts the choices made in choose() so that they
defeat the original choice (ie, :rock becomes :paper and so on). By
using all of the possible combinations of these, a single Player class
can show up 6 times in the AJBMetaPlayer's players array: normally,
defeated, defeated twice (so :rock becomes :scissors), inverted,
inverted + defeated, inverted + defeated + defeated. This allows it
to model all of the potential second- and triple-guessing an opponent
might be doing. The generic algorithm for picking the best player
instance will automatically detect and exploit this if it's there.

Absent any randomness, if you have a player instance identical to your
opponent in the players array, wrapped with an Inverter (so it gets
the same results your opponent does) and then a Defeater, you will
beat it every time. Indeed, if it were considered legal, a very
effective approach would be to search for all active Player subclasses
and construct an instance of each, wrapped in all the possible
variations. Since this seems a little too much like cheating,
AJBMetaPlayer by default uses only two base strategies that are
hopefully representative of the deterministic players it will
encounter. One of these, AJBFrequencyPlayer, just counters whatever
move its opponent has played most often in the past. The other,
AJBHistoryPlayer, builds a tree of its opponents previous runs of
moves (of lengths 1..20), and assumes that if it finds the same run
again, the player might continue it in the same way. The meta player
should do quite well against players that are either susceptible to
such analysis, *or are using a sufficiently similar analysis
themselves*. For safety, there's also AJBRandomPlayer thrown in by
default, as a fallback if nothing else seems to work.

Here's the code:
# AJBMetaPlayer.rb
# (c) Avi Bryant 2005
# heavily inspired by Dan Egnor's "Iocaine Powder":
# http://dan.egnor.name/iocaine.html

class Hash
def max_key
max{|a,b| a[1]<=>b[1]}[0] unless empty?
end
end

class Symbol
def defeat
case self
when :paper
:scissors
when :scissors
:rock
when :rock
:paper
end
end
end

class AJBRandomPlayer < Player
def choose
[:paper, :scissors, :rock][rand(3)]
end
end

class AJBFrequencyPlayer < AJBRandomPlayer
def initialize(op)
super
@frequencies = Hash.new(0)
end

def choose
(@frequencies.max_key || super).defeat
end

def result(y, t, win)
@frequencies[t] += 1
end
end

class AJBHistoryPlayer < AJBRandomPlayer

class Node
def initialize
@children = {}
@scores = Hash.new(0)
end

def add_child(key)
@scores[key] += 1
@children[key] ||= Node.new
end

def add_scores_to(totals)
@scores.each{|k,v| totals[k] += v}
end
end

MaxNodes = 20

def initialize(op)
super
@nodes = []
@root = Node.new
end

def choose
scores = Hash.new(0)
@nodes.each{|n| n.add_scores_to(scores)}
(scores.max_key || super).defeat
end

def result(y, t, win)
(@nodes << @root).collect!{|n| n.add_child(t)}
@nodes.shift until @nodes.size <= MaxNodes
end
end

class AJBMetaPlayer < Player
class PlayerDecorator
def initialize(player)
@player = player
end
end

class Defeater < PlayerDecorator
def choose
@player.choose.defeat
end

def result(y, t, win)
end
end

class Inverter < PlayerDecorator
def choose
@player.choose
end

def result(y, t, win)
@player.result(t, y, !win)
end
end

def initialize(op)
super
@players = [AJBRandomPlayer.new(op)] +
variations(AJBHistoryPlayer) +
variations(AJBFrequencyPlayer)
@scores = {}
@players.each{|p| @scores[p] = 0}
end

def result(y, t, win)
@players.each{|p| score(p, t)}
@players.each{|p| p.result(y, t, win)}
end

def choose
@scores.max_key.choose
end

:private

def variations(klass)
straight = klass.new(@opponent)
inverted = Inverter.new(klass.new(@opponent))
[straight,
inverted,
Defeater.new(straight),
Defeater.new(inverted),
Defeater.new(Defeater.new(straight)),
Defeater.new(Defeater.new(inverted))]
end

def score(player, move)
@scores[player] += ScoreTable[[player.choose, move]]
end

ScoreTable =
{[:scissors, :rock] => -1,
[:scissors, :scissors] => 0,
[:scissors, :paper] => 1,
[:paper, :rock] => 1,
[:paper, :scissors] => -1,
[:paper, :paper] => 0,
[:rock, :rock] => 0,
[:rock, :scissors] => 1,
[:rock, :paper] => -1}
end
 
J

Jannis Harder

--------------000402080705020908050800
Content-Type: text/plain; charset=ISO-8859-15; format=flowed
Content-Transfer-Encoding: 7bit

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

This is my first player (Markov chain based)
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (Darwin)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFB87jh5YRWfc27RzQRAoqpAJ4uEzEGtiKqZpgl7wRJopUofihhywCcDK+k
eG/3jZfl12/u8KFhfHqxuwE=
=RXxb
-----END PGP SIGNATURE-----


--------------000402080705020908050800
Content-Type: text/plain; x-mac-type="2A2A2A2A"; x-mac-creator="48647261";
name="jix_player_m.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="jix_player_m.rb"

class JIXPlayerM < Player
WIN = {:paper => :scissors, :rock => :paper , :scissors => :rock}
def initialize( opponent )
@mk=Hash.new
@last=[nil]*3 # try other values
end
def choose
if !@last[0].nil?
nodekey = @last.map do |i|
i[0].to_s+"-"+i[1].to_s
end.join(",")
@mk[nodekey]= MKNode.new if !@mk[nodekey]
@mk[nodekey].choose
else
[:paper,:rock,:scissors][rand(3)]
end
end

def result( you, them, win_lose_or_draw )

if !@last[0].nil?
nodekey = @last.map do |i|
i[0].to_s+"-"+i[1].to_s
end.join(",")
@mk[nodekey]= MKNode.new if !@mk[nodekey]
@mk[nodekey]<< WIN[them]
end
@last[0,1]=[]
@last<< [you,them]

end
private
class MKNode
def initialize(paper=0,rock=0,scissors=0)
@paper=paper
@rock=rock
@scissors=scissors
end
def choose
if @paper+@rock+@scissors == 0
[:paper,:rock,:scissors][rand(3)]
else
rnd = rand(@paper+@rock+@scissors)
if rnd < @paper
:paper
elsif rnd < @paper+@rock
:rock
else
:scissors
end
end
end
def <<(x)
case x
when :paper
@paper+=1
when :rock
@rock+=1
when :scissors
@scissors+=1
end
end
def inspect
max = @paper+@[email protected]_f
if max == 0
"#<JIXPlayerM::MKNode --- >"
else
"#<JIXPlayerM::MKNode p{#{@paper/max}} r{#{@rock/max}} s{#{@scissors/max}} >"
end
end
end
end
--------------000402080705020908050800--
 
B

Bill Atkins

My 12-line solution has so far won 100% of the time against every
player I've been able to come up with, even players whose moves are
completely random. Here it is:

class Cheater < Player
def initialize opponent
Object.const_get(opponent).send :define_method, :choose do
:paper
end
end

def choose
:scissors
end
end


# :D
 
J

Jannis Harder

Bill said:
My 12-line solution has so far won 100% of the time against every
player I've been able to come up with, even players whose moves are
completely random.


# Start
class HyperCheater < Player
def initialize(opponent)
@opponent=opponent
Object.const_get(opponent).send :define_method, :choose do
:scissors
end
end
def choose
:rock
end
def result( you, them, win_lose_or_draw )
Object.const_get(@opponent).send :define_method, :choose do
:scissors
end
Object.const_get(self.class.to_s).send :define_method, :choose
do # SelfRepair
:rock
end
end
end
END
# :p
 
J

James Edward Gray II

Otherwise, a strategy simply selecting a random choice will always
have a
50% chance of winning

Looks like we are seeing some strategies that do better than 50%
against a random player. ;)

James Edward Gray II
 
J

James Edward Gray II

My 12-line solution has so far won 100% of the time against every
player I've been able to come up with, even players whose moves are
completely random. Here it is:

My own was pretty much identical. ;)

James Edward Gray II

#!/usr/biin/env ruby

class JEGCheater < Player
def initialize( opponent )
Object.const_get(opponent).class_eval do
alias_method :eek:ld_choose, :choose
def choose
:paper
end
end
end

def choose
:scissors
end
end
 
J

James Edward Gray II

# Start
class HyperCheater < Player
def initialize(opponent)
@opponent=opponent
Object.const_get(opponent).send :define_method, :choose do
:scissors
end
end
def choose
:rock
end
def result( you, them, win_lose_or_draw )
Object.const_get(@opponent).send :define_method, :choose do
:scissors
end
Object.const_get(self.class.to_s).send :define_method, :choose
do # SelfRepair
:rock
end
end
end
END

Protecting yourself, eh? That looks like a challenge. Anyone want to
out cheat this cheater? It can be done!

James Edward Gray II
 
J

James Edward Gray II

First, congratulations to this great quiz. It was a great fun for me
and other people on #ruby-lang.

Any interesting discussion occur on the channel you would like to share
with the rest of the group?

James Edward Gray II
 
D

Dennis Ranke

Here is my history based solution. It tries to guess the most probable
choice based on the last few rounds and then counters that.

class DRPatternPlayer < Player
MAX_PATTERN_LENGTH = 6

def initialize(opponent)
super
@stat = {}
@history = []
end

def choose
paper = rock = scissors = 0
(1..MAX_PATTERN_LENGTH).each do |i|
break if i > @history.size*2
stat = @stat[@history[0, i*2]]
next unless stat
p = stat[:paper]
r = stat[:rock]
s = stat[:scissors]
count = (p + r + s).to_f
sig = [p, r, s].max / count - 1.0 / 3
f = sig * (1 - 1/count)
p /= count
r /= count
s /= count
if p > 0.4 && r > 0.4
r += p
p = s
s = 0
end
if r > 0.4 && s > 0.4
s += r
r = p
p = 0
end
if s > 0.4 && p > 0.4
p += s
s = r
r = 0
end
paper += p * f
rock += r * f
scissors += s * f
end
case rand(3)
when 0: paper += 0.2
when 1: rock += 0.2
when 2: scissors += 0.2
end
paper *= rand()
rock *= rand()
scissors *= rand()
return :scissors if paper > rock && paper > scissors
return :paper if rock > scissors
return :rock
end

def result(you, them, result)
(1..MAX_PATTERN_LENGTH).each do |i|
break if i > @history.size*2
key = @history[0, i*2]
@stat[key] ||= {:paper => 0, :rock => 0, :scissors => 0}
@stat[key][them] += 1
end
@history = ([you, them] + @history)[0, MAX_PATTERN_LENGTH*2]
end
end
 
F

Florian Gross

James said:
Protecting yourself, eh? That looks like a challenge. Anyone want to
out cheat this cheater? It can be done!

Use .freeze or the method_added etc. hooks. :)
 
J

James Edward Gray II


Thanks for the link. Here's a good quote from there:

lypanov notes that the quiz is a bit silly as the reason many good
players win in real life
is because they react fast to the hand formations so unless you have a
real person playing
against a bot guessing the players move by recognition, its kind of
boring :p

Actually, this is one of the reasons I chose to use Paper Rock
Scissors. I could have easily picked something with a little more
strategy. ;)

James Edward Gray II
 
J

Jannis Harder

--------------010706050109070401000700
Content-Type: text/plain; charset=ISO-8859-15; format=flowed
Content-Transfer-Encoding: 7bit

Test results (-m 5000)(Generated using my edited paper_rock_scissors.rb):

# Name Score
1 AJBMetaPlayer 53116.0
2 JIXPlayerD 50965.0
3 DRPatternPlayer 49527.0
4 JIXPlayerM 48782.5
5 AJBHistoryPlayer 45518.0
6 CNBiasInverter 35336.0
7 AJBRandomPlayer 35101.5
8 CNIrrflug 33951.5
9 JIXPlayerT 31794.5
10 JIXPlayerC 26901.0
11 CNBiasFlipper 23821.5
12 CNBiasBreaker 23498.5
13 CNStepAhead 23043.0
14 AJBFrequencyPlayer 22988.5
15 CNMeanPlayer 20655.5

JIXPlayer D,T and C:
D: Dynamic Length Markov Chains
T: Word lengths of a text ;)
C: p r r s p s,p r r s ...



--------------010706050109070401000700
Content-Type: text/plain; x-mac-type="2A2A2A2A"; x-mac-creator="48647261";
name="jix_player_c.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="jix_player_c.rb"

class JIXPlayerC < Player
def initialize( opponent )
@que=%w{paper rock rock scissors paper scissors}.map{|z|z.to_sym}
@i=0
end
def choose
@i+=1
@i%[email protected]
@que[@i]
end
end
--------------010706050109070401000700
Content-Type: text/plain; x-mac-type="2A2A2A2A"; x-mac-creator="48647261";
name="jix_player_d.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="jix_player_d.rb"

class JIXPlayerD < Player
WIN = {:paper => :scissors, :rock => :paper , :scissors => :rock}
def initialize( opponent )
@mk=Hash.new
@last=[nil]*10
end

def choose
out = []
0.upto(@last.size-2) do |z|

if !@last[z].nil?
nodekey = @last[z..-1].map do |i|
i[0].to_s+"-"+i[1].to_s
#i[1].to_s
end.join(",")
out << @mk[nodekey] if @mk[nodekey]

end
end

return [:paper,:rock,:scissors][rand(3)] if out == []

out.sort_by{|z| - z.dfm}
out[0].choose
end

def result( you, them, win_lose_or_draw )


0.upto(@last.size-2) do |z|
if !@last[z].nil?
nodekey = @last[z..-1].map do |i|
i[0].to_s+"-"+i[1].to_s
#i[1].to_s
end.join(",")
@mk[nodekey]= MKNode.new if !@mk[nodekey]
@mk[nodekey]<< WIN[them]
end
end


@last[0,1]=[]
@last<< [you,them]

end
private



class MKNode
def initialize(paper=0,rock=0,scissors=0)
@paper=paper
@rock=rock
@scissors=scissors
end
def choose
if @paper+@rock+@scissors == 0
[:paper,:rock,:scissors][rand(3)]
else
rnd = rand(@paper+@rock+@scissors)
if rnd < @paper
:paper
elsif rnd < @paper+@rock
:rock
else
:scissors
end
end
end
def dfm
mid = (@paper+@rock+@scissors)/3.0
(mid-@paper).abs+(mid-@rock).abs+(mid-@scissors).abs+mid
end
def <<(x)
case x
when :paper
@paper+=1
when :rock
@rock+=1
when :scissors
@scissors+=1
end
end
def inspect
max = @paper+@[email protected]_f
if max == 0
"#<JIXPlayerDM::MKNode --- >"
else
"#<JIXPlayerDM::MKNode p{#{@paper/max}} r{#{@rock/max}} s{#{@scissors/max}} dfm{#{dfm}}>"
end
end
end
end
--------------010706050109070401000700
Content-Type: text/plain; x-mac-type="2A2A2A2A"; x-mac-creator="48647261";
name="jix_player_t.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="jix_player_t.rb"

class JIXPlayerT < Player
def initialize( opponent )

text = <<'EOT'
Man is driven to create; I know I really love to create things. And while I'm not good at painting, drawing, or music, I can write software.

Shortly after I was introduced to computers, I became interested in programming languages. I believed that an ideal programming language must be attainable, and I wanted to be the designer of it. Later, after gaining some experience, I realized that this kind of ideal, all-purpose language might be more difficult than I had thought. But I was still hoping to design a language that would work for most of the jobs I did everyday. That was my dream as a student.

Years later I talked with colleagues about scripting languages, about their power and possibility. As an object-oriented fan for more than fifteen years, it seemed to me that OO programming was very suitable for scripting too. I did some research on the 'net for a while, but the candidates I found, Perl and Python, were not exactly what I was looking for. I wanted a language more powerful than Perl, and more object-oriented than Python.

Then, I remembered my old dream, and decided to design my own language. At first I was just toying around with it at work. But gradually it grew to be a tool good enough to replace Perl. I named it Ruby---after the precious red stone---and released it to the public in 1995.

Since then a lot of people have become interested in Ruby. Believe it or not, Ruby is actually more popular than Python in Japan right now. I hope that eventually it will be just as well received all over the world.

I believe that the purpose of life is, at least in part, to be happy. Based on this belief, Ruby is designed to make programming not only easy, but also fun. It allows you to concentrate on the creative side of programming, with less stress. If you don't believe me, read this book and try Ruby. I'm sure you'll find out for yourself.

I'm very thankful to the people who have joined the Ruby community; they have helped me a lot. I almost feel like Ruby is one of my children, but in fact, it is the result of the combined efforts of many people. Without their help, Ruby could never have become what it is.

I am especially thankful to the authors of this book, Dave Thomas and Andy Hunt. Ruby has never been a well-documented language. Because I have always preferred writing programs over writing documents, the Ruby manuals tend to be less thorough than they should be. You had to read the source to know the exact behavior of the language. But now Dave and Andy have done the work for you.

They became interested in a lesser-known language from the Far East. They researched it, read thousands of lines of source code, wrote uncountable test scripts and e-mails, clarified the ambiguous behavior of the language, found bugs (and even fixed some of them), and finally compiled this great book. Ruby is certainly well documented now!

Their work on this book has not been trivial. While they were writing it, I was modifying the language itself. But we worked together on the updates, and this book is as accurate as possible.

It is my hope that both Ruby and this book will serve to make your programming easy and enjoyable. Have fun!

Yukihiro Matsumoto, a.k.a. ``Matz''
EOT
@sizes=text.gsub(/[^A-Za-z\s]/,"").split(/\s+/).map{|z|z.size%3}
@count=rand(@sizes.size)
end
def choose
@count+=1
@count%[email protected]
[:paper,:rock,:scissors][@sizes[@count]]
end
end
--------------010706050109070401000700
Content-Type: text/plain; x-mac-type="2A2A2A2A"; x-mac-creator="48647261";
name="paper_rock_scissors.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="paper_rock_scissors.rb"

#!/usr/bin/env ruby
class Player
@@players = [ ]

def self.inherited( player )
@@players << player
end

def self.each_pair
(0...(@@players.size - 1)).each do |i|
((i + 1)...@@players.size).each do |j|
yield @@players, @@players[j]
end
end
end

def initialize( opponent )
@opponent = opponent
end

def choose
raise NoMethodError, "Player subclasses must override choose()."
end

def result( you, them, win_lose_or_draw )
# do nothing--sublcasses can override as needed
end
end

class Game
attr_reader :score1,:score2
def initialize( player1, player2 )
@player1 = player1.new(player2.to_s)
@player2 = player2.new(player1.to_s)
@score1 = 0
@score2 = 0
end

def play( match )
match.times do
hand1 = @player1.choose
hand2 = @player2.choose
case hand1
when :paper
case hand2
when :paper
draw hand1, hand2
when :rock
win @player1, hand1, hand2
when :scissors
win @player2, hand1, hand2
else
raise "Invalid choice by #{@player2.class}."
end
when :rock
case hand2
when :paper
win @player2, hand1, hand2
when :rock
draw hand1, hand2
when :scissors
win @player1, hand1, hand2
else
raise "Invalid choice by #{@player2.class}."
end
when :scissors
case hand2
when :paper
win @player1, hand1, hand2
when :rock
win @player2, hand1, hand2
when :scissors
draw hand1, hand2
else
raise "Invalid choice by #{@player2.class}."
end
else
raise "Invalid choice by #{@player1.class}."
end
end
end

def results
match = "#{@player1.class} vs. #{@player2.class}\n" +
"\t#{@player1.class}: #{@score1}\n" +
"\t#{@player2.class}: #{@score2}\n"
if @score1 == @score2
match + "\tDraw\n"
elsif @score1 > @score2
match + "\t#{@player1.class} Wins\n"
else
match + "\t#{@player2.class} Wins\n"
end
end

private

def draw( hand1, hand2 )
@score1 += 0.5
@score2 += 0.5
@player1.result(hand1, hand2, :draw)
@player2.result(hand2, hand1, :draw)
end

def win( winner, hand1, hand2 )
if winner == @player1
@score1 += 1
@player1.result(hand1, hand2, :win)
@player2.result(hand2, hand1, :lose)
else
@score2 += 1
@player1.result(hand1, hand2, :lose)
@player2.result(hand2, hand1, :win)
end
end
end


match_game_count = 1000
if ARGV.size > 2 and ARGV[0] == "-m" and ARGV[1] =~ /^[1-9]\d*$/
ARGV.shift
match_game_count = ARGV.shift.to_i
end

ARGV.each do |p|
if test(?d, p)
Dir.foreach(p) do |file|
next if file =~ /^\./
next unless file =~ /\.rb$/
require File.join(p, file)
end
else
require p
end
end
overallscore=Hash.new(0)

Player.each_pair do |one, two|
game = Game.new one, two
game.play match_game_count
puts game.results
overallscore[one]+=game.score1
overallscore[two]+=game.score2
end
puts("%2s %20s %10s" % ["#","Name","Score"])
z=0

overallscore.to_a.sort_by{|i|-i[1]}.each do |scorepair|
puts("%2i %20s %10.1f" % [z+=1,scorepair[0],scorepair[1]])
end
--------------010706050109070401000700--
 
B

Benedikt Huber

Looks like we are seeing some strategies that do better than 50%
against a random player. ;)

This is impossible for players which do not cheat.
You can't give any predictions on the next move of a random player.
Therefore you have a 1/3 prop. to choose a winning,losing or drawing move.

Here is another 'cheater' (which does not redefine a method):

SYMBOLS = [ :rock,
:paper,
:scissors ]
KILLER = { :rock => :paper, :paper => :scissors, :scissors => :rock }

class BHCheatPlayer < Player

def initialize( opponent )
super
@opp = Object.const_get(opponent).new(self)
end

def choose
KILLER[@opp.choose]
end

def result(you,them,result)
@opp.result(them,you,result)
end

end
 
B

Benedikt Huber

I have a similar solution:

SYMBOLS = [ :rock,
:paper,
:scissors ]
KILLER = { :rock => :paper, :paper => :scissors, :scissors => :rock }
MAXSIZE = 5

class Symbol
def +(o)
return (self.to_s+o.to_s).to_sym
end
end

class BHAdaptPlayer4 < Player

def initialize( opponent )
@stats = Hash.new()
@lastmoves = []
end

def get_keys
keys = [ ]
@lastmoves.each do |pair|
keys.unshift( keys.empty? ? pair : (keys[0]+pair))
end
keys
end

def choose
info = nil
get_keys.each do |key|
info = @stats[key]
break if info
end
if ! info
SYMBOLS[rand(3)]
else
max = -1
msym = nil
info.keys.each do |sym|
msym,max = sym,info[sym] if(info[sym] > max)
end
KILLER[msym]
end
end

# called after each choice you make to give feedback
# you = your choice
# them = opponent's choice
# win_lose_or_draw = :win, :lose or :draw, your result
def result( you, them, win_lose_or_draw )
get_keys.each do |key|
@stats[key] = create_stat if ! @stats[key]
@stats[key][them] += 1
end
@lastmoves.unshift(@lastchoice)
@lastmoves.pop if @lastmoves.size > MAXSIZE

@lastchoice = them+you
end

def create_stat
stat = {}
SYMBOLS.each {|sym| stat[sym] = 0 }
stat
end

end
 
F

Florian Gross

Benedikt said:
This is impossible for players which do not cheat.
You can't give any predictions on the next move of a random player.
Therefore you have a 1/3 prop. to choose a winning,losing or drawing move.

Here is another 'cheater' (which does not redefine a method):

This is similar to my solution, but mine does also copy the state of its
enemy.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top