sc2ai 0.0.2 → 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/data/data.json +1 -1
- data/data/data_readable.json +238 -134
- data/data/stableid.json +2574 -404
- data/lib/sc2ai/api/ability_id.rb +319 -12
- data/lib/sc2ai/api/buff_id.rb +11 -1
- data/lib/sc2ai/api/tech_tree.rb +0 -1
- data/lib/sc2ai/api/tech_tree_data.rb +32 -28
- data/lib/sc2ai/api/unit_type_id.rb +58 -6
- data/lib/sc2ai/api/upgrade_id.rb +2 -0
- data/lib/sc2ai/cli/cli.rb +35 -33
- data/lib/sc2ai/cli/ladderzip.rb +22 -3
- data/lib/sc2ai/player/actions.rb +4 -3
- data/lib/sc2ai/player/debug.rb +50 -4
- data/lib/sc2ai/player/geometry.rb +75 -6
- 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 +44 -72
- data/sc2ai.gemspec +0 -80
@@ -2012,11 +2012,63 @@ module Api
|
|
2012
2012
|
MINERALFIELD450 = 1996
|
2013
2013
|
MINERALFIELDOPAQUE = 1997
|
2014
2014
|
MINERALFIELDOPAQUE900 = 1998
|
2015
|
-
|
2016
|
-
|
2017
|
-
|
2018
|
-
|
2019
|
-
|
2020
|
-
|
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
|
data/lib/sc2ai/api/upgrade_id.rb
CHANGED
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
36
|
+
say ""
|
37
37
|
puts 'Type "iagreetotheeula" (without quotes) and press ENTER to continue:'
|
38
38
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
say ""
|
40
|
+
say ""
|
41
|
+
say "Great decision."
|
42
42
|
|
43
|
+
require "sc2ai"
|
43
44
|
Async do
|
44
45
|
Sc2.logger.level = :fatal
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
72
|
+
say " "
|
73
|
+
say "Success. Download complete.", :green
|
74
|
+
say " "
|
74
75
|
else
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
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
|
data/lib/sc2ai/cli/ladderzip.rb
CHANGED
@@ -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
|
-
|
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
|
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"
|
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
|
@@ -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
|
-
|
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
|
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
|