A Map Renders

One of the things that really attracted me to the rot.js framework is the ability to use hex maps. As an old school Avalon Hill board gamer, I had to be dragged kicking and screaming into the modern age of area and point-to-point map systems and, while I’ve made my peace with those topologies, to my inner geek hexes just scream hard core.

Most roguelikes use a square grid map and that has loads of plusses, especially in the ‘ease of programming’ category. Hexes offer a distinct advantage, however, in that the distance between all hexes is constant. The downside is that orthogonal movement in some directions involves zig-zaging. Addtionally, indexing presents its own special problems.

The original Source of the Nile game was hex based and that’s how I want to do it too, challenges be damned. To get this map rendering, here’s what I wanted to do:

1) Read in a map from a file and render to the rot.js console 2) Only show a portion of the map on the display, panning the map as the user move’s the ‘@’.

My first thought was have map data stored in a text file like this

1
2
3
…….###……
……#####…
……#####…

However, to read a text file in javascript it seems you have to do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
path: 'maps',
get: function(name){
    var path = 'js' + "/" + this.path + "/" + name + ".txt?" + ROT.RNG.getUniform();
    return Promise.request(path).then(function(data){
        var map_data =  this._mapFromTemplate(data);
        return map_data;
    }.bind(this)).then(null, function(){debugger;});
},
_mapFromTemplate: function(data){
    console.log(data);
    return data;
}

The problem is I suck and the various asyncronous calls I had to wait on to get the map to the screen were messing with my chi, so I decided to go with a lamer approach:

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
map: function(){
    var ret_map = [];
    var map_arr = ['????............................................................................',
        '??..............................................................................',
        '..?.............................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................',
        '................................................................................'
    ];//.join('\n');
    for(var y = 0; y < map_arr.length; y++){
        var line = map_arr[y].split('');
        ret_map.push([])
        for(var x = 0; x < line.length; x++){
            //console.log(x+ ", " + y + ": " + line[x]);
            ret_map[y][x] = line[x];
        }
    }
    return ret_map;
}

Obviously, instead of a map that is empty except a few spaces in the upper left, this will eventually be a map of Africa. To make sure I have some lousy code to refactor later, I created another array and looped over the first array to figure out what sort of tile I’m dealing with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   var map = [];
    var mapHeight = 80;
    var str_y = 0;
    for(var y = 0; y < 24; y++){
        map.push([]);
        var str_x = 0;
        for(var x = y%2; x < mapHeight; x += 2){
            //map[x].push(Game.Tile.nullTile)
            if(str[str_y][str_x] == "."){
                map[y][x] = Game.Tile.floorTile;
            } else {
                map[y][x] = Game.Tile.wallTile;
            }
            str_x++;
        }
        str_y++
    }
    this._map = new Game.Map(map);

To render to the screen, I simply loop (again!) on the map array

1
2
3
4
5
6
7
    for(var y = topLeftY; y < topLeftY + screenHeight; y++){
        for(var x = y%2; x < topLeftX + screenWidth; x += 2){
            var glyph = this._map.getTile(y, x).getGlyph();
            //console.log(y + ", " + x + ": " + glyph.getChar());
            display.draw(x - topLeftX, y - topLeftY, glyph.getChar(), glyph.getForeground(), glyph.getBackground());
        }
    }

The end result is that I ended up with this

What this screenshot does not show is that the ‘@’ can move around the map and the map pans with the user. Which is all great until you run into one of several, literal, edge cases:

Notice where our hero is? When using the “odd shift” method of indexing, odd lines are shifted to the right by half a cell. So, when I attempted to move due east on an odd row (zero indexed) against an edge, our hero ended up half way in no-mans land instead of having his movement blocked. To avoid those edge cases, I wrote a lot of really crappy code along the lines of (in the case of moving west)

1
2
3
4
5
6
7
   if(this._centerY == 0){
      this.move(-2, 0);
  } else if(this._centerX == 1 && this._centerY%2 == 1){
      this.move(0,0);
  } else {
      this.move(-2, 0);
  }

That’s ugly. So ugly, I’m not going to horrify you by showing what I did for the intercardinal directions.

I’m running this project loosely in 2 week sprints. For the 10.2 sprint (10/15 – 10/31), I wanted to accomplish the following

  • Render the main ui
  • Read a map from a file and display it in the rot.js container
  • Move an avatar around the map, panning the display as it moves

I accomplished these goals with time to spare and a lucky thing, too, as my work/life balance has made fiting in JavaScript vanity projects more challenging. However, I am determined to soldier on and get this game into at least a playable state before I am attracted by the next shiny object to flit before my eyes.

Also, before I forget, a big h/t to Dominic at Coding Cookies, whose excellent rot.js tutorial I have used a starting point for this project.

Comments