Created
December 5, 2010 18:39
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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