sc2ai 0.1.0 → 0.3.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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/data/sc2ai/protocol/common.proto +6 -6
  3. data/data/sc2ai/protocol/data.proto +23 -20
  4. data/data/sc2ai/protocol/debug.proto +25 -21
  5. data/data/sc2ai/protocol/error.proto +217 -215
  6. data/data/sc2ai/protocol/query.proto +1 -1
  7. data/data/sc2ai/protocol/raw.proto +16 -14
  8. data/data/sc2ai/protocol/sc2api.proto +108 -94
  9. data/data/sc2ai/protocol/score.proto +4 -3
  10. data/data/sc2ai/protocol/spatial.proto +6 -5
  11. data/data/sc2ai/protocol/ui.proto +17 -14
  12. data/exe/sc2ai +0 -3
  13. data/lib/docker_build/Dockerfile.ruby +4 -2
  14. data/lib/sc2ai/api/ability_id.rb +6 -1
  15. data/lib/sc2ai/api/data.rb +18 -3
  16. data/lib/sc2ai/api/tech_tree.rb +1 -1
  17. data/lib/sc2ai/api/tech_tree_data.rb +54 -3
  18. data/lib/sc2ai/connection/connection_listener.rb +3 -3
  19. data/lib/sc2ai/connection/requests.rb +31 -35
  20. data/lib/sc2ai/connection/status_listener.rb +1 -1
  21. data/lib/sc2ai/connection.rb +1 -2
  22. data/lib/sc2ai/local_play/client.rb +2 -2
  23. data/lib/sc2ai/local_play/match.rb +7 -2
  24. data/lib/sc2ai/overrides/async/process/child.rb +1 -1
  25. data/lib/sc2ai/paths.rb +12 -2
  26. data/lib/sc2ai/player/actions.rb +54 -35
  27. data/lib/sc2ai/player/debug.rb +54 -20
  28. data/lib/sc2ai/player/game_state.rb +11 -18
  29. data/lib/sc2ai/player/geo.rb +56 -66
  30. data/lib/sc2ai/player/units.rb +41 -17
  31. data/lib/sc2ai/player.rb +104 -47
  32. data/lib/sc2ai/ports.rb +1 -2
  33. data/lib/sc2ai/protocol/_meta_documentation.rb +270 -25
  34. data/lib/sc2ai/protocol/common_pb.rb +3862 -33
  35. data/lib/sc2ai/protocol/data_pb.rb +9106 -36
  36. data/lib/sc2ai/protocol/debug_pb.rb +10434 -45
  37. data/lib/sc2ai/protocol/error_pb.rb +1084 -29
  38. data/lib/sc2ai/protocol/extensions/ability_remapable.rb +9 -9
  39. data/lib/sc2ai/protocol/extensions/action.rb +60 -0
  40. data/lib/sc2ai/protocol/extensions/point_2_d.rb +9 -0
  41. data/lib/sc2ai/protocol/extensions/position.rb +11 -36
  42. data/lib/sc2ai/protocol/extensions/power_source.rb +3 -0
  43. data/lib/sc2ai/protocol/extensions/unit.rb +61 -36
  44. data/lib/sc2ai/protocol/extensions/unit_type_data.rb +8 -0
  45. data/lib/sc2ai/protocol/query_pb.rb +5022 -36
  46. data/lib/sc2ai/protocol/raw_pb.rb +18347 -46
  47. data/lib/sc2ai/protocol/sc2api_pb.rb +48424 -126
  48. data/lib/sc2ai/protocol/score_pb.rb +5965 -30
  49. data/lib/sc2ai/protocol/spatial_pb.rb +11941 -37
  50. data/lib/sc2ai/protocol/ui_pb.rb +12924 -46
  51. data/lib/sc2ai/unit_group/action_ext.rb +0 -2
  52. data/lib/sc2ai/unit_group/filter_ext.rb +24 -8
  53. data/lib/sc2ai/unit_group/geo_ext.rb +0 -2
  54. data/lib/sc2ai/unit_group.rb +1 -1
  55. data/lib/sc2ai/version.rb +2 -3
  56. data/lib/sc2ai.rb +10 -11
  57. data/lib/templates/ladderzip/bin/ladder.tt +0 -3
  58. data/lib/templates/new/.ladderignore +15 -5
  59. data/lib/templates/new/api/common.proto +6 -6
  60. data/lib/templates/new/api/data.proto +23 -20
  61. data/lib/templates/new/api/debug.proto +25 -21
  62. data/lib/templates/new/api/error.proto +217 -215
  63. data/lib/templates/new/api/query.proto +1 -1
  64. data/lib/templates/new/api/raw.proto +16 -14
  65. data/lib/templates/new/api/sc2api.proto +108 -94
  66. data/lib/templates/new/api/score.proto +4 -3
  67. data/lib/templates/new/api/spatial.proto +6 -5
  68. data/lib/templates/new/api/ui.proto +17 -14
  69. data/lib/templates/new/boot.rb.tt +1 -1
  70. data/lib/templates/new/my_bot.rb.tt +2 -2
  71. data/lib/templates/new/run_example_match.rb.tt +2 -2
  72. data/sig/sc2ai.rbs +11072 -1651
  73. metadata +31 -26
  74. data/lib/sc2ai/overrides/kernel.rb +0 -33
  75. data/lib/sc2ai/protocol/extensions/unit_type.rb +0 -9
