acpc_table_manager 3.0.18 → 4.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: baafc01d85fd023455f3ae040095e0136c70965b
4
- data.tar.gz: 6b1f411c878dbcbaf19d31446a3f8a93ed159a8f
3
+ metadata.gz: 655cf8543981e79b37a64173b226e5f8a70a9505
4
+ data.tar.gz: b7e29cbda06b285efcf2226ca6a8f30aa18162b9
5
5
  SHA512:
6
- metadata.gz: 252335c1b38e9a254bf811f0c1709f3119da5b9ddce9a1e98f039ad8da0541f346d5dc334baf790fa5a6c83c863e289a67a45e49058501f6a01360927bcb5655
7
- data.tar.gz: 1dbad1a737aa5ea205b6f427da1b8052ba9b68a41c27b8a6c55ddb5ccc8d85964148fb43bab84edc46795a95a5b026526cd6cf2ad92df270cd1a941a4b7b9106
6
+ metadata.gz: aa46f88eb7051f13e971151493b2502b11fd22fbc7262817ce7ef48fac8ad6b4b4729c8b10c0d4de3bec97047ef5f4676a7ae81e97452f64cd33ecdd1dc30f66
7
+ data.tar.gz: c76a667f2ff2a731ab0590e91d6c850e1f1b7c7f2f5a0fd080f87a727ef9156682724a69797292925b42946796f5175f4a9bc7e203df534888fe47d788b272cc
data/.gitignore CHANGED
@@ -7,4 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
- /test/tmp
10
+ /test/*tmp
11
+ dump.rdb
data/.travis.yml CHANGED
@@ -2,3 +2,5 @@ language: ruby
2
2
  rvm:
3
3
  - 2.1.2
4
4
  before_install: gem install bundler -v 1.10.6
5
+ services:
6
+ - redis-server
data/README.md CHANGED
@@ -1,25 +1,13 @@
1
- # AcpcTableManager
1
+ # Annual Computer Poker Competition Table Manager
2
2
 
3
- Must be able to accomplish the following tasks:
3
+ This is a server that starts an ACPC poker match on demand according to a fixed
4
+ configuration set at start-up through a messaging server
5
+ ([Redis](https://redis.io/)). It manages the log files produced by all matches.
6
+ All matches to be started are persisted in a file-based queue, and information
7
+ about those already running are also saved in a file. Special ports can be
8
+ designed for use by agents that require them to connect remotely to
9
+ `dealer` instances, and this server will manage their allocation.
4
10
 
5
- - Start a dealer
6
- - Start a bot and have it connect to the dealer
7
- - Start multiple bots and have them connect to the dealer
8
- - Start a proxy and connect it to the dealer
9
- - Send actions to the proxy for them to be played
10
- - Ensure dealer processes are killed when matches are finished
11
- - Ensure the number of matches being run is less than set maximum
12
- - Manage a queue of matches
13
- - Start the next match in the queue when one finishes
14
- - Manage a pool of port on which remote bots can connect to dealers
15
-
16
- The following tasks can be done in parallel:
17
-
18
- - Playing actions
19
- - Starting proxies
20
- - Starting bots
21
-
22
- Everything else must be done sequentially.
23
11
 
24
12
  ## Installation
25
13
 
@@ -55,4 +43,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERN
55
43
  ## License
56
44
 
57
45
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
58
-
data/Rakefile CHANGED
@@ -5,6 +5,7 @@ Rake::TestTask.new(:test) do |t|
5
5
  t.libs << "test"
6
6
  t.libs << "lib"
7
7
  t.test_files = FileList['test/**/*_test.rb']
8
+ t.warning = false
8
9
  end
9
10
 
10
11
  task :default => :test
@@ -20,42 +20,30 @@ Gem::Specification.new do |spec|
20
20
  spec.require_paths = ["lib"]
21
21
 
22
22
  # To send emails
23
- spec.add_dependency "pony"
24
-
25
- # For persistence
26
- spec.add_dependency 'origin', '~>1.0'
27
- spec.add_dependency 'moped', '~>1.4'
28
- spec.add_dependency "mongoid", '~> 3.1'
23
+ spec.add_dependency "pony", '~> 1.11'
29
24
 
30
25
  # For message passing
31
- spec.add_dependency 'redis', '~> 3.2'
26
+ spec.add_dependency 'redis', '~> 3.3'
32
27
 
33
28
  # For poker logic
34
- spec.add_dependency "acpc_poker_types"
35
- spec.add_dependency 'acpc_dealer', '~> 3.0'
36
- spec.add_dependency 'acpc_poker_player_proxy', '~> 1.3'
29
+ spec.add_dependency "acpc_poker_types", '~> 7.8'
30
+ spec.add_dependency 'acpc_dealer', '~> 3.1'
31
+ spec.add_dependency 'acpc_poker_player_proxy', '~> 1.6'
37
32
 
38
33
  # Simple exception email notifications
39
- spec.add_dependency 'rusen'
40
-
41
- # To run processes asynchronously
42
- spec.add_dependency 'process_runner', '~> 0.0'
34
+ spec.add_dependency 'rusen', '~> 0.1'
43
35
 
44
36
  # For better errors
45
- spec.add_dependency 'contextual_exceptions'
37
+ spec.add_dependency 'contextual_exceptions', '~> 0.0'
46
38
 
47
39
  # For better logging
48
- spec.add_dependency 'awesome_print'
40
+ spec.add_dependency 'awesome_print', '~> 1.7'
49
41
 
50
42
  # For sanitizing file names
51
- spec.add_dependency 'zaru'
52
-
53
- # For sanitizing file names in the match slice action log
54
- spec.add_dependency 'hescape'
43
+ spec.add_dependency 'zaru', '~> 0.1'
55
44
 
56
45
  spec.add_development_dependency "bundler", "~> 1.10"
57
46
  spec.add_development_dependency "rake", "~> 10.0"
58
- spec.add_development_dependency "minitest"
59
- spec.add_development_dependency "mocha"
60
- spec.add_development_dependency "pry"
47
+ spec.add_development_dependency "minitest", '~> 5.10'
48
+ spec.add_development_dependency "pry", '~> 0.10'
61
49
  end
data/exe/acpc_proxy CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'acpc_table_manager'
4
- require 'redis'
5
4
  require 'json'
6
5
  require 'optparse'
7
- require 'mongoid'
6
+
7
+ include AcpcTableManager::SimpleLogging
8
8
 
9
9
  ARGV << '-h' if ARGV.empty?
10
10
 
@@ -12,101 +12,188 @@ options = {}
12
12
  OptionParser.new do |opts|
13
13
  opts.banner = "Usage: #{$0} [options]"
14
14
 
15
+ opts.version = AcpcTableManager::VERSION
16
+
15
17
  opts.on_tail("-h", "--help", "Show this message") do
16
18
  puts opts
17
19
  exit
18
20
  end
19
21
 
20
- opts.on("-m", "--match_id MATCH ID", "The ID of the match to join.") do |c|
21
- options[:match_id] = c.strip
22
+ opts.on("-i", "--id ID", "This proxie's ID. Used as its messaging channel name.") do |c|
23
+ options[:id] = c
24
+ end
25
+ opts.on("-s", "--seat SEAT", "This proxie's seat at the table (zero indexed).") do |c|
26
+ options[:seat] = c.to_i
22
27
  end
23
28
  opts.on("-t", "--config TABLE MANAGER CONFIG", "Table manager configuration file.") do |c|
24
29
  options[:table_manager_config] = File.expand_path c, Dir.pwd
25
30
  end
31
+ opts.on("-p", "--port PORT", "The dealer port on which to connect.") do |c|
32
+ options[:port] = c.to_i
33
+ end
34
+ opts.on("-g", "--game GAME DEF KEY", "The game to be played.") do |c|
35
+ options[:game] = c
36
+ end
26
37
  end.parse!
27
38
 
28
- raise OptionParser::MissingArgument.new('MATCH ID') unless options[:match_id]
39
+ raise OptionParser::MissingArgument.new('ID') unless options[:id]
40
+ raise OptionParser::MissingArgument.new('SEAT') unless options[:seat]
41
+ raise OptionParser::MissingArgument.new('PORT') unless options[:port]
29
42
  raise OptionParser::MissingArgument.new('TABLE MANAGER CONFIG') unless options[:table_manager_config]
43
+ raise OptionParser::MissingArgument.new('GAME DEF KEY') unless options[:game]
30
44
 
