19. More things
Last updated
Last updated
Now, for something completely different.
Random numbers.
The NES has no good way to generate random numbers.
neslib has rand8() and rand16() functions, but it doesn’t have a good way to seed it. It just uses a constant at startup, meaning that the random numbers will always be exactly the same at reset.
So I wrote a function that puts the frame count into the seed. So, you could put this on a title screen. seed_rng()
This just pushes the frame counter (an internal value) to the random seed. So it isn’t random at startup either, which is why you should wait for user input to randomly trigger it, like “PRESS START” on the title screen.
Here’s an example.
https://github.com/nesdoug/23_Random/blob/master/Random.c
https://github.com/nesdoug/23_Random
Note: rand8() is not very good. It repeats itself in a determinate loop of 255 numbers, regardless of the seed. Changing the seed only changes the starting position of that loop. If you truly need random numbers you should use the standard C function rand() and srand() to seed that. If you are using a recent cc65 version (2016 or later), it will produce a much better set of random numbers than rand8. If you use that, you will need to…
#include <stdlib.h>
.
Mappers
So far we’ve been using the most basic mapper, NROM. This is defined in the ines header in crt0.s, in the “HEADER” segment. That is actually importing a linker symbol from the .cfg file, and was zero. NROM is mapper zero. So, to change the mapper number, we change the .cfg file, at the bottom. NES_MAPPER, value = #.
Why change the mapper? Well, if we wanted more PRG space or more CHR, or more RAM, we would need a special cartridge that had those features.
It does that by taking the much larger ROM, and remapping a small piece of it to the CPU address $8000 (for example).
For C programming, it would be especially difficult to use most of the mappers. One function might expect another to exist and jump to it, except it’s not mapped into place…crashing the game.
Possibly, you could put data (not code) into several small chunks, and swap those into place as the game progresses. Another thing you could do is put the music data into alternate banks, and only put them in place when needed.
This is an advanced topic, and I won’t cover it as much as I should. Let’s discuss a few of the more common ones.
CNROM, allows you to change the entire graphics, between 4 options. (see examples below)
AxROM and UxROM have no CHR ROM. The graphics are in the (large) PRG ROM, and need to be loaded to the CHR RAM to be used. AxROM allows you to change the entire $8000-ffff banks where UNROM has $c000-ffff fixed, and allows you to change the $8000-bfff bank. I would prefer UNROM to AxROM, since you could put your C code in the fixed bank and swap data in and out. It would be very difficult to program a AxROM game in C, some ASM will be necessary.
AxROM is fixed mirroring to 1 nametable, but you could use the variation BNROM (Deadly Towers) which isn’t. Because the entire PRG ROM is changed, even the reset vectors, you need to have a reset vector in every set of banks, which should redirect you to the starting bank, if the user presses the RESET button.
In fact, never assume the startup state. The init code should explicitly put the correct initial banks in place before use. The user could press reset while the wrong bank is in place, for instance.
There is a homebrew version on UNROM, mapper 30, UNROM 256, which is much larger than any commercial game. NESmaker uses it. This might be useful for C programming.
Mojon twins made at least 1 game in C with standard UNROM…
http://forums.nesdev.com/viewtopic.php?p=169438#p169438
For any mapper, you COULD specify that it has RAM at $6000-7fff, in the header. byte 10, bit 4. If you just start writing (reading) to this area, most emulators will assume a PRG RAM chip exists. But, if you want it battery backed (save RAM), you need to indicate it in the header, byte 6, bit 1.
GNROM, mapper 66 (or Wisdom Tree / Color Dreams, mapper 11) can swap the entire PRG and the entire CHR. Of course this presents the same problems as AxROM, getting one bank to call a function in another bank, and making sure the CRT library is available all the time.
.
More advanced mappers, like MMC1.
cppchriscpp uses MMC1 (SxROM) in his C project.
https://github.com/cppchriscpp/nes-starter-kit
some of the bank switching code…
https://github.com/cppchriscpp/nes-starter-kit/tree/master/source/library
You can change the $8000-bfff areas. and you can change either tileset (ppu 0-fff or ppu 1000-1fff). and you can change mirroring from H to V. This is one of the most popular mappers.
And a bit more advanced, MMC3 (TxROM). You can change $8000-9fff, and/or $a000-bfff banks. You can change a smaller area of CHR ROM, as small as $800 sized, for animated backgrounds (waterfalls, etc). And you can use the scanline counter IRQ to do multiple background splits. However, the IRQ code needs to be written in ASM.
I made a simple CNROM example. There are 4 CHR files, and I’m simply swapping between them, and changing the palette.
CNROM has a technical problem, called bus conflicts. The mapper works by writing to the ROM, which if course you can’t do. If the byte at the ROM is different from the byte written, it might not work… so I made a const array, with values 0-3, and I’m simply writing the same value to the ROM value. I know technically, you’re not supposed to write to a const, but with a little trickery, it is easy. I’m using this POKE() macro:
POKE(address, value);
Which casts the address to a char * and then stores the value there.
Press start, the CHR changes (and the palette). It is entirely the POKE() statement that changes the CHR banks.
The corners are missing from the picture, because, I needed a blank tile for the rest of the BG, and the RLE compression required that I have 1 unused tile.