Click here to Skip to main content
15,888,803 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
/* Rasal Amarasurya */
/* 21304178 */

#include <stdio.h>
#include <ncurses.h>
#include <stdlib.h> /* For malloc, free */
#include "game.h"   /* Include the game header */

void initializeGame(char **grid, int rows, int cols, FILE *file, int *playerRow, int *playerCol, int *win, int *collision) {
    int i, j, val;

    *win = 0; /* Initialize game as not won */
    *collision = 0; /* Initialize as no collision */

    /* Read grid configuration from the file */
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            if (fscanf(file, "%d", &val) != 1) {
                /* If reading the value fails, exit */
                exit(EXIT_FAILURE);
            }
            switch (val) {
                case 0:
                    grid[i][j] = EMPTY_SPACE; /* Empty space */
                    break;
                case 1:
                    grid[i][j] = ROAD; /* Road */
                    break;
                case 2:
                    /* Initially assuming the car faces right (east) as per initial requirements */
                    grid[i][j] = CAR_RIGHT; /* Car facing right (east) */
                    break;
                case 3:
                    grid[i][j] = PLAYER; /* Player's position */
                    *playerRow = i; /* Set player's row position */
                    *playerCol = j; /* Set player's column position */
                    break;
                case 4:
                    grid[i][j] = GOAL; /* Goal position */
                    break;
                /* Optionally, add cases for different car directions if they're specified in the file */
                case 5:
                    grid[i][j] = CAR_LEFT; /* Car facing left */
                    break;
                case 6:
                    grid[i][j] = CAR_UP; /* Car facing up */
                    break;
                case 7:
                    grid[i][j] = CAR_DOWN; /* Car facing down */
                    break;
                /* Add more cases as needed */
            }
        }
    }

    /* Set borders explicitly */
    for (i = 0; i < rows; i++) {
        grid[i][0] = BORDER; /* Set left border */
        grid[i][cols - 1] = BORDER; /* Set right border */
    }
    for (j = 0; j < cols; j++) {
        grid[0][j] = BORDER; /* Set top border */
        grid[rows - 1][j] = BORDER; /* Set bottom border */
    }
}




void displayGame(char **grid, int rows, int cols) {
    int i, j; /* Declaration at the top for C89 compliance */

    clear(); /* Clear the screen to avoid overlapping prints */
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            move(i, j); /* Move cursor to the correct position */
            printw("%c", grid[i][j]); /* Print the character in the grid cell */
        }
    }

    /* Display controls below the grid */
    mvprintw(rows, 0, "Press 'w' to move up");
    mvprintw(rows + 1, 0, "Press 'a' to move left");
    mvprintw(rows + 2, 0, "Press 's' to move down");
    mvprintw(rows + 3, 0, "Press 'd' to move right");
    refresh(); /* Refresh the screen to reflect the updates */
}

void moveCars(char **grid, int rows, int cols) {
    int i, j, nextI, nextJ;
    char direction, nextDirection;

    /* Temporary grid to handle movements without immediate override */
    char **tempGrid = (char **)malloc(rows * sizeof(char *));
    for (i = 0; i < rows; i++) {
        tempGrid[i] = (char *)malloc(cols * sizeof(char));
        for (j = 0; j < cols; j++) {
            tempGrid[i][j] = grid[i][j]; 
        }
    }

    /* Iterate through each grid cell */
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            direction = grid[i][j];
            nextI = i;
            nextJ = j;
            nextDirection = direction;

            /* Determine the next position based on the current direction */
            if (direction == CAR_RIGHT && j + 1 < cols && grid[i][j + 1] == ROAD) {
                nextJ++;
            } else if (direction == CAR_LEFT && j - 1 >= 0 && grid[i][j - 1] == ROAD) {
                nextJ--;
            } else if (direction == CAR_UP && i - 1 >= 0 && grid[i - 1][j] == ROAD) {
                nextI--;
            } else if (direction == CAR_DOWN && i + 1 < rows && grid[i + 1][j] == ROAD) {
                nextI++;
            }

            /* Move car in tempGrid and leave a road behind in the original position */
            if (nextI != i || nextJ != j) {
                tempGrid[nextI][nextJ] = nextDirection;
                tempGrid[i][j] = (grid[i][j] == PLAYER) ? PLAYER : ROAD; 
            }
        }
    }

    /* Copy tempGrid back to the main grid */
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++) {
            grid[i][j] = tempGrid[i][j];
        }
        free(tempGrid[i]); 
    }
    free(tempGrid); 
}



/* Add a flag for collision detection */
int collision = 0;


