IdleRPG at a LAN party

1 September 2025

The Lobsters Blog Carnival has invited us all to write about something we've made made for ourselves, particularly if it's not especially useful to anyone else. My peak period for writing marginally-useful-but-cursed software was about 15 years ago so today we're travelling back to 2008 when I was regularly attending LAN parties at uni.

These LANs were hosted by students in the computing society, running overnight on a weekend and usually getting about twenty people. Along with the likes of Far Cry, Brood War, and DOTA (the Warcraft III version) we also enjoyed games that you could leave running in the background like OpenTTD and Progress Quest. One day I had a not-especially-brilliant idea: what if we played competitive IdleRPG?

For those not in the know, IdleRPG is an IRC bot where the goal is to join an IRC channel, log in to the bot via private message, then do absolutely nothing. You are penalised for quitting or speaking. As time elapses your character goes on adventures, good and bad things happen to them at random, and they acquire increasingly higher levels. The amount of time required to reach each level increases exponentially, as do the penalties, so a disconnection can become absolutely punishing. Hopefully it is clear how silly this is: apart from being in the channel there's not really a competitive element. All else being equal, the character who attains the highest level is entirely random. This made it all the more appealing.

From a technical perspective there wasn't much stopping me. IdleRPG is an open source perl script. It was no problem to build and run an ircd on my trusty iBook G4 that everyone can connect to, along with the bot. The only problem was, it just wasn't going to be very good or fun.

IdleRPG had some customisation built in. It was pretty easy to crank up the speed and also replace the "good" and "bad" events with goofy things inspired by the memes everybody knew. For the rest I was going to need some code.

At this time I'd recently picked up some casual work where my boss introduced me to PHP and Postgres. Not in a particularly good way, mind you. The situation was more like "here's some code I wrote and maybe you can write more of it for me" and at that point I didn't know what SQL injection was. With these mediocre skills in hand I enabled the Apache and PHP server that Apple shipped in OS X and built a preregistration form where you could enter your character details and password and they would be stashed in a database table.

The trick would be to get this into the bot somehow. Here I caught a lucky break: the irpg.db is plain text TSV and self-documenting. Here's an example of the two top lines (heavily wrapped since they're quite long):

# username	pass	is admin	level	class	next ttl	nick	userhost	online	idled	x pos	y pos	pen_mesg	pen_nick	pen_part	pen_kick	pen_quit	pen_quest	pen_logout	created	last login	amulet	charm	helm	boots	gloves	ring	leggings	shield	tunic	weapon	alignment
funny	fojM7XE1OWcyY	0	24	mermaid	266	googoo	googoo!~user@host	1	2395	30	5	0	0	0	0	0	0	0	50	50	32	32	11	24	27	19	19	28	27	13	n

It would be quite easy to take the details from the preregistration and create a line for each person. One thing that was especially useful was the online field. This indicates if the user is currently logged on. The bot used this on startup so that if it got disconnected or restarted, it would give everybody the benefit of the doubt and continue as if they had been idling. If I set this flag on all the new characters and started the bot, it would auto-login anybody already in the channel—assuming they typed in their IRC nicknames correctly, anyway. The bot also periodically updates this database with everybody's levels and time-to-next-level which makes it easy to read out the current scores.

Now I just needed to tie this all together, which I did with some python.

# Change these times when not debugging:
starttime = 1400
endtime = 1800

def mainloop():
	global mode, t
	while mode != 2:
		print "Heartbeat, t = %d" % t
		
		# Start the game if it's now 2pm
		if mode == 0 and int(time.strftime("%H%M")) >= starttime:
			startgame()
			mode = 1
		
		# End the game if it's now 6pm	
		if int(time.strftime("%H%M")) >= endtime:
			stopgame()
			mode = 2
			refreshscores()
		
		# Refresh the scores page every 10 seconds. It will autorefresh every 10 seconds
		if t % 10 == 0:
			refreshscores()
		
		t += 1
		time.sleep(1)

Please enjoy my very robust scheduling code. As you can see, this program was responsible for updating the user-friendly scoreboard, as well as starting and stopping the game. Now let's see what happens when the game starts:

def startgame():
	print "Building irpg.db from database..."

	# Open character database file
	dbfile = open('irpg.db', 'w')
	dbfile.write("funny	fojM7XE1OWcyY	1	1	Mage	20	funny	funny!~funny@localhost	1	17	17	49	0	0	0	0	0	0	0	1221823719	1222069216	0	0	0	0	0	1	0	0	0	0	n\n")
	# Take pre-registrations
	preregos = dbquery("SELECT * FROM preregistrations")
	for r in preregos:
		row = "%s\t%s\t0\t0\t%s\t20\t%s\t%s!~user@host\t" \
			% (r['charname'], crypt.crypt(r['charpassword'], 'foobar'), r['charclass'], r['ircnick'], r['ircnick'])
		row += "1\t0\t%d\t%d\t0\t0\t0\t0\t0\t0\t0\t50\t50\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\tn\n" \
			% (int(random.random()*50), int(random.random()*50))
		dbfile.write(row)
		print "Set up character %s for IRC nick %s" % (r['charname'], r['ircnick'])
	dbfile.close()
	
	# Disable pre-registrations
	db.query("UPDATE gameinfo SET preregosallowed = false")
	
	# Now that the idleRPG database is written, start up the bot
	print "Starting up the bot..."
	commands.getoutput('perl bot.v3.1.2.pl')

