bitpoker 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d0f3c1729b675b7cc63c6332c40720b0b905a093
4
+ data.tar.gz: f7ab633639edce93cac5b8484d0d029d44459d18
5
+ SHA512:
6
+ metadata.gz: 4d88295c37e073213753f5da61be201d11c3b7fc6c75cdef9c3b49395138cc3221717f8de061fac64b09b253409416f08d7fc152e9a2cbb65989adedb18cd8bf
7
+ data.tar.gz: 3f573f8b0d3e5554b7166b5e2dca847735d96e11ad9eca04b55baed535fcc9d534e2688174bb1a11eb90cec05421d6091061e0a56da0dde5d581aa467e294e7c
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "https://rubygems.org"
2
+ ruby "2.1.0"
3
+
4
+ gem "parallel", "~> 0.9.2"
5
+
6
+ group :develop do
7
+ gem "yard", "~> 0.8.7.3"
8
+ end
9
+
10
+ group :test do
11
+ gem "shoulda", "~> 3.5.0"
12
+ gem "mocha", "~> 1.0.0"
13
+ end
@@ -0,0 +1,36 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ activesupport (4.0.2)
5
+ i18n (~> 0.6, >= 0.6.4)
6
+ minitest (~> 4.2)
7
+ multi_json (~> 1.3)
8
+ thread_safe (~> 0.1)
9
+ tzinfo (~> 0.3.37)
10
+ atomic (1.1.14)
11
+ i18n (0.6.9)
12
+ metaclass (0.0.2)
13
+ minitest (4.7.5)
14
+ mocha (1.0.0)
15
+ metaclass (~> 0.0.1)
16
+ multi_json (1.8.4)
17
+ parallel (0.9.2)
18
+ shoulda (3.5.0)
19
+ shoulda-context (~> 1.0, >= 1.0.1)
20
+ shoulda-matchers (>= 1.4.1, < 3.0)
21
+ shoulda-context (1.1.6)
22
+ shoulda-matchers (2.5.0)
23
+ activesupport (>= 3.0.0)
24
+ thread_safe (0.1.3)
25
+ atomic
26
+ tzinfo (0.3.38)
27
+ yard (0.8.7.3)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ mocha (~> 1.0.0)
34
+ parallel (~> 0.9.2)
35
+ shoulda (~> 3.5.0)
36
+ yard (~> 0.8.7.3)
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Maciej Komorowski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,50 @@
1
+ # BitPoker
2
+
3
+ Ruby implementation of [BitPoker](http://bitpoker.sfi.org.pl/tutorial/pokaz/4) (polish readers only) game.
4
+
5
+ ## Idea
6
+
7
+ **In general**: let computer programs play in simplified version of poker.
8
+
9
+ **For devs**: create an bot that will beat the crap out of opponents.
10
+
11
+ ##Rules of game
12
+
13
+ In a BitPoker duel participate two bots. Duel has `1000` rounds. Each round consists of following steps.
14
+
15
+ 1. **Rules introduction:** croupier introduces bots with round rules. Bots are sent information about `minimal` and `maximal` number from the card range, `maximal stake` they can bet during betting rounds and `timeout` - maximal duration of bot response.
16
+ 2. **Card deal:** bots receive random card from `0..4` (min_card .. max_card) range .
17
+ 3. **First betting round:** bots have to set their bets. Bet value must be from `10..200` (min_stake .. max_stake) range.
18
+ 1. If both bets are already `200` (max_stake) go to **step 5**. If bets are even, go to **step 4**.
19
+ 2. Otherwise, lower bidder is ask to call the round stake (opponents higher bid). If he agrees, go to next step. If he does not agree, lower bidder folds and loses points equal to his last bet. Higher bidder is a winner and receives score equal to last bet of folding bot. **Round is over**.
20
+ 4. **Second betting round:** is performed alike first one, however bet minimal value is equal to the stake determined after previous betting round.
21
+ 5. **Showdown:** croupier sends information to bots about opponent card. If cards are equal, round ends with a draw and result is 0 to 0. Otherwise, bot with lower card loses number of points equal to the round stake and winner receives the equivalent amount of points. **Round is over**.
22
+ 6. Round result is appended to a total score of the duel.
23
+
24
+ It is important for a bot to respect duel rules, otherwise the bot will be disqualified.
25
+
26
+ **NOTE:** All of highlighted values are fully customizable. Take a peak at `BitPoker::Croupier#setup` method. Also they can be altered during duel (not recommenced so far), so each round can be different.
27
+
28
+ ##How to start?
29
+
30
+ Before any action is taken, use [Bundler](http://bundler.io/) to make sure your ruby environment is ready to play BitPoker.
31
+
32
+ ```
33
+ gem update --system; gem install bundler # If you don't have Bundler installed
34
+ bundle install
35
+ ```
36
+
37
+ It's very simple. First of all we need to build our own bot. To do so, create class that implements `BitPoker::BotInterface`. When you finished, place your bot file to `./bot` dir. To be sure that your bot is ready
38
+ for all game scenarios, play test duel with `DummyBot`. Enter following commands in your console:
39
+
40
+ ```
41
+ rake bitpoker:duel[your_bot_name,dummy_bot]
42
+ ```
43
+ When duel is over, you should see result of the duel.
44
+
45
+ ```
46
+ [45795, -45795]
47
+ ```
48
+ Now you are ready to fight your friends bots!
49
+ ##TCPoker
50
+ Main reason of creating BitPoker is to create solid foundation for TCPoker - BitPoker over TCP. More to come very soon.
@@ -0,0 +1,43 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => "bitpoker:duel"
4
+
5
+ namespace :bitpoker do
6
+
7
+ desc "Setup BitPocket"
8
+ task :setup do
9
+ require_relative 'lib/bitpoker'
10
+ end
11
+
12
+ desc "Play BitPoker"
13
+ task :duel, [:bot_one, :bot_two] => :setup do |t, args|
14
+
15
+ # Load bots
16
+ bot_one = BitPoker::load_bot( args.bot_one )
17
+ bot_two = BitPoker::load_bot( args.bot_two )
18
+
19
+ # Init local bot proxy
20
+ proxy_one = BitPoker::BotProxy.new( bot_one )
21
+ proxy_two = BitPoker::BotProxy.new( bot_two )
22
+
23
+ croupier = BitPoker::Croupier.new
24
+
25
+ # Init new duel
26
+ duel = BitPoker::Duel.new( croupier, proxy_one, proxy_two )
27
+
28
+ begin
29
+ until duel.finished? do
30
+ duel.play_round
31
+ end
32
+ rescue BitPoker::BotError => e
33
+ puts "Bot #{e.bot.trigger(:name)} is disqualified. Reason: \"#{e.message}\"."
34
+ end
35
+ p duel.total_score
36
+
37
+ end
38
+
39
+ Rake::TestTask.new( :unit ) do |t|
40
+ t.test_files = FileList['test/test_*.rb']
41
+ end
42
+
43
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'lib/bitpoker'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'bitpoker'
5
+ s.version = BitPoker::VERSION
6
+ s.license = 'MIT'
7
+ s.summary = "Bots play poker!"
8
+ s.description = "Ruby implementation of BitPoker game."
9
+ s.author = "Maciej Komorowski"
10
+ s.email = 'mckomo@gmail.com'
11
+ s.files = `git ls-files`.split("\n") - %w[.gitignore]
12
+ s.test_files = s.files.select { |p| p =~ /^test\/test_.*.rb/ }
13
+ s.homepage = 'https://github.com/mckomo/BitPoker'
14
+ s.add_dependency 'parallel', '~> 0.9', '>= 0.9.2'
15
+ end
16
+
@@ -0,0 +1 @@
1
+ This is a place for your bots.
@@ -0,0 +1,50 @@
1
+ module Bot
2
+
3
+ class DummyBot < BitPoker::BotInterface
4
+
5
+ def initialize
6
+ @prng = Random.new # Init Pseudo-Random Number Generator
7
+ end
8
+
9
+ def introduce( rules )
10
+ @min_card = rules["min_card"]
11
+ @max_card = rules["max_card"]
12
+ @max_stake = rules["max_stake"]
13
+ @timeout = rules["timeout"]
14
+ end
15
+
16
+ def get_card( card )
17
+ # @card = card - I could do that, but I won't.
18
+ end
19
+
20
+ def bet_one( min_stake )
21
+ @prng.rand( min_stake .. @max_stake ) # Bet random number, no logic here :)
22
+ end
23
+
24
+ def agree_one( opponent_stake )
25
+ [true, false].sample # Call it or not. It's all to PRNG
26
+ end
27
+
28
+ def bet_two( min_stake )
29
+ @prng.rand( min_stake .. @max_stake )
30
+ end
31
+
32
+ def agree_two( opponent_stake )
33
+ [true, false].sample
34
+ end
35
+
36
+ def showdown( opponent_card )
37
+ # I could here analyze opponent's strategy, but I'm to dummy for that!
38
+ end
39
+
40
+ def end_of_round( score )
41
+ # Is it good idea to keep score history?
42
+ end
43
+
44
+ def end_of_duel( total_score, opponent_score )
45
+ # Finally, it's over!
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,61 @@
1
+ require "timeout"
2
+ require "parallel"
3
+
4
+ require_relative 'bitpoker/bot_interface'
5
+ require_relative 'bitpoker/bot_proxy_interface'
6
+ require_relative 'bitpoker/bot_proxy'
7
+ require_relative 'bitpoker/rules'
8
+ require_relative 'bitpoker/game_logic'
9
+ require_relative 'bitpoker/croupier'
10
+ require_relative 'bitpoker/duel'
11
+ require_relative 'bitpoker/round'
12
+
13
+ # Great game of the BitPoker
14
+ #
15
+ # @author Mckomo
16
+ module BitPoker
17
+
18
+ VERSION = "0.1.1"
19
+
20
+ # Load bot from file
21
+ #
22
+ # @raise ArrgumentError
23
+ # @param bot_file [String]
24
+ # @return [BotInterface]
25
+ def self.load_bot( bot_file, bot_dir = "./bot", module_prefix = "Bot" )
26
+
27
+ # Get path to bot file
28
+ bot_path = "#{bot_dir}/#{bot_file}.rb"
29
+
30
+ # Raise exception if bot's file doesn't exist
31
+ raise ArgumentError, "Bot \"#{bot_file}\" does not exist in path \"#{bot_path}\"." unless File.exist?( bot_path )
32
+
33
+ # Load bot
34
+ require bot_path
35
+
36
+ # Convert file name to class name
37
+ module_prefix += "::" unless ! module_prefix or module_prefix.empty?
38
+ klass_name = bot_file.split( '_' ).map { |s| s.capitalize }.join
39
+ klass = Kernel.const_get( "#{module_prefix}#{klass_name}" )
40
+
41
+ # Check if bot class implements BotInterface
42
+ raise ArgumentError, "Bot \"#{bot_file}\" does not implement BotInterface." unless klass.ancestors.include?( BitPoker::BotInterface )
43
+
44
+ # Init bot
45
+ klass.new
46
+
47
+ end
48
+
49
+ # Exception raised when a bot breaks rules of the BitPoker
50
+ class BotError < RuntimeError
51
+
52
+ attr_reader :bot
53
+
54
+ def initialize( bot, message = "" )
55
+ super( message )
56
+ @bot = bot
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,99 @@
1
+ module BitPoker
2
+
3
+ class BotInterface
4
+
5
+ # Name of the bot (should be a class name)
6
+ #
7
+ # @return [String]
8
+ def name
9
+ self.class.to_s.split( '::' ).last
10
+ end
11
+
12
+ # Bot gets to know about duel rules
13
+ # Rules hash have following structure:
14
+ # {
15
+ # "min_card" => ?, - Minimal value from a card range
16
+ # "max_card" => ?, - Maximal value from the card range
17
+ # "max_stake" => ? - Maximal stake value to set during betting rounds
18
+ # "timeout" => ?, - Maximal duration (in seconds) of bot response
19
+ # }
20
+ # @param [Hash] rules
21
+ # @return [NilKlass] Response is not required
22
+ def introduce( rules )
23
+ raise NotImplementedError
24
+ end
25
+
26
+ # Bot receives card from a croupier
27
+ #
28
+ # @param [Integer] card Random number from the card range
29
+ # @param [Integer] card Minimal number from the card range
30
+ # @param [Integer] card Maximal number from the card range
31
+ # @return [NilKlass] Response is not required
32
+ def get_card( card )
33
+ raise NotImplementedError
34
+ end
35
+
36
+ # Bot decides on a first bet
37
+ #
38
+ # @param [Integer] min_stake Minimal stake to bet
39
+ # @return [Integer] Bet value that is a number from range (min_stake; max_stake)
40
+ def bet_one( min_stake )
41
+ raise NotImplementedError
42
+ end
43
+
44
+ # Bot decides whether to call opponent's bet after first betting
45
+ #
46
+ # @param [Integer] stake_to_call Opponent's bet after betting round
47
+ # @return [Mixed] True of false
48
+ def agree_one( stake_to_call )
49
+ raise NotImplementedError
50
+ end
51
+
52
+ # Bot decides on a second bet
53
+ #
54
+ # @param [Integer] min_stake Minimal stake to bet
55
+ # @return [Integer] Bet value that is a number from range (min_stake; max_stake)
56
+ def bet_two( min_stake )
57
+ raise NotImplementedError
58
+ end
59
+
60
+ # Bot decides whether to call opponent's stake after second betting
61
+ #
62
+ # @param [Integer] stake_to_call Opponent's bet
63
+ # @return [Mixed] True of false
64
+ def agree_two( stake_to_call )
65
+ raise NotImplementedError
66
+ end
67
+
68
+ # Bot gets to know about opponent's card
69
+ #
70
+ # @param [Integer] opponent_card
71
+ # @return [NilKlass] Response is not required
72
+ def showdown( opponent_card )
73
+ raise NotImplementedError
74
+ end
75
+
76
+ # Bot receives his score after round
77
+ # 0 when round ended with draw,
78
+ # - last_bet_value when folded or lost
79
+ # + opponent_last_bet_value when opponent folded
80
+ # + last_bet_value or when won in a showdown
81
+ #
82
+ # @param [Integer]
83
+ # @return [NilKlass] Response is not required
84
+ def end_of_round( score )
85
+ raise NotImplementedError
86
+ end
87
+
88
+ # Bot receives result of a duel when it is over
89
+ #
90
+ # @param [Integer] total_score Total score received in the duel
91
+ # @param [Integer] opponent_score Total score received by a opponent
92
+ # @return [NilKlass] Response is not required
93
+ def end_of_duel( total_score, opponent_score )
94
+ raise NotImplementedError
95
+ end
96
+
97
+ end
98
+
99
+ end
@@ -0,0 +1,21 @@
1
+ module BitPoker
2
+
3
+ #
4
+ #
5
+ # @author Mckomo
6
+ class BotProxy
7
+
8
+ include BotProxyInterface
9
+
10
+ def initialize( bot )
11
+ raise ArgumentError unless bot.kind_of?( BitPoker::BotInterface )
12
+ @bot = bot
13
+ end
14
+
15
+ def trigger( action, args = [] )
16
+ @bot.send( action, *args )
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,18 @@
1
+ module BitPoker
2
+
3
+ module BotProxyInterface
4
+
5
+ # Trigger bot action
6
+ #
7
+ # @raise TCPoker::BotError
8
+ # @param bot [String]
9
+ # @param action [String]
10
+ # @param args [Array]
11
+ # @return [Mixed]
12
+ def trigger( action, args = [] )
13
+ raise NotImplementedError
14
+ end
15
+
16
+ end
17
+
18
+ end