January 29, 2008

Signs, Portents, and Little White Stickers

Remember that scene in Time Bandits when the giant creator's head was chasing them shouting, "RETURN WHAT YOU HAVE STOLEN"? Now picture the frozen noggin of Walt Disney saying it in the voice of a certain immortal/undead rodent.

I really like getting used books from Amazon. You can save a lot off the retail price and, so far, I've had great luck with regards to the condition of the books.

Last week I ordered Designing Virtual Worlds by Richard A. Bartle. Mr. Bartle famously co-wrote the first MUD. It arrived yesterday and I saw this sticker on the contents page:Interesting, eh? I sent Mr. Bartle an email and some scans which he added to his blog. He was even kind enough not to mention what a cheap bastard I was for buying it used.

I just hit chapter two and, so far, it is an excellent and entertaining read -- especially for someone like me that grew up on text adventure games and then bounced around MMORPG's.

January 26, 2008

"You are in a maze of twisty little passages, all alike"

When I was a kid, a pack of us used to play Dungeons & Dragons and I was often coerced into the role of Dungeon Master. Yes, it was as nerdy as it sounds but hey, it was fun and really, really cheap entertainment. One adventure I ran my victims, err ... cohorts through required them jumping blindly into portals to go from the current section into the next (really, it was just a way to buy time as I made up the next part). Plus, I could make up anything without needing it be consistent with the previous setting.

They hated the portals.

They were right to. It was a cheap and crummy design.

I'm fleshing out the geography system for the MUD, e.g. moving from one location to another. Historically, these are called 'rooms' with simplistic, direction based linking between them. You're in 'The Dining Hall' and go 'north' to be in the 'The Kitchen'. The simplicity of this design lends itself to a text based MUD. I could sweat area dimensions and the coordinates of players, creatures, and objects but how do I convey that to a player in a meaningful way using words alone? If you have to kill the goblin king to get his scepter, do you really care what bearing and how many meters away he is?

One concern I had was dropping players into a lady and the tiger scenario where they type 'east' and suddenly find themselves in certain death. The answer to this is the same as it is in books. Foreshadowing. Alan Quartermain doesn't sail his canoe smack into the headhunters' camp. He finds skulls on sticks, burned settlements, gross nasty things that all say, 'turn the hell back, Dude.'

In other words, it's not a programming problem.

January 20, 2008

Parsing Commands

This is a topic I thought about for a long time. My family thought I was just slacking about like a bum but I was really thinking. The challenge is converting what the player types into function calls -- only some function calls need more information than others and in differing order. I started with a huge, ugly IF-THEN sequence but quickly hated it. Plus, it wasn't very clean to expand.

I knew I wanted a dictionary lookup that would map verbs to functions and I wanted flexible control over which verbs different players could use -- 'equip sword' vs. 'cast fireball at Chadu'. The only common theme is every command begins with a verb.

I think my solution is both elegant and functional. I changed the dictionary to map verbs to functions and parsers. So the verb "tell" uses parser.dialogue (extract the recipient and the message) and "shout" uses parser.monoloque (no target, everything is the message).

Here's a snippet of where the Player class processes a line of input:
cmd = self.get_cmd()
verb, words = split_verb(cmd)

if self.has_ability(verb):
parser, function = shared.ABILITY_DICT[verb]
args = parser(words)
function(self, *args)

Notice the call to self.has_ability? We can grant and revoke abilities easily.

January 14, 2008

More about the Event Scheduler

In addition to the classic "main loop", the game will have an event scheduler. This is a queue for function calls. You pass it a command and the number of seconds from now in which to run it. This can be 1 / 1000 of second or years. Let's say you wanted to give a room some character by telling the players something like "You hear the scurrying of rats deep within the walls." Now, you don't want it to print every time someone enters or it seems canned and loses all effect. So you could do something like
event_scheduler.add(600, say_ambient, "You hear the scurrying of rats deep within the walls.")

