Keeping it Small and Simple

2008.04.05

An mpd clone in Ruby

Filed under: Ruby Programming, Rubygame — Tags: , , , , , , , — Lorenzo E. Danielsson @ 18:02

I was bored, so I decided to write a clone of my music player of choice, the excellent mpd. I also put together a little client, similar to my favorite mpd client, the highly usable mpc.

These are of course watered-down clones that cannot match the original mpd. I wasn’t so much interested in writing an alternative to mdp as just writing something for the fun of it. Feel free to improve upon it.

You will need to have Rubygame installed on the machine that is running the server. There are alternatives that you could look into, but I happen to know Rubygame so I am using it. It is likely that I will at some point experiment with other libraries.

Server/client communication is done via XML-RPC. This makes everything very simple. Again, if you want to practice, you change to some other method for client/server communication (SOAP, your own protocol, or whatever).

Other things you could do include writing a GUI client (I recommend Tk, but Qt4 is also nice, Gtk less so, but at least its Ruby bindings are decent to work with). Or, if you think everything should run in the browser, why not a web client? Of course, you can also add more features. In that case I recommend looking into what mpd supports and try to implement as much of it as possible.

First the server, rmpd:

  1 #! /usr/bin/ruby
  2
  3 # A music playing daemon written in Ruby.
  4
  5 begin
  6   require rubygame
  7   require xmlrpc/server
  8   require optparse
  9 rescue LoadError => e
 10   msg, lib = e.to_s.split /\s+–\s+/
 11   abort "Unable to find #{lib}, cannot continue."
 12 end
 13
 14 class PlayerService
 15   def initialize(player)
 16     @player = player
 17   end
 18
 19   def add(song)
 20     @player.add song.to_i
 21   end
 22
 23   def lsc
 24     @player.lsc
 25   end
 26
 27   def lsq
 28     @player.lsq
 29   end
 30
 31   def play
 32     @player.play
 33   end
 34
 35   def next
 36     @player.next
 37   end
 38
 39   def stop
 40     @player.stop
 41   end
 42 end
 43
 44 class ControllerService
 45   def initialize(player)
 46     @player = player
 47   end
 48
 49   def shutdown
 50     @player.shutdown
 51   end
 52 end
 53
 54 class Player
 55   def initialize(path, port=8080, addr=*)
 56     @col = []
 57     @queue = []
 58     @current = nil
 59     @control = nil
 60
 61     parsedir(path)
 62
 63     @server = XMLRPC::Server.new port, addr
 64     @server.add_handler "player", PlayerService.new(self)
 65     @server.add_handler "controller", ControllerService.new(self)
 66     @server.set_default_handler do |name, *args|
 67       obj, meth = name.split /\./
 68       "No such command: #{meth}"
 69     end
 70
 71     Rubygame.init
 72     Rubygame::Mixer.open_audio
 73   end
 74
 75   def run
 76     @server.serve
 77   end
 78
 79   def add(song)
 80     @queue << @col[song]
 81     "Added #{song}: #{File.basename(@col[song])}"
 82   end
 83
 84   def lsc
 85     return "Collection is empty." if @col.empty?
 86
 87     str = ""
 88     @col.each_index do |idx|
 89       str += "#{idx.to_s.rjust(3)}: #{File.basename(@col[idx])}\n"
 90     end
 91
 92     return str
 93   end
 94
 95   def lsq
 96     return "Queue is empty." if @queue.empty?
 97
 98     str = ""
 99     @queue.each_index do |idx|
100       str += "#{idx.to_s.rjust(3)}: #{File.basename(@queue[idx])}\n"
101     end
102
103     return str
104   end
105
106   def play
107     return "Already playing" if !@current.nil? && @current.playing?
108     return "Queue is empty" if @queue.empty?
109
110     begin
111       song = @queue.shift
112       @current = Rubygame::Mixer::Music.load_audio song
113       @current.play
114       control
115       "Playing #{File.basename(song)}"
116     rescue 
117       "Something went wrong"
118     end
119   end
120
121   def next
122     return "Not playing." if @current.nil? || !@current.playing?
123     @control.exit unless @control.nil?
124     @current.stop
125     sleep 1 while @current.playing?
126     play
127   end
128
129   def stop
130     return "Not playing." if @current.nil? || !@current.playing?
131
132     @control.exit unless @control.nil?
133     @current.stop
134     "Stopped."
135   end
136
137   def control
138     @control = Thread.new {
139       sleep 1 while @current.playing?
140       play unless @queue.empty?
141     }
142
143     @control.run
144   end
145
146   def shutdown
147     @server.shutdown
148     Rubygame::Mixer.close_audio
149     Rubygame.quit
150     "Shutting down server."
151   end
152
153   def parsedir(path)
154     Dir.new(path).each do |file|
155       next if file =~ /^\./
156
157       if File.directory? "#{path}/#{file}"
158         parsedir "#{path}/#{file}"
159       else
160         @col << "#{path}/#{file}"
161       end
162     end
163   end
164 end
165
166 options = {
167   :addr => 127.0.0.1,
168   :port => 8080,
169   :dir => "#{ENV[HOME]}/music"    
170 }
171
172 begin
173   OptionParser.new do |opts|
174     opts.banner = "usage: #{$0} [options]"
175
176     opts.on("-a", "–address=ADDR", String, "Address to listen on.") do |addr|
177       options[:addr] = addr
178     end
179
180     opts.on("-p", "–port=PORT", Integer, "Port to listen on.") do |port|
181       options[:port] = port
182     end
183
184     opts.on("-d", "–dir=DIR", String, "Collection directory.") do |dir|
185       options[:dir] = dir
186     end
187   end.parse!
188 rescue
189   abort "Command line foo!"
190 end
191
192 mpd = Player.new options[:dir], options[:port], options[:addr]
193 mpd.run

Here is the client, rmpc:

 1 #! /usr/bin/ruby
 2
 3 # A client for rmpd.
 4
 5 require xmlrpc/client
 6 require optparse
 7
 8 options = {
 9   :port => 8080,
10   :addr => "127.0.0.1"
11 }
12
13 begin
14   OptionParser.new do |opts|
15     opts.banner = "usage: #{$0} [options] command"
16
17     opts.on("-a", "–address=ADDR", String, "Address of server") do |addr|
18       options[:addr] = addr
19     end
20
21     opts.on("-p", "–port=PORT", Integer, "Port of server") do |port|
22       options[:port] = port
23     end
24   end.parse!
25 rescue
26   abort "Command line foo!"
27 end
28
29 abort "No command." if ARGV.empty?
30
31 begin
32   server = XMLRPC::Client.new options[:addr], "/RPC2", options[:port]
33 rescue
34   abort "No contact with server."
35 end
36
37 player = server.proxy "player"
38 control = server.proxy "controller"
39
40 command = ARGV.shift
41 if command == shutdown
42   puts control.shutdown
43 else
44   puts player.send(command, *ARGV)
45 end

2008.03.11

A programming blog worth reading

Filed under: Blogs, programming — Tags: , , , — Lorenzo E. Danielsson @ 21:58

If you are into Ruby or just programming in general, you may find this blog interesting. Just found it today and like it. Simple, straight to the point and lots of code.

2008.03.01

Testing JRuby 1.1 RC2

Filed under: JRuby, Ruby Programming — Tags: , , , — Lorenzo E. Danielsson @ 18:20

Of late my schedule has been a bit more hectic than I had hoped. So I haven’t had as much time I would have liked to play around with JRuby and Rubinius. But the other day I was finally able to find a little time. So I downloaded the latest JRuby 1.1 release candidate, which is RC2.

So far it has worked well. I hear that 1.1 is significantly faster than previous versions of JRuby. I cannot comment on that yet, because I haven’t begun playing around with the types of applications where speed would matter.

Installing it

I downloaded the .tar.gz package and installed it to /opt. Do this (as root):

# cd /opt
# tar zxf /home/lorenzod/dl/jruby-bin-1.1RC2.tar.gz

After that you will want to either (1) add /opt/jruby-1.1RC2/bin to PATH or (2) create a few symlinks. I chose the symlink option.

# JD=/opt/jruby-1.1RC2/bin
# ln -s $JD/jruby /usr/local/bin
# ln -s $JD/jrubyc /usr/local/bin
# ln -s $JD/jirb /usr/local/bin
# ln -s $JD/gem /usr/local/bin/jgem

You can of course add all the links that you need. These were enough for me to get started. Note that I named the link to jruby’s gem jgem.

Testing the compiler

I wanted to see how well the JRuby compiler works. So, I quickly put together the following little test program.


 1 #! /usr/bin/jruby
 2
 3 # Count the number of times the user clicks some buttons.
 4
 5 require java
 6
 7 include_class java.awt.GridLayout
 8 include_class java.awt.event.ActionListener
 9 include_class java.awt.event.WindowListener
10 include_class java.lang.System
11 include_class javax.swing.JButton
12 include_class javax.swing.JFrame
13
14 class ClickButton < JButton
15   include ActionListener
16
17   def initialize(text)
18     @count = 0
19     @text = text
20     super "#{@text} (0)"
21     add_action_listener self
22   end
23
24   def actionPerformed(event)
25     @count += 1
26     self.text = "#{@text} (#{@count})"
27   end
28 end
29
30 class MainWindow < JFrame
31   include WindowListener
32   include ActionListener
33
34   def initialize
35     super "Click Counter"
36     set_layout GridLayout.new(5, 1)
37     @total = 0
38
39     1.upto 5 do |n|
40       button = ClickButton.new "Button #{n}"
41       button.add_action_listener self
42       add button
43     end
44
45     add_window_listener self
46     pack
47   end
48
49   def actionPerformed(event)
50     @total += 1
51   end
52
53   # Bah, humbug!
54   def windowActivated(event); end
55   def windowClosed(event); end
56   def windowDeactivated(event); end
57   def windowDeiconified(event); end
58   def windowIconified(event); end
59   def windowOpened(event); end
60
61   def windowClosing(event)
62     puts "Total clicks: #{@total}"
63     System::exit 0
64   end
65 end
66
67 MainWindow.new.set_visible(true)

First I tried to run it normally.

% jruby cc.rb

That worked fine. After a few seconds a Swing application popped up on my screen. Next I tried compiling it.

% jrubyc cc.rb
Compiling cc.rb to class ruby/cc
% ls ruby/
cc.class

To run this program I did the following:

% java -cp /opt/jruby-1.1RC2/lib/jruby.jar:. ruby.cc

It worked. JVM takes a moment or two to fire up, then the application window pops up. Of course, I haven’t looked into the options to jrubyc yet, but at least this was enough to get me started.

Next, I’ll want to try JRuby out in Netbeans. I haven’t tried JRuby on Rails yet, so that is something I want to dedicate some time to. So expect more on JRuby from me in the near future.

2008.02.26

Polygamy according to a Ruby hacker

Filed under: Humor — Tags: , , , — Lorenzo E. Danielsson @ 23:41

Oh MRI, for me there is nobody else. (By the way, your sister, what’s her name? 1.9? She’s looking quite HOT!)

Rubinius, Rubinius, so young yet so fair. Still inexperienced, but I’ll get you there. (Hm.. why do I feel like a dirty old man all of a sudden?)

JRuby, without you the sun would not rise. I was never serious with Java, but with you it’s different, honest.

Er, XRuby, I don’t know how to say this. We’ve gone through some good times and some bad times. But I still appreciate ya.


If you think this is bad, it could be worse. I hear there is at least one or two implementations of Ruby for that cheap JVM imitation (dotcom or whatever its called). At least I’m not *that* desperate!

Rubygame tutorial #4: Like wow, a re-usable pixel

Filed under: Rubygame Tutorial — Tags: , , , — Lorenzo E. Danielsson @ 19:05

Welcome to another Rubygame tutorial. This time we will create a more generic moving pixel so that we can re-use it. We will also allow the pixel to make some noise if it crashes against a border.

I am going to go through different parts of the program. At the end I will put it all together.

Initialization

First off all, since we will be using sound in this application, we need to initialize the sound sub-system. To do that we can do as follows:


1 Rubygame.init
2 Mixer::open_audio
3
4 screen = Screen.new [500, 500]
5 events = EventQueue.new
6 clock = Clock.new

Most of this should be familiar by now. What is new is the call to Rubygame::Mixer::open_audio. This is required if we want to be able to play samples.

We are also going to create two Pixel instances. This is how we prepare these two.


1 p1 = Pixel.new "Pixel the First", screen
2 p1.color = [255, 240, 0]
3 p1.keys = [K_UP, K_DOWN, K_LEFT, K_RIGHT]
4 p1.crash_sound = Mixer::Sample.load_audio "pop.wav"
5
6 p2 = Pixel.new "Pixel the Second", screen
7 p2.color = [0, 255, 255]
8 p2.keys = [K_W, K_X, K_A, K_D]
9 p2.crash_sound = Mixer::Sample.load_audio "error.wav"

Our new pixel class (which we shall get to shortly, is a bit expanded compared to the one in the previous tutorial). Our pixel now has a name. We give it a name by passing the name in as the first argument to the Pixel constructor. Then we pass in the surface that the pixel will draw itself onto.

Our new Pixel has a color attribute. We can create pixels in different colors, which we do here. The Pixel also takes an array of keys used for moving it. The order of the keys here is significant. You must pass them in the order: up, down, left and right. Finally, the Pixel takes a sound to be played if the pixel crashes against a border. I copied two sounds from /usr/share/sounds/ but you can choose whatever you like. You can even record your own. Just try to keep them relatively short. You wouldn’t want a recording of one of Edgar Allan Poe’s stories to be played every time a pixel hits a borders.

Keep the samples in the same directory as the ruby code, or pass the full path to the sample. The samples should be in WAV format.

The new Pixel class

Now let us look at the new version of Pixel. We have a little bit more information here than in the previous version. First, here is the structure of the class and the constructor.


 1 class Pixel
 2   attr_accessor :color, :keys, :name
 3   attr_reader :hit
 4   attr_writer :crash_sound
 5
 6   def initialize(name, surface)
 7     @name = name
 8     @surface = surface
 9     @x = @surface.w / 2
10     @y = @surface.h / 2
11     @vx, @vy = 0, 0
12     @color = [255, 255, 255]
13     @hit = false
14     @keys = []
15     @crash_sound = nil
16   end
17 end

We have a few attributes that we want to make accessible:

  • color: the color of the pixel
  • keys: the keys used to move the pixel (up, down, left and right)
  • name: the name of the pixel
  • hit: a flag that is set if we just crashed into a border (read-only)
  • crash_sound: the sound to play when we hit a border (write-only)

Don’t worry too much about the fact that hit is read-only and crash-sound is write only. It was a spur of the moment thing. Use the rules that make sense to you, and your needs.

In the initialize method we set up a few other, internal use variables, such as vx and vy which are used for horizontal and vertical velocity. Again, never mind the fact the in previous versions I called these horizontal and vertical direction (dx and dy). That was also a spur of the moment thing. Feel free to do the right thing. Also feel free to define what the right thing is. I felt free to somewhere along the line redefine what the right thing is.

The variables vx and can both have one of three values:

  • 0 means no movement (zero velocity)
  • -1 means upward motion for vy or leftward for vx
  • 1 means downward motion for vy or rightward for vy

If you are not sure of why this is so, think about it for a while. Take into consideration how screen coordinates work and it will be obvious.

Next, we need a move method.


 1 def move
 2   @hit = false
 3   return if @vx.zero? && @vy.zero?
 4   
 5   unless (0..@surface.w-1).include? @x + @vx
 6     @vx = 0
 7     @hit = true
 8     Mixer::play(@crash_sound, 0, 0) unless @crash_sound.nil?
 9     return
10   end
11
12   unless (0..@surface.h-1).include? @y + @vy
13     @vy = 0
14     @hit = true
15     Mixer::play(@crash_sound, 0, 0) unless @crash_sound.nil?
16     return
17   end
18
19   @x += @vx
20   @y += @vy
21 end
22

