sc2ai 0.0.0.pre → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/data/data.json +1 -0
  3. data/data/data_readable.json +22946 -0
  4. data/data/sc2ai/protocol/common.proto +59 -0
  5. data/data/sc2ai/protocol/data.proto +120 -0
  6. data/data/sc2ai/protocol/debug.proto +127 -0
  7. data/data/sc2ai/protocol/error.proto +221 -0
  8. data/data/sc2ai/protocol/query.proto +55 -0
  9. data/data/sc2ai/protocol/raw.proto +202 -0
  10. data/data/sc2ai/protocol/sc2api.proto +718 -0
  11. data/data/sc2ai/protocol/score.proto +108 -0
  12. data/data/sc2ai/protocol/spatial.proto +115 -0
  13. data/data/sc2ai/protocol/ui.proto +145 -0
  14. data/data/setup/setup.SC2Map +0 -0
  15. data/data/setup/setup.SC2Replay +0 -0
  16. data/data/stableid.json +37900 -0
  17. data/data/versions.json +554 -0
  18. data/exe/sc2ai +35 -0
  19. data/lib/docker_build/Dockerfile.ruby +74 -0
  20. data/lib/docker_build/docker-compose-base-image.yml +10 -0
  21. data/lib/docker_build/docker-compose-ladderzip.yml +9 -0
  22. data/lib/sc2ai/api/ability_id.rb +1951 -0
  23. data/lib/sc2ai/api/buff_id.rb +316 -0
  24. data/lib/sc2ai/api/data.rb +101 -0
  25. data/lib/sc2ai/api/effect_id.rb +20 -0
  26. data/lib/sc2ai/api/tech_tree.rb +82 -0
  27. data/lib/sc2ai/api/tech_tree_data.rb +2342 -0
  28. data/lib/sc2ai/api/unit_type_id.rb +2074 -0
  29. data/lib/sc2ai/api/upgrade_id.rb +312 -0
  30. data/lib/sc2ai/cli/cli.rb +177 -0
  31. data/lib/sc2ai/cli/ladderzip.rb +173 -0
  32. data/lib/sc2ai/cli/new.rb +88 -0
  33. data/lib/sc2ai/configuration.rb +145 -0
  34. data/lib/sc2ai/connection/connection_listener.rb +30 -0
  35. data/lib/sc2ai/connection/requests.rb +417 -0
  36. data/lib/sc2ai/connection/status_listener.rb +15 -0
  37. data/lib/sc2ai/connection.rb +146 -0
  38. data/lib/sc2ai/local_play/client/configurable_options.rb +115 -0
  39. data/lib/sc2ai/local_play/client.rb +159 -0
  40. data/lib/sc2ai/local_play/client_manager.rb +70 -0
  41. data/lib/sc2ai/local_play/map_file.rb +48 -0
  42. data/lib/sc2ai/local_play/match.rb +184 -0
  43. data/lib/sc2ai/overrides/array.rb +14 -0
  44. data/lib/sc2ai/overrides/async/process/child.rb +31 -0
  45. data/lib/sc2ai/overrides/kernel.rb +33 -0
  46. data/lib/sc2ai/paths.rb +294 -0
  47. data/lib/sc2ai/player/actions.rb +386 -0
  48. data/lib/sc2ai/player/debug.rb +224 -0
  49. data/lib/sc2ai/player/game_state.rb +131 -0
  50. data/lib/sc2ai/player/geometry.rb +766 -0
  51. data/lib/sc2ai/player/previous_state.rb +49 -0
  52. data/lib/sc2ai/player/units.rb +337 -0
  53. data/lib/sc2ai/player.rb +661 -0
  54. data/lib/sc2ai/ports.rb +152 -0
  55. data/lib/sc2ai/protocol/_meta_documentation.rb +39 -0
  56. data/lib/sc2ai/protocol/common_pb.rb +43 -0
  57. data/lib/sc2ai/protocol/data_pb.rb +47 -0
  58. data/lib/sc2ai/protocol/debug_pb.rb +56 -0
  59. data/lib/sc2ai/protocol/error_pb.rb +36 -0
  60. data/lib/sc2ai/protocol/extensions/color.rb +20 -0
  61. data/lib/sc2ai/protocol/extensions/point.rb +23 -0
  62. data/lib/sc2ai/protocol/extensions/point_2_d.rb +26 -0
  63. data/lib/sc2ai/protocol/extensions/position.rb +202 -0
  64. data/lib/sc2ai/protocol/extensions/power_source.rb +19 -0
  65. data/lib/sc2ai/protocol/extensions/unit.rb +489 -0
  66. data/lib/sc2ai/protocol/query_pb.rb +47 -0
  67. data/lib/sc2ai/protocol/raw_pb.rb +57 -0
  68. data/lib/sc2ai/protocol/sc2api_pb.rb +130 -0
  69. data/lib/sc2ai/protocol/score_pb.rb +40 -0
  70. data/lib/sc2ai/protocol/spatial_pb.rb +48 -0
  71. data/lib/sc2ai/protocol/ui_pb.rb +56 -0
  72. data/lib/sc2ai/unit_group/action_ext.rb +74 -0
  73. data/lib/sc2ai/unit_group/filter_ext.rb +379 -0
  74. data/lib/sc2ai/unit_group.rb +277 -0
  75. data/lib/sc2ai/version.rb +2 -1
  76. data/lib/sc2ai.rb +93 -2
  77. data/lib/templates/ladderzip/bin/ladder.tt +23 -0
  78. data/lib/templates/new/.ladderignore +20 -0
  79. data/lib/templates/new/Gemfile.tt +7 -0
  80. data/lib/templates/new/api/common.proto +59 -0
  81. data/lib/templates/new/api/data.proto +120 -0
  82. data/lib/templates/new/api/debug.proto +127 -0
  83. data/lib/templates/new/api/error.proto +221 -0
  84. data/lib/templates/new/api/query.proto +55 -0
  85. data/lib/templates/new/api/raw.proto +202 -0
  86. data/lib/templates/new/api/sc2api.proto +718 -0
  87. data/lib/templates/new/api/score.proto +108 -0
  88. data/lib/templates/new/api/spatial.proto +115 -0
  89. data/lib/templates/new/api/ui.proto +145 -0
  90. data/lib/templates/new/boot.rb.tt +6 -0
  91. data/lib/templates/new/my_bot.rb.tt +23 -0
  92. data/lib/templates/new/run_example_match.rb.tt +14 -0
  93. metadata +353 -9
@@ -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"
data/lib/sc2ai/version.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sc2
4
- VERSION = "0.0.0.pre"
4
+ # gem version
5
+ VERSION = "0.0.3"
5
6
  end