ruby

Networked Drawing Canvas in DRb

In honor of Why The Lucky Stiff’s contributions to the fun side of the Ruby community, whyday.org includes a challenge to “see how far you can push some weird corner of Ruby”. I can think of few corners of Ruby that are weirder (or more fun) than DRb.

Distributed RuBy (DRb) is, in my opinion, the most underrated portion of the Ruby standard library. It lets you take a Ruby class and network-enable it with almost no additional code (and without modifying the original class). Back in 2006 when I was considering whether to learn Ruby or not, I took one look at DRb and realized that a language that made such things possible was probably a language worth knowing.

I made this screencast to show off how powerful DRb is, and how easy it is to get started. We create a simple drawing canvas in Tk, then use DRb to network-enable it and draw to it from a 4-line client. We finish with a Ruby client that runs on Android via the Ruboto environment. And along the way, we cover a little basic security to help keep you safe (this is a network app, after all).

Here’s the complete code for the server:

require 'tk'
require 'drb'

$SAFE = 1

canvas = TkCanvas.new(:width => 800, :height => 600)
canvas.pack

class RemoteCanvas
  def initialize(canvas)
    @canvas = canvas
  end
  def circle(x, y)
    TkcOval.new(@canvas, x, y, x + 40, y + 40)
  end
end

DRb.start_service("druby://192.168.0.100:9000", RemoteCanvas.new(canvas))

canvas.mainloop

Here’s our (tiny) sample client:

require 'drb'
DRb.start_service
canvas = DRbObject.new(nil, "druby://192.168.0.100:9000")
canvas.circle(300, 400)

And here’s the complete touchscreen client for Ruboto:

require "ruboto.rb"
confirm_ruboto_version(4, false)
java_import "org.ruboto.embedded.RubotoView"

require 'drb'
DRb.start_service

$activity.start_ruboto_activity "$druby" do

  setup_content do
    @service = DRbObject.new(nil, "druby://192.168.0.100:9000")
    RubotoView.new($druby)
  end

  handle_touch_event do |event|
    @service.circle(event.get_x, event.get_y)
  end

end

Enjoy, and of course feel free to post questions and comments below. And if you’re looking for your own way to celebrate WhyDay, why not pick your favorite library and network-enable it?

android
development
ruby

Permalink

Ruboto On Acid - Multitouch

Ruboto 0.3 added TouchListener to its event handlers. Here’s my experiment with adding multitouch input to the Ruby On Acid demo…

When it starts up (or the factory resets), all attributes are controlled by an ExampleFactory, meaning they’ll randomly be assigned to LoopFactories, SineFactories, RandomWalkFactories, etc. Touch the screen, though, and control of the drawing x/y position will be given to the touchscreen. Touch in a second place, and control of random attributes will go to your second finger: r/g/b channel, opacity, width, or height.

Continue Reading »

android
development
ruby

Permalink

Pat Maddox - I Love Ruby

From Pat’s MountainWest RubyConf talk. I’m giving a presentation on Rails at work (a JEE shop), and I figured this would help convince my audience. But it also says what many in the Ruby community already feel, so I figured I’d share it.

Like the original, this is released under the Creative Commons Attribution-ShareAlike license.

Pat Maddox - I Love Ruby

development
ruby

Permalink

A challenge!

30 minutes ago, I got this comment on my Ruboto on Acid post:

LSD makes letters on a computer screen look as if they’re dancing around and have a mind of their own. It’s also entirely impossible to think logically enough to code while on it. I recommend you buy some acid and see how it’s nothing like this at all. ;)

And here is my answer. :)

#!/usr/bin/env ruby

require 'rubyonacid/factories/example'
require 'tk'

#This factory will be in charge of all drawing coordinates, colors, etc.
f = RubyOnAcid::ExampleFactory.new

#A skip factory, in charge of randomly resetting the meta factory.
resetter = RubyOnAcid::SkipFactory.new(:odds => 0.99)

#The window to draw to.
canvas = TkCanvas.new(:width => 400, :height => 400)
canvas.pack

#The line objects we create will be stored here.
shapes = []

letters = "woah, trippy, dude!".split(//)

#Create a thread to update the window while it's displayed.
Thread.abort_on_exception = true
Thread.new do
  loop do

    #Get red, green, and blue values for a color from the factory.
    #Format is #RRGGBB in hexadecimal (like HTML).
    color = sprintf("#%02x%02x%02x",
      f.get(:red, :max => 254).to_i,
      f.get(:green, :max => 254).to_i,
      f.get(:blue, :max => 254).to_i
    )

    #Create and store a line of the chosen color.
    #Get width and locations of the endpoints from the factory.
    shapes << TkcText.new(
      canvas,
      f.get(:x1, :max => 400),
      f.get(:y1, :max => 400),
      :text => f.choose(:text, letters),
      :font => TkFont.new(
        :size => f.get(:size, :min => 1, :max => 72).to_i,
      ),
      :fill => color
    )

    #If the resetter returns true, tell the ExampleFactory to reassign
    #its source factories to different keys.
    f.reset_assignments if resetter.boolean(:reset)

    #Delete the oldest line if we have accumulated too many.
    shapes.shift.delete if shapes.length > letters.length

  end
end

#Display the window.
canvas.mainloop

screen-shot-2010-06-12-at-94231-pm.pngscreen-shot-2010-06-12-at-94109-pm.png
screen-shot-2010-06-12-at-93947-pm.pngscreen-shot-2010-06-12-at-93537-pm.png

video

development
ruby

Permalink

MP3 conversions of Ruby conference videos…

I’ve got a sizable commute, so I’ve taken to downloading conference videos and converting to MP3 to play in the car. Most work reasonably well as speech-only, I’m finding. In hopes of helping others, I’m posting them here.

Many thanks to Confreaks, the conference organizers, and to the generous speakers, who released their speeches under a Creative Commons license. In accordance with the license of the original videos, these MP3s are also released under a Creative Commons Attribution-ShareAlike license.

Download MP3 conversions of Confreaks conference videos here.

You can automatically download the lot with wget if you have it installed:

wget --recursive --no-parent --accept mp3,ogg,MP3,OGG http://jay.mcgavren.com/files/conference_mp3s

Conversions were done with ffmpeg. Here’s the script I used:

#!/bin/sh
for filename; do
    ffmpeg -i "${filename}" -acodec libmp3lame -ab 96k -ac 2 -ar 44100 "${filename}.mp3"
done

My chief complaint was wild variation in volume levels; I haven’t blown a speaker yet but loud noises have made me jump sometimes. I’ve processed all these with “mp3gain” (which I thought was only available as a Windows GUI app but have since learned has a Linux command-line version) in an effort to correct this, but I make no guarantees there won’t be sudden jumps or drops in volume.

Many tweaks are still possible, and I will look into them if there’s demand:

  • Remove the periods of silence on each track (where Confreaks originally showed sponsor logos).
  • Convert remaining videos (these were hand-picked).
  • Lower-bitrate versions for the space-conscious.
  • See if The Levelator produces better results than mp3gain.
  • ID3 tags with speakers, titles, and other metadata.
  • An actual podcast feed.

Enjoy, and leave a comment with the improvements you’d like to see!

development
ruby

Permalink

Ruboto On Acid…

After dissecting the demos that come with Ruboto 0.2 and figuring out how to upload a library and runner script to an Android emulator (hint: “$ adb push . /sdcard/jruby/subdirname”), it only took an hour or so to create a working Ruby On Acid demo. And what worked on the emulator immediately worked when uploaded to the phone.

Ruby On Acid in Ruboto IRB
Continue Reading »

android
development
ruby

Permalink

RubyOnAcid version in progress…

Ditched the idea of “tuples” because they complicated the API, and I realized I could do what I want without ‘em. Behold, a LissajousFactory filtered through a RoundingFactory, and feeding into a ProximityFactory:

screen-shot-2010-05-20-at-81348-pm.png

development
ruby

Permalink

Ruby tweets!

A while back I stumbled across a page of JAPHs, or Just Another Perl Hacker scripts. This was an ongoing competition Perl users had years ago to print the phrase “Just Another Perl Hacker” in the most convoluted way possible, utilizing little-known tricks of the language. They usually stuffed these mini-scripts into their Usenet signatures. I had always admired JAPHs, and decided to try my hand at a Ruby version. But I needed a forum, and I hadn’t been on Usenet in years.

So what’s the modern version? Why, Twitter, of course! The 140-character limit would provide an extra level of challenge, enough so that I didn’t feel a need restrict myself to printing “Just Another Ruby Hacker”.

I started simple, printing a wave to STDOUT.

ruby -e "i=0;loop{puts ' '*(29*(Math.sin(i)/2+1))+'|'*(29*(Math.cos(i)/2+1)); i+=0.1}" #ruby

Copy-paste that to a terminal, and hit Enter. (If you copy from Twitter, there’s no need to worry about line breaks or the Favorite star; the browser strips them.) You get something like this:

               |||||||||||||||||||||||||||||||||||||||||||
                   ||||||||||||||||||||||||||||||||||||||||||
                       ||||||||||||||||||||||||||||||||||||||||
                          ||||||||||||||||||||||||||||||||||||||
                            ||||||||||||||||||||||||||||||||||
                             ||||||||||||||||||||||||||||||
                             |||||||||||||||||||||||||
                           |||||||||||||||||||||
                        ||||||||||||||||||
                     |||||||||||||||
                 ||||||||||||||
            ||||||||||||||
        |||||||||||||||
     ||||||||||||||||||
  |||||||||||||||||||||
|||||||||||||||||||||||||
||||||||||||||||||||||||||||||
 ||||||||||||||||||||||||||||||||||
   ||||||||||||||||||||||||||||||||||||||
       |||||||||||||||||||||||||||||||||||||||||
          ||||||||||||||||||||||||||||||||||||||||||
               |||||||||||||||||||||||||||||||||||||||||||
                   ||||||||||||||||||||||||||||||||||||||||||
                       ||||||||||||||||||||||||||||||||||||||||
                          |||||||||||||||||||||||||||||||||||||
                            ||||||||||||||||||||||||||||||||||
                             |||||||||||||||||||||||||||||
                             |||||||||||||||||||||||||
                           |||||||||||||||||||||
...

Here’s a more-readable version:

            #Have to initialize i before incrementing.
            i=0;

            #loop{} is a few characters less than 1000.times{}.
            #People know where Ctrl-C is on their keyboards. :)
            #Brackets cost less characters than do/end.
            loop {

              #sin() and cos() range -1 to 1.
              #We change result to range 0 to 1.
              left_width = Math.sin(i) / 2 + 1
              wave_width = Math.cos(i) / 2 + 1

              #Indentation of wave's left side.
              print ' ' * (29*left_width)
              #Wave's width, which decides placement of right side.
              print '|' * (29*wave_width)

              print "\n"

              #Increasing input for sin()/cos() makes wave undulate.
              i += 0.1

            }

That’s still among my favorites. But anybody can use puts(). The next was more ambitious…

Continue Reading »

development
ruby

Permalink

Name the Top X of All Time - go!

A friend asked for a quick script to help him sort a top-50 list of games. Here’s what I threw together:

#!/usr/bin/env ruby

# rank.rb - Rank a list of items, two at a time.

# Copyright (c) 2010 Jay McGavren
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require 'fileutils'
require 'curses'

file_name = ARGV[0] or fail "Usage: #{__FILE__} filename.txt"

def random_index(list)
  rand(list.length)
end

def write_store(name, entries)
  File.open(name, 'w+') do |file|
    file.puts(entries)
  end
end

def read_store(name)
  entries = []
  File.open(name) do |file|
    entries = file.readlines
  end
  entries
end

entries = read_store(file_name)

Curses.noecho
Curses.init_screen
Curses.stdscr.keypad(true)

loop do
  # Pick 2 random items.
  indices = [0, 0]
  while (indices[0] == indices[1]) do
    indices = [random_index(entries), random_index(entries)]
  end
  indices.sort!
  # Present them.
  Curses.setpos(0, 0)
  Curses.addstr(<<-EOD)
    Which is better?
    1. #{entries[indices[0]]}
    2. #{entries[indices[1]]}
    3. Exit
  EOD
  begin
    result = nil
    until ([?1, ?2, ?3].include?(result)) do
      result = Curses.getch
    end
    case result
    # Higher one chosen?
    when ?1
      # Do nothing.
    # Otherwise:
    when ?2
      # Swap them.
      entries[indices[0]], entries[indices[1]] = entries[indices[1]], entries[indices[0]]
    # Exit chosen?
    when ?3
      # Exit loop.
      break
    else
      fail "wtf?"
    end
  end
  write_store("#{file_name}.bak", entries)
end

# Save.
FileUtils.mv("#{file_name}.bak", file_name)

Curses.close_screen

It picks two entries at random, and swaps their positions if you choose the later one as your favorite. Lather, rinse, repeat. Like a bubble sort without adjacent items.

Curses? Well, it was the quickest way to get Ruby to respond to a single keypress. And since you’re going to be making a lot of entries, I didn’t want you to have to hit Enter after every one.

So here you see my list of artists with 5-star songs, originally in alphabetical order, but with my favorites starting to bubble to the top:

Continue Reading »

development
ruby

Permalink

Jemini Tutorial (Part 2 of 2)

In Part 2 of our tutorial, we’ll create some enemies for our player to fight. We’ll set up collision detection, use timers to make a pretty fading effect, and set up a custom manager to coordinate enemy movements and shooting.

Be sure to visit jemini.org for help on starting your own game!

You can view part 1 here.

Jemini Tutorial (Part 2 of 2)

development
games
ruby

Permalink