Roguelike Aoc – Christmas is all around
With the Advent of Code finished I’ve decided to write one more post about the fixes and changes that I’ve introduced. During the lessons I was writing down all the ideas for improvements of the code. Below You’ll find all the changes.
Previous location removed
Originally my idea was that previousLocation (in both player and monster) will be used as a backup when eg. during checking next move the target location is not available. However, it turned out that it’s not necessary, and this property is not used in the drawing/pathfinding logic. So I’ve removed it and all the calls to it.
Consistent naming of drawing methods
For some reason the player-drawing function was called playerDraw, and all other drawing ones follow the convention draw-something. So it was a quick renaming to be done – right now we have drawPlayer.
Rename a player to be player, not user
This is something that author left unchanged, but I still don’t know why we use user property name to describe a player in the Level struct. The renaming was fast and clean.
Make player move in all the directions
From the beginning moving around was available only in four main directions. However, I’ve already prepared my Direction enum to have value for all the directions. What I did is then change movePlayer function to also handle additional directions. What is more – I’ve changed the keys used to navigate – right now the numerical keyboard can be used for that.
When creating a level make creation methods consistent
Right now level creation function looks like this:
Level *createLevel(int level, int numberOfRooms, int numberOfMonsters) { Level *newLevel = malloc(sizeof(Level)); newLevel->levelNumber = level; newLevel->numberOfRooms = numberOfRooms; newLevel->numberOfMonsters = numberOfMonsters; // We setup all the rooms and connections on the screen newLevel->rooms = roomsSetup(numberOfRooms); // Put all the Location instances in the Level saveLevelLocations(newLevel); // Monsters creation createMonsters(newLevel, newLevel->rooms); // We setup a player as a last thing - we put him on top of the existing dungeon newLevel->user = playerSetup(); placePlayerInTheLevel(newLevel); return newLevel; }
There was a mixture of direct returned values being assigned to Level instance, and also setting it directly in the method for creating monsters. My task was to rewrite createMonsters function to also return monsters array, and assign it to the proper level property.
Monsters movement improved
Right now the monsters move as they are designed to do – random or seeking a user. The problem is that they do not care for the other monsters/user on the location they want to go. So sometimes it happens, that when fighting the monster it actually ‘disappears’. Why? Because it went on the location that player is staying at. As the player is drawn always last – it somehow ‘covers’ the monster. To fix that I had to change moveMonsters function. One might argue that I’ve removed previousLocation and now it might be handy – but this is the only simple check needed and currentLocation had to be handled in many places. I think this solution is cleaner.
void moveMonsters(Level *level) { for (int i = 0; i < level->numberOfMonsters; i++) { int x = level->monsters[i]->currentLocation->position.x; int y = level->monsters[i]->currentLocation->position.y; if (level->monsters[i]->pathfinding == TRUE) { pathfindingSeek(level->monsters[i], level->player->currentLocation); } else { pathfindingRandom(level->monsters[i]); } // We do not let monster to step on the same place as player if(level->player->currentLocation->position.x == level->monsters[i]->currentLocation->position.x && level->player->currentLocation->position.y == level->monsters[i]->currentLocation->position.y) { level->monsters[i]->currentLocation->position.y = y; level->monsters[i]->currentLocation->position.x = x; } } }
Start using locations in the level for checks
Yeah, that was a tough. My whole reason for introducing locations to the Level struct was to never use direct screen access for checks. For example if I want to check whether on the specific location there’s a monster – well, I shouldn’t be invoking mvinch to get a sign displayed on the screen, but search monsters array. Right now the thing is that it’s not happening 😉 There were a couple of occurrences of mvinch in the code. The first one was in the saveLevelLocations. That obviously had to stay there as this is the input method for locations property.
Second occurrence was in the playerMove function – change was trivial:
level->player->currentLocation->displaySign = unctrl(mvinch(possibleY, possibleX));
changed to:
level->player->currentLocation->displaySign = level->locations[possibleY][possibleX].displaySign;
Next was harder – canGoToPosition. Actually, the name did not sound quite right, so I’ve changed it along with the
parameters and contents:
bool canGoToLocation(struct Location location) { const int ALLOWED_LOCATIONS_SIZE = 2; // Hard-coded to simplify iteration loop char *allowedLocations[] = {FLOOR_SIGN, DOOR_SIGN}; char *locationToCheck = location.displaySign; for(int i = 0; i < ALLOWED_LOCATIONS_SIZE; i++) { if(locationToCheck == allowedLocations[i]) { return TRUE; } } return FALSE; }
In order for the player not to go over the monster I had to also readjust playerMove function:
bool monsterOnLocation = isMonsterOnLocation(&level->locations[possibleY][possibleX], level->monsters); if (canGoToLocation(level->locations[possibleY][possibleX]) && monsterOnLocation == FALSE) { // We switch the current position to the new one where we are allowed to go level->player->currentLocation->position.y = possibleY; level->player->currentLocation->position.x = possibleX; level->player->currentLocation->displaySign = level->locations[possibleY][possibleX].displaySign; } else if(monsterOnLocation == TRUE) { combat(level->player, getMonsterAt(&level->locations[possibleY][possibleX], level->monsters), TRUE); } else { // When player 'stays' where he is then we have to move cursor 'back' to his current location move(level->player->currentLocation->position.y, level->player->currentLocation->position.x); }
We had two files left to change – pathFinding.c and monster.c. I’ve started with the first one as it was easier. The calls being made there were added in order to check if the path can be drawn on the specific location. However, connecting rooms occurs before we store the information about locations in the Level instance. In such case I’ve decided to leave the call in place.
The last change was in pathfindingRandom function. It was tricky – because at this point I’ve realised that the check is still only for type of the location (eg. floor), and not the ability to actually step on the location . This check is performed eg. in the upper chain checking for the player not to be on the location (which I’ve just did above). Therefore, I’ve decided that canGoToLocation function requires new changes.
bool canGoToLocation(struct Location location, struct Player player, struct Monster **monsters) { bool locationTypeAllowed, locationIsEmpty = false; const int ALLOWED_LOCATIONS_SIZE = 2; // Hard-coded to simplify iteration loop char *allowedLocations[] = {FLOOR_SIGN, DOOR_SIGN}; char *locationToCheck = location.displaySign; // Monster is on location - no go if(isMonsterOnLocation(&location, monsters) == TRUE) { return FALSE; } // Player is on location - no go if(player.currentLocation->position.x == location.position.x && player.currentLocation->position.y == location.position.y) { return FALSE; } // We check if the type of location permits entering for(int i = 0; i < ALLOWED_LOCATIONS_SIZE; i++) { if(locationToCheck == allowedLocations[i]) { locationTypeAllowed = TRUE; } } return locationTypeAllowed; }
All function calls were changed, also I’ve removed the previous checks in the moveMonsters function – right now they’re not needed. The only thing to change was to substitute all the calls to mvinch in monster.c file with canGoToLocation function. Won’t be pasting everything here – simple change looks like this (there were also functions signatures changes):
if (canGoToLocation(level->locations[monster->currentLocation->position.y - 1][monster->currentLocation->position.x], *level->player, level->monsters)) { monster->currentLocation->position.y -= 1; } break;
Strange bug – doors that disappear
This is the bug that I’ve discovered recently – every time the user goes through the door (whichever one) – in the start place of player a door sign also appear. The solution is to rewrite this code:
if (canGoToLocation(level->locations[possibleY][possibleX], *level->player, level->monsters)) { // We switch the current position to the new one where we are allowed to go level->player->currentLocation->position.y = possibleY; level->player->currentLocation->position.x = possibleX; level->player->currentLocation->displaySign = level->locations[possibleY][possibleX].displaySign; }
To the one that should be there – direct assign of a pointer:
if (canGoToLocation(level->locations[possibleY][possibleX], *level->player, level->monsters)) { level->player->currentLocation = &level->locations[possibleY][possibleX]; }
That’s it. All the changes I had planned are there. It was a great journey through the whole month, now it’s time to get some of this cherished Christmas’ time 😉
Code
Here is the direct link to the GitHub repo.
Leave a Reply
You must be logged in to post a comment.