31
45
  raise OptionParser::ArgumentError.new("#{options[:table_manager_config]} doesn't exist.") unless File.exist?(options[:table_manager_config])
46
+ raise OptionParser::ArgumentError.new("SEAT must be non-negative, received #{options[:seat] } instead.") unless options[:seat] >= 0
32
47
 
33
48
  CONFIG_FILE = options[:table_manager_config]
34
49
 
35
50
  AcpcTableManager.load! CONFIG_FILE
36
51
 
37
- match = begin
38
- AcpcTableManager::Match.find options[:match_id]
39
- rescue Mongoid::Errors::DocumentNotFound
40
- raise OptionParser::ArgumentError.new("Match \"#{options[:match_id]}\" doesn't exist.")
52
+ game_info = AcpcTableManager.exhibition_config.games[options[:game]]
53
+ unless game_info
54
+ raise OptionParser::ArgumentError.new(
55
+ "\"#{options[:game]}\" is not a recognized game. Registered games: #{AcpcTableManager.exhibition_config.games.keys}."
56
+ )
41
57
  end
42
58
 
43
- unless match.running? && !match.finished?
44
- raise OptionParser::ArgumentError.new("Match \"#{options[:match_id]}\" is not running or has already finished.")
45
- end
59
+ Signal.trap("INT") { exit_and_del_saved }
60
+ Signal.trap("TERM") { exit_and_del_saved }
46
61
 
47
- Signal.trap("INT") { exit }
48
- Signal.trap("TERM") { exit }
62
+ must_send_ready = AcpcTableManager.config.must_send_ready
49
63
 
50
- must_send_ready = !AcpcTableManager.config.next_hand_request_code.nil?
64
+ @logger = AcpcTableManager.new_log(
65
+ "#{options[:id]}.log",
66
+ File.join(AcpcTableManager.config.log_directory, 'proxies')
67
+ )
68
+ include AcpcTableManager::ProxyUtils
69
+
70
+ @communicator = AcpcTableManager::ProxyCommunicator.new(options[:id])
71
+ @communicator.del_saved # Clear stale messages to avoid unpredictable behaviour
72
+ @state_index = 0
51
73
 
52
74
  last_message_received = Time.now
53
75
 
54
76
  begin
