acpc_table_manager 3.0.18 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,