Every time we are going to move a pixel we need to check if it just hit a border. However, we are victims of a particular idea of justice that says that innocent until proven guilty so we start by setting hit to false. At the same time, if there is no horizontal and no vertical velocity, then there really isn’t much point in the rest of the method.

Next we have two block that check if the next position (horizontally and vertically) is within the screen or if we hit a border. (Note that x+vx will be the position that we are about to move to. I am sure that you can think of 1001 other ways of implementing this, many of which are more efficient. Guess what? That’s an exercise: to re-write the two tests that start with unless statements.

If we have hit a border, we stop the pixel, set the hit flag, play a crash sound and exit the method. If we didn’t exit the method we would get to the end where the pixel is actually moved, which wouldn’t be a good idea. If you are sure why, walk straight until you hit the wall. When you do still walk forward.

So, how about the event method? Remember that the @keys array holds the keys that are used to control this Pixel. So the new method looks like this.


1 def event(key)
2   case key
3   when @keys[0]: @vx, @vy = 0, –1
4   when @keys[1]: @vx, @vy = 0, 1
5   when @keys[2]: @vx, @vy = –1, 0
6   when @keys[3]: @vx, @vy = 1, 0
7   end
8 end
9

Hopefully that isn’t difficult to understand. Last we have the draw method. But that hasn’t changed enough to warrant us looking at it in any detail. So let’s instead look at the whole program.

Putting it all together

Forcing all the little bits and pieces into a single program we get this:


  1 #! /usr/bin/ruby
  2
  3 # Move a pixel around the screen.
  4
  5 require rubygame
  6 include Rubygame
  7
  8 class Pixel
  9   attr_accessor :color, :keys, :name
 10   attr_reader :hit
 11   attr_writer :crash_sound
 12
 13   def initialize(name, surface)
 14     @name = name
 15     @surface = surface
 16     @x = @surface.w / 2
 17     @y = @surface.h / 2
 18     @vx, @vy = 0, 0
 19     @color = [255, 255, 255]
 20     @hit = false
 21     @keys = []
 22     @crash_sound = nil
 23   end
 24   
 25   def move
 26     @hit = false
 27     return if @vx.zero? && @vy.zero?
 28     
 29     unless (0..@surface.w-1).include? @x + @vx
 30       @vx = 0
 31       @hit = true
 32       Mixer::play(@crash_sound, 0, 0) unless @crash_sound.nil?
 33       return
 34     end
 35
 36     unless (0..@surface.h-1).include? @y + @vy
 37       @vy = 0
 38       @hit = true
 39       Mixer::play(@crash_sound, 0, 0) unless @crash_sound.nil?
 40       return
 41     end
 42
 43     @x += @vx
 44     @y += @vy
 45   end
 46
 47   def draw
 48     @surface.set_at [@x, @y], @color
 49   end
 50
 51   def event(key)
 52     case key
 53     when @keys[0]: @vx, @vy = 0, –1
 54     when @keys[1]: @vx, @vy = 0, 1
 55     when @keys[2]: @vx, @vy = –1, 0
 56     when @keys[3]: @vx, @vy = 1, 0
 57     end
 58   end
 59 end
 60
 61 Rubygame.init
 62 Mixer::open_audio
 63
 64 screen = Screen.new [500, 500]
 65 events = EventQueue.new
 66 clock = Clock.new
 67
 68 p1 = Pixel.new "Pixel the First", screen
 69 p1.color = [255, 240, 0]
 70 p1.keys = [K_UP, K_DOWN, K_LEFT, K_RIGHT]
 71 p1.crash_sound = Mixer::Sample.load_audio "pop.wav"
 72
 73 p2 = Pixel.new "Pixel the Second", screen
 74 p2.color = [0, 255, 255]
 75 p2.keys = [K_W, K_X, K_A, K_D]
 76 p2.crash_sound = Mixer::Sample.load_audio "error.wav"
 77
 78 running = true
 79
 80 while running
 81   events.each do |event|
 82     case event
 83     when KeyDownEvent
 84       p1.event(event.key) if p1.keys.include? event.key
 85       p2.event(event.key) if p2.keys.include? event.key
 86       running = false if event.key == K_Q
 87     when QuitEvent
 88       running = false
 89     end
 90   end
 91
 92   screen.fill [0, 0, 0]
 93   [p1, p2].each { |pixel|
 94     pixel.move
 95     pixel.draw
 96     puts "#{pixel.name} crashed!!!!" if pixel.hit
 97   }
 98
 99   clock.tick
100   screen.update
101 end
102
103 Mixer::close_audio
104 Rubygame.quit
105
106

Notice what happens on a KeyDown event. A specific Pixels’s event method is called if that Pixel has registered that it handles that key press. I’ve also made ‘q’ quit the program. I only did that to be cool.

When it comes to the move/draw cylce of the pixels, remember that you have more than one pixel to take into consideration. I only added the console message to find some use for the name and hit attributes. It at least shows that the external world can know whether or not a Pixel hit a border.

Finally, notice that I close the audio sub-system when the application quits. That is a good idea (as in it really, really is a good idea).

Exercises

1. It kind of/really (depending on your perspective) sucks that both the pixels start at the same position on the screen. Add the ability to specify a starting position for a pixel. (Hint: if you start adding attributes or lots and lots of code then you probably have a hormonal imbalance)

2. Add another two pixels to the program. Make sure that each pixels has its own set of keys to control it.

3. Make the program display four pixels as in the previous exercise, but have them all be controlled by the same set of keys. Make sure the pixels all have different starting positions.

4. Re-write the program in such a way that each Pixel gets a random starting location.

5. See if you can get the program to detect when two Pixel objects collide.

6. Add a counter that counts how many times a pixel has crashed. At the end of the program dumps some stats about number of crashes to the terminal.

7. Allow a pixel to move not only horizontally and vertically but diagonally as well. There are two ways to do this, and you should try both. One is that a change in vx should not reset the value of vy and vice versa. The other is that you register more keys (up-left, up-right, down-left and down-right) for controlling the pixel.

8. Instead of using an array to hold the motion keys, use a hash with UP, DOWN, LEFT and RIGHT as hash keys (also the others if you want to add diagonal motion).

9. Make the program well-behaved, as in don’t crash if a sound sample isn’t found. Add some error-handling.

10. Change the draw method to plot four pixels. @x and @y should be a reference point. The first pixel should be drawn 5 pixels above @y and 5 pixels to the left of @x. The second pixel should be 5 pixels above @y and 5 pixels to the right of @x. The third pixel should be 5 pixels below @y and 5 pixels to the left of @x. Finally, the last pixel should be 5 pixels below @y and 5 pixels to the right of @x. The four pixels should now make up the corners of a square. Update the collision detection in the move method to check if any of the points has hit a border. Don’t write more checks than is necessary. Calling the class ‘Pixel’ may not be appropriate anymore.

11. Modify the program in exercise 10 to instead of drawing four corners of a square, the four pixels should make up the four corners of a diamond shape.

12. Write a program similar to mine, but that plays a sample any time the Pixel’s x and y coordinates are both divisible by 5. This sound should be different from the crash sound.

13. Speaking of crash sound, is it really a good idea with different crash sounds for each Pixel? If not, don’t offer a writable attribute to set the crash sound. Hard-code it into the class once and for all.

14. Is it a bit weird to have 3 possible velocities. When was the last time you saw that in the real world. See if you could get the pixel to accelerate up to its final speed instead of going from 0 to 1 in an instant.

15. Expanding further on the idea of acceleration, what if the pixel should accelerate as long as a key is held down (the longer you hold left pressed down the faster it moves in that direction)? Or each time the down button is pressed, it should increase it’s downward velocity?

16. See if you can convince our Pixel to join Pixels without Borders. Any time it gets to the top of the screen it should appear at the very bottom. If it dissapears to the right it should reappear from the left. You know the idea. Make it happen.

18. My program is intentionally raw. Think of ways to improve or worsen it (your choice). Once you’ve understood how everything works together you should be able to re-write the whole program in different ways. You could for instance try to write a version that doesn’t use classes at all. With more practice you become older. Er.. maybe you learn a thing or two as well.

Conclusion

Are you as sick and tired of plotting individual pixels as I am? Good. Next time we will look at other drawing methods. Eventually we will get to more interesting things like being able to

2008.02.22

Rubygame tutorial #3: In control

Filed under: Ruby Programming, Rubygame Tutorial — Tags: , , , — Lorenzo E. Danielsson @ 07:37

Welcome back. Due to (somewhat) popular demand, I’m going to continue the rubygame tutorials. In this tutorial we will look at a keyboard event: KeyDownEvent.

Looking back for a second or two

Let us remind ourselves of the steps we need to take to create a very simple Rubygame program:

  1. Create a screen
  2. Create a event queue
  3. Create a clock
  4. Enter the main loop. The main loop does drawing and contains an event loop.
  5. The event loop should have some exit condition

That is really it. Armed with only this knowledge and a few drawing methods we can do a lot already.

A single pixel

Let’s start off with a program that plots a single pixel. The following program will just plot a single pixel at the center of the screen.


 1 #! /usr/bin/ruby
 2
 3 # A pixel.
 4
 5 require rubygame
 6 include Rubygame
 7
 8 screen = Screen.new([640, 400])
 9 events = EventQueue.new
10 clock = Clock.new
11 clock.target_framerate = 120
12 running = true
13
14 while running
15   events.each do |event|
16     case event
17     when QuitEvent
18       running = false
19     end
20   end
21
22   screen.fill([0, 0, 0])
23   screen.set_at([screen.w / 2, screen.h / 2], [255, 255, 255])
24   screen.update
25   clock.tick
26 end
27

Not too interesting I guess. I single pixel and it doesn’t even move! Let’s change that.

Exercises

1. Modify the program to display a blue pixel.

2. Modify the progam to plot the pixel at the position 100, 100.

3. Change the screen size to 500, 500.

4. Write a program that takes the pixel coordinates as command-line arguments. Provide the center as a default position if no arguments are passed. Give an error if the coordinates are beyond the size of the screen.

Preparing for motion

Let’s give our pixel a life of its own. We will create a pixel class. Here is a new version of the program.


 1 #! /usr/bin/ruby
 2
 3 # Another pixel.
 4
 5 require rubygame
 6 include Rubygame
 7
 8 class Pixel
 9   attr_reader 😡, :y
10
11   def initialize(x, y, surface)
12     @x, @y = x, y
13     @surface = surface
14   end
15
16   def plot
17     @surface.set_at [@x, @y], [255, 255, 255]
18   end
19 end
20
21 screen = Screen.new [640, 400]
22 events = EventQueue.new
23 clock = Clock.new
24 clock.target_framerate = 120
25 pix = Pixel.new(screen.w/2, screen.h/2, screen)
26 running = true
27
28 while running
29   events.each { |event|
30     case event
31     when QuitEvent
32       running = false
33     end
34   }
35
36   pix.plot
37   screen.update
38   clock.tick
39 end

We still only see a single pixel at the middle of the screen. Still no fun! 😦

On the other hand, we now have a pixel that knows its own position and is able to “draw itself” onto its target surface.

Exercises

1. Add a color attribute to the Pixel class. Set the color attribute to red before plotting the pixel.

2. Create five Pixel instances, each with different coordinates and plot each of them.

Now, move it!

So far we have used QuitEvent to determine if the user has closed the Rubygame screen. Now let’s look at another event: KeyDownEvent. This event is triggered any time a key is pressed down. We can find out which key via KeyDownEvent#key. Let’s use this to add the ability to move the pixel around.

We give our Pixel class some new methods: up, down, left, right and stop. Internally, it will also have two new fields: one to determine the horizontal direction and one to determine the vertical direction.


 1 #! /usr/bin/ruby
 2
 3 # Moving pixel.
 4
 5 require rubygame
 6 include Rubygame
 7
 8 class Pixel
 9   attr_reader 😡, :y
10
11   def initialize(x, y, surface)
12     @x, @y = x, y
13     @dx, @dy = 0, 0
14     @surface = surface
15   end
16
17   def up
18     @dx, @dy = 0, –1
19   end
20
21   def down 
22     @dx, @dy = 0, 1
23   end
24
25   def left
26     @dx, @dy = –1, 0
27   end
28
29   def right
30     @dx, @dy = 1, 0
31   end
32
33   def stop
34     @dx = @dy = 0
35   end
36
37   def move
38     tx, ty = @x + @dx, @y + @dy
39     return unless (0..@surface.w-1).include? tx
40     return unless (0..@surface.h-1).include? ty
41
42     @x, @y = tx, ty
43   end
44
45   def plot
46     @surface.set_at [@x, @y], [255, 255, 255]
47   end
48 end
49
50 screen = Screen.new [640, 400]
51 events = EventQueue.new
52 clock = Clock.new
53 clock.target_framerate = 120
54 pix = Pixel.new(screen.w/2, screen.h/2, screen)
55 running = true
56
57 while running
58   events.each { |event|
59     case event
60     when QuitEvent
61       running = false
62     when KeyDownEvent
63       case event.key
64       when K_UP: pix.up
65       when K_DOWN: pix.down
66       when K_LEFT: pix.left
67       when K_RIGHT: pix.right
68       when K_SPACE: pix.stop
69       end
70     end
71   }
72
73   screen.fill [0, 0, 0]
74   pix.move
75   pix.plot
76   screen.update
77   clock.tick
78 end

Exercises

1. Try to comment out the screen.fill line inside the drawing loop. What happens?

2. Currently, the pixel can move in four directions: up, down, left and right. How could you modify the program to allow the pixel to move diagonally. (There are several ways, experiment as much as you can.)

3. Modify the program to create three Pixels. Give each a different starting position. When one of the motion keys is pressed, all three should move.

4. What if you wanted to create three Pixels that each used a different set of keys to control its motion, how would you go about that? Would you make any changes to the Pixel class itself?

5. Rewrite the program to check the KeyUpEvent instead of KeyDown. How does this affect the program?

Conclusion

In this tutorial you have learned how to use the KeyDown event. We will continue with our moving pixel in the next tutorial (hey, I told you I was going to go real slow).

In the meantime, experiment with what you have learned so far. There are loads of things you can do to get extra practice. For instance you could modify the program so that the pixel only moves while a key is pressed down. Once the key is released it should stop (use both KeyDownEvent and KeyUpEvent). The more you practice the better you will become.

2008.02.12

Use Ruby’s Array#delete_if

Filed under: Ruby Programming — Tags: , — Lorenzo E. Danielsson @ 15:20

In case you didn’t know, Ruby has a convenient way of allowing you to remove items from an array called delete_if. You simply pass in a block with an expression. All items in the array for which the block expression evaluates to true are removed. Here are a few simple examples:

Remove a specific item:

 1 #! /usr/bin/ruby
 2
 3 wd = %w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday]
 4
 5 # Tell me why
 6 # I don’t like Mondays
 7 # I want to shoot
 8 # The whole day down
 9 wd.delete_if { |day| day == "Monday" }
10 p wd

Remove all words that end with the letter s:

1 #! /usr/bin/ruby
2
3 words = %w[eggs ham vinegar salt pancakes wine oranges]
4 words.delete_if { |w| w =~ /s$/ }
5 p words

Remove odd numbers.

1 #! /usr/bin/ruby
2
3 nums = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
4 nums.delete_if { |n| n % 2 > 0 }
5 p nums

Remove people under 18.

 1 #! /usr/bin/ruby
 2
 3 class Person
 4   attr_reader :name, :age
 5
 6   def initialize(name, age)
 7     @name = name
 8     @age = age
 9   end
10
11   def to_s
12     @name
13   end
14 end
15
16 class Bar
17   def enter(group)
18     group.delete_if { |person| person.age < 18 }
19     puts "#{group.join(, )} are welcome!"
20   end
21 end
22
23 drunken_sailor = Bar.new
24 drunks = [
25   Person.new("Tom", 28),
26   Person.new("Dick", 17),
27   Person.new("Harry", 18)
28 ]
29
30 drunken_sailor.enter drunks

Delete all items:

1 #! /usr/bin/ruby
2
3 nums = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
4 nums.delete_if { true }
5 p nums

Delete no items (a way of doing a NOP in Ruby).

1 #! /usr/bin/ruby
2
3 nums = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
4 nums.delete_if { false }
5 p nums

There you have it. Using delete_if makes your life simpler, so just use it.

2008.01.31

Why I keep stressing students to write more code

Filed under: Ruby Programming — Tags: , , , — Lorenzo E. Danielsson @ 21:08

One thing I always tell my students is that if they really want to become good at programming they have to write a lot of code. They shouldn’t just work on projects but also create a separate directory dedicated to little experimental code snippets.

When somebody asks me if “such and such” works, I normally tell them to go and write some code to test it. I usually refuse to answer such questions until I can see some code. Needless to say, many of my students over the years have felt that I’m a royal pain in the ass.

Today I gave one of my Ruby students a little challenge: to extend Ruby’s Array class with methods to rotate the array left and right. The ROL and ROR methods should be familiar to every programmer unless, of course, you fell asleep during assembly language class. In this case we’re not even rotating bits in a byte but elements of an array.

At the end of the day, my student told me that he’ll present his solution tomorrow. I think he’s got it mostly figured out but he’s over-complicating it. This student is at the stage where he has a firm grasp of the basic elements of Ruby. But he needs to learn how to combine the facilities that the language gives him in different ways to produce the results he wants.

I’ve come to realize that this is the most difficult part of learning how to program, and the time when most students risk giving up. I guess it’s a bit similar to learning how to speak a new language. It’s fairly simple to learn the formal grammar of a language and to pick up a basic vocabulary of common, every-day expressions.

But, to combine these building blocks into more interesting sentences is challenging. I often fumble on sentences in Ga where I know all the individual words I need to use, but somehow fail to combine them in a way where I can make myself understood. The solution is simple: I have to force myself to go out and listen to and speak Ga much more. Most importantly, I have to practice the basic constructs of the language until I can use them “without” thinking.

Exactly the same thing goes with writing code. The syntax of the language and other basic elements (the most commonly used classes in Ruby’s core classes) need to be practiced until the student “becomes fluent” in using them.

So I keep on forcing my students to write loads and loads of small, trivial programs. It gets very boring for them from time to time, and I’m sure some of them would love to run me over with a freight train or something like that. But, at end my responsibility is to turn them into programmers, not win popularity contests. And I do feel that that the extra time spent on programming basics pays of for them at a later stage.

If you are a struggling with learning how to program, don’t give up. Give yourself little challenges every day. Don’t tell me you can’t think of any, because their are loads of them around you. They don’t have to be “useful” (whatever that means), and it’s okay to reinvent the wheel over and over again while you are learning.

Back to our rotating array. Anybody who has not only used, but has understood what the following methods do won’t have any problems implementing it.

  • Array#shift
  • Array#<<
  • Array#unshift
  • Array#pop

Of course there are other ways of solving it, but I’m allergic to solutions that are more complex than they need to be. It gives me a bad rash.


 1 #! /usr/bin/ruby
 2
 3 # Rotate an array.
 4
 5 class Array
 6   def rol!(n=1)
 7     n.times { self << self.shift }
 8   end
 9
10   def ror!(n=1)
11     n.times { self.unshift self.pop }
12   end
13 end
14
15 wd = %w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday]
16 puts "Original array:"
17 p wd
18
19 puts "Rotating right 3 times."
20 wd.ror! 3
21 p wd
22
23 puts "Rotating left once."
24 wd.rol!
25 p wd
26     
27 puts "Rotating left nine times (should be like rotating left twice)."
28 wd.rol! 9
29 p wd

2008.01.27

Another Ruby implementation: XRuby

Filed under: Ruby Programming — Tags: , , , , — Lorenzo E. Danielsson @ 21:21

In #rubinius on Freenode today, I heard of another Ruby implementation called xruby. It compiles ruby files to Java class files that can be run in the JVM. I just downloaded xruby-0.3.2, which appears to be the latest release at least as this time of writing.

It’s cool that there is a lot of interest in Ruby and so many different implementations coming up. At this point in time I think it’s a good idea that there are several different implementations. I also think it’s cool if at least some of them take the opportunity to experiment a little bit, without straying away too far from the core Ruby language of course. Several implementations gives us the ability to compare and contrast ideas, to see what works well and what doesn’t.

What I wouldn’t want is that each implementation over time adding its own extensions to the point where we end up with 100,000 incompatible Ruby implementations. I don’t want Ruby to become fragmented to point where we have Ruby, Ruby++, Ruby#, RubyLite and RubyDuby. Wouldn’t you also hate to have to sit and add a section in your READMEs about which Rubies are supported or to go to download some Ruby program just to be confronted with 1000 different packages for different rubies?

I understand the good folks over at the Rubinius have added a few extensions. A few methods here and there that have been added to core Ruby classes. If these extensions prove useful they should obviously go into all Ruby implementations. I don’t have anything against Rubinius at all (on the contrary, I’m very excited about it). But I’d be reluctant to use a feature that is only available in Rubinius, at least if it is going to remain a Rubinius-specific extension.

JRuby obviously has its own set of extensions which are required in order to interact with the Java class libraries and to be a good citizen of the JVM community. JRuby manages to be a good citizen of the Ruby community as well.

