Search the Community
Showing results for tags 'procedural'.
-
Dragon's Descent Level Generation The latest build is here: DragonsDescent_2019_8_15_2019.bin A recent build is here: DragonsDescent_3_19_2019_Beta7.bin A fairly recent build is here: DragonsDescent_3_5_2019_Beta6.bin An older build of the game is here: DragonsDescent_1_10_2019_Beta5.bin ...an an even older version here: DragonsDescent_12_16_2018_Beta4.bin When I posted my Atari 2600 game "Dragon's Descent," I said, among other things, that it was an experiment in maze generation for the Atari. I'm fascinated with algorithmically generating virtual spaces, from VR to arcade games. When given only a few kilobytes to work with, using procedural systems almost becomes a necessity. I wanted to see if I could make a "desert island" game that someone could play multiple times while still getting a sense of novelty - one of my favorite parts of a good Roguelike. I learned to program for the Atari 2600 using batari Basic, which has proven very handy and fun. I'm using the 16k SuperChip template - this gives me four banks to work with, a little more resolution, and extra RAM, but still keeps things relatively simple. High end games in the early 80s could use this setup. One caveat - Some of this comes from my imperfect understanding of how rand works in batari basic. In practice it seems that using the 16-bit random generator means you can set each byte of the "seed" independently, either by setting rand = <value> or z = <value>. It seems to work pretty well for me, but there's still a lot I can learn... (click to enlarge) Each level generated in Dragon's Descent comes from a specific random seed based on the 16 bit random function used by batari Basic. Using the same seed will produce the same maze. Since the random seed uses two bytes, I can vary each byte, and in this case I usually pick one as a "world" byte and the other as the "level" byte, adding 1 to the "level" byte to create a new, but replicatable maze level. I even allow the player to select the seed they want using the "Select" switch, acting as a de-facto level select mode. The random seed primarily drives where the actual rooms exist within an 8x8 grid (7x8 after a fix, a silly story I might get to later). I allocated 8 bytes in RAM, and used their bit values to indicate if a room exists (1) or not (0). Using the array function I could see what each row maze rooms looked like. Going into this, I thought that getting the bit value would be relatively easy, but alas Batari Basic cannot use a variable when checking a specific bit of a variable (variable{2} is fine but variable{x} is not). The other way to check a given bit in a byte is to bit shift, but that required a bit of assembly. My clumsy implementation produced a bug I only found much later, but the system seems to work. As for the generation of rooms within the maze, the algorithm is pretty simple. Each maze has several rooms, but specifically includes a Starting Room, an Exit Room, a Key Room, and a Treasure Room. Each of these rooms is placed in a separate quadrant of the 8x8 maze, although which room gets which quadrant is randomly determined. Separating the rooms like this guarantees that they won't be placed on top of one another (bad) and -usually- spreads them out to make a more interesting maze. If I just did this, though, there wouldn't really be any dead ends, and the odds are high of each room being very close to each other (there's a chance they'll all be next to each other, and a 4 room maze is not really a maze). To help add potential loops and dead ends I also add in 2 "spare" rooms placed randomly but each on specific areas of the maze, roughly corresponding to opposite corners of the maze, away from the center, to make sure the maze is properly spread out, and entertain the possibility of a dead end or two. To connect a room I check to see where the rooms are in relation to each other - I then create a horizontal corridor until a passage of rooms is vertically aligned with the target room, and them vertically connect them. In a larger maze array this would show up as a fairly obvious set of "L" shaped passages, and sill kind of does, but an 8x8 array is small enough that the resulting patterns aren't too obviously L shaped. As the passages criss-cross you get loops, dead ends, and large internal spaces, creating new rooms as I make my way towards a special room. I don't connect all rooms to each other - that would pretty much fill the 8x8 array and always leave a fairly open maze. Certain rooms will always connect with certain other rooms, which in sum total means you can eventually get anywhere you need to. The start room always connects to a spare room, while that same spare room always connects to the exit. This increases the probability of more interesting maze - while a straight shot to the exit (or just having it next door) can still happen, you generally have to turn a few corners, even before you try to find the key. The 8x8 bit array just stores if there is a room or not. I also store the location of each of the 4 special rooms in their own byte, being a value between 0 and 63, accounting for the 64 possible rooms in the 8x8 grid. At any given point I might need to convert this 1D array value to a semi-2D bit/byte location, which results in some clumsy code on my part. What I outlined above accounts for the maze room placement, but not the actual contents of the room- this involves another system. Each of the special rooms has their own playfield data, and I have around 10 different room designs defined as playfields for non-special rooms. In order to determine what kind of room will be in a given place on the 8x8 array, I have an array of 64 random "noise" values stored in ROM. A simpler maze setup would just equate one value to one type of room, so the array bit at (2,3) would always have room 7, for instance. Instead, I also take into account what level the player is currently on (0-7), and add a further lookup table of rooms for each level. While mixing things up a little more, this also allows more difficult or wall-heavy rooms to have a higher chance of appearing the deeper you go. What I've outlined here is the "pseudo-code" for generating the mazes - implementing this all in batari Basic was interesting, and required a few tricks. Explaining everything would take a while, but I'll say here that key components involved referring to 8 adjacent variables in RAM as array data, and bit-shifting/ORing binary values in order to actually set the room values. According to my calculations, there are potentially 255x255x(8 variants dependent on level depth) = 520,200 different mazes. A lot of this depends on the quirks of the random generation system, and I'm certain there are repeats, but I did a few experiments in outputting a few dozen mazes and found a pretty good variation - below are a few maze shapes that have been produced (I ended up outputting the data to the player sprite and making screenshots to make these): And here's level 001-001: I might go into further detail on how the code actually works in a later post, but I wanted to get some of this knowledge posted, and show how some of the game works "under the hood." DragonsDescent_3_22_2019_Beta8.bin
- 5 comments
-
- 3
-
- atari 2600
- batari basic
-
(and 1 more)
Tagged with:
-
DRAGON'S DESCENT Direct your dragon through a sprawling labyrinth, hunting for treasure, power, and danger! Dragon's Descent is currently part of the Holiday Homebrew High Score Competition - here is a ROM you can use for the contest: DragonsDescent_Holiday_2019.bin Now for sale on the AtariAge Store! Instructions on how to play below... -Thousands of possible maps to explore, either randomly selected or predetermined -Eight different types of enemies -One mid boss, one final boss, one hidden boss -Powerups to increase firepower or health as you delve deeper into the labyrinth ...and an easter egg or two, of course! Here's a video of the first three levels, up to the midboss. The default mode has four more levels ending with the final boss, and other modes allow potentially thousands of different levels! This game is, among other things, an experiment in procedural maze generation for the Atari 2600 - There are a few secrets to find, so I won't reveal everything here, but I will give a basic outline of the controls and mechanics. I posted little write up of how I generated the mazes, and programmed the game in general. Many thanks to those on this board who have posted an incredible amount of useful information, as well as those who have authored the kernels and other components on which this game has been programmed. STORY Legends speak of a labyrinth created by the mind of a dreaming elder dragon. This maze is filled with the promise of wealth, power and danger-an endless length of corridors, with spectres and monsters appearing out of thin air, and strange happenings occurring the deeper one travels and survives. You are a young dragon yourself, perhaps trapped here, perhaps tempted by the wealth and power that drives your kind. Regardless, you have little choice but to find your way through the corridors and chambers of the labyrinth, finding glory, or perhaps escape... CONTROLS Joystick - Move the dragon around the labyrinth. Button - The dragon will breathe fire in the direction it is facing. SETTINGS Left Difficulty A - Game will continue indefinitely, only ending with a game over. Left Difficulty B - Game will end after you complete level 7 Right Difficulty A - You will start in a randomized maze. Right Difficulty B - Maze will be the same layout each playthrough. Game Select (Make sure right difficulty is also set to "B") - Will allow you to set the random "seed" when starting the maze. Move the joystick left/right to select the left or right seed, each can be set to a value between 1 and 255. The title screen will reflect what options the game is set to - an infinity symbol for an unending maze, and an alternating maze pattern for random mazes. HOW TO PLAY Depending on your game settings, you may find an end to the maze on the 7th level, or the maze can continue until you are defeated, trying to attain the highest score! A - The Player B - Dragonfire C - Monster D - Firepower Meter E - Score F - Health Meter G - Room exit Each level of the maze is made up of several rooms - you can leave through any exit on the boundaries of the screen you find. To make progress in the maze, find the key on each level, and then the level's exit. The exit, resembling a door with a key imprint, will only activate if you touch it while you have the key found on the same level. Upon each new level you will face more dangers but also potentially increased power and scoring! Key Exit Avoid touching walls and enemies - doing so will deplete your hit points, and eventually terminate your game! Scoring comes from collecting gems and defeating monsters. You get more points for defeating monsters in deeper levels, and a slightly higher score for each shot you use with higher fire breath power. In addition to a key and exit, each level of the labyrinth has a treasure room: This room allows you to pick one of three power ups, just wait until you see the one you want: Gem - increases your score. Heart - increases your total hit points, while completely replenishing your health. Lamp - increases the strength of your fire breath, while refilling its supply. Don't stay too long on a single level, or you may find things getting much more difficult! The deeper you explore, the more monsters, dangers, and higher scores you find... Your hit points are indicated by meter on the right, as well as the color of the dragon. The strength of your fire breath is indicated by the color of your score, as well as the size the fire itself. If your firepower ever increased, you only have a limited amount you must replenish somehow - this amount is indicated by the meter on the left. If it ever runs out, you will go back your initial, weakened fire breath. You can find non-flashing hearts and lamps from fallen enemies, which will replenish a small part of your hit points or fire breath, respectively. If you survive long enough, you may reach a maximum amount of hit points or firepower, in which case your health meters or score will be flashing. SCORING Defeating Enemies: Fiery Eye - 5 points Medusae - 10 points Dragon Head Sentry - 10 points Teleporting Masque - 20 points Janus Guardian - 25 points Ghost - 20 points Dragon - 25 points Shadow - just 1 point base, if you can even manage it. Getting rid of it might be reward enough, though... Revenant Dragon (Midboss) - 500 points Elder Dragon (Boss) - 1000 points Jeweled Dragon (a hidden beast) - 2000 points Collecting a gemstone in a treasure room will get you 500 points. Your strength of your firepower is also added to your score whenever you hit an enemy with your fire breath, so you can gain 1-6 points for every hit even if an enemy is not defeated. Defeating enemies on lower levels adds further bonuses: Level 2 - 1 point Level 3 - 2 points Level 4 - 3 points Level 5 - 4 points Level 6 - 5 points Level 7 - 10 points Level 8+ - 15 points HINTS -Find a balance between increasing your hit points and increasing your firepower - each level gives you the opportunity to do one or the other. -Gems give you large amounts of points, but your forgo an increase of power for that level - they're for those brave or foolish enough to think they can survive regardless. -The beginner mode always gives you the same maze, be sure you become familiar with the game before tackling the random mazes offered by the advanced mode. -Each enemy has a specific type of behavior, learn all of them-and learn how to counter them! -Time can be your enemy, but remember that you don't have to fight everything - pick your battles! -Despite the enticement to hurry, be patient and careful! Most situations can be escaped with a little bit of caution and forethought, and impatience has ended more games than the cruellest monster. There are many secrets to discover within the labyrinth, so I won't tell you everything here! Older builds: Another change to the door graphic - the door should be flashing with a key symbol. DragonsDescent_2019_8_15_2019.bin This older build has a changed the graphic for the exit. I even squeezed a few more bytes out so that it has "closed" and "open" graphics, depending on whether you have a key. I'm hoping this is close to the final version, so any feedback on how it plays is appreciated! DragonsDescent_2019_8_12.bin I've added four separate high scores, one for each "mode" - they should show up when you have the appropriate switches/settings applied on the title screen. I think it works properly (with caveats, for instance you can fool it by using the switches mid-game) but I would appreciate feedback! I also added an easter egg bug fix: DragonsDescent_2019_May_HiScoreTests.bin DragonsDescent_3_22_2019_Beta8.bin 3-22-2019 Another update - found a semi-exploit (really just method of scoring late game that results in boring gameplay) and adjusted things to discourage the behavior. DragonsDescent_3_19_2019_Beta7.bin 3-20-2019 Another update - no better method to find a bug or quirk in a project than to claim it's finished! The method to reach the hidden boss, while functional, had some odd loopholes and inconsistencies that I wanted to close. Fixed a thematic inconsistency involved with getting to the hidden bonus boss. DragonsDescent_3_5_2019_Beta6.bin - Font change and a few other tiny adjustments. DragonsDescent_1_10_2019_Beta5.bin - Retry random maze option (press up or down on the Game Over screen to see the "Retry Maze" screen, press button to start again), starting rooms have no enemies, score placement adjusted. DragonsDescent_12_16_2018_Beta4.bin - Maze generation fix, random seed shown when picking random level, other small fixes I might go into more details about what I did to fix the maze generation later - the short answer is that every seed -seems- to work now, without the hacky checker I had in earlier versions. I also added a -little- more time solve a given level before various "complications" arise. It can still be a little mean. I'm keeping an informal version history even as I remove the older versions - I can repost them if folks clamor for the older versions, but hopefully the bugs present in them have been addressed. 3-5-2019 - Changed to a custom font. Semi-final build. 12-16-2018 - Current public version. All attempted maps seem to be solvable, certain UI color cycling adjusted. 12-15-2018 - Tentative "fixed" beta for random mazes This version has a band-aid for the random mode bug - I'm keeping it here for the moment in case the above version has some unforseen bug or error. 12-14-2018 - Old version, only play the default mode (Left/Right switches set to "B") on this one. I've tentatively tested it on my ol' sixer as well as the Flashback Portable, and the game seems to run ok, although I would welcome any bugs/issues found if anyone else plays. I'm curious to see what people find! Updates: -You can now retry a randomly generated maze by pressing up or down on the Game Over screen - when you see words "Retry Maze," press the button to restart the game at your old starting point. -Starting rooms on a level no longer generate enemies, giving you some time to breathe or get used to the controls. -Hopefully the level generation bug is fixed - any random seed should generate a solvable level. When choosing a random level, you will be shown the seed in case you want to try the level again. -I also fixed a flashing problem to a more pleasant "pulsing" effect for max health/fire breath. DragonsDescent_2019_April_Competition.bin
- 42 replies
-
- 22
-
- batari basic
- 2600
-
(and 3 more)
Tagged with: