the-swarm 1.0.3

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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.yardopts +8 -0
  4. data/Gemfile +24 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +35 -0
  7. data/Rakefile +26 -0
  8. data/bin/swarm +11 -0
  9. data/docs/Swarm.html +296 -0
  10. data/docs/Swarm/Catalog.html +819 -0
  11. data/docs/Swarm/Console.html +1050 -0
  12. data/docs/Swarm/Console/Color.html +160 -0
  13. data/docs/Swarm/Console/NormalKey.html +410 -0
  14. data/docs/Swarm/Console/PopUp.html +236 -0
  15. data/docs/Swarm/Console/VIKey.html +410 -0
  16. data/docs/Swarm/Five.html +421 -0
  17. data/docs/Swarm/Four.html +389 -0
  18. data/docs/Swarm/Game.html +1197 -0
  19. data/docs/Swarm/Intro.html +419 -0
  20. data/docs/Swarm/Level.html +700 -0
  21. data/docs/Swarm/Map.html +2336 -0
  22. data/docs/Swarm/One.html +353 -0
  23. data/docs/Swarm/Three.html +383 -0
  24. data/docs/Swarm/Tile.html +3823 -0
  25. data/docs/Swarm/Two.html +355 -0
  26. data/docs/_index.html +313 -0
  27. data/docs/class_list.html +51 -0
  28. data/docs/css/common.css +1 -0
  29. data/docs/css/full_list.css +58 -0
  30. data/docs/css/style.css +492 -0
  31. data/docs/file.LICENSE.html +70 -0
  32. data/docs/file.README.html +101 -0
  33. data/docs/file_list.html +61 -0
  34. data/docs/frames.html +17 -0
  35. data/docs/index.html +101 -0
  36. data/docs/js/app.js +248 -0
  37. data/docs/js/full_list.js +216 -0
  38. data/docs/js/jquery.js +4 -0
  39. data/docs/method_list.html +1195 -0
  40. data/docs/top-level-namespace.html +110 -0
  41. data/lib/swarm.rb +60 -0
  42. data/lib/swarm/catalog.rb +71 -0
  43. data/lib/swarm/console.rb +165 -0
  44. data/lib/swarm/game.rb +173 -0
  45. data/lib/swarm/level.rb +58 -0
  46. data/lib/swarm/levels/five.rb +71 -0
  47. data/lib/swarm/levels/four.rb +55 -0
  48. data/lib/swarm/levels/intro.rb +36 -0
  49. data/lib/swarm/levels/one.rb +35 -0
  50. data/lib/swarm/levels/three.rb +51 -0
  51. data/lib/swarm/levels/two.rb +38 -0
  52. data/lib/swarm/map.rb +254 -0
  53. data/lib/swarm/tile.rb +149 -0
  54. data/lib/swarm/version.rb +5 -0
  55. data/swarm.gemspec +24 -0
  56. metadata +112 -0