And now there’s XRuby. That was a lie. It’s been around for quite some time, just that I hadn’t heard of it before today. I’ve managed to build it while I’m writing this post. I compiled one of the samples. It seems to compile and JARify the ruby sources. I haven’t been able to figure out how to run the compiled program yet (complaining about not finding RubyProgram.class), but I haven’t tried all that hard yet.

Of course, I hear there is a Ruby implementation for Mono as well. But an aptitude search doesn’t turn up anything interesting. It could be that it’s not in Debian yet. It could also be that it’s got a weird name that has nothing to do with Ruby. I don’t really have much experience with Mono.

What I think is important is that the various communities work closely together so that all the good stuff finds its way back and becomes an official (whatever *that* means) part of Ruby eventually. I guess in the end MRI is the One Ruby to rule them all, so if a feature makes it there it is likely to get adapted by the others. I hope MRI doesn’t turn the others into mindless zombie (read: nazgul) processes that only know how to obey their master.. 😉

Enough already! I’m a programmer not a damn philosopher.

2008.01.25

Simple wallpaper tool in Ruby

Filed under: Ruby Programming — Tags: , , , , — Lorenzo E. Danielsson @ 15:39

Ben is constantly on the look-out for small applications that he can write in order to practice his Ruby skills. This morning he told me he wants to write a “wallpaper switcher” (or whatever you call those things). It seemed like an interesting little program, so I hastily put this together.


 1 #! /usr/bin/ruby
 2
 3 # A wallpaper tool.
 4
 5 require optparse
 6
 7 def parsedir(path)
 8   a = []
 9
10   Dir.new(path).each do |file|
11     next if file =~ /^\./
12   
13     fp = "#{path}/#{file}"
14     File.directory?(fp) ? a.concat(parsedir(fp)) : a << fp
15   end
16   a
17 end
18
19 options = {
20   :dir => nil,
21   :file => nil,
22   :interval => 60,
23   :random => false,
24   :use => wmsetbg
25 }
26
27 begin
28   OptionParser.new do |opts|
29     opts.banner = "usage: #{$0} [options]"
30
31     opts.on("-d", "–directory=DIR", String, "scan directory") do |dir|
32       options[:dir] = dir
33     end
34
35     opts.on("-f", "–file=FILE", String, "read wallpapers from file") do |file|
36       options[:file] = file
37     end
38
39     opts.on("-i", "–interval=I", Integer, "set the interval") do |interval|
40       options[:interval] = interval
41     end
42
43     opts.on("-r", "–[no-]random", "randomize the order") do |r|
44       options[:random] = r
45     end
46
47     opts.on("-u", "–use=APP", String, "set the utility to use") do |use|
48       options[:use] = use
49     end
50   end.parse!
51 rescue OptionParser::InvalidOption => err
52   $stderr.print err.to_s + "\n"
53   exit
54 rescue OptionParser::MissingArgument => err
55   $stderr.print err.to_s + "\n"
56   exit
57 end
58
59 wp = []
60 wp.concat(IO.readlines(options[:file])) unless options[:file].nil?
61 wp.concat(parsedir(options[:dir])) unless options[:dir].nil?
62 wp.collect! { |image| image.chomp }.delete_if { |image| !File.exists? image }
63 abort "No wallpapers in list." if wp.empty?
64
65 %w[TERM INT].each do |sig|
66   trap(sig) {
67     puts "Are you crazy?? What are you doing to me?"
68     exit
69   }
70 end
71
72 loop do
73   (options[:random] ? wp.sort_by { rand } : wp).each { |image|
74     `#{options[:use]} #{image} > /dev/null 2>&1`
75     sleep options[:interval]
76   }
77 end

Improving it

Several ideas come to mind:

  • Add standard options like –help
  • Handle errors.
  • Make sure all the files added to the wp array actually are images.
  • Add an option to make the program run through the wallpapers once only (or a certain number of times.
  • Currently all the images get scaled by default (using wmsetbg). Add options for centering, tiling etc. images.
  • I’m calling a utility called wmsetbg, which comes with Window Maker. Instead of doing this, you could set the “paint” an image on the root window yourself. Look into Ruby/Xlib, Imlib2-ruby or any other library that you know of that will allow you access to the root window.
  • I’m sure you can think of lots more.

If you really wanted to take this little exercise a bit further you could create a custom format for the wallpapers file. For example, it could look like this

path/to/wallpaper:options

Where options would be something like scale, center, tile, etc. You could take it way too far and create an XML file with all kinds of options, such as display such-and-such image only during a specific time of the day, or on a specific day or interval of days. This way you could have special images for special occasions, such as Yule, Easter and so on. If you do decide to implement this, you may also want to consider a visit to your shrink. You are going waaay overboard and need help. Urgently.

Does it work with JRuby?

I’m glad you asked. Short answer: yes. Long answer: Hell, yeah! 😉

Does it work with Ruby 1.9?

Yes it does. Notice that there are some things you could do to ruby1.9-ify it. For instance, you could initialize the options array as follows:


options = {
  dir: nil,
  file: nil,
  interval: 60,
  random: false,
  use: wmsetbg
}

Does it work with Rubinius?

Yes, of course it does! 🙂

I did discover one small annoyance. Hitting CTRL+C will display the “Are you..” message alright, but the program will keep running. Not sure why, but I’ll look into that.

Older Posts »

Blog at WordPress.com.