rofltim 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +94 -0
  3. data/bin/roflbalt +5 -0
  4. data/lib/roflbalt.rb +477 -0
  5. metadata +47 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 30444f0e7475fea054716007d5ab3253ec50e13da69c6d293691580704affac1
4
+ data.tar.gz: 776a3cc65cfcee5b4666d97ef14bc45ce7d925f014b9ce6eb777abd4ac00aa6e
5
+ SHA512:
6
+ metadata.gz: 457fcd1cc833d03b206b6dc22c04bafd05c3ff26a17e60379cee07b6281343d3248cc5bf08863a90d3527df1652cfd2824e1f9ec7dadd99ad3983d529aeeab14
7
+ data.tar.gz: 03fcde9e488cadf6470135664c66e5c11a7513e83777799167f6972caae9ef70dfc5235d3b63a6b9fbc1c6cc837cde5406c94f511ac776aaeee5c7fe84dc79a2
data/README.md ADDED
@@ -0,0 +1,94 @@
1
+ ROFLBALT
2
+ ========
3
+
4
+ A Canabalt-inspired sidescroller in ASCII (with ANSI color!) for your console.
5
+
6
+
7
+ WTF?
8
+ ----
9
+
10
+ It's built by [Paul Annesley][1] ([@pda][2])
11
+ and [Dennis Hotson][3] ([@dennishotson][4])
12
+ with < 500 lines of Ruby, no dependencies;
13
+ it just uses `print` and raw xterm-256color escape codes.
14
+
15
+ We wrote it at [Rails Camp X][5] in two days,
16
+ pair programming over SSH with a shared [tmux][6]/[vim][7] session.
17
+ As such, ~50% of the commits labelled Paul were actually Dennis!
18
+
19
+ [1]: http://paul.annesley.cc/
20
+ [2]: https://twitter.com/pda
21
+ [3]: http://dhotson.tumblr.com/
22
+ [4]: https://twitter.com/dennishotson
23
+ [5]: http://railscamps.com/#adelaide_jan_2012
24
+ [6]: http://tmux.sourceforge.net/
25
+ [7]: http://www.vim.org/
26
+
27
+
28
+ Requirements
29
+ ------------
30
+
31
+ **Ruby 1.9**. It doesn't work with Ruby 1.8, but I'm sure somebody could easily fix that...
32
+
33
+ You'll need a terminal with 256 color support and at least 120 columns by 40 rows of text.
34
+
35
+ * For Mac OS X we highly recommend [iTerm2](http://www.iterm2.com/),
36
+ but if you're running Lion (or newer?) you can use the default Terminal.app.
37
+ * For Windows, I imagine [PuTTY](http://www.chiark.greenend.org.uk/~sgtatham/putty/)
38
+ is still the thing to use.
39
+ * For GNU/Linux etc, use xterm!
40
+
41
+
42
+ Instructions
43
+ ------------
44
+
45
+ ```sh
46
+ # install:
47
+ gem install roflbalt
48
+
49
+ # using rbenv? it rocks.
50
+ rbenv rehash
51
+
52
+ # LOL
53
+ roflbalt
54
+ ```
55
+
56
+ Press any key to jump! If you can't find the any key, try the spacebar.
57
+
58
+ And of course, ctrl-c to exit.
59
+
60
+ If your terminal isn't quite right afterwards, try running `reset` to get it back to normal.
61
+ There's [an issue open](https://github.com/pda/roflbalt/issues/2) for this.
62
+
63
+
64
+ "Screenshot"
65
+ ------------
66
+
67
+ (or [check out the video](http://www.youtube.com/watch?v=VoHmJfXqwbM))
68
+
69
+ Score: 23432
70
+
71
+ ROFL:ROFL:LoL:ROFL:ROFL
72
+ O/ L ____|__
73
+ /| O ===` []\
74
+ / > L \________]
75
+ .__|____|__/
76
+
77
+ ==========================================
78
+ ::::::::::::::::::::::::::::::::::::::::::
79
+ ::: :: :: :: ::
80
+ ::: :: :: :: ::
81
+ ==================== ::::::::::::::::::::::::::::::::::::::::::
82
+ ::::::::::::::::::::: ::: :: :: :: ::
83
+ :: :: :: ::: :: :: :: ::
84
+ :: :: :: ::::::::::::::::::::::::::::::::::::::::::
85
+ ::::::::::::::::::::: ::: :: :: :: ::
86
+ :: :: :: ::: :: :: :: ::
87
+
88
+
89
+ License
90
+ -------
91
+
92
+ (c) 2012 Dennis Hotson, Paul Annesley
93
+
94
+ 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
+ ' :TiM:TIM:TIM ',
445
+ ' T ____|__ ',
446
+ ' I ===` []\ ',
447
+ ' M \________] ',
448
+ ' .__|____|__/ ',
449
+ ],
450
+ [
451
+ ' TIM:TIM:tIm: ',
452
+ ' ____|__ ',
453
+ ' TIM===` []\ ',
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,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rofltim
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Annesley
8
+ - Dennis Hotson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2022-07-28 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Tim edition of an ASCII side-scrolling game, with ANSI color!
15
+ email:
16
+ - paul@annesley.cc
17
+ executables:
18
+ - roflbalt
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - README.md
23
+ - bin/roflbalt
24
+ - lib/roflbalt.rb
25
+ homepage: https://github.com/pda/roflbalt
26
+ licenses: []
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubygems_version: 3.0.3.1
44
+ signing_key:
45
+ specification_version: 4
46
+ summary: Canabalt-inspired ASCII side-scroller for your terminal, with ANSI color!
47
+ test_files: []