It would seem that writing a program for the Nintendo is not like writing a program for a “normal” computer: It’s not enough to target the core system, you also have to consider the layout of the cartridge you are storing the program on. There are over 200 “mappers” for the Nintendo that define different memory maps created over the course of a decade as developers pushed various limits of what the hardware could do. Some have more tile memory, others can save data to the cartridge. No one cartridge mapper can do everything. I have not yet decided if this is a breaking point for me or not, but for now I would love to see my initial level generator go live on the emulator.
The initial version of my level generator is based around 64×60 tile levels, using all 4 nametables. The initial version sets the background attribute colors to an XOR-pattern, and places 5 rooms randomly around the level connected with passages. I had to do a bit of math to get the generator to merge the 4 nametables into a single map, then split it back out again for the 4 binary files. You’ll see some weird math in a couple of places, like where I’m getting the tableIndex in SetTile. Ultimately all of this is going to be implemented in 6502 assembly, so all base-10 math is going to need to be evicted, and anything that can be rendered down to simple logic operators should be.
If you run the LINQPad script that I’m about to share with you, it will generate a simple level (in 4 files) that you could scroll around if only you had a NES-compatible program to load it into. I have the start of an NES ROM that is suitable for this, but it’s not quite debugged yet.

The next edition of the level generator will probably line up the rooms with the attribute tiles, so that I can more easily assign a palette to each room.
const string PATH = @"C:\Users\Trey.Tomes\projects\personal\nes\";
const int NAMETABLE_SIZE = 1024;
const int NUM_TABLES = 4;
const int NT_ROWS = 30;
const int NT_COLUMNS = 32;
const int TILE_START = 0;
const int ATTR_START = NT_ROWS * NT_COLUMNS;
const int TOT_ROWS = NT_ROWS * 2;
const int TOT_COLUMNS = NT_COLUMNS * 2;
const int COLORS_PER_PALETTE = 4;
static Random rnd = new Random();
struct Room {
public int x;
public int y;
public int w;
public int h;
}
void Main() {
const string FILENAME_OUTPUT = "sample";
byte[][] data = CreateNametableSet();
for (var y = 0; y < 60; y++) {
for (var x = 0; x < 64; x++) {
SetTile(data, x, y, (byte)'#');
}
}
// Set the attribute table to an XOR-pattern.
for (var y = 0; y < 30; y++) {
for (var x = 0; x < 32; x++) {
SetAttribute(data, x, y, (x ^ y) % COLORS_PER_PALETTE);
}
}
var maxRooms = 5;
var roomNumber = 0;
var room = CreateRoom(data);
while (roomNumber < maxRooms) {
var lastRoom = room;
room = CreateRoom(data);
CarveTunnel(data, lastRoom, room);
roomNumber++;
}
File.WriteAllBytes(Path.Combine(PATH, FILENAME_OUTPUT + $"{0}.nam"), data[0]);
File.WriteAllBytes(Path.Combine(PATH, FILENAME_OUTPUT + $"{1}.nam"), data[1]);
File.WriteAllBytes(Path.Combine(PATH, FILENAME_OUTPUT + $"{2}.nam"), data[2]);
File.WriteAllBytes(Path.Combine(PATH, FILENAME_OUTPUT + $"{3}.nam"), data[3]);
}
void CarveTunnel(byte[][] data, Room room1, Room room2) {
var x = room1.x + (room1.w >> 1);
var y = room1.y + (room1.h >> 1);
var x2 = room2.x + (room2.w >> 1);
var y2 = room2.y + (room2.h >> 1);
var offset = (x < x2) ? 1 : -1;
while (x != x2) {
SetTile(data, x, y, (byte)' ');
x += offset;
}
offset = (y < y2) ? 1 : -1;
while (y != y2) {
SetTile(data, x, y, (byte)' ');
y += offset;
}
}
Room CreateRoom(byte[][] data) {
const int ROOM_MIN_WIDTH = 5;
const int ROOM_MAX_WIDTH = 8;
const int ROOM_MIN_HEIGHT = 5;
const int ROOM_MAX_HEIGHT = 8;
var width = rnd.Next(ROOM_MIN_WIDTH, ROOM_MAX_WIDTH + 1);
var height = rnd.Next(ROOM_MIN_HEIGHT, ROOM_MAX_HEIGHT + 1);
var left = rnd.Next(1, TOT_COLUMNS - width - 1);
var right = left + width - 1;
var top = rnd.Next(1, TOT_ROWS - height - 1);
var bottom = top + height - 1;
for (var x = left; x <= right; x++) {
for (var y = top; y <= bottom; y++) {
SetTile(data, x, y, (byte)' ');
}
}
return new Room() { x = left, y = top, w = width, h = height };
}
/**
* 4 nametables for the 4 screen quadrants.
*/
byte[][] CreateNametableSet() {
return new byte[NUM_TABLES][] {
new byte[NAMETABLE_SIZE],
new byte[NAMETABLE_SIZE],
new byte[NAMETABLE_SIZE],
new byte[NAMETABLE_SIZE]
};
}
void SetTile(byte[][] data, int x, int y, byte tileNumber) {
var tableX = x >> 5; // x / 32
var tableY = y / 30; // How to shift right to divide by 30?
var tableIndex = ((tableY & 1) << 1) | (tableX & 1); // convert to range [0, 3]
var tbl = data[tableIndex];
x = x % 32;
y = y % 30;
var offset = y;
offset = offset << 5;
offset += x;
offset += TILE_START;
tbl[offset] = (byte)tileNumber;
}
/**
* Set the background palette number for a 4x4 tile block.
*
* blockX: tile x / 2, [0, 15]
* blockY: tile y / 2, [0, 14]
* attr: background palette number, [0, 3]
*/
void SetAttribute(byte[][] data, int blockX, int blockY, int paletteNumber) {
var tableX = blockX >> 4; // blockX / 16;
var tableY = blockY / 15; // How to divide by 15?
var tableIndex = ((tableY & 1) << 1) | (tableX & 1); // convert to range [0, 3]
var tbl = data[tableIndex];
blockX = blockX % 16;
blockY = blockY % 15;
if ((blockX < 0) || (blockX > 15)) {
throw new ArgumentNullException("Must be [0, 15].", nameof(blockX));
} else if ((blockY < 0) || (blockY > 14)) {
throw new ArgumentNullException("Must be [0, 14].", nameof(blockY));
} else if ((paletteNumber < 0) || (paletteNumber > 3)) {
throw new ArgumentNullException("Must be [0, 3].", nameof(paletteNumber));
}
var bitOffset = (((blockY & 1) << 1) | (blockX & 1)) << 1;
var byteOffset = ATTR_START + (int)((blockY >> 1) << 3) + (blockX >> 1);
var oldAttr = tbl[byteOffset];
var attr = (oldAttr & ~(0b11 << bitOffset)) + (paletteNumber << bitOffset);
tbl[byteOffset] = (byte)(attr);
}
It’ll probably be a couple of weeks before I come back to this. I recently had a dream where I was playing a game on the Color Computer 3 that hasn’t been created yet, so I’m going to spend some time seeing if I can build it.








