Late january 2024, i remember that the SMS Power (www.smspower.org) anniversary competition is happening in march, since i usually try to enter on the music competition, i thought to myself, what if i tried my hand at the coding competition instead? might be cool to try and code something for my favorite console, for a change.
-- Part I: The Toolset --
I remember that there's a C devkit, appropriately named "devkitSMS", so i googled that and found the github for the devkit. So i started reading what i'd need to install to make it work. There's SDCC (small devices c compiler) and the devkit itself. Ok seems simple enough. Installed everything, run a test following the manual and it seems correct. Went to the examples folder and there's something there to test. Try to build the examples and it managed to get them both to run, nice, time to learn.
Or is it? Just realized i have no tools to make graphics or sounds. devkitSMS seems to like files converted with Maxim's BMP2Tile, so downloaded that, but apparentely, it also needs another plugin, time to search for that. Installed that, managed to get a very simple demo running.
Its just my logo, basically, graphics tools, check. What i'll be using: Libresprite to draw, BMP2Tile to convert to SMS, seems good enough.
Its been a while that i've been wanting to learn Furnace, to write tracker music for the SMS, instead of using my 'usual' method of recording midi in the my daw, converting to vgm, editing the vgm in mod2psg2, so i went to try and get, install and get a basic tutorial going. Managed the very basic, so i guess its time to try my hand at adding sound to my logo demo. Thanks to the PSGlib included in the devkitSMS, it was easy enough, a couple of minutes and lines of codes and i had my old western_sms.vgm song playing the same. NOW it's time to learn.
-- Part II: First tests and coming up with an idea --
Next step, testing controllers.
Added a very badly made sprite over my logo background and made it move with the controller. Realized the sprite wraps around the screen when going off-screen, might be easy to implement something like Asteroids, then? But i really wanted something a bit more involved and less cliché than Asteroids. Maybe i should do Space Invaders? But there's already a good Space Invaders for the SMS, i'm sure.
So, time to start searching the web, 'best fixed screen games', 'famous arcade games' and so on. Nothing really grabs my attention. So i decided that i might just convert an Atari2600 game, seems simple enough, but... which one? I know haroldoop did a very cool version of Turmoil, so that's out of question. Kaboom might require more skills than i currently have, Enduro definitely requires more skills than i currently have and the SMS already has Outrun, Keystone Cappers requires more graphical skills than i have, Beamrider seems cool, might try to do that... but i realized that animating the background grid with tile based graphics and my current skill set would take more time than i had (a little under two months before the SMSComp deadline).
Then, browsing youtube, i saw a game that might be a good fit, Solar Fox, an arcade game originally, with a slightly tweaked VCS port, that might be it.
Watched about 2hrs of gameplay, of both the original arcade and the VCS port, my feeling after it was: "i can do this", 100%. But, i didn't want to do a port, i just want to inspire and get ideas and build my own game, so it begins, the coding of the Sega Master System "port" of Solar Fox.
-- Part III: SMS_setTileatXY ---
First things first, i start with a routine to draw walls on the 4 limits of the screen.
My very first initial idea was to use sprites for the walls, but it turned out to be a very bad idea, very quickly, since the walls won't move, there's no reason to use them as sprites, instead of background tiles, so, that's what i did.
// Vertical Walls // for(y = 0; y < 22; y++) { SMS_setTileatXY(1,y,108); SMS_setTileatXY(31,y,108); } // Horizontal Walls // for(x = 0; x < 32; x++) { SMS_setTileatXY(x,0,107); SMS_setTileatXY(x,21,107); } // Corners // SMS_setTileatXY(1,0,105); SMS_setTileatXY(1,21,105); SMS_setTileatXY(31,0,106); SMS_setTileatXY(31,21,106);
That does it, pretty simple, two columns (vertical walls) and two lines (horizontal walls), with 4 decorative corner tiles, the beginning of a playfield. And for the walls inside the playfield, i wrote a very simple routine to draw them, grabbing the location from the arrays, so i could design a proper level.
void walls_draw(void) { for(int i = 0; i < level_walls_num; i++) { SMS_setTileatXY(walls_x_y[i][0] / 8, walls_x_y[i][1] / 8, 100); } }A very simple routine, but you might notice that i divided the value by 8, that's because, there's a difference between tile position and pixel position, since i didn't realized that in the beginning, my arrays are storing pixel position for the X/Y of the walls, instead of tile position, and since each tile is 8x8 pixels, a very quick and dirty fix.
Now i had already had a ship, controlled by the player, and bound inside a playfield, with a couple of walls drawn in, time to write a collision detection routine. That was the first 'obstacle' i encountered, because i knew, due to previous experiences writing other small games in other languages that collision detection could make the game craw to a halt, specially since it is a routine that you need to check at least every other frame, in most cases of action games. And since my wall locations were written inside arrays, that also meant i needed to loop the arrays. Luckly for me, the little z80 could handle it like a champ.
This is what i came up with:
for(int i = 0; i < level_walls_num; i++) { wall_x = walls_x_y[i][0]; wall_y = walls_x_y[i][1]; // collision bounderies // if( pl_x < wall_x + 8 && pl_x + 16 > wall_x && pl_y < wall_y + 8 && pl_y + 16 > wall_y) { ...It ssems to work, i can even call it up every single frame and since i'm not really re-drawing anything at the moment, it seems that i still have cpu cycles to spare. And since it works, i used a similar collision check for the shots and the little 'satellites' which are called 'pellets' in the code, because i still wasn't sure what to call them. :)
-- Part IV: random number is random --
One of the things that i've realized its very difficult to do on the SMS is random numbers, this is because the random seed for the rand() function in C is generated at compile-time and not at run-time, which means that everytime, the rand() returns the same things, unless you re-compile the program, which is not exactly viable, especially in a system like the SMS. But if you change the seed, you can simulate randomness, other way to simulate randomness is to generate a random LUT and use that either as the seed or as the numbers themselves. I took the first approach, basically i created an unsigned int and add to it every frame, until it reaches the int limit (no need to be a long, in this case, i think).
// For the random_level_generator // unsigned int randomseed = 30; // Used to seed the rand command, 30 just being whatever to avoid starting at zero // randomseed++; if(randomseed == 25600) { randomseed = 0; } // unsigned int limit, no need to go over it.That allows me to re-seed the rand() command, which different values, and since there's hardly a chance of someone taking the exact same number of frames in a menu or to complete a level, it kinda makes the random seems really random. Next step is to write a simple function to generate a random number between two values:
int rand_num(int lb, int ub) { srand(seed); // re-seed the random number generator each time // int ret; ret = rand() % (ub - lb + 1) + lb; return ret; }With that simple trick, i can now generate 'random levels', based on some parameters i designed, to avoid getting levels that were too easy or even worse, impossible to complete, which is never what you want.
// Level Random // if(level == 0) { srand(seed); // re-seed the random number generator each time // int r_pellet_x = 0; int r_pellet_y = 0; level_pellet_num = rand_num(5,20); level_walls_num = rand_num(5,15); en_l_y = rand_num(en_lim_top, en_lim_bot); en_l_dir = 0; en_r_y = rand_num(en_lim_top+10, en_lim_bot-10); en_r_dir = 1; level_pellet_collected = 0; // Pellets // for(int i = 0; i < level_pellet_num; i++ ) { r_pellet_x = rand_num(pl_lim_left, pl_lim_right); r_pellet_y = rand_num(pl_lim_top, pl_lim_bottom); pellets_x_y[i][0] = r_pellet_x; pellets_x_y[i][1] = r_pellet_y; r_pellets_x_y[i][0] = pellets_x_y[i][0]; r_pellets_x_y[i][1] = pellets_x_y[i][1]; } // Walls // while(i < level_walls_num) { w_x = rand_num(pl_lim_left, pl_lim_right); w_y = rand_num(pl_lim_top, pl_lim_bottom); wall_pellet_collision(w_x, w_y); if(wall_pellet_col == 0) { walls_x_y[i][0] = w_x; walls_x_y[i][1] = w_y; r_walls_x_y[i][0] = w_x; r_walls_x_y[i][1] = w_y; } i++; } } walls_draw(); gamestate = 1; // goto gameplay //Its really not overly complicated, en_l_ variables set where the left turret will start, and en_r_ variables set where the right turret will start. Then we get random numbers for how much satellites there will be on the screen (level_pellet_num) and we place that inside the playfield which is determined by pl_lim_left, right, top and bottom, that is to avoid generating an unreachable sattelite.
Next step we generate a random number for how much wall (blocking) tiles there will be on the level.
For each new wall, we also check if its colliding with a already placed sattelite, you can't have overlap between them, to avoid generating an 'unsolvable' level, so if, and only if, there's no sattelite on that spot, we can place the wall, otherwise, we try a new random location, until we fulfill the required number of walls.
Then we call for the routine that draws the wall tiles on the screen and move to the gameplay.
This 'random world' is imho, the best thing about the game, because the gameplay is really simple and designing a bunch of levels that are interesting enough to keep the gameplay interesting even after the player have finished them would be above my skill level, so with the random world sequence, we can at least add a real re-playability factor to the game.
-- Part V : conclusion --
In the end of the day, Raposa do Sol was a learning experience for me, which is why i ended up adding things like SRAM and even voice samples, they were added more for me than for the improvement of the game experience itself, because saving hi-scores on this game is not something that really makes or break the same and the same about anyone hearing my voice very muffled saying 'raposa do sol' or 'game over, yeah" (because Sega Rally references are *tight*).
This was very fun to make and i've learned a lot, so i consider this project worth of the time i've invested into it and if anyone can also benefit from this, even better. Which is why the code is avaiable: github.com/jflores82/raposadosol feel free to explore it and if you have any questions just hit the contact button, maybe you too will have fun making little games for the old Sega Master System.