sc2ai 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -2012,11 +2012,63 @@ module Api
2012
2012
  MINERALFIELD450 = 1996
2013
2013
  MINERALFIELDOPAQUE = 1997
2014
2014
  MINERALFIELDOPAQUE900 = 1998
2015
- COLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN = 1999
2016
- COLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN = 2000
2017
- COLLAPSIBLEROCKTOWERPUSHUNITRAMPLEFTGREEN = 2001
2018
- COLLAPSIBLEROCKTOWERPUSHUNITRAMPRIGHTGREEN = 2002
2019
- COLLAPSIBLEROCKTOWERRAMPLEFTGREEN = 2003
2020
- COLLAPSIBLEROCKTOWERRAMPRIGHTGREEN = 2004
2015
+ MECHAZERGLINGACGLUESCREENDUMMY_2 = 1999
2016
+ MECHABANELINGACGLUESCREENDUMMY_2 = 2000
2017
+ MECHAHYDRALISKACGLUESCREENDUMMY_2 = 2001
2018
+ MECHAINFESTORACGLUESCREENDUMMY_2 = 2002
2019
+ MECHACORRUPTORACGLUESCREENDUMMY_2 = 2003
2020
+ MECHAULTRALISKACGLUESCREENDUMMY_2 = 2004
2021
+ MECHAOVERSEERACGLUESCREENDUMMY_2 = 2005
2022
+ MECHALURKERACGLUESCREENDUMMY_2 = 2006
2023
+ MECHABATTLECARRIERLORDACGLUESCREENDUMMY_2 = 2007
2024
+ MECHASPINECRAWLERACGLUESCREENDUMMY_2 = 2008
2025
+ MECHASPORECRAWLERACGLUESCREENDUMMY_2 = 2009
2026
+ TROOPERMENGSKACGLUESCREENDUMMY_2 = 2010
2027
+ MEDIVACMENGSKACGLUESCREENDUMMY_2 = 2011
2028
+ BLIMPMENGSKACGLUESCREENDUMMY_2 = 2012
2029
+ MARAUDERMENGSKACGLUESCREENDUMMY_2 = 2013
2030
+ GHOSTMENGSKACGLUESCREENDUMMY_2 = 2014
2031
+ SIEGETANKMENGSKACGLUESCREENDUMMY_2 = 2015
2032
+ THORMENGSKACGLUESCREENDUMMY_2 = 2016
2033
+ VIKINGMENGSKACGLUESCREENDUMMY_2 = 2017
2034
+ BATTLECRUISERMENGSKACGLUESCREENDUMMY_2 = 2018
2035
+ BUNKERDEPOTMENGSKACGLUESCREENDUMMY_2 = 2019
2036
+ MISSILETURRETMENGSKACGLUESCREENDUMMY_2 = 2020
2037
+ ARTILLERYMENGSKACGLUESCREENDUMMY_2 = 2021
2038
+ LOADOUTSPRAY1_2 = 2022
2039
+ LOADOUTSPRAY2_2 = 2023
2040
+ LOADOUTSPRAY3_2 = 2024
2041
+ LOADOUTSPRAY4_2 = 2025
2042
+ LOADOUTSPRAY5_2 = 2026
2043
+ LOADOUTSPRAY6_2 = 2027
2044
+ LOADOUTSPRAY7_2 = 2028
2045
+ LOADOUTSPRAY8_2 = 2029
2046
+ LOADOUTSPRAY9_2 = 2030
2047
+ LOADOUTSPRAY10_2 = 2031
2048
+ LOADOUTSPRAY11_2 = 2032
2049
+ LOADOUTSPRAY12_2 = 2033
2050
+ LOADOUTSPRAY13_2 = 2034
2051
+ LOADOUTSPRAY14_2 = 2035
2052
+ COLLAPSIBLEROCKTOWERDEBRISRAMPLEFTGREEN = 2036
2053
+ COLLAPSIBLEROCKTOWERDEBRISRAMPRIGHTGREEN = 2037
2054
+ COLLAPSIBLEROCKTOWERPUSHUNITRAMPLEFTGREEN = 2038
2055
+ COLLAPSIBLEROCKTOWERPUSHUNITRAMPRIGHTGREEN = 2039
2056
+ COLLAPSIBLEROCKTOWERRAMPLEFTGREEN = 2040
2057
+ COLLAPSIBLEROCKTOWERRAMPRIGHTGREEN = 2041
2058
+ DUMMYUNIT000 = 2042
2059
+ DUMMYUNIT001 = 2043
2060
+ DUMMYUNIT002 = 2044
2061
+ DUMMYUNIT003 = 2045
2062
+ DUMMYUNIT004 = 2046
2063
+ DUMMYUNIT005 = 2047
2064
+ DUMMYUNIT006 = 2048
2065
+ DUMMYUNIT007 = 2049
2066
+ DUMMYUNIT008 = 2050
2067
+ DUMMYUNIT009 = 2051
2068
+ DUMMYUNIT010 = 2052
2069
+ DUMMYUNIT011 = 2053
2070
+ DUMMYUNIT012 = 2054
2071
+ DUMMYUNIT013 = 2055
2072
+ DUMMYUNIT014 = 2056
2021
2073
  end