@@ -0,0 +1,58 @@
1
+ module Swarm
2
+
3
+ class Level
4
+
5
+ def Level.each(&block)
6
+ [Intro, One, Two, Three, Four, Five].each { |level| block.call level.new }
7
+ end
8
+
9
+ def update
10
+ @map.update
11
+ end
12
+
13
+ def update!
14
+ @map.each(&:change!)
15
+ @map.update
16
+ end
17
+
18
+ def find_player
19
+ @player = (@player && @player.player?) ? @player : @map.find(&:player?)
20
+ end
21
+
22
+ def spawn_player
23
+ @map.center.player!
24
+ end
25
+
26
+ def move_player(direction)
27
+ move find_player, direction
28
+ end
29
+
30
+ def move(tile, direction)
31
+ case direction
32
+ when :north, :south, :west, :east
33
+ @map.move(tile, direction)
34
+ else
35
+ available = @map.available_moves(tile)
36
+ aggressive = @map.aggressive_moves(tile, direction)
37
+ aggressive &= available
38
+
39
+ if aggressive.any?
40
+ @map.move tile, aggressive.sample
41
+ elsif available.any?
42
+ @map.move tile, available.sample
43
+ end
44
+ end
45
+ end
46
+
47
+ def over?
48
+ !Catalog.select(*%i[worker soldier queen egg]).any?
49
+ end
50
+
51
+ def initialize
52
+ @map = Map.new(Console.width, Console.height, tile_width: 2)
53
+
54
+ @map.each &:empty!
55
+ @map.update
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,71 @@
1
+ module Swarm
2
+ class Five < Level
3
+
4
+ def play
5
+ promoted =
6
+ laid_egg =
7
+ hatched = false
8
+
9
+ Catalog.select(*%i[worker soldier queen egg]).each do |tile|
10
+ tile.age!
11
+
12
+ if tile.worker?
13
+ if !promoted && (tile.age % 15).zero?
14
+ tile.soldier!
15
+
16
+ promoted = true
17
+ else
18
+ move tile, @player
19
+ end
20
+ elsif tile.queen?
21
+ if !laid_egg && (tile.age % 10).zero?
22
+ move tile, @player
23
+ tile.egg!
24
+
25
+ laid_egg = true
26
+ else
27
+ move tile, @player
28
+ end
29
+ elsif tile.egg?
30
+ if !hatched && (tile.age % 5).zero?
31
+ tile.queen!
32
+
33
+ hatched = true
34
+ end
35
+ elsif tile.soldier?
36
+ move tile, @player
37
+ end
38
+ end
39
+
40
+ true
41
+ end
42
+
43
+
44
+ def show(players)
45
+ pause do
46
+ <<-POPUP.gsub(/[ ]{10}/, '') % Console.key_info
47
+
48
+ - LEVEL 5 -
49
+
50
+
51
+ Press %<pause>s to start.
52
+
53
+ (#{players} lives remaining)
54
+ POPUP
55
+ end
56
+ end
57
+
58
+ def setup
59
+ @map.each &:empty!
60
+
61
+ @map.spawn :dirt!, 30
62
+ @map.spawn :rock!, 5
63
+ @map.spawn :worker!, 0.5
64
+ @map.spawn :soldier!, 0.15
65
+ @map.spawn :queen!, 0.3
66
+ @map.spawn :egg!, 0.15
67
+
68
+ @map.center.player!
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,55 @@
1
+ module Swarm
2
+ class Four < Level
3
+
4
+ def play
5
+ promoted = false
6
+
7
+ Catalog.select(*%i[worker soldier queen]).each do |tile|
8
+ tile.age!
9
+
10
+ if tile.worker?
11
+ if !promoted && (tile.age % 15).zero?
12
+ tile.soldier!
13
+
14
+ promoted = true
15
+ else
16
+ move tile, @player
17
+ end
18
+ elsif tile.queen?
19
+ move tile, @player
20
+ elsif tile.soldier?
21
+ move tile, @player
22
+ end
23
+ end
24
+
25
+ true
26
+ end
27
+
28
+
29
+ def show(players)
30
+ pause do
31
+ <<-POPUP.gsub(/[ ]{10}/, '') % Console.key_info
32
+
33
+ - LEVEL 4 -
34
+
35
+
36
+ Press %<pause>s to start.
37
+
38
+ (#{players} lives remaining)
39
+ POPUP
40
+ end
41
+ end
42
+
43
+ def setup
44
+ @map.each &:empty!
45
+
46
+ @map.spawn :dirt!, 30
47
+ @map.spawn :rock!, 5
48
+ @map.spawn :worker!, 0.5
49
+ @map.spawn :soldier!, 0.15
50
+ @map.spawn :queen!, 0.3
51
+
52
+ @map.center.player!
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,36 @@
1
+ module Swarm
2
+ class Intro < Level
3
+ def play
4
+ @over = true
5
+ end
6
+
7
+ def show(*args)
8
+ @over = false
9
+ pause do <<-POPUP.gsub(/[ ]{10}/, '') % Console.key_info
10
+ Swarm Instructions
11
+ ----------------------------------------------
12
+ Movement:
13
+ north: %<north>s
14
+ south: %<south>s
15
+ west: %<west>s
16
+ east: %<east>s
17
+
18
+ Pause: %<pause>s
19
+ Quit: %<quit>s
20
+ ----------------------------------------------
21
+
22
+ Press %<pause>s to begin.
23
+ POPUP
24
+ end
25
+ end
26
+
27
+ def setup
28
+ @map.center.player!
29
+ end
30
+
31
+ def over?
32
+ @over
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,35 @@
1
+ module Swarm
2
+ class One < Level
3
+
4
+ def play
5
+ Catalog.select(:worker).each do |tile|
6
+ move tile, @player
7
+ end
8
+
9
+ true
10
+ end
11
+
12
+ def show(players)
13
+ pause do
14
+ <<-POPUP.gsub(/[ ]{10}/, '') % Console.key_info
15
+
16
+ - LEVEL 1 -
17
+
18
+ Press %<pause>s to start.
19
+
20
+ (#{players} lives remaining)
21
+ POPUP
22
+ end
23
+ end
24
+
25
+ def setup
26
+ @map.each &:empty!
27
+
28
+ @map.spawn :dirt!, 30
29
+ @map.spawn :rock!, 5
30
+ @map.spawn :worker!, 0.75
31
+
32
+ @map.center.player!
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,51 @@
1
+ module Swarm
2
+ class Three < Level
3
+
4
+ def play
5
+ promoted = false
6
+
7
+ Catalog.select(*%i[worker soldier]).each do |tile|
8
+ tile.age!
9
+
10
+ if tile.worker?
11
+ if !promoted && (tile.age % 15).zero?
12
+ tile.soldier!
13
+
14
+ promoted = true
15
+ else
16
+ move tile, @player
17
+ end
18
+ elsif tile.soldier?
19
+ move tile, @player
20
+ end
21
+ end
22
+
23
+ true
24
+ end
25
+
26
+ def show(players)
27
+ pause do
28
+ <<-POPUP.gsub(/[ ]{10}/, '') % Console.key_info
29
+
30
+ - LEVEL 3 -
31
+
32
+
33
+ Press %<pause>s to start.
34
+
35
+ (#{players} lives remaining)
36
+ POPUP
37
+ end
38
+ end
39
+
40
+ def setup
41
+ @map.each &:empty!
42
+
43
+ @map.spawn :dirt!, 30
44
+ @map.spawn :rock!, 5
45
+ @map.spawn :worker!, 0.5
46
+ @map.spawn :soldier!, 0.2
47
+
48
+ @map.center.player!
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,38 @@
1
+ module Swarm
2
+ class Two < Level
3
+
4
+ def play
5
+ Catalog.select(*%i[worker soldier]).each do |tile|
6
+ move tile, @player
7
+ end
8
+
9
+ true
10
+ end
11
+
12
+
13
+ def show(players)
14
+ pause do
15
+ <<-POPUP.gsub(/[ ]{10}/, '') % Console.key_info
16
+
17
+ - LEVEL 2 -
18
+
19
+
20
+ Press %<pause>s to start.
21
+
22
+ (#{players} lives remaining)
23
+ POPUP
24
+ end
25
+ end
26
+
27
+ def setup
28
+ @map.each &:empty!
29
+
30
+ @map.spawn :dirt!, 30
31
+ @map.spawn :rock!, 5
32
+ @map.spawn :worker!, 0.5
33
+ @map.spawn :soldier!, 0.15
34
+
35
+ @map.center.player!
36
+ end
37
+ end
38
+ end
data/lib/swarm/map.rb ADDED
@@ -0,0 +1,254 @@
1
+ module Swarm
2
+
3
+ # @example The +Map+ Layout
4
+ # +-------------------------------------------+
5
+ # | |
6
+ # | [0,0] [1,0] [2,0] [3,0] [4,0] [5,0] [6,0] |
7
+ # | |
8
+ # | [0,1] [1,1] [2,1] [3,1] [4,1] [5,1] [6,1] |
9
+ # | |
10
+ # | [0,2] [1,2] [2,2] [3,2] [4,2] [5,2] [6,2] |
11
+ # | |
12
+ # | [0,3] [1,3] [2,3] [3,3] [4,3] [5,3] [6,3] |
13
+ # | |
14
+ # | [0,4] [1,4] [2,4] [3,4] [4,4] [5,4] [6,4] |
15
+ # | |
16
+ # | [0,5] [1,5] [2,5] [3,5] [4,5] [5,5] [6,5] |
17
+ # | |
18
+ # | [0,6] [1,6] [2,6] [3,6] [4,6] [5,6] [6,6] |
19
+ # | |
20
+ # +-------------------------------------------+
21
+ class Map
22
+
23
+ # @macro swarm.map.dimensions
24
+ # @return [Integer] the +$1+ of the +Map+.
25
+
26
+ # @macro swarm.map.dimensions
27
+ attr_reader :width
28
+
29
+ # @macro swarm.map.dimensions
30
+ attr_reader :height
31
+
32
+ # A 2 dimensional array of tiles.
33
+ def initialize(width, height, tile_width: 1, tile_height: 1)
34
+ @tiles = (0...height).step(tile_height).map.with_index do |console_y, map_y|
35
+ (0...width).step(tile_width).map.with_index do |console_x, map_x|
36
+ Tile.new map_x,
37
+ map_y,
38
+ [console_y, console_x] # reversed for Console::setpos
39
+ end
40
+ end
41
+
42
+ @height = height / tile_height
43
+ @width = width / tile_width
44
+ end
45
+
46
+ include Enumerable
47
+
48
+ def each(&block)
49
+ @tiles.each { |row| row.each &block }
50
+ end
51
+
52
+ # @return [Tile] a center tile
53
+ def center
54
+ @tiles[height / 2][width / 2]
55
+ end
56
+
57
+ # @return [Tile] a random tile
58
+ def sample
59
+ @tiles.sample.sample
60
+ end
61
+
62
+ # @param x [Integer]
63
+ # @param y [Integer]
64
+ # @return [Tile]
65
+ def [](x, y)
66
+ @tiles.fetch(y, Array.new)[x]
67
+ end
68
+
69
+ # @param x [Integer]
70
+ # @param y [Integer]
71
+ # @param tile [Tile]
72
+ # @return [Tile]
73
+ def []=(x, y, tile)
74
+ @tiles[y] ||= Array.new
75
+ @tiles[y][x] = tile
76
+ end
77
+
78
+ # @return [Tile] the last tile (highest +x+ and +y+ value)
79
+ def last
80
+ self[-1, -1]
81
+ end
82
+
83
+ def score(key)
84
+ Catalog.fetch :destroyed, key, 0
85
+ end
86
+
87
+ def spawn(flag, percent = 0.0)
88
+ total = ((percent.to_f / 100) * count)
89
+
90
+ select(&:empty?).sample(total.to_i).each &flag
91
+ end
92
+
93
+ # Move a +Tile+ on the +Map+. This movement action causes a chain
94
+ # reaction potentially moving (or killing) tiles down the map in that
95
+ # same direction.
96
+ #
97
+ # It does this by comparing each neighboring pair
98
+ # tile_1 -> tile_2
99
+ # tile_2 -> tile_3
100
+ # tile_3 -> tile_4
101
+ #
102
+ # And follows these rules
103
+ # * empty tiles don't transfer movement
104
+ # * enemies block enemies
105
+ # * players are killed when touching enemies
106
+ # * enemies get squished by players
107
+ # * players get squished by queens
108
+ # * rocks block everything
109
+ #
110
+ # @param tile [Tile]
111
+ # @param direction [Symbol] +:north+, +:south+, +:east+ or +:west+
112
+ # @return [Boolean] +true+ if the +tile+ is moved successfully, +false+ if it is not
113
+ def move(tile, direction)
114
+ tiles = send(direction, tile)
115
+ move = []
116
+
117
+ tiles.each_cons(2) do |this, that|
118
+ break if this.empty?
119
+ # break move.clear if (tile == this) && this.enemy? && that.empty?
120
+ break move.clear if this.enemy? && that.enemy?
121
+
122
+ break move.clear if this.egg? && that.enemy?
123
+ break move.clear if this.enemy? && that.egg?
124
+
125
+ break this.destroy! if this.player? && (that.enemy? || that.egg?)
126
+ break that.destroy! if (this.enemy? || this.egg?) && that.player?
127
+
128
+ if (this.worker? || this.queen? || this.egg?) && tile.player? && (that.dirt? || that.rock?)
129
+ this.destroy!
130
+ break
131
+ end
132
+
133
+ if this.soldier? && tile.player? && that.rock?
134
+ (neighbors(this) - neighbors(tile)).each &:worker!
135
+
136
+ this.destroy!
137
+ break
138
+ end
139
+
140
+ if this.player? && tile.queen? && !that.empty?
141
+ this.destroy!
142
+ break
143
+ end
144
+
145
+ break move.clear if that.rock?
146
+
147
+ move << [that, this.type, this.age]
148
+ end
149
+
150
+ return if move.empty?
151
+
152
+ tile.empty!
153
+ move.each { |t, type, age| t.send(type, age) }
154
+ end
155
+
156
+ # Find the closest route from +this+ to +that+ based only on position
157
+ # (ignoring all obstacles between +this+ and +that+).
158
+ #
159
+ # @param this [Tile] origin tile
160
+ # @param that [Tile] destination tile
161
+ # @return [Array<Symbol, ...>] +:north+, +:east+, +:west+ and/or +:south+
162
+ def aggressive_moves(this, that)
163
+ directions = []
164
+ directions << :north if (this.y > that.y) && ((this.y - that.y) < 10)
165
+ directions << :south if (that.y > this.y) && ((that.y - this.y) < 10)
166
+ directions << :west if (this.x > that.x) && ((this.x - that.x) < 10)
167
+ directions << :east if (that.x > this.x) && ((that.x - this.x) < 10)
168
+ directions
169
+ end
170
+
171
+ # Find "open" (non-blocking) moves for a +worker+ or +soldier+.
172
+ #
173
+ # @see {Map#north}
174
+ # @see {Map#south}
175
+ # @see {Map#east}
176
+ # @see {Map#west}
177
+ #
178
+ # @param tile [Tile] a +worker+ or +soldier+
179
+ # @return [Array<Symbol, ...>] +:north+, +:east+, +:west+ and/or +:south+
180
+ def available_moves(tile)
181
+ %i(north south east west).select do |direction|
182
+ _, neighbor, _ = send(direction, tile)
183
+
184
+ next if neighbor.nil?
185
+
186
+ neighbor.player? || neighbor.empty? || (tile.queen? && neighbor.dirt?)
187
+ end
188
+ end
189
+
190
+ def neighbors(tile)
191
+ [
192
+ [ 0, 1],
193
+ [ 1, 0],
194
+ [ 0,-1],
195
+ [-1, 0],
196
+ # [ 0, 0], <- self
197
+ [ 1, 1],
198
+ [-1,-1],
199
+ [ 1,-1],
200
+ [-1, 1]
201
+ ].map do |x, y|
202
+ self[tile.x + x, tile.y + y]
203
+ end
204
+ end
205
+
206
+ def update
207
+ Console.update Catalog.flush(:changed)
208
+ end
209
+
210
+ # @macro swarm.map.direction
211
+ # @see Map#column
212
+ # @see Map#row
213
+ # @param tile [Tile]
214
+ # @return [Array<Tile, ...>] +column+ or +row+ of tiles starting with +tile+ headed $0 until the end of the map
215
+
216
+ # @macro swarm.map.direction
217
+ def north(tile)
218
+ tiles = column(tile).reverse
219
+ tiles.rotate(tiles.index tile)
220
+ end
221
+
222
+ # @macro swarm.map.direction
223
+ def south(tile)
224
+ tiles = column(tile)
225
+ tiles.rotate(tiles.index tile)
226
+ end
227
+
228
+ # @macro swarm.map.direction
229
+ def east(tile)
230
+ tiles = row(tile)
231
+ tiles.rotate(tiles.index tile)
232
+ end
233
+
234
+ # @macro swarm.map.direction
235
+ def west(tile)
236
+ tiles = row(tile).reverse
237
+ tiles.rotate(tiles.index tile)
238
+ end
239
+
240
+ # @macro swarm.map.axis
241
+ # @param tile [Tile]
242
+ # @return [Array<Tile, ...>] the $0 of tiles that the +tile+ resides on
243
+
244
+ # @macro swarm.map.axis
245
+ def row(tile)
246
+ @tiles[tile.y]
247
+ end
248
+
249
+ # @macro swarm.map.axis
250
+ def column(tile)
251
+ @tiles.map { |row| row[tile.x] }
252
+ end
253
+ end
254
+ end