The final project on the old freeCodeCamp curriculum, a seemingly gargantuan task to push the React framework to create a procedural generated rouge-like dungeon crawler.
This was my second really big React project and also served as my first introduction to Redux.
The game is grid-based, displaying the user in roughly the middle of the map, rooms branding off in various directions. The player’s view is obstructed by default, showing only their immediate surroundings. The player can move around, pick up items and health packs and fight enemies by ramming into them, every time calculating damage for the enemy and player.
The game progresses through levels which are generated completely randomly each time with a set, or slightly varying number of health packs, enemies etc. The player can progress their weapon to deal more damage on each hit of the enemy and prepare them for the final boss (a really tough enemy!)
The game was balanced to make it actually challenging to play; there is a genuine trade off between getting more XP, health and risk factors going into the next level. The collectables and diminished view port incentivise exploration of the auto-generated labyrinths.
Games like this and the Game of Life are brilliant for learning fundamental data structures and data-visualisation methods in an intuitive way. Years later I found myself learning about such data structures, not knee-deep in C or Java, but by relating what I was reading back to this project.
The core data structure is a matrix; a two-dimensional array representing the rows and columns, each entry holding cell data. A single Board.js component is tasked with itirating over this array and painting a cell a particular colour, off-white if the cell is a floor, blue if it is a wall etc.
React then handles updates to this board by re-rendering after mutations apply to the array. This was a great project to learn Redux with, given that it is an ideal use case for Redux’s feed-forward immutable patterns.
Given that nothing on the board moves independently, there was no need for a sequencer or ‘game tick’, the only actions come from the player moving, this is one of the realisations that helps to break down the problem early one; you only have to listen to four key press’es effectively.
On player move, the action-creator looks to the cell the user wishes to move to and decides an action based on what is found there, it then calculates the next state of the board and updates it all at once.
The biggest challenge with this project was dynamically creating each level, every level, and every game had to be unique. A function was created that followed this basic process:
The centre of the board was found, a starting square of 9 x 9 was turned into ‘floor’ (the default cell being ‘wall’). This was designed to ensure there were no proximity collisions on level load, hoping that the user would not notice the centre of each map was the same.
A function for generating a single ‘room’ took in a direction as an argument and decided on a size (width * height) within constraints. Then, in the direction dictated, using the previous centre as a starting point, it would move it’s pointer a random number of cells across (within constraint). Then in the perpendicular direction it would also move a random direction but only to a maximum of two cells.
For instance if I was ‘moving’ right, the pointer would move to the right a random around between 2 and 7 and then move up or down between 0 and 2.
This would then give us a new section of floor space which overlaps with the previous (thus extending the room) or, if operated by a wall, a door would be made at a random point to connect the two.
The last thing this function would do is make note if any value was outside of the board boundaries, this allowed the implementation of the last major function; the recursive path generator.
The recursive generator would, for each cardinal direction, keep generating rooms using the previous function, picking a new direction each time, allowing it to loop back on itself. It continues until the data being returned would signal that an outer edge had been hit, and the function would stop. This enabled truly unique levels every time.
All that remains is to randomly place a set number of entities per level; enemies, weapon upgrades, health packs and the exit point leading to the next level. A recursive function is used to do this, randomly picking a cell and calling itself again if that cell is occupied.
I also put some small quirks in the game to give it some personality such as the “you died” screen and the choice of weapons in the progression such as “Rock on a stick” and “The power of React itself”.
This being a project in summer 2017, it was built using an older version of both react and redux which is amusing in hind sight. How remembers React.createComponent({}) ? or lifecycle method based Redux store subscriptions?
Looking back, this project was a key moment in my development career. Sure, the code is highly inefficient and messy by my current standards given that three years have passed as of time of writing. There were also some pretty bad mistakes and antipatterns which I learned from later on such as the un-pure functions called from within the reducer and functions which dispatch also in the reducer.
But the biggest effect this project had was that it allowed me to realise the size of a challenge I could overcome. Before starting, I had taken a break from code because I had not idea where to even begin with this challenge. I believed I would quickly burn out or waste weeks worth of time only to eventually fail, thus validating my imposter syndrome.
I can’t remember what exactly what let me get over this self-imposed block but I do remember the thought process, bringing together all of my experience and knowledge of algorithms to that point, and experiencing inspiration about the issue of procedural generation which lead me to start.