Skip to content

Instantly share code, notes, and snippets.

@dwt
Created December 5, 2010 18:39
Show Gist options
  • Save dwt/729333 to your computer and use it in GitHub Desktop.
Save dwt/729333 to your computer and use it in GitHub Desktop.
Implements a Martingale strategy for a player of roulette to let me learn about rspec
class RouletteCroupier
attr_accessor :table, :last_result
def bets
@bets ||= []
end
def place_color_bet(player, color, amount)
bets << { :player => player, :color => color, :amount => amount }
end
def bet_payouts_for_color_result(color)
bets.map do |bet|
[
bet[:player],
bet_payout_with_result(bet[:color], bet[:amount], color)
]
end
end
def bet_payout_with_result(bet_color, bet_amount, result_color)
if bet_color == result_color
bet_amount * 2
else
0
end
end
def inform_players_of_losses_and_wins
self.bet_payouts_for_color_result(self.last_result).each do | player, amount |
player.receive_payout amount
end
end
def no_more_bets
self.last_result = self.table.roll
self.inform_players_of_losses_and_wins
end
end
describe RouletteCroupier do
def table
@table ||= PerfectRouletteTable.new
end
def croupier
self.table.croupier
end
def first_player
@first_player ||= MartingalPlayer.new 1000, self.table
end
def second_player
@second_player ||= MartingalPlayer.new 1000, self.table
end
it "records placed bets" do
croupier.place_color_bet self.first_player, :black, 100
croupier.bets.should include({ :player => self.first_player, :color => :black, :amount => 100 })
end
it "doubles color bet on color match" do
croupier.bet_payout_with_result(:black, 100, :black).should equal 200
end
it "pays nothing if color mismatch" do
croupier.bet_payout_with_result(:black, 100, :white).should equal 0
croupier.bet_payout_with_result(:black, 100, :green).should equal 0
end
it "process bets on result announcement" do
croupier.place_color_bet self.first_player, :black, 100
croupier.place_color_bet self.second_player, :white, 300
results = croupier.bet_payouts_for_color_result(:white)
results.should include [self.first_player, 0]
results.should include [self.second_player, 600]
end
it "records result when rolling a game" do
table.stub!(:roll_result).and_return :black
croupier.no_more_bets
croupier.last_result.should == :black
end
it "notifies players of their results" do
croupier.place_color_bet first_player, :black, 100
table.stub!(:roll_result).and_return :black
first_player.should_receive(:receive_payout).once.with(200)
croupier.no_more_bets
end
end
class PerfectRouletteTable
attr_accessor :last_result, :croupier
# REFACT: consider to invert relationship between table and croupier
# REFACT: for consistency, it might make sense to let the table track
# players (nearer to real life would be to let the croupier track them though...)
def initialize(optional_croupier = RouletteCroupier.new)
self.croupier = optional_croupier
self.croupier.table = self
end
def roll
self.last_result = self.roll_result
end
def table_color_values
@table_color_values || [:black] * 18 + [:green] + [:white] * 18
end
def roll_result
table_color_values[rand(table_color_values.length)]
end
def minimum_bet
return 100
end
end
describe PerfectRouletteTable do
def table
@table ||= PerfectRouletteTable.new
end
it "returns a random color from allowed colors" do
table.roll.should satisfy {|roll| [:black, :white, :green].include? roll }
end
it "has minimum bet" do
table.minimum_bet.should equal 100
end
it "should retain result of last roll" do
table.stub!(:roll_result).and_return :black
table.roll
table.last_result.should == :black
end
end
class MartingalPlayer
attr_accessor :table, :lost_last_game, :current_bet, :fortune
def initialize(fortune, optional_table = PerfectRouletteTable.new)
self.fortune = fortune
self.join_table optional_table
end
def join_table(table)
self.table = table
self.current_bet = self.starting_bet
end
def starting_bet
self.table.minimum_bet
end
def place_jetons(color)
self.table.croupier.place_color_bet self, color, self.current_bet
self.fortune -= self.current_bet
end
def receive_payout(payout)
self.fortune += payout
self.lost_last_game = 0 == payout
self.current_bet = self.next_bet
if self.current_bet > self.fortune
self.admit_bankruptcy
end
end
def next_bet
if self.lost_last_game
self.current_bet * 2
else
self.starting_bet
end
end
def admit_bankruptcy
self.current_bet = 0
end
def bankrupt?
0 == self.fortune || 0 == self.current_bet
end
end
describe MartingalPlayer do
def table
@table ||= PerfectRouletteTable.new
end
def croupier
self.table.croupier
end
def initial_fortune
1000
end
def player
@player ||= MartingalPlayer.new initial_fortune, table
end
it "starts with minimal bet for that table" do
player.current_bet.should == table.minimum_bet
end
it "can place bets at croupier" do
player.place_jetons :black
croupier.bets.should include({
:player => self.player,
:color => :black,
:amount => self.table.minimum_bet
})
end
it "knows when it lost" do
player.receive_payout 0
player.lost_last_game.should be_true
end
it "should double bet when loosing" do
player.current_bet.should == 100
player.receive_payout 0
player.current_bet.should == 200
end
it "should return bet to table minimum on winning" do
player.current_bet.should == 100
player.receive_payout 200
player.current_bet.should == 100
end
it "should track his expenses" do
player.place_jetons :white
player.fortune.should == (initial_fortune - player.current_bet)
end
it "should track his wins" do
payout = 200
player.receive_payout payout
player.fortune.should == (initial_fortune + payout)
end
it "knows he is bankrupt when he has no money left" do
player.fortune = 0
player.should be_bankrupt
end
it "knows he is bankrupt if he can't afford the next bet" do
player.current_bet = 900
player.receive_payout 0
player.should be_bankrupt
end
it "does stop betting if he can't afford his next bet" do
player.current_bet = initial_fortune
player.place_jetons :black
player.receive_payout 0
player.current_bet.should == 0
end
end
__END__
improve wording of methos / variables to get more roulette feel into it. Also have more fun naming stuff.
implement table maximum
plot player money flow
make min and max bet value of table configurable
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment