4
\$\begingroup\$

I've written a very revised and developed version of the game of Runes. The major changes are listed:

  • Convert runes to use curses.
  • Add color-coding to the pieces.
  • Change the board to use the alternate character set (box-drawing characters)
  • Use a struct to hold game data
  • Enforce all rules (can only move a certain piece once in a round, cannot shoot too far or move too far)
  • Add better in-program documentation (instructions, command help)
  • Add functionality to save and load games from a file
  • General appearance improvements

I realize that this code is now rather long, so if that's a problem then leave a comment and I'll see what I can do about that.

Makefile

CC := cc
CFLAGS := -Os -s -pipe -fomit-frame-pointer -W -Wall -Wextra -Werror\
      -I./include -std=c11

# Keep the alphabetical order!
OBJS := \
action_performed_already.o\
big_message.o\
check_winner.o\
distance.o\
get_color.o\
get_piece.o\
get_strength.o\
is_stronger_than.o\
load_game.o\
move_piece.o\
notification.o\
print_colored_piece.o\
print_commands_help.o\
print_header.o\
print_instructions.o\
print_menu.o\
read_command_line.o\
render_board.o\
render_screen.o\
runes.o\
runes_exit.o\
runes_init.o\
runes_pager.o\
run_runes_commands.o\
save_game.o\
set_action_record.o\
set_piece.o\
shoot.o\
split_line.o\
surrender.o\

all: strip

test: strip
    ./runes

runes: $(OBJS)
    $(CC) $(CFLAGS) -o runes $(OBJS) -lcurses

strip: runes
    strip -s runes

distclean: clean
clean:
    rm -f $(OBJS) runes

# Keep the alphabetical order!
action_performed_already.o: action_performed_already.c
big_message.o: big_message.c
check_winner.o: check_winner.c
distance.o: distance.c
get_color.o: get_color.c
get_piece.o: get_piece.c
get_strength.o: get_strength.c
is_stronger_than.o: is_stronger_than.c
load_game.o: load_game.c
move_piece.o: move_piece.c
notification.o: notification.c
print_colored_piece.o: print_colored_piece.c
print_commands_help.o: print_commands_help.c
print_header.o: print_header.c
print_instructions.o: print_instructions.c
print_menu.o: print_menu.c
read_command_line.o: read_command_line.c
render_board.o: render_board.c
render_screen.o: render_screen.c
runes.o: runes.c
runes_exit.o: runes_exit.c
runes_init.o: runes_init.c
runes_pager.o: runes_pager.c
run_runes_commands.o: run_runes_commands.c
save_game.o: save_game.c
set_action_record.o: set_action_record.c
set_piece.o: set_piece.c
shoot.o: shoot.c
split_line.o: split_line.c
surrender.o: surrender.c

include/constants.h

#ifndef __RUNES_CONSTANTS_H
#define __RUNES_CONSTANTS_H

/*
 * Constants, such as what an empty square is, and values for black and white which are returned by functions.
 * This file should be included in only the .c files which require it.
 */

/* empty piece on board */
#define EMPTY ' '

/* piece color values */
#define BLACK 0
#define WHITE 1

/* maximum size of notification string */
#define NSTRSIZ 100

#define CURS_OFF 0
#define CURS_NORMAL 1
#define CURS_BRIGHT 2

/* strength values for pieces */
#define STR_DRAGON 6
#define STR_WIZARD 5
#define STR_KNIGHT 4
#define STR_ARCHER 3
#define STR_PEASANT 2
#define STR_EMPEROR 1

/* move distance for long-move-range pieces */
#define MD_DRAGON 4
#define MD_KNIGHT 2

/* action record values */
#define AR_MOVED 1
#define AR_NOTMOVED 0

/* color pair values for pieces */
#define CP_PEASANT 1
#define CP_KNIGHT 2
#define CP_ARCHER 3
#define CP_WIZARD 4
#define CP_EMPEROR 5
#define CP_DRAGON 6

/* minimum screen size */
#define MIN_COLUMNS 130
#define MIN_ROWS 40

/* maximum number of pieces on the board, for each side */
#define MAX_PIECES 104
#define MAX_SIDE_EMPERORS 1
#define MAX_SIDE_DRAGONS 1
#define MAX_SIDE_WIZARDS 4
#define MAX_SIDE_KNIGHTS 7
#define MAX_SIDE_ARCHERS 7
#define MAX_SIDE_PEASANTS 32

/* table sizes */
#define SIZE_BOARD (256 * sizeof(char))
#define SIZE_ACTIONTABLE (256 * sizeof(int))

#endif

include/functions.h

#ifndef __RUNES_FUNCTIONS_H
#define __RUNES_FUNCTIONS_H

#include <stdarg.h>

#include <structures.h>

/*
 * Function declarations for Runes
 */

char get_piece(const int, const int, struct Game *);
void set_piece(const int, const int, const char, struct Game *);
int get_strength(const char);
char get_color(const char);
int is_stronger_than(const char, const char);
void render_board(struct Game *);
void render_screen(struct Game *);
void print_instructions(void);
void print_header(void);
void print_commands_help(void);
void move_piece(const int, const int, const int, const int, struct Game *);
void shoot(const int, const int, const int, const int, struct Game *);
void print_menu(void);
char *read_command_line(void);
char **split_line(char *);
int run_runes_commands(char **, struct Game *);
void check_winner(struct Game *);
int distance(const int, const int, const int, const int);
void save_game(const char *, struct Game *);
void load_game(const char *, struct Game *);
void print_colored_piece(const char);
int action_performed_already(const int, const int, struct Game *);
void set_action_record(const int, const int, const int, struct Game *);
int surrender(struct Game *);
int big_message(const char *, ...);
void runes_exit(int);
void runes_init(void);
void runes_pager(const char *);
char *notification(char *, ...);

#endif

include/macros.h

#ifndef __RUNES_MACROS_H
#define __RUNES_MACROS_H

#include <string.h>

#include <constants.h>

/*
 * Miscellaneous macros used by Runes
 */

#define streq(str1, str2) (!strcmp(str1, str2))
#define get_notification() (notification(NULL))
#define set_notification(str) ((void) notification(str))

#define PLAYER_TEXT(cp) (cp == WHITE ? "White" : "Black")
#define OTHER_PLAYER_TEXT(cp) (cp == WHITE ? "Black" : "White")

#endif

include/structures.h

#ifndef __RUNES_STRUCTURES_H
#define __RUNES_STRUCTURES_H

/* Runes structures. */

struct Game {
    char board[256];
    int actiontable[256];
    char current_player;
    int turn;
};

#endif

action_performed_already.c

#include <structures.h>

/*
 * This function returns whether a piece has already been moved.
 */

int action_performed_already(const int x, const int y, struct Game *r)
{
    return r->actiontable[x + (y * 16)];
}

action_performed_already.c

#include <structures.h>

/*
 * This function returns whether a piece has already been moved.
 */

int action_performed_already(const int x, const int y, struct Game *r)
{
    return r->actiontable[x + (y * 16)];
}

big_message.c

#include <curses.h>
#include <stdarg.h>
#include <string.h>

#include <constants.h>

/*
 * Clears the screen and displays a very important message
 */

int big_message(const char *msg, ...)
{
    va_list args;
    int row, col;
    int old_curs = curs_set(CURS_OFF);
    int ret;

    va_start(args, msg);

    clear();
    getmaxyx(stdscr, row, col);
    attrset(A_NORMAL);

    attron(A_BOLD);
    move(row / 2, (col - strlen(msg)) / 2);
    vwprintw(stdscr, msg, args);
    attroff(A_BOLD);

    attron(A_DIM);
    mvprintw(row - 1, 1, "[ Press key to continue ]");
    attroff(A_DIM);

    refresh();
    ret = getch();

    curs_set(old_curs);
    return ret;
}

check_winner.c

#include <stdlib.h>

#include <functions.h>
#include <structures.h>

/*
 * Check for a winner. If so, print out that information and exit
 */

void check_winner(struct Game *r)
{
    /* a critpiece is either a dragon or an emperor */
    int w_critpieces = 0;
    int b_critpieces = 0;

    for (int i = 0; i <= 256; ++i) {
        switch (r->board[i]) {
        case 'D':
        case 'E':
            ++w_critpieces;
            break;
        case 'd':
        case 'e':
            ++b_critpieces;
            break;
        default:
            continue;
        }
    }

    /* Check if a side has lost */
    if (!w_critpieces) {
        big_message("WHITE LOSES, BLACK WINS!\n");
        runes_exit(EXIT_SUCCESS);
    } else if (!b_critpieces) {
        big_message("BLACK LOSES, WHITE WINS!\n");
        runes_exit(EXIT_SUCCESS);
    }

    return;
}

distance.c

/*
 * Check the distance between two coordinates.
 */

int distance(const int x1, const int y1, const int x2, const int y2)
{
    const int x_dist = (x1 > x2) ? (x1 - x2) : (x2 - x1);
    const int y_dist = (y1 > y2) ? (y1 - y2) : (y2 - y1);
    return x_dist + y_dist;
}

get_color.c

#include <ctype.h>

#include <constants.h>

/*
 * Return WHITE or BLACK depending on color of piece
 */

char get_color(const char piece)
{
    if (piece == EMPTY) return EMPTY;
    return islower(piece) ? BLACK : WHITE;
}

get_piece.c

#include <structures.h>

/*
 * This function returns the character value of a specified piece on the board.
 */

char get_piece(const int x, const int y, struct Game *r)
{
    return r->board[x + (y * 16)];
}

get_strength.c

#include <ctype.h>

#include <constants.h>

/*
 * Get the "strength" of a piece, from 0 to 6. We have here a strength table: On the left side of the chart is
 * the attacking piece, and the columns represent the piece which is being attacked. 1 = can attack, 0 = cannot.
 *   D W K A P E
 * D 1 1 1 1 1 1 = 6 <- dragon can attack everything
 * W   1 1 1 1 1 = 5 <- wizard can attack everything ex. dragon
 * K     1 1 1 1 = 4 <- knight cannot attack dragon or wizard
 * A       1 1 1 = 3 <- archer can only attack archer, peasant, emperor
 * P         1 1 = 2 <- peasant can only attack itself and emperor
 * E           1 = 1 <- emperor can only attack emperor
 */

int get_strength(const char piece)
{
    switch (toupper(piece)) {
        case 'D':
            return STR_DRAGON;
        case 'W':
            return STR_WIZARD;
        case 'K':
            return STR_KNIGHT;
        case 'A':
            return STR_ARCHER;
        case 'P':
            return STR_PEASANT;
        case 'E':
            return STR_EMPEROR;
        default:
            return 0; /* error */
    }
}

is_stronger_than.c

#include <functions.h>

/*
 * Check if newpiece is stronger than oldpiece.
 */

int is_stronger_than(const char oldpiece, const char newpiece)
{
    int s1 = get_strength(oldpiece);
    int s2 = get_strength(newpiece);
    return (s2 > s1);
}

load_game.c

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

#include <constants.h>
#include <functions.h>

void load_game(const char *filename, struct Game *r)
{
    FILE *fp = fopen(filename, "r");

    char new_board[256];
    int new_actiontable[256];
    char new_current_player;
    int new_turn;

    int positive_movetable_entries = 0;
    int total_pieces = 0;
    int w_dragons = 0, w_emperors = 0, w_wizards = 0, w_knights = 0, w_archers = 0, w_peasants = 0;
    int b_dragons = 0, b_emperors = 0, b_wizards = 0, b_knights = 0, b_archers = 0, b_peasants = 0;

    if (!fp) {
        notification("That file does not exist or cannot be read");
        return;
    }

    /*
     * Gather all data from file
     */
    for (int i = 0; i < 256; ++i) {
        int ch = fgetc(fp);

        if (ch == EOF) {
            notification("Could not read board from save file");
            fclose(fp);
            return;
        }

        new_board[i] = ch;
    }

    for (int i = 0; i < 256; ++i) {
        int ch = fgetc(fp);

        if (ch == EOF) {
            notification("Could not read actiontable from save file");
            fclose(fp);
            return;
        }

        new_actiontable[i] = ch;
    }

    if ((new_current_player = fgetc(fp)) == EOF) {
        notification("Could not read current player from save file");
        fclose(fp);
        return;
    } else if ((new_turn = fgetc(fp)) == EOF) {
        notification("Could not read turn number from save file");
        fclose(fp);
        return;
    }

    fclose(fp);

    /*
     * Perform verification on gathered data
     */

    /* Verify the board. We need to make sure that there are no invalid
     * pieces and the number of pieces does not exceed the constants
     * defined in constants.h. */
    for (int i = 0; i < 256; ++i) {
        switch (new_board[i]) {
        case 'D':
            ++w_dragons;
            break;
        case 'W':
            ++w_wizards;
            break;
        case 'A':
            ++w_archers;
            break;
        case 'K':
            ++w_knights;
            break;
        case 'P':
            ++w_peasants;
            break;
        case 'E':
            ++w_emperors;
            break;
        case 'd':
            ++b_dragons;
            break;
        case 'w':
            ++b_wizards;
            break;
        case 'a':
            ++b_archers;
            break;
        case 'k':
            ++b_knights;
            break;
        case 'p':
            ++b_peasants;
            break;
        case 'e':
            ++b_emperors;
            break;
        case ' ':
            /* don't increment total piece count */
            continue;
        default:
            notification("The board contains invalid pieces.");
            return;
        }

        ++total_pieces;
    }

    if (total_pieces > MAX_PIECES) {
        notification("Too many pieces on the board.");
        return;
    }

    if (w_emperors > MAX_SIDE_EMPERORS || b_emperors > MAX_SIDE_EMPERORS) {
        notification("Too many emperors on the board.");
        return;
    } else if (w_dragons > MAX_SIDE_DRAGONS || b_dragons > MAX_SIDE_DRAGONS) {
        notification("Too many dragons on the board.");
        return;
    } else if (w_archers > MAX_SIDE_ARCHERS || b_archers > MAX_SIDE_ARCHERS) {
        notification("Too many archers on the board.");
        return;
    } else if (w_knights > MAX_SIDE_KNIGHTS || b_knights > MAX_SIDE_KNIGHTS) {
        notification("Too many knights on the board.");
        return;
    } else if (w_wizards > MAX_SIDE_WIZARDS || b_wizards > MAX_SIDE_WIZARDS) {
        notification("Too many wizards on the board.");
        return;
    } else if (w_peasants > MAX_SIDE_PEASANTS || b_peasants > MAX_SIDE_PEASANTS) {
        notification("Too many peasants on the board.");
        return;
    }

    /* Verify the actiontable. We need to make sure that the entries are
     * of the correct type, that no blank pieces have an action record of
     * AR_MOVED, and that the number of AR_MOVED entries does not exceed
     * 10. */
    for (int i = 0; i < 256; ++i) {
        switch (new_actiontable[i]) {
        case AR_MOVED:
            ++positive_movetable_entries;
            break;
        case AR_NOTMOVED:
            break;
        default:
            notification("Invalid movetable entry.");
            return;
        }
    }

    if (positive_movetable_entries > 10 || positive_movetable_entries > total_pieces) {
        notification("Too many movetable entries.");
        return;
    }

    /*
     * Verify current_player and turn.
     */
    if (new_current_player != WHITE && new_current_player != BLACK) {
        notification("Invalid current player.");
        return;
    } else if (new_turn < 1 || new_turn > 10) {
        notification("Invalid turn number.");
        return;
    }

    /*
     * Now write the new_ variables into the main game vars.
     */
    memcpy(r->board, new_board, 256);
    memcpy(r->actiontable, new_actiontable, 256);
    r->current_player = new_current_player;
    r->turn = new_turn;

    notification("Loaded game from savefile %s", filename);
    return;
}

move_piece.c

#include <ctype.h>

#include <constants.h>
#include <functions.h>
#include <structures.h>

/*
 * This function moves a piece. If you try to move an opponent's piece, there will be an error. If you try to
 * move your piece into another of your pieces, it will also display an error. If you move your piece into the
 * piece of your opponent, it will check if your piece is powerful enough to capture that piece. If so, the
 * function will call set_piece() to capture the opponent's piece.
 */

void move_piece(const int oldx, const int oldy, const int newx, const int newy,
        struct Game *r)
{
    char oldpiece = get_piece(oldx, oldy, r);
    char newpiece = get_piece(newx, newy, r);

    /* first do some sanity checks */
    if (newx > 16 || newy > 16) {
        notification("Can't move piece (%d,%d) off board to (%d,%d)!", oldx, oldy, newx, newy);
        return;
    } else if (get_color(oldpiece) == EMPTY) {
        notification("Can't control empty square (%d,%d)!", oldx, oldy);
        return;
    } else if (get_color(oldpiece) != r->current_player) {
        notification("Can't control member of other team (%d,%d)!", oldx, oldy);
        return;
    } else if (get_color(newpiece) == r->current_player) {
        notification("Can't kill your own team member (%d,%d)!", newx, newy);
        return;
    } else if (oldx == newx && oldy == newy) {
        notification("Those two places (%d,%d) are identical!", oldx, oldy);
        return;
    }

    /* check if action has already been performed on that piece */
    if (action_performed_already(oldx, oldy, r)) {
        notification("That piece (%d,%d) is already exhausted!", oldx, oldy);
        return;
    }

    /* check move distance is ok */
    if (toupper(oldpiece) == 'D' && distance(oldx, oldy, newx, newy) > MD_DRAGON) {
        notification("Even the mighty Dragon (%d,%d) cannot jump that far...", oldx, oldy);
        return;
    } else if (toupper(oldpiece) == 'K' && distance(oldx, oldy, newx, newy) > MD_KNIGHT) {
        notification("The horse of the Knight (%d,%d) cannot jump that far...", oldx, oldy);
        return;
    } else if (toupper(oldpiece) != 'D' && toupper(oldpiece) != 'K' && distance(oldx, oldy, newx, newy) > 1) {
        notification("Can't move that far... you wasted a move.");
        return;
    }

    /* these rules only apply for capturing pieces */
    if (get_color(newpiece) != EMPTY && is_stronger_than(oldpiece, newpiece)) {
        notification("Can't kill stronger piece (%d vs %d)!", get_strength(oldpiece), get_strength(newpiece));
        return;
    }

    /* record that the piece (which will be in the new position) has been moved */
    set_action_record(newx, newy, AR_MOVED, r);

    /* actually move the pieces */
    set_piece(newx, newy, get_piece(oldx, oldy, r), r);
    set_piece(oldx, oldy, EMPTY, r); /* clear the old square */

    notification("Moved (%d,%d) to (%d,%d)", oldx + 1, oldy + 1, newx + 1, newy + 1);

    return;
}

notification.c

#include <stdarg.h>
#include <stdio.h>

#include <constants.h>

char *notification(char *msg, ...)
{
    va_list args;
    static char str[NSTRSIZ] = {0};

    va_start(args, msg);

    if (msg) {
        vsnprintf(str, NSTRSIZ, msg, args);
    }

    return str;
}

print_colored_piece.c

#include <ctype.h>
#include <curses.h>

#include <constants.h>

/*
 * P = magenta
 * K = white
 * A = green
 * W = blue
 * E = yellow
 * D = red
 */

void print_colored_piece(const char piece)
{
    char piece_upper = toupper(piece);

    if (!has_colors() || COLOR_PAIRS < 6) {
        /* no colors or not enough colors, don't bother doing anything else */
        addch(piece);
        return;
    }

    init_pair(CP_PEASANT, COLOR_MAGENTA, COLOR_BLACK);
    init_pair(CP_KNIGHT, COLOR_WHITE, COLOR_BLACK);
    init_pair(CP_ARCHER, COLOR_GREEN, COLOR_BLACK);
    init_pair(CP_WIZARD, COLOR_BLUE, COLOR_BLACK);
    init_pair(CP_EMPEROR, COLOR_YELLOW, COLOR_BLACK);
    init_pair(CP_DRAGON, COLOR_RED, COLOR_BLACK);

    attron(A_BOLD);

    switch (piece_upper) {
    case 'P':
        attron(COLOR_PAIR(CP_PEASANT));
        break;
    case 'K':
        attron(COLOR_PAIR(CP_KNIGHT));
        break;
    case 'A':
        attron(COLOR_PAIR(CP_ARCHER));
        break;
    case 'W':
        attron(COLOR_PAIR(CP_WIZARD));
        break;
    case 'E':
        attron(COLOR_PAIR(CP_EMPEROR));
        break;
    case 'D':
        attron(COLOR_PAIR(CP_DRAGON));
        break;
    default:
        attrset(A_NORMAL);
        break;
    }

    addch(piece);

    attrset(A_NORMAL);
    return;
}

print_commands_help.c

#include <functions.h>

void print_commands_help(void)
{
    runes_pager(
"\n"
"R U N E S   C O M M A N D S"
"\n\n"
"Here is a list of the commands which can be used in Runes:"
"\n\n"
"  [p]ass                        -- skip one turn\n"
"  surrender                     -- surrender honorably and possibly lose\n"
"  [f]lee                        -- end the game dishonorably\n"
"  [q]uit                        -- exit the game, not saving\n"
"  exit                          -- same as quit\n"
"  [m]ove <x1> <y1> <x2> <y2>    -- move piece from (x1,y1) to (x2,y2)\n"
"  [i]nstructions                -- display game instructions\n"
"  [s]hoot <sx> <sy> <vx> <vy>   -- shoot (vx,vy) using (sx,sy)\n"
"  save [file]                   -- save game to file, or 'game.runes' if unspecified\n"
"  load [file]                   -- load game from file, or 'game.runes' if unspecified\n"
"  hasmoved <x> <y>              -- check if (x,y) has moved in this round\n"
"\n"
"Any other command will result in this text being displayed."
    );
    return;
}

print_header.c

#include <curses.h>

/*
 * Prints out title in ASCII art
 */

void print_header(void)
{
    attron(A_BOLD);
    printw("\n");

    printw(" RRRRR   UU   UU  NNN   NN  EEEEEEE   SSSSS\n");
    printw(" RR  RR  UU   UU  NNNN  NN  EE       SS\n");
    printw(" RRRRR   UU   UU  NN NN NN  EEEEEE  SSSSSSSS\n");
    printw(" RR  RR  UUU UUU  NN  NNNN  EE           SS\n");
    printw(" RR  RR   UUUUU   NN   NNN  EEEEEEE  SSSSS\n");

    printw("\n");
    attroff(A_BOLD);
    return;
}

