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 +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +2 -0
- data/README.md +8 -21
- data/Rakefile +1 -0
- data/acpc_table_manager.gemspec +11 -23
- data/exe/acpc_proxy +146 -58
- data/exe/acpc_table_manager +45 -26
- data/exe/acpc_testing_bot +115 -0
- data/lib/acpc_table_manager.rb +635 -13
- data/lib/acpc_table_manager/config.rb +31 -141
- data/lib/acpc_table_manager/match.rb +52 -79
- data/lib/acpc_table_manager/proxy_utils.rb +290 -0
- data/lib/acpc_table_manager/version.rb +1 -1
- metadata +39 -127
- data/lib/acpc_table_manager/dealer.rb +0 -59
- data/lib/acpc_table_manager/maintainer.rb +0 -31
- data/lib/acpc_table_manager/match_slice.rb +0 -194
- data/lib/acpc_table_manager/match_view.rb +0 -203
- data/lib/acpc_table_manager/opponents.rb +0 -62
- data/lib/acpc_table_manager/proxy.rb +0 -346
- data/lib/acpc_table_manager/table_queue.rb +0 -240
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 655cf8543981e79b37a64173b226e5f8a70a9505
|
4
|
+
data.tar.gz: b7e29cbda06b285efcf2226ca6a8f30aa18162b9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa46f88eb7051f13e971151493b2502b11fd22fbc7262817ce7ef48fac8ad6b4b4729c8b10c0d4de3bec97047ef5f4676a7ae81e97452f64cd33ecdd1dc30f66
|
7
|
+
data.tar.gz: c76a667f2ff2a731ab0590e91d6c850e1f1b7c7f2f5a0fd080f87a727ef9156682724a69797292925b42946796f5175f4a9bc7e203df534888fe47d788b272cc
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,25 +1,13 @@
|
|
1
|
-
#
|
1
|
+
# Annual Computer Poker Competition Table Manager
|
2
2
|
|
3
|
-
|
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
data/acpc_table_manager.gemspec
CHANGED
@@ -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.
|
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.
|
36
|
-
spec.add_dependency 'acpc_poker_player_proxy', '~> 1.
|
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 "
|
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
|
-
|
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("-
|
21
|
-
options[:
|
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('
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
44
|
-
|
45
|
-
end
|
59
|
+
Signal.trap("INT") { exit_and_del_saved }
|
60
|
+
Signal.trap("TERM") { exit_and_del_saved }
|
46
61
|
|
47
|
-
|
48
|
-
Signal.trap("TERM") { exit }
|
62
|
+
must_send_ready = AcpcTableManager.config.must_send_ready
|
49
63
|
|
50
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
63
|
-
|
100
|
+
state_index += 1
|
101
|
+
end
|
64
102
|
|
65
|
-
|
103
|
+
log 'starting event loop'
|
66
104
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
(
|
82
|
-
|
83
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
193
|
+
log(
|
107
194
|
__method__,
|
108
195
|
{
|
109
|
-
|
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
|
data/exe/acpc_table_manager
CHANGED
@@ -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 '
|
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: #{$
|
14
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
|
15
|
+
|
16
|
+
opts.version = AcpcTableManager::VERSION
|
15
17
|
|
16
|
-
opts.on_tail(
|
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(
|
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
|
28
|
+
raise OptionParser::MissingArgument, 'TABLE MANAGER CONFIG' unless options[:table_manager_config]
|
27
29
|
|
28
|
-
raise OptionParser::ArgumentError
|
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(
|
36
|
-
Signal.trap(
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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
|
-
|
77
|
+
AcpcTableManager.config.log(
|
59
78
|
__method__,
|
60
79
|
{
|
61
80
|
message: e.message,
|