sc2ai 0.0.0.pre → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/data.json +1 -0
- data/data/data_readable.json +22842 -0
- data/data/sc2ai/protocol/common.proto +59 -0
- data/data/sc2ai/protocol/data.proto +120 -0
- data/data/sc2ai/protocol/debug.proto +127 -0
- data/data/sc2ai/protocol/error.proto +221 -0
- data/data/sc2ai/protocol/query.proto +55 -0
- data/data/sc2ai/protocol/raw.proto +202 -0
- data/data/sc2ai/protocol/sc2api.proto +718 -0
- data/data/sc2ai/protocol/score.proto +108 -0
- data/data/sc2ai/protocol/spatial.proto +115 -0
- data/data/sc2ai/protocol/ui.proto +145 -0
- data/data/setup/setup.SC2Map +0 -0
- data/data/setup/setup.SC2Replay +0 -0
- data/data/stableid.json +35730 -0
- data/data/versions.json +554 -0
- data/exe/sc2ai +35 -0
- data/lib/docker_build/Dockerfile.ruby +74 -0
- data/lib/docker_build/docker-compose-base-image.yml +10 -0
- data/lib/docker_build/docker-compose-ladderzip.yml +9 -0
- data/lib/sc2ai/api/ability_id.rb +1644 -0
- data/lib/sc2ai/api/buff_id.rb +306 -0
- data/lib/sc2ai/api/data.rb +101 -0
- data/lib/sc2ai/api/effect_id.rb +20 -0
- data/lib/sc2ai/api/tech_tree.rb +83 -0
- data/lib/sc2ai/api/tech_tree_data.rb +2338 -0
- data/lib/sc2ai/api/unit_type_id.rb +2022 -0
- data/lib/sc2ai/api/upgrade_id.rb +310 -0
- data/lib/sc2ai/cli/cli.rb +175 -0
- data/lib/sc2ai/cli/ladderzip.rb +154 -0
- data/lib/sc2ai/cli/new.rb +88 -0
- data/lib/sc2ai/configuration.rb +145 -0
- data/lib/sc2ai/connection/connection_listener.rb +30 -0
- data/lib/sc2ai/connection/requests.rb +417 -0
- data/lib/sc2ai/connection/status_listener.rb +15 -0
- data/lib/sc2ai/connection.rb +146 -0
- data/lib/sc2ai/local_play/client/configurable_options.rb +115 -0
- data/lib/sc2ai/local_play/client.rb +159 -0
- data/lib/sc2ai/local_play/client_manager.rb +70 -0
- data/lib/sc2ai/local_play/map_file.rb +48 -0
- data/lib/sc2ai/local_play/match.rb +184 -0
- data/lib/sc2ai/overrides/array.rb +14 -0
- data/lib/sc2ai/overrides/async/process/child.rb +31 -0
- data/lib/sc2ai/overrides/kernel.rb +33 -0
- data/lib/sc2ai/paths.rb +294 -0
- data/lib/sc2ai/player/actions.rb +386 -0
- data/lib/sc2ai/player/debug.rb +224 -0
- data/lib/sc2ai/player/game_state.rb +131 -0
- data/lib/sc2ai/player/geometry.rb +766 -0
- data/lib/sc2ai/player/previous_state.rb +49 -0
- data/lib/sc2ai/player/units.rb +337 -0
- data/lib/sc2ai/player.rb +661 -0
- data/lib/sc2ai/ports.rb +152 -0
- data/lib/sc2ai/protocol/_meta_documentation.rb +39 -0
- data/lib/sc2ai/protocol/common_pb.rb +43 -0
- data/lib/sc2ai/protocol/data_pb.rb +47 -0
- data/lib/sc2ai/protocol/debug_pb.rb +56 -0
- data/lib/sc2ai/protocol/error_pb.rb +36 -0
- data/lib/sc2ai/protocol/extensions/color.rb +20 -0
- data/lib/sc2ai/protocol/extensions/point.rb +23 -0
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +26 -0
- data/lib/sc2ai/protocol/extensions/position.rb +202 -0
- data/lib/sc2ai/protocol/extensions/power_source.rb +19 -0
- data/lib/sc2ai/protocol/extensions/unit.rb +489 -0
- data/lib/sc2ai/protocol/query_pb.rb +47 -0
- data/lib/sc2ai/protocol/raw_pb.rb +57 -0
- data/lib/sc2ai/protocol/sc2api_pb.rb +130 -0
- data/lib/sc2ai/protocol/score_pb.rb +40 -0
- data/lib/sc2ai/protocol/spatial_pb.rb +48 -0
- data/lib/sc2ai/protocol/ui_pb.rb +56 -0
- data/lib/sc2ai/unit_group/action_ext.rb +74 -0
- data/lib/sc2ai/unit_group/filter_ext.rb +379 -0
- data/lib/sc2ai/unit_group.rb +277 -0
- data/lib/sc2ai/version.rb +2 -1
- data/lib/sc2ai.rb +93 -2
- data/lib/templates/ladderzip/bin/ladder.tt +23 -0
- data/lib/templates/new/.ladderignore +20 -0
- data/lib/templates/new/Gemfile.tt +7 -0
- data/lib/templates/new/api/common.proto +59 -0
- data/lib/templates/new/api/data.proto +120 -0
- data/lib/templates/new/api/debug.proto +127 -0
- data/lib/templates/new/api/error.proto +221 -0
- data/lib/templates/new/api/query.proto +55 -0
- data/lib/templates/new/api/raw.proto +202 -0
- data/lib/templates/new/api/sc2api.proto +718 -0
- data/lib/templates/new/api/score.proto +108 -0
- data/lib/templates/new/api/spatial.proto +115 -0
- data/lib/templates/new/api/ui.proto +145 -0
- data/lib/templates/new/boot.rb.tt +6 -0
- data/lib/templates/new/my_bot.rb.tt +23 -0
- data/lib/templates/new/run_example_match.rb.tt +14 -0
- data/sc2ai.gemspec +80 -0
- metadata +344 -13
@@ -0,0 +1,379 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sc2ai/unit_group"
|
4
|
+
require "kdtree"
|
5
|
+
|
6
|
+
module Sc2
|
7
|
+
# Manage virtual control groups of units, similar to Hash or Array.
|
8
|
+
class UnitGroup
|
9
|
+
TYPE_WORKER = [Api::UnitTypeId::SCV, Api::UnitTypeId::MULE, Api::UnitTypeId::DRONE, Api::UnitTypeId::DRONEBURROWED, Api::UnitTypeId::PROBE].freeze
|
10
|
+
TYPE_GAS_STRUCTURE = [
|
11
|
+
Api::UnitTypeId::REFINERY,
|
12
|
+
Api::UnitTypeId::REFINERYRICH,
|
13
|
+
Api::UnitTypeId::ASSIMILATOR,
|
14
|
+
Api::UnitTypeId::ASSIMILATORRICH,
|
15
|
+
Api::UnitTypeId::EXTRACTOR,
|
16
|
+
Api::UnitTypeId::EXTRACTORRICH
|
17
|
+
].freeze
|
18
|
+
TYPE_MINERAL = [
|
19
|
+
Api::UnitTypeId::MINERALCRYSTAL,
|
20
|
+
Api::UnitTypeId::RICHMINERALFIELD,
|
21
|
+
Api::UnitTypeId::RICHMINERALFIELD750,
|
22
|
+
Api::UnitTypeId::MINERALFIELD,
|
23
|
+
Api::UnitTypeId::MINERALFIELD450,
|
24
|
+
Api::UnitTypeId::MINERALFIELD750,
|
25
|
+
Api::UnitTypeId::LABMINERALFIELD,
|
26
|
+
Api::UnitTypeId::LABMINERALFIELD750,
|
27
|
+
Api::UnitTypeId::PURIFIERRICHMINERALFIELD,
|
28
|
+
Api::UnitTypeId::PURIFIERRICHMINERALFIELD750,
|
29
|
+
Api::UnitTypeId::PURIFIERMINERALFIELD,
|
30
|
+
Api::UnitTypeId::PURIFIERMINERALFIELD750,
|
31
|
+
Api::UnitTypeId::BATTLESTATIONMINERALFIELD,
|
32
|
+
Api::UnitTypeId::BATTLESTATIONMINERALFIELD750,
|
33
|
+
Api::UnitTypeId::MINERALFIELDOPAQUE,
|
34
|
+
Api::UnitTypeId::MINERALFIELDOPAQUE900
|
35
|
+
].freeze
|
36
|
+
TYPE_GEYSER = [
|
37
|
+
Api::UnitTypeId::VESPENEGEYSER,
|
38
|
+
Api::UnitTypeId::SPACEPLATFORMGEYSER,
|
39
|
+
Api::UnitTypeId::RICHVESPENEGEYSER,
|
40
|
+
Api::UnitTypeId::PROTOSSVESPENEGEYSER,
|
41
|
+
Api::UnitTypeId::PURIFIERVESPENEGEYSER,
|
42
|
+
Api::UnitTypeId::SHAKURASVESPENEGEYSER
|
43
|
+
].freeze
|
44
|
+
TYPE_REJECT_DEBRIS = ((TYPE_MINERAL + TYPE_GEYSER) << Api::UnitTypeId::XELNAGATOWER).freeze
|
45
|
+
TYPE_TECHLAB = [
|
46
|
+
Api::UnitTypeId::TECHLAB,
|
47
|
+
Api::UnitTypeId::BARRACKSTECHLAB,
|
48
|
+
Api::UnitTypeId::FACTORYTECHLAB,
|
49
|
+
Api::UnitTypeId::STARPORTTECHLAB
|
50
|
+
].freeze
|
51
|
+
TYPE_REACTOR = [
|
52
|
+
Api::UnitTypeId::REACTOR,
|
53
|
+
Api::UnitTypeId::BARRACKSREACTOR,
|
54
|
+
Api::UnitTypeId::FACTORYREACTOR,
|
55
|
+
Api::UnitTypeId::STARPORTREACTOR
|
56
|
+
].freeze
|
57
|
+
TYPE_BASES = [
|
58
|
+
Api::UnitTypeId::COMMANDCENTER, Api::UnitTypeId::COMMANDCENTERFLYING,
|
59
|
+
Api::UnitTypeId::ORBITALCOMMAND, Api::UnitTypeId::ORBITALCOMMANDFLYING,
|
60
|
+
Api::UnitTypeId::PLANETARYFORTRESS,
|
61
|
+
Api::UnitTypeId::HATCHERY, Api::UnitTypeId::HIVE, Api::UnitTypeId::LAIR,
|
62
|
+
Api::UnitTypeId::NEXUS
|
63
|
+
].freeze
|
64
|
+
# Returns a new UnitGroup containing all units matching unit type id(s)
|
65
|
+
# Multiple values work as an "OR" filter
|
66
|
+
# @example
|
67
|
+
# # Single
|
68
|
+
# ug.select_type(Api::UnitTypeId::MARINE) #=> <UnitGroup: ...>
|
69
|
+
# # Multiple - select space-men
|
70
|
+
# ug.select_type([Api::UnitTypeId::MARINE, Api::UnitTypeId::REAPER]) #=> <UnitGroup: ...>
|
71
|
+
# @param unit_type_ids [Integer, Array<Integer>] one or an array of unit Api::UnitTypeId
|
72
|
+
# @return [UnitGroup]
|
73
|
+
def select_type(unit_type_ids)
|
74
|
+
cached("#{__method__}:#{unit_type_ids.hash}") do
|
75
|
+
if unit_type_ids.is_a? Array
|
76
|
+
select { |unit| unit_type_ids.include?(unit.unit_type) }
|
77
|
+
else
|
78
|
+
select { |unit| unit_type_ids == unit.unit_type }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns a new UnitGroup excluding all units matching unit type id(s)
|
84
|
+
# @example
|
85
|
+
# # Single
|
86
|
+
# ug.reject_type(Api::UnitTypeId::SCV) #=> <UnitGroup: ...>
|
87
|
+
# # Multiple - reject immovable army
|
88
|
+
# ug.reject_type([Api::UnitTypeId::SIEGETANKSIEGED, Api::UnitTypeId::WIDOWMINEBURROWED]) #=> <UnitGroup: ...>
|
89
|
+
# @param unit_type_ids [Integer, Array<Integer>] one or an array of unit Api::UnitTypeId
|
90
|
+
# @return [UnitGroup]
|
91
|
+
def reject_type(unit_type_ids)
|
92
|
+
cached("#{__method__}:#{unit_type_ids.hash}") do
|
93
|
+
if unit_type_ids.is_a? Array
|
94
|
+
reject { |unit| unit_type_ids.include?(unit.unit_type) }
|
95
|
+
else
|
96
|
+
reject { |unit| unit_type_ids == unit.unit_type }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Creates a negative selector, which will perform the opposite on the current scope
|
102
|
+
# for it's next select_type/reject_type call.
|
103
|
+
# @example
|
104
|
+
# structures.not.creep_tumors #=> all structures
|
105
|
+
# structures.not.pylons #=>
|
106
|
+
# units.not.workers # equivalent of units.army, but works too
|
107
|
+
# @return [Sc2::UnitGroupNotSelector]
|
108
|
+
def not
|
109
|
+
UnitGroupNotSelector.new(self)
|
110
|
+
end
|
111
|
+
|
112
|
+
# @private
|
113
|
+
# Negative selector allowing unit group "ug.not." filter
|
114
|
+
class UnitGroupNotSelector < UnitGroup
|
115
|
+
attr_accessor :parent
|
116
|
+
|
117
|
+
def initialize(unit_group)
|
118
|
+
@parent = unit_group
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
# @private
|
123
|
+
# Does the opposite of selector and returns those values for parent
|
124
|
+
def select_type(*)
|
125
|
+
@parent.reject_type(*)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Does the opposite of selector and returns those values for parent
|
129
|
+
def reject_type(*)
|
130
|
+
@parent.select_type(*)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Returns a new UnitGroup containing all units matching attribute id(s)
|
135
|
+
# Multiple values work as an "AND" filter
|
136
|
+
# @example
|
137
|
+
# # Single
|
138
|
+
# ug.select_attribute(Api::Attribute::Structure) #=> <UnitGroup: ...>
|
139
|
+
# ug.select_attribute(:Structure) #=> <UnitGroup: ...>
|
140
|
+
# # Multiple - select mechanical flying units
|
141
|
+
# ug.select_attribute([:Mechanical, :Flying]) #=> <UnitGroup: ...>
|
142
|
+
# @param attributes [Integer, Array<Integer>] one or an array of unit Api::UnitTypeId
|
143
|
+
# @return [UnitGroup]
|
144
|
+
def select_attribute(attributes)
|
145
|
+
cached("#{__method__}:#{attributes.hash}") do
|
146
|
+
attributes = [attributes] unless attributes.is_a? Array
|
147
|
+
attributes = attributes.map { |a| a.is_a?(Symbol) ? a : Api::Attribute.lookup(a) }
|
148
|
+
select do |unit|
|
149
|
+
attributes & unit.attributes == attributes
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns a new UnitGroup containing all units excluding attribute id(s)
|
155
|
+
# Multiple values work as an "AND" filter
|
156
|
+
# @example
|
157
|
+
# # Single
|
158
|
+
# ug.reject_attribute(Api::Attribute::Structure) #=> <UnitGroup: ...>
|
159
|
+
# ug.reject_attribute(:Structure) #=> <UnitGroup: ...>
|
160
|
+
# # Multiple - reject mechanical flying units
|
161
|
+
# ug.reject_attribute(:Mechanical, :Flying) #=> <UnitGroup: ...>
|
162
|
+
# @param attributes [Integer, Array<Integer>] one or an array of unit Api::UnitTypeId
|
163
|
+
# @return [UnitGroup]
|
164
|
+
def reject_attribute(attributes)
|
165
|
+
cached("#{__method__}:#{attributes.hash}") do
|
166
|
+
attributes = [attributes] unless attributes.is_a? Array
|
167
|
+
attributes = attributes.map { |a| a.is_a?(Symbol) ? a : Api::Attribute.lookup(a) }
|
168
|
+
reject do |unit|
|
169
|
+
unit.attributes & attributes == attributes
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# GENERICS ---
|
175
|
+
|
176
|
+
# Selects worker units
|
177
|
+
# @return [Sc2::UnitGroup] workers
|
178
|
+
def workers
|
179
|
+
select_type(TYPE_WORKER)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Selects non army units workers. Generally run on Sc2::Player#units
|
183
|
+
# @example in the Player context
|
184
|
+
# fighters = units.army
|
185
|
+
# enemy_fighters = units.army
|
186
|
+
# @return [Sc2::UnitGroup] army
|
187
|
+
# @see #non_army_unit_type_ids to modify filters
|
188
|
+
def army
|
189
|
+
reject_type(non_army_unit_type_ids)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Selects units with attribute Structure
|
193
|
+
# @return [Sc2::UnitGroup] structures
|
194
|
+
def structures
|
195
|
+
select_attribute(Api::Attribute::Structure)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Contains an array non-army types
|
199
|
+
# Override to remove or add units you want units.army to exclude
|
200
|
+
# @example
|
201
|
+
# # i.e. to have units.army to exclude Overseers
|
202
|
+
# @non_army_unit_types.push(Api::UnitTypeId::OVERSEER)
|
203
|
+
# # i.e. to have units.army to include Queens
|
204
|
+
# @non_army_unit_types.delete(Api::UnitTypeId::QUEEN)
|
205
|
+
# @non_army_unit_types.delete(Api::UnitTypeId::QUEENBURROWED)
|
206
|
+
def non_army_unit_type_ids
|
207
|
+
@non_army_unit_type_ids ||= TYPE_WORKER + [
|
208
|
+
Api::UnitTypeId::QUEEN, Api::UnitTypeId::QUEENBURROWED,
|
209
|
+
Api::UnitTypeId::OVERLORD, Api::UnitTypeId::OVERLORDCOCOON,
|
210
|
+
Api::UnitTypeId::LARVA
|
211
|
+
]
|
212
|
+
end
|
213
|
+
|
214
|
+
# Selects command posts (CC, OC, PF, Nexus, Hatch, Hive, Lair)
|
215
|
+
# Aliases are #hq and #townhalls
|
216
|
+
# @return [Sc2::UnitGroup] unit group of workers
|
217
|
+
def bases
|
218
|
+
select_type(TYPE_BASES)
|
219
|
+
end
|
220
|
+
alias_method :hq, :bases # short name
|
221
|
+
alias_method :townhalls, :bases # bad name
|
222
|
+
|
223
|
+
# Selects gas structures (refinery/extractor/assimilator)
|
224
|
+
# @return [UnitGroup] gas structures
|
225
|
+
def gas
|
226
|
+
select_type(TYPE_GAS_STRUCTURE)
|
227
|
+
end
|
228
|
+
alias_method :refineries, :gas
|
229
|
+
alias_method :extractors, :gas
|
230
|
+
alias_method :assimilators, :gas
|
231
|
+
|
232
|
+
# NEUTRAL ------------------------------------------
|
233
|
+
|
234
|
+
# Selects mineral fields
|
235
|
+
# @return [Sc2::UnitGroup] mineral fields
|
236
|
+
# @example
|
237
|
+
# # Typically a Player selects via group @neutral
|
238
|
+
# @neutral.minerals
|
239
|
+
def minerals
|
240
|
+
select_type(TYPE_MINERAL)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Selects gas geysers
|
244
|
+
# @return [Sc2::UnitGroup] gas geysers
|
245
|
+
# @example
|
246
|
+
# # Typically a Player selects via group @neutral
|
247
|
+
# @neutral.geysers
|
248
|
+
def geysers
|
249
|
+
select_type(TYPE_GEYSER)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Selects xel'naga watchtowers
|
253
|
+
# @return [Sc2::UnitGroup] watchtowers
|
254
|
+
# @example
|
255
|
+
# # Typically a Player selects via group @neutral
|
256
|
+
# @neutral.watchtowers
|
257
|
+
def watchtowers
|
258
|
+
select_type(Api::UnitTypeId::XELNAGATOWER)
|
259
|
+
end
|
260
|
+
|
261
|
+
# Reverse filters our minerals, geysers and towers to get what is hopefully debris
|
262
|
+
# @return [Sc2::UnitGroup] debris
|
263
|
+
# @example
|
264
|
+
# # Typically a Player selects via group @neutral
|
265
|
+
# @neutral.debris
|
266
|
+
def debris
|
267
|
+
reject_type(TYPE_REJECT_DEBRIS)
|
268
|
+
end
|
269
|
+
|
270
|
+
# ZERG ------------------------------------------
|
271
|
+
# Selects larva units
|
272
|
+
# @return [Sc2::UnitGroup] larva
|
273
|
+
def larva
|
274
|
+
select_type(Api::UnitTypeId::LARVA)
|
275
|
+
end
|
276
|
+
alias_method :larvae, :larva
|
277
|
+
|
278
|
+
# Selects queens
|
279
|
+
# @return [Sc2::UnitGroup] queens
|
280
|
+
def queens
|
281
|
+
select_type([Api::UnitTypeId::QUEEN, Api::UnitTypeId::QUEENBURROWED])
|
282
|
+
end
|
283
|
+
|
284
|
+
# Selects overlords
|
285
|
+
# @return [Sc2::UnitGroup]
|
286
|
+
def overlords
|
287
|
+
select_type([Api::UnitTypeId::OVERLORD, Api::UnitTypeId::OVERLORDCOCOON])
|
288
|
+
end
|
289
|
+
|
290
|
+
# Selects creep tumors (all)
|
291
|
+
# CREEPTUMORQUEEN is still building & burrowing
|
292
|
+
# while CREEPTUMOR was spread from another tumor still building & burrowing
|
293
|
+
# and CREEPTUMORBURROWED are burrowed tumors which have already spread or can still spread more
|
294
|
+
# @see #creep_tumors_burrowed for those ready to be spread
|
295
|
+
# @return [Sc2::UnitGroup] all tumors
|
296
|
+
def creep_tumors
|
297
|
+
select_type([Api::UnitTypeId::CREEPTUMORQUEEN, Api::UnitTypeId::CREEPTUMOR, Api::UnitTypeId::CREEPTUMORBURROWED])
|
298
|
+
end
|
299
|
+
alias_method :tumors, :creep_tumors
|
300
|
+
|
301
|
+
# Selects creep tumors which are burrowed.
|
302
|
+
# Burrowed tumors have already been spread or are spread-ready.
|
303
|
+
# No way to distinguish spreadable tumors without manual tracking.
|
304
|
+
# @return [Sc2::UnitGroup] burrowed tumors (with and without spread ability)
|
305
|
+
def creep_tumors_burrowed
|
306
|
+
select_type(Api::UnitTypeId::CREEPTUMORBURROWED)
|
307
|
+
end
|
308
|
+
|
309
|
+
# Protoss ---
|
310
|
+
|
311
|
+
# Selects pylons
|
312
|
+
# @return [Sc2::UnitGroup] pylons
|
313
|
+
def pylons
|
314
|
+
select_type(Api::UnitTypeId::PYLON)
|
315
|
+
end
|
316
|
+
|
317
|
+
# Selects warp gates (not gateways)
|
318
|
+
# @return [Sc2::UnitGroup] warp gates
|
319
|
+
def warpgates
|
320
|
+
select_type(Api::UnitTypeId::WARPGATE)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Selects pylons and warp prisms in phasing mode
|
324
|
+
# @return [Sc2::UnitGroup] pylons annd warp prisms phasing
|
325
|
+
def warpables
|
326
|
+
select_type([Api::UnitTypeId::PYLON, Api::UnitTypeId::WARPPRISMPHASING])
|
327
|
+
end
|
328
|
+
|
329
|
+
# Geometric/Map ---
|
330
|
+
|
331
|
+
# Whether we should be building a kdtree
|
332
|
+
# Added to allow the disabling of this property
|
333
|
+
# i.e. allows optimization of not to build if group is too big:
|
334
|
+
# return @units.size > 200
|
335
|
+
# If you don't do a lot of repeat filtering and don't get gains from repeated searches
|
336
|
+
# then override the attribute and set this to: @units.size > 120
|
337
|
+
# @return [Boolean]
|
338
|
+
attr_accessor :use_kdtree
|
339
|
+
|
340
|
+
# Builds a kdtree if not already built and returns it
|
341
|
+
# @return [Kdtree]
|
342
|
+
def kdtree
|
343
|
+
return @kdtree unless @kdtree.nil?
|
344
|
+
@kdtree = Kdtree.new(@units.values.each_with_index.map { |unit, index| [unit.pos.x, unit.pos.y, index] })
|
345
|
+
end
|
346
|
+
|
347
|
+
# Returns an
|
348
|
+
# @param pos [Sc2::Position] unit.pos or a point of any kind
|
349
|
+
# @return [Sc2::UnitGroup, Api::Unit, nil] return group or single unit if amount is not supplied
|
350
|
+
def nearest_to(pos:, amount: nil)
|
351
|
+
return UnitGroup.new if !amount.nil? && amount.to_i <= 0
|
352
|
+
|
353
|
+
if use_kdtree
|
354
|
+
if amount.nil?
|
355
|
+
index = kdtree.nearest(pos.x, pos.y)
|
356
|
+
return @units.values[index]
|
357
|
+
else
|
358
|
+
result_indexes = kdtree.nearestk(pos.x, pos.y, amount)
|
359
|
+
return UnitGroup.new(@units.values.values_at(*result_indexes))
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Traditional array min_by with distance calcs on the fly
|
364
|
+
if amount.nil?
|
365
|
+
# noinspection RubyMismatchedReturnType
|
366
|
+
@units.values.min_by { |unit| unit.distance_to(pos) }
|
367
|
+
else
|
368
|
+
UnitGroup.new(@units.values.min_by(amount) { |unit| unit.distance_to(pos) })
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# Selects units which are in a particular circle
|
373
|
+
# @param point [Api::Point2D, Api::Point] center of circle
|
374
|
+
# @param radius [Float]
|
375
|
+
def select_in_circle(point:, radius:)
|
376
|
+
select { |unit| unit.in_circle?(point:, radius:) }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
@@ -0,0 +1,277 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
# A collection of Api::Unit, which acts as a hash of unit.tag => unit.
|
5
|
+
# Consider a UnitGroup a virtual Control Group.
|
6
|
+
# Most Hash operations like select/filter, reject, etc. are implemented to return a UnitGroup to to allow fluent group control
|
7
|
+
class UnitGroup
|
8
|
+
extend Forwardable
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
# @!attribute units
|
12
|
+
# A hash of units by tag.
|
13
|
+
# @return [Hash<Integer, Api::Unit>] Api::Unit.tag => Api::Unit
|
14
|
+
attr_accessor :units
|
15
|
+
|
16
|
+
# @param units [Api::Unit, Hash<Integer, Api::Unit>, Array<Api::Unit>, Sc2::UnitGroup, nil] default to be added.
|
17
|
+
# @return Sc2::UnitGroup new unit group
|
18
|
+
def initialize(units = nil)
|
19
|
+
@units = {}
|
20
|
+
case units
|
21
|
+
when Array, Google::Protobuf::RepeatedField
|
22
|
+
units.each do |unit|
|
23
|
+
@units[unit.tag] = unit
|
24
|
+
end
|
25
|
+
when Hash
|
26
|
+
@units = units
|
27
|
+
when UnitGroup
|
28
|
+
@units = units.units
|
29
|
+
else
|
30
|
+
# noop
|
31
|
+
end
|
32
|
+
|
33
|
+
@_cache_hash = {}
|
34
|
+
@_cache = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!macro [attach] def_delegators
|
38
|
+
# @!method $2
|
39
|
+
# Forwards to hash of #units.
|
40
|
+
# @see Hash#$2
|
41
|
+
def_delegator :@units, :empty? # Returns whether there are no entries.
|
42
|
+
def_delegator :@units, :eql? # Returns whether a given object is equal to #units.
|
43
|
+
def_delegator :@units, :length # Returns the count of entries.
|
44
|
+
def_delegator :@units, :size # Returns the count of entries.
|
45
|
+
|
46
|
+
# @!macro [attach] def_delegators
|
47
|
+
# @!method $2
|
48
|
+
# Forwards to hash of #units.
|
49
|
+
# @see Hash#$2
|
50
|
+
def_delegator :@units, :< # Returns whether #units is a proper subset of a given object.
|
51
|
+
def_delegator :@units, :<= # Returns whether #units is a subset of a given object.
|
52
|
+
def_delegator :@units, :== # Returns whether a given object is equal to #units.
|
53
|
+
def_delegator :@units, :> # Returns whether #units is a proper superset of a given object
|
54
|
+
def_delegator :@units, :>= # Returns whether #units is a proper superset of a given object.
|
55
|
+
|
56
|
+
# @!macro [attach] def_delegators
|
57
|
+
# @!method $2
|
58
|
+
# Forwards to hash of #units.
|
59
|
+
# @see Hash#$2
|
60
|
+
def_delegator :@units, :[] # Returns the value associated with the given key, if found
|
61
|
+
def_delegator :@units, :keys # Returns an array containing all keys in #units.
|
62
|
+
def_delegator :@units, :values # Returns an array containing all values in #units.
|
63
|
+
def_delegator :@units, :values_at # Returns an array containing values for given tags in #units.
|
64
|
+
|
65
|
+
# @!macro [attach] def_delegators
|
66
|
+
# @!method $2
|
67
|
+
# Forwards to hash of #units.
|
68
|
+
# @see Hash#$2
|
69
|
+
def_delegator :@units, :clear # Removes all entries from #units.
|
70
|
+
def_delegator :@units, :delete_if # Removes entries selected by a given block.
|
71
|
+
def_delegator :@units, :select! # Keep only those entries selected by a given block
|
72
|
+
def_delegator :@units, :filter! # Keep only those entries selected by a given block
|
73
|
+
def_delegator :@units, :keep_if # Keep only those entries selected by a given block
|
74
|
+
# def_delegator :@units, :compact! # Removes all +nil+-valued entries from #units.
|
75
|
+
|
76
|
+
# find_all, #filter, #select: Returns elements selected by the block.
|
77
|
+
|
78
|
+
def_delegator :@units, :reject! # Removes entries selected by a given block.
|
79
|
+
def_delegator :@units, :shift # Removes and returns the first entry.
|
80
|
+
|
81
|
+
def_delegator :@units, :to_a # Returns a new array of 2-element arrays; each nested array contains a key-value pair from #units
|
82
|
+
def_delegator :@units, :to_h # Returns {UnitGroup#units}
|
83
|
+
def_delegator :@units, :to_hash # Returns {UnitGroup#units}
|
84
|
+
def_delegator :@units, :to_proc # Returns a proc that maps a given key to its value.Returns a proc that maps a given key to its value.
|
85
|
+
|
86
|
+
# Gets the Unit at an index
|
87
|
+
# @return [Api::Unit]
|
88
|
+
def at(index)
|
89
|
+
@units[tags.at(index)]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Calls the given block with each Api::Unit value
|
93
|
+
# @example
|
94
|
+
# unit_group.each {|unit| puts unit.tag } #=> 1234 ...
|
95
|
+
#
|
96
|
+
# unit_group.each do |unit|
|
97
|
+
# puts unit.tag #=> 1234 ...
|
98
|
+
# end
|
99
|
+
def each(&block)
|
100
|
+
@units.each_value(&block)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Calls the given block with each key-value pair
|
104
|
+
# @return [self] a new unit group with items merged
|
105
|
+
# @example
|
106
|
+
# unit_group.each {|tag, unit| puts "#{tag}: #{unit}"} #=> "12345: #<Api::Unit ...>"
|
107
|
+
#
|
108
|
+
# unit_group.each do |tag, unit|
|
109
|
+
# puts "#{tag}: #{unit}"} #=> "12345: #<Api::Unit ...>"
|
110
|
+
# end
|
111
|
+
def each_with_tag(&block)
|
112
|
+
@units.each(&block)
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
# Checks whether this group contains a unit.
|
117
|
+
# @param unit [Api::Unit, Integer] a unit or a tag.
|
118
|
+
# @return [Boolean] A boolean indicating if the #units include? unit.
|
119
|
+
def contains?(unit)
|
120
|
+
tag = unit.is_a?(Api::Unit) ? unit.tag : unit
|
121
|
+
@units.include?(tag)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Associates a given unit tag with a given unit.
|
125
|
+
# @see UnitGroup#add #add, which is easier to just pass an Api::Unit
|
126
|
+
def []=(unit_tag, unit)
|
127
|
+
return if unit_tag.nil? || unit.nil?
|
128
|
+
@units[unit_tag] = unit
|
129
|
+
end
|
130
|
+
|
131
|
+
# Adds a unit or units to the group.
|
132
|
+
# @param units [Api::Unit, Array<Api::Unit>, Sc2::UnitGroup]
|
133
|
+
# @return [self]
|
134
|
+
def add(units)
|
135
|
+
case units
|
136
|
+
when Api::Unit
|
137
|
+
@units[units.tag] = units
|
138
|
+
when Array
|
139
|
+
units.each do |unit|
|
140
|
+
@units[unit.tag] = unit
|
141
|
+
end
|
142
|
+
when Hash
|
143
|
+
@units.merge(units.units)
|
144
|
+
when UnitGroup
|
145
|
+
@units.merge(units)
|
146
|
+
else
|
147
|
+
# noop
|
148
|
+
end
|
149
|
+
@units
|
150
|
+
end
|
151
|
+
|
152
|
+
alias_method :push, :add
|
153
|
+
|
154
|
+
# Remove a another UnitGroup's units from ours or a singular Api::Unit either by object or Api::Unit#tag
|
155
|
+
# @param unit_group_unit_or_tag [Sc2::UnitGroup, Api::Unit, Integer, nil]
|
156
|
+
# @return [Hash<Integer, Api::Unit>, Api::Unit, nil] the removed item(s) or nil
|
157
|
+
def remove(unit_group_unit_or_tag)
|
158
|
+
case unit_group_unit_or_tag
|
159
|
+
when Sc2::UnitGroup
|
160
|
+
@units.reject! { |tag, _unit| unit_group_unit_or_tag.units.has_key?(tag) }
|
161
|
+
when Api::Unit
|
162
|
+
@units.delete(unit_group_unit_or_tag.tag)
|
163
|
+
when Integer
|
164
|
+
# noinspection RubyMismatchedArgumentType
|
165
|
+
@units.delete(unit_group_unit_or_tag)
|
166
|
+
else
|
167
|
+
# noop
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
alias_method :delete, :remove
|
172
|
+
|
173
|
+
# Creates a new unit group which is the result of the two being subtracted
|
174
|
+
# @param other_unit_group [UnitGroup]
|
175
|
+
# @return [UnitGroup] new unit group
|
176
|
+
def subtract(other_unit_group)
|
177
|
+
UnitGroup.new(@units.reject { |tag, _unit| other_unit_group.units.has_key?(tag) })
|
178
|
+
end
|
179
|
+
|
180
|
+
# Merges unit_group with our units and returns a new unit group
|
181
|
+
# @return [Sc2::UnitGroup] a new unit group with items merged
|
182
|
+
def merge(unit_group)
|
183
|
+
UnitGroup.new(@units.merge(unit_group))
|
184
|
+
end
|
185
|
+
alias_method :+, :merge
|
186
|
+
|
187
|
+
# Merges unit_group.units into self.units and returns self
|
188
|
+
# @return [self]
|
189
|
+
def merge!(unit_group)
|
190
|
+
@units.merge!(unit_group.units)
|
191
|
+
self
|
192
|
+
end
|
193
|
+
|
194
|
+
# Replaces the entire contents of #units with the contents of a given unit_group.
|
195
|
+
# Synonymous with self.units = unit_group.units
|
196
|
+
# @return [void]
|
197
|
+
def replace(unit_group)
|
198
|
+
@units = unit_group.units
|
199
|
+
end
|
200
|
+
|
201
|
+
# Returns a new UnitGroup object whose #units entries are those for which the block returns a truthy value
|
202
|
+
# @return [Sc2::UnitGroup] new unit group
|
203
|
+
# noinspection RubyMismatchedReturnType # UnitGroup acts as an array, so sig is ok.
|
204
|
+
def select
|
205
|
+
result = @units.select { |_tag, unit| yield unit }
|
206
|
+
UnitGroup.new(result)
|
207
|
+
end
|
208
|
+
|
209
|
+
alias_method :filter, :select
|
210
|
+
|
211
|
+
# Returns a new UnitGroup object whose entries are all those from #units for which the block returns false or nil
|
212
|
+
# @return [Sc2::UnitGroup] new unit group
|
213
|
+
# noinspection RubyMismatchedReturnType # UnitGroup acts as an array, so sig is ok.
|
214
|
+
def reject(&block)
|
215
|
+
result = @units.reject { |_tag, unit| yield unit }
|
216
|
+
UnitGroup.new(result)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Returns a copy of self with units removed for specified tags.
|
220
|
+
# @return [Sc2::UnitGroup] new unit group
|
221
|
+
def except(...)
|
222
|
+
UnitGroup.new(@units.except(...))
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns a hash containing the entries for given tag(s).
|
226
|
+
# @return [Sc2::UnitGroup] new unit group
|
227
|
+
def slice(...)
|
228
|
+
UnitGroup.new(@units.slice(...))
|
229
|
+
end
|
230
|
+
|
231
|
+
# Selects a single random Unit without a parameter or an array of Units with a param, i.e. self.random(2)
|
232
|
+
# @return []
|
233
|
+
def sample(...)
|
234
|
+
@units.values.sample(...)
|
235
|
+
end
|
236
|
+
alias_method :random, :sample
|
237
|
+
|
238
|
+
# def select_or(*procs)
|
239
|
+
# result = UnitGroup.new
|
240
|
+
# procs.each do |proc|
|
241
|
+
# selected = select(&proc)
|
242
|
+
# result.merge!(selected) unless selected.nil?
|
243
|
+
# end
|
244
|
+
# result
|
245
|
+
# end
|
246
|
+
|
247
|
+
# Returns an array of unit tags
|
248
|
+
# @return [Array<Integer>] array of unit#tag
|
249
|
+
def tags
|
250
|
+
keys
|
251
|
+
end
|
252
|
+
|
253
|
+
class << self
|
254
|
+
end
|
255
|
+
|
256
|
+
private
|
257
|
+
|
258
|
+
# @private
|
259
|
+
# Caches a block based on key and current UnitGroup#units.hash
|
260
|
+
# If the hash changes (units are modified) the cache expires
|
261
|
+
# This allows lazy lookups which only fires if the units change
|
262
|
+
# Allows, i.e. Player#units.workers to fire only the first time it's called per frame
|
263
|
+
def cached(key)
|
264
|
+
if @_cache_hash[key] != @units.hash
|
265
|
+
@_cache_hash[key] = @units.hash
|
266
|
+
@_cache[key] = yield
|
267
|
+
end
|
268
|
+
@_cache[key]
|
269
|
+
end
|
270
|
+
|
271
|
+
attr_accessor :_cache_hash
|
272
|
+
attr_accessor :_cache
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
require_relative "unit_group/action_ext"
|
277
|
+
require_relative "unit_group/filter_ext"
|