print_instructions.c

#include <functions.h>

/*
 * Prints out instructions.
 */

void print_instructions(void)
{
    runes_pager(
"\n"
"R U N E S   I N S T R U C T I O N S"
"\n\n"
"Runes is a strategy game similar to chess. The key differences are the names of the pieces, how they move, "
"and that the board size is quadrupled. The aims of these changes are to create a more interesting and varied "
"game."
"\n\n"
"PIECES:\n"
"There are six difference types of pieces, listed in order of 'importance': Peasant, Knight, Archer, Wizard, "
"Emperor, Dragon. All pieces can move 1 square in the cardinal directions, except for the Dragon, which can "
"move 4 squares in the cardinal directions or 2 squares in the diagonal directions. The Dragon can fly over "
"other pieces. The main advantage of the Dragon is its superior movement abilities. The Knight can move 2 "
"squares in the cardinal directions, or 1 square in the diagonal directions."
"\n\n"
"To move a piece, use the [m]ove command. That command takes four parameters: the coordinates (x,y) of both "
"locations. For example, to move the piece at (05,10) to (06,10), the command would be 'm 05 10 06 10'."
"\n\n"
"ABILITIES:\n"
"The Dragon can breathe fire on an enemy piece and destroy it, if there is a maximum of 1 square in between "
"the Dragon and the enemy piece. Breathing fire does not move the attacking piece. "
"\n\n"
"The Archer can shoot arrows at pieces 4 squares away. The Wizard follows the same rules, but it can only "
"shoot 3 squares. Shooting arrows or magic also does not move the attacking piece."
"\n\n"
"To make use of ranged attacks, use the [s]hoot command. That command takes four parameters: the coordinates "
"(x,y) of the attacker, and the coordinates of the victim. For example, to shoot the piece at (05,07) with "
"the dragon at (05,10), use the command 's 05 10 05 07'. "
"\n\n"
"PIECE STRENGTH:\n"
"A piece can only melee-attack another piece if the attacking piece is stronger than the victim. Here is a "
"table of strengths (E=emperor, P=peasant, A=archer, K=knight, W=wizard, D=dragon): "
"\n\n"
"  D W K A P E    Strength\n"
"D 1 1 1 1 1 1 -> 6\n"
"W   1 1 1 1 1 -> 5\n"
"K     1 1 1 1 -> 4\n"
"A       1 1 1 -> 3\n"
"P         1 1 -> 2\n"
"E           1 -> 1\n"
"\n\n"
"Note that higher-strength pieces are not necessarily more valuable. The position of the pieces, its ranged "
"attack abilities, and whether you have a dragon and emperor also influence its importance."
"\n\n"
"Also note that strength does not apply when using ranged attacks. Thus, the strength values of ranged-attack "
"pieces are often unimportant, since those pieces mainly use ranged attacks. Those values are useful when the "
"path of a ranged character is blocked by a weaker enemy piece."
"\n\n"
"FORMATIONS:\n"
"When the game is started, the peasants and wizards are in four 'battallions'. This formation is invulnerable "
"to knights, peasants and emperors due to a combination of peasant strength and the wizard's ranged attack. "
"An enemy emperor is too weak to attack the battallion, while the enemy knights and peasants can easily be "
"dealt with using the wizard in the center of the formation."
"\n\n"
"It is generally a good idea to preserve the integrity of formations due to their special properties. "
"However, it may be useful to create new formations. One example of a 'custom' formation is an ordinary "
"battallion, but with an archer in the center rather than a wizard. This grants one more square of protection "
"zone around the battallion, and immunity to dragons. It is obvious that this is incredibly powerful."
"\n\n"
"OBJECTIVE:\n"
"The objective of the game is to destroy the enemy emperor AND the enemy dragon. The last task becomes much "
"more difficult if your own dragon is lost, but not impossible! "
"\n\n"
"MISCELLANEOUS:\n"
"You have nine turns to perform actions. Attempted surrender, shooting and moving/capturing all consume one "
"turn each. One turn is sufficient to move an entire battallion by one square. You are not allowed to move "
"one piece more than once."
"\n\n"
"You can skip turns with the [p]ass command if you want to, although there is almost never a good reason to "
"do so."
    );

    return;
}

print_menu.c

#include <ctype.h>
#include <curses.h>
#include <stdlib.h>

#include <functions.h>

/*
 * Ask the user what to do in the beginning of the game
 */

void print_menu(void)
{
    char choice;

    printw("1. Start game\n");
    printw("2. Instructions\n");
    printw("3. Exit\n");
    refresh();

retry:
    choice = getch();
    refresh();

    if (!isdigit(choice)) {
        goto retry;
    }

    printw("\n");

    switch (choice) {
        case '1':
            return;
        case '2':
            print_instructions(); /* fall through */
        case '3':
            runes_exit(EXIT_SUCCESS);
        default:
            big_message("Invalid entry!\n");
            runes_exit(EXIT_FAILURE);
    }
}

read_command_line.c

#include <ctype.h>
#include <curses.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include <constants.h>
#include <functions.h>

/*
 * Read a line from stdin _for runes commands only_.
 */

char *read_command_line(void)
{
    const int const_bufsize = 256;
    int bufsize = const_bufsize;
    int position = 0;
    char *buffer = malloc(sizeof(char) * bufsize);
    int ch;

    if (!buffer) {
        big_message("malloc: %s", strerror(errno));
        runes_exit(EXIT_FAILURE);
    }

    for (;;) {
        ch = getch();

        if (ch == KEY_BACKSPACE && position) {
            --position;
            printw("\b \b");
            continue;
        }

        if (ch == EOF || ch == '\n') {
            buffer[position] = '\0';
            printw("\n");
            return buffer;
        }

        if (!isprint(ch) && ch != ' ') {
            continue;
        }

        buffer[position++] = ch;

        if (position >= bufsize) {
            bufsize += const_bufsize;
            buffer = realloc(buffer, bufsize);

            if (!buffer) {
                big_message("realloc: %s", strerror(errno));
                runes_exit(EXIT_FAILURE);
            }
        }

        printw("%c", ch);
        refresh();
    }
}

