
This post is part of a series on creating a roguelike game in C#. For the latest chapters and more information, please visit the Official Project Page on AndrewComeau.com. The current code for this project is also available on Github.
Don’t miss the next chapter! Subscribe for free on Substack to be notified when new chapters are posted.
The project …
It’s been a couple of weeks since the last post and I have taken some time away from the project. I won’t bother spinning it – I’m a little burned out. Between the planning, the coding and the writeups, it started to feel like a real job with no actual paycheck. I don’t want to send it into hiatus again like I did in 2023, especially since the game is reasonably close to being at a completion point, but I needed to take time to bounce back to other things and recharge.
I have made some changes, though, so here’s where it stands now.
Recent changes
As I said last time, it’s important to recognize the difference between passing a copy of a value in code and a reference to an object. The implications and applications of this can take time and experience to work out. Another example of my over-caution when referring to objects was with code like this.
public List<Inventory> DetectInventory(MapSpace Location)
{
List<Inventory> foundItem = (from Inventory inv in MapInventory
where inv.Location!.X == Location.X
&& inv.Location.Y == Location.Y
select inv).ToList();
return foundItem;
}
The map’s inventory list holds the items that have been placed on that level and each Inventory object holds a reference to the MapSpace that it’s supposed to appear on. When the inventory is added to the list by the map creation process, it copies references to the map’s MapSpace objects to the Inventory object.
while (MapInventory.Count < startingCount + Number && --maxAttempts > 0)
{
do
itemSpace = GetOpenSpace(false);
while (itemSpace == null);
do
invItem = GameInventory.GetInventoryItem(itemSpace)!;
while (invItem == null);
That means as long as the DetectInventory() function is receiving a reference to a MapSpace object on the current map, it should be able to test it for equality against the MapSpace objects referenced in the inventory list without having to drill into the location coordinates.
public List<Inventory> DetectInventory(MapSpace Location)
{
List<Inventory> foundItem = (from Inventory inv in MapInventory
where inv.Location == Location
select inv).ToList();
return foundItem;
}
Of course, if it’s sending a clone of the MapSpace object instead of the map reference, this will fail pretty quickly which means that something else in the code needs to be fixed because it’s making unnecessary copies. In this way, writing the code to use object-oriented features helps to ensure that the rest of my code will stay in line.
I also looked at the amount of movement the player had on Fast Play mode. The function MapLevel.DetectObstruction() looks at the PassableSpacesGlyphList List<Char> to determine if one of the places around the player has something they need to stop for like the stairway or an inventory item. The movement seemed a little restricted to me but I couldn’t exactly pin down how. Finally I decided that it works well enough for now not to mess with it but I did see another chance to replace separate X, Y coordinates with a MapSpace.
public bool DetectObstruction(int xPos, int yPos)
{
// TODO: This might need to be adjusted to allow more gliding.
bool retValue = false;
foreach (MapSpace space in GetSurrounding(xPos, yPos, 1))
{
// If there's something one of the spaces, return True.
// Ignore player's space.
if (!retValue)
if (space.X != xPos || space.Y != yPos)
retValue = (!PassableSpacesGlyphList.Contains(PriorityChar(space, false).DisplayChar));
}
return retValue;
}
public bool DetectObstruction(MapSpace Location)
{
bool retValue = false;
foreach (MapSpace space in GetSurrounding(Location, 1))
{
// If there's something one of the spaces, return True.
// Ignore player's space.
if (!retValue)
if (space.X != Location.X || space.Y != Location.Y)
retValue = (!PassableSpacesGlyphList.Contains(PriorityChar(space, false).DisplayChar));
}
return retValue;
}
I imagine I’ll be finding examples of this to change for awhile.
Monster distance attacks
There are a couple of monsters like the Ice Monster and Dragon, that can launch distance attacks just like the player can throw objects. For now, I decided against implementing it since it requires extra decisions on each monster movement for only two monster types. I did, however, add a new function, GetRegionAndPlacement() to the MapLevel class that finally enables the rest of the code to quickly determine if a point is within a room or outside.
private (int Region, bool InRoom) GetRegionAndPlacement(int xPos, int yPos)
{
// The map is divided into a 3 x 3 grid of 9 equal regions.
// This function returns 1 to 9 to indicate where the region is on the map.
(int Region, bool InRoom) returnVal;
//int regionX = ((int)RoomAnchorX / REGION_WD) + 1;
// Using Ceiling works regardless of where in the region the coords are,
// even if they're on the edge.
int regionX = (int)(Math.Ceiling((decimal)xPos / REGION_WD));
int regionY = (int)(Math.Ceiling((decimal)yPos / REGION_HT));
returnVal.Region = (regionX) + ((regionY - 1) * 3);
returnVal.InRoom = (RoomInteriorGlyphList.Contains(levelMap[xPos, yPos].MapCharacter.DisplayChar));
return returnVal;
}
I’ll be able to use this to more clearly see a monster’s relative position to the player which might fix a couple of problems I’ve noticed with items like the Scroll of Create Monster which sometimes creates a monster outside the room where the player is.
Other fixes
- The ListingDescription() function in the Inventory class needed cleanup and I took care of that with some helper functions and adjustments to the messages.
- When the player wields a groupable item and then puts it down, the program will now set it to the default for its inventory type and it can go back into the group to avoid taking up an inventory slot.
Where are we going from here?
I need to finish adding monster special attacks to the game. I coded a few for the article and then got distracted and I need to go back to that. There are some minor messages to be added to the Inventory types, too.
Then, it’s time to add a database to the program to hold program data, saved games and a scoreboard. I’m still planning on going with a SQLite database with Entity Framework to help references the objects in code. Rogue SQLite will be a short series in itself and I’m leaning toward making it a video series first with the articles here on the site based on them. I’m thinking the series will end with a complete playthrough and testing of the game to see what bugs I can shake out.
Stay tuned …