Keeping it Small and Simple

2008.02.28

Confused

Filed under: Politics — Tags: , , — Lorenzo E. Danielsson @ 23:48

What the hell does this mean? (Taken from here.)

Q You can get the Congress to protect telecom companies from lawsuits, but then there’s no recourse for Americans who feel that they’ve been caught up in this. I know it’s not intended to spy on Americans, but in the collection process, information about everybody gets swept up and then it gets sorted. So if Americans don’t have any recourse, are you just telling them, when it comes to their privacy, to suck it up?

THE PRESIDENT: I wouldn’t put it that way, if I were you, in public. Well, you’ve been long been long enough to — anyway, yes, I — look, there’s — people who analyze the program fully understand that America’s civil liberties are well protected. There is a constant check to make sure that our civil liberties of our citizens aren’t — you know, are treated with respect. And that’s what I want, and that’s what most — all Americans want.

I am not going to ask questions that others have asked already, but I wonder what the hell the following is supposed to mean:

I wouldn’t put it that way, if I were you, in public.

I’m guessing that it means that the tool for manipulating the public that is known as media, stepped out of line and asked the wrong question.

Advertisements

Double oppression

Filed under: Politics — Tags: , , , — Lorenzo E. Danielsson @ 21:59

Just stumbled upon this and found it hilarious. Not only does the “employment contract” make you a wage slave but you are also held a moral hostage via a “social contract”? Er.. I think not.

In a free world, all forms of association are purely voluntary. Anything else should be resisted. If your freedom is at stake, fuck your grades, fuck your career, fuck your dreams, fuck having a wife, two kids, a house and a dog. Fuck it all, because without freedom those things don’t mean a damn thing.

My advice? Don’t listen to career consultants. They would have you slaving away for a company that you don’t even own without even blinking. Some would call them modern day slave merchants, but I prefer the more technical term “cock suckers”.

2008.02.27

Alfresco on Debian: using MySQL

Filed under: Alfresco — Tags: , , , , — Lorenzo E. Danielsson @ 16:44

Yesterday I blogged about how to perform the most basic possible installation of Alfresco on a Debian system. That setup included using Hypersonic SQL as the database backend. Eventually you will want to migrate to using MySQL instead. Let us look at how to set it up.

I am assuming that you have grabbed the Alfresco Tomcat bundle (the community version). I am also assuming that you are running Debian Etch or beyond (if you are running experimental and things don’t work, that’s your mess to sort out). You should also have MySQL installed and up and running. I have MySQL 5.0.45 here and that works fine.

Setting it up

Before you start, make sure the Alfresco server is not running. Shut it down if it is. Then you need to run an SQL script that will create the MySQL database for Alfresco to use. Do this (as root):

# cp /opt/alfresco/extras/databases/mysql
# mysql --user root -p < db_setup.sql

You should be prompted for the password of the MySQL root user. If you don’t have a password for the root user, you should leave out the -p option. Of course, you are already aware of the security implications of not having a password set, so I won’t go over that here. Your server, your choice.

Next, you need to edit two files located in /opt/alfresco/tomcat/shared/classes/alfresco/extension. The first one is custom-repository.properties where you need to comment out the lines relating to HSQL and uncomment the MySQL ones.
Here is the part of the file that needs to be modified with the changes made.

#
# HSQL connection
#
#db.driver=org.hsqldb.jdbcDriver
#db.url=jdbc:hsqldb:file:alf_data/hsql_data/alfresco;ifexists=true;shutdown=true
;

#
# MySQL connection (This is default and requires mysql-connector-java-5.0.3-bin.
jar, which ships with the Alfresco server)
#
db.driver=org.gjt.mm.mysql.Driver
db.url=jdbc:mysql://localhost/alfresco

The other file that needs to be edited is custom-hibernate-dialect.properties. Again, un-comment the line that relates to MySQL and comment out the one relating to HSQL. Look at the very top of the file and you will find the lines to modify.

#
# HSQL dialect
#
#hibernate.dialect=org.hibernate.dialect.HSQLDialect

#
# MySQL dialect (default)
#
hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect

If this was a completely new Alfresco installation and you have never run it before you should be good to go. Start the Alfresco server and wait. It does take a little longer to start Alfresco when you are using MySQL. You could follow along by doing:

# tail -f /opt/alfresco/alfresco.log

That will give you Alfresco’s log output in the terminal.

If it doesn’t work

If the Alfresco server has been started at least once before migrating to MySQL, Alfresco will probably not start. You will be able to start Tomcat, but the Alfresco application will not be available. If this is the case, look inside the log file and you should find a few lines like this:

Caused by: org.alfresco.repo.search.SearcherException: More than one root node i
n index: 2

This fix for that, which is available here is:

  1. # cd /opt/alfresco
  2. # ./alfresco stop
  3. # rm -rf alf_data/lucene_indexes
  4. # cd tomcat/webapps/alfresco/WEB-INF/classes/alfresco
  5. # vim repository.properties

Make the following changes to the file:

# The index recovery mode (NONE, VALIDATE, AUTO, FULL)
#index.recovery.mode=VALIDATE
index.recovery.mode=FULL

# Change the failure behaviour of the configuration checker
#system.bootstrap.config_check.strict=true
system.bootstrap.config_check.strict=false

Now you can start Alfresco again. Hopefully it will work. If it doesn’t, stop Alfresco again, drop the database and re-create it, remove the /opt/alfresco/alf_data/lucene-indexes directory again and start. Then it *should* work.

Once you have been able to start Alfresco successfully and gone to http://localhost:8080/alfresco to confirm that it is indeed working, you can revert the changes that you made in /opt/alfresco/tomcat/webapps/alfresco/WEB-INF/classes/alfresco/respository.properties. If you don’t, Alfresco will attempt a full index recovery every time you start Alfresco.

The best option is probably that you configure Alfresco to use MySQL instead of HSQL before you start it the first time. That way you should not have any issues (at least I never have).

Other thing that could go wrong

Occasionally you see error messages like this one in your log file:

