In many traditional MUDs, the list of things a player or an administrator can do is fixed at compile-time. In a standard Zork-style adventure you might be able to do the typical things like enter ‘north’, ‘south’, ‘get’, ‘attack’, ‘kill’, ‘drop’, ‘quit’, etc. In a MUD you get some additional commands that might be relevant in a multi-player environment like ‘who’ or ‘say’ or ‘tell’ and even some fancy muds let you emote with commands like ‘bow’, ‘praise’, ‘introduce’, ‘wave’, etc.

While I’m perfectly OK with that list of commands, I’ve never liked how the player is given those commands. In almost all MUDs that I’ve encountered, the game engine knows about the fixed list of available commands and the execution of said commands takes place somewhere at the low engine level. This totally violates my feelings toward the single responsibility principle (SRP), encapsulation, and domain-bleeding.

The following sample scenarios should make it clear why I hate the idea of the engine having built-in commands:

Scenario 1: A player is in a MUD and they enter a disco. While inside this disco, the player can type ‘disco’ to get their 70s funk on. The ‘disco’ command is only available to the player while they are in this one room out of the thousands of rooms that make up the MUD. Do we really want to go modify the command parser at the engine level in order to grant the player the ‘disco’ command in a single room? What happens if that room gets deleted … now we have orphaned, scar tissue code sitting in our engine that serves no purpose. This might not seem like a big deal for a game, but it illustrates a very good point about designing large applications with large domain models.

Scenario 2: A player in that same MUD stumbles across a sword. The sword has an inscription on it and when the player types the words from that inscription into the console, the sword bursts into flames and does an extra bajillion points of damage. In this case, the command is even more situational than the disco room. Only when the player is wielding this sword are they granted the ability to type the secret word ‘clambake’ into the console that will activate the sword’s secret powers.

So, what I’m envisioning here is I want to be able to send the Player actor a message whenever I grant them the ability to type a command and likewise, send them another message when they no longer have the ability to type that command. Consider the following functions that we might find in the Holy Clambake Sword:


def onWielded(player: Player) : Unit = {
    player ! AddCommand(List("clambake"), handleClambake)
}

def handleClambake(verb: String, extParams: List[String]): Boolean = {
    player ! TextMessage("You have activated the mighty abilities of the Holy Clambake Sword!")
    player ! Emote("has activated the mighty abilities of the Holy Clambake Sword!")
    isFlaming = true
    true
}

To be able to send Player’s the AddCommand message, I’m going to create a trait called CommandIssuer. This trait can be mixed into any Actor that is capable of issuing commands to the system. This includes NPCs and other interactive entities within the game. For now, since I haven’t coded NPCs, we can just start with players. Here’s the code for the CommandIssuer trait:

package com.kotancode.mud.engine

import scala.actors._
import scala.actors.Actor._

sealed abstract class CommandRequest
case class AddCommand(verbs: List[String], executor: Function2[String, List[String], Boolean],
	cmdSoul: CommandSoul = null) extends CommandRequest
case class RemoveCommand(verbs: List[String], cmdSoul: CommandSoul = null) extends CommandRequest
case class ExecuteCommand(verb: String, extParams: List[String]) extends CommandRequest

trait CommandIssuer extends Actor
{	                             // CMD verbs , Func to call
	private var commandMap: Map[List[String], Function2[String, List[String], Boolean]] =
		Map.empty[List[String], Function2[String, List[String], Boolean]]

	var commandSouls: Map[String, CommandSoul] = Map.empty[String, CommandSoul]

	protected val handleCommandMapping : PartialFunction[Any, Unit] = {
		case req: AddCommand => {
			commandMap += req.verbs -> req.executor
			if (req.cmdSoul != null) {
				commandSouls += req.cmdSoul.describe()._1 -> req.cmdSoul
			}
		}
		case req: RemoveCommand => {
			commandMap -= req.verbs
			if (req.cmdSoul != null)
				cmdSouls -= req.cmdSoul.describe()._1
		}
		case exec: ExecuteCommand => {
			val toExecute = commandMap.filter(m => m._1.contains(exec.verb))
			if (toExecute.size == 0) {
				self ! TextMessage("What?\n")
			}
			else {
				toExecute.foreach( m => m._2(exec.verb, exec.extParams))
			}
		}
	}
}

