sc2ai 0.6.1 → 0.6.2

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
  SHA256:
3
- metadata.gz: ddd8bd1e37b50ba6d4e89c24fb330d646589d869cc53cf96a69624eaa0e6133c
4
- data.tar.gz: d88b2674f094621c11aa7de9a0c59ae82a6668aa6c882783a54236776ceb9f37
3
+ metadata.gz: a52a7a32bf219355f194817695d5505e47a897da5f3023ff856f4c88a66cc4d0
4
+ data.tar.gz: 94a565f3dc3c3f43896559664961242a9c06b30429bb41345183eb3fd680f29c
5
5
  SHA512:
6
- metadata.gz: 10c922d086149c4f2e4cac906607d7f5fb2c31fde1ace225186b0d83342efaea6720c48b799eb01e4d29fec5b295a4890d44b9cf803bb0c47e75e5830489c466
7
- data.tar.gz: 7430bdcd6935e457b88b562838c0dc112f28ecdfbacd072293ddfdf358d065287dcdbc300bb7cc76b4efe0e671a82006f9d53766b79bd2d006496982061e3fc7
6
+ metadata.gz: 8275c1840c4c7a9548a08ccf90215d41d8eabea15f7915109221f0c8c1deba96847103fc7f552927304c23b702a53aab5f091d8af7a52da33d7d0c419cfe2369
7
+ data.tar.gz: c547597a3bfb0babfbc2765c0d9fb6af7fab9137077faaf6f419ff51cad0cdfc30dd9816bab8383e9559b1cabc88aa5ec908aaf66348e3ac5570192708c23959
@@ -0,0 +1,12 @@
1
+ FROM aiarena/arenaclient-bot:latest AS build
2
+ LABEL service="bot-ruby-sc2ai-local"
3
+
4
+ USER root
5
+ WORKDIR /bots/
6
+
7
+ ARG DEBIAN_DISABLE_RUBYGEMS_INTEGRATION=true
8
+
9
+ RUN apt-get update
10
+ RUN apt-get install -y unzip
11
+
12
+ ENTRYPOINT ["/bin/bash"]
@@ -9,7 +9,7 @@ ARG DEBIAN_DISABLE_RUBYGEMS_INTEGRATION=true
9
9
 
10
10
  # Deps - Ruby build
11
11
  RUN apt-get update
12
- RUN apt install --assume-yes rustc curl build-essential libssl-dev zlib1g-dev libgmp-dev libffi-dev uuid-dev libopenblas0-serial
12
+ RUN apt install --assume-yes rustc curl build-essential libssl-dev zlib1g-dev libgmp-dev libffi-dev uuid-dev
13
13
 
14
14
  # Deps - libyaml from source
15
15
  RUN curl https://pyyaml.org/download/libyaml/yaml-0.2.5.tar.gz -o yaml-0.2.5.tar.gz
@@ -33,20 +33,20 @@ RUN rm yaml-0.2.5.tar.gz
33
33
 
34
34
  # Package config
35
35
  # numo-linalg needs openblas, copy to ruby-prefix/lib/ dir.
36
- #RUN apt download libopenblas0-serial
37
- #RUN mkdir openblas
38
- #RUN dpkg-deb -x ./libopenblas*.deb openblas
39
- #RUN cp -d openblas/usr/lib/x86_64-linux-gnu/openblas-serial/* /root/ruby-builder/.ruby/lib/
40
- #RUN rm -rf ./openblas
41
- #RUN rm ./libopenblas0-serial*.deb
42
-
43
- #RUN apt download libgfortran5
44
- #RUN mkdir libgfortran5
45
- #RUN dpkg-deb -x ./libgfortran*.deb libgfortran5
46
- #RUN find libgfortran5
47
- #RUN cp libgfortran5/usr/lib/x86_64-linux-gnu/libgfortran.so.5 /root/ruby-builder/.ruby/lib/
48
- #RUN rm -rf ./libgfortran5
49
- #RUN rm ./libgfortran5*.deb
36
+ RUN apt download libopenblas0-serial
37
+ RUN mkdir openblas
38
+ RUN dpkg-deb -x ./libopenblas*.deb openblas
39
+ RUN cp -d openblas/usr/lib/x86_64-linux-gnu/openblas-serial/* /root/ruby-builder/.ruby/lib/
40
+ RUN rm -rf ./openblas
41
+ RUN rm ./libopenblas0-serial*.deb
42
+
43
+ RUN apt download libgfortran5
44
+ RUN mkdir libgfortran5
45
+ RUN dpkg-deb -x ./libgfortran*.deb libgfortran5
46
+ RUN find libgfortran5
47
+ RUN cp libgfortran5/usr/lib/x86_64-linux-gnu/libgfortran.so.5 /root/ruby-builder/.ruby/lib/
48
+ RUN rm -rf ./libgfortran5
49
+ RUN rm ./libgfortran5*.deb
50
50
 
51
51
  RUN /root/ruby-builder/.ruby/bin/ruby --yjit -v
52
52
 
@@ -65,7 +65,7 @@ RUN /root/ruby-builder/.ruby/bin/bundle config set --global without test develop
65
65
  RUN apt-get update
66
66
  RUN apt-get purge $(aptitude search '~i!~M!~prequired!~pimportant!~R~prequired!~R~R~prequired!~R~pimportant!~R~R~pimportant!busybox!grub!initramfs-tools' | awk '{print $2}') --assume-yes
67
67
  RUN apt-get purge aptitude --assume-yes
68
- RUN apt-get install zip openssl build-essential libopenblas0-serial libffi-dev --assume-yes
68
+ RUN apt-get install zip openssl build-essential libffi-dev --assume-yes
69
69
  RUN apt-get autoremove --assume-yes
70
70
  RUN apt-get clean --assume-yes
71
71
 
@@ -1,4 +1,3 @@
1
- version: "3.9"
2
1
  services:
3
2
  bot:
4
3
  image: dysonreturns/aiarena-ruby-builder:latest
@@ -1,4 +1,3 @@
1
- version: "3.9"
2
1
  services:
3
2
  bot:
4
3
  image: dysonreturns/aiarena-ruby-builder:latest
@@ -0,0 +1,15 @@
1
+ services:
2
+ versus_bot:
3
+ container_name: versus_bot_container
4
+ image: dysonreturns/arenaclient-bot:local
5
+ environment:
6
+ - CPPFLAGS=-DPNG_ARM_NEON_OPT=0
7
+ platform: "linux/amd64"
8
+ tty: true
9
+ build:
10
+ dockerfile: ./Dockerfile.aiarenabot
11
+ ports:
12
+ - "${SERVER_BASE_PORT}:${SERVER_BASE_PORT}/udp"
13
+ - "${SERVER_GAME_PORT}:${SERVER_GAME_PORT}"
14
+ - "${CLIENT_BASE_PORT}:${CLIENT_BASE_PORT}/udp"
15
+ - "${CLIENT_GAME_PORT}:${CLIENT_GAME_PORT}"
data/lib/sc2ai/cli/cli.rb CHANGED
@@ -2,6 +2,7 @@ require "thor"
2
2
 
3
3
  require "sc2ai/cli/new"
4
4
  require "sc2ai/cli/ladderzip"
5
+ require "sc2ai/cli/versus_bot"
5
6
 
6
7
  module Sc2
7
8
  # Command line utilities
@@ -16,6 +17,7 @@ module Sc2
16
17
 
17
18
  register(Sc2::Cli::New, "new", "new BOTNAME RACE", "Creates botname folder with bot template")
18
19
  register(Sc2::Cli::Ladderzip, "ladderzip", "ladderzip BOTNAME", "Prepares a zip via docker for supplied aiarena bot name")
20
+ register(Sc2::Cli::VersusBot, "vsbot", "vsbot FILENAME", "Copies target zip to docker and starts a match")
19
21
 
20
22
  # TODO: Use thor's #ask and other methods helpers of raw $stdin and puts.
21
23
  desc "setup410", "Downloads and install SC2 v4.10"
@@ -95,23 +97,6 @@ module Sc2
95
97
  Sc2.logger.info " interface_options: #{$bot.interface_options}"
96
98
  end
97
99
 
98
- # desc "ladderzip", "Uses docker to cross-compile a compatible binary for the ladder"
99
- # def ladderzip
100
- # end
101
-
102
- # desc "ladderquickzip", "Ladder zip, if you have no additional gems installed."
103
- # long_desc <<-LONGDESC
104
- # `sc2ai ladderzip_basic` will download a portable ruby and gems, then copy your bot files into a zip file.
105
- #
106
- # If you have added any gems which need compiling, this option is not for you.
107
- # Any gems built with native extensions will likely not be targeting the linux platform and be nonfunctional.
108
- #
109
- # For a cross-platform build, use ladderzip instead. This will launch a Docker container to compile a zip for you.
110
- # LONGDESC
111
- # def ladderquickzip
112
- # raise Sc2::Error, "Not yet implemented."
113
- # end
114
-
115
100
  desc "laddermatch", "Joins a ladder match as per aiarena spec"
116
101
  option :GamePort, required: true, desc: "SC2 port. Corresponds to SC2 launch option '-port'"
117
102
  option :LadderServer, required: true, desc: "SC2 server ip or hostname"
@@ -147,16 +132,6 @@ module Sc2
147
132
  $bot.play
148
133
  end.wait
149
134
  end
150
-
151
- # desc "install APP_NAME", "install one of the available apps" # [4]
152
- # method_options :force => :boolean, :alias => :string # [5]
153
- # def install(name)
154
- # user_alias = options[:alias]
155
- # if options.force?
156
- # # do something
157
- # end
158
- # # other code
159
- # end
160
135
  end
161
136
  # standard:enable Style/GlobalVars
162
137
  end
@@ -78,8 +78,12 @@ module Sc2
78
78
  # create_link("./.build/#{botname}", "./bin/ladder")
79
79
  end
80
80
 
81
+ def create_ladderbots_json
82
+ template("ladderzip/ladderbots.json", "./.build/ladderbots.json")
83
+ end
84
+
81
85
  def start_container
82
- cmd = "docker compose -f #{@compose_file} up bot -d --force-recreate"
86
+ cmd = "docker compose -f #{@compose_file} up versus_bot -d --force-recreate"
83
87
  Kernel.system(cmd)
84
88
  end
85
89
 
@@ -95,6 +99,7 @@ module Sc2
95
99
  ignore_pattern -= include_pattern
96
100
 
97
101
  include_pattern.collect! { |pt| pt.delete_prefix("!") }
102
+ include_pattern.collect! { |pt| pt + "**/*" if pt.ends_with?("/") }
98
103
 