@@ -22,7 +22,7 @@ module Sc2
22
22
 
23
23
  # Prints debug text top left corner
24
24
  # @param text [String] will respect newlines
25
- # @param size [Size] of font, default 14px
25
+ # @param size [Integer] of font, default 14px
26
26
  # @return [void]
27
27
  def debug_print(text, size: 14)
28
28
  queue_debug_command Api::DebugCommand.new(
@@ -43,7 +43,7 @@ module Sc2
43
43
  # @param left_percent [Numeric] range 0..100. percent from left of screen
44
44
  # @param top_percent [Numeric] range 0..100. percent from top of screen
45
45
  # @param color [Api::Color] default white
46
- # @param size [Size] of font, default 14px
46
+ # @param size [Integer] of font, default 14px
47
47
  # @return [void]
48
48
  def debug_text_screen(text, left_percent: 1.0, top_percent: 1.0, color: nil, size: 14)
49
49
  queue_debug_command Api::DebugCommand.new(
@@ -67,7 +67,7 @@ module Sc2
67
67
  # @param text [String] will respect newlines
68
68
  # @param point [Api::Point] point in the world, i.e. unit.pos
69
69
  # @param color [Api::Color] default white
70
- # @param size [Size] of font, default 14px
70
+ # @param size [Integer] of font, default 14px
71
71
  # @return [void]
72
72
  def debug_text_world(text, point:, color: nil, size: 14)
73
73
  queue_debug_command Api::DebugCommand.new(
@@ -121,8 +121,8 @@ module Sc2
121
121
  draw: Api::DebugDraw.new(
122
122
  boxes: [
123
123
  Api::DebugBox.new(
124
- min: Api::Point.new(x: point.x - radius, y: point.y - radius, z: point.z + 0.02),
125
- max: Api::Point.new(x: point.x + radius, y: point.y + radius, z: point.z + (radius * 2) + 0.02),
124
+ min: Api::Point.new(x: point.x - radius, y: point.y - radius, z: point.z + 0.03),
125
+ max: Api::Point.new(x: point.x + radius, y: point.y + radius, z: point.z + (radius * 2) + 0.03),
126
126
  color:
127
127
  )
128
128
  ]
@@ -149,22 +149,56 @@ module Sc2
149
149
  )
150
150
  end
151
151
 
152
+ # Renders a block on the floor, drawn by 4 lines
153
+ # Pass in either a pos (Position/Unit) or exact x * y coordinates
154
+ # Optional indentation adds padding on borders inward
155
+ # @param pos [Api::Unit, Sc2::Position]
156
+ # @param x [Float, Integer]
157
+ # @param y [Float, Integer]
158
+ # @param color [Api::Color]
159
+ # @param indent [Float] default 0.05. should be lower than < 1.0
160
+ # @example
161
+ # debug_tile(x: 12.3, y: 4.56, color: Api::Color.new(r: 255, g: 0, b: 0))
162
+ # debug_tile(some_unit)
163
+ # debug_tile(some_unit.pos)
164
+ def debug_tile(pos = nil, x: nil, y: nil, color: nil, indent: 0.05)
165
+ if pos.is_a?(Api::Unit)
166
+ x = pos.pos.x.floor
167
+ y = pos.pos.y.floor
168
+ elsif pos.is_a?(Sc2::Position)
169
+ x = pos.x.floor
170
+ y = pos.y.floor
171
+ end
172
+
173
+ # Raise above floor to prevent texture clipping
174
+ z = geo.terrain_height(x:, y:).to_f + 0.1
175
+ tl = Api::Point[x + indent, y + 1.0 - indent, z]
176
+ bl = Api::Point[x + indent, y + indent, z]
177
+ br = Api::Point[x + 1.0 - indent, y + indent, z]
178
+ tr = Api::Point[x + 1.0 - indent, y + 1.0 - indent, z]
179
+
180
+ debug_draw_line(p0: tl, p1: bl, color:)
181
+ debug_draw_line(p0: bl, p1: br, color:)
182
+ debug_draw_line(p0: br, p1: tr, color:)
183
+ debug_draw_line(p0: tr, p1: tl, color:)
184
+ end
185
+
152
186
  # Other Commands ---
153
187
 
154
188
  # @param command [Integer] one of Api::DebugGameState::*
155
189
  # Possible values:
156
- # Api::DebugGameState::Show_map
157
- # Api::DebugGameState::Control_enemy
158
- # Api::DebugGameState::Food
159
- # Api::DebugGameState::Free
160
- # Api::DebugGameState::All_resources
161
- # Api::DebugGameState::God
162
- # Api::DebugGameState::Minerals
163
- # Api::DebugGameState::Gas
164
- # Api::DebugGameState::Cooldown
165
- # Api::DebugGameState::Tech_tree
166
- # Api::DebugGameState::Upgrade
167
- # Api::DebugGameState::Fast_build
190
+ # Api::DebugGameState::SHOW_MAP
191
+ # Api::DebugGameState::CONTROL_ENEMY
192
+ # Api::DebugGameState::FOOD
193
+ # Api::DebugGameState::FREE
194
+ # Api::DebugGameState::ALL_RESOURCES
195
+ # Api::DebugGameState::GOD
196
+ # Api::DebugGameState::MINERALS
197
+ # Api::DebugGameState::GAS
198
+ # Api::DebugGameState::COOLDOWN
199
+ # Api::DebugGameState::TECH_TREE
200
+ # Api::DebugGameState::UPGRADE
201
+ # Api::DebugGameState::FAST_BUILD
168
202
  # @return [void]
169
203
  def debug_game_state(command)
170
204
  queue_debug_command Api::DebugCommand.new(
@@ -203,7 +237,7 @@ module Sc2
203
237
 
204
238
  # @private
205
239
  # Hangs, crashes and exits the Sc2 client. DO NOT USE.
206
- # @param test [Integer] one of Api::DebugTestProcess::Test::Crash, Api::DebugTestProcess::Test::Hang, Api::DebugTestProcess::Test::Exit
240
+ # @param test [Integer] one of Api::DebugTestProcess::Test::CRASH, Api::DebugTestProcess::Test::HANG, Api::DebugTestProcess::Test::EXIT
207
241
  # @param delay_ms [Integer] default 0, how long this test is delayed
208
242
  # @return [void]
209
243
  def debug_test_process(test:, delay_ms: 0)
@@ -226,8 +260,8 @@ module Sc2
226
260
  )
227
261
  end
228
262
 
229
- # Ends game with a specified result of either Surrender or DeclareVictory
230
- # @param end_result [Integer] either 1/2. Api::DebugEndGame::EndResult::Surrender or Api::DebugEndGame::EndResult::DeclareVictory
263
+ # Ends game with a specified result of either :SURRENDER or :DECLARE_VICTORY
264
+ # @param end_result [Integer] either 1/2. Api::DebugEndGame::EndResult::SURRENDER or Api::DebugEndGame::EndResult::DECLARE_VICTORY
231
265
  # @return [void]
232
266
  def debug_end_game(end_result:)
233
267
  queue_debug_command Api::DebugCommand.new(
@@ -5,7 +5,7 @@ module Sc2
5
5
  # Holds game state
6
6
  module GameState
7
7
  # @!attribute status
8
- # @return [:launched, :in_game, :in_replay, :ended, :quit, :unknown] status
8
+ # @return [:LAUNCHED, :IN_GAME, :IN_REPLAY, :ENDED, :QUIT, :UNKNOWN] status
9
9
  attr_accessor :status
10
10
 
11
11
  include Connection::StatusListener
@@ -27,10 +27,16 @@ 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
- attr_reader :game_info
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
31
37
 
32
38
  def game_info=(new_info)
33
- @game_info_loop = game_loop || 0
39
+ @game_info_loop = game_loop
34
40
  @game_info = new_info
35
41
  end
36
42
 
@@ -40,19 +46,6 @@ module Sc2
40
46
  # @return [Integer]
41
47
  attr_accessor :game_info_loop
42
48
 
43
- # Determines if your game_info will be refreshed at this moment
44
- # Has a hard-capped refresh of only ever 4 steps
45
- # In general game_info is only refreshed Player::Bot reads from pathing_grid or placement_grid
46
- # @return [Boolean]
47
- def game_info_stale?
48
- return true if game_info_loop.nil? || game_info.nil?
49
- return false if game_info_loop == game_loop
50
-
51
- # Note: No minimum step count set anymore
52
- # We can do something like, only updating every 2+ frames:
53
- game_info_loop + 4 <= game_loop
54
- end
55
-
56
49
  # @!attribute data
57
50
  # @return [Api::ResponseData]
58
51
  attr_accessor :data
@@ -141,14 +134,14 @@ module Sc2
141
134
  player_id: 0,
142
135
  minerals: 50,
143
136
  vespene: 0,
144
- food_cap: ((race == Api::Race::Zerg) ? 14 : 15),
137
+ food_cap: ((race == Api::Race::ZERG) ? 14 : 15),
145
138
  food_used: 12,
146
139
  food_army: 0,
147
140
  food_workers: 12,
148
141
  idle_worker_count: 0,
149
142
  army_count: 0,
150
143
  warp_gate_count: 0,
151
- larva_count: ((race == Api::Race::Zerg) ? 3 : 0)
144
+ larva_count: ((race == Api::Race::ZERG) ? 3 : 0)
152
145
  )
153
146
  end
154
147
 
@@ -11,10 +11,30 @@ module Sc2
11
11
  # @return [Sc2::Player] player with active connection
12
12
  attr_accessor :bot
13
13
 
14
+ # @private
14
15
  def initialize(bot)
15
16
  @bot = bot
16
17
  end
17
18
 
19
+ # @private
20
+ # Called once per update loop.
21
+ # It will clear memoization and caches where necessary
22
+ # @return [void]
23
+ def reset
24
+ # Only re-parse and cache-bust if strings don't match
25
+ if bot.game_info.start_raw.pathing_grid.data != bot.previous&.game_info&.start_raw&.pathing_grid&.data
26
+ @parsed_pathing_grid = nil
27
+ clear_placement_cache
28
+ end
29
+ if bot.observation.raw_data.map_state.creep.data != bot.previous.observation.raw_data&.map_state&.creep&.data
30
+ @parsed_creep = nil
31
+ clear_placement_cache
32
+ end
33
+ if bot.observation.raw_data.map_state.visibility.data != bot.previous.observation.raw_data&.map_state&.visibility&.data
34
+ @parsed_visibility_grid = nil
35
+ end
36
+ end
37
+
18
38
  # Gets the map tile width. Range is 1-255.
19
39
  # Effected by crop_to_playable_area
20
40
  # @return [Integer]
@@ -78,13 +98,13 @@ module Sc2
78
98
  # Each value in [row][column] holds a boolean value represented as an integer
79
99
  # It does not say whether a position is occupied by another building.
80
100
  # One pixel covers one whole block. Rounds fractionated positions down.
81
- # @return [Numo::Bit]
101
+ # @return [::Numo::Bit]
82
102
  def parsed_placement_grid
83
103
  if @parsed_placement_grid.nil?
84
104
  image_data = bot.game_info.start_raw.placement_grid
85
105
  # Fix endian for Numo bit parser
86
106
  data = image_data.data.unpack("b*").pack("B*")
87
- @parsed_placement_grid = ::Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
107
+ @parsed_placement_grid = Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
88
108
  end
89
109
  @parsed_placement_grid
90
110
  end
@@ -99,7 +119,7 @@ module Sc2
99
119
  end
100
120
 
101
121
  # Returns a grid where only the expo locations are marked
102
- # @return [Numo::Bit]
122
+ # @return [::Numo::Bit]
103
123
  def expo_placement_grid
104
124
  if @expo_placement_grid.nil?
105
125
  @expo_placement_grid = Numo::Bit.zeros(map_height, map_width)
@@ -108,13 +128,13 @@ module Sc2
108
128
  y = point.y.floor
109
129
 
110
130
  # For zerg, reserve a layer at the bottom for larva->egg
111
- if bot.race == Api::Race::Zerg
131
+ if bot.race == Api::Race::ZERG
112
132
  # Reserve one row lower, meaning (y-3) instead of (y-2)
113
133
  @expo_placement_grid[(y - 3).clamp(map_tile_range_y)..(y + 2).clamp(map_tile_range_y),
114
- (x - 2).clamp(map_tile_range_y)..(x + 2).clamp(map_tile_range_y)] = 1
134
+ (x - 2).clamp(map_tile_range_x)..(x + 2).clamp(map_tile_range_x)] = 1
115
135
  else
116
136
  @expo_placement_grid[(y - 2).clamp(map_tile_range_y)..(y + 2).clamp(map_tile_range_y),
117
- (x - 2).clamp(map_tile_range_y)..(x + 2).clamp(map_tile_range_y)] = 1
137
+ (x - 2).clamp(map_tile_range_x)..(x + 2).clamp(map_tile_range_x)] = 1
118
138
  end
119
139
  end
120
140
  end
@@ -123,7 +143,7 @@ module Sc2
123
143
 
124
144
  # Returns a grid where only placement obstructions are marked
125
145
  # includes Tumors and lowered Supply Depots
126
- # @return [Numo::Bit]
146
+ # @return [::Numo::Bit]
127
147
  private def placement_obstruction_grid
128
148
  # Get obstructing structures
129
149
  obstructure_types = [Api::UnitTypeId::SUPPLYDEPOTLOWERED, Api::UnitTypeId::CREEPTUMORQUEEN, Api::UnitTypeId::CREEPTUMOR, Api::UnitTypeId::CREEPTUMORBURROWED]
@@ -158,7 +178,7 @@ module Sc2
158
178
  end
159
179
 
160
180
  # Returns a grid where powered locations are marked true
161
- # @return [Numo::Bit]
181
+ # @return [::Numo::Bit]
162
182
  def parsed_power_grid
163
183
  # Cache for based on power unit tags
164
184
  cache_key = bot.power_sources.map(&:tag).sort.hash
@@ -188,7 +208,7 @@ module Sc2
188
208
  # 00001111110000
189
209
  # perf: Saving pre-created shape for speed (0.5ms saved) by using hardcode from .to_binary.unpack("C*")
190
210
  blueprint_data = [0, 0, 254, 193, 255, 248, 127, 254, 159, 255, 231, 243, 249, 124, 254, 159, 255, 231, 255, 241, 63, 248, 7, 0, 0].pack("C*")
191
- blueprint_pylon = ::Numo::Bit.from_binary(blueprint_data, [14, 14])
211
+ blueprint_pylon = Numo::Bit.from_binary(blueprint_data, [14, 14])
192
212
 
193
213
  # Warp Prism
194
214
  # 00011000
@@ -200,7 +220,7 @@ module Sc2
200
220
  # 01111110
201
221
  # 00011000
202
222
  blueprint_data = [24, 126, 126, 255, 255, 126, 126, 24].pack("C*")
203
- blueprint_prism = ::Numo::Bit.from_binary(blueprint_data, [8, 8])
223
+ blueprint_prism = Numo::Bit.from_binary(blueprint_data, [8, 8])
204
224
 
205
225
  # Print each power-source on map using shape above
206
226
  bot.power_sources.each do |ps|
@@ -273,20 +293,13 @@ module Sc2
273
293
  # parsed_pathing_grid[0,0] # reads bottom left corner
274
294
  # # use helper function #pathable
275
295
  # pathable?(x: 0, y: 0) # reads bottom left corner
276
- # @return [Numo::Bit] Numo array
296
+ # @return [::Numo::Bit] Numo array
277
297
  def parsed_pathing_grid
278
- if bot.game_info_stale?
279
- previous_data = bot.game_info.start_raw.pathing_grid.data
280
- bot.refresh_game_info
281
- # Only re-parse if binary strings don't match
282
- clear_cached_pathing_grid if previous_data != bot.game_info.start_raw.pathing_grid.data
283
- end
284
-
285
298
  if @parsed_pathing_grid.nil?
286
299
  image_data = bot.game_info.start_raw.pathing_grid
287
300
  # Fix endian for Numo bit parser
288
301
  data = image_data.data.unpack("b*").pack("B*")
289
- @parsed_pathing_grid = ::Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
302
+ @parsed_pathing_grid = Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
290
303
  end
291
304
  @parsed_pathing_grid
292
305
  end
@@ -294,8 +307,7 @@ module Sc2
294
307
  # Clears pathing-grid dependent objects like placements.
295
308
  # Called when pathing grid gets updated
296
309
  #
297
- private def clear_cached_pathing_grid
298
- @parsed_pathing_grid = nil
310
+ private def clear_placement_cache
299
311
  @_build_coordinates = {}
300
312
  @_build_coordinate_tree = {}
301
313
  end
@@ -318,13 +330,13 @@ module Sc2
318
330
 
319
331
  # Returns a parsed terrain_height from bot.game_info.start_raw.
320
332
  # Each value in [row][column] holds a float value which is the z height
321
- # @return [Numo::SFloat] Numo array
333
+ # @return [::Numo::SFloat] Numo array
322
334
  def parsed_terrain_height
323
335
  if @parsed_terrain_height.nil?
324
336
 
325
337
  image_data = bot.game_info.start_raw.terrain_height
326
- @parsed_terrain_height = ::Numo::UInt8.from_binary(image_data.data,
327
- [image_data.size.y, image_data.size.x])
338
+ @parsed_terrain_height = Numo::UInt8
339
+ .from_binary(image_data.data, [image_data.size.y, image_data.size.x])
328
340
  .cast_to(Numo::SFloat)
329
341
 
330
342
  # Values are between -16 and +16. The api values is a float height compressed to rgb range (0-255) in that range of 32.
@@ -339,7 +351,7 @@ module Sc2
339
351
  # Returns one of three Integer visibility indicators at tile for x & y
340
352
  # @param x [Float, Integer]
341
353
  # @param y [Float, Integer]
342
- # @return [Integer] 0=Hidden,1= Snapshot,2=Visible
354
+ # @return [Integer] 0=HIDDEN,1=SNAPSHOT,2=VISIBLE
343
355
  def visibility(x:, y:)
344
356
  parsed_visibility_grid[y.to_i, x.to_i]
345
357
  end
@@ -371,12 +383,13 @@ module Sc2
371
383
  # Returns a parsed map_state.visibility from bot.observation.raw_data.
372
384
  # Each value in [row][column] holds one of three integers (0,1,2) to flag a vision type
373
385
  # @see #visibility for reading from this value
374
- # @return [Numo::SFloat] Numo array
386
+ # @return [::Numo::SFloat] Numo array
375
387
  def parsed_visibility_grid
376
388
  if @parsed_visibility_grid.nil?
377
389
  image_data = bot.observation.raw_data.map_state.visibility
378
- @parsed_visibility_grid = ::Numo::UInt8.from_binary(image_data.data,
379
- [image_data.size.y, image_data.size.x])
390
+ # Fix endian for Numo bit parser
391
+ data = image_data.data.unpack("b*").pack("B*")
392
+ @parsed_visibility_grid = Numo::UInt8.from_binary(data, [image_data.size.y, image_data.size.x])
380
393
  end
381
394
  @parsed_visibility_grid
382
395
  end
@@ -391,17 +404,16 @@ module Sc2
391
404
  end
392
405
 
393
406
  # Provides parsed minimap representation of creep spread
394
- # Caches for 4 frames
395
- # @return [Numo::Bit] Numo array
407
+ # Caches for this frame
408
+ # @return [::Numo::Bit] Numo array
396
409
  def parsed_creep
397
- if @parsed_creep.nil? || @parsed_creep[1] + 4 < bot.game_loop
410
+ if @parsed_creep.nil? # || image_data != previous_data
398
411
  image_data = bot.observation.raw_data.map_state.creep
399
412
  # Fix endian for Numo bit parser
400
413
  data = image_data.data.unpack("b*").pack("B*")
401
- result = ::Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
402
- @parsed_creep = [result, bot.game_loop]
414
+ @parsed_creep = Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
403
415
  end
404
- @parsed_creep[0]
416
+ @parsed_creep
405
417
  end
406
418
 
407
419
  # TODO: Removing. Better name or more features for this? Maybe check nearest units.
@@ -416,7 +428,7 @@ module Sc2
416
428
  # TODO: Remove this method if it has no use. Build points uses this code directly for optimization.
417
429
  # Reduce the dimensions of a grid by merging cells using length x length squares.
418
430
  # Merged cell keeps it's 1 value only if all merged cells are equal to 1, else 0
419
- # @param input_grid [Numo::Bit] Bit grid like parsed_pathing_grid or parsed_placement_grid
431
+ # @param input_grid [::Numo::Bit] Bit grid like parsed_pathing_grid or parsed_placement_grid
420
432
  # @param length [Integer] how many cells to merge, i.e. 3 for finding 3x3 placement
421
433
  def divide_grid(input_grid, length)
422
434
  height = input_grid.shape[0]
@@ -713,7 +725,7 @@ module Sc2
713
725
  length = 1 if length < 1
714
726
  @_build_coordinates ||= {}
715
727
  cache_key = [length, on_creep, in_power].hash
716
- return @_build_coordinates[cache_key] if !@_build_coordinates[cache_key].nil? && !bot.game_info_stale?
728
+ return @_build_coordinates[cache_key] unless @_build_coordinates[cache_key].nil?
717
729
 
718
730
  result = []
719
731
  input_grid = parsed_pathing_grid & parsed_placement_grid & ~expo_placement_grid & ~placement_obstruction_grid
@@ -722,7 +734,6 @@ module Sc2
722
734
  else
723
735
  ~parsed_creep & input_grid
724
736
  end
725
-
726
737
  input_grid = parsed_power_grid & input_grid if in_power
727
738
 
728
739
  # Dimensions
@@ -736,35 +747,14 @@ module Sc2
736
747
  # Build points are in center of square, i.e. 1.5 inwards for a 3x3 building
737
748
  offset_to_inside = length / 2.0
738
749
 
739
- # Note, these loops are structured for speed
740
- y = 0
741
- while y < capped_height
742
- x = 0
743
- while x < capped_width
744
- # We are on the bottom-left of a placement tile of Length x Length
745
- # Check right- and upwards for any negatives and break both loops, as soon as we find one
746
- valid_position = true
747
- inner_y = 0
748
- while inner_y < length
749
- inner_x = 0
750
- while inner_x < length
751
- if (input_grid[y + inner_y, x + inner_x]).zero?
752
- # break sub-cells check and don't save position
753
- valid_position = false
754
- inner_y = length
755
- break
756
- end
757
- inner_x += 1
758
- end
759
- inner_y += 1
760
- end
761
- # End of checking sub-cells
762
-
763
- result << [x + offset_to_inside, y + offset_to_inside] if valid_position
764
- x += length
765
- end
766
- y += length
750
+ output_grid = input_grid[0...capped_height, 0...capped_width]
751
+ .reshape(capped_height / length, length, capped_width / length, length)
752
+ .all?(1, 3)
753
+ output_grid.where.each do |true_index|
754
+ y, x = true_index.divmod(capped_width / length)
755
+ result << [x * length + offset_to_inside, y * length + offset_to_inside]
767
756
  end
757
+
768
758
  @_build_coordinates[cache_key] = result
769
759
  end
770
760
 
@@ -781,7 +771,7 @@ module Sc2
781
771
  target = target.pos if target.is_a? Api::Unit
782
772
  random = 1 if random.to_i.negative?
783
773
  length = 1 if length < 1
784
- on_creep = bot.race == Api::Race::Zerg
774
+ on_creep = bot.race == Api::Race::ZERG
785
775
 
786
776
  coordinates = build_coordinates(length:, on_creep:, in_power:)
787
777
  cache_key = coordinates.hash
@@ -23,7 +23,7 @@ module Sc2
23
23
  # @return [Sc2::UnitGroup] a group of placeholder structures
24
24
  attr_accessor :placeholders
25
25
 
26
- # All units with alliance :Neutral
26
+ # All units with alliance :NEUTRAL
27
27
  # @!attribute neutral
28
28
  # @return [Sc2::UnitGroup] a group of neutral units
29
29
  attr_accessor :neutral
@@ -91,6 +91,17 @@ module Sc2
91
91
  # For this unit type, tells you how many are in progress by checking orders for all it's sources.
92
92
  # @return [Integer]
93
93
  def units_in_progress(unit_type_id)
94
+ if unit_type_id == Api::UnitTypeId::REACTOR
95
+ return units_in_progress(Api::UnitTypeId::BARRACKSREACTOR) +
96
+ units_in_progress(Api::UnitTypeId::FACTORYREACTOR) +
97
+ units_in_progress(Api::UnitTypeId::STARPORTREACTOR)
98
+ elsif unit_type_id == Api::UnitTypeId::TECHLAB
99
+ return units_in_progress(Api::UnitTypeId::BARRACKSTECHLAB) +
100
+ units_in_progress(Api::UnitTypeId::FACTORYTECHLAB) +
101
+ units_in_progress(Api::UnitTypeId::STARPORTTECHLAB)
102
+ end
103
+
104
+ # Get source unit
94
105
  source_unit_types = Api::TechTree.unit_created_from(unit_type_id: unit_type_id)
95
106
 
96
107
  # When building from LARVA, check the intermediate models
@@ -114,14 +125,27 @@ module Sc2
114
125
  target: unit_type_id
115
126
  )