If we add the CommandIssuer trait to our Player class (or any other class we want to be able to issue commands, either from a terminal or at the behest of other Actors) then we can send Players the AddCommand, RemoveCommand, and ExecuteCommand messages. The code that reads in lines of text from the player’s attached socket can now invoke ExecuteCommand after splitting the input line into a head and tail (how LISPy is that??):

val tokens = line.split(' ').toList
this ! ExecuteCommand(tokens.head, tokens.tail)

Finally, I want to mention Command Souls. Command souls are logically bundled groups of command handlers that are also bundled with the code that attaches and removes the commands from targets. This provides a neat and tidy way of packaging logically related commands like the stock commands that belong to all mortals (who, quit, say, get, look, etc), stock commands that belong to wizards (broadcast, cmdsoul to inspect commands, destroy, teleport, etc). Command souls are part of what I consider a MUD’s library code, logically and physically separated from the MUD’s engine code. This concept neither original nor mine – I first encountered it when I was an Archwizard of Code on the LPmud Genesis.

Take a look at the code for the Wizard command soul, a classic example of what Scala should look like when done in clean, tidy, small modules:

/*
 * Wizard Command Soul
 * Commands granted to all wizards
 */
package com.kotancode.mud.lib.wizard

import com.kotancode.mud.engine._
import scala.actors._
import scala.actors.Actor._

class WizardCommandSoul extends CommandSoul
{
	private var soulOwner : Actor = null

	def attachTo(target:Actor) : Unit = {
		soulOwner = target
		target ! AddCommand( List("cmdsoul"), handleCommandSoul, this )
		target ! AddCommand( List("broadcast", "bcast"), handleBroadcast, this)
	}

	def detachFrom(target:Actor) : Unit = {
		soulOwner = null
		target ! RemoveCommand( List("cmdsoul"), this)
		target ! RemoveCommand( List("broadcast", "bcast"), this)
	}

	def describe() : (String, String) = {
		("Wizard", "Standard commands available to all Wizards")
	}

	def handleCommandSoul(verb: String, extParams: List[String]) : Boolean = {
		val cmdIssuer = soulOwner.asInstanceOf[CommandIssuer]
		var string = "\nCurrently Attached Command Souls:\n"
		cmdIssuer.commandSouls.foreach( mapitem => string += mapitem._1 + " - " + mapitem._2.describe()._2 + "\n")
		soulOwner ! TextMessage(string)
		true
	}

	def handleBroadcast(verb:String, extParams: List[String]) : Boolean = {
		var text = "[Broadcast from " + soulOwner.asInstanceOf[Player].name + "]: "
		text += extParams.reduceLeft(_ + " " + _)
		PlayerRegistry.allPlayers.foreach(p => p ! TextMessage(text))
		true
	}
}

And finally, to prove that I’m not just blowing hot air, here’s a screenshot of a session I had while logged into the MUD. My prototype MUD auto-grants every user both Mortal and Wizard command souls and doesn’t care what you type for a name. In the future, it will have to ask for a username and a password, look that person up from storage, and load their profile, yadda yadda … For now, this is what I’ve got:

Sample MUD Session, Illustrating Command Processing

Sample MUD Session, Illustrating Command Processing

Overall I’m still fairly pleased with Scala. A few times I’ve been slapped in the face by the strong-typing because I’m not used to that when everything else feels so functional and lightweight – side-effects of Scala’s Frankenstein/hybrid nature. Still, even with a few syntactic bits of ugliness, I continue to keep forging ahead with this to get a real feel for what it’s like to work with Scala to solve practical problems.