Skip to content

Instantly share code, notes, and snippets.

@alexdantas
Created September 6, 2013 03:15

Revisions

  1. alexdantas created this gist Sep 6, 2013.
    284 changes: 284 additions & 0 deletions pipes.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,284 @@
    #!/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