JSFiddle - React, Tailwind, and code Playground

by bnickel

HTML

<div class="drawing-area">
    <canvas width="500" height="300"></canvas>
</div>
<textarea>
# TurtleScript

set-color green      # color (or any valid canvas style)
move      50         # pixels
turn-left 100        # degrees
set-color yellow
move 40
turn-right 150
</textarea>
<button id="execute">Push Commands</button>
<button id="execute10">Push Commands 10 times</button>
<button id="reset">Push <tt>reset</tt></button>
<button id="clear">Push <tt>clear</tt></button>

CSS

body {
    width: 500px;
    margin: 5px auto;
}

.drawing-area, textarea {
    display: block;
    width: 502px;
    margin: 5px auto;
    position: relative;
}

.drawing-area {
    border: 1px solid black;
}

#turtle {
    position: absolute;
    width: 6px;
    height: 6px;
    margin-top: -3px;
    margin-left: -3px;
    border-radius: 3px;
    border: 1px solid black;
    box-sizing: border-box;
}

textarea {
    box-sizing: border-box;
    height: 10em;
    font-family: monospace;
}

JavaScript

/**
 * A super-lazy event manager.  Internally it uses a HTML element to store events.
 */

function EventManager(el) {
    el = el || document.createElement('span');

    function dispatchCustomEvent(type, details) {
        var event = document.createEvent('CustomEvent');
        event.initCustomEvent(type, false, true, details);
        this.dispatchEvent(event);
        return event;
    }

    this.addEventListener = el.addEventListener.bind(el);
    this.dispatchEvent = el.dispatchEvent.bind(el);
    this.dispatchCustomEvent = dispatchCustomEvent;
}

function CommandParser() {
    var NOOP_PATTERN = /^\s*(?:#.*)?$/;
    var LINE_PATTERN = /^\s*([\w\-]+)(?:\s*([^#]*))?/;
    var ARGS_PATTERN = /\s*,\s*/;

    function parseCommand(command) {
        if (NOOP_PATTERN.test(command)) {
            return {
                command: 'noop',
                args: []
            };
        }

        var components = command.match(LINE_PATTERN);

        if (!components) {
            return {
                command: 'parse-error',
                args: [command]
            };
        }

        return {
            command: components[1],
            args: components[2] ? components[2].trim().split(ARGS_PATTERN) : []
        };
    }

    function parseCommands(commands) {
        return commands.split(/\r?\n/).map(parseCommand);
    }

    this.parseCommand = parseCommand;
    this.parseCommands = parseCommands;
}

function StatelessTurtle(eventManager, commandParser) {
    eventManager = eventManager || new EventManager();
    commandParser = commandParser || new CommandParser();

    var running = false;
    var pendingCommands = [];
    var commandMap;

    function trigger(name, details) {
        details.resumeExecution = executeNextCommand;
        return eventManager.dispatchCustomEvent(name, details);
    }

    // TODO: It would be really cool if we injected commands.

    function reset() {
        return trigger('reset', {});
    }

    function...