Roguelike AoC – part 10

A tenth lesson of roguelike game tutorial goes back to the topic of connecting doors. This time it’s the last lesson about the topic. The goal is to fix problems that appear on and on.

Final cleanups with constants becoming macros and ncurses

In the previous lesson we’ve extracted the code into separate files. What actually have happened, is that our constants with signs used to render locations were set as macros. Sudden problem arose with warnings being shown when passing them to ncurses display methods (like mwprintw). What I’ve finally ended up doing was:

  • removed const modifier from getDisplaySignForLocationType function (in both declaration and body)
  • made displaySign property in Location struct a char pointer

Now the code compiles without warnings.

 

New function of curses library

Before going into next subchapters I have to mention an existence of a function called unctrl. When we call curses functions that read something from the screen (eg. mvinch) it does not return char instance but chtype. That type is a special type in curses that holds not only the character on the screen but also additional attributes. Therefore, when we try to use its result to eg. compare it again plain char – we get a warning.

To fix that aforementioned function of unctrl is used. It takes a parameter of type chtype and return only a character associated with it.

 

Get rid of leaving floor sign when player goes (eg. through door)

In the lesson the solution was to scan the whole screen and read signs that are on it. With that information an two-dimensional array was filled with signs that are originally present at that location. My code, already having Location struct, does not need that. What it needs is for the Player structure to actually hold information about current location he’s at, and the previous one. Therefore, our existing methods could be handling this without referencing to the external information. From what I’ve read in the description of the next lesson – we will be creating some kind of wrap-up structure for the whole level. Maybe then I will have to change my approach. However, I think that even then this Level structure could actually benefit from storing my Location instances.

Below I present only changed playerMoved function. There was a change in Location struct – I’ve added currentLocation and previousLocation pointers and removed position completely.

int playerMove(Player *user, enum Direction direction)
{
    int possibleX = user->currentLocation->position.x;
    int possibleY = user->currentLocation->position.y;

    switch (direction) {
        // move up
        case DIRECTION_N:
            possibleY -= 1;
            break;

        // move left
        case DIRECTION_W:
            possibleX -= 1;
            break;

        // move right
        case DIRECTION_E:
            possibleX += 1;
            break;

        // move down
        case DIRECTION_S:
            possibleY += 1;
            break;

        default:
            break; // We stay in place (usually 'turn' will pass then)
     }

    if(canGoToPosition(possibleY, possibleX)) {
        // Cleanup of current position
        mvprintw(user->currentLocation->position.y, user->currentLocation->position.x, user->currentLocation->displaySign);

        // We store info about the previous position (which is current one)
        user->previousLocation->position.y = user->currentLocation->position.y;
        user->previousLocation->position.x = user->currentLocation->position.x;
        user->previousLocation->displaySign = user->currentLocation->displaySign;

        // We switch the current position to the new one where we'll allow to go
        user->currentLocation->position.y = possibleY;
        user->currentLocation->position.x = possibleX;
        user->currentLocation->displaySign = unctrl(mvinch(possibleY, possibleX));

        // We redraw the player no matter if the position changed or not
        playerDraw(user);
    } else {
        // When player 'stays' where he is then we have to move cursor 'back' to his current location
        move(user->currentLocation->position.y, user->currentLocation->position.x);
    }

    return 0;
}

Get rid of leaving an additional sign when drawing connections

In the Youtube lesson this is left unresolved. However, I wanted a nice, clean map being rendered so, I’ve decided to take care of it. The main problem actually lies in a connection generating algorithm. My code has a problem that author’s code actually does not have. Sometimes (depending on which doors were chosen to be connected), there’s no way for the algorithm to proceed further – there are obstacles everywhere (that cannot be used to go) or chosen direction is not applicable.

This can be easily illustrated by an example. Let’s say there’s a room on the right part of the screen, and also having a door on the eastern wall. This door was chosen to be connected. Then a second door is chosen in a different room that lies on the left. When our algorithm tries to connect door on the right to door on the left it first tries going up. It cannot go there as there’s a wall. Then tries right direction – also there’s no way as that would increase the distance between the doors. For goind down the situation is the same like with going up – there’s a wall, and we cannot allow it. Finally, there’s only going left – unfortunately there’s a floor there, and we not allow to go when there’s floor. Why, You may wonder? Good question!

It seems that fixing that bug is trivial – just add floor to the allowed locations our algorithm can go through! Unfortunately that may fix a part of them problem – as we go left through the first room, we still probably will hit the wall on our way. We could get lucky when there’s a door on that wall – algorithm might use them, but that’s not granted.

Taking everything under consideration I’ve decided to go for it – so enable using floor as allowed location. However, for the ‘power’ of connecting algorithm I’ve decided to leave it as it is. I’m doing this whole project in order to get some familiarity with C language, not to create full-blown RPG game.

// example of changed condition for going left
if ((abs((temp.x - 1) - doorTwo.x) < abs(temp.x - doorTwo.x)) &&
    ((mvinch(temp.y, temp.x - 1) == *EMPTY_SIGN) || (mvinch(temp.y, temp.x - 1) == *FLOOR_SIGN))) {
    previous.x = temp.x;
    temp.x = temp.x - 1;        
}

Get rid of leaving an additional sign when drawing connections

That was also leaving the map in some inconsistent state sometimes. Although, it was fairly simple to fix – due to the fact that I’m passing Location variables and not just positions to the connectLocations function it is only a matter of additional assignment statements. I won’t post the whole method here – You can see it in the ZALINKUJ.

Below You can find an image showing (with the red circle, pardon my poor designer’s skills 😉 ) that the algorithm was fixed in this regard.

Code

Here is the direct link to the GitHub repository.

You Might Also Like

One Comment

  1. Roguelike AoC – part 11 – Bare.Metal.Dev

    […] the previous lesson I’ve actually fixed a couple of bugs. However, I did not implement the solution that the […]

Leave a Reply

Back to top