Skip to content

Instantly share code, notes, and snippets.

@alexdantas
Created September 6, 2013 03:15
Show Gist options
  • Save alexdantas/6459071 to your computer and use it in GitHub Desktop.
Save alexdantas/6459071 to your computer and use it in GitHub Desktop.
Shows a nice screensaver on the terminal with Ruby Curses.
#!/usr/bin/env ruby
#
# pipes.rb: Displays a nice animation on the terminal,
# based on an old screensaver.
#
# If you want to see some action, scroll to the bottom.
# The main code is between "begin" and "end".
#
# This code uses Ruby and it's Curses module to display
# characters on the terminal.
#
# I've tried to keep the code clean but it's a mess by
# now. Hey, I was excited when coding, don't blame me.
#
# This code is DEFINITELY not Rubyish. Looks like a messed
# up C/C++ cousing. Maybe that creepy uncle who shows up
# at birthday parties.
require 'curses' # Awesome terminal-handling library
# Since Ruby doesn't natively supports "enums", I've
# used a lot of labels (those things with ':')
# If you find a better one, please tell me (eu@alexdantas.net)
:direction_left
:direction_right
:direction_up
:direction_down
# Represents a single pipe, that will scroll around the
# screen, leaving a colourful trail behind.
class Pipe
# It's public components.
attr_reader :x, :y, :behaviour
# All possible colors that a pipe can have.
Colors = {
:black => 1,
:white => 2,
:red => 3,
:yellow => 4,
:magenta => 5,
:blue => 6,
:green => 7,
:cyan => 8
}.freeze
# The possible ways a pipe can behave.
# Nice little feature, right?
Behaviour = [:dumb, # Randomly decide where to go
:line, # Keep making long lines
:curvy] # Make a lot of curves
# Constructor, creates a pipe.
def initialize(x, y, behaviour = :dumb, color = Colors[:cyan])
@x = x
@y = y
@appearance = '-'
@previous_dir = :direction_right
@current_dir = :direction_right
@color = Curses::color_pair color
@behaviour = behaviour
end
# Shows the pipe on the screen.
# This is VERY UGLY, DAMn.
def print
# If we're changing direction it's best to use '+' instead
# of '-' or '|'
if @current_dir == :direction_right or @current_dir == :direction_left
if (@previous_dir == :direction_up or @previous_dir == :direction_down)
@appearance = '+'
else
@appearance = '-'
end
elsif @current_dir == :direction_up or @current_dir == :direction_down
if (@previous_dir == :direction_left or @previous_dir == :direction_right)
@appearance = '+'
else
@appearance = '|'
end
end
Curses::setpos(@y, @x)
Curses::attrset @color
Curses::addstr @appearance
Curses::refresh
end
# Refreshes the pipe's direction.
# If `change_direction` is true, forces the pipe to
# do it.
# If not, it will keep on it's current way.
def refresh_direction(change_direction)
dir = @current_dir
if change_direction
begin
# Getting a random direction
result = Random.new.rand(1..4)
if result == 1; dir = :direction_left
elsif result == 2; dir = :direction_up
elsif result == 3; dir = :direction_right
else dir = :direction_down
end
end while not is_valid_movement? dir
end
@previous_dir = @current_dir
@current_dir = dir
end
# Tells if we can move the cursor to the next direction.
# Invalid movements will be returning 180o.
def is_valid_movement? dir
if (@current_dir == :direction_right and dir == :direction_left) or
(@current_dir == :direction_left and dir == :direction_right) or
(@current_dir == :direction_up and dir == :direction_down) or
(@current_dir == :direction_down and dir == :direction_up)
return false
else
return true
end
end
# Actually steps the terminal a position on the screen,
# based on it's internal directions.
def move
if @current_dir == :direction_right; @x += 1
elsif @current_dir == :direction_left; @x -= 1
elsif @current_dir == :direction_up; @y -= 1
elsif @current_dir == :direction_down; @y += 1
end
out_of_screen = false
if @x < 0
@x = Curses::cols - 1
out_of_screen = true
elsif @x > Curses::cols - 1
@x = 0
out_of_screen = true
end
if @y < 0
@y = Curses::lines - 1
out_of_screen = true
elsif @y > Curses::lines - 1
@y = 0
out_of_screen = true
end
self.change_color if out_of_screen
end
# Randomly changes the pipe's color.
def change_color
result = Random.new.rand(Colors[:red]..Colors[:cyan]) # first..last
@color = Curses::color_pair result
# Randomly choosing between normal and bold
@color = @color | Curses::A_BOLD if random_bool
end
# Updates all the pipe's internal stuff.
def update
if @behaviour == :dumb
if random_bool
self.refresh_direction true
else
self.refresh_direction false
end
elsif @behaviour == :line
if random_bool_with_chance 0.2
self.refresh_direction true
else
self.refresh_direction false
end
elsif @behaviour == :curvy
if random_bool_with_chance 0.8
self.refresh_direction true
else
self.refresh_direction false
end
end
self.print
self.move
end
end
# Initializes the curses engine.
# ugly function is ugly D:
def curses_init timeout
Curses::init_screen
Curses::start_color
Curses::init_pair(Pipe::Colors[:black], Curses::COLOR_BLACK, Curses::COLOR_BLACK)
Curses::init_pair(Pipe::Colors[:white], Curses::COLOR_WHITE, Curses::COLOR_BLACK)
Curses::init_pair(Pipe::Colors[:red], Curses::COLOR_RED, Curses::COLOR_BLACK)
Curses::init_pair(Pipe::Colors[:yellow], Curses::COLOR_YELLOW, Curses::COLOR_BLACK)
Curses::init_pair(Pipe::Colors[:magenta], Curses::COLOR_MAGENTA, Curses::COLOR_BLACK)
Curses::init_pair(Pipe::Colors[:blue], Curses::COLOR_BLUE, Curses::COLOR_BLACK)
Curses::init_pair(Pipe::Colors[:green], Curses::COLOR_GREEN, Curses::COLOR_BLACK)
Curses::init_pair(Pipe::Colors[:cyan], Curses::COLOR_CYAN, Curses::COLOR_BLACK)
Curses::curs_set 0
Curses::noecho
Curses::nonl
Curses::timeout = timeout
Curses::refresh
end
# Returns randomly 'true' or 'false'.
def random_bool
result = Random.new.rand(1..10)
if (result % 2) == 0 # is even
return true
else
return false
end
end
# Returns 'true' or 'false' with a probability of 'chance'
def random_bool_with_chance chance
result = Random.new.rand(1..100)
return true if (result <= (chance*100))
return false
end
# Here's the main function!
# _ _ __
# (_) / \ \
# _ __ ___ __ _ _ _ __ | | | |
# | '_ ` _ \ / _` | | '_ \| | | |
# | | | | | | (_| | | | | | | | |
# |_| |_| |_|\__,_|_|_| |_| | | |
# \_ /_/
begin
timeout = 50 # default delay in miliseconds
curses_init timeout
pipes = [Pipe.new(Curses::cols/2, Curses::lines/2, :dumb, Pipe::Colors[:cyan]),
Pipe.new(Curses::cols/4, Curses::lines/8, :line, Pipe::Colors[:red]),
Pipe.new(Curses::cols/8, Curses::lines/4, :curvy, Pipe::Colors[:magenta])]
while true
# Updating all pipes
for i in 0..(pipes.size-1)
pipes[i].update
end
# This is where the timeout delay happens.
case Curses::getch
when 'q', 'Q' # quit
break
when 'a', 'A' # faster!
timeout -= 10
timeout = 10 if timeout < 10
Curses::timeout = timeout
when 's', 'S' # slower!
timeout += 10
timeout = 50 if timeout > 50
Curses::timeout = timeout
when 'c', 'C' # clear
Curses::clear
Curses::refresh
end
end
exit 0
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment