Roguelike AoC – part 4

A fourth lesson of roguelike game tutorial was longer than the previous ones. It was about creating struct for rooms – which can be never-ending story but here, it was settled on the quite simple solution.

Before I start I have to admit, that sometimes I may use ‘wrong’ terminology. I’m so used to the object-oriented paradigm, that right now I put an equity sign between methods and functions. Or objects, instances or variables (in different contexts). I assume that my blog is not read by freshmen in programming, therefore readers are able to understand what I mean in the specific part of the text. For hardcore C purists – I’m sorry. Will try to do my best in the future 😉

 

Constants, constants everywhere

First of all – I’ve decided to make things less inline-style and extracted signs used to create the rooms to constants. The intention of these signs is never to actually change them, so the idea is right. I’ve started with the following approach:

const char WALL_SIGN = '#';
const char FLOOR_SIGN = '.';
const char PLAYER_SIGN = '@';

However, here I got warnings from mvprintw methods stating, that:

warning: passing argument 3 of 'mvprintw' makes pointer from integer without a cast [-Wint-conversion]

If You take a look at the signature of mvprintw method, two first parameters are integers with Y and X coordinates. The third parameter is actually a const char pointer, not a char itself! So in order to make it work (and after heavy googling, and some small help from IRC colleagues), I got this:

const char *WALL_SIGN = "#";
const char *FLOOR_SIGN = ".";
const char *PLAYER_SIGN = "@";

What above code does is creating char pointers that points to the memory address of the first sign in the assigned string. Now, they can be safely used in all mvprintw calls in the code. However, another warning appeared in the line that creates an allowedLocations array in canGoToPosition function.

char allowedLocations[] = {FLOOR_SIGN};

produces:

warning: initialization of 'char' from 'const char *' makes integer from pointer without a cast [-Wint-conversion]

As our constant is no longer a char, but char pointer, we have to explicitly ask for its value. Passing just a pointer is wrong here. Therefore, the code has to look like this:

char allowedLocations[] = {*FLOOR_SIGN};

 

Gimme some room!

The main topic are rooms. To start with – I did not put commented out future properties of the Room struct in the code. For the time being it is better to keep the code clean and containing only the parts that are actually being used. Second of all – I’ve rearranged the methods definitions and added additional comments there.

Next thing to explain is actually the change of a signature of mapSetup method. Up until now this method was returning simple int value, that was not of special use for us. Rooms were created with hardcoded data and just displayed on the screen. If we wanted to actually do something with them in the code – we did not have a proper handle to do that! In the canGoToPosition function, we’ve bypassed that problem with just operating directly on the screen output – not on the data structures representing the rooms! Now we want to change that (which is great), however here comes another issue.

In C language, there is no concept of returning an array from a function. What we’re used to in other programming languages, here requires a different approach. To return a single array from the function we can:

  • wrap it using struct
  • declare the array not as local variable (put on stack) but as static which will put it on the heap and persist during whole program’s execution
  • use dynamically allocated array

Above solutions apply to the situation, where we return an array with values that are built-in types. Here, we plan to actually return an array consisting of Room struct instances. Struct is not a scalar value, but to use it we have to use an additional pointer. Therefore, the signature of the mapSetup function must look like this:

Room **mapSetup(void)

With such signature we inform the compiler that we return a pointer containing a list of pointers of type Room. In other words – an array containing variables of type Room. So far, so good.

 

Code

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <curses.h>


const char *WALL_SIGN = "#";
const char *FLOOR_SIGN = ".";
const char *PLAYER_SIGN = "@";


typedef struct Room {
    int xPosition;
    int yPosition;
    int height;
    int width;
} Room;

typedef struct Player {
    int xPosition;
    int yPosition;
    int health;
} Player;

enum Direction {DIRECTION_NW, DIRECTION_N, DIRECTION_NE, DIRECTION_W, DIRECTION_E, DIRECTION_SW, DIRECTION_S, DIRECTION_SE};


// Config and bootstrap methods
int screenSetup(void);
Room **mapSetup(void);

// Player methods
Player *playerSetup(void);
int playerMove(Player *user, enum Direction dir);
int playerDraw(Player *user);