render_board.c

#include <curses.h>

#include <functions.h>

/*
 * This function renders the board onto the screen.
 */

void render_board(struct Game *r)
{
    printw("  ");

    /* Print X coordinate values at the top of the board */
    for (int i = 1; i <= 16; ++i) {
        printw("  %02d", i);
    }

    /* draw top of board */
    printw("\n   ");
    addch(ACS_ULCORNER);

    for (int i = 0; i < 16; ++i) {
        addch(ACS_HLINE);
        addch(ACS_HLINE);
        addch(ACS_HLINE);

        if (i < 15) {
            addch(ACS_TTEE);
        }
    }

    addch(ACS_URCORNER);
    printw("\n");

    for (int y = 0; y < 16; ++y) {
        printw("%02d ", y + 1);

        for (int x = 0; x < 16; ++x) {
            addch(ACS_VLINE);
            addch(' ');
            print_colored_piece(get_piece(x, y, r));
            addch(' ');
        }

        addch(ACS_VLINE);
        printw(" %02d", y + 1);
        printw("\n   ");

        if (y < 15) {
            /* avoid printing same thing for bottom */
            addch(ACS_LTEE);

            for (int i = 0; i < 15; ++i) {
                addch(ACS_HLINE);
                addch(ACS_HLINE);
                addch(ACS_HLINE);
                addch(ACS_PLUS);
            }

            addch(ACS_HLINE);
            addch(ACS_HLINE);
            addch(ACS_HLINE);
            addch(ACS_RTEE);

            printw("\n");
        }
    }

    addch(ACS_LLCORNER);

    for (int i = 0; i < 16; ++i) {
        addch(ACS_HLINE);
        addch(ACS_HLINE);
        addch(ACS_HLINE);

        if (i < 15) {
            addch(ACS_BTEE);
        }
    }

        addch(ACS_LRCORNER);
    printw("\n  ");

    /* Print X coordinate values at the bottom of the board */
    for (int i = 1; i <= 16; ++i) {
        printw("  %02d", i);
    }

    printw("\n");
}

render_screen.c

#include <curses.h>

#include <functions.h>
#include <macros.h>

void render_screen(struct Game *r)
{
    int row, col;
    int y, x;

    clear();
    attrset(A_NORMAL);

    getmaxyx(stdscr, row, col);
    (void) row;

    attron(A_REVERSE);
    mvprintw(0, 0, "[Runes] Turn: %d/10 Player: %s [%s]", r->turn,
         PLAYER_TEXT(r->current_player), get_notification());

    getyx(stdscr, y, x);
    (void) y;

    for (int i = x; i < col; ++i) {
        printw(" ");
    }

    attroff(A_REVERSE);
    render_board(r);

    printw("\n");
    refresh();

    return;
}

runes.c

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

#include <functions.h>
#include <macros.h>

/*
 * Contains the main() function for the runes binary.
 */

int main(void)
{
    char *line;
    char **args;

    struct Game *r; /* r is not a commonly used variable letter */

    if ((r = malloc(sizeof(struct Game))) == NULL) {
        big_message("Could not allocate memory for game structure");
        runes_exit(EXIT_FAILURE);
    }

    memcpy(r->board,
        "kakakak  akakaka"
        "       de       "
        "ppp ppp  ppp ppp"
        "pwp pwp  pwp pwp"
        "ppp ppp  ppp ppp"
        "                "
        "                "
        "                "
        "                "
        "                "
        "                "
        "PPP PPP  PPP PPP"
        "PWP PWP  PWP PWP"
        "PPP PPP  PPP PPP"
        "       ED       "
        "AKAKAKA  KAKAKAK",
           256);

    runes_init();
    print_header();
    print_menu();

    /* main loop */
    for (r->current_player = WHITE ;; r->current_player = WHITE - r->current_player) {
        memset(r->actiontable, AR_NOTMOVED, SIZE_ACTIONTABLE);

        for (r->turn = 1; r->turn <= 10; ++r->turn) {
            render_screen(r);
            printw("runes> ");
            fflush(stdout);
            line = read_command_line();
            args = split_line(line);

            if (run_runes_commands(args, r)) {
                ++r->turn;
            }

            free(line);
            free(args);
            check_winner(r);
        }
    }
}

runes_exit.c

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

/*
 * Deinitializes curses and exits runes
 */

void runes_exit(int val)
{
    endwin();
    exit(val);
}

runes_init.c

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

#include <constants.h>
#include <functions.h>

/*
 * Setup curses.
 */

void runes_init(void)
{
    int row, col;

    initscr();
    noecho();
    keypad(stdscr, TRUE);
    cbreak();
    start_color();

    getmaxyx(stdscr, row, col);

    if (row < MIN_ROWS || col < MIN_COLUMNS) {
        big_message("This screen is not large enough to properly play Runes.\n");
        runes_exit(EXIT_FAILURE);
    }
}

runes_pager.c

#include <curses.h>

/*
 * A rudimentary pager-like thing for displaying instructions and other long text.
 */