99
104
  files = Dir.glob("**/*")
100
105
 
@@ -0,0 +1,305 @@
1
+ module Sc2
2
+ # Command line utilities
3
+ class Cli < Thor
4
+ # Command line action allowing your bot to play vs a ladder bot via docker
5
+ class VersusBot < Thor::Group
6
+ # standard:disable Style/GlobalVars
7
+ include Thor::Actions
8
+
9
+ # Define arguments and options
10
+ argument :filename, desc: "Path to zip. Relative or absolute."
11
+ argument :map, desc: "Absolute path or shot name, i.e. 'ThunderbirdAIE', 'ThunderbirdAIE.SC2Map'.", default: "ThunderbirdAIE"
12
+ desc "Sends a bot zip to the ladder and makes it play against us"
13
+
14
+ def get_botname
15
+ @enemy_botname = File.basename(filename, ".zip")
16
+ end
17
+
18
+ def docker_exists
19
+ if Kernel.system("docker --version", out: File::NULL, err: File::NULL)
20
+ say_status("docker", "found", :green)
21
+ else
22
+ say_status("docker", "not found", :red)
23
+ say("Please install 'docker' and/or ensure it's in your PATH to continue.")
24
+ raise SystemExit
25
+ end
26
+
27
+ if Kernel.system("docker info", out: File::NULL, err: File::NULL)
28
+ say_status("docker engine", "found", :green)
29
+ else
30
+ say(" ")
31
+ say_status("docker engine", "offline", :red)
32
+ say("Please start docker engine. If you have Docker Desktop installed, open it before retrying.")
33
+ raise SystemExit
34
+ end
35
+ end
36
+
37
+ def bot_validation
38
+ if Pathname("./boot.rb").exist?
39
+ say_status("detected boot.rb", "found", :green)
40
+ else
41
+ say_status("detected boot.rb", "not found", :red)
42
+ raise Sc2::Error, "boot.rb not found. Bot started from wrong directory."
43
+ end
44
+
45
+ ENV.delete("AIARENA")
46
+ require "./boot"
47
+
48
+ if $bot.is_a? Sc2::Player
49
+ say_status("instance $bot", $bot.class, :green)
50
+ else
51
+ say_status("instance $bot", $bot.class, :red)
52
+ raise Sc2::Error, "$bot instance nil or not Sc2::Player. Review boot.rb to ensure it creates $bot."
53
+ end
54
+ end
55
+
56
+ def self.source_paths
57
+ [Pathname(".").expand_path.to_s]
58
+ end
59
+
60
+ def self.source_root
61
+ Paths.template_root.to_s
62
+ end
63
+
64
+ def port_configuration
65
+ # Start with basic port setup
66
+ @port_config = Sc2::Ports.port_config_auto(num_players: 2)
67
+ say_status("port config", "configured", :green)
68
+ ENV["SERVER_BASE_PORT"] = @port_config.server_port_set.base_port.to_s
69
+ ENV["SERVER_GAME_PORT"] = @port_config.server_port_set.game_port.to_s
70
+ ENV["CLIENT_BASE_PORT"] = @port_config.client_port_sets.first.base_port.to_s
71
+ ENV["CLIENT_GAME_PORT"] = @port_config.client_port_sets.first.game_port.to_s
72
+
73
+ # @port_config_env =
74
+ # "-e SERVER_BASE_PORT=#{@port_config.server_port_set.base_port} " +
75
+ # "-e SERVER_GAME_PORT=#{@port_config.server_port_set.game_port} " +
76
+ # "-e CLIENT_BASE_PORT=#{@port_config.client_port_sets.first.base_port} " +
77
+ # "-e CLIENT_GAME_PORT=#{@port_config.client_port_sets.first.game_port} "
78
+
79
+ # # Create a modified version of ports for docker bot
80
+ # @client_port_config = @port_config.dup
81
+ # # Adjust ports for docker
82
+ # @client_port_config.client_port_sets[0].base_port = 8453
83
+ # @client_port_config.client_port_sets[0].game_port = 2359
84
+ #
85
+ # # Get dynamic assigned ports for host
86
+ # real_client_base_port = `docker port versus_bot_container 8453/udp`.strip.split(":").last
87
+ # real_client_game_port = `docker port versus_bot_container 2359`.strip.split(":").last
88
+ # # Fix our client ports to forward to the docker bot
89
+ # @port_config.client_port_sets[0].base_port = real_client_base_port.to_i
90
+ # @port_config.client_port_sets[0].game_port = real_client_game_port.to_i
91
+ end
92
+
93
+ def set_compose_file
94
+ say_status("docker", "build local play container", :cyan)
95
+ @compose_file = Sc2::Paths.gem_root
96
+ .join("docker_build", "docker-compose-versus-bot.yml")
97
+ .expand_path.to_s
98
+ @compose_args = "-f #{@compose_file} #{@port_config_env}"
99
+ cmd = "docker compose #{@compose_args} build"
100
+ Kernel.system(cmd)
101
+ end
102
+
103
+ def versus_bot_validation
104
+ if Pathname(filename).exist?
105
+ say_status("checking zip #{filename}", "found", :green)
106
+ else
107
+ say_status("checking zip #{filename}", "not found", :red)
108
+ raise Sc2::Error, "#{filename} not found. Try an absolute path if relative will not detect."
109
+ end
110
+ end
111
+
112
+ def contains_ladderbot_json
113
+ # Max size of the portion where zip filenames are stored
114
+ max_end_of_cd_size = (1 << 16) - 1 + 22 + 20
115
+ searching_for_file = "ladderbots.json" # what we want to find
116
+ found = File.open(Pathname(filename).realpath, "rb") do |f|
117
+ # Borrowed code, will search near the end of a zip
118
+ if f.size > max_end_of_cd_size
119
+ f.seek(-max_end_of_cd_size, IO::SEEK_END)
120
+ end
121
+ content = f.read
122
+ content.include?(searching_for_file)
123
+ end
124
+
125
+ if found
126
+ say_status("zip contains ladderbots.json", "found", :green)
127
+ else
128
+ say_status("zip contains ladderbots.json", "not found", :red)
129
+ puts "#{filename} should contain ladderbots.json. Add the file to the zip and try again."
130
+ raise SystemExit
131
+ end
132
+ end
133
+
134
+ def start_container
135
+ say_status("docker", "starting bot container", :cyan)
136
+ cmd = "docker compose #{@compose_args} up versus_bot -d" ## { rand(20) == 0 ? "--force-recreate" : "" }
137
+ Kernel.system(cmd)
138
+ end
139
+
140
+ def copy_opponent_zip
141
+ say_status("docker", "copying opponent...", :cyan)
142
+ cmd = "docker compose #{@compose_args} cp #{Pathname(filename).realpath} versus_bot:/bots/"
143
+ Kernel.system(cmd)
144
+ end
145
+
146
+ def extract_opponent
147
+ say_status("docker", "extracting opponent...", :cyan)
148
+ cmd = "docker compose #{@compose_args} exec --workdir /bots versus_bot unzip -o #{Pathname(filename).basename} -d #{@enemy_botname}"
149
+ Kernel.system(cmd)
150
+ end
151
+
152
+ def pull_ladderbots_json
153
+ require "tempfile"
154
+
155
+ say_status("docker", "pulling enemy 'ladderbots.json'", :cyan)
156
+
157
+ @temp_path = Tempfile.new("enemy_ladderbots.json").path
158
+
159
+ cmd = "docker compose #{@compose_args} cp versus_bot:/bots/#{@enemy_botname}/ladderbots.json #{@temp_path}"
160
+ Kernel.system(cmd)
161
+ if File.exist?(@temp_path)
162
+ say_status("docker", "pulled 'ladderbots.json' ok", :green)
163
+ else
164
+ say_status("docker", "error pulling 'ladderbots.json'", :red)
165
+ exit
166
+ end
167
+ end
168
+
169
+ def parse_enemy_config
170
+ say_status("docker", "configuring enemy", :cyan)
171
+ json = JSON.parse(File.read(@temp_path))
172
+ bot_json = json["Bots"][@enemy_botname]
173
+ @enemy_runtime = bot_json["Type"].downcase # "BinaryCpp",
174
+ @enemy_race = bot_json["Race"].upcase.to_sym
175
+ @enemy_filename = bot_json["FileName"]
176
+ @command = case @enemy_runtime
177
+ when "cppwin32"
178
+ "wine #{@enemy_filename}.exe"
179
+ when "cpplinux", "binarycpp"
180
+ "/bots/#{@enemy_filename}/#{@enemy_filename}"
181
+ when "dotnetcore"
182
+ "dotnet #{@enemy_filename}.dll"
183
+ when "java"
184
+ "java -jar #{@enemy_filename}.jar"
185
+ when "nodejs"
186
+ "node #{@enemy_filename}.js"
187
+ when "python"
188
+ "python run.py"
189
+ else
190
+ @enemy_filename
191
+ end
192
+
193
+ # Make executable
194
+ if %w[cpplinux binarycpp].include?(@enemy_runtime)
195
+ cmd = "docker compose #{@compose_args} exec --workdir /bots/#{@enemy_botname} versus_bot chmod +x #{@command}"
196
+ Kernel.system(cmd)
197
+ end
198
+
199
+ # Make data dir
200
+ say_status("docker", "ensure data directory exists", :cyan)
201
+ cmd = "docker compose #{@compose_args} exec --workdir /bots/#{@enemy_botname} versus_bot sh -c \"mkdir data && chmod +w data\""
202
+ Kernel.system(cmd)
203
+ end
204
+
205
+ def run_match
206
+ Async do
207
+ Sc2.logger.level = :info
208
+
209
+ client = Sc2::ClientManager.obtain(0)
210
+ enemy_client = Sc2::ClientManager.obtain(1)
211
+
212
+ if Gem.win_platform?
213
+ sleep(10)
214
+ end
215
+
216
+ # Ladder specific overrides
217
+ $bot.realtime = false
218
+ $bot.opponent_id = @enemy_botname
219
+
220
+ say_status("host", "connecting...", :cyan)
221
+ $bot.connect(host: client.host, port: client.port)
222
+ say_status("host", "creating game...", :cyan)
223
+ $bot.create_game(map: MapFile.new(map.to_s), players: [
224
+ $bot,
225
+ Sc2::Player.new(race: @enemy_race, name: @enemy_botname, type: Api::PlayerType::PARTICIPANT)
226
+ ], realtime: false)
227
+
228
+ begin
229
+ Async do
230
+ say_status("host", "joining game...", :cyan)
231
+ $bot.join_game(
232
+ server_host: client.host,
233
+ port_config: @port_config
234
+ )
235
+ $bot.add_listener($bot, klass: Sc2::Connection::StatusListener)
236
+ $bot.play
237
+
238
+ begin
239
+ safe_player_name = $bot.name.gsub(/\s*[^A-Za-z0-9.-]\s*/, "_").downcase
240
+ response = $bot.api.save_replay
241
+ path = Pathname(Paths.bot_data_replay_dir).join("autosave-#{safe_player_name}-vs-#{@enemy_botname}.SC2Replay")
242
+ f = File.new(path, "wb:ASCII-8BIT")
243
+ f.write_nonblock(response.data)
244
+ f.close
245
+ rescue
246
+ # Replay save failed
247
+ end
248
+ $bot.disconnect
249
+ end
250
+
251
+ Async do
252
+ say_status("opponent", "joining game...", :cyan)
253
+
254
+ # 192... ip
255
+ # host_ladder_ip = `docker compose #{@compose_args} exec versus_bot sh -c "getent ahostsv4 host.docker.internal | grep STREAM"`.split(" ").first.strip
256
+ # Local ip
257
+ # host_ladder_ip = `docker inspect -f '{{range.NetworkSettings.Networks}}{{.Gateway}}{{end}}' versus_bot_container`.strip
258
+ enemy_command = [@command,
259
+ # "--LadderServer #{client.host}",
260
+ # "--RealTime 0"
261
+ "--LadderServer host.docker.internal",
262
+ "--GamePort #{enemy_client.port}",
263
+ "--StartPort #{@port_config.start_port}",
264
+ "--OpponentId 00000000-0000-0000-0000-000000000000"]
265
+ .join(" ")
266
+
267
+ # cmd = "docker compose #{@compose_args} exec --workdir /bots/#{@enemy_botname} versus_bot bash -c '#{enemy_command}'"
268
+ cmd = "docker compose #{@compose_args} exec --workdir /bots/#{@enemy_botname} versus_bot bash -c '#{enemy_command}'"
269
+
270
+ puts cmd
271
+ process_options = {}
272
+ if Gem.win_platform?
273
+ process_options[:new_pgroup] = true
274
+ else
275
+ process_options[:pgroup] = true
276
+ end
277
+ # ::Async::Process.spawn(cmd, **process_options)
278
+ unless Kernel.system(cmd)
279
+ say_status("opponent", "exited with bad status", :red)
280
+ raise SystemExit
281
+ end
282
+ end
283
+ rescue => e
284
+ say_status("error", e.message, :red)
285
+ raise SystemExit
286
+ # Replay save failed
287
+ end
288
+ end.wait
289
+ ensure
290
+ ClientManager.stop_all
291
+ end
292
+
293
+ def stop_container
294
+ cmd = "docker stop versus_bot_container"
295
+ Kernel.system(cmd)
296
+ end
297
+
298
+ def bye
299
+ say "Game over."
300
+ end
301
+ end
302
+
303
+ # standard:enable Style/GlobalVars
304
+ end
305
+ end
@@ -10,7 +10,7 @@ module Sc2
10
10
 
11
11
  class << self
12
12
  extend Forwardable
13
- def_delegators :instance, :obtain, :get, :start, :stop, :stop_all
13
+ def_delegators :instance, :obtain, :get, :start, :stop, :stop_all, :host
14
14
  end
15
15
 
16
16
  # Gets client for player X or starts an instance
@@ -63,6 +63,8 @@ module Sc2
63
63
  end
64
64
  end
65
65
 
66
+ attr_accessor :host
67
+
66
68
  private
67
69
 
68
70
  attr_accessor :clients, :ports
@@ -27,15 +27,10 @@ module Sc2
27
27
  # Access useful game information. Used in parsed pathing grid, terrain height, placement grid.
28
28
  # Holds Api::ResponseGameInfo::#start_locations.
29
29
  # @return [Api::ResponseGameInfo]
30
- def game_info
31
- if @game_info_task&.running?
32
- @game_info_task&.wait
33
- @game_info_task = nil
34
- end
35
- @game_info
36
- end
30
+ attr_reader :game_info
37
31
 
38
32
  def game_info=(new_info)
33
+ @game_info_loop = game_loop || 0
39
34
  @game_info = new_info
40
35
  end
41
36
 
@@ -81,6 +81,16 @@ module Sc2
81
81
  0..(map_height - 1)
