roflbalt 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README.md +54 -0
  2. data/bin/roflbalt +5 -0
  3. data/lib/roflbalt.rb +477 -0
  4. metadata +50 -0
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ ROFLBALT
2
+ ========
3
+
4
+ A Canabalt-inspired sidescroller in ASCII (with ANSI color!) for your console.
5
+
6
+ Run it in Terminal.app or iTerm.app with xterm-256color.
7
+
8
+ Instructions
9
+ ------------
10
+
11
+ With Ruby 1.9 installed, run:
12
+
13
+ cd roflbalt
14
+ ./bin/roflbalt
15
+
16
+
17
+ "Screenshot"
18
+ ------------
19
+
20
+ Score: 23432
21
+
22
+ ROFL:ROFL:LoL:ROFL:ROFL
23
+ O/ L ____|__
24
+ /| O ===` []\
25
+ / > L \________]
26
+ .__|____|__/
27
+
28
+ ==========================================
29
+ ::::::::::::::::::::::::::::::::::::::::::
30
+ ::: :: :: :: ::
31
+ ::: :: :: :: ::
32
+ ==================== ::::::::::::::::::::::::::::::::::::::::::
33
+ ::::::::::::::::::::: ::: :: :: :: ::
34
+ :: :: :: ::: :: :: :: ::
35
+ :: :: :: ::::::::::::::::::::::::::::::::::::::::::
36
+ ::::::::::::::::::::: ::: :: :: :: ::
37
+ :: :: :: ::: :: :: :: ::
38
+
39
+ WTF?
40
+ ----
41
+
42
+ This was made by [Dennis Hotson][1] and [Paul Annesley][2] at [Rails Camp X][3] in Adelaide.
43
+
44
+ [1]: http://dhotson.tumblr.com/
45
+ [2]: http://paul.annesley.cc/
46
+ [3]: http://railscamps.com/
47
+
48
+
49
+ License
50
+ -------
51
+
52
+ (c) 2012 Dennis Hotson, Paul Annesley
53
+
54
+ Open source: MIT license.
data/bin/roflbalt ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "roflbalt"
4
+
5
+ Game.new.run
data/lib/roflbalt.rb ADDED
@@ -0,0 +1,477 @@
1
+ # encoding: utf-8
2
+
3
+ SCREEN_WIDTH = 120
4
+ SCREEN_HEIGHT = 40
5
+
6
+ class Game
7
+ def initialize
8
+ reset
9
+ end
10
+ def reset
11
+ @run = true
12
+ @world = World.new(SCREEN_WIDTH)
13
+ @screen = Screen.new(SCREEN_WIDTH, SCREEN_HEIGHT, @world)
14
+ end
15
+ def run
16
+ Signal.trap(:INT) do
17
+ @run = false
18
+ end
19
+ while @run
20
+ start_time = Time.new.to_f
21
+ unless @world.tick
22
+ reset
23
+ end
24
+ render start_time
25
+ end
26
+ on_exit
27
+ end
28
+ def render start_time
29
+ @world.buildings.each do |building|
30
+ @screen.draw(building)
31
+ end
32
+ @screen.draw(@world.player)
33
+ @world.misc.each do |object|
34
+ @screen.draw(object)
35
+ end
36
+ @screen.render start_time
37
+ end
38
+ def on_exit
39
+ @screen.on_exit
40
+ end
41
+ end
42
+
43
+ class Screen
44
+ OFFSET = -20
45
+ def initialize width, height, world
46
+ @width = width
47
+ @height = height
48
+ @world = world
49
+ @background = world.background
50
+ create_frame_buffer
51
+ %x{stty -icanon -echo}
52
+ print "\033[0m" # reset
53
+ print "\033[2J" # clear screen
54
+ print "\x1B[?25l" # disable cursor
55
+ end
56
+ attr_reader :width, :height, :world
57
+ def create_frame_buffer
58
+ @fb = Framebuffer.new @background
59
+ end
60
+ def draw renderable
61
+ renderable.each_pixel(world.ticks) do |x, y, pixel|
62
+ @fb.set x, y, pixel
63
+ end
64
+ end
65
+ def render start_time
66
+ print "\e[H"
67
+ buffer = ''
68
+ previous_pixel = nil
69
+ (0...height).each do |y|
70
+ (OFFSET...(width + OFFSET)).each do |x|
71
+ pixel = @fb.get(x, y)
72
+ if Pixel === previous_pixel && Pixel === pixel && pixel.color_equal?(previous_pixel)
73
+ buffer << pixel.char
74
+ else
75
+ buffer << pixel.to_s
76
+ end
77
+ previous_pixel = pixel
78
+ end
79
+ buffer << "\n"
80
+ end
81
+ print "\033[0m"
82
+
83
+ dt = Time.new.to_f - start_time;
84
+ target_time = 0.04
85
+ sleep target_time - dt if dt < target_time
86
+
87
+ print buffer
88
+ create_frame_buffer
89
+ end
90
+ def on_exit
91
+ print "\033[0m" # reset colours
92
+ print "\x1B[?25h" # re-enable cursor
93
+ print "\n"
94
+ end
95
+ end
96
+
97
+ class Pixel
98
+ def initialize char = " ", fg = nil, bg = nil
99
+ @char = char
100
+ @fg, @bg = fg, bg
101
+ end
102
+ attr_reader :char
103
+ def fg; @fg || 255 end
104
+ def bg; @bg || 0 end
105
+ def to_s
106
+ "\033[48;5;%dm\033[38;5;%dm%s" % [ bg, fg, @char ]
107
+ end
108
+ def color_equal? other
109
+ fg == other.fg && bg == other.bg
110
+ end
111
+ end
112
+
113
+ class Background
114
+ PALETTE = [ 16, 232, 233 ]
115
+ PERIOD = 16.0
116
+ SPEED = 0.5
117
+ BLOCKINESS = 10.0
118
+ def initialize world
119
+ @world = world
120
+ end
121
+ def pixel x, y, char = " "
122
+ Pixel.new char, 0, color(x, y)
123
+ end
124
+ def color x, y
125
+ y = (y / BLOCKINESS).round * BLOCKINESS
126
+ sin = Math.sin((x + @world.distance.to_f * SPEED) / PERIOD + y / PERIOD)
127
+ PALETTE[(0.9 * sin + 0.9).round]
128
+ end
129
+ end
130
+
131
+ class WindowColor
132
+ PALETTE = [ 16, 60 ]
133
+ PERIOD = 6.0
134
+ def pixel x, y, char = " "
135
+ Pixel.new char, 0, color(x, y)
136
+ end
137
+ def color x, y
138
+ sin = Math.sin(x / PERIOD + y / (PERIOD * 0.5))
139
+ PALETTE[(0.256 * sin + 0.256).round]
140
+ end
141
+ end
142
+
143
+ class Framebuffer
144
+ def initialize background
145
+ @pixels = Hash.new { |h, k| h[k] = {} }
146
+ @background = background
147
+ end
148
+ def set x, y, pixel
149
+ @pixels[x][y] = pixel
150
+ end
151
+ def get x, y
152
+ @pixels[x][y] || @background.pixel(x, y)
153
+ end
154
+ def size
155
+ @pixels.values.reduce(0) { |a, v| a + v.size }
156
+ end
157
+ end
158
+
159
+ class World
160
+ def initialize horizon
161
+ @ticks = 0
162
+ @horizon = horizon
163
+ @building_generator = BuildingGenerator.new(self, WindowColor.new)
164
+ @background = Background.new(self)
165
+ @player = Player.new(25, @background)
166
+ @buildings = [ @building_generator.build(-10, 30, 120) ]
167
+ @misc = [ Scoreboard.new(self), RoflCopter.new(50, 4, @background) ]
168
+ @speed = 4
169
+ @distance = 0
170
+ end
171
+ attr_reader :buildings, :player, :horizon, :speed, :misc, :ticks, :distance, :background
172
+ def tick
173
+ # TODO: this, but less often.
174
+ if @ticks % 20 == 0
175
+ @building_generator.generate_if_necessary
176
+ @building_generator.destroy_if_necessary
177
+ end
178
+
179
+ @distance += speed
180
+
181
+ buildings.each do |b|
182
+ b.move_left speed
183
+ end
184
+
185
+ if b = building_under_player
186
+ if player.bottom_y > b.y
187
+ b.move_left(-speed)
188
+ @speed = 0
189
+ @misc << Blood.new(player.x, player.y)
190
+ @misc << GameOverBanner.new
191
+ player.die!
192
+ end
193
+ end
194
+
195
+ begin
196
+ if STDIN.read_nonblock(1)
197
+ if player.dead?
198
+ return false
199
+ else
200
+ player.jump
201
+ end
202
+ end
203
+ rescue Errno::EAGAIN
204
+ end
205
+
206
+ player.tick
207
+
208
+ if b = building_under_player
209
+ player.walk_on_building b if player.bottom_y >= b.y
210
+ end
211
+
212
+ @ticks += 1
213
+ end
214
+ def building_under_player
215
+ buildings.detect do |b|
216
+ b.x <= player.x && b.right_x >= player.right_x
217
+ end
218
+ end
219
+ end
220
+
221
+ class BuildingGenerator
222
+ def initialize world, background
223
+ @world = world
224
+ @background = background
225
+ end
226
+ def destroy_if_necessary
227
+ while @world.buildings.any? && @world.buildings.first.x < -100
228
+ @world.buildings.shift
229
+ end
230
+ end
231
+ def generate_if_necessary
232
+ while (b = @world.buildings.last).x < @world.horizon
233
+ @world.buildings << build(
234
+ b.right_x + minimium_gap + rand(24),
235
+ next_y(b),
236
+ rand(40) + 40
237
+ )
238
+ end
239
+ end
240
+ def minimium_gap; 16 end
241
+ def maximum_height_delta; 10 end
242
+ def minimum_height_clearance; 12; end
243
+ def next_y previous_building
244
+ p = previous_building
245
+ delta = 0
246
+ while delta.abs <= 1
247
+ delta = maximum_height_delta * -1 + rand(2 * maximum_height_delta + 1)
248
+ end
249
+ [25, [previous_building.y - delta, minimum_height_clearance].max].min
250
+ end
251
+ def build x, y, width
252
+ Building.new x, y, width, @background
253
+ end
254
+ end
255
+
256
+ module Renderable
257
+ def each_pixel ticks
258
+ (y...(y + height)).each do |y|
259
+ (x...(x + width)).each do |x|
260
+ rx = x - self.x
261
+ ry = y - self.y
262
+ yield x, y, pixel(x, y, rx, ry, ticks)
263
+ end
264
+ end
265
+ end
266
+ def right_x; x + width end
267
+ end
268
+
269
+ class Building
270
+ include Renderable
271
+ def initialize x, y, width, background
272
+ @x, @y = x, y
273
+ @width = width
274
+ @background = background
275
+ @period = rand(4) + 6
276
+ @window_width = @period - rand(2) - 1
277
+ @color = (235..238).to_a.shuffle.first # Ruby 1.8
278
+ @top_color = @color + 4
279
+ @left_color = @color + 2
280
+ end
281
+ attr_reader :x, :y, :width
282
+ def move_left distance
283
+ @x -= distance
284
+ end
285
+ def height; SCREEN_HEIGHT - @y end
286
+ def pixel x, y, rx, ry, ticks
287
+ if ry == 0
288
+ if rx == width - 1
289
+ Pixel.new " "
290
+ else
291
+ Pixel.new "=", 234, @top_color
292
+ end
293
+ elsif rx == 0 || rx == 1
294
+ Pixel.new ":", @left_color + 1, @left_color
295
+ elsif rx == 2
296
+ Pixel.new ":", 236, 236
297
+ elsif rx == width - 1
298
+ Pixel.new ":", 236, 236
299
+ else
300
+ if rx % @period >= @period - @window_width && ry % 5 >= 2
301
+ Pixel.new(" ", 255, @background.color(rx + x/2, ry))
302
+ else
303
+ Pixel.new(":", 235, @color)
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ class Player
310
+ include Renderable
311
+ def initialize y, background
312
+ @y = y
313
+ @background = background
314
+ @velocity = 1
315
+ @walking = false
316
+ end
317
+ def x; 0; end
318
+ def width; 3 end
319
+ def height; 3 end
320
+ def pixel x, y, rx, ry, ticks
321
+ Pixel.new char(rx, ry, ticks), 255, @background.color(x, y)
322
+ end
323
+
324
+ def char rx, ry, ticks
325
+ if dead?
326
+ [
327
+ ' @ ',
328
+ '\+/',
329
+ ' \\\\',
330
+ ][ry][rx]
331
+ elsif !@walking
332
+ [
333
+ ' O/',
334
+ '/| ',
335
+ '/ >',
336
+ ][ry][rx]
337
+ else
338
+ [
339
+ [
340
+ ' O ',
341
+ '/|v',
342
+ '/ >',
343
+ ],
344
+ [
345
+ ' 0 ',
346
+ ',|\\',
347
+ ' >\\',
348
+ ],
349
+ ][ticks / 4 % 2][ry][rx]
350
+ end
351
+ end
352
+ def acceleration
353
+ if @dead
354
+ 0.05
355
+ else
356
+ 0.35
357
+ end
358
+ end
359
+ def tick
360
+ @y += @velocity
361
+ @velocity += acceleration
362
+ @walking = false
363
+ end
364
+ def y; @y.round end
365
+ def bottom_y; y + height end
366
+ def walk_on_building b
367
+ @y = b.y - height
368
+ @velocity = 0
369
+ @walking = true
370
+ end
371
+ def jump
372
+ jump! if @walking
373
+ end
374
+ def jump!
375
+ @velocity = -2.5
376
+ end
377
+ def die!
378
+ @dead = true
379
+ @velocity = 0
380
+ end
381
+ def dead?
382
+ @dead
383
+ end
384
+ end
385
+
386
+ class Blood < Struct.new(:x, :y)
387
+ include Renderable
388
+ def height; 4 end
389
+ def width; 2 end
390
+ def x; super + 2 end
391
+ def pixel x, y, rx, ry, ticks
392
+ Pixel.new ":", 124, 52
393
+ end
394
+ end
395
+
396
+ class Scoreboard
397
+ include Renderable
398
+ def initialize world
399
+ @world = world
400
+ end
401
+ def height; 3 end
402
+ def width; 20 end
403
+ def x; -18 end
404
+ def y; 1 end
405
+ def template
406
+ [
407
+ ' ',
408
+ ' Score: %9s ' % [ @world.distance],
409
+ ' '
410
+ ]
411
+ end
412
+ def pixel x, y, rx, ry, ticks
413
+ Pixel.new template[ry][rx], 244, 234
414
+ end
415
+ end
416
+
417
+ class GameOverBanner
418
+ FG = 16
419
+ BG = 244
420
+ include Renderable
421
+ def x; 28 end
422
+ def y; 14 end
423
+ def width; 28 end
424
+ def height; 3 end
425
+ def template
426
+ [
427
+ ' ',
428
+ ' YOU DIED. LOL. ',
429
+ ' ',
430
+ ]
431
+ end
432
+ def pixel x, y, rx, ry, ticks
433
+ Pixel.new template[ry][rx], FG, BG
434
+ end
435
+ end
436
+
437
+ class RoflCopter
438
+ include Renderable
439
+ def initialize x, y, background
440
+ @x, @y = x, y
441
+ @background = background
442
+ @frames = [
443
+ [
444
+ ' :LoL:ROFL:ROFL',
445
+ ' L ____|__ ',
446
+ ' O ===` []\ ',
447
+ ' L \________] ',
448
+ ' .__|____|__/ ',
449
+ ],
450
+ [
451
+ ' ROFL:ROFL:LoL: ',
452
+ ' ____|__ ',
453
+ ' LOL===` []\ ',
454
+ ' \________] ',
455
+ ' .__|____|__/ ',
456
+ ],
457
+ ]
458
+ end
459
+ def width; 24 end
460
+ def height; 5 end
461
+ def y
462
+ range = 1.5
463
+ @y + (range * Math.sin(Time.new.to_f * 1.5)).round
464
+ end
465
+ def x
466
+ range = 20
467
+ @x + (range * Math.sin(Time.new.to_f * 0.7)).round
468
+ end
469
+ def pixel x, y, rx, ry, ticks
470
+ Pixel.new char(rx, ry, ticks), 246, @background.color(x, y)
471
+ end
472
+ def char rx, ry, ticks
473
+ @frames[ticks % 2][ry][rx] || " "
474
+ rescue
475
+ " " # Roflcopter crashes from time to time..
476
+ end
477
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roflbalt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Paul Annesley
9
+ - Dennis Hotson
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-01-18 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: ASCII side-scrolling game, with ANSI color!
16
+ email:
17
+ - paul@annesley.cc
18
+ executables:
19
+ - roflbalt
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - bin/roflbalt
24
+ - lib/roflbalt.rb
25
+ - README.md
26
+ homepage: https://github.com/pda/roflbalt
27
+ licenses: []
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 1.8.11
47
+ signing_key:
48
+ specification_version: 3
49
+ summary: Canabalt-inspired ASCII side-scroller for your terminal, with ANSI color!
50
+ test_files: []