2022
2074
  end
@@ -306,5 +306,7 @@ module Api
306
306
  PSIONICAMPLIFIERS = 300
307
307
  SECRETEDCOATING = 301
308
308
  ENHANCEDSHOCKWAVES = 302
309
+ HURRICANETHRUSTERS = 303
310
+ INTERFERENCEMATRIX = 304
309
311
  end
310
312
  end
data/lib/sc2ai/cli/cli.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "thor"
2
+
2
3
  require "sc2ai/cli/new"
3
4
  require "sc2ai/cli/ladderzip"
4
5
 
@@ -20,36 +21,36 @@ module Sc2
20
21
  desc "setup410", "Downloads and install SC2 v4.10"
21
22
  # downloads and install SC2 v4.10
22
23
  def setup410
23
- puts " "
24
- puts "This script sets up SC2 at version 4.10, which we use competitively."
25
- puts "Press any key to continue..."
26
- puts " "
27
- $stdin.getch
28
-
29
- puts "You must accept the Blizzard® Starcraft® II AI and Machine Learning License at"
30
- puts "https://blzdistsc2-a.akamaihd.net/AI_AND_MACHINE_LEARNING_LICENSE.html"
31
- puts "It is PERMISSIVE and grants you freedoms over the standard EULA."
32
- puts "We do not record this action, but depend on software goverend by that license to continue."
24
+ say " "
25
+ say "This script sets up SC2 at version 4.10, which we use competitively."
26
+ say "Press any key to continue..."
27
+ ask ""
28
+
29
+ say "You must accept the Blizzard® Starcraft® II AI and Machine Learning License at"
30
+ say "https://blzdistsc2-a.akamaihd.net/AI_AND_MACHINE_LEARNING_LICENSE.html"
31
+ say "It is PERMISSIVE and grants you freedoms over the standard EULA."
32
+ say "We do not record this action, but depend on software goverend by that license to continue."
33
33
  puts 'If you accept, type "iagreetotheeula" (without quotes) and press ENTER to continue:'
34
34
 
35
35
  while $stdin.gets.chomp != "iagreetotheeula"
36
- puts ""
36
+ say ""
37
37
  puts 'Type "iagreetotheeula" (without quotes) and press ENTER to continue:'
38
38
  end
39
- puts ""
40
- puts ""
41
- puts "Great decision."
39
+ say ""
40
+ say ""
41
+ say "Great decision."
42
42
 
43
+ require "sc2ai"
43
44
  Async do
44
45
  Sc2.logger.level = :fatal
45
- puts "SC2 will launch a blank window, be unresponsive, but download about 100mb in the background."
46
- puts "Let it finish and close itself."
47
- puts "Press ENTER if you understand."
48
- $stdin.getch
46
+ say "SC2 will launch a blank window, be unresponsive, but download about 100mb in the background."
47
+ say "Let it finish and close itself."
48
+ say "Press ENTER if you understand."
49
+ ask ""
49
50
 
50
- puts ""
51
- puts ""
52
- puts "This will only take a minute..."
51
+ say ""
52
+ say ""
53
+ say "This will only take a minute..."
53
54
 
54
55
  Sc2.config.version = nil
55
56
  client = Sc2::ClientManager.obtain(0)
@@ -68,22 +69,23 @@ module Sc2
68
69
  base_build = "75689"
69
70
  path = Sc2::Paths.executable(base_build: base_build)
70
71
  if path.include?(base_build) && Pathname(path).exist?
71
- puts " "
72
- puts "Success. Downloaded complete."
73
- puts " "
72
+ say " "
73
+ say "Success. Download complete.", :green
74
+ say " "
74
75
  else
75
- puts "Error. Slightly worrying, but no fear."
76
- puts "To manually setup, grab the latest ladder maps and add them to your map folder."
77
- puts "Grab the latest maps from https://aiarena.net/wiki/maps/"
78
- puts "Detected map folder: #{Sc2::Paths.maps_dir}"
79
- puts "Then, download any recent replay from https://aiarena.net/ and double click to launch"
76
+ say "Error. Slightly worrying, but no fear.", :red
77
+ say "To manually setup, grab the latest ladder maps and add them to your map folder."
78
+ say "Grab the latest maps from https://aiarena.net/wiki/maps/"
79
+ say "Detected map folder: #{Sc2::Paths.maps_dir}"
80
+ say "Then, download any recent replay from https://aiarena.net/ and double click to launch"
80
81
  end
81
82
 
83
+ observer.api.quit
82
84
  observer.disconnect
83
- puts "Generating sc2ai.yml to always use 4.10..."
85
+ say "Generating sc2ai.yml to always use 4.10..."
84
86
  Sc2.config.config_file.write({"version" => "4.10"}.to_yaml.to_s)
85
- puts ""
86
- puts "Done. You're good to go."
87
+ say ""
88
+ say "Done. You're good to go.", :green
87
89
  ensure
88
90
  Sc2::ClientManager.stop(0)
89
91
  end
@@ -133,7 +135,7 @@ module Sc2
133
135
  option :RealTime, type: :boolean, default: false, desc: "Forces realtime flag"
134
136
  def laddermatch
135
137
  require "sc2ai"
136
-
138
+
137
139
  unless Sc2.ladder?
138
140
  raise Sc2::Error, "This command is only for competing on aiarena.net"
139
141
  end
@@ -18,11 +18,21 @@ module Sc2
18
18
  desc "Builds a ladder zip using docker (requires docker)"
19
19
 
20
20
  def docker_exists
21
- if Kernel.system("docker --version")
21
+ if Kernel.system("docker --version", out: File::NULL, err: File::NULL)
22
22
  say_status("docker", "found", :green)
23
23
  else
24
24
  say_status("docker", "not found", :red)
25
25
  say("Please install 'docker' and/or ensure it's in your PATH to continue.")
26
+ raise SystemExit
27
+ end
28
+
29
+ if Kernel.system("docker info", out: File::NULL, err: File::NULL)
30
+ say_status("docker engine", "found", :green)
31
+ else
32
+ say(" ")
33
+ say_status("docker engine", "offline", :red)
34
+ say("Please start docker engine. If you have Docker Desktop installed, open it before retrying.")
35
+ raise SystemExit
26
36
  end
27
37
  end
28
38
 
@@ -64,7 +74,8 @@ module Sc2
64
74
 
65
75
  def create_executable
66
76
  template("ladderzip/bin/ladder", "./.build/bin/ladder")
67
- create_link(".build/#{botname}", "./bin/ladder")
77
+ # This fails on Windows. creating by hand in #link_ladder function below.
78
+ # create_link("./.build/#{botname}", "./bin/ladder")
68
79
  end
69
80
 
70
81
  def start_container
@@ -86,9 +97,11 @@ module Sc2
86
97
  include_pattern.collect! { |pt| pt.delete_prefix("!") }
87
98
 
88
99
  files = Dir.glob("**/*")
100
+
89
101
  ignored_files = files
90
102
  .select { |f| ignore_pattern.any? { |p| File.fnmatch?(p, f) } }
91
- .reject! { |f| include_pattern.any? { |p| File.fnmatch?(p, f) } }
103
+ .reject { |f| include_pattern.any? { |p| File.fnmatch?(p, f) } }
104
+
92
105
  files -= ignored_files unless ignored_files.nil?
93
106
 
94
107
  files.each do |file|
@@ -103,6 +116,12 @@ module Sc2
103
116
  Kernel.system(cmd)
104
117
  end
105
118
 
119
+ def link_ladder
120
+ say_status("docker", "linking executable...", :cyan)
121
+ cmd = "docker compose -f #{@compose_file} exec --workdir /root/ruby-builder bot ln -s ./bin/ladder ./#{botname}"
122
+ Kernel.system(cmd)
123
+ end
124
+
106
125
  def install_gems
107
126
  say_status("docker", "bundle install...", :cyan)
108
127
  cmd = "docker compose -f #{@compose_file} exec --workdir /root/ruby-builder bot bundle install"
@@ -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
@@ -202,7 +205,7 @@ module Sc2
202
205
  # @param y [Float, Integer]
203
206
  # @return [Boolean] true if location is powered
204
207
  def powered?(x:, y:)
205
- parsed_creep[y.to_i, x.to_i] != 0
208
+ parsed_power_grid[y.to_i, x.to_i] != 0
206
209
  end
207
210
 
208
211
  # Returns whether a x/y block is pathable as per minimap
@@ -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