Comeau Software Solutions

Making sense of technology since 2000.

Menu
  • Home / Updates
  • Services / Contact Us
  • Rogue C# Project Page
  • More Resources
    • Join Ocala’s Tech Community!
    • YouTube Channel
    • Personal Site
Menu

Rogue C# – A Few Random Updates

Posted on July 13, 2023July 13, 2023 by Andrew Comeau

I had to take a break in the development of the roguelike game to look at other things for awhile. As I get back into it, I decided to check off a couple of minor features and a couple of bugfixes from testing. I think I’m avoiding getting into the magical items like scrolls and potions as I try to find just the right way to code them but that’s definitely next on the list.

“Play Again?”

The ability to start a new game after the player dies is an important one in the game; you don’t want the user to have to close and restart the program each time. I finally decided to get on it and it wasn’t a difficult fix. The code change was on the main form itself.

I refactored the code for starting a new game into a StartGame() method which accepts the player name as an option. It’s called by btnStart_Click() which passes the name from txtName. If there is no name passed, it throws a messagebox asking for one.

private void StartGame(string PlayerName = "")
{
    if (PlayerName.Length > 0)
    {
        currentGame = new Game(txtName.Text);
        listStatus.DataSource = currentGame.StatusList;
        pnlName.Visible = false;
        lblArray.Text = currentGame.ScreenDisplay;
        lblStats.Text = currentGame.StatsDisplay();
    }
    else
        MessageBox.Show("Please enter a name for your character.");
}

DungeonMain_KeyDown() will detect if the GameMode property of the current Game object is set to DisplayMode.GameOver, show the panel with the player name and start button control and fill in the player name from the current game as a default.

Bugfix – Falling off the map

The game’s hallway generator occasionally draws hallways to the edge of the map and this led to a fatal error when my player reached the end of one of these hallways because the SearchAdjacent() function tried to find a space with a X coordinate less than 0.

First, I updated MapLevel.HallwayGeneration() to prevent the drawing of hallways that far.

hallwayLimit = hallwaySpace.X < 2 || hallwaySpace.Y < 2 
    || hallwaySpace.X > MAP_WD || hallwaySpace.Y > MAP_HT;

The hallwayLimit boolean variable is then used as an extra condition when deciding whether to add another space to the hallway if it cannot connect to an existing hallway.

