sc2ai 0.0.0.pre → 0.0.2

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.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/data/data.json +1 -0
  3. data/data/data_readable.json +22842 -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 +35730 -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 +1644 -0
  23. data/lib/sc2ai/api/buff_id.rb +306 -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 +83 -0
  27. data/lib/sc2ai/api/tech_tree_data.rb +2338 -0
  28. data/lib/sc2ai/api/unit_type_id.rb +2022 -0
  29. data/lib/sc2ai/api/upgrade_id.rb +310 -0
  30. data/lib/sc2ai/cli/cli.rb +175 -0
  31. data/lib/sc2ai/cli/ladderzip.rb +154 -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. data/sc2ai.gemspec +80 -0
  94. 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"
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.2"
5
6
  end