rofltim 1.0.0

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 (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: []