82
82
  end
83
83
 
84
+ # Ensures a Sc2::Position's x/y stays in map tile range
85
+ # Prevents out of bound exceptions when working with minimap
86
+ # @param position [Sc2::Position, Api::Point2D]
87
+ # @return [Sc2::Position, Api::Point2D]
88
+ def clamp_to_grid(position)
89
+ position.y = position.y.clamp(map_tile_range_y)
90
+ position.x = position.x.clamp(map_tile_range_x)
91
+ position
92
+ end
93
+
84
94
  # Map Parsing functions -----
85
95
 
86
96
  # Returns whether a x/y (integer) is placeable as per minimap image data.
@@ -300,6 +310,13 @@ module Sc2
300
310
  # Fix endian for Numo bit parser
301
311
  data = image_data.data.unpack("b*").pack("B*")
302
312
  @parsed_pathing_grid = Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
313
+
314
+ # Remove erroneous mineral blockers as pathable
315
+ bot.neutral.select_type(Api::UnitTypeId::MINERALFIELD450).each do |mineral|
316
+ @parsed_pathing_grid[mineral.pos.y.to_i, mineral.pos.x] = 0
317
+ @parsed_pathing_grid[mineral.pos.y.to_i, mineral.pos.x - 1] = 0
318
+ end
319
+
303
320
  end
