Skip to page content

Implementing a Simple Task with qtMUD 0.3.1

Tag: qtMUD.

Introduction

In this piece I’d like to explore implementing a simple task-handler using qtMUD 0.3.1. In doing so, I hope to explain (for the first time, so bear with me) some of the concepts I’ve implemented in my MUD engine, and how they’re used.

To explain the concepts I’m going to be using literate programming, or at least my lazy and quick interpretation of it.

Creating the Universe

So, to get started, we declare which programming language we’re going to be using (Racket), we require the qtMUD module, and we start a logger, which provides messages to the REPL that we get once we run the MUD.

NOTE: If you want to play along at home, 1) You’ll need to get qtMUD 0.3.1 (and Racket) 2) Open a Racket REPL with the racket command. 3) Start entering the commands in the code blocks below, making sure that your require line points to the actual location of your qtMUD’s main.rkt.

#lang racket

(require "../qtmud/main.rkt")

(run-logger (create-logger 'TaskMUD))

Once we’ve set up our logger, we can create our new task-handling universe.

(define taskverse (create-universe "Task-Handler"))

This creates a new data record of qtMUD’s #<universe> struct type. Next, we can save ourselves some time down the line by defining a create-thing procedure that creates things directly into our taskverse.

(define create-thing
 (create-thing-creator-for-universe taskverse))

This means that when we call create-thing, any thing (another data record like #<universe>) that’s created will have its universe set to the taskverse, and the taskverse will have the created thing added to its list of things.

Planning Tasks

At this point we’re ready to start making new things, which in this context are tasks.

Before we can get started with that though, it’s important to define at least some of the qualities of a task: what is it that’ll make a task a task, and not a blueberry or a hot air balloon or the concept of debt?

You see, in qtMUD - which stands for the “Qualities of Things MUD” - every thing has a set of qualities which define its attributes. Every thing also has a set of procedures, which define its capabilities.

To make a task, we’re going to come up with qualities - and probably procedures - that define its “taskiness.”

For having said the word about once every six words this section, I still haven’t said what a task is. Let’s try:

A task is a record of an action to be done, along with data about when and how it should be done, as well as who should do it. I’m sure there’s a lot more that can be recorded within a task, but for now, that seems like enough to get started with: what to do, how to do it, when to do it, and who should do it.

Each of those is a record of some type itself - and I mean that precisely: we can’t just say “when,” we have to figure out how to store the date. (Also, do we want to store more than one date: Org-mode has provisions for both a scheduled date, when to start something, and a deadline date, when something should be completed by.)

Implementing “What to Do”

“What to do” seems like, for now, it can simply be a use of the existing description quality, which is simply an unprocessed string used to describe a thing: what “describe” means is highly contextual: sometimes it means a description of the interior of a tavern, sometimes it means a description of a webpage’s contents.

The description quality lives as a component of the qtMUD library, and those components aren’t provided when we require qtMUD itself, so let’s require that one now.

(require "../qtmud/library/descriptions.rkt")

This gives us access to two procedures for working with the description quality:

add-description-to-thing!
This adds the description quality to a thing that doesn’t already have it. (Well, this code is very early and unsafe, so it would actually reset a thing’s description as well, but that is unintentional.)
set-thing-description!
This updates a thing’s description to be whatever the passed thing is.

(! at the end of a procedure name means “this changes something about one or more of the arguments being passed to it”)

But! - and this is important to understand: as a MUD builder, we won’t actually be calling those procedures ourselves, ever! We’ll be using procedures stored inside things and universes, which is why the component also provides procedures to add-description-procedures-to-thing! and add-description-procedures-to-universe!.

(add-description-procedures-to-universe! taskverse)

Tangent: Using a Thing’s Procedures

A qtMUD thing is a data record that has several records held within it. It has a record of its name, its grammar, its qualities, the universe its a part of (if it is a part of one), and its procedures. (Universes have an equivalent procedures record themselves.)

When you want to do anything with a thing, using its (or its universe’s) procedures is the preferred way. That is, rather than calling (set-thing-description! moose "This is a moose."), it’s better to call ((thing-procedure moose 'set-thing-description!) moose "This is a moose.")

Or, if there isn’t a 'set-thing-description! procedure within the moose, you should use one within the thing’s universe, and only if neither the thing nor its universe have that procedure should you use the default set-thing-description!

It’s more verbose, but it means that this particular moose could do something different than anything else when you set its procedure, by changing the value of the set-thing-description!. For example, a bear and a housecat probably react differently to being pet, and this can be reflected by setting their petted-by-thing procedures to be different.

Luckily, the engine comes with procedures for facilitating this logic. First, there’s a use-thing-procedure procedure that tries to use a thing’s (or its universe’s) procedure, and otherwise returns false. There is another procedure, use-thing-quality-procedure, that replaces instances of quality in the procedure name with qualities in the argument.

This (and some other nuance) comes together to have the effect of:

You, the person building a MUD world, won’t call (set-thing-description! moose "This is a moose.")

Instead, you’ll call (set-thing-quality! moose 'description "This is a moose.")

The set-thing-quality! procedure starts by replacing the word quality in its name with whatever the quality is, becoming set-thing-description!. Then, the moose is checked for that procedure, and if it has it, it’s used. Otherwise, if the moose’s universe has a set-thing-description! procedure, that’s used. If neither has the appropriate procedure, the description quality is directly updated within the thing’s qualities.

This has the effect of being able to add and manipulate qualities without any additional overhead, the following, as a stand-alone module, works:

(The require here is to qtMUD’s main module, and is just relative because well, I’m on my own computer and it’s easy.)

#lang racket

(require "../main.rkt")

(define banana (create-thing "banana")) ; #<thing>
(add-quality-to-thing! 'folic-acid banana)
(set-thing-quality! banana 'folic-acid 140)
(thing-quality banana 'folic-acid) ; 140

This can be unsafe though - there’s nothing stopping us from writing procedures that assume folic-acid is a number, but also setting it to a string. So, a procedure can be written:

(define (set-thing-folic-acid! acidic-thing quantity)
 (unless (number? quantity)
  (raise-argument-error 'set-thing-folic-acid!
			"number?"
			quantity))
 (set-thing-quality! acidic-thing 'folic-acide quantity #:skip #t))

(You might notice that this does use set-thing-quality!, but with a previously unmentioned skip keyword argument that’s been set to true: this skips over the whole “replace the quality name and look inside the thing and its universe for alternative procedures” and just goes straight to setting it. (There’s another keyword argument force that will set the quality even if it doesn’t exist - that’s how qualities are added.)

But remember - we don’t use set-thing-folic-acid!: we add it to our banana.

(set-thing-procedure! banana
		      'set-thing-folic-acid!
		       set-thing-folic-acid!)

Now, trying to set our banana’s folic acid to anything but a number should raise an argument error:

(set-thing-quality! banana 'folic-acid "Lots")

And indeed:

; set-thing-folic-acid!: contract violation
;   expected: number?
;   given: "Lots"

This is a simple example, but is the concept behind how to properly work with things in qtMUD.

Making Our First Task

So now that I’ve (hopefully) explained a bit about how the MUD handles things, it is more clear how by calling (add-description-procedures-to-thing! taskverse) we can now safely use set-thing-quality! to manipulate the description quality and create our first-task.

(define task-A
 (create-thing "Task #A"))

(add-quality-to-thing! 'description task-A)
(set-thing-quality!
 task-A 'description
 "Implement task-handling qualities and procedures for qtMUD.")

Remember we defined create-thing as a thing-creating procedure that creates them straight into the taskverse, which we added description procedures so using set-thing-quality! will use the Taskverse to find the set-thing-description! procedure and use it.

I discussed what, how, who, and when, earlier, but I left out an obvious part: progress:

(add-quality-to-thing! 'task-progress task-A)
(set-thing-quality! task-A 'task-progress 'drafting)

Before we go further let’s create a procedure for building a text block that provides human-readable information about this thing, as a task:

(define (render-thing-as-task task)
 (format "~a: ~a\n  ~a\n"
       (thing-quality task 'task-progress)
       (thing-name task)
       (thing-quality task 'description)))

(display (render-thing-as-task task-A))

This shows the following:

drafting: Task #A
  Implement task-handling qualities and procedures for qtMUD.

Alright, not too bad! And at this point I’ve lost steam for this particular exercise, so I’m going to end on creating a procedure that takes a thing and turns it into a task - rather unsafely, but it demonstrates how the creation of things can be mechanized:

(define (build-task! task
		   #:description [description #f]
		   #:task-progress [task-progress #f])
  (add-qualities-to-thing!
   '(description task-progress) task)
  (when description
    (set-thing-quality! task 'description description))
  (when task-progress
    (set-thing-quality! task 'task-progress task-progress)))

The end!