sc2ai 0.2.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.
- checksums.yaml +4 -4
- data/data/sc2ai/protocol/common.proto +5 -5
- data/data/sc2ai/protocol/data.proto +22 -22
- data/data/sc2ai/protocol/debug.proto +24 -24
- data/data/sc2ai/protocol/error.proto +216 -215
- data/data/sc2ai/protocol/raw.proto +20 -20
- data/data/sc2ai/protocol/sc2api.proto +111 -111
- data/data/sc2ai/protocol/score.proto +3 -3
- data/data/sc2ai/protocol/spatial.proto +5 -5
- data/data/sc2ai/protocol/ui.proto +16 -16
- data/exe/sc2ai +0 -3
- data/lib/sc2ai/api/data.rb +3 -3
- data/lib/sc2ai/connection/connection_listener.rb +3 -3
- data/lib/sc2ai/connection/requests.rb +31 -35
- data/lib/sc2ai/connection/status_listener.rb +1 -1
- data/lib/sc2ai/connection.rb +1 -1
- data/lib/sc2ai/local_play/client.rb +2 -2
- data/lib/sc2ai/local_play/match.rb +7 -2
- data/lib/sc2ai/paths.rb +12 -2
- data/lib/sc2ai/player/actions.rb +54 -35
- data/lib/sc2ai/player/debug.rb +21 -21
- data/lib/sc2ai/player/game_state.rb +11 -18
- data/lib/sc2ai/player/geo.rb +54 -64
- data/lib/sc2ai/player/units.rb +16 -16
- data/lib/sc2ai/player.rb +103 -41
- data/lib/sc2ai/ports.rb +1 -1
- data/lib/sc2ai/protocol/_meta_documentation.rb +265 -265
- data/lib/sc2ai/protocol/common_pb.rb +3865 -15
- data/lib/sc2ai/protocol/data_pb.rb +9109 -18
- data/lib/sc2ai/protocol/debug_pb.rb +10437 -26
- data/lib/sc2ai/protocol/error_pb.rb +1086 -10
- data/lib/sc2ai/protocol/extensions/ability_remapable.rb +9 -9
- data/lib/sc2ai/protocol/extensions/action.rb +60 -0
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +5 -0
- data/lib/sc2ai/protocol/extensions/position.rb +10 -35
- data/lib/sc2ai/protocol/extensions/power_source.rb +3 -0
- data/lib/sc2ai/protocol/extensions/unit.rb +19 -35
- data/lib/sc2ai/protocol/query_pb.rb +5025 -17
- data/lib/sc2ai/protocol/raw_pb.rb +18350 -27
- data/lib/sc2ai/protocol/sc2api_pb.rb +48420 -93
- data/lib/sc2ai/protocol/score_pb.rb +5968 -12
- data/lib/sc2ai/protocol/spatial_pb.rb +11944 -18
- data/lib/sc2ai/protocol/ui_pb.rb +12927 -28
- data/lib/sc2ai/unit_group/action_ext.rb +0 -2
- data/lib/sc2ai/unit_group/filter_ext.rb +10 -9
- data/lib/sc2ai/unit_group/geo_ext.rb +0 -2
- data/lib/sc2ai/unit_group.rb +1 -1
- data/lib/sc2ai/version.rb +2 -3
- data/lib/sc2ai.rb +10 -11
- data/lib/templates/ladderzip/bin/ladder.tt +0 -3
- data/lib/templates/new/api/common.proto +6 -6
- data/lib/templates/new/api/data.proto +23 -20
- data/lib/templates/new/api/debug.proto +25 -21
- data/lib/templates/new/api/error.proto +217 -215
- data/lib/templates/new/api/query.proto +1 -1
- data/lib/templates/new/api/raw.proto +16 -14
- data/lib/templates/new/api/sc2api.proto +108 -94
- data/lib/templates/new/api/score.proto +4 -3
- data/lib/templates/new/api/spatial.proto +6 -5
- data/lib/templates/new/api/ui.proto +17 -14
- data/lib/templates/new/boot.rb.tt +1 -1
- data/lib/templates/new/my_bot.rb.tt +1 -1
- data/lib/templates/new/run_example_match.rb.tt +2 -2
- data/sig/sc2ai.rbs +11005 -1926
- metadata +26 -21
- data/lib/sc2ai/overrides/kernel.rb +0 -33
- data/sig/minaswan.rbs +0 -10323
data/lib/sc2ai/player/geo.rb
CHANGED
@@ -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 =
|
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,7 +128,7 @@ 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::
|
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
134
|
(x - 2).clamp(map_tile_range_x)..(x + 2).clamp(map_tile_range_x)] = 1
|
@@ -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 =
|
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 =
|
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 =
|
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
|
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 =
|
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=
|
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
|
-
|
379
|
-
|
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
|
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? ||
|
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
|
-
|
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
|
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]
|
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
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
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::
|
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
|
data/lib/sc2ai/player/units.rb
CHANGED
@@ -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 :
|
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
|
@@ -125,7 +125,7 @@ module Sc2
|
|
125
125
|
target: unit_type_id
|
126
126
|
)
|
127
127
|
|
128
|
-
origin = if unit_data(source_unit_types.first).attributes.include?(
|
128
|
+
origin = if unit_data(source_unit_types.first).attributes.include?(Api::Attribute::STRUCTURE)
|
129
129
|
structures
|
130
130
|
else
|
131
131
|
units
|
@@ -215,12 +215,12 @@ module Sc2
|
|
215
215
|
|
216
216
|
# Checks unit data for an attribute value
|
217
217
|
# @param unit [Integer,Api::Unit] Api::UnitTypeId or Api::Unit
|
218
|
-
# @param attribute [Symbol] Api::Attribute, i.e. Api::Attribute::
|
218
|
+
# @param attribute [Symbol] Api::Attribute, i.e. Api::Attribute::MECHANICAL or :Mechanical
|
219
219
|
# @return [Boolean] whether unit has attribute
|
220
220
|
# @example
|
221
|
-
# unit_has_attribute?(Api::UnitTypeId::SCV, Api::Attribute::
|
222
|
-
# unit_has_attribute?(units.workers.first, :
|
223
|
-
# unit_has_attribute?(Api::UnitTypeId::SCV, :
|
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)
|
224
224
|
def unit_has_attribute?(unit, attribute)
|
225
225
|
unit_data(unit).attributes.include? attribute
|
226
226
|
end
|
@@ -355,7 +355,7 @@ module Sc2
|
|
355
355
|
# Categorize own units/structures, enemy units/structures, neutral
|
356
356
|
if unit.is_blip
|
357
357
|
@blips[tag] = unit
|
358
|
-
elsif unit.display_type == :
|
358
|
+
elsif unit.display_type == :PLACEHOLDER
|
359
359
|
@placeholders[tag] = unit
|
360
360
|
elsif unit.alliance == own_alliance || unit.alliance == enemy_alliance
|
361
361
|
if unit.alliance == own_alliance
|
@@ -368,7 +368,7 @@ module Sc2
|
|
368
368
|
end
|
369
369
|
|
370
370
|
unit_data = unit_data(unit.unit_type)
|
371
|
-
if unit_data.attributes.include?
|
371
|
+
if unit_data.attributes.include?(Api::Attribute::STRUCTURE)
|
372
372
|
structure_collection[tag] = unit
|
373
373
|
else
|
374
374
|
unit_collection[tag] = unit
|
@@ -379,8 +379,8 @@ module Sc2
|
|
379
379
|
|
380
380
|
# Dont parse callbacks on first loop or for neutral units
|
381
381
|
if !@previous.all_units.nil? &&
|
382
|
-
unit.alliance != :
|
383
|
-
unit.display_type != :
|
382
|
+
unit.alliance != :NEUTRAL &&
|
383
|
+
unit.display_type != :PLACEHOLDER &&
|
384
384
|
unit.is_blip == false
|
385
385
|
|
386
386
|
previous_unit = @previous.all_units[unit.tag]
|
@@ -402,23 +402,23 @@ module Sc2
|
|
402
402
|
|
403
403
|
# @private
|
404
404
|
# Returns alliance based on whether you are a player or an enemy
|
405
|
-
# @return [:Symbol] :
|
405
|
+
# @return [:Symbol] :SELF or :ENEMY from Api::Alliance
|
406
406
|
def own_alliance
|
407
407
|
if is_a? Sc2::Player::Enemy
|
408
|
-
Api::Alliance.lookup(Api::Alliance::
|
408
|
+
Api::Alliance.lookup(Api::Alliance::ENEMY)
|
409
409
|
else
|
410
|
-
Api::Alliance.lookup(Api::Alliance::
|
410
|
+
Api::Alliance.lookup(Api::Alliance::SELF)
|
411
411
|
end
|
412
412
|
end
|
413
413
|
|
414
414
|
# @private
|
415
415
|
# Returns enemy alliance based on whether you are a player or an enemy
|
416
|
-
# @return [:Symbol] :
|
416
|
+
# @return [:Symbol] :SELF or :ENEMY from Api::Alliance
|
417
417
|
def enemy_alliance
|
418
418
|
if is_a? Sc2::Player::Enemy
|
419
|
-
Api::Alliance.lookup(Api::Alliance::
|
419
|
+
Api::Alliance.lookup(Api::Alliance::SELF)
|
420
420
|
else
|
421
|
-
Api::Alliance.lookup(Api::Alliance::
|
421
|
+
Api::Alliance.lookup(Api::Alliance::ENEMY)
|
422
422
|
end
|
423
423
|
end
|
424
424
|
|
data/lib/sc2ai/player.rb
CHANGED
@@ -21,10 +21,10 @@ module Sc2
|
|
21
21
|
extend Forwardable
|
22
22
|
def_delegators :@api, :add_listener
|
23
23
|
|
24
|
-
# Known races for detecting race on Api::Race::
|
24
|
+
# Known races for detecting race on Api::Race::RANDOM or nil
|
25
25
|
# @!attribute IDENTIFIED_RACES
|
26
|
-
#
|
27
|
-
IDENTIFIED_RACES = [Api::Race::
|
26
|
+
# @return [Array<Integer>]
|
27
|
+
IDENTIFIED_RACES = [Api::Race::PROTOSS, Api::Race::TERRAN, Api::Race::ZERG].freeze
|
28
28
|
|
29
29
|
# @!attribute api
|
30
30
|
# Manages connection to client and performs Requests
|
@@ -57,16 +57,16 @@ module Sc2
|
|
57
57
|
# @return [String] in-game name
|
58
58
|
attr_accessor :name
|
59
59
|
|
60
|
-
# @return [Api::PlayerType::
|
60
|
+
# @return [Integer] Api::PlayerType::PARTICIPANT, Api::PlayerType::COMPUTER, Api::PlayerType::OBSERVER
|
61
61
|
attr_accessor :type
|
62
62
|
|
63
|
-
# if @type is Api::PlayerType::
|
63
|
+
# if @type is Api::PlayerType::COMPUTER, set one of Api::Difficulty scale 1 to 10
|
64
64
|
# @see Api::Difficulty for options
|
65
|
-
# @return [
|
66
|
-
# @return [Api::Difficulty::CheatInsane] if toughest, int 10
|
65
|
+
# @return [Integer]
|
67
66
|
attr_accessor :difficulty
|
68
67
|
|
69
68
|
# @see Api::AIBuild proto for options
|
69
|
+
# @return [Integer]
|
70
70
|
attr_accessor :ai_build
|
71
71
|
|
72
72
|
# @return [String] ladder matches will set an opponent id
|
@@ -148,7 +148,7 @@ module Sc2
|
|
148
148
|
def join_game(server_host:, port_config:)
|
149
149
|
Sc2.logger.debug { "Player \"#{@name}\" joining game..." }
|
150
150
|
response = @api.join_game(name: @name, race: @race, server_host:, port_config:, enable_feature_layer: @enable_feature_layer, interface_options: @interface_options)
|
151
|
-
if response.error != :
|
151
|
+
if response.error != :ENUM_RESPONSE_JOIN_GAME_ERROR_UNSET && response.error != :MISSING_PARTICIPATION
|
152
152
|
raise Sc2::Error, "Player \"#{@name}\" join_game failed: #{response.error}"
|
153
153
|
end
|
154
154
|
add_listener(self, klass: Connection::StatusListener)
|
@@ -167,7 +167,7 @@ module Sc2
|
|
167
167
|
# Bot
|
168
168
|
# race != None
|
169
169
|
# name=''
|
170
|
-
# type: Api::PlayerType::
|
170
|
+
# type: Api::PlayerType::PARTICIPANT
|
171
171
|
|
172
172
|
# An object which interacts with an SC2 client and is game-aware.
|
173
173
|
class Bot < Player
|
@@ -188,7 +188,7 @@ module Sc2
|
|
188
188
|
attr_accessor :geo
|
189
189
|
|
190
190
|
def initialize(race:, name:)
|
191
|
-
super(race:, name:, type: Api::PlayerType::
|
191
|
+
super(race:, name:, type: Api::PlayerType::PARTICIPANT, difficulty: nil, ai_build: nil)
|
192
192
|
@previous = Sc2::Player::PreviousState.new
|
193
193
|
@geo = Sc2::Player::Geo.new(self)
|
194
194
|
|
@@ -206,11 +206,12 @@ module Sc2
|
|
206
206
|
# end
|
207
207
|
def configure
|
208
208
|
end
|
209
|
+
|
209
210
|
alias_method :before_join, :configure
|
210
211
|
|
211
212
|
# TODO: If this suffices for Bot and Observer, they should share this code.
|
212
213
|
# Initializes and refreshes game data and runs the game loop
|
213
|
-
# @return [Api::Result::
|
214
|
+
# @return [Integer] One of Api::Result::VICTORY, Api::Result::DEFEAT, Api::Result::TIE, Api::Result::UNDECIDED
|
214
215
|
def play
|
215
216
|
# Step 0
|
216
217
|
prepare_start
|
@@ -222,16 +223,45 @@ module Sc2
|
|
222
223
|
# Callback for step 0
|
223
224
|
on_step
|
224
225
|
|
226
|
+
# Local play prints out avg times
|
227
|
+
unless Sc2.ladder?
|
228
|
+
running_avg_step_times = []
|
229
|
+
average_runtime = 0.0
|
230
|
+
end
|
231
|
+
|
225
232
|
puts ""
|
233
|
+
|
226
234
|
# Step 1 to n
|
235
|
+
i = 0
|
227
236
|
loop do
|
237
|
+
if i >= 5
|
238
|
+
i = 0
|
239
|
+
end
|
228
240
|
r = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
229
241
|
perform_actions
|
230
242
|
perform_debug_commands unless Sc2.ladder?
|
231
243
|
step_forward
|
232
|
-
|
244
|
+
|
245
|
+
unless Sc2.ladder?
|
246
|
+
time_delta = (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - r) * 1000
|
247
|
+
step_delta = game_loop - @previous.game_loop
|
248
|
+
# running_avg_step_times.shift if running_avg_step_times.size == 5
|
249
|
+
running_avg_step_times << [time_delta, step_delta]
|
250
|
+
|
251
|
+
if i == 0
|
252
|
+
sum_t, sum_s = running_avg_step_times.each_with_object([0, 0]) do |n, total|
|
253
|
+
total[0] += n[0]
|
254
|
+
total[1] += n[1]
|
255
|
+
end
|
256
|
+
average_runtime = sum_t / sum_s
|
257
|
+
running_avg_step_times.clear
|
258
|
+
end
|
259
|
+
print "\e[2K#{step_delta} Step(s) Took (ms): #{"%.2f" % time_delta} | Avg (ms/frame): #{"%.2f" % average_runtime}\n\e[1A\r"
|
260
|
+
end
|
261
|
+
|
262
|
+
i += 1
|
233
263
|
return @result unless @result.nil?
|
234
|
-
break if @status != :
|
264
|
+
break if @status != :IN_GAME
|
235
265
|
end
|
236
266
|
end
|
237
267
|
|
@@ -256,11 +286,11 @@ module Sc2
|
|
256
286
|
# Callbacks ---
|
257
287
|
|
258
288
|
# Override to handle game result (:Victory/:Loss/:Tie)
|
259
|
-
# Called when game has ended with a result, i.e. result =
|
260
|
-
# @param result [Symbol] Api::Result::
|
289
|
+
# Called when game has ended with a result, i.e. result = :Victory
|
290
|
+
# @param result [Symbol] Api::Result::VICTORY or Api::Result::DEFEAT or Api::Result::UNDECIDED
|
261
291
|
# @example
|
262
292
|
# def on_finish(result)
|
263
|
-
# if result == :
|
293
|
+
# if result == :VICTORY
|
264
294
|
# puts "Yay!"
|
265
295
|
# else
|
266
296
|
# puts "Lets try again!"
|
@@ -272,7 +302,7 @@ module Sc2
|
|
272
302
|
|
273
303
|
# Called when Random race is first detected.
|
274
304
|
# Override to handle race identification of random enemy.
|
275
|
-
# @param race [Integer] Api::Race
|
305
|
+
# @param race [Integer] see {Api::Race}
|
276
306
|
def on_random_race_detected(race)
|
277
307
|
end
|
278
308
|
|
@@ -296,9 +326,9 @@ module Sc2
|
|
296
326
|
# @example
|
297
327
|
# alerts.each do |alert|
|
298
328
|
# case alert
|
299
|
-
# when :
|
329
|
+
# when :NUCLEAR_LAUNCH_DETECTED
|
300
330
|
# pp "TAKE COVER!"
|
301
|
-
# when :
|
331
|
+
# when :NYDUS_WORM_DETECTED
|
302
332
|
# pp "FIND THE WORM!"
|
303
333
|
# end
|
304
334
|
# end
|
@@ -418,26 +448,26 @@ module Sc2
|
|
418
448
|
# Allows using CLI launch options hash or "laddorconfig.json"-complient launcher.
|
419
449
|
class BotProcess < Player
|
420
450
|
def initialize(race:, name:)
|
421
|
-
super(race:, name:, type: Api::PlayerType::
|
451
|
+
super(race:, name:, type: Api::PlayerType::PARTICIPANT)
|
422
452
|
raise "not implemented"
|
423
453
|
end
|
424
454
|
end
|
425
455
|
|
426
456
|
# A Computer opponent using the game's built-in AI for a Match
|
427
457
|
class Computer < Player
|
428
|
-
# @param race [Integer]
|
429
|
-
# @param difficulty [Integer]
|
430
|
-
# @param ai_build [
|
458
|
+
# @param race [Integer] see {Api::Race}
|
459
|
+
# @param difficulty [Integer] Api::Difficulty::VERY_EASY, Api::Difficulty::VERY_HARD,etc.
|
460
|
+
# @param ai_build [Integer] default: Api::AIBuild::RANDOM_BUILD
|
431
461
|
# @param name [String]
|
432
462
|
# @return [Sc2::Computer]
|
433
|
-
def initialize(race:, difficulty: Api::Difficulty::
|
463
|
+
def initialize(race:, difficulty: Api::Difficulty::VERY_EASY, ai_build: Api::AIBuild::RANDOM_BUILD,
|
434
464
|
name: "Computer")
|
435
|
-
difficulty = Api::Difficulty::
|
436
|
-
ai_build = Api::AIBuild::
|
465
|
+
difficulty = Api::Difficulty::VERY_EASY if difficulty.nil?
|
466
|
+
ai_build = Api::AIBuild::RANDOM_BUILD if ai_build.nil?
|
437
467
|
raise Error, "unknown difficulty: '#{difficulty}'" if Api::Difficulty.lookup(difficulty).nil?
|
438
468
|
raise Error, "unknown difficulty: '#{ai_build}'" if Api::AIBuild.lookup(ai_build).nil?
|
439
469
|
|
440
|
-
super(race:, name:, type: Api::PlayerType::
|
470
|
+
super(race:, name:, type: Api::PlayerType::COMPUTER, difficulty:, ai_build:)
|
441
471
|
end
|
442
472
|
|
443
473
|
# Returns whether or not the player requires a sc2 instance
|
@@ -455,14 +485,14 @@ module Sc2
|
|
455
485
|
# A human player for a Match
|
456
486
|
class Human < Player
|
457
487
|
def initialize(race:, name:)
|
458
|
-
super(race:, name:, type: Api::PlayerType::
|
488
|
+
super(race:, name:, type: Api::PlayerType::PARTICIPANT)
|
459
489
|
end
|
460
490
|
end
|
461
491
|
|
462
492
|
# A spectator for a Match
|
463
493
|
class Observer < Player
|
464
494
|
def initialize(name: nil)
|
465
|
-
super(race: Api::Race::
|
495
|
+
super(race: Api::Race::NO_RACE, name:, type: Api::PlayerType::OBSERVER)
|
466
496
|
end
|
467
497
|
end
|
468
498
|
|
@@ -476,6 +506,29 @@ module Sc2
|
|
476
506
|
|
477
507
|
private
|
478
508
|
|
509
|
+
# @private
|
510
|
+
CALLBACK_METHODS = %i[on_finish
|
511
|
+
on_random_race_detected
|
512
|
+
on_action_errors
|
513
|
+
on_actions_performed
|
514
|
+
on_alerts
|
515
|
+
on_upgrades_completed
|
516
|
+
on_parse_observation_unit
|
517
|
+
on_unit_destroyed
|
518
|
+
on_unit_created
|
519
|
+
on_unit_type_changed
|
520
|
+
on_structure_started
|
521
|
+
on_structure_completed
|
522
|
+
on_unit_damaged]
|
523
|
+
|
524
|
+
# @private
|
525
|
+
# Checks if callback method is defined on our bot
|
526
|
+
# Used to skip processing on unused callbacks
|
527
|
+
# @param callback [Symbol]
|
528
|
+
def callback_defined?(callback)
|
529
|
+
CALLBACK_METHODS.include?(callback)
|
530
|
+
end
|
531
|
+
|
479
532
|
# Initialize data on step 0 before stepping and before on_start is called
|
480
533
|
def prepare_start
|
481
534
|
@data = Sc2::Data.new(@api.data)
|
@@ -520,6 +573,10 @@ module Sc2
|
|
520
573
|
|
521
574
|
# Save previous frame before continuing
|
522
575
|
@previous.reset(self)
|
576
|
+
|
577
|
+
# We can request game info async, while we process observation
|
578
|
+
refresh_game_info
|
579
|
+
|
523
580
|
# Reset
|
524
581
|
self.observation = response_observation.observation
|
525
582
|
self.game_loop = observation.game_loop
|
@@ -527,17 +584,14 @@ module Sc2
|
|
527
584
|
self.spent_minerals = 0
|
528
585
|
self.spent_vespene = 0
|
529
586
|
self.spent_supply = 0
|
530
|
-
|
531
|
-
if observation.raw_data.map_state.visibility != previous.observation&.raw_data&.map_state&.visibility
|
532
|
-
@parsed_visibility_grid = nil
|
533
|
-
end
|
587
|
+
geo.reset
|
534
588
|
|
535
|
-
#
|
536
|
-
|
537
|
-
|
538
|
-
|
589
|
+
# First game-loop: set enemy and our race if random
|
590
|
+
if enemy.nil?
|
591
|
+
# Finish game_info load immediately, because we need it's info
|
592
|
+
game_info
|
539
593
|
set_enemy
|
540
|
-
set_race_for_random if race == Api::Race::
|
594
|
+
set_race_for_random if race == Api::Race::RANDOM
|
541
595
|
end
|
542
596
|
|
543
597
|
parse_observation_units(response_observation.observation)
|
@@ -549,7 +603,12 @@ module Sc2
|
|
549
603
|
|
550
604
|
# Actions performed and errors (only if implemented)
|
551
605
|
on_actions_performed(response_observation.actions) unless response_observation.actions.empty?
|
552
|
-
on_action_errors
|
606
|
+
if callback_defined?(:on_action_errors)
|
607
|
+
unless response_observation.action_errors.empty?
|
608
|
+
@action_errors.concat(response_observation.action_errors.to_a)
|
609
|
+
end
|
610
|
+
on_action_errors(@action_errors) unless @action_errors&.empty?
|
611
|
+
end
|
553
612
|
on_alerts(observation.alerts) unless observation.alerts.empty?
|
554
613
|
|
555
614
|
# Diff previous observation upgrades to see if anything new completed
|
@@ -578,7 +637,10 @@ module Sc2
|
|
578
637
|
# Refreshes bot#game_info ignoring all caches
|
579
638
|
# @return [void]
|
580
639
|
public def refresh_game_info
|
581
|
-
|
640
|
+
@game_info_task = Async do
|
641
|
+
self.game_info = @api.game_info
|
642
|
+
@game_info_task = nil
|
643
|
+
end
|
582
644
|
end
|
583
645
|
|
584
646
|
# Enemy -----------------------
|
@@ -595,7 +657,7 @@ module Sc2
|
|
595
657
|
self.enemy = Sc2::Player::Enemy.from_proto(player_info: enemy_player_info)
|
596
658
|
|
597
659
|
if enemy.nil?
|
598
|
-
self.enemy = Sc2::Player::Enemy.new(name: "Unknown", race: Api::Race::
|
660
|
+
self.enemy = Sc2::Player::Enemy.new(name: "Unknown", race: Api::Race::RANDOM)
|
599
661
|
end
|
600
662
|
if enemy.race_unknown?
|
601
663
|
detected_race = enemy.detect_race_from_units
|
data/lib/sc2ai/ports.rb
CHANGED