// Room methods
Room* createRoom(int y, int x, int height, int width);
int drawRoom(Room *room);

// Other methods
int handleInput(char, Player *user);
bool canGoToPosition(int y, int x);



int main()
{
    Player *user;
    char keyPressed;

    screenSetup();
    mapSetup();
    user = playerSetup();

    /* Main game loop */
    while((keyPressed = getch()) != 'q') {
        handleInput(keyPressed, user);
    }

	endwin();

	return 0;
}


Player *playerSetup(void)
{
    Player *newPlayer = malloc(sizeof(Player));
    newPlayer->xPosition = 14;
    newPlayer->yPosition = 14;
    newPlayer->health = 20;

    playerDraw(newPlayer);

    return newPlayer;
}

int screenSetup(void)
{
    initscr();
    printw("Roguelike game!");
    noecho();
    refresh();

    return 1;
}

Room **mapSetup(void)
{
    Room **rooms = malloc(sizeof(Room)*3);

    rooms[0] = createRoom(13, 13, 6, 9);
    drawRoom(rooms[0]);
    rooms[1] = createRoom(13, 30, 6, 9);
    drawRoom(rooms[1]);
    rooms[2] = createRoom(25, 13, 6, 12);
    drawRoom(rooms[2]);

    return rooms;
}

Room *createRoom(int y, int x, int height, int width)
{
    Room *newRoom = malloc(sizeof(Room));

    newRoom->yPosition = y;
    newRoom->xPosition = x;
    newRoom->height = height;
    newRoom->width = width;

    return newRoom;
}

int drawRoom(Room *room)
{
    int x, y;

    // Draw top and bottom
    for(x = room->xPosition; x < room->xPosition + room->width; x++) {
        mvprintw(room->yPosition, x, WALL_SIGN);
        mvprintw(room->yPosition + room->height - 1, x, WALL_SIGN);
    }

     // Draw floors and side walls
    for(y = room->yPosition + 1; y < room->yPosition + room->height - 1; y++) {
        // Draw side walls
        mvprintw(y, room->xPosition, WALL_SIGN);
        mvprintw(y, room->xPosition + room->width - 1, WALL_SIGN);

        // Draw floor inside specific row
        for(x = room->xPosition + 1; x < room->xPosition + room->width - 1; x++) {
            mvprintw(y, x, FLOOR_SIGN);
        }
    }

    return 0;
}


int handleInput(char keyPressed, Player *user)
{
    switch (keyPressed) {
        // move up
        case 'w':
        case 'W':
            playerMove(user, DIRECTION_N);
            break;

        // move left
        case 'a':
        case 'A':
            playerMove(user, DIRECTION_W);
            break;

        // move right
        case 'd':
        case 'D':
            playerMove(user, DIRECTION_E);
            break;

        // move down
        case 's':
        case 'S':
            playerMove(user, DIRECTION_S);
            break;

        default:
            break;
    }

    return 0;
}


int playerMove(Player *user, enum Direction direction)
{
     int possibleX = user->xPosition;
     int possibleY = user->yPosition;

     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)
     }

    // Cleanup of current position
    mvprintw(user->yPosition, user->xPosition, FLOOR_SIGN);

    if(canGoToPosition(possibleY, possibleX)) {
        user->yPosition = possibleY;
        user->xPosition = possibleX;
    }

    // We redraw the player no matter if the position changed or not
    playerDraw(user);

    return 0;
}

bool canGoToPosition(int y, int x)
{
    const int ALLOWED_LOCATIONS_SIZE = 1;   // Hard-coded to simplify iteration loop
    char allowedLocations[] = {*FLOOR_SIGN};
    char locationToCheck = mvinch(y, x);

    for(int i = 0; i < ALLOWED_LOCATIONS_SIZE; i++) {
        if(locationToCheck == allowedLocations[i]) {
            return TRUE;
        }
    }

    return FALSE;
}

int playerDraw(Player *user)
{
    mvprintw(user->yPosition, user->xPosition, PLAYER_SIGN);
    move(user->yPosition, user->xPosition);

    return 0;
}

You Might Also Like

Leave a Reply

Back to top