bitpoker 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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