void movePlayer(char **grid, int rows, int cols, char move, int *win, int *playerRow, int *playerCol, int *collision) {
    int newPlayerRow = *playerRow;
    int newPlayerCol = *playerCol;

    /* Determine the new position based on input, with improved boundary checks */
    switch (move) {
        case 'w': newPlayerRow -= (newPlayerRow > 0) ? 1 : 0; break;
        case 'a': newPlayerCol -= (newPlayerCol > 0) ? 1 : 0; break;
        case 's': newPlayerRow += (newPlayerRow < rows - 1) ? 1 : 0; break;
        case 'd': newPlayerCol += (newPlayerCol < cols - 1) ? 1 : 0; break;
    }

    /* Ensure new position is within the grid boundaries */
    if (newPlayerRow >= 0 && newPlayerRow < rows && newPlayerCol >= 0 && newPlayerCol < cols) {
        /* Check if the new position is not a collision (e.g., empty space, road, or goal) */
        if (grid[newPlayerRow][newPlayerCol] == EMPTY_SPACE || grid[newPlayerRow][newPlayerCol] == ROAD) {
            /* Valid move: update player's position on the grid */
            grid[*playerRow][*playerCol] = EMPTY_SPACE;  
            *playerRow = newPlayerRow;
            *playerCol = newPlayerCol;
            grid[newPlayerRow][newPlayerCol] = PLAYER;      
            *collision = 0;  
        } else if (grid[newPlayerRow][newPlayerCol] == GOAL) {
            /* Player reaches the goal */
            *win = 1;
        } else {
            /* Handle collision (e.g., obstacle or border) */
            *collision = 1;
        }
    } else {
        /* The new position is out of bounds, handle it as a collision */
        *collision = 1;
    }
}


#include "game.h"
#include <ncurses.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    FILE *file;
    int mapRows, mapCols;
    char **gameGrid;
    int move; /* Changed to int to conform with return type of getch() in C89 */
    int win = 0, collision = 0;
    int playerRow = -1, playerCol = -1; /* Initial values to be set by initializeGame */
    int i;

    if (argc != 2) {
        printf("Usage: %s <config_file>\n", argv[0]);
        return 1;
    }

    file = fopen(argv[1], "r");
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }

    if (fscanf(file, "%d %d", &mapRows, &mapCols) != 2) {
        printf("Invalid file format. The first line must contain two integers.\n");
        fclose(file);
        return 1;
    }

    if (mapRows < 3 || mapCols < 5) {
        printf("Invalid map size. Please ensure <map_row> is >= 3 and <map_col> is >= 5.\n");
        fclose(file);
        return 1;
    }

    gameGrid = (char **)malloc(mapRows * sizeof(char *));
    if (!gameGrid) {
        fprintf(stderr, "Memory allocation failed for gameGrid.\n");
        return 1;
    }
    for (i = 0; i < mapRows; i++) {
        gameGrid[i] = (char *)malloc(mapCols * sizeof(char));
        if (!gameGrid[i]) {
            fprintf(stderr, "Memory allocation failed for gameGrid row %d.\n", i);
            while (--i >= 0) {
                free(gameGrid[i]);
            }
            free(gameGrid);
            return 1;
        }
    }

    /* Adjusted call to initializeGame */
    initializeGame(gameGrid, mapRows, mapCols, file, &playerRow, &playerCol, &win, &collision);
    fclose(file);

    /* Basic ncurses setup */
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE); /* Enable keyboard mapping */
    curs_set(0); /* Hide the cursor */

    do {
        displayGame(gameGrid, mapRows, mapCols);
        move = getch(); /* Get player input as int */

        if (move == 'w' || move == 'a' || move == 's' || move == 'd') {
            movePlayer(gameGrid, mapRows, mapCols, move, &win, &playerRow, &playerCol, &collision);
            moveCars(gameGrid, mapRows, mapCols);
            displayGame(gameGrid, mapRows, mapCols); 
        }

        collision = 0; /* Ensure collision is correctly reset here only if needed */

        /* Collision handling */
        if (collision) {
            clear();
            printw("You have failed to clear the level.\nPress any key to exit.\n");
            refresh();
            getch();
            break;
        } else if (win) {
            clear();
            printw("Congratulations! You have cleared the level.\nPress any key to exit.\n");
            refresh();
            getch();
            break;
        }

        napms(100); /* Delay to control game speed */

    } while (move != 'q');

    endwin(); /* End curses mode */

    for (i = 0; i < mapRows; i++) {
        free(gameGrid[i]);
    }
    free(gameGrid);

    return 0;
}


#ifndef GAME_H
#define GAME_H

#include <stdio.h> /* Include for FILE type */

/* Definitions for various game elements */
#define CAR_LEFT '<'    /* Define symbol for car facing left */
#define CAR_RIGHT '>'   /* Define symbol for car initially facing east and for general rightward movement */
#define CAR_UP '^'      /* Define symbol for car facing upwards */
#define CAR_DOWN 'V'    /* Define symbol for car facing downwards */
#define EMPTY_SPACE ' ' /* Symbol for empty space */
#define ROAD '.'        /* Symbol for road */
#define PLAYER 'P'      /* Symbol for player */
#define GOAL 'G'        /* Symbol for goal */
#define BORDER '*'      /* Symbol to represent the border */

/* Function prototypes */
void initializeGame(char **grid, int rows, int cols, FILE *file, int *playerRow, int *playerCol, int *win, int *collision);
void displayGame(char **grid, int rows, int cols);
void moveCars(char **grid, int rows, int cols);
void movePlayer(char **grid, int rows, int cols, char move, int *win, int *playerRow, int *playerCol, int *collision);

#endif /* GAME_H */


Configuration file:

10 15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 3 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 1 1 1 0 0 0 1 1 1 1 0 0
0 0 1 0 0 1 0 0 0 1 0 0 1 0 0
0 0 1 0 0 1 1 1 1 1 1 1 1 0 0
0 0 1 0 0 0 0 0 0 1 0 0 0 0 0
0 0 1 0 0 0 0 0 4 1 0 0 0 0 0
0 0 1 1 1 1 1 2 1 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

The first two integers represent the dimensions of the grid
0 represent the grid borders and empty spaces
1 represents the road path
2 represents the car
3 represents the player
4 represents the goal

What I have tried:

I'm working on a simple grid-based game in C where a player navigates through roads, avoiding cars that move along predetermined paths. I've implemented basic movement for the player and static car placement, but I'm struggling with two specific issues:

Making the car move: The car should move one step in its facing direction each time the player moves, following the road ('.'). Initially, the car faces east ('>'), but it needs to turn and follow the road's path, changing direction smoothly.

Implementing turns for the car: When the car reaches a turn or an intersection, it should choose the correct direction to continue following the road. The car's direction should be visually indicated (east '>', west '<', north '^', south 'v').

I am also facing the issue where the road disappears when the player moves through it.
Posted
Comments
Richard MacCutchan 30-Mar-24 7:14am    
Using a cursor based display for such a game creates its own problems. You need to redraw every itme that the car moves through each time it passes along a road. This makes it quite difficult if the car is moving in any direction other thn left to right on the screen.
Jo_vb.net 30-Mar-24 8:09am    
As Richard said you would have to redraw every time when the car direction changes.

And/Or - change the background of the cell where the direction change happens for one or two seconds.
Perhaps you may use other characters as a sign that the direction changes like
47 /
92 \

But the best way by far would be using a GUI and images.

If I were doing this, I might be tempted to track the grid, the player and the car(s) separately,
something like:
C
struct position { int row, col };

struct position player;

struct vehicle {
  char direction;
  struct position pos;
};

// and possibly
struct board {
   char **grid;
   size_t rows;
   size_t cols;
};

Now when you move an object, you can directly update the screen at the position without having to re-draw it every time (N.B. This probably isn't a big deal, as curses is supposed to track changes and optimally move the cursor to only update the screen at locations that have changed since the last refresh() ), or alter the contents of the game grid.

When a car follows a road, you need to know not only where the car is, and what direction it is pointing, but also what the surrounding grid looks like so psuedo codeish
C
if ( car.direction == CAR_LEFT )
   if( grid_type(grid, car.position, CAR_LEFT) == road)
      move_object(grid, car.postion, CAR_LEFT);
   else  {
      /* couldn't move left must be up or down */
     if( grid_type(grid, car.position, CAR_UP)  == road ) {
        car.direction = CAR_UP;
        move_object(grid, car.position, CAR_UP)
    } else {
        /* move car down similar to previous if clause */
    }
   /* repeat for UP, DOWN, RIGHT */
}

the function grid_type() would return what the grid type from a given position is if you were to look up,down,left or right, and move_object() would change the [row, col] of the objects position in the given direction. You could also update the terminal screen inside move_object : replace the tile at the current object position from the (unchanged) grid, and after updating the objects position (e.g, move it up or left, etc), you can update the screen at the new location with the object (e.g Player or car going UP, or DOWN, etc)
 
Share this answer
 
v2
To restore the background position, the old background can be saved with its position before it is overwritten by cars or players. As soon as the field is free again, the saved position can be restored.

An alternative would be to use a copy of the empty playing field to restore everything. With such a solution, the playing field could also be much larger than the current window and the window size could be changeable.

It would be much more realistic if cars and players moved independently. Timer-based movements would be ideal for the cars.
 
Share this answer
 
v2
Generally in a computer graphics smooth rendering is achieved by using a back buffer to which an image is drawn with subsequent BLIT (Block Image Transfer) into the Video memory. Drawing into the video memory directly results in visual anomalies such as flicker, torn screen etc..
 
Share this answer
 
As somebody already wrote: best is to hold the complete map (your background) as drawable image the whole game, so your drawing code may look similar like:
C++
void drawScreen(int x, int y) {
  if( isSomeCarAt( x, y ) {
    drawCar( x, y );
  } else {
    drawBackground( x, y );
  }
}
Tip: use some struct like for the car or position.
C++
struct Position {
    int x;
    int y;
  }
struct Car {
    Position position;
    COLOR color;
    // further information
  }
Add than struct pointers to your function to use them in functions.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900