This is the second post in the Building a Roguelike in Javascript series. I recommend you start at the beginning. As I mentioned in the first post, I will try to match each post to a post in Trystan's series on creating a roguelike in Java. This part corresponds to the second part in Trystan's series. All the code for this part can be found at the part 2 tag of the jsrogue repository. At the time of writing, I am still using the 81a8eb0d6c commit of rot.js.

Before we get started, I just want to mention that I updated index.html to refer to a local copy of rot.js as opposed to the copy on Github in the case the breaking changes are implemented in the future. All you have to do is download rot.min.js, put it in your assets folder, and update the line:

1
<script src="https://raw.github.com/ondras/rot.js/master/rot.min.js"></script>

to

1
<script src="assets/rot.min.js"></script>

In this post we will be introducing the notion of a screen to our game. A screen will represent the current state of the application, for example telling us if we are in the character creation menu or in the main game view. We want to be able to switch easily between screens, and each screen should be responsible for its own rendering and input processing. In order to do this, we will make a master Game object which will take care of containing the game's display as well as the current screen.

Demo Link

The results after this post can be seen here

assets/game.js

The first thing we wil want to do here is create a Game object which will wrap around a ROT.Display object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var Game =  {
    _display: null,
    init: function() {
        // Any necessary initialization will go here.
        this._display = new ROT.Display({width: 80, height: 24});
    },
    getDisplay: function() {
        return this._display;
    }
}

We can now rewrite our window.onload function to simply invoke the init function to setup the game and then we can use getDisplay to add our display to the screen. With this in mind, we can now rewrite our assets/game.js like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var Game =  {
    _display: null,
    init: function() {
        // Any necessary initialization will go here.
        this._display = new ROT.Display({width: 80, height: 24});
    },
    getDisplay: function() {
        return this._display;
    }
}

window.onload = function() {
    // Check if rot.js can work on this browser
    if (!ROT.isSupported()) {
        alert("The rot.js library isn't supported by your browser.");
    } else {
        // Initialize the game
        Game.init();
        // Add the container to our HTML page
        document.body.appendChild(Game.getDisplay().getContainer());
    }
}

assets/screens.js

First we have to create this file! We're going to want to include this script in our index.html page:

1
2
3
<script src="assets/rot.min.js"></script>
<script src="assets/game.js"></script>
<script src="assets/screens.js"></script>

This file will contain the code for the majority of our game screens. As I mentioned above, we want each screen to be in charge of its own rendering and input processing, so we're going to have to define a pretty standard interface for our screens. As we want to be able to switch screens, we may also want to do some processing when we enter or exit a given screen. This gives us the following rough interface:

enter : function()
exit : function()
render : function(display)
handleInput : function(inputType, inputData)


The handleInput function will take as an argument a string (being the type of input event, such as 'keydown' or 'keyup') as well as the key event. The render function will accept a ROT.Display object to render onto. Before we go ahead and create our screens, let's go ahead and add screen functionality to the Game object to make the connections more clear.

assets/game.js

The first thing we need to do is keep track of the current screen:

1
2
_display: null,
_currentScreen: null, 

Now we want to allow switching between screens. Essentialy when we switch screens, we want to call notify the old screen that we exited (if there was one) and then notify the new screen that we are entering it and render it. So we're going to add this function to our Game object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
switchScreen: function(screen) {
    // If we had a screen before, notify it that we exited
    if (this._currentScreen !== null) {
        this._currentScreen.exit();
    }
    // Clear the display
    this.getDisplay().clear();
    // Update our current screen, notify it we entered
    // and then render it
    this._currentScreen = screen;
    if (!this._currentScreen !== null) {
        this._currentScreen.enter();
        this._currentScreen.render(this._display);
    }
}

Finally, we want to dispatch all input events to the screen's handleInput function. As the logic is similar for keydown, keypress and keyup events, I'll create a small helper function just to facilitate this. We'll put this code in the Game.init function as we should bind to the events as soon as possible:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
init: function() {
    // Any necessary initialization will go here.
    this._display = new ROT.Display({width: 80, height: 24});
    // Create a helper function for binding to an event
    // and making it send it to the screen
    var game = this; // So that we don't lose this
    var bindEventToScreen = function(event) {
        window.addEventListener(event, function(e) {
            // When an event is received, send it to the
            // screen if there is one
            if (game._currentScreen !== null) {
                // Send the event type and data to the screen
                game._currentScreen.handleInput(event, e);
            }
        });
    }
    // Bind keyboard input events
    bindEventToScreen('keydown');
    bindEventToScreen('keyup');
    bindEventToScreen('keypress');
}

