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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f4d88f09b0eaa016360346fe4b150664feb3072792ded12302267a2cf64d2854
4
- data.tar.gz: 50f38a9008d26a62fbe9fae1f05076dee04544ea73b8e72e8f9447371501f54b
3
+ metadata.gz: fcc96e2f18e9cc95b0fd8b9427d3c76866551031b69811251b547eeb13284cba
4
+ data.tar.gz: 88c3dce17dba1e6b4378c306de8d754e1f33dcd092a9e44ac7477e50940ca911
5
5
  SHA512:
6
- metadata.gz: e3f980a5519d31a49cb890cb529d213b34819dd3a4888301ea7dfde1b2edf9fda7685fb62d28c4187809b5b9a646e1bed2e4af6b86f037c5207c8d515306d88a
7
- data.tar.gz: 579de21d38e0f596cdc7fc29a43d878caa4e72b4a9a995e22c26edc0b3bfda4c79b266703a8eaea0e60b513e88a6bcaa5de196d8e8d90e353344925666bb7ecb
6
+ metadata.gz: 39997d407d6fbf2ffc936c387f703d4b12d6aff1f016bc1905148a6590f156c4f39acee982074be28d33ea9451cbbf483f985c62a902693c65da450465d915e2
7
+ data.tar.gz: af9ed623bf2ee6ce905127fb89828a45c95456b4996e1aa4f6c3d477d31b383ed54d425de80abcd28f2df9cf08bfa127fbd36c89f822e226804b116bf20e73c1
@@ -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] Shift+Click, default: false
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] Shift+Click, default: false
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] Shift+Click, default: false
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(
@@ -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
- # Note: Api::Color RGB is broken for this command. Will use min(r,b)
67
- # Note: Z index is elevated 0.01 so the line is visible and doesn't clip through terrain
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.01),
78
- max: Api::Point.new(x: point.x + radius, y: point.y + radius, z: point.z + (radius * 2) + 0.01),
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 Holds the parent bot object
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 - neutral.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], UnitGroup] Location => UnitGroup of resources (minerals+geysers)
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 locatin should be on creep
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 ||= {}
@@ -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
- # Note: Morphed units should watch #event_units_type_changed
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 = 1
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 step_size, define
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
- # ##TODO: Numsteps as config
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 step_size being minimum of 1, so no zero-steps occur.
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 + step_size
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.include Api::PointExtension::ClassMethods
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
- build(unit_type_id: Api::UnitTypeId::REACTOR)
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
- build(unit_type_id: Api::UnitTypeId::TECHLAB)
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 repitition in the is_*action*?(target:) methods
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: target, queue_command:)
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: target, queue_command: queue_command)
83
+ action(ability_id: Api::AbilityId::ATTACK, target:, queue_command:)
64
84
  end
65
85
 
66
86
  # Issues repair command on target
@@ -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
- def each(&block)
100
- @units.each_value(&block)
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.each {|tag, unit| puts "#{tag}: #{unit}"} #=> "12345: #<Api::Unit ...>"
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
- def each_with_tag(&block)
112
- @units.each(&block)
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Sc2
4
4
  # gem version
5
- VERSION = "0.0.3"
5
+ VERSION = "0.0.4"
6
6
  end
@@ -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
- @step_size = 1 # Gives 22.4ms, typically compete at 2 (44.8ms) or 4 (179.2ms).
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
- enemy_start_pos = game_info.start_raw.start_locations.first
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