user10801922
user10801922

Reputation:

Unable to read a maze of chars and put it into a bidimensional array

I am making a maze solving game. There is a function in the middle of the game that saves the progress and prints everything into the file. But now i want to read the maze, the character, etc from the file and put it into a bidimensional array but i am not being able to do so.

The array is declared globally (because of its use in several functions) and it is maze[30][30]. N is also declared globally as a variable size but at this point it should be 10. filePath too and it gives the name of the file.

This is how they are declared globally.

int N = 10;
char* filePath = "./save.txt";
char maze[30][30];

This is the read function:

void ler()                   
{
    int i, j;
    ex=1; ey=0;
    sx=N-2; sy=N-1;

    int aux;
    FILE *fp = fopen(filePath, "r");
    if (fp == NULL)
    {
        printf("Unable to perform.");
    return;
    }
    system("cls");

    for(i=0 ; i<N ; i++)
    {
        for(j=0 ; j<N ; j++)
        {
        fscanf(fp, "%c", maze[j][i]);
        }
    }

    for (i = 0; i < N; i++) 
    {
    for (j = 0; j < N; j++) 
    {
        printf("%c", maze[j][i]);   //Double print just for visuals
        printf("%c", maze[j][i]);
    }
    printf("\n");
    }
    fclose(fp);
}

This is the save function:

void save(char maze[30][30]){
int i,j;
FILE *fp = fopen(filePath, "w");
if(fp==NULL){
    printf("Unable to perform.");
    return; 
    }

for(i=0 ; i<N ; i++){
    for(j=0 ; j<N ; j++){
        fprintf(fp, "%c", maze[j][i]);
        fprintf(fp, "%c", maze[j][i]);
    }
    fprintf(fp, "\n", maze[j][i]);
}
fclose(fp);}

At this point it should only be possible to print the maze but it is not doing even that.

What save.txt file looks like after saving The weird II is the caharcter and the other is like a highscore thing.

Upvotes: 0

Views: 159

Answers (1)

Nominal Animal
Nominal Animal

Reputation: 39396

Using multidimensional arrays in C is actually more pain than they're worth. A much better option is to use a structure with a dynamically allocated array describing the maze, and accessor functions to examine and change the maze cells. Instead of putting markers in the maze data, you can put start/end/current location coordinates in the structure.

(I do realize that this does not answer the OP's stated question, but this is an answer to the underlying problem OP is trying to solve.)

Consider the following example. It limits the maze size to 255×255, but because each coordinate and maze cell is always just one byte, the save files are portable between architectures, as there is no byte order (endianness) to worry about. (You, as the programmer, do need to choose to use only codes 0..255 in the maze, though, to keep the data portable; the functions below won't enforce that.)

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

typedef struct {
    unsigned char  row;
    unsigned char  col;
} mazepoint;

typedef struct {
    unsigned char  rows;
    unsigned char  cols;
    unsigned char *cell;
    mazepoint      player;
} maze;

#define  OUTSIDE    0

static inline unsigned char  maze_get(maze *const  m,
                                      const int    row,
                                      const int    col)
{
    if (m &&
        row >= 0 && row < (int)(m->rows) &&
        col >= 0 && col < (int)(m->cols))
        return m->cell[ (size_t)col + (size_t)(m->cols) * (size_t)row ];
    else
        return OUTSIDE;
}

static inline unsigned char  maze_set(maze *const          m,
                                      const int            row,
                                      const int            col,
                                      const unsigned char  val)
{
    if (m &&
        row >= 0 && row < (int)(m->rows) &&
        col >= 0 && col < (int)(m->cols))
        return m->cell[ (size_t)col + (size_t)(m->cols) * (size_t)row ] = val;
    else
        return OUTSIDE;
}

static inline void maze_free(maze *const m)
{
    if (m) {
        free(m->cell);
        m->rows = 0;
        m->cols = 0;
        m->cell = NULL;
    }
}

int maze_create(maze *const    m,
                const int      rows,
                const int      cols)
{
    size_t          cells = (size_t)rows * (size_t)cols;
    unsigned char  *cell;

    if (!m)
        return -1;  /* NULL reference to a maze variable! */

    if (rows < 1 || rows > 255 ||
        cols < 1 || cols > 255)
        return -1;  /* Invalid number of rows or columns! */

    cell = malloc(cells); /* sizeof (unsigned char) == 1. */
    if (!cell)
        return -1;

    /* Initialize all maze cells to OUTSIDE. */
    memset(cell, OUTSIDE, cells);

    m->rows = rows;
    m->cols = cols;
    m->cell = cell;

    /* Let's initialize player location to upper left corner. */
    m->player.row = 0;
    m->player.col = 0;

    return 0; /* Success. */
}

int maze_save(maze *const m, const char *filename)
{
    size_t  cells;
    FILE   *out;

    if (!m || m->rows < 1 || m->cols < 1)
        return -1; /* No maze to save! */
    if (!filename || !filename[0])
        return -1; /* NULL or empty filename! */

    cells = (size_t)(m->rows) * (size_t)(m->cols);

    out = fopen(filename, "wb");
    if (!out)
        return -1; /* Cannot open file for writing! */

    do {

        /* First byte is the number of rows. */
        if (fputc(m->rows, out) == EOF)
             break;

        /* Second byte is the number of columns. */
        if (fputc(m->cols, out) == EOF)
             break;

        /* rows*cols bytes of maze data follows. */
        if (fwrite(m->cell, 1, cells, out) != cells)
            break;

        /* Player location follows. */
        if (fputc(m->player.row, out) == EOF)
            break;
        if (fputc(m->player.col, out) == EOF)
            break;

        /* You can save additional data at this point. */

        /* That completes the save file. Ensure it is correctly saved. */
        if (fflush(out))
            break;
        if (fclose(out))
            break;

        /* Maze successfully saved. */
        return 0;

    } while (0);

    /* Save failed. */
    fclose(out);
    remove(filename);
    return -1;
}

int maze_load(maze *const m, const char *filename)
{
    size_t         cells;
    unsigned char *cell;
    int            rows, cols, r, c;
    FILE          *in;

    if (!m)
        return -1; /* No reference to a maze variable to load into! */

    /* Just in case, we clear the maze first. Might help finding bugs! */
    m->rows = 0;
    m->cols = 0;
    m->cell = NULL;

    if (!filename || !filename[0])
        return -1; /* NULL or empty filename! */

    in = fopen(filename, "rb");
    if (!in)
        return -1; /* Cannot open file for reading. */

    rows = fgetc(in);
    cols = fgetc(in);
    if (rows == EOF || rows < 1 || rows > 255 ||
        cols == EOF || cols < 1 || cols > 255) {
        fclose(in);
        return -1; /* Not a saved maze! */
    }

    cells = (size_t)(rows) * (size_t)(cols);
    cell = malloc(cells);
    if (!cell) {
        fclose(in);
        return -1; /* Not enough memory available! */
    }

    do {
        /* Read maze cell data. */
        if (fread(cell, 1, cells, in) != cells)
            break;

        /* Player location. */
        r = fgetc(in);
        c = fgetc(in);
        if (r == EOF || r < 0 || r > 255 ||
            c == EOF || c < 0 || c > 255)
            break;
        m->player.row = r;
        m->player.col = c;

        /* Load other saved data here. */

        /* All data read successfully. */
        fclose(in);

        m->rows = rows;
        m->cols = cols;
        m->cell = cell;

        return 0;
    } while (0);

    /* Read error. */
    fclose(in);
    free(cell);
    return -1;
}

In your own program, you'd create a maze thus:

    maze  m;

    /* Create a 20-row, 30-column maze. */
    if (maze_create(&m, 20, 30)) {
        /* Failed to create maze! Show an error message. */
        exit(EXIT_FAILURE);
    }

To save the maze to say maze.dat, you use

    m.player.row = /* row where the player is */
    m.player.col = /* column where the player is */
    if (maze_save(&m, "maze.dat")) {
        /* Failed! Show an error message. */
        exit(EXIT_FAILURE);
    }

If you look at the example code, you can add additional data, especially points like the player place, to be saved and loaded along with the maze cells themselves.

To destroy a maze when it is no longer needed, use

    maze_free(&m);

To load a saved maze, say from maze.dat, use

    if (maze_load(&m, "maze.dat")) {
        /* Failed! Show an error message. */
        exit(EXIT_FAILURE);
    }
    /* Restore player place from m.player.row and m.player.col */

The accessor function maze_get() is not limited to the valid coordinates (0 through rows-1 or cols-1, inclusive). If you examine outside the maze itself, it will just return the value of the OUTSIDE macro. For example,

    if (maze_get(&m, row, col) == 5) {
        /* That cell has value 5 */
    } else {
        /* Either the cell has a different value,
           or row,col is outside the maze. */
    }

Similarly, you can try to set any cell value safely. It will only "stick" if it is within the valid maze coordinate range, however; elsewhere it will return OUTSIDE:

    if (maze_set(&m, row, col, 5) == 5) {
        /* Changed cell value to 5 */
    } else {
        /* row,col is outside the maze. */
    }

The reason I wrote the accessor macros that way, is that it makes rendering only a part of the maze very simple. If the view is viewrows by viewcols in size, centered at row and col, then you can render the view using a simple loop:

    const int  top = row - viewrows / 2;
    const int  left = col - viewcols / 2;
    int        vr, vc;
    for (vr = 0; vr < viewrows; vr++) {
        for (vc = 0; vc < viewcols; vc++) {
            const unsigned char  v = maze_get(&m, top+vr, left+vc);
            /* Draw v at row vr, col vc */
        }
    }

and the cells are even drawn in the same order as you read this text; from top to bottom, left to right.

Note that instead of using the maze cell values for character codes, you should use a lookup table instead. For example,

int cell_char[256];

Instead of printing cell values directly, you'd print the corresponding cell_char, for example

fputc(cell_char[maze_get(&m, row, col)], stdout);

That way you can group e.g. different wall characters into a consecutive range, or even use the individual bits in the 8-bit cell value as identifiers. The maze cells then describe the logical contents in that maze cell, rather than its visual representation, with the logical-to-visual mapping in a separate array.

If you used Gtk+, you could have an array of GtkImage pointers,

GtkImage *cell_image[256] = {0}; /* All NULL by default */

or using SDL, you could have the maze cells as textures you can render,

SDL_Texture *cell_texture[256] = {0}; /* All NULL by default */

and in both cases, read them from either one large image (say, divided into 16×16 exact same size rectangles), or from individual image files.

For example, you could decide that the four least significant bits in the cell value specify whether movement from that cell up (previous row), down (next row), left (previous column), or right (next column) is possible:

#define  CAN_GO_UP(value)    ((value) & (1 << 0))   /* == 1 */
#define  CAN_GO_DOWN(value)  ((value) & (1 << 1))   /* == 2 */
#define  CAN_GO_LEFT(value)  ((value) & (1 << 2))   /* == 4 */
#define  CAN_GO_RIGHT(value) ((value) & (1 << 3))   /* == 8 */

Note that this allows you to do "trap walls": passages that only work one way. Maze cell values that are multiples of 16 (0, 16, 32, 48, 64, 80, 96, ..., 208, 224, and 240) represent completely blocked cells: no way out. +1 allows passage up; +2 allows passage down; +3 allows passage up and down; +4 allows passage left; +5 allows passage left and up; +6 allows passage left and down; +7 allows passage up, left, and down; +8 allows passage right; +9 allows passage up and right; +10 allows passage down and right; +11 allows passage up, down, and right; +12 allows passage left and right; +13 allows passage up, left, and right; +14 allows passage down, left, and right; and +15 allows passage up, down, left, and right.

I would personally also recommend using the wide version of the ncurses library (ncursesw). (I do not use Windows, so I am not exactly sure how you install and use it in windows, but the ncurses home page does have downloads when using mingw.)

Then, you would have a much wider variety of glyphs you could use. (When using UTF-8 locales, potentially the entire Unicode glyph set -- the Box Drawing block especially would be useful for maze drawing, and most of those glyphs are also available in the old CP437 codepage, which means they should work both in Windows and non-Windows terminals nicely.)

In that case, you'd probably use

cchar_t  cell_char[256];

As I mentioned above, you could even do a graphical version (perhaps later on, extending your terminal version?) in C using SDL or GTK+. (Note that the above separation between logical maze cell content value and the visual describing the cell also means you can, at runtime, choose between "themes", by having more than one set of cell images. That allows you to start with crude informational versions, for debugging, and then add visual goodness.)

The approach shown in this answers allows you to start with a simple terminal-based game, and if you decide you want to, add support for graphical UI, with image-based maze cells, without having to rewrite any of your core maze code.

Upvotes: 1

Related Questions