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,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; &quot;Swarm&quot; Source Code Documentation
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Swarm.html" title="Swarm (module)">Swarm</a></span>
86
+
87
+
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Wed Aug 30 07:17:58 2017 by
104
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.9 (ruby-2.4.1).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
data/lib/swarm.rb ADDED
@@ -0,0 +1,60 @@
1
+ require 'curses'
2
+ require 'io/console'
3
+
4
+ $LOAD_PATH.unshift __dir__
5
+
6
+ require 'swarm/console'
7
+ require 'swarm/game'
8
+ require 'swarm/map'
9
+ require 'swarm/catalog'
10
+
11
+ require 'swarm/tile'
12
+
13
+ require 'swarm/level'
14
+ require 'swarm/levels/intro'
15
+ require 'swarm/levels/one'
16
+ require 'swarm/levels/two'
17
+ require 'swarm/levels/three'
18
+ require 'swarm/levels/four'
19
+ require 'swarm/levels/five'
20
+
21
+ require 'swarm/version'
22
+
23
+ # Namespace for classes and modules of the +Swarm+ application.
24
+ module Swarm
25
+
26
+ # Initialize the console, launch the game, play the game, print the
27
+ # scoreboard and exit to the shell.
28
+ #
29
+ # @return [Kernel#exit] exit to shell with success/failure status code
30
+ def self.start
31
+ Signal.trap('INT') { game.stop } # Ctrl-C stops the game gracefully
32
+
33
+ game = Game.new
34
+
35
+ Console.open do
36
+
37
+ game.start
38
+
39
+ Level.each do |level|
40
+ next if game.over?
41
+
42
+ game.setup! level
43
+ game.show!
44
+ game.play! until game.over? || level.over?
45
+ end
46
+
47
+ game.stop
48
+ end
49
+
50
+ puts game
51
+
52
+ exit true # success!
53
+ rescue => error
54
+ Console.close
55
+
56
+ warn 'Swarm encountered an unhandled error'
57
+ warn error.message, *error.backtrace
58
+ exit false # failure!
59
+ end
60
+ end
@@ -0,0 +1,71 @@
1
+ module Swarm
2
+ class Catalog
3
+
4
+ class << self
5
+
6
+ def count(key)
7
+ instance.synchronize { |categories| categories[key].count }
8
+ end
9
+
10
+ def fetch(key, value, default = nil)
11
+ instance.synchronize { |categories| categories[key].fetch value, default }
12
+ end
13
+
14
+ def increment(key, value)
15
+ instance.synchronize do |categories|
16
+ categories[key][value] ||= 0
17
+ categories[key][value] += 1
18
+ end
19
+ end
20
+
21
+ def store(key, value)
22
+ instance.synchronize { |categories| categories[key].store value, true }
23
+ end
24
+
25
+ def delete(key, value)
26
+ instance.synchronize { |categories| categories[key].delete value }
27
+ end
28
+
29
+ def flush(key, fetch: :keys)
30
+ instance.synchronize do |categories|
31
+ result = categories.delete(key)
32
+ result ||= Hash.new
33
+ result.send fetch
34
+ end
35
+ end
36
+
37
+ def select(*keys, fetch: :keys)
38
+ instance.synchronize do |categories|
39
+ keys.flat_map { |key| categories[key].send fetch }
40
+ end
41
+ end
42
+
43
+ def clear(key)
44
+ instance.synchronize { |categories| categories[key].clear }
45
+ end
46
+
47
+ def instance
48
+ @instance ||= new
49
+ end
50
+
51
+ private :new,
52
+ :instance
53
+ end
54
+
55
+ def synchronize
56
+ yield categories
57
+ # transaction.synchronize { yield categories }
58
+ end
59
+
60
+ private
61
+
62
+ def transaction
63
+ @transaction ||= Mutex.new
64
+ end
65
+
66
+ def categories
67
+ @categories ||= Hash.new { |h, k| h[k] = Hash.new }
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,165 @@
1
+ module Swarm
2
+
3
+ module Console
4
+
5
+ module Color
6
+ BLACK = 234
7
+ SILVER = 240
8
+ WHITE = 248
9
+ GREY = 237
10
+ RED = 167
11
+ PURPLE = 104
12
+ GREEN = 120
13
+ BLUE = 39
14
+ YELLOW = 186
15
+ end
16
+
17
+ module NormalKey
18
+ def up; @up ||= Curses::Key::UP end
19
+ def down; @down ||= Curses::Key::DOWN end
20
+ def left; @left ||= Curses::Key::LEFT end
21
+ def right; @right ||= Curses::Key::RIGHT end
22
+
23
+ def pause; @pause ||= ?\s.freeze end
24
+ def quit; @quit ||= ?q.freeze end
25
+ end
26
+
27
+ module VIKey
28
+ def up; @up ||= ?k.freeze end
29
+ def down; @down ||= ?j.freeze end
30
+ def left; @left ||= ?h.freeze end
31
+ def right; @right ||= ?l.freeze end
32
+
33
+ def pause; @pause ||= ?\s.freeze end
34
+ def quit; @quit ||= ?q.freeze end
35
+ end
36
+
37
+ include Curses
38
+ include Color
39
+
40
+ class PopUp < Window
41
+
42
+ def initialize(message, indent: 2)
43
+ lines = message.split(?\n)
44
+ height = 2 + lines.count
45
+ max = lines.map(&:length).max
46
+ width = max + (indent*2)
47
+
48
+ super height,
49
+ width,
50
+ (Console.height/2) - (height/2),
51
+ (Console.width/2) - (max/2) - indent
52
+
53
+ box ?|, ?-, ?+
54
+
55
+ lines.each.with_index do |line, index|
56
+ setpos (index + 1), 2
57
+ addstr line.center(max)
58
+ end
59
+ end
60
+ end
61
+
62
+ attr_reader :key_info
63
+
64
+ extend self
65
+
66
+ def init_keys
67
+ nonl
68
+ noecho # don't echo keypresses
69
+ stdscr.nodelay = -1 # don't wait for input, just listen for it
70
+ stdscr.keypad true
71
+
72
+ @key_info = {pause: '<SPACEBAR>', quit: ?q}
73
+
74
+ if ENV.fetch('SWARM', 'VIM_MODE') == 'easy'
75
+ extend NormalKey
76
+
77
+ @key_info.merge! north: '<UP>', south: '<DOWN>', west: '<LEFT>', east: '<RIGHT>'
78
+ else
79
+ extend VIKey
80
+
81
+ @key_info.merge! north: ?k, south: ?j, west: ?h, east: ?l
82
+ end
83
+ end
84
+
85
+ def init_colors
86
+ start_color
87
+ use_default_colors
88
+
89
+ #id #fg #bg
90
+ init_pair BLACK, BLACK, BLACK
91
+ init_pair SILVER, SILVER, SILVER
92
+ init_pair WHITE, WHITE, WHITE
93
+ init_pair GREY, GREY, GREY
94
+ init_pair RED, RED, BLACK
95
+ init_pair PURPLE, PURPLE, BLACK
96
+ init_pair GREEN, GREEN, BLACK
97
+ init_pair BLUE, BLUE, BLACK
98
+ init_pair YELLOW, YELLOW, BLACK
99
+ end
100
+
101
+ # @yieldparam command [String] yields +command+ on keypress
102
+ def keypress
103
+ command = getch and yield(command)
104
+ end
105
+
106
+ # @macro swarm.console.dimensions
107
+ # @return [Integer] +$0+ of console
108
+
109
+ # @macro swarm.console.dimensions
110
+ def width
111
+ @width ||= cols
112
+ end
113
+
114
+ # @macro swarm.console.dimensions
115
+ def height
116
+ @height ||= lines
117
+ end
118
+
119
+ # Redraw all +tiles+ given and refresh the console
120
+ # @param tiles [Array<Tile, ...>]
121
+ def update(tiles)
122
+ tiles.each &method(:draw)
123
+ refresh
124
+ end
125
+
126
+ # @param color [Integer] 0 to 8
127
+ # @param x [Integer]
128
+ # @param y [Integer]
129
+ def draw(tile)
130
+ setpos *tile.location
131
+
132
+ attron(color_pair tile.color) { addstr tile.icon }
133
+ end
134
+
135
+ def wipe
136
+ clear
137
+ refresh
138
+ end
139
+
140
+ # Turn line buffering on and close the +Curses+ session.
141
+ def close
142
+ return if closed?
143
+
144
+ nocbreak
145
+ close_screen
146
+ end
147
+
148
+ # Initialize the +Curses+ environment, configure it and then clean up
149
+ # when the caller is done. 9 different bg/fg pairs are initialized for
150
+ # the colored +Tile+ squares (0-8): (black, red, green, yellow, blue,
151
+ # magenta, cyan, white, grey)
152
+ def open
153
+ init_screen
154
+ init_colors
155
+ init_keys
156
+
157
+ curs_set 0 # hide the cursor
158
+ cbreak # turn off line buffering
159
+
160
+ yield
161
+
162
+ close
163
+ end
164
+ end
165
+ end
data/lib/swarm/game.rb ADDED
@@ -0,0 +1,173 @@
1
+ module Swarm
2
+
3
+ class Game
4
+
5
+ REFRESH_RATE = 0.05 # This seems to keep the laptop under 750°.
6
+
7
+ def play!
8
+ turn = Time.now.to_f
9
+
10
+ @player = @level.find_player
11
+ @player ||= spawn_player
12
+
13
+ listen_for_keypress
14
+
15
+ unless @popup
16
+ if (turn - @turn) >= @rate
17
+
18
+ @level.play
19
+
20
+ @turn = turn
21
+ end
22
+
23
+ @level.update
24
+ end
25
+
26
+ sleep REFRESH_RATE
27
+ end
28
+
29
+ # Set the "start game" flag.
30
+ #
31
+ # @return [true]
32
+ def start
33
+ @status = true
34
+
35
+ true
36
+ end
37
+
38
+ # Set the "stop game" flag.
39
+ #
40
+ # @return [true]
41
+ def stop
42
+ @status = false
43
+
44
+ true
45
+ end
46
+
47
+ # @return [Boolean]
48
+ def stopped?
49
+ !@status
50
+ end
51
+
52
+ # A game is "over" when any of these conditions are met
53
+ #
54
+ # - the game has been stopped via {Game#stop}
55
+ # - all of the player's lives are gone
56
+ # - all of the invaders are dead
57
+ #
58
+ # @return [Boolean]
59
+ def over?
60
+ stopped? || @players.zero?
61
+ end
62
+
63
+ # @return [String]
64
+ def to_s
65
+ <<-TO_S.gsub(/^[ ]{8}/, '') % statistics
66
+ GAME OVER!
67
+
68
+ + %<eggs>4i x oo (eggs) squished (x20)
69
+ + %<workers>4i x ├┤ (workers) killed (x20)
70
+ + %<soldiers>4i x ╟╢ (soldiers) crushed (x50)
71
+ + %<queens>4i x ╬╬ (queens) defeated (x50)
72
+
73
+ - %<deaths>4i x ◄▶ (players) destroyed (x100)
74
+ -------------------------------
75
+ %<points>6i points
76
+ TO_S
77
+ end
78
+
79
+ def pause
80
+ if @popup
81
+ @popup.close
82
+ @popup = false
83
+
84
+ @level.update!
85
+ else
86
+ @popup = Console::PopUp.new(yield)
87
+ @popup.refresh
88
+ end
89
+ end
90
+
91
+ def setup!(level)
92
+ @level = level
93
+ @level.define_singleton_method(:pause, &method(:pause))
94
+
95
+ @popup = false
96
+
97
+ @status = true
98
+ @turn = Time.now.to_f
99
+
100
+ @level.setup
101
+ end
102
+
103
+ def show!
104
+ @level.update # update the level and refresh the console before displaying anything
105
+
106
+ @level.show(@players)
107
+ end
108
+
109
+ private
110
+
111
+ # @return [Hash]
112
+ def statistics
113
+ Hash.new(0).tap do |stats|
114
+ stats[:eggs] += Catalog.fetch(:destroyed, :egg, 0)
115
+ stats[:workers] += Catalog.fetch(:destroyed, :worker, 0)
116
+ stats[:soldiers] += Catalog.fetch(:destroyed, :soldier, 0)
117
+ stats[:queens] += Catalog.fetch(:destroyed, :queen, 0)
118
+ stats[:deaths] += Catalog.fetch(:destroyed, :player, 0)
119
+ stats[:points] +=
120
+ (stats[:eggs] * 20) +
121
+ (stats[:workers] * 20) +
122
+ (stats[:soldiers] * 50) +
123
+ (stats[:queens] * 50) -
124
+ (stats[:deaths] * 100)
125
+ end
126
+ end
127
+
128
+ # # @return [Tile]
129
+ # def find_or_spawn_player
130
+ # find_player || spawn_player
131
+ # end
132
+
133
+ # def find_player
134
+ # @player = @level.find_player
135
+ # end
136
+
137
+ def spawn_player
138
+ @players -= 1
139
+
140
+ pause do
141
+ <<-POPUP.gsub(/[ ]{10}/, '') % Console.key_info
142
+ YOU HAVE BEEN KILLED!
143
+ ----------------------------------------------
144
+ Press %<pause>s to continue.
145
+
146
+ (#@players lives remaining)
147
+ POPUP
148
+ end
149
+
150
+ @player = @level.spawn_player
151
+ end
152
+
153
+ def listen_for_keypress
154
+ Console.keypress do |command|
155
+ case command
156
+ when Console.up; @popup || @level.move_player(:north)
157
+ when Console.down; @popup || @level.move_player(:south)
158
+ when Console.left; @popup || @level.move_player(:west)
159
+ when Console.right; @popup || @level.move_player(:east)
160
+ when Console.pause; pause { 'PAUSED' }
161
+ when Console.quit; stop
162
+ end
163
+ STDIN.iflush # prevent command queueing
164
+ end
165
+ end
166
+
167
+ def initialize
168
+ @players = 5
169
+
170
+ @rate = 2.0
171
+ end
172
+ end
173
+ end