304
321
  @parsed_pathing_grid
305
322
  end
@@ -455,7 +472,7 @@ module Sc2
455
472
  while inner_y < length
456
473
  inner_x = 0
457
474
  while inner_x < length
458
- if (input_grid[y + inner_y, x + inner_x]).zero?
475
+ if input_grid[y + inner_y, x + inner_x].zero?
459
476
  output_grid[y / length, x / length] = 0
460
477
  inner_y = length
461
478
  break
@@ -507,7 +524,7 @@ module Sc2
507
524
  end
508
525
 
509
526
  # Split resources by Z axis
510
- resources = bot.neutral.minerals + bot.neutral.geysers
527
+ resources = bot.neutral.minerals.reject_type(Api::UnitTypeId::MINERALFIELD450) + bot.neutral.geysers
511
528
  resource_group_z = resources.group_by do |resource|
512
529
  resource.pos.z.round # 32 units of Y, most maps will have use 3. round to nearest.
513
530
  end
data/lib/sc2ai/player.rb CHANGED
@@ -529,8 +529,6 @@ module Sc2
529
529
  !IDENTIFIED_RACES.include?(race)
530
530
  end
531
531
 
532
- private
533
-
534
532
  # @private
535
533
  CALLBACK_METHODS = %i[on_finish
536
534
  on_random_race_detected
@@ -545,6 +543,7 @@ module Sc2
545
543
  on_structure_started
546
544
  on_structure_completed
547
545
  on_unit_damaged]
546
+ private_constant :CALLBACK_METHODS
548
547
 
549
548
  # @private
550
549
  # @return [Array<Symbol>] callbacks implemented on player class
@@ -599,6 +598,7 @@ module Sc2
599
598
  # Process.clock_gettime(Process::CLOCK_MONOTONIC)
600
599
  step_to_loop = @realtime ? game_loop + @step_count : nil
601
600
  response_observation = @api.observation(game_loop: step_to_loop)
601
+ return if response_observation.nil?
602
602
 
603
603
  # Check if match has a result and callback
604
604
  on_player_result(response_observation.player_result) unless response_observation.player_result.empty?
@@ -671,10 +671,7 @@ module Sc2
671
671
  # Refreshes bot#game_info ignoring all caches
672
672
  # @return [void]
673
673
  public def refresh_game_info
674
- @game_info_task = Async do
675
- self.game_info = @api.game_info
676
- @game_info_task = nil
677
- end
674
+ self.game_info = @api.game_info
678
675
  end
679
676
 
680
677
  # Enemy -----------------------
data/lib/sc2ai/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Sc2
2
2
  # gem version
3
3
  # @return [String]
4
- VERSION = "0.6.1"
4
+ VERSION = "0.6.2"
5
5
  end
@@ -8,13 +8,7 @@ ruby_dir=$base_dir/.ruby
8
8
  export PATH=$ruby_dir/bin:$PATH
9
9
  export LD_LIBRARY_PATH=$(ruby -e 'puts RbConfig::CONFIG["libdir"]')
10
10
 
11
- # Conservative heap sizes are based on 2x a tested medium-sized game
12
- export RUBY_GC_HEAP_INIT_SIZE_40_SLOTS=340000
13
- export RUBY_GC_HEAP_INIT_SIZE_80_SLOTS=50000
14
- export RUBY_GC_HEAP_INIT_SIZE_160_SLOTS=18000
15
- export RUBY_GC_HEAP_INIT_SIZE_320_SLOTS=2000
16
- export RUBY_GC_HEAP_INIT_SIZE_640_SLOTS=1000
17
-
11
+ export RUBY_YJIT_ENABLE=1
18
12
  export AIARENA=true
19
13
  sc2ai ladderconfig
20
14
  exec sc2ai laddermatch "$@" 1>&2
@@ -0,0 +1,11 @@
1
+ {
2
+ "Bots": {
3
+ "<%= botname %>": {
4
+ "Race": "<%= Api::Race.lookup($bot.race).to_s.capitalize %>",
5
+ "Type": "BinaryCpp",
6
+ "RootPath": "./",
7
+ "FileName": "<%= botname %>",
8
+ "Debug": false
9
+ }
10
+ }
11
+ }
@@ -21,7 +21,7 @@ test/
21
21
  tmp/
22
22
  .byebug_history
23
23
  .bundle/
24
- vendor/bundle
24
+ vendor/bundle/
25
25
  lib/bundler/man/
26
26
 
27
27
  # Other
data/sig/sc2ai.rbs CHANGED
@@ -1268,6 +1268,12 @@ module Sc2
1268
1268
  # Returns zero to map_height-1 as range
1269
1269
  def map_tile_range_y: () -> ::Range[untyped]
1270
1270
 
1271
+ # Ensures a Sc2::Position's x/y stays in map tile range
1272
+ # Prevents out of bound exceptions when working with minimap
1273
+ #
1274
+ # _@param_ `position`
1275
+ def clamp_to_grid: ((Sc2::Position | Api::Point2D) position) -> (Sc2::Position | Api::Point2D)
1276
+
1271
1277
  # Returns whether a x/y (integer) is placeable as per minimap image data.
1272
1278
  # It does not say whether a position is occupied by another building.
1273
1279
  # One pixel covers one whole block. Corrects floats on your behalf
@@ -4068,6 +4074,9 @@ module Sc2
4068
4074
 
4069
4075
  def initialize: () -> void
4070
4076
 
4077
+ # Returns the value of attribute host.
4078
+ attr_accessor host: untyped
4079
+
4071
4080
  # Returns the value of attribute clients.
4072
4081
  attr_accessor clients: untyped
4073
4082
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sc2ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dyson Returns
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-02-27 00:00:00.000000000 Z
10
+ date: 2025-04-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: async
@@ -354,9 +354,11 @@ files:
354
354
  - data/setup/setup.SC2Replay
355
355
  - data/stableid.json
356
356
  - data/versions.json
357
+ - docker_build/Dockerfile.aiarenabot
357
358
  - docker_build/Dockerfile.ruby
358
359
  - docker_build/docker-compose-base-image.yml
359
360
  - docker_build/docker-compose-ladderzip.yml
361
+ - docker_build/docker-compose-versus-bot.yml
360
362
  - exe/sc2ai
361
363
  - lib/sc2ai.rb
362
364
  - lib/sc2ai/api/ability_id.rb
@@ -370,6 +372,7 @@ files:
370
372
  - lib/sc2ai/cli/cli.rb
371
373
  - lib/sc2ai/cli/ladderzip.rb
372
374
  - lib/sc2ai/cli/new.rb
375
+ - lib/sc2ai/cli/versus_bot.rb
373
376
  - lib/sc2ai/configuration.rb
374
377
  - lib/sc2ai/connection.rb
375
378
  - lib/sc2ai/connection/connection_listener.rb
@@ -419,6 +422,7 @@ files:
419
422
  - lib/sc2ai/unit_group/geo_ext.rb
420
423
  - lib/sc2ai/version.rb
421
424
  - lib/templates/ladderzip/bin/ladder.tt
425
+ - lib/templates/ladderzip/ladderbots.json.tt
422
426
  - lib/templates/new/.ladderignore
423
427
  - lib/templates/new/Gemfile.tt
424
428
  - lib/templates/new/api/common.proto