sc2ai 0.0.3 → 0.0.4
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/lib/sc2ai/player/actions.rb +4 -3
- data/lib/sc2ai/player/debug.rb +50 -4
- data/lib/sc2ai/player/geometry.rb +74 -5
- data/lib/sc2ai/player/units.rb +2 -2
- data/lib/sc2ai/player.rb +7 -7
- data/lib/sc2ai/protocol/extensions/point.rb +13 -1
- data/lib/sc2ai/protocol/extensions/unit.rb +54 -7
- data/lib/sc2ai/unit_group/action_ext.rb +22 -2
- data/lib/sc2ai/unit_group.rb +22 -10
- data/lib/sc2ai/version.rb +1 -1
- data/lib/templates/new/my_bot.rb.tt +2 -3
- data/sig/sc2ai.rbs +10183 -0
- metadata +37 -78
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fcc96e2f18e9cc95b0fd8b9427d3c76866551031b69811251b547eeb13284cba
|
4
|
+
data.tar.gz: 88c3dce17dba1e6b4378c306de8d754e1f33dcd092a9e44ac7477e50940ca911
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39997d407d6fbf2ffc936c387f703d4b12d6aff1f016bc1905148a6590f156c4f39acee982074be28d33ea9451cbbf483f985c62a902693c65da450465d915e2
|
7
|
+
data.tar.gz: af9ed623bf2ee6ce905127fb89828a45c95456b4996e1aa4f6c3d477d31b383ed54d425de80abcd28f2df9cf08bfa127fbd36c89f822e226804b116bf20e73c1
|
data/lib/sc2ai/player/actions.rb
CHANGED
@@ -17,7 +17,7 @@ module Sc2
|
|
17
17
|
# Queues a Api::ActionRaw. Perform ability on unit_tags optionally on target_world_space_pos/target_unit_tag
|
18
18
|
# @param unit_tags [Array<Integer>]
|
19
19
|
# @param ability_id [Integer]
|
20
|
-
# @param queue_command [Boolean]
|
20
|
+
# @param queue_command [Boolean] shift+command
|
21
21
|
# @param target_world_space_pos [Api::Point2D]
|
22
22
|
# @param target_unit_tag [Integer]
|
23
23
|
def action_raw_unit_command(unit_tags:, ability_id:, queue_command: false, target_world_space_pos: nil, target_unit_tag: nil)
|
@@ -39,7 +39,7 @@ module Sc2
|
|
39
39
|
# @param units [Array<Integer>,Integer,Api::Unit] can be an Api::Unit, array of Api::Unit#tag or single tag
|
40
40
|
# @param ability_id [Integer]
|
41
41
|
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
42
|
-
# @param queue_command [Boolean]
|
42
|
+
# @param queue_command [Boolean] shift+command
|
43
43
|
def action(units:, ability_id:, target: nil, queue_command: false)
|
44
44
|
unit_tags = unit_tags_from_source(units)
|
45
45
|
|
@@ -79,6 +79,7 @@ module Sc2
|
|
79
79
|
# Warps in unit type at target (location or pylon) with optional source units (warp gates)
|
80
80
|
# When not specifying the specific warp gate(s), all warpgates will be used
|
81
81
|
# @param unit_type_id [Integer] Api::UnitTypeId the unit type which will do the creation
|
82
|
+
# @param queue_command [Boolean] shift+command
|
82
83
|
# @param target [Api::Point2D, Integer] is a unit tag or a Api::Point2D
|
83
84
|
def warp(unit_type_id:, target:, queue_command:, units: nil)
|
84
85
|
warp_ability = Api::TechTree.unit_type_creation_abilities(
|
@@ -128,7 +129,7 @@ module Sc2
|
|
128
129
|
# @param ability_id [Api::AbilityId]
|
129
130
|
# @param target_screen_coord [Api::Point2I]
|
130
131
|
# @param target_minimap_coord [Api::Point2I]
|
131
|
-
# @param queue_command [Boolean]
|
132
|
+
# @param queue_command [Boolean] shift+command
|
132
133
|
# @return [void]
|
133
134
|
def action_spatial_unit_command(ability_id:, target_screen_coord: nil, target_minimap_coord: nil, queue_command: false)
|
134
135
|
queue_action Api::Action.new(
|
data/lib/sc2ai/player/debug.rb
CHANGED
@@ -37,6 +37,52 @@ module Sc2
|
|
37
37
|
)
|
38
38
|
end
|
39
39
|
|
40
|
+
# Prints text on screen from top and left
|
41
|
+
# @param text [String] will respect newlines
|
42
|
+
# @param left_percent [Numeric] range 0..100. percent from left of screen
|
43
|
+
# @param top_percent [Numeric] range 0..100. percent from top of screen
|
44
|
+
# @param color [Api::Color] default white
|
45
|
+
# @param size [Size] of font, default 14px
|
46
|
+
# @return [void]
|
47
|
+
def debug_text_screen(text, left_percent: 1.0, top_percent: 1.0, color: nil, size: 14)
|
48
|
+
queue_debug_command Api::DebugCommand.new(
|
49
|
+
draw: Api::DebugDraw.new(
|
50
|
+
text: [
|
51
|
+
Api::DebugText.new(
|
52
|
+
text:,
|
53
|
+
virtual_pos: Api::Point.new(
|
54
|
+
x: left_percent.to_f / 100,
|
55
|
+
y: top_percent.to_f / 100
|
56
|
+
),
|
57
|
+
color:,
|
58
|
+
size:
|
59
|
+
)
|
60
|
+
]
|
61
|
+
)
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Prints text on screen at 3d world position
|
66
|
+
# @param text [String] will respect newlines
|
67
|
+
# @param point [Api::Point] point in the world, i.e. unit.pos
|
68
|
+
# @param color [Api::Color] default white
|
69
|
+
# @param size [Size] of font, default 14px
|
70
|
+
# @return [void]
|
71
|
+
def debug_text_world(text, point:, color: nil, size: 14)
|
72
|
+
queue_debug_command Api::DebugCommand.new(
|
73
|
+
draw: Api::DebugDraw.new(
|
74
|
+
text: [
|
75
|
+
Api::DebugText.new(
|
76
|
+
text:,
|
77
|
+
world_pos: point,
|
78
|
+
color:,
|
79
|
+
size:
|
80
|
+
)
|
81
|
+
]
|
82
|
+
)
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
40
86
|
# Draws a line between two Api::Point's for color
|
41
87
|
# @param p0 [Api::Point] the first point
|
42
88
|
# @param p1 [Api::Point] the second point
|
@@ -63,8 +109,8 @@ module Sc2
|
|
63
109
|
# # Draws a box on structure placement grid
|
64
110
|
# debug_draw_box(point: unit.pos, radius: unit.footprint_radius)
|
65
111
|
#
|
66
|
-
#
|
67
|
-
#
|
112
|
+
# @note Api::Color RGB is broken for this command. Will use min(r,b)
|
113
|
+
# @note Z index is elevated 0.02 so the line is visible and doesn't clip through terrain
|
68
114
|
# @param point [Api::Point]
|
69
115
|
# @param radius [Float] default one tile wide, 1.0
|
70
116
|
# @param color [Api::Color] default white. min(r,b) is used for both r&b
|
@@ -74,8 +120,8 @@ module Sc2
|
|
74
120
|
draw: Api::DebugDraw.new(
|
75
121
|
boxes: [
|
76
122
|
Api::DebugBox.new(
|
77
|
-
min: Api::Point.new(x: point.x - radius, y: point.y - radius, z: point.z + 0.
|
78
|
-
max: Api::Point.new(x: point.x + radius, y: point.y + radius, z: point.z + (radius * 2) + 0.
|
123
|
+
min: Api::Point.new(x: point.x - radius, y: point.y - radius, z: point.z + 0.02),
|
124
|
+
max: Api::Point.new(x: point.x + radius, y: point.y + radius, z: point.z + (radius * 2) + 0.02),
|
79
125
|
color:
|
80
126
|
)
|
81
127
|
]
|
@@ -7,7 +7,7 @@ module Sc2
|
|
7
7
|
class Player
|
8
8
|
# Holds map and geography helper functions
|
9
9
|
class Geometry
|
10
|
-
# @!attribute
|
10
|
+
# @!attribute bot
|
11
11
|
# @return [Sc2::Player] player with active connection
|
12
12
|
attr_accessor :bot
|
13
13
|
|
@@ -72,6 +72,7 @@ module Sc2
|
|
72
72
|
# Each value in [row][column] holds a boolean value represented as an integer
|
73
73
|
# It does not say whether a position is occupied by another building.
|
74
74
|
# One pixel covers one whole block. Rounds fractionated positions down.
|
75
|
+
# @return [Numo::Bit]
|
75
76
|
def parsed_placement_grid
|
76
77
|
if @parsed_placement_grid.nil?
|
77
78
|
image_data = bot.game_info.start_raw.placement_grid
|
@@ -83,6 +84,7 @@ module Sc2
|
|
83
84
|
end
|
84
85
|
|
85
86
|
# Returns a grid where ony the expo locations are marked
|
87
|
+
# @return [Numo::Bit]
|
86
88
|
def expo_placement_grid
|
87
89
|
if @expo_placement_grid.nil?
|
88
90
|
@expo_placement_grid = Numo::Bit.zeros(map_height, map_width)
|
@@ -97,6 +99,7 @@ module Sc2
|
|
97
99
|
end
|
98
100
|
|
99
101
|
# Returns a grid where powered locations are marked true
|
102
|
+
# @return [Numo::Bit]
|
100
103
|
def parsed_power_grid
|
101
104
|
# Cache for based on power unit tags
|
102
105
|
cache_key = bot.power_sources.map(&:tag).sort.hash
|
@@ -399,12 +402,25 @@ module Sc2
|
|
399
402
|
output_grid
|
400
403
|
end
|
401
404
|
|
405
|
+
# Returns own 2d start position as set by initial camera
|
406
|
+
# This differs from position of first base structure
|
407
|
+
# @return [Api::Point2D]
|
408
|
+
def start_position
|
409
|
+
@start_position ||= bot.observation.raw_data.player.camera
|
410
|
+
end
|
411
|
+
|
412
|
+
# Returns the enemy 2d start position
|
413
|
+
# @return [Api::Point2D]
|
414
|
+
def enemy_start_position
|
415
|
+
bot.game_info.start_raw.start_locations.first
|
416
|
+
end
|
417
|
+
|
402
418
|
# Gets expos and surrounding minerals
|
403
419
|
# The index is a build location for an expo and the value is a UnitGroup, which has minerals and geysers
|
404
420
|
# @example
|
405
|
-
# random_expo = expansions.keys.sample #=> Point2D
|
421
|
+
# random_expo = geo.expansions.keys.sample #=> Point2D
|
406
422
|
# expo_resources = geo.expansions[random_expo] #=> UnitGroup
|
407
|
-
# alive_minerals = expo_resources.minerals
|
423
|
+
# alive_minerals = expo_resources.minerals & neutral.minerals
|
408
424
|
# geysers = expo_resources.geysers
|
409
425
|
# @return [Hash<Api::Point2D, UnitGroup>] Location => UnitGroup of resources (minerals+geysers)
|
410
426
|
def expansions
|
@@ -503,24 +519,77 @@ module Sc2
|
|
503
519
|
end
|
504
520
|
|
505
521
|
# Returns a slice of #expansions where a base hasn't been built yet
|
522
|
+
# The has index is a build position and the value is a UnitGroup of resources for the base
|
506
523
|
# @example
|
507
524
|
# # Lets find the nearest unoccupied expo
|
508
525
|
# expo_pos = expansions_unoccupied.keys.min { |p2d| p2d.distance_to(structures.hq.first) }
|
509
526
|
# # What minerals/geysers does it have?
|
510
527
|
# puts expansions_unoccupied[expo_pos].minerals # or expansions[expo_pos]... => UnitGroup
|
511
528
|
# puts expansions_unoccupied[expo_pos].geysers # or expansions[expo_pos]... => UnitGroup
|
512
|
-
# @return [Hash<Api::Point2D
|
529
|
+
# @return [Hash<Api::Point2D, UnitGroup>] Location => UnitGroup of resources (minerals+geysers)
|
513
530
|
def expansions_unoccupied
|
514
531
|
taken_bases = bot.structures.hq.map { |hq| hq.pos.to_p2d } + bot.enemy.structures.hq.map { |hq| hq.pos.to_p2d }
|
515
532
|
remaining_points = expansion_points - taken_bases
|
516
533
|
expansions.slice(*remaining_points)
|
517
534
|
end
|
518
535
|
|
536
|
+
# Gets minerals for a base or base position
|
537
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
538
|
+
# @return [Sc2::UnitGroup] UnitGroup of minerals for the base
|
539
|
+
def minerals_for_base(base)
|
540
|
+
# resources_for_base contains what we need, but slice neutral.minerals,
|
541
|
+
# so that only active patches remain
|
542
|
+
bot.neutral.minerals.slice(*resources_for_base(base).minerals.tags)
|
543
|
+
end
|
544
|
+
|
545
|
+
# Gets geysers for a base or base position
|
546
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
547
|
+
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
548
|
+
def geysers_for_base(base)
|
549
|
+
resources_for_base(base).geysers
|
550
|
+
end
|
551
|
+
|
552
|
+
# @private
|
553
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
554
|
+
# @return [Sc2::UnitGroup] UnitGroup of resources (minerals+geysers)
|
555
|
+
private def resources_for_base(base)
|
556
|
+
pos = base.is_a?(Api::Unit) ? base.pos : base
|
557
|
+
|
558
|
+
# If we have a base setup for this exact position, use it
|
559
|
+
if expansions.has_key?(pos)
|
560
|
+
return expansions[pos]
|
561
|
+
end
|
562
|
+
|
563
|
+
# Tolerance for misplaced base: Find the nearest base to this position
|
564
|
+
pos = expansion_points.min_by { |p| p.distance_to(pos) }
|
565
|
+
|
566
|
+
expansions[pos]
|
567
|
+
end
|
568
|
+
|
569
|
+
# Gets gasses for a base or base position
|
570
|
+
# @param base [Api::Unit, Sc2::Position] base Unit or Position
|
571
|
+
# @return [Sc2::UnitGroup] UnitGroup of geysers for the base
|
572
|
+
def gas_for_base(base)
|
573
|
+
# No gas structures at all yet, return nothing
|
574
|
+
return UnitGroup.new if bot.structures.gas.size.zero?
|
575
|
+
|
576
|
+
geysers = geysers_for_base(base)
|
577
|
+
|
578
|
+
# Mineral-only base, return nothing
|
579
|
+
return UnitGroup.new if geysers.size == 0
|
580
|
+
|
581
|
+
# Loop and collect gasses places exactly on-top of geysers
|
582
|
+
bot.structures.gas.select do |gas|
|
583
|
+
geysers.any? { |geyser| geyser.pos.to_p2d.eql?(gas.pos.to_p2d) }
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
519
587
|
# Gets buildable point grid for squares of size, i.e. 3 = 3x3 placements
|
520
588
|
# Uses pathing grid internally, to ignore taken positions
|
521
589
|
# Does not query the api and is generally fast.
|
522
590
|
# @param length [Integer] length of the building, 2 for depot/pylon, 3 for rax/gate
|
523
|
-
# @param on_creep [Boolean] whether this build
|
591
|
+
# @param on_creep [Boolean] whether this build location should be on creep
|
592
|
+
# @return [Array<Array<(Float, Float)>>] Array of [x,y] tuples
|
524
593
|
def build_coordinates(length:, on_creep: false, in_power: false)
|
525
594
|
length = 1 if length < 1
|
526
595
|
@_build_coordinates ||= {}
|
data/lib/sc2ai/player/units.rb
CHANGED
@@ -47,10 +47,10 @@ module Sc2
|
|
47
47
|
|
48
48
|
# Event-driven unit groups ---
|
49
49
|
|
50
|
+
# @!attribute event_units_created
|
50
51
|
# Units created since last frame (visible only, units not structures)
|
51
52
|
# Read this on_step. Alternative to callback on_unit_created
|
52
|
-
#
|
53
|
-
# @!attribute event_units_created
|
53
|
+
# @note Morphed units should watch #event_units_type_changed
|
54
54
|
# @return [Sc2::UnitGroup] group of created units
|
55
55
|
attr_accessor :event_units_created
|
56
56
|
|
data/lib/sc2ai/player.rb
CHANGED
@@ -97,7 +97,7 @@ module Sc2
|
|
97
97
|
@difficulty = difficulty
|
98
98
|
@ai_build = ai_build
|
99
99
|
@realtime = false
|
100
|
-
@step_count =
|
100
|
+
@step_count = 2
|
101
101
|
|
102
102
|
@enable_feature_layer = false
|
103
103
|
@interface_options = {}
|
@@ -201,7 +201,7 @@ module Sc2
|
|
201
201
|
|
202
202
|
# Override to customize initialization
|
203
203
|
# Alias of before_join
|
204
|
-
# You can enable_feature_layer=true, set
|
204
|
+
# You can enable_feature_layer=true, set step_count, define
|
205
205
|
# @example
|
206
206
|
# def configure
|
207
207
|
# step_count = 4 # Update less frequently
|
@@ -490,6 +490,8 @@ module Sc2
|
|
490
490
|
def started
|
491
491
|
# Calculate expansions
|
492
492
|
geo.expansions
|
493
|
+
# Set our start position base on camera
|
494
|
+
geo.start_position
|
493
495
|
end
|
494
496
|
|
495
497
|
# Moves emulation ahead and calls back #on_step
|
@@ -498,9 +500,7 @@ module Sc2
|
|
498
500
|
# Sc2.logger.debug "#{self.class} step_forward"
|
499
501
|
|
500
502
|
unless @realtime
|
501
|
-
|
502
|
-
num_steps = 1
|
503
|
-
@api.step(num_steps)
|
503
|
+
@api.step(@step_count)
|
504
504
|
end
|
505
505
|
|
506
506
|
refresh_state
|
@@ -610,9 +610,9 @@ module Sc2
|
|
610
610
|
# ##TODO: perfect loop implementation
|
611
611
|
# observation has an optional param game_loop and will only return once that step is reached (blocking).
|
612
612
|
# without it, it returns things as they are.
|
613
|
-
# broadly, i think this is what it should be doing, with
|
613
|
+
# broadly, i think this is what it should be doing, with step_count being minimum of 1, so no zero-steps occur.
|
614
614
|
# @example
|
615
|
-
# desired_game_loop = current_game_loop +
|
615
|
+
# desired_game_loop = current_game_loop + step_count
|
616
616
|
# response = client.observation(game_loop: desired_game_loop)
|
617
617
|
#
|
618
618
|
# if response.game_loop > desired_game_loop {
|
@@ -1,6 +1,15 @@
|
|
1
1
|
module Api
|
2
2
|
# Adds additional functionality to message object Api::Point
|
3
3
|
module PointExtension
|
4
|
+
# @private
|
5
|
+
def hash
|
6
|
+
[x, y, z].hash
|
7
|
+
end
|
8
|
+
|
9
|
+
def eql?(other)
|
10
|
+
self.class == other.class && hash == other.hash
|
11
|
+
end
|
12
|
+
|
4
13
|
# Creates a Point2D using x and y
|
5
14
|
# @return [Api::Point2D]
|
6
15
|
def to_p2d
|
@@ -13,6 +22,9 @@ module Api
|
|
13
22
|
# @example
|
14
23
|
# Api::Point[1,2,3] # Where x is 1.0, y is 2.0 and z is 3.0
|
15
24
|
# @return [Api::Point]
|
25
|
+
# @param [Float] x
|
26
|
+
# @param [Float] y
|
27
|
+
# @param [Float] z
|
16
28
|
def [](x, y, z)
|
17
29
|
Api::Point.new(x: x, y: y, z: z)
|
18
30
|
end
|
@@ -20,4 +32,4 @@ module Api
|
|
20
32
|
end
|
21
33
|
end
|
22
34
|
Api::Point.include Api::PointExtension
|
23
|
-
Api::Point.
|
35
|
+
Api::Point.extend Api::PointExtension::ClassMethods
|
@@ -179,6 +179,26 @@ module Api
|
|
179
179
|
action(ability_id: Api::AbilityId::SMART, target:, queue_command:)
|
180
180
|
end
|
181
181
|
|
182
|
+
# Shorthand for performing action MOVE
|
183
|
+
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
184
|
+
# @param queue_command [Boolean] shift+command
|
185
|
+
def move(target:, queue_command: false)
|
186
|
+
action(ability_id: Api::AbilityId::MOVE, target:, queue_command:)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Shorthand for performing action STOP
|
190
|
+
# @param queue_command [Boolean] shift+command
|
191
|
+
def stop(queue_command: false)
|
192
|
+
action(ability_id: Api::AbilityId::STOP, queue_command:)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Shorthand for performing action HOLDPOSITION
|
196
|
+
# @param queue_command [Boolean] shift+command
|
197
|
+
def hold(queue_command: false)
|
198
|
+
action(ability_id: Api::AbilityId::HOLDPOSITION, queue_command:)
|
199
|
+
end
|
200
|
+
alias_method :hold_position, :hold
|
201
|
+
|
182
202
|
# Shorthand for performing action ATTACK
|
183
203
|
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
184
204
|
# @param queue_command [Boolean] shift+command
|
@@ -224,6 +244,7 @@ module Api
|
|
224
244
|
# Draws a placement outline
|
225
245
|
# @param color [Api::Color] optional api color, default white
|
226
246
|
# @return [void]
|
247
|
+
# noinspection RubyArgCount
|
227
248
|
def debug_draw_placement(color = nil)
|
228
249
|
# Slightly elevate the Z position so that the line doesn't clip into the terrain at same Z level
|
229
250
|
z_elevated = pos.z + 0.01
|
@@ -432,6 +453,12 @@ module Api
|
|
432
453
|
build_progress == 1.0 # standard:disable Lint/FloatComparison
|
433
454
|
end
|
434
455
|
|
456
|
+
# Returns true if build progress is < 100%
|
457
|
+
# @return [Boolean]
|
458
|
+
def in_progress?
|
459
|
+
!is_completed?
|
460
|
+
end
|
461
|
+
|
435
462
|
# Convenience functions ---
|
436
463
|
|
437
464
|
# TERRAN Convenience functions ---
|
@@ -444,7 +471,7 @@ module Api
|
|
444
471
|
|
445
472
|
# Returns whether the structure has a reactor add-on
|
446
473
|
# @return [Boolean] if the unit has a reactor attached
|
447
|
-
def has_reactor
|
474
|
+
def has_reactor?
|
448
475
|
Sc2::UnitGroup::TYPE_REACTOR.include?(add_on&.unit_type)
|
449
476
|
end
|
450
477
|
|
@@ -455,26 +482,46 @@ module Api
|
|
455
482
|
# # Get the actual tech-lab with #add_on
|
456
483
|
# sp.add_on.research ...
|
457
484
|
# @return [Boolean] if the unit has a tech lab attached
|
458
|
-
def has_tech_lab
|
485
|
+
def has_tech_lab?
|
459
486
|
Sc2::UnitGroup::TYPE_TECHLAB.include?(add_on&.unit_type)
|
460
487
|
end
|
461
488
|
|
462
489
|
# For Terran builds a tech lab add-on on the current structure
|
463
490
|
# @return [void]
|
464
|
-
def build_reactor
|
465
|
-
|
491
|
+
def build_reactor(queue_command: false)
|
492
|
+
unit_type_id = case unit_type
|
493
|
+
when Api::UnitTypeId::BARRACKS, Api::UnitTypeId::BARRACKSFLYING
|
494
|
+
Api::UnitTypeId::BARRACKSREACTOR
|
495
|
+
when Api::UnitTypeId::FACTORY, Api::UnitTypeId::FACTORYFLYING
|
496
|
+
Api::UnitTypeId::FACTORYREACTOR
|
497
|
+
when Api::UnitTypeId::STARPORT, Api::UnitTypeId::STARPORTFLYING
|
498
|
+
Api::UnitTypeId::STARPORTREACTOR
|
499
|
+
end
|
500
|
+
build(unit_type_id: unit_type_id, queue_command:)
|
466
501
|
end
|
467
502
|
|
468
503
|
# For Terran builds a tech lab add-on on the current structure
|
469
504
|
# @return [void]
|
470
|
-
def build_tech_lab
|
471
|
-
|
505
|
+
def build_tech_lab(queue_command: false)
|
506
|
+
unit_type_id = case unit_type
|
507
|
+
when Api::UnitTypeId::BARRACKS, Api::UnitTypeId::BARRACKSFLYING
|
508
|
+
Api::UnitTypeId::BARRACKSTECHLAB
|
509
|
+
when Api::UnitTypeId::FACTORY, Api::UnitTypeId::FACTORYFLYING
|
510
|
+
Api::UnitTypeId::FACTORYTECHLAB
|
511
|
+
when Api::UnitTypeId::STARPORT, Api::UnitTypeId::STARPORTFLYING
|
512
|
+
Api::UnitTypeId::STARPORTTECHLAB
|
513
|
+
end
|
514
|
+
build(unit_type_id: unit_type_id, queue_command:)
|
472
515
|
end
|
473
516
|
|
517
|
+
# GENERAL Convenience functions ---
|
518
|
+
|
519
|
+
# ...
|
520
|
+
|
474
521
|
private
|
475
522
|
|
476
523
|
# @private
|
477
|
-
# Reduces
|
524
|
+
# Reduces repetition in the is_*action*?(target:) methods
|
478
525
|
def is_performing_ability_on_target?(abilities, target: nil)
|
479
526
|
# Exit if not actioning the ability
|
480
527
|
return false unless is_performing_ability?(abilities)
|
@@ -46,7 +46,7 @@ module Sc2
|
|
46
46
|
# @param queue_command [Boolean] shift+command
|
47
47
|
def warp(unit_type_id:, target: nil, queue_command: false)
|
48
48
|
return if size.zero?
|
49
|
-
bot&.warp(units: self, unit_type_id:, target
|
49
|
+
bot&.warp(units: self, unit_type_id:, target:, queue_command:)
|
50
50
|
end
|
51
51
|
|
52
52
|
# Shorthand for performing action SMART (right-click)
|
@@ -56,11 +56,31 @@ module Sc2
|
|
56
56
|
action(ability_id: Api::AbilityId::SMART, target:, queue_command:)
|
57
57
|
end
|
58
58
|
|
59
|
+
# Shorthand for performing action MOVE
|
60
|
+
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
61
|
+
# @param queue_command [Boolean] shift+command
|
62
|
+
def move(target:, queue_command: false)
|
63
|
+
action(ability_id: Api::AbilityId::MOVE, target:, queue_command:)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Shorthand for performing action STOP
|
67
|
+
# @param queue_command [Boolean] shift+command
|
68
|
+
def stop(queue_command: false)
|
69
|
+
action(ability_id: Api::AbilityId::STOP, queue_command:)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Shorthand for performing action HOLDPOSITION
|
73
|
+
# @param queue_command [Boolean] shift+command
|
74
|
+
def hold(queue_command: false)
|
75
|
+
action(ability_id: Api::AbilityId::HOLDPOSITION, queue_command:)
|
76
|
+
end
|
77
|
+
alias_method :hold_position, :hold
|
78
|
+
|
59
79
|
# Shorthand for performing action ATTACK
|
60
80
|
# @param target [Api::Unit, Integer, Api::Point2D] is a unit, unit tag or a Api::Point2D
|
61
81
|
# @param queue_command [Boolean] shift+command
|
62
82
|
def attack(target:, queue_command: false)
|
63
|
-
action(ability_id: Api::AbilityId::ATTACK, target
|
83
|
+
action(ability_id: Api::AbilityId::ATTACK, target:, queue_command:)
|
64
84
|
end
|
65
85
|
|
66
86
|
# Issues repair command on target
|
data/lib/sc2ai/unit_group.rb
CHANGED
@@ -89,27 +89,32 @@ module Sc2
|
|
89
89
|
@units[tags.at(index)]
|
90
90
|
end
|
91
91
|
|
92
|
+
# Meta documentation methods
|
93
|
+
# @!method first
|
94
|
+
# @return [Api::Unit]
|
95
|
+
# @!method last
|
96
|
+
# @return [Api::Unit]
|
97
|
+
|
92
98
|
# Calls the given block with each Api::Unit value
|
93
99
|
# @example
|
94
|
-
# unit_group.each {|unit| puts unit.tag } #=> 1234 ...
|
95
|
-
#
|
96
100
|
# unit_group.each do |unit|
|
97
101
|
# puts unit.tag #=> 1234 ...
|
98
102
|
# end
|
99
|
-
|
100
|
-
|
103
|
+
# @yieldparam unit [Api::Unit]
|
104
|
+
def each(&)
|
105
|
+
@units.each_value(&)
|
101
106
|
end
|
102
107
|
|
103
108
|
# Calls the given block with each key-value pair
|
104
109
|
# @return [self] a new unit group with items merged
|
105
110
|
# @example
|
106
|
-
# unit_group.
|
107
|
-
#
|
108
|
-
# unit_group.each do |tag, unit|
|
111
|
+
# unit_group.each_with_tag do |tag, unit|
|
109
112
|
# puts "#{tag}: #{unit}"} #=> "12345: #<Api::Unit ...>"
|
110
113
|
# end
|
111
|
-
|
112
|
-
|
114
|
+
# @yieldparam tag [Integer]
|
115
|
+
# @yieldparam unit [Api::Unit]
|
116
|
+
def each_with_tag(&)
|
117
|
+
@units.each(&)
|
113
118
|
self
|
114
119
|
end
|
115
120
|
|
@@ -229,12 +234,19 @@ module Sc2
|
|
229
234
|
end
|
230
235
|
|
231
236
|
# Selects a single random Unit without a parameter or an array of Units with a param, i.e. self.random(2)
|
232
|
-
# @return []
|
237
|
+
# @return [Api::Unit]
|
233
238
|
def sample(...)
|
234
239
|
@units.values.sample(...)
|
235
240
|
end
|
236
241
|
alias_method :random, :sample
|
237
242
|
|
243
|
+
# Returns the first Unit for which the block returns a truthy value
|
244
|
+
# @return [Api::Unit]
|
245
|
+
def detect(...)
|
246
|
+
@units.values.find(...)
|
247
|
+
end
|
248
|
+
alias_method :find, :detect
|
249
|
+
|
238
250
|
# def select_or(*procs)
|
239
251
|
# result = UnitGroup.new
|
240
252
|
# procs.each do |proc|
|
data/lib/sc2ai/version.rb
CHANGED
@@ -4,14 +4,13 @@ class <%= @classname %> < Sc2::Player::Bot
|
|
4
4
|
|
5
5
|
def configure
|
6
6
|
@realtime = false # Step-mode vs Bot, Realtime vs Humans
|
7
|
-
@
|
7
|
+
@step_count = 2 # 2s/22.4 = ~89.29ms step cycle time allowance
|
8
8
|
@enable_feature_layer = false; # Enables ui_ and spatial_ actions. Advanced, and has performance cost.
|
9
9
|
end
|
10
10
|
|
11
11
|
def on_step
|
12
12
|
if game_loop == 0
|
13
|
-
|
14
|
-
units.workers.attack(target: enemy_start_pos)
|
13
|
+
units.workers.attack(target: geo.enemy_start_position)
|
15
14
|
end
|
16
15
|
|
17
16
|
# If your attack fails, "good game" and exit
|