Our Game object now fully supports screens!

assets/screens.js

We're now going to define our screens. To keep the spirit of Trystan's tutorial, we'll start with a Start screen which will prompt the user to press Enter. Once we've hit Enter, we're going to switch to a Play Screen. The Play screen will then prompt us to either hit Enter or Escape, bringing us to the Win or Lose screen respectively. In order to prevent polluting the global scope, we will create a Screen namespace under Game, and our screens will be accessible from there. Before we define the screens, I encourage you to check out the VK constants which correspond to a key obtainable from the keyCode member of our input data.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
Game.Screen = {};

// Define our initial start screen
Game.Screen.startScreen = {
    enter: function() {    console.log("Entered start screen."); },
    exit: function() { console.log("Exited start screen."); },
    render: function(display) {
        // Render our prompt to the screen
        display.drawText(1,1, "%c{yellow}Javascript Roguelike");
        display.drawText(1,2, "Press [Enter] to start!");
    },
    handleInput: function(inputType, inputData) {
        // When [Enter] is pressed, go to the play screen
        if (inputType === 'keydown') {
            if (inputData.keyCode === ROT.VK_RETURN) {
                Game.switchScreen(Game.Screen.playScreen);
            }
        }
    }
}

// Define our playing screen
Game.Screen.playScreen = {
    enter: function() {    console.log("Entered play screen."); },
    exit: function() { console.log("Exited play screen."); },
    render: function(display) {
        display.drawText(3,5, "%c{red}%b{white}This game is so much fun!");
        display.drawText(4,6, "Press [Enter] to win, or [Esc] to lose!");
    },
    handleInput: function(inputType, inputData) {
        if (inputType === 'keydown') {
            // If enter is pressed, go to the win screen
            // If escape is pressed, go to lose screen
            if (inputData.keyCode === ROT.VK_RETURN) {
                Game.switchScreen(Game.Screen.winScreen);
            } else if (inputData.keyCode === ROT.VK_ESCAPE) {
                Game.switchScreen(Game.Screen.loseScreen);
            }
        }    
    }
}

// Define our winning screen
Game.Screen.winScreen = {
    enter: function() {    console.log("Entered win screen."); },
    exit: function() { console.log("Exited win screen."); },
    render: function(display) {
        // Render our prompt to the screen
        for (var i = 0; i < 22; i++) {
            // Generate random background colors
            var r = Math.round(Math.random() * 255);
            var g = Math.round(Math.random() * 255);
            var b = Math.round(Math.random() * 255);
            var background = ROT.Color.toRGB([r, g, b]);
            display.drawText(2, i + 1, "%b{" + background + "}You win!");
        }
    },
    handleInput: function(inputType, inputData) {
        // Nothing to do here      
    }
}

// Define our winning screen
Game.Screen.loseScreen = {
    enter: function() {    console.log("Entered lose screen."); },
    exit: function() { console.log("Exited lose screen."); },
    render: function(display) {
        // Render our prompt to the screen
        for (var i = 0; i < 22; i++) {
            display.drawText(2, i + 1, "%b{red}You lose! :(");
        }
    },
    handleInput: function(inputType, inputData) {
        // Nothing to do here      
    }
}

assets/game.js

We're almost done! The only thing we haven't done is define the screen we actually start on! All we have to do is call Game.switchScreen after we call Game.init to make sure we don't try to load the screen before our initialization is done:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
window.onload = function() {
    // Check if rot.js can work on this browser
    if (!ROT.isSupported()) {
        alert("The rot.js library isn't supported by your browser.");
    } else {
        // Initialize the game
        Game.init();
        // Add the container to our HTML page
        document.body.appendChild(Game.getDisplay().getContainer());
        // Load the start screen
        Game.switchScreen(Game.Screen.startScreen);
    }
}

Conclusion

Everything should now be working fine! If you load up index.html, you should be able to play the game we've made so far and see a colorful win screen and depressing losing screen. The next few posts will be more exciting as we're actually going to start display game maps and characters on the screen, but this post was an important step towards structuring our game.

Remember that all the code for this part can be found at the part 2 tag of the jsrogue repository. I hope you enjoyed this post and that you'll stick around for the next part!

Thanks for reading,

Dominic

Next Part

Part 3a - Carving Caves