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