116
127
 
117
- origin = if unit_data(source_unit_types.first).attributes.include?(:Structure)
128
+ origin = if unit_data(source_unit_types.first).attributes.include?(Api::Attribute::STRUCTURE)
118
129
  structures
119
130
  else
120
131
  units
121
132
  end
133
+ # For SCV, the structure might be completed but dangling order for a few frames.
134
+ source_is_scv = source_unit_types.include?(Api::UnitTypeId::SCV)
135
+
136
+ # Let's count orders matching the ability
122
137
  total_in_progress = origin.select_type(source_unit_types).sum do |source|
123
138
  source.orders.count do |order|
124
- true if order.ability_id == unit_create_ability
139
+ if order.ability_id == unit_create_ability
140
+ if source_is_scv
141
+ # If we are a SCV, do not count our order if the target is a completed structure pos
142
+ structures.select_type(unit_type_id).completed.none? do |structure|
143
+ structure.pos == order.target_world_space_pos
144
+ end
145
+ else
146
+ true
147
+ end
148
+ end
125
149
  end
126
150
  end
127
151
  total_in_progress *= 2 if unit_type_id == Api::UnitTypeId::ZERGLING
@@ -191,12 +215,12 @@ module Sc2
191
215
 
192
216
  # Checks unit data for an attribute value
193
217
  # @param unit [Integer,Api::Unit] Api::UnitTypeId or Api::Unit
194
- # @param attribute [Symbol] Api::Attribute, i.e. Api::Attribute::Mechanical or :Mechanical
218
+ # @param attribute [Symbol] Api::Attribute, i.e. Api::Attribute::MECHANICAL or :Mechanical
195
219
  # @return [Boolean] whether unit has attribute
196
220
  # @example
197
- # unit_has_attribute?(Api::UnitTypeId::SCV, Api::Attribute::Mechanical)
198
- # unit_has_attribute?(units.workers.first, :Mechanical)
199
- # unit_has_attribute?(Api::UnitTypeId::SCV, :Mechanical)
221
+ # unit_has_attribute?(Api::UnitTypeId::SCV, Api::Attribute::MECHANICAL)
222
+ # unit_has_attribute?(units.workers.first, :MECHANICAL)
223
+ # unit_has_attribute?(Api::UnitTypeId::SCV, :MECHANICAL)
200
224
  def unit_has_attribute?(unit, attribute)
201
225
  unit_data(unit).attributes.include? attribute
202
226
  end
@@ -331,7 +355,7 @@ module Sc2
331
355
  # Categorize own units/structures, enemy units/structures, neutral
332
356
  if unit.is_blip
333
357
  @blips[tag] = unit
334
- elsif unit.display_type == :Placeholder
358
+ elsif unit.display_type == :PLACEHOLDER
335
359
  @placeholders[tag] = unit
336
360
  elsif unit.alliance == own_alliance || unit.alliance == enemy_alliance
337
361
  if unit.alliance == own_alliance
@@ -344,7 +368,7 @@ module Sc2
344
368
  end
345
369
 
346
370
  unit_data = unit_data(unit.unit_type)
347
- if unit_data.attributes.include? :Structure
371
+ if unit_data.attributes.include?(Api::Attribute::STRUCTURE)
348
372
  structure_collection[tag] = unit
349
373
  else
350
374
  unit_collection[tag] = unit
@@ -355,8 +379,8 @@ module Sc2
355
379
 
356
380
  # Dont parse callbacks on first loop or for neutral units
357
381
  if !@previous.all_units.nil? &&
358
- unit.alliance != :Neutral &&
359
- unit.display_type != :Placeholder &&
382
+ unit.alliance != :NEUTRAL &&
383
+ unit.display_type != :PLACEHOLDER &&
360
384
  unit.is_blip == false
361
385
 
362
386
  previous_unit = @previous.all_units[unit.tag]
@@ -378,23 +402,23 @@ module Sc2
378
402
 
379
403
  # @private
380
404
  # Returns alliance based on whether you are a player or an enemy
381
- # @return [:Symbol] :Self or :Enemy from Api::Alliance
405
+ # @return [:Symbol] :SELF or :ENEMY from Api::Alliance
382
406
  def own_alliance
383
407
  if is_a? Sc2::Player::Enemy
384
- Api::Alliance.lookup(Api::Alliance::Enemy)
408
+ Api::Alliance.lookup(Api::Alliance::ENEMY)
385
409
  else
386
- Api::Alliance.lookup(Api::Alliance::Self)
410
+ Api::Alliance.lookup(Api::Alliance::SELF)
387
411
  end
388
412
  end
389
413
 
390
414
  # @private
391
415
  # Returns enemy alliance based on whether you are a player or an enemy
392
- # @return [:Symbol] :Self or :Enemy from Api::Alliance
416
+ # @return [:Symbol] :SELF or :ENEMY from Api::Alliance
393
417
  def enemy_alliance
394
418
  if is_a? Sc2::Player::Enemy
395
- Api::Alliance.lookup(Api::Alliance::Self)
419
+ Api::Alliance.lookup(Api::Alliance::SELF)
396
420
  else
397
- Api::Alliance.lookup(Api::Alliance::Enemy)
421
+ Api::Alliance.lookup(Api::Alliance::ENEMY)
398
422
  end
399
423
  end
400
424