Here we write out a fresh database, starting with my own character hard-coded. (I was self-disqualified from winning, of course.) I had to crypt the password in the way that irpg expected in case anybody needed to re-login, so I guess the Postgres database had plaintext passwords. Marvellous. Being careful to set the "online" field to 1, everybody starts out as a new level 0 character. Then we disable the preregistration form and execute the idlerpg perl script, making it join the channel.

What about when we reach the end time?

def stopgame():
	print "Shutting down the bot..."
	HOST="localhost"
	PORT=6667
	NICK="pyminder"
	IDENT="pyminder"
	REALNAME="pyminder"
	readbuffer=""
	
	s=socket.socket( )
	s.connect((HOST, PORT))
	s.send("NICK %s\r\n" % NICK)
	s.send("USER %s %s bla :%s\r\n" % (IDENT, HOST, REALNAME))
	#s.send ( 'JOIN #idle\r\n' )
	s.sendall('PRIVMSG idlebot :SPECIALCOMMANDGOESHERE\r\n')
	readbuffer=readbuffer+s.recv(1024)
	temp=string.split(readbuffer, "\n")
	readbuffer=temp.pop( )
	
	for line in temp:
		line=string.rstrip(line)
		line=string.split(line)
		
		if(line[0]=="PING"):
		    s.send("PONG %s\r\n" % line[1])
	
	time.sleep(5)
	s.close()
	
	# This totally isn't really nice. It won't write the DB file.
	#commands.getoutput('kill `cat .irpg.pid`')
	
	# Generate final scores page
	refreshscores()
	
	# Play a sound
	commands.getoutput('open -a vlc fanfare.wav')
	
	# All done. Bot will terminate after this method

As you can see, this function implements an IRC client. Obviously. How else could we possibly tell the bot to shut down cleanly so that we can read the final DB? I don't think this functionality existed so I hacked it into the perl script—when it received a particular secret string via PRIVMSG it would shut down. Then of course the iBook should play a sound to announce the end of the game, a task for which VLC is perfectly suited.

How does that refreshscores() function work, anyway? Well...

def refreshscores():
	print "Refreshing scores.html..."
	
	# Start of file
	sc = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n\
        \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n\
<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n\
\n\
<head>\n\
	<title>Scores - LAN idleRPG</title>\n\
	<meta http-equiv=\"content-type\" \n\
		content=\"text/html;charset=utf-8\" />\n\
	<meta http-equiv=\"Content-Style-Type\" content=\"text/css\" />\n\
	<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\" />\n\
	<meta http-equiv=\"refresh\" content=\"10\"/> \n\
</head>\n\
\n\
<body>\n\
<h1>LAN idleRPG Scores Page</h1>\n\
<div align=\"center\">\
"
	
	# This is where we need to write stuff to sc depending
	# on what mode we're in, yada yada
	if mode == 0:
		sc += "<p>The game hasn't started yet! This page will automatically update!</p>"
	if mode == 1 or mode == 2:
		commands.getoutput('cp irpg.db irpg.db.tmp')
		f = open('irpg.db.tmp', 'r')
		f.readline()	# comment line
		if mode == 2:
			sc += "<p><b>SCORES ARE FINALISED! WINNER IS TOP CHARACTER!</b></p>"
		sc += "<table>\n"
		
        # (snip... writing rows here...)
	
	scf = open('scores.html.temp', 'w')
	scf.write(sc)
	scf.close()
	commands.getoutput('mv scores.html.temp scores.html')

Glorious. I exposed this scores.html on Apache and I was ready to go. Everything seemed to be working... now I just had to see if it held together on the day.

Incredibly, it was a complete success. Around a dozen people registered and participated. The game started on time and everybody got logged in. During the session, one individual who shall not be named wanted to throw some extra randomness into the mix since they were losing. They snuck over to the iBook and disconnected its ethernet cable. I caught them in the act and ensured the cable went back in a few seconds later. By good fortune, this didn't invalidate any TCP connections and everything carried on as if nothing had happened. We did have the scores HTML page on a projector and in the few minutes before 6pm we enjoyed the surreal situation of twenty people staring[1] at an HTML table self-refreshing every 10 seconds. Luckily I had juiced the bot's parameters enough that there was some jostling right up to the end.

By complete chance my archive of this code contains the full state from when the competition took place in November 2008.

I distinctly remember that the prize for the winner was a CD, a compilation of Boney M's greatest hits. I hope they enjoyed it as much as the game.


  1. Coincidentally I witnessed an even more extreme surreal silence on the same university campus only a few months later. This occurred at linux.conf.au 2009. There was a fundraiser at the conference dinner to help Tasmanian devils and one of the gambits to solicit more donations was that Linus Torvalds would personally shave Bdale Garbee's beard. This took place in front of hundreds of people in the university's main lecture theatre the following morning. Shaving somebody actually takes a few minutes, especially if you're mostly used to shaving yourself, and the audience wasn't quite sure what to do with themselves after he got going. I took some photos. ↩︎

Serious Computer Business Blog by Thomas Karpiniec
Posts RSS, Atom