DEV Community

loading...
Cover image for Writing a command system in JavaScript

Writing a command system in JavaScript

Siddharth
13. Coding for a hobby
・2 min read

If you search Google for "text adventure" and open the Developer tools, you get a neat little text adventure game to play, which involves the blue G finding his friends red o, yellow o, blue g, green l, and the always quirky red e.

text-adventure

Pretty hard

I started wondering how they did this: They implemented a whole command system without using any external stuff, with only plain JavaScript. And so at once I started digging through the code, immediately stopped because it was obfuscated, and started thinking. The result was this simple trivia quiz (hosted here).

Quiz!

I'm really bad at trivia.
Wondering how I'm styling those logs? Check out this explainer I wrote

How does this even work?

yes, no, north, moon, they all don't seem to be anything. If you open the DevTools and run them, you'll just get a Uncaught ReferenceError: yes is not defined. But that gives us a hint – why don't we define it?

const yes = "yes";

// Later...
yes
// => "yes"
Enter fullscreen mode Exit fullscreen mode

That works perfectly, but we have no way of saying whether it was called. But then, we can use getters.

Quick demo of getters

const obj = {
  foo: 'bar',
  get foo() {
    return 'something entirely different'
  }
}

obj.foo //=> 'something entirely different'
Enter fullscreen mode Exit fullscreen mode

We obviously can't use getters on global variables, but we can just set the variables on window and add getters to them:

Object.defineProperty(window, "yes", {get: () => {
    // Do something
    console.log("Got yes");
    return "yes";
}});

yes
// => "yes"
// => "Got yes" (logged to console)
Enter fullscreen mode Exit fullscreen mode

And that's basically it, you can just keep setting variables statically or dynamically, and you basically get a command system!


What are the uses of this? I dunno, all this can be done by using regular functions instead of this. Maybe easter eggs? Maybe for some debugging?

I can't wait to see people writing code like this:

Object.defineProperty(window, "main", {get: () =>  {...}})

main;
// Wait, is main supposed to be a function or something?
// Linters are gonna be angry...
Enter fullscreen mode Exit fullscreen mode

Discussion (1)

Collapse
lukeshiru profile image
LUKESHIRU

Worth mentioning that you can define several "commands" this way:

// First we create a function like this one:
const addCommands = commands => target =>
    Object.defineProperties(
        target,
        Object.fromEntries(
            Object.entries(commands).map(([command, action]) => [
                command,
                {
                    get: () => {
                        action();
                        return command;
                    }
                }
            ])
        )
    );

// This is just to make the window.open shorter:
const redirect = url => () => window.open(url);

// And then we add as many commands as we want:
// It expects an object with the format { commandName: function to call }
addCommands({
    twitter: redirect("https://twitter.com/lukeshiru"),
    dev: redirect("https://dev.to/lukeshiru")
})(window);
Enter fullscreen mode Exit fullscreen mode

Cheers!