55
- proxy = AcpcTableManager::Proxy.start match, must_send_ready
56
- proxy.log __method__, options
57
- loop do
58
- message = AcpcTableManager.redis.blpop(
59
- "#{AcpcTableManager.config.player_action_channel_prefix}#{options[:match_id]}",
60
- :timeout => AcpcTableManager.config.maintenance_interval_s
77
+ log(
78
+ __method__,
79
+ options: options,
80
+ version: AcpcTableManager::VERSION,
81
+ send_channel: @communicator.send_channel,
82
+ receive_channel: @communicator.receive_channel,
83
+ must_send_ready: must_send_ready
84
+ )
85
+
86
+ proxy = start_proxy(
87
+ game_info,
88
+ options[:seat],
89
+ options[:port],
90
+ must_send_ready
91
+ ) do |patt|
92
+ log 'start_proxy_block'
93
+ @communicator.publish(
94
+ AcpcTableManager::ProxyUtils.players_at_the_table_to_json(
95
+ patt,
96
+ game_info['num_hands_per_match'],
97
+ state_index
98
+ )
61
99
  )
62
- if message
63
- data = JSON.parse message[1]
100
+ state_index += 1
101
+ end
64
102
 
65
- proxy.log(__method__, {data: data})
103
+ log 'starting event loop'
66
104
 
67
- action = data[AcpcTableManager.config.action_key]
68
- if (
69
- AcpcTableManager.config.next_hand_request_code &&
70
- action == AcpcTableManager.config.next_hand_request_code
71
- )
72
- proxy.next_hand!
73
- else
74
- proxy.play! action
105
+ loop do
106
+ begin
107
+ @communicator.subscribe_with_timeout do |data|
108
+ log __method__, data: data
109
+
110
+ if data['resend']
111
+ log __method__, msg: 'Resending match state'
112
+ @communicator.publish(
113
+ AcpcTableManager::ProxyUtils.players_at_the_table_to_json(
114
+ proxy,
115
+ game_info['num_hands_per_match'],
116
+ @state_index
117
+ )
118
+ )
119
+ @state_index += 1
120
+ elsif data['kill']
121
+ log __method__, msg: 'Exiting'
122
+ exit_and_del_saved
123
+ else
124
+ action = data['action']
125
+ if action == 'next-hand'
126
+ proxy.next_hand! do |patt|
127
+ log 'next_hand! block', msg: 'Sending match state'
128
+ @communicator.publish(
129
+ AcpcTableManager::ProxyUtils.players_at_the_table_to_json(
130
+ patt,
131
+ game_info['num_hands_per_match'],
132
+ @state_index
133
+ )
134
+ )
135
+ @state_index += 1
136
+ end
137
+
138
+ log(
139
+ 'after next_hand!',
140
+ users_turn_to_act?: proxy.users_turn_to_act?,
141
+ match_ended?: proxy.match_ended?(game_info['num_hands_per_match'])
142
+ )
143
+ else
144
+ log 'before acting', users_turn_to_act?: proxy.users_turn_to_act?,
145
+ action: action
146
+
147
+ if proxy.users_turn_to_act?
148
+ action = PokerAction.new(action) unless action.is_a?(PokerAction)
149
+ proxy.play!(action) do |patt|
150
+ log 'play! block', msg: 'Sending match state'
151
+ @communicator.publish(
152
+ AcpcTableManager::ProxyUtils.players_at_the_table_to_json(
153
+ patt,
154
+ game_info['num_hands_per_match'],
155
+ @state_index
156
+ )
157
+ )
158
+ @state_index += 1
159
+ end
160
+
161
+ log(
162
+ 'after play!',
163
+ users_turn_to_act?: proxy.users_turn_to_act?,
164
+ match_ended?: proxy.match_ended?(game_info['num_hands_per_match'])
165
+ )
166
+ end
167
+ end
168
+ end
169
+ exit_and_del_saved if proxy.match_ended?(game_info['num_hands_per_match'])
170
+ last_message_received = Time.now
75
171
  end
76
- last_message_received = Time.now
77
- elsif !proxy.users_turn_to_act?
78
- last_message_received = Time.now
79
- elsif (
80
- AcpcTableManager.config.proxy_timeout_s &&
81
- (
82
- Time.now > (
83
- last_message_received + AcpcTableManager.config.proxy_timeout_s
172
+ rescue AcpcTableManager::SubscribeTimeout
173
+ if proxy.match_ended? game_info['num_hands_per_match']
174
+ exit_and_del_saved
175
+ elsif !proxy.users_turn_to_act?
176
+ last_message_received = Time.now
177
+ elsif (
178
+ AcpcTableManager.config.proxy_timeout_s && (
179
+ Time.now > (
180
+ last_message_received + AcpcTableManager.config.proxy_timeout_s
181
+ )
84
182
  )
85
183
  )
86
- )
87
- if AcpcTableManager.config.on_proxy_timeout == 'fold'
88
- proxy.play_check_fold!
89
- else
90
- match = AcpcTableManager::Match.find options[:match_id]
91
- match.proxy_pid = nil
92
- match.dealer_pid = nil
93
- match.save!
94
- exit
184
+ if AcpcTableManager.config.on_proxy_timeout == 'fold'
185
+ play_check_fold! proxy
186
+ else
187
+ exit_and_del_saved
188
+ end
95
189
  end
96
190
  end
97
- match = AcpcTableManager::Match.find options[:match_id]
98
- if proxy.match_ended? or !match.dealer_running?
99
- match.proxy_pid = nil
100
- match.dealer_pid = nil
101
- match.save!
102
- exit
103
- end
104
191
  end
105
192
  rescue => e
106
- proxy.log(
193
+ log(
107
194
  __method__,
108
195
  {
109
- match_id: options[:match_id],
196
+ id: options[:id],
110
197
  message: e.message,
111
198
  backtrace: e.backtrace
112
199
  },
@@ -114,3 +201,4 @@ rescue => e
114
201
  )
115
202
  AcpcTableManager.notify e # Send an email notification
116
203
  end
204
+ exit_and_del_saved
@@ -1,61 +1,80 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'acpc_dealer'
3
4
  require 'acpc_table_manager'
4
5
  require 'redis'
5
6
  require 'json'
6
7
  require 'optparse'
7
- require 'acpc_table_manager/match'
8
-
8
+ require 'yaml'
9
9
 
10
10
  ARGV << '-h' if ARGV.empty?
11
11
 
12
12
  options = {}
13
13
  OptionParser.new do |opts|
14
- opts.banner = "Usage: #{$0} [options]"
14
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
15
+
16
+ opts.version = AcpcTableManager::VERSION
15
17
 
16
- opts.on_tail("-h", "--help", "Show this message") do
18
+ opts.on_tail('-h', '--help', 'Show this message') do
17
19
  puts opts
18
20
  exit
19
21
  end
20
22
 
21
- opts.on("-t", "--table_manager TABLE MANAGER CONFIG", "Table manager configuration file.") do |c|
23
+ opts.on('-t', '--table_manager TABLE MANAGER CONFIG', 'Table manager configuration file.') do |c|
22
24
  options[:table_manager_config] = File.expand_path c, Dir.pwd
23
25
  end
24
26
  end.parse!
25
27
 
26
- raise OptionParser::MissingArgument.new('TABLE MANAGER CONFIG') unless options[:table_manager_config]
28
+ raise OptionParser::MissingArgument, 'TABLE MANAGER CONFIG' unless options[:table_manager_config]
27
29
 
28
- raise OptionParser::ArgumentError.new("#{options[:table_manager_config]} doesn't exist.") unless File.exist?(options[:table_manager_config])
30
+ raise OptionParser::ArgumentError, "#{options[:table_manager_config]} doesn't exist." unless File.exist?(options[:table_manager_config])
29
31
 
30
32
  CONFIG_FILE = options[:table_manager_config]
31
33
 
32
34
  AcpcTableManager.load! CONFIG_FILE
33
- table_manager = AcpcTableManager::Maintainer.new
34
35
 
35
- Signal.trap("INT") { exit }
36
- Signal.trap("TERM") { exit }
36
+ Signal.trap('INT') { exit }
37
+ Signal.trap('TERM') { exit }
38
+
39
+ communicator = AcpcTableManager::TableManagerReceiver.new('table-manager')
40
+
41
+ AcpcTableManager.config.log(
42
+ __method__,
43
+ options: options,
44
+ version: AcpcTableManager::VERSION,
45
+ receive_channel: communicator.channel
46
+ )
37
47
 
38
48
  loop do
39
49
  begin
40
- message = AcpcTableManager.redis.blpop("table-manager", :timeout => AcpcTableManager.config.maintenance_interval_s)
41
- if message
42
- data = JSON.parse message[1]
43
- table_manager.log(__method__, {data: data})
50
+ communicator.subscribe_with_timeout do |match_info|
51
+ AcpcTableManager.config.log(__method__, match_info: match_info)
52
+
53
+ raise "No match information provided in message, \"#{message}\" sent on channel, \"#{channel}\"." unless match_info
44
54
 
45
- case data['request']
46
- when AcpcTableManager.config.check_matches
47
- when AcpcTableManager.config.kill_match
48
- match = AcpcTableManager::Match.quiet_find data['params'][AcpcTableManager.config.match_id_key]
49
- if match then match.kill_proxy! end
50
- when 'reload'
51
- AcpcTableManager.load! CONFIG_FILE
52
- else
53
- raise StandardError.new("Unrecognized request: #{data['request']}")
54
- end
55
+ AcpcTableManager.enqueue_match(
56
+ match_info['game_def_key'],
57
+ match_info['players'],
58
+ match_info['random_seed']
59
+ )
60
+ AcpcTableManager.start_matches_if_allowed
55
61
  end
56
- table_manager.maintain!
62
+ rescue AcpcTableManager::SubscribeTimeout
63
+ AcpcTableManager.start_matches_if_allowed
64
+ rescue Redis::ConnectionError => e
65
+ AcpcTableManager.config.log(
66
+ __method__,
67
+ {
68
+ message: e.message,
69
+ exit: true,
70
+ backtrace: e.backtrace
71
+ },
72
+ Logger::Severity::ERROR
73
+ )
74
+ AcpcTableManager.notify e
75
+ exit
57
76
  rescue => e
58
- table_manager.log(
77
+ AcpcTableManager.config.log(
59
78
  __method__,
60
79
  {
61
80
  message: e.message,