So now the message will play in exactly 10 minutes. To make it even better, let's do this:
def the_rats():
say_ambient("You hear the scurrying of rats deep within the walls.")
offset = random.randrange(600,1200)
event_scheduler.add(offset, the_rats)

We just made a function that, once called, continues to re-schedule itself forever in random intervals between 10 to 20 minutes apart -- and the best part is we can forget all about it.

Let's say we wanted to implement a four-hour game-day. We could write functions for noon(), dusk(), midnight(), and dawn() which did things like sent farmers to the fields and turned vampires to dust in the sunlight. Each one of these would re-schedule itself, just like the_rats() did, but with a four hour delay (14,400 seconds). So in our start up code we do something like:
# Call noon right now

# call dusk() in 1 hour
event_scheduler.add(3600, dusk)

# call midnight() in 2 hours
event_scheduler.add(7260, midnight)

# call dawn() in 3 hours
event_scheduler.add(10860, dawn)
And our world begins to spin.

An even more efficient version would have each daily event schedule the next one to fire in one hour, round-robin style.

January 13, 2008

Progress Check

For the MUD, I have the following features done:
  • Asynchronous TCP communications -- Multiple Telnet clients can connect, send, and receive.
  • Event Manager -- Function calls can be scheduled to run (float X) seconds in the future.
  • Logging -- Log messages get recorded in a text file and displayed on the server's terminal.
  • SQLite Database storage -- SQLite3 is now part of Python 2.5 so I'm still on my track for my goal of zero dependencies.
  • ANSI Color Tokens -- This is something I did in my BBS days. If you want a message to show in color you can enter a simple code that begins with a caret. ^y is yellow and ^Y is bright yellow.
  • Word Wrap Function -- This takes a block of text and, given a target column width, formats it for easy reading.
  • Kicking Idle Clients -- Automatically drops clients who remain inactive for a period of time.

Here's an example of the word wrapping using four paragraphs from the Declaration of Independence. The first is the text simply dumped to the terminal:

Here's the thing run through the wrap_text() function:

I wanted it book-like and easy to read so we're padding the left and right and indenting the paragraphs slightly. A way more sophisticated function would break words with hyphenation.

January 11, 2008


Back in the early 90's I ran a computer BBS -- that's where someone spends thousands of dollars to let other people play with his computer. I ran a number of games like Tradewars 2002 , Global Wars, and Murder Motel where you had X number of moves to find a weapon, victim, and hiding spot. It was multi-one-player-at-a-time, but great fun. Joe and Bill Trebing took pity on some poor kid and invited them into their Tradewars corporation. The kid thanked them by activating the laser cannons, kicking them out of the company and into space, then blasting them to sparkley dust.

I wrote a chess game where people could play each other and some utilities like a message tosser that converted from RBBS format to TriBBS.

Last weekend I started writing a MUD in Python. It will be a text based game that you Telnet into. Yes, it's extremely retro but makes for an interesting geek project and, since I use a lot of Python at work, is a good way to brush up on my programming skills. And, unlike the old BBS games, this will be real multi-player.

At first, I was going to use a library called Twisted Matrix for networking but then decided to try writing the whole thing using only those libraries that come with Python 2.5.

The trick is to design it to be non-blocking. What is blocking, you ask? Imagine a restaurant with only one employee. He's the cook, waiter, busboy, dishwasher, and cashier. In walks Mr. Ficklehead who can never make up his mind. A blocking waiter has to stand there while Ficklehead reads the menu front to back. The food burns, the customers don't get seated, people run out on the check -- a mess.

A non-blocking waiter runs to every tables and goes, "Haveyoudecidedyet? No? That'soktakeyourtime, I'llbebackinasecond" and whoosh, moves on. Everything gets a split second of attention.

Another method is call multi-threading which would be like cloning yourself. That sounds good until your run into problems like telling three clones to wash the car, change the oil, and drive to the store for groceries. Keeping any two clones from flopping on the couch to watch reruns of Scrubs is just as tricky as it sounds. Besides, some of the Python libs are not thread safe to begin with.

So I'm going with single threaded asynchronous networking.