Old Curses Game
Here is a re-release of an old game I programmed a long time ago, called simply "cgame". It was one of my first "real" projects with C and the curses library. The code has gone through heavy re-factoring for readability, so it looks almost nothing like the original, but the game mechanics remains the same.
Here's what it looks like:
The rules are simple, collect all the "money" and avoid being captured by the yellow 'M'. The idea behind the 'M' actually came from a game on the Commodore 64, called "Escape MCP", that I recalled playing as a kid. Here's what the main screen looks like:
Finally, the code:
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <ncurses.h>
#define GAME_ROWS 24
#define GAME_COLS 80
#define AMOUNT_OF_MONEY 20
#define AMOUNT_OF_MINES 10
#define AMOUNT_OF_CRATES 5
#define PAIR_YELLOW 1
#define PAIR_CYAN 2
typedef enum {
DIRECTION_NONE,
DIRECTION_LEFT,
DIRECTION_RIGHT,
DIRECTION_UP,
DIRECTION_DOWN,
} direction_t;
typedef enum {
MOVE_OK,
MOVE_BLOCKED,
MOVE_ON_MINE,
MOVE_ON_MONEY,
MOVE_ON_ENEMY,
} move_t;
typedef struct object_s {
int y, x;
char symbol;
int attributes;
int taken;
} object_t;
static object_t player;
static object_t enemy;
static object_t money[AMOUNT_OF_MONEY];
static object_t mines[AMOUNT_OF_MINES];
static object_t crates[AMOUNT_OF_CRATES];
static int money_collected;
static int steps_taken;
static int enemy_idle_count;
static void draw_object(object_t *object)
{
if (object->attributes != 0 && has_colors())
wattrset(stdscr, object->attributes);
mvaddch(object->y, object->x, object->symbol);
if (object->attributes != 0 && has_colors())
wattrset(stdscr, A_NORMAL);
}
static void find_empty_spot(int *y, int *x)
{
do {
move((random() % (GAME_ROWS - 2)) + 1, (random() % (GAME_COLS - 2)) + 1);
} while ((inch() & A_CHARTEXT) != ' ');
getyx(stdscr, *y, *x);
}
static void init_game(void)
{
int i;
money_collected = 0;
steps_taken = 0;
enemy_idle_count = 0;
srandom(time(NULL));
player.symbol = '@';
player.attributes = 0;
find_empty_spot(&player.y, &player.x);
draw_object(&player);
enemy.symbol = 'M';
enemy.attributes = A_BOLD | COLOR_PAIR(PAIR_YELLOW);
find_empty_spot(&enemy.y, &enemy.x);
draw_object(&enemy);
for (i = 0; i < AMOUNT_OF_MONEY; i++) {
money[i].symbol = '$';
money[i].attributes = A_BOLD;
money[i].taken = 0;
find_empty_spot(&money[i].y, &money[i].x);
draw_object(&money[i]);
}
for (i = 0; i < AMOUNT_OF_MINES; i++) {
mines[i].symbol = '*';
mines[i].attributes = COLOR_PAIR(PAIR_CYAN);
find_empty_spot(&mines[i].y, &mines[i].x);
draw_object(&mines[i]);
}
for (i = 0; i < AMOUNT_OF_CRATES; i++) {
crates[i].symbol = '#';
crates[i].attributes = COLOR_PAIR(PAIR_YELLOW);
find_empty_spot(&crates[i].y, &crates[i].x);
draw_object(&crates[i]);
}
}
static direction_t direction_to_player(void)
{
int y, x;
y = enemy.y - player.y;
x = enemy.x - player.x;
if ((x < 0) && ((x <= (-y)) || (x >= y)) &&
((x <= y) || (x >= (-y))))
return DIRECTION_RIGHT;
if ((x > 0) && ((x <= y) || (x >= (-y))) &&
((x <= (-y)) || (x >= y)))
return DIRECTION_LEFT;
if (y > 0)
return DIRECTION_UP;
if (y < 0)
return DIRECTION_DOWN;
return DIRECTION_NONE;
}
static direction_t random_direction(void)
{
switch (random() % 4) {
case 0:
return DIRECTION_LEFT;
case 1:
return DIRECTION_RIGHT;
case 2:
return DIRECTION_UP;
case 3:
return DIRECTION_DOWN;
}
return DIRECTION_NONE;
}
static void move_object_actual(object_t *object, direction_t direction)
{
switch (direction) {
case DIRECTION_LEFT:
object->x--;
break;
case DIRECTION_RIGHT:
object->x++;
break;
case DIRECTION_UP:
object->y--;
break;
case DIRECTION_DOWN:
object->y++;
break;
default:
break;
}
}
static move_t move_object_check(object_t *object, direction_t direction)
{
int i, hit_y, hit_x;
char hit_object;
switch (direction) {
case DIRECTION_LEFT:
hit_y = object->y;
hit_x = object->x - 1;
break;
case DIRECTION_RIGHT:
hit_y = object->y;
hit_x = object->x + 1;
break;
case DIRECTION_UP:
hit_y = object->y - 1;
hit_x = object->x;
break;
case DIRECTION_DOWN:
hit_y = object->y + 1;
hit_x = object->x;
break;
case DIRECTION_NONE:
default:
return MOVE_BLOCKED;
}
hit_object = mvinch(hit_y, hit_x) & A_CHARTEXT;
if (object->symbol == '@') { /* Player. */
switch (hit_object) {
case '*':
move_object_actual(object, direction);
return MOVE_ON_MINE;
case '$':
move_object_actual(object, direction);
return MOVE_ON_MONEY;
case 'M':
move_object_actual(object, direction);
return MOVE_ON_ENEMY;
case '#':
for (i = 0; i < AMOUNT_OF_CRATES; i++) {
if (hit_y == crates[i].y && hit_x == crates[i].x) {
if (move_object_check(&crates[i], direction) == MOVE_OK) {
move_object_actual(object, direction);
return MOVE_OK;
} else {
return MOVE_BLOCKED;
}
}
}
return MOVE_BLOCKED;
case ' ':
move_object_actual(object, direction);
return MOVE_OK;
default:
return MOVE_BLOCKED;
}
} else if (object->symbol == 'M') { /* Enemy. */
if (hit_object == ' ' || hit_object == '@') {
move_object_actual(object, direction);
return MOVE_OK;
} else {
return MOVE_BLOCKED;
}
} else { /* Crates, etc. */
if (hit_object == ' ') {
move_object_actual(object, direction);
return MOVE_OK;
} else {
return MOVE_BLOCKED;
}
}
}
static void draw_screen(void)
{
int i;
erase();
box(stdscr, '|', '-');
for (i = 0; i < AMOUNT_OF_MONEY; i++)
if (! money[i].taken)
draw_object(&money[i]);
for (i = 0; i < AMOUNT_OF_MINES; i++)
draw_object(&mines[i]);
for (i = 0; i < AMOUNT_OF_CRATES; i++)
draw_object(&crates[i]);
draw_object(&player);
draw_object(&enemy);
move(player.y, player.x);
refresh();
}
int main(void)
{
int i, maxx, maxy, direction;
char *done = NULL;
initscr();
if (has_colors()) {
start_color();
init_pair(PAIR_YELLOW, COLOR_YELLOW, COLOR_BLACK);
init_pair(PAIR_CYAN, COLOR_CYAN, COLOR_BLACK);
}
noecho();
keypad(stdscr, TRUE);
getmaxyx(stdscr, maxy, maxx);
if (maxy != GAME_ROWS || maxx != GAME_COLS) {
endwin();
fprintf(stderr, "Terminal must be %dx%d!\n", GAME_ROWS, GAME_COLS);
return 1;
}
init_game();
while (done == NULL) {
draw_screen();
/* Get player input. */
direction = DIRECTION_NONE;
switch (getch()) {
case KEY_LEFT:
direction = DIRECTION_LEFT;
break;
case KEY_RIGHT:
direction = DIRECTION_RIGHT;
break;
case KEY_UP:
direction = DIRECTION_UP;
break;
case KEY_DOWN:
direction = DIRECTION_DOWN;
break;
case KEY_RESIZE:
done = "Window resize detected, aborted...";
break;
case 'q':
case 'Q':
case '\e':
done = "Quitting...";
break;
default:
break;
}
/* Move player, and check for collisions. */
switch (move_object_check(&player, direction)) {
case MOVE_OK:
steps_taken++;
break;
case MOVE_BLOCKED:
break;
case MOVE_ON_MINE:
done = "You were killed by a mine!";
break;
case MOVE_ON_MONEY:
for (i = 0; i < AMOUNT_OF_MONEY; i++) {
if (player.y == money[i].y && player.x == money[i].x) {
money_collected++;
money[i].taken = 1;
}
}
break;
case MOVE_ON_ENEMY:
done = "You moved onto the enemy!";
break;
}
draw_screen(); /* To update the positions of the objects, not visible. */
/* Move enemy, and check for collision with player. */
if (move_object_check(&enemy, direction_to_player()) == MOVE_BLOCKED) {
enemy_idle_count++;
if (enemy_idle_count > 3) {
if (move_object_check(&enemy, random_direction()) == MOVE_OK)
enemy_idle_count = 0;
}
}
if (player.y == enemy.y && player.x == enemy.x) {
done = "You were killed by the enemy!";
break;
}
if (money_collected == AMOUNT_OF_MONEY)
done = "Well done! all money collected!";
}
draw_screen();
endwin();
fprintf(stderr, "%s\n", done);
fprintf(stderr, "You collected %d$ of %d$.\n",
money_collected, AMOUNT_OF_MONEY);
fprintf(stderr, "You walked %d step%s.\n",
steps_taken, (steps_taken == 1) ? "" : "s");
return 0;
}