java.io.IOException: Cannot bind to URL [rmi://localhost:50500/alfresco/jmxrmi]:
 javax.naming.NameAlreadyBoundException: alfresco/jmxrmi [Root exception is java
.rmi.AlreadyBoundException: alfresco/jmxrmi]

They will show up if you try to start Alfresco when its already running. But there are a few other circumstances as well. One is if you shut it down and restart it without giving it enough time to fully shut down. You can do:

# ps ax | grep java

to see if there are any java processes related to Tomcat and/or Alfresco still alive. If you find any, give them a minute or two to terminate.

While you are trying to migrate to MySQL, it sometimes happens that some processes refuse to close (I have waited for over twenty minutes without them croaking). In that case, just use ps to find the PID of the process and manually kill it.

For example, suppose I find this:

# ps ax | grep java
11180 pts/0    Sl     0:40 /usr/lib/jvm/java-6-sun/bin/java -Xms128m -Xmx512m -server -XX:CompileCommand=exclude,org/apache/lucene/index/IndexReader$1,doBody -XX:CompileCommand=exclude,org/alfresco/repo/search/impl/lucene/index/IndexInfo$Merger,mergeIndexes -XX:CompileCommand=exclude,org/alfresco/repo/search/impl/lucene/index/IndexInfo$Merger,mergeDeletions -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file=/opt/alfresco/tomcat/conf/logging.properties -Djava.endorsed.dirs=/opt/alfresco/tomcat/common/endorsed -classpath :/opt/alfresco/tomcat/bin/bootstrap.jar:/opt/alfresco/tomcat/bin/commons-logging-api.jar -Dcatalina.base=/opt/alfresco/tomcat -Dcatalina.home=/opt/alfresco/tomcat -Djava.io.tmpdir=/opt/alfresco/tomcat/temp org.apache.catalina.startup.Bootstrap start
11362 pts/0    R+     0:00 grep java

The process with PID 11180 is an Alfresco process. If I have waited long enough for this to terminate, but it isn’t, step in and be the executioner:

# kill 11180

Conclusion

Migrating Alfresco to MySQL involves a little more work than just using HSQL, but not much more. It really isn’t that complex, just that it can be a bit frustrating when you keep trying to get it up and running and it keeps refusing. But just monitor you log file and you should be okay.

Next time I’ll look at how to set Alfresco using only the WAR file, into your own Tomcat installation. This is a good idea if you already have Tomcat installed. There are a few additional things you will have to do if you use your own Tomcat installation instead of the bundled one. But, on the other hand, using our own gives us the opportunity to experiment with Alfresco on Tomcat 6.

Pygame tutorial #8: the worm game

Filed under: Pygame Tutorial — Tags: , , , — Lorenzo E. Danielsson @ 11:46

After the little detour I took with tutorial #7, we are going back to writing our little worm game. I am going to write a first, (barely) playable version here, and then we are gradually going to improve upon it.

As always, I am taking things really slow. That way I hope everybody can keep up. There are loads of other, more advanced pygame tutorials floating around for those who feel I’m moving too slowly. Many of them are really good.

Rules of the game

The idea behind the game is very simple. You control a worm that move around the screen looking for food. When the worm gets the food, you get some points and the worms grows longer. Crashing onto yourself or the borders ends the game.

I first played the worm game on a VIC-20, which means this was one of the first games I ever played.

The Worm class

We will expand a little upon the worm class the we developed earlier. I’m going to take it method by method. You will find the whole program at the end of the tutorial.

Let’s look at the constructor first. Here we just set up some information.


 1 def __init__(self, surface):
 2     self.surface = surface
 3     self.x = surface.get_width() / 2
 4     self.y = surface.get_height() / 2
 5     self.length = 1
 6     self.grow_to = 50
 7     self.vx = 0
 8     self.vy = -1
 9     self.body = []
10     self.crashed = False
11     self.color = 255, 255, 0

This constructor only takes the surface as an argument. We position to worm in the center of the screen initially (remember the x and y attributes hold the position of the worm’s head). The worm’s initial length is 1. I have added a new attribute, grow_to. As long as length is less than grow_to our worm hasn’t reached full length yet. This will allow the worm to grow, something that is important in the game.

Apart from that, most things should be straight-forward. The vx (horizontal velocity) and vy attributes control the direction of the worm. Each one of these can be -1, 0 or 1. Depending on the direction of motion. The color attribute should be obvious. The body attribute is an array holding all the positions of the worm’s body. Crashed is a flag that gets set to true if the worm bumps into itself.

The worm’s event() method is hopefully clear now. It looks like this:

 1 def event(self, event):
 2     """ Handle keyboard events. """
 3     if event.key == pygame.K_UP:
 4         self.vx = 0
 5         self.vy = -1
 6     elif event.key == pygame.K_DOWN:
 7         self.vx = 0
 8         self.vy = 1
 9     elif event.key == pygame.K_LEFT:
10         self.vx = -1
11         self.vy = 0
12     elif event.key == pygame.K_RIGHT:
13         self.vx = 1
14         self.vy = 0
15

The move() method has changed slightly. Let us look at what it does.


 1 def move(self):
 2     """ Move the worm. """
 3     self.x += self.vx
 4     self.y += self.vy
 5
 6     if (self.x, self.y) in self.body:
 7         self.crashed = True
 8
 9     self.body.insert(0, (self.x, self.y))
10
11     if (self.grow_to > self.length):
12         self.length += 1
13
14     if len(self.body) > self.length:
15         self.body.pop()
16

First we move the worm’s head to its new position. Where this is will depend on the values of vx and vy (note that exactly one of these will be non-zero). Next we check if the new position of the worm’s head exists in the worm’s body array. If it does, we have crashed onto ourselves and set the crashed flag.

The new position then gets inserted into the worm’s body array. We then check if the worm is growing. If it is, simply increase the worm’s length by 1. Finally, if we have reached full length, then we need to remove the last item of the body array. Make sure you understand how the different parts of this method work together to make the worm move and grow. See if you can figure out a few ways to improve it (it shouldn’t be too difficult once you’ve got the point of the code).

The draw() method should be familiar by now. It just plots out each one of the points in the worm’s body array.

1 def draw(self):
2     for x, y in self.body:
3         self.surface.set_at((x, y), self.color)
4
5

There is a new method that I’ve called position(). This will return the x and y-coordinate of the worm’s head. This is just there for convenience.


1 def position(self):
2     return self.x, self.y
3
4

Another new method is the eat() method. This just instructs the worm to start growing.


1 def eat(self):
2     self.grow_to += 25
3

That is how the Worm class works. That main thing that is new about it as compared to the one we did in tutorial #5 is that this worm has the ability to grow. In order to grow, this worm needs food, so let’s look at that next.

The Food class

The Food class needs a surface to draw itself onto, just like the Worm class. Apart from that it holds it x and y coordinates and color. It contains a method to draw itself and one to get its position in the form x, y. It looks as follows.


 1 class Food:
 2     def __init__(self, surface):
 3         self.surface = surface
 4         self.x = random.randint(0, surface.get_width())
 5         self.y = random.randint(0, surface.get_height())
 6         self.color = 255, 255, 255
 7
 8     def draw(self):
 9         self.surface.set_at((self.x, self.y), self.color)
10
11     def position(self):
12         return self.x, self.y
13

When a new Food is created, it places itself at a random location on the surface it gets passed.

The game

Putting our two classes to use, we come up with this:


  1 #! /usr/bin/env python
  2
  3 # A simple worm game.
  4
  5 import pygame
  6 import random
  7
  8 class Worm:
  9     def __init__(self, surface):
 10         self.surface = surface
 11         self.x = surface.get_width() / 2
 12         self.y = surface.get_height() / 2
 13         self.length = 1
 14         self.grow_to = 50
 15         self.vx = 0
 16         self.vy = -1
 17         self.body = []
 18         self.crashed = False
 19         self.color = 255, 255, 0
 20
 21     def eat(self):
 22         self.grow_to += 25
 23
 24     def event(self, event):
 25         """ Handle keyboard events. """
 26         if event.key == pygame.K_UP:
 27             self.vx = 0
 28             self.vy = -1
 29         elif event.key == pygame.K_DOWN:
 30             self.vx = 0
 31             self.vy = 1
 32         elif event.key == pygame.K_LEFT:
 33             self.vx = -1
 34             self.vy = 0
 35         elif event.key == pygame.K_RIGHT:
 36             self.vx = 1
 37             self.vy = 0
 38
 39     def move(self):
 40         """ Move the worm. """
 41         self.x += self.vx
 42         self.y += self.vy
 43
 44         if (self.x, self.y) in self.body:
 45             self.crashed = True
 46
 47         self.body.insert(0, (self.x, self.y))
 48
 49         if (self.grow_to > self.length):
 50             self.length += 1
 51
 52         if len(self.body) > self.length:
 53             self.body.pop()
 54
 55     def draw(self):
 56         for x, y in self.body:
 57             self.surface.set_at((x, y), self.color)
 58
 59     def position(self):
 60         return self.x, self.y
 61
 62 class Food:
 63     def __init__(self, surface):
 64         self.surface = surface
 65         self.x = random.randint(0, surface.get_width())
 66         self.y = random.randint(0, surface.get_height())
 67         self.color = 255, 255, 255
 68
 69     def draw(self):
 70         self.surface.set_at((self.x, self.y), self.color)
 71
 72     def position(self):
 73         return self.x, self.y
 74
 75 w = 500
 76 h = 500
 77
 78 screen = pygame.display.set_mode((w, h))
 79 clock = pygame.time.Clock()
 80
 81 score = 0
 82 worm = Worm(screen)
 83 food = Food(screen)
 84 running = True
 85
 86 while running:
 87     screen.fill((0, 0, 0))
 88     worm.move()
 89     worm.draw()
 90     food.draw()
 91
 92     if worm.crashed:
 93         running = False
 94     elif worm.x <= 0 or worm.x >= w – 1:
 95         running = False
 96     elif worm.y <= 0 or worm.y >= h – 1:
 97         running = False
 98     elif worm.position() == food.position():
 99         score += 1
100         worm.eat()
101         print "Score: %d" % score
102         food = Food(screen)
103     
104     for event in pygame.event.get():
105         if event.type == pygame.QUIT:
106             running = False
107         elif event.type == pygame.KEYDOWN:
108             worm.event(event)
109
110     pygame.display.flip()
111     clock.tick(240)

We initialize a counter called score to 0 (unless you want to cheat of course). We create a our worm and an initial food item.

In the game loop we need to make sure we draw not only the worm but the food item as well. This is done with food.draw().

In addition to checking if the worm has crashed with itself or the borders, we check if the worm has eaten the food. This is where the position() methods in the Worm and Food classes comes in handy. If the worm’s head is at the same location as the food the worm has taken the food. We then increase the score and print the new score to the terminal (which sucks, I know, but don’t worry one of our improvements will be to get the score onto the game screen). We also need to signal the worm to start growing. We do that by calling worm.eat(). Finally, since the food item has been eaten, we need to generate a new one.

The rest of the program should be familiar by now.

Running it

The one thing you will notice quickly with this game is how annoyingly difficult it is to eat the food (unless you have really good eye-sight which I don’t). This will be the first improvement we have to make to the game.

Exercises

1. Ew! There are like magic numbers, EVERYWHERE! Find each one and change it. It’s often useful to create attributes. For instance the worm is hard-coded to grow 25 pixels longer each time it eats. You could change that to an attribute called grow_by (or whatever you like). That means it can be manipulated from outside the class.

2. Play around with the initial worm size and the amount that the worm grows by when it eats. By modifying these you can affect how difficult the game is when it first starts and how much more difficult it gets over time.

3. Currently the program ends as soon as the worm crashes. Change it so that the user gets asked if they want to play again.

4. Build upon exercise 2 to keep a track of the high score.

5. Imagine you wanted to turn this into a two-player game. What would you need to change in order to make the Worm class re-usable?

6. Add the concept of lives to the game. The player should start off with three lives. Each time they crash the lose a life. When they run out, the game is over.

7. Let the worm start at a random position.

8. Suppose you wanted to implement difficulty levels. What things would you vary in order to make the game more or less difficult?

Conclusion

Now that we have a functional game, we need to make it properly playable as well. The first thing we need to do is to make the food item a little larger so that our worm stands a reasonable chance of getting it. It would be nice if the worm could make a “chomp” sound when it takes a bite as well. That will be the topic for the next tutorial.

Why do we have hands?

Filed under: Humor — Tags: , — Lorenzo E. Danielsson @ 01:05

Find out here.

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!

Setting up Alfresco on Debian (Tomcat bundle)

Filed under: Alfresco — Tags: , , , — Lorenzo E. Danielsson @ 23:09

I have been playing around a lot with Alfresco of late. I’ll write some posts about what I have learned up to now, just in case it helps somebody out there.

Before we begin, here is what I’m using:

  • Debian Etch. For most part, these instructions should work on any Linux system.
  • Sun Java 6 (packages from Sid repository)

Installation

In this post I am going to use the Alfresco Tomcat bundle, which is available here. I will start by using all the defaults, which will use Hyperonic SQL as a backend database. This may not be a good idea in the “real world”, but is acceptable for testing. Most importantly, we should make sure that works before moving on.

Once you have downloaded Alfresco, you need to extract it. Perform the following (as root):

# mkdir /opt/alfresco
# cd /opt/alfresco
# tar zxf /home/lorenzod/dl/alfresco-community-tomcat-2.1.0.tar.gz

Of course, you will need to change the path in the last line to wherever you downloaded Alfresco to. If everything went well, ls should give you the following files (and directories):

alf_data
amps
extras
README_osx.txt
zstart_oo.sh
alfresco.log
apply_amps.sh
licenses
README.txt
alfresco.sh
bin
README_mysql.txt
tomcat

Starting the server

Before you can start the Alfresco server, you need to set JAVA_HOME if you have not already done so. On my system, I did the following:

# export JAVA_HOME=/usr/lib/jvm/java-6-sun

(Check to make sure where your JDK is installed.) To save yourself from having to type this over and over again, add that line to file such as /etc/profile. That way JAVA_HOME will automatically be set for you.

Now you should be able to start Alfresco. Type (still as root):

# ./alfresco.sh start

Hopefully you will see this message:

Using CATALINA_BASE:   /opt/alfresco/tomcat
Using CATALINA_HOME:   /opt/alfresco/tomcat
Using CATALINA_TMPDIR: /opt/alfresco/tomcat/temp
Using JRE_HOME:       /usr/lib/jvm/java-6-sun

If, on the other hand, you get this message:

Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
At least one of these environment variable is needed to run this program

Then JAVA_HOME hasn’t been set properly. Go back and follow the instructions, properly this time. Once Alfresco is up and running, open a browser and go to http://localhost:8080/alfresco if you are browsing from the same machine that you installed Alfresco to, or http://your-server:8080/alfresco if you are browsing from a different machine.

If that doesn’t work, try http://localhost:8080 to make sure Tomcat itself is up. Depending on your hardware, it make take a while for Alfresco to fully pull itself up, so give it a little time and try again. Otherwise look at the log file (/opt/alfresco/alfresco.log), something you should do even if Alfresco does run properly.

Once you see the Alfresco dashboard in your browser, you can click around a little to familiarize yourself with the interface. At the top you will see a login link. Click there and you can log in. The only user that exists by default is admin with password admin.

Peeking at the log file

When you look at the log file there are some errors and warnings that you might see. I will try to explain them as we go along.

With the current set-up the only error you are likely to see is:

21:31:17,079 ERROR [org.alfresco.smb.protocol.netbios] NetBIOSNameServer setup e
rror:
java.net.BindException: Address already in use

This indicates that you already have Samba running. This will prevent Alfresco’s own CIFS server from starting. To prevent this, stop the Samba server before running Alfresco. Note that this error will not prevent Alfresco itself from running, but you will not be able use its CIFS server.

Shutting down the server

To shut down Alfresco, go to /opt/alfresco and type ./alfresco stop. If anything goes wrong during shutdown, there may be some running Java processes on the system. Use ps to locate them, and kill them manually. Normally you shouldn’t have any problems shutting Alfresco down.

Conclusion

As you have seen, getting Alfresco up and running on Debian is not difficult. Next time, I will show you how to migrate the database to MySQL, and some of the common issues you are likely to encounter along the way.

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.24

Mozilla Messaging

Filed under: Software — Tags: , , , , — Lorenzo E. Danielsson @ 13:12

So work on Thunderbird 3 is underway. Mozilla Messaging is a project to develop the next generation of Thunderbird and to integrate it with calendaring. This is good news. I have begun to really like Thunderbird 2, and have got to a point where I use it more than mutt.

Calendaring will be based on Mozilla Lightning, a plug-in version of Sunbird for Thunderbird. I am not really a fan of big applications. I would prefer that my MUA does email, and a separate application handles calendaring, as is the case today with Thunderbird/Sunbird. On the other hand, the masses seem to love bloatware these days.

There are probably benefits of having an integrated email/calendaring solution as well. Thunderbird provides a contact list (via the addressbook), and this information is used by the calendaring component as well (for meetings, for instance). I’ll try to keep an open mind about this, and I might even install Lightning again, just to see if I can get comfortable with the idea of my toaster being able to launch SCUD missiles.

It seems they are going to improve searches as well. That would be a good thing, since searching in Thunderbird currently er.. leaves a lot to be desired (although there are a few extensions that might help you out). While they’re at it, they should do some work on the contact list component as well, which I am not too fond of.

I hope that Thunderbird now gets the attention it deserves beside Firefox. It has always felt as Thunderbird development hasn’t been prioritized as much as the browser development. This is not fair, since Thunderbird also has a large user base. Also, we need a good alternative to the crap that the Gnome and KDE projects want to force upon us, otherwise those desktops will enslave us all one day.

2008.02.22

Xobni? I don’t think so

Filed under: Technology — Tags: , , — Lorenzo E. Danielsson @ 08:03

I stumbled upon this post and got a little curious. Xobni? Never heard of it before.

A quick glance at the “Learn more” page on their web site revealed all I needed to know:

Xobni Insight is an add-on for Microsoft Outlook that offers effortless email management and provides instant access to the most important information in your email.

Useless crap in other words. (If you don’t know why, try doing an aptitude search outlook. That’s right, outlook doesn’t even exist.)

No, this is no new Google. Writing plug-ins for a MUA that only works on a game console won’t get you far.

Older Posts »

Create a free website or blog at WordPress.com.