if (!hallwayDug && !hallwayLimit)
{
    // If there's no hallway to connect to, just add another space 
    // where possible for the next iteration to pick up on.
    adjacentChars = SearchAdjacent(EMPTY, hallwaySpace.X, hallwaySpace.Y);
    ...

The larger problem was in the overload of the SearchAdjacent() function used to look ahead when the player is moving. It simply assumed that all adjacent spaces were available and didn’t test the coordinates to see if they were on the map edge like the other overload did. I fixed this and updated references to it to account for the update.

public Dictionary<Direction, MapSpace> SearchAdjacent(int x, int y)
{
    // For each direction, add the existing mapspace if available.
    Dictionary<Direction, MapSpace> retValue = 
        new Dictionary<Direction, MapSpace>();
         
    if (y - 1 >= 0)  // North
        retValue.Add(Direction.North, levelMap[x, y - 1]);

    if (x + 1 <= MAP_WD) // East
        retValue.Add(Direction.East, levelMap[x + 1, y]);

    if (y + 1 <= MAP_HT)  // South
        retValue.Add(Direction.South, levelMap[x, y + 1]);

    if ((x - 1) >= 0)  // West
        retValue.Add(Direction.West, levelMap[x - 1, y]);

    return retValue;
}

The references to this function also needed to take into account that it might not be returning all directions anymore so, somewhere before the reference, something like this would be needed.

if (adjacent.ContainsKey(direct)) ...

This searches the Dictionary that the function returns for the specific member of the MapLevel.Direction enumeration that is to be used.

The Great Refactoring – I missed a spot.

I noticed while testing that monsters and inventory were appearing in empty space on the map, outside of the rooms and hallways. I haven’t been able to get it to happen again and it could have been due to testing but I checked the MapLevel.RoomGeneration() method which generates individual rooms on the map and found that it was still looking at the actual character properties of the MapSpace objects rather than using the MapLevel.PriorityChar() function to determine the content of the space when placing inventory and monsters. That’s been fixed.

public char PriorityChar(MapSpace Space, bool ShowHidden)
{
    Monster? monster = DetectMonster(Space);
    Inventory? invItem = DetectInventory(Space);

    char retValue;

    if (monster != null)
        retValue = monster.DisplayCharacter;
    else if (Space == CurrentPlayer.Location)
        retValue = Player.CHARACTER;
    else if (invItem != null)
        retValue = invItem.DisplayCharacter;
    else if (Space.AltMapCharacter != null && !ShowHidden)
        retValue = (char)Space.AltMapCharacter;
    else
        retValue = (char)Space.MapCharacter;

    return retValue;
}

Monsters and inventory items are now maintained in lists at the MapLevel class level since the major refactoring I did a short while ago so PriorityChar() looks for both in a space and then decides which character to display

Spawning new monsters

New monsters are supposed to be added every so often while a player remains on a level. According to the documentation I found, it’s supposed to happen every so many turns but I decided to add one each time a monster is killed – a reincarnation of sorts. I thought about having a new one spawn every time two monsters collided but the idea of an Emu and a Centaur possibly spawning a Hobgoblin together just weirded me out a little.

First, I refactored the code that was adding monsters as part of the room generation into a new AddMonsters() method. This simplified it as I’m now able to use the MapLevel.GetOpenSpace() function to just get an empty space on the map instead of manually searching for a random one. I specified True for the function parameter to indicate that a hallway space was acceptable.

public void AddMonsters(int Number)
{
    Monster? spawned;
    MapSpace itemSpace;

    // Pick random monsters until its probability of appearing is 
    // within the random limit generated.
    for (int i = 1; i <= Number; i++)
    {
        do
        {
            spawned = Monster.SpawnMonster(CurrentLevel);
        } while (spawned != null && rand.Next(1, 101) <= spawned.AppearancePct);

        // Place monster on map.
        if (spawned != null)
        {
            itemSpace = GetOpenSpace(true);
            spawned.Location = itemSpace;
            ActiveMonsters.Add(spawned);
        }
    }
}

The initial spawning of monsters was previously done during the generation of each room but I looked back and couldn’t see a good reason it had to be done that way so now it’s done just after the generation of hallways in the MapGeneration() method. This means that monsters might double up in rooms but that’s okay, they can share. As shown above, the AddMonsters() method accepts an argument for the number of monsters to be created so I can add nine at the beginning and then one as needed. Later in development, I might decide that two or three monsters need to be spawned under certain conditions.

To spawn the new monsters, I simply added a couple of lines in the Game.Attack() method after the player defeats the monster.

if(Defender.CurrentHP < 1)
{
    CurrentMap.ActiveMonsters.Remove(Defender);
    UpdateStatus($"You defeated the {Defender.MonsterName.ToLower()}.", false);
    CurrentPlayer.Experience += Defender.ExpReward + (int)(Defender.MaxHP / 6);                
    CurrentMap.AddMonsters(1);
}
Next: Rogue C# – Adding Scrolls and Potions

ComeauSoftware.com provides learning resources, including tutorials and videos, to guide you in understanding today's technology. Please check out our YouTube channel and bookmark this site to stay informed of upcoming projects.

Comeau Software Solutions also provides software consultation, including the development of database solutions, in Ocala, Florida. This includes rescuing Microsoft Access database projects and assistance in move to other solutions. Please contact us for more information on how we can help you with your project needs.

Available on Amazon.com


BISSELL Little Green Multi-Purpose Portable Carpet and Upholstery Cleaner
Great for pet stains. 48 oz tank for fewer refills - works on carpet and upholstery.

Bissel
  • Articles
  • C#
  • Careers
  • Commentary
  • Database Design
  • Databases
  • Hardware
  • How-to
  • Humor
  • Internet
  • Jobs
  • Linux
  • Microsoft Access
  • MySQL
  • Ocala I.T. Professionals
  • Personal
  • Personal Tech
  • Programming
  • Resources
  • Reviews
  • Rogue C# Series
  • Software
  • SQL
  • Uncategorized
  • Web Design
  • Writing
©2023 Comeau Software Solutions | Theme by SuperbThemes