bitpoker 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,99 @@
1
+ module BitPoker
2
+
3
+
4
+ #
5
+ #
6
+ # @author Mckomo
7
+ class Croupier
8
+
9
+ attr_reader :rules
10
+
11
+ include GameLogic
12
+
13
+ def initialize( custom_rules = {} )
14
+ setup_with( custom_rules )
15
+ @prng = Random.new
16
+ end
17
+
18
+ def call( bot, action, args = [] )
19
+
20
+ raise ArgumentError, "Croupier plays only with BotProxy." unless bot.kind_of? BotProxyInterface
21
+
22
+ begin
23
+ # Get bot response within timeout
24
+ bot_response = timeout( @rules[:timeout] ) do
25
+ bot.trigger( action, args )
26
+ end
27
+ rescue Timeout::Error
28
+ raise BitPoker::BotError.new( bot, "Bot exceeded timeout" )
29
+ rescue NotImplementedError
30
+ raise BitPoker::BotError.new( bot, "Bot does not implement '#{action}' action" )
31
+ rescue => e
32
+ raise BitPoker::BotError.new( bot, "Bot failed during '#{action}' action execution." )
33
+ end
34
+
35
+ # Validate response if yield given
36
+ if block_given?
37
+ raise BitPoker::BotError.new( bot, "Bot response '#{bot_response}' after '#{action}' action is invalid" ) unless yield( bot_response )
38
+ end
39
+
40
+ bot_response
41
+
42
+ end
43
+
44
+ #
45
+ #
46
+ #
47
+ #
48
+ #
49
+ def parallel_call( bots, action, *args_list )
50
+
51
+ Parallel.map_with_index( bots, { :in_threads => 2 } ) do |b, i|
52
+ # Get args
53
+ args = args_list[i] || args_list[0]
54
+ # Call bot with or without yield
55
+ if block_given?
56
+ call( b, action, args ) do |result|
57
+ yield( result )
58
+ end
59
+ else
60
+ call( b, action, args )
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ #
67
+ #
68
+ #
69
+ def round_rules
70
+ {
71
+ "min_card" => @rules[:card_range].min,
72
+ "max_card" => @rules[:card_range].max,
73
+ "max_stake" => @rules[:max_stake],
74
+ "timeout" => @rules[:timeout]
75
+ }
76
+ end
77
+
78
+ def deal_cards
79
+ [ @prng.rand( Rules::CARD_RANGE ), @prng.rand( Rules::CARD_RANGE ) ]
80
+ end
81
+
82
+ def rules=( custom_rules )
83
+ setup( custom_rules )
84
+ end
85
+
86
+ def setup_with( rules )
87
+ @rules = {
88
+ rounds: rules[:rounds] || Rules::ROUNDS,
89
+ min_stake: rules[:min_stake] || Rules::MIN_STAKE,
90
+ max_stake: rules[:max_stake] || Rules::MAX_STAKE,
91
+ timeout: rules[:timeout] || Rules::TIMEOUT,
92
+ card_range: rules[:card_range] || Rules::CARD_RANGE,
93
+ }
94
+ end
95
+
96
+ end
97
+
98
+
99
+ end
@@ -0,0 +1,71 @@
1
+ module BitPoker
2
+
3
+ # Basic element of the BitPoker game
4
+ # - poker duel between two bots
5
+ #
6
+ # @author Mckomo
7
+ class Duel
8
+
9
+ attr_reader :options, :total_score
10
+
11
+ # Constructor of a duel object
12
+ #
13
+ # @parma [Croupier] croupier Croupier that will perform duel
14
+ # @param [BotProxyInterface] bot_one First bot to participate the duel
15
+ # @param [BotProxyInterface] bot_two Second bot to participate the duel
16
+ # @return [Duel]
17
+ def initialize( croupier, bot_one, bot_two )
18
+
19
+ # Check if interfaces are implemented
20
+ raise ArgumentError, "Proxy one does not implement ProxyInterface." unless bot_one.kind_of?( BitPoker::BotProxyInterface )
21
+ raise ArgumentError, "Proxy two does not implement ProxyInterface." unless bot_two.kind_of?( BitPoker::BotProxyInterface )
22
+
23
+ # Set dependencies
24
+ @croupier = croupier
25
+ @bots = [bot_one, bot_two]
26
+
27
+ # Set properties
28
+ @round_counter = 0
29
+ @finished = false # Note: look on 'finished?' method
30
+
31
+ # Init score table with 0
32
+ @total_score = [0, 0]
33
+
34
+ end
35
+
36
+ # Play one round of the BitPoker
37
+ #
38
+ # @return [Duel]
39
+ def play_round
40
+
41
+ # Don't perform deal if a game is finished
42
+ return nil if finished?
43
+
44
+ @round_counter += 1
45
+
46
+ # Init new round
47
+ round = Round.new( @bots )
48
+
49
+ # Proceed round process until it is finished
50
+ until round.state == Round::STATE_FINISHED do
51
+ @croupier.perform_next_step( round )
52
+ end
53
+
54
+ # Update total score
55
+ @total_score[0] += round.score[0]
56
+ @total_score[1] += round.score[1]
57
+
58
+ self
59
+
60
+ end
61
+
62
+ # Return state of the duel
63
+ #
64
+ # @return [Mixed] True of false
65
+ def finished?
66
+ @round_counter >= @croupier.rules[:rounds]
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -0,0 +1,186 @@
1
+ module BitPoker
2
+
3
+ module GameLogic
4
+
5
+ def perform_next_step( round )
6
+
7
+ case round.state
8
+ when Round::STATE_RULES_INTRODUCTION
9
+ perform_rules_introduction( round )
10
+ when Round::STATE_CARD_DEAL
11
+ perform_card_deal( round )
12
+ when Round::STATE_FIRST_BETTING
13
+ perform_first_betting( round )
14
+ when Round::STATE_FIRST_CALL
15
+ perform_first_call( round )
16
+ when Round::STATE_FIRST_CALLED
17
+ perform_first_called( round )
18
+ when Round::STATE_SECOND_BETTING
19
+ perform_second_betting( round )
20
+ when Round::STATE_SECOND_CALL
21
+ perform_second_call( round )
22
+ when Round::STATE_SECOND_CALLED
23
+ perform_second_called( round )
24
+ when Round::STATE_FOLDED
25
+ perform_folded( round )
26
+ when Round::STATE_SHOWDOWN
27
+ perform_showdown( round )
28
+ when Round::STATE_POINTS_DISTRIBUTION
29
+ perform_points_distributions( round )
30
+ else
31
+ raise "Unsupported state"
32
+ end
33
+
34
+ end
35
+
36
+ private
37
+
38
+ def perform_rules_introduction( round )
39
+
40
+ # Inform bots about rules
41
+ parallel_call( round.bots, :introduce, [round_rules] )
42
+ # Change round.state to first round of betting
43
+ round.state = Round::STATE_CARD_DEAL
44
+
45
+ end
46
+
47
+ def perform_card_deal( round )
48
+
49
+ # Set random cards
50
+ round.cards = deal_cards
51
+ # Give bots cards
52
+ parallel_call( round.bots, :get_card, round.cards[0], round.cards[1] )
53
+ # Change round.state to first round of betting
54
+ round.state = Round::STATE_FIRST_BETTING
55
+
56
+ end
57
+
58
+ def perform_first_betting( round )
59
+
60
+ min_stake = @rules[:min_stake] # Just shortcuts, no rocket science!
61
+ max_stake = @rules[:max_stake]
62
+
63
+ # Ask bots for bets
64
+ round.bets = parallel_call( round.bots, :bet_one, min_stake ) do |bet|
65
+ ( min_stake .. max_stake ).include?( bet )
66
+ end
67
+
68
+ if round.bets_even?
69
+ # If bets are even go to second betting at once
70
+ round.state = Round::STATE_SECOND_BETTING
71
+ else
72
+ # Otherwise perform first call
73
+ round.state = Round::STATE_FIRST_CALL
74
+ end
75
+
76
+ end
77
+
78
+ def perform_first_call( round )
79
+
80
+ # Ask lower bidder if calls the stake
81
+ has_called = call( round.lower_bidder, :agree_one, round.stake )
82
+
83
+ if has_called
84
+ # Continue with second betting
85
+ round.state = Round::STATE_FIRST_CALLED
86
+ else
87
+ # Lower bidder has folded
88
+ round.state = Round::STATE_FOLDED
89
+ end
90
+
91
+ end
92
+
93
+ def perform_first_called( round )
94
+
95
+ # Lower bidder calls the stake, now bets should be leveled
96
+ round.bets[round.lower_bidder_index] = round.higher_bid
97
+ # Now second betting round can be preformed
98
+ round.state = Round::STATE_SECOND_BETTING
99
+
100
+ end
101
+
102
+ def perform_second_betting( round )
103
+
104
+ min_stake = round.stake
105
+ max_stake = @rules[:max_stake]
106
+
107
+ # If bots already bet the max stake, go to showdown at once
108
+ if round.stake == max_stake
109
+ round.state = Round::STATE_SHOWDOWN; return
110
+ end
111
+
112
+ # Ask bots for bets
113
+ round.bets = parallel_call( round.bots, :bet_two, min_stake ) do |bet|
114
+ ( min_stake .. max_stake ).include?( bet )
115
+ end
116
+
117
+ if round.bets_even?
118
+ # If bets are even go to showdown right away
119
+ round.state = Round::STATE_SHOWDOWN
120
+ else
121
+ # otherwise second call is required
122
+ round.state = Round::STATE_SECOND_CALL
123
+ end
124
+
125
+ end
126
+
127
+ def perform_second_call( round )
128
+
129
+ # Ask lower bidder if calls the stake
130
+ has_called = call( round.lower_bidder, :agree_two, round.stake )
131
+
132
+ if has_called
133
+ # Bot accepted the challenge!
134
+ round.state = Round::STATE_SECOND_CALLED
135
+ else
136
+ # Bot has folded
137
+ round.state = Round::STATE_FOLDED
138
+ end
139
+
140
+ end
141
+
142
+ def perform_second_called( round )
143
+
144
+ # Lower bidder calls the stake, now bets should be leveled
145
+ round.bets[round.lower_bidder_index] = round.higher_bid
146
+ # Time for showdown!
147
+ round.state = Round::STATE_SHOWDOWN
148
+
149
+ end
150
+
151
+ def perform_folded( round )
152
+
153
+ # Change the score
154
+ round.score[round.lower_bidder_index] -= round.lower_bid
155
+ round.score[round.higher_bidder_index] += round.lower_bid
156
+
157
+ # Round is over
158
+ round.state = Round::STATE_POINTS_DISTRIBUTION
159
+
160
+ end
161
+
162
+ def perform_showdown( round )
163
+
164
+ # Set score unless it is a draw
165
+ unless round.draw?
166
+ round.score[round.winner_index] += round.stake
167
+ round.score[round.loser_index] -= round.stake
168
+ end
169
+
170
+ # After showdown round it's time to distribute points
171
+ round.state = Round::STATE_POINTS_DISTRIBUTION
172
+
173
+ end
174
+
175
+ def perform_points_distributions( round )
176
+
177
+ # Send info to bots about their results
178
+ parallel_call( round.bots, :end_of_round, round.score[0], round.score[1] )
179
+ # Round if over
180
+ round.state = Round::STATE_FINISHED
181
+
182
+ end
183
+
184
+ end
185
+
186
+ end
@@ -0,0 +1,111 @@
1
+ module BitPoker
2
+
3
+ # Model of a BitPoker round
4
+ # @author Mckomo
5
+ class Round
6
+
7
+ STATE_RULES_INTRODUCTION = 0 # Bots are sent rules of duel
8
+ STATE_CARD_DEAL = 1 # Bots receive random cards
9
+ STATE_FIRST_BETTING = 2 # Bots set first bet
10
+ STATE_FIRST_CALL = 3 # Lower bidder is ask to call a stake after first betting round
11
+ STATE_FIRST_CALLED = 4 # Lower bidder called the stake
12
+ STATE_SECOND_BETTING = 5 # Bots set second bet
13
+ STATE_SECOND_CALL = 6 # Lower bidder is ask to call a stake after second betting round
14
+ STATE_SECOND_CALLED = 7 # Lower bidder called the stake
15
+ STATE_FOLDED = 8 # Lower bidder folded
16
+ STATE_SHOWDOWN = 9 # Result of the round is determined
17
+ STATE_POINTS_DISTRIBUTION = 10 # Bots receive their results after the round
18
+ STATE_FINISHED = -1 # Round is over
19
+
20
+ attr_accessor :bets, :cards, :score, :state
21
+ attr_reader :bots
22
+
23
+ # Contractor of a round object
24
+ #
25
+ # @param [Array] Array with two bots that participate in the round
26
+ # @return [Round]
27
+ def initialize( bots )
28
+ @bots = bots # Bind bots to the round
29
+ @score = [0, 0] # Init with clear score table
30
+ @state = STATE_RULES_INTRODUCTION # Start with card deal
31
+ end
32
+
33
+ # Value of a stake
34
+ #
35
+ # @return [Integer]
36
+ def stake
37
+ higher_bid
38
+ end
39
+
40
+ # Whether bets are even
41
+ #
42
+ # @return [Mixed] True of false
43
+ def bets_even?
44
+ @bets[0] == @bets[1]
45
+ end
46
+
47
+ # Return higher bid
48
+ #
49
+ # @return [Integer]
50
+ def higher_bid
51
+ @bets.max
52
+ end
53
+
54
+ # Return lower bid
55
+ #
56
+ # @return [Integer]
57
+ def lower_bid
58
+ @bets.min
59
+ end
60
+
61
+ # Return index of higher bidder
62
+ #
63
+ # @return [Integer] 0 or 1
64
+ def higher_bidder_index
65
+ @bets.index( higher_bid )
66
+ end
67
+
68
+ # Return index of lower bidder
69
+ #
70
+ # @return [Integer] 0 or 1
71
+ def lower_bidder_index
72
+ @bets.index( lower_bid )
73
+ end
74
+
75
+ # Return higher bidder
76
+ #
77
+ # @return [BotInterface]
78
+ def higher_bidder
79
+ @bots[higher_bidder_index]
80
+ end
81
+
82
+ # Return lower bidder
83
+ #
84
+ # @return [BotInterface]
85
+ def lower_bidder
86
+ @bots[lower_bidder_index]
87
+ end
88
+
89
+ # Whether it is a draw
90
+ #
91
+ # @return [Mixed] True of false
92
+ def draw?
93
+ @cards[0] == @cards[1]
94
+ end
95
+
96
+ # Return index of the round winner
97
+ #
98
+ # @return [Integer] 0 or 1
99
+ def winner_index
100
+ draw? ? nil : @cards.index( @cards.max )
101
+ end
102
+
103
+ # Return index of the round loser
104
+ #
105
+ # @return [Integer] 0 or 1
106
+ def loser_index
107
+ draw? ? nil : @cards.index( @cards.min )
108
+ end
109
+
110
+ end
111
+ end