5
\$\begingroup\$

Recently, I made a small bot to simulate an ASCII-based RPG in a chat room. The other users can enter commands (.left, .right, .up, .down) to move the character.

It's made with JavaScript and everything is in global scope, so I can simply enter it in the console window to create a game.

This version can run outside of a chatroom by entering it to the developer console:

var target = document.getElementById("chat");

var walkable = [1,  4, 5, 6];
var inCave = false;
var gold = 0;

var obs = new MutationObserver(function(muts) {
    if (muts[0].addedNodes[0].className.indexOf("mine") == -1) {  
        var messages = document.getElementsByClassName("content");
        var lastMessage = messages[messages.length - 1].innerHTML;
       
       if (lastMessage.charAt(0) == ".") {
           walk(lastMessage.slice(1));
       }
    }
});

var config = { attributes: true, childList: true, characterData: true};
//obs.observe(target, config);

// Code above this is used to get the last message

var map = [];
var mapSize = 20;
var caveSize = 10;
var currMapSize = mapSize;

for (var y = 0; y < mapSize; y++) {
    for (var x = 0; x < mapSize; x++) {
        var rand = Math.random();
        if (rand < 0.03) {
            map.push(3);
        } else if (rand < 0.05) {
            map.push(2);
        } else {
            map.push(1);
        }
    }
}

for (var i = 0; i < 5; i++) {
    var xx = Math.floor(Math.random() * mapSize);
    var yy = Math.floor(Math.random() * mapSize);
    map[xx + yy * mapSize] = 4;
}

var player = {x: mapSize / 2, y: mapSize / 2};
var tempPlayer = {x: 0, y: 0};

var cave = [];
var temp = [];

function drawMap() {
    var msg = "Your gold: " + gold + ", You're in " + (inCave ? "a cave" : "the overworld" )+ ".    \n    ";
    for (var y = player.y - 6; y < player.y + 7; y++) {
        for (var x = player.x - 6; x < player.x + 7; x++) {
            if (x == player.x && y == player.y) {
                msg += "O";
            } else {
                if (x >= 0 && y >= 0 && x < currMapSize && y < currMapSize) {
                    switch (map[x + y * currMapSize]) {
                        case 1:
                            msg += "_";
                            break;
                        case 2:
                            msg += "#";
                            break;
                        case 3:
                            msg += "T";
                            break;
                        case 4:
                            msg += "C";
                            break;
                        case 5:
                            msg += "E";
                            break;
                        case 6:
                            msg += "G";
                            break;
                    }
                } else {
                    msg += "#";
                }
            }
        }
        msg += "\n    ";
    }
    msg += "\n    # - Rocks/Walls, _ - Walkable ground, O - Player \n";
    msg += "    T - Trees, C - Cave, E - Entrance to the world\n";
    msg += "    G - Gold";
    
    sendMessage(msg);
}

var sendButton = document.getElementById("sayit-button");
var input = document.getElementById("input");

function sendMessage(message) {
    /*input.value = message;
    sendButton.click();*/
    
    console.log(message);
}

function walk(direction) {
    var moved = false;
    switch (direction) {
        case "left":
            if (walkable.indexOf(map[player.x - 1 + (player.y) * currMapSize]) != -1)
                player.x--;
            moved = true;
            break;
        case "right":
            if (walkable.indexOf(map[player.x + 1 + (player.y) * currMapSize]) != -1)
                player.x++;
            moved = true;
            break;
        case "up":
            if (walkable.indexOf(map[player.x + (player.y - 1) * currMapSize]) != -1)
                player.y--;
            moved = true;
            break;
        case "down":
            if (walkable.indexOf(map[player.x + (player.y + 1) * currMapSize]) != -1)
                player.y++;
            moved = true;
            break;
            
    }
    
    if (player.x < 0) {
        player.x = 0;
    } else if (player.x >= currMapSize) {
        player.x = currMapSize - 1;
    }
    if (player.y < 0) {
        player.y = 0;
    } else if (player.y >= mapSize) {
        player.y = currMapSize - 1;
    }
    
    if (map[player.x + player.y * currMapSize] == 4) {
        map[player.x + player.y * currMapSize] = 1;
        inCave = true;
        for (var y = 0; y < caveSize; y++) {
            for (var x = 0; x < caveSize; x++) {
                cave.push(1);
            }
        }
        cave[0] = 5;
        
        var treasureAmount = Math.floor(Math.random() * 3 + 1);
        for (var i = 0; i < treasureAmount; i++) {
            var xx = Math.floor(Math.random() * caveSize);
            var yy = Math.floor(Math.random() * caveSize);
            if (xx != 0 || yy != 0) {
                cave[xx + yy * caveSize] = 6;
            }
        }
        
        tempPlayer.x = player.x;
        tempPlayer.y = player.y;
        temp = map.slice();
        map = cave;
        player.x = 5;
        player.y = 5;
        currMapSize = caveSize;
    } else if (map[player.x + player.y * currMapSize] == 5) {
        inCave = false;
        map = temp;
        player.x = tempPlayer.x;
        player.y = tempPlayer.y;
        currMapSize = mapSize;
    } else if (map[player.x + player.y * currMapSize] == 6) {
        map[player.x + player.y * currMapSize] = 1;
        gold += 50;
    }
    
    if (moved) {
        drawMap();
    }
}

drawMap();

To enter a command, you need to write:

walk(<command without full stop here>);
\$\endgroup\$
1
  • \$\begingroup\$ For the finale code, you should remove commented-out code \$\endgroup\$ – Conor O'Brien Jun 24 '16 at 21:49
2
\$\begingroup\$

First, I would suggest moving up the functions higher in the code, preferably in the beginning of the code for visibility. It's nice to know functions exist before you see code that actually uses it.

Next is that your functions are trying to do too much. For instance, walk I expect to just update the player's position. However, inside walk, you have gold updates, and other state changes. Break these operations to smaller functions for maintainability. You don't want to be updating several functions for just gold updates.

In addition, this game is turn-based. It's a simple get-input-and-return-output which fits nicely with the functional paradigm. For instance, we could have a move function that accepts the current state and returns the next state. Given the same input, it will always give out the same result. Also, it won't mutate the input. It will give out a new object all the time.

var currentState = {
  direction: 'left',
  mapState: [0,0,0,0,1,G,0,0,0],
  player: { id: 1, gold: 0, hp: 100}
};

var nextState = move(currentState);

// {
//   mapState: [0,0,0,0,0,1,0,0,0],
//   player: { gold: 10, hp: 100 }
// }

if (map[player.x + player.y * currMapSize] == 4)
if (map[player.x + player.y * currMapSize] == 5)
if (map[player.x + player.y * currMapSize] == 6)

Don't make me read the code to know what 4, 5 and 6 are. Put them in variables so they are readable. If they belong to a common set of items, use an object.

var mapItems = {
  treasure: 4,
  arena: 5,
  gold: 6,
}

if (map[player.x + player.y * currMapSize] == mapItems.treasure)
if (map[player.x + player.y * currMapSize] == mapItems.arena)
if (map[player.x + player.y * currMapSize] == mapItems.gold)

Also, map[player.x + player.y * currMapSize] is being repeated. Consider extracting to a function.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.