sc2ai 0.1.0 → 0.3.0

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