void runes_pager(const char *text)
{
    int row, col;
    int y, x;
    int input;

    clear();
    getmaxyx(stdscr, row, col);

    move(0, 0);

    for (size_t ptr = 0; text[ptr] != '\0'; ++ptr) {
        getyx(stdscr, y, x);

        if (y >= (row - 1)) {
            /* almost at end of screen -- print prompt */
            refresh();

            attron(A_BOLD);
            printw("[spacebar to advance; q to quit]");
            attroff(A_BOLD);

            for (;;) {
                input = getch();

                switch (input) {
                case 'q':
                    clear();
                    return;
                case '\n':
                case ' ':
                    break;
                default:
                    continue;
                }

                break;
            }

            /* clear screen and prepare for another screen */
            clear();
            move(0, 0);
        } else if (x >= (col - 3)) {
            /* almost at right side of screen -- get to next line */
            printw("-\n");
        }

        printw("%c", text[ptr]);
    }

    getch();
    clear();
    return;
}

run_runes_commands.c

#include <stdlib.h>

#include <functions.h>
#include <macros.h>
#include <structures.h>

/*
 * Handles runes commands: move, exit, surrender, pass, flee, help, instructions, render, shoot
 * Returns 1 if turn not used, 0 otherwise. Lots of the game code is in here, I should clean it up...
 */

int run_runes_commands(char **argv, struct Game *r)
{
    char incorrect_command_text[] = "Syntax error. Use 'help' command to see syntax help.";

    if (!argv[0]) {
        return 1; /* turn isn't used if user pressed Enter without a command */
    } else if (streq(argv[0], "pass") || streq(argv[0], "p")) {
        return 0; /* pass == skip one turn */
    } else if (streq(argv[0], "surrender")) {
        return surrender(r);
    } else if (streq(argv[0], "flee") || streq(argv[0], "f")) {
        big_message("%s has fled the battlefield! %s wins.",
                PLAYER_TEXT(r->current_player), OTHER_PLAYER_TEXT(r->current_player));
        runes_exit(EXIT_SUCCESS);
    } else if (streq(argv[0], "exit") || streq(argv[0], "quit") || streq(argv[0], "q")) {
        runes_exit(EXIT_SUCCESS);
    } else if (streq(argv[0], "m") || streq(argv[0], "move")) {
        if (!argv[1] || !argv[2] || !argv[3] || !argv[4]) {
            notification(incorrect_command_text);
            return 1;
        }

        move_piece(atoi(argv[1]) - 1, atoi(argv[2]) - 1, atoi(argv[3]) - 1, atoi(argv[4]) - 1, r);
        return 0;
    } else if (streq(argv[0], "instructions") || streq(argv[0], "i")) {
        print_instructions();
        return 1;
    } else if (streq(argv[0], "shoot") || streq(argv[0], "s")) {
        if (!argv[1] || !argv[2] || !argv[3] || !argv[4]) {
            notification(incorrect_command_text);
            return 1;
        }

        shoot(atoi(argv[1]) - 1, atoi(argv[2]) - 1, atoi(argv[3]) - 1, atoi(argv[4]) - 1, r);
        return 0;
    } else if (streq(argv[0], "save")) {
        if (!argv[1]) {
            save_game("game.runes", r);
            return 1;
        }

        save_game(argv[1], r);
        return 1;
    } else if (streq(argv[0], "load")) {
        if (!argv[1]) {
            load_game("game.runes", r);
            return 1;
        }

        load_game(argv[1], r);
        return 1;
    } else if (streq(argv[0], "hasmoved")) {
        int x, y;

        if (!argv[1] || !argv[2]) {
            notification(incorrect_command_text);
            return 1;
        }

        x = atoi(argv[1]);
        y = atoi(argv[2]);

        if (get_piece(x - 1, y - 1, r) == EMPTY) {
            notification("No piece at (%d,%d)", x, y);
            return 1;
        }

        if (get_color(get_piece(x - 1, y - 1, r)) != r->current_player) {
            notification("That is not your piece");
            return 1;
        }

        if (action_performed_already(x - 1, y - 1, r)) {
            notification("Piece at (%d,%d) is exhausted", x, y);
        } else {
            notification("Piece at (%d,%d) is free to move", x, y);
        }

        return 1;
    } else {
        print_commands_help();
        return 1;
    }

    return 1;
}

save_game.c

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

#include <functions.h>

void save_game(const char *filename, struct Game *r)
{
    FILE *fp = fopen(filename, "w");

    if (!fp) {
        notification("%s: %s", filename, strerror(errno));
        return;
    }

    for (int i = 0; i < 256; ++i) {
        fputc(r->board[i], fp);
    }

    for (int i = 0; i < 256; ++i) {
        fputc(r->actiontable[i], fp);
    }

    fputc(r->current_player, fp);
    fputc(r->turn, fp);

    fclose(fp);
    return;
}

set_action_record.c

#include <structures.h>

/*
 * This function sets a actiontable cell to a value. No error checking.
 */

void set_action_record(const int x, const int y, const int value, struct Game *r)
{
    r->actiontable[x + (y * 16)] = value;
}

set_piece.c

#include <structures.h>

/*
 * This function sets a piece to a value. No error checking.
 */

void set_piece(const int x, const int y, const char piece, struct Game *r)
{
    r->board[x + (y * 16)] = piece;
}

shoot.c

#include <ctype.h>
#include <string.h>

#include <constants.h>
#include <functions.h>
#include <structures.h>

/*
 * Tells a shooter at (sx, sy) to shoot victim at (vx, vy). Only archers, dragons and wizards can shoot. When
 * shooting, strength is not relevant. Shooting does not move the attacker.
 */

void shoot(const int sx, const int sy, const int vx, const int vy,
       struct Game *r)
{
    const char shooter = get_piece(sx, sy, r);
    const char victim = get_piece(vx, vy, r);

    if (shooter == EMPTY) {
        notification("Cannot shoot using a blank! Wasted a turn.");
        return;
    } else if (!strchr("DAW", toupper(shooter))) {
        notification("Cannot shoot using that piece!");
        return;
    } else if (get_color(shooter) != r->current_player) {
        notification("Cannot control other team member!");
        return;
    } else if (get_color(victim) == r->current_player) {
        notification("Cannot kill your own team member!");
        return;
    } else if (get_color(victim) == EMPTY) {
        notification("Cannot shoot at empty square!");
        return;
    }

    /* Check if that piece has already performed an action. */
    if (action_performed_already(sx, sy, r)) {
        notification("That piece is already exhausted, wasted a turn!");
        return;
    }

    /* The Archer can shoot with a distance of 4, the Wizard can shoot with a distance of 3, and the
     * Dragon can shoot with a distance of 2. */
    if (toupper(shooter) == 'A') {
        if (distance(sx, sy, vx, vy) > 4) {
            notification("Cannot shoot that far, wasted an arrow.");
            return;
        }
    } else if (toupper(shooter) == 'W') {
        if (distance(sx, sy, vx, vy) > 3) {
            notification("Cannot shoot that far, wasted a spell.");
            return;
        }
    } else if (toupper(shooter) == 'D') {
        if (distance(sx, sy, vx, vy) > 2) {
            notification("Fire can only travel 2 squares, wasted a turn.");
            return;
        }
    }

    set_action_record(sx, sy, AR_MOVED, r);
    set_piece(vx, vy, EMPTY, r); /* clear out killed piece */

    notification("Shot (%d,%d) with (%d,%d)", vx + 1, vy + 1, sx + 1, sy + 1);
    return;
}

split_line.c

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

#include <functions.h>

/*
 * Split line from read_command_line() into tokens.
 */

char **split_line(char *line)
{
    const int const_bufsize = 256;
    const char delim[] = " \t\r\n\a";
    int bufsize = const_bufsize;
    int position;
    char **tokens = malloc(bufsize * sizeof(char *));
    char *token;

    if (!tokens) {
        big_message("malloc: %s", strerror(errno));
        runes_exit(EXIT_FAILURE);
    }

    token = strtok(line, delim);

    for (position = 0; token != NULL; ++position) {
        tokens[position] = token;

        if (position >= bufsize) {
            bufsize += const_bufsize;
            tokens = realloc(tokens, bufsize * sizeof(char *));

            if (!tokens) {
                big_message("realloc: %s", strerror(errno));
                runes_exit(EXIT_FAILURE);
            }
        }

        token = strtok(NULL, delim);
    }

    tokens[position] = NULL;
    return tokens;
}

surrender.c

#include <stdlib.h>

#include <functions.h>
#include <macros.h>

int surrender(struct Game *r)
{
    int answer = big_message("%s has offered to surrender, does %s accept [y/n]",
                 PLAYER_TEXT(r->current_player), OTHER_PLAYER_TEXT(r->current_player));

    if (answer == 'y') {
        big_message("%s has surrendered, %s wins.", PLAYER_TEXT(r->current_player), OTHER_PLAYER_TEXT(r->current_player));
        runes_exit(EXIT_SUCCESS);
    } else if (answer == 'n') {
        big_message("%s has refused %s's offer of surrender, the game will continue.",
                OTHER_PLAYER_TEXT(r->current_player), PLAYER_TEXT(r->current_player));
        return 0;
    } else {
        big_message("Invalid response, try again");
        return 1;
    }

    return 1;
}

The git repository where this is hosted is here. This program is in the public domain under the Unlicense.

\$\endgroup\$
0
\$\begingroup\$
  • The Makefile needs to mention which object depends on which header file.
  • The constants in include/constants.h should be enums, since they are not intended to be used by the C preprocessor.
  • The inclusion guards __RUNES_CONSTANTS_H must be written without leading underscores.
  • sizeof(char) is defined to be 1.
  • Instead of #include <structures.h>, write #include "structures.h" to make clear that this header file is from your own project.
  • In the function definitions, the Game * parameter should come first, following the style of object oriented programming. This also makes clear that these methods modify the game.
  • Don't declare function parameters as const in the declarations, it is useless.
  • Rename action_performed_already to already_moved or moved_already, so that the comment above becomes useless and can be removed.
  • In check_winner, the trailing return; is useless.
  • In get_color, the call to islower should cast its argument to unsigned char, just to be on the safe side. Same for get_strength and toupper.

  • In get_strength.c, the table is useless, since it can be summarized with each piece can attack pieces of the same or lower strength.

  • I would replace is_stronger_than(b, a) with !can_attack(a, b), since this is how you would explain the rule to a human.
  • load_game should be split into several smaller functions, one of them being validate_board.
  • The comment above move_piece is redundant. Unless you are required by someone else to write such "documentation", you should remove it. Everyone who knows the game and sees the function name move_piece will expect exactly this code, so there is no need to explain anything.
  • In read_command_line, there is no need to realloc, since all game commands have much less than 256 characters.
  • In render_board.c, the last addch is indented too far.
  • In main, there is no need to dynamically allocate a Game *. Just define a Game and pass its address to the various functions.
  • In main, since this is C, not C++, it is more common to use postfix increment r->turn++ instead of prefix.
  • In run_runes_commands, the variable name argv should be args, since these strings do not come from the command line.
  • Don't use atoi for parsing integers. Use sscanf instead, which allows for error handling.
  • Instead of split_line and parsing the commands yourself, you can parse the commands using sscanf("move %d %d %d %d%c", &x1, &y1, &x2, &y2, &end) == 4. The end variable makes sure that the command really ends there.
  • If you don't modify a string, pass it as const char * instead of char *, e.g. in split_line.
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for taking the time to review my very long post. +1 \$\endgroup\$ – user73265 Oct 11 '16 at 8:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy