8
\$\begingroup\$

I have created a game called Runes in C. It is similar to Chess, but has a bigger board and different pieces. I would like to know how I can improve the code, for example, with user interface improvements.

This question is not a duplicate of that question, because this one has a revised and easier to read version of the code in the other one.

/*
 * runes.c The Game of Runes, similar to chess.
 *
 * RULES
 * Pieces: Wizard, Peasant, Knight, Emperor, Archer, Dragon.
 *
 * Attack Abilities:
 * Wizard can attack wizard, peasant, knight, emperor, archer.
 * Peasant can attack peasant, knight.
 * Knight can attack peasant, knight, emperor.
 * Emperor cannot attack.
 * Archer cannot attack, can only shoot.
 * Dragon can attack anything.
 *
 * Movement Abilities:
 * All pieces can move 1 square in any direction except for the following:
 * Knight. It moves in the L shape, as in chess. It can jump over pieces.
 * Dragon. Each turn, it can move 3 squares in any direction. It can jump
 *         over pieces. However, it cannot hover above a piece.
 *
 * Shooting Pieces:
 * All pieces can kill enemy pieces if they can walk over them, with two
 * exceptions:
 * Archer. It can kill enemy pieces with 2 squares between them, 1 if
 *         diagonal. 1 piece cannot block the arrow, but 2 pieces can.
 * Wizard. Same as Archer.
 * Dragon. It can kill enemy pieces with up to 1 square between them, 1 if
 *         diagonal
 *
 * The Wizard, Dragon and Emperor are major pieces.
 * The Peasant, Knight, and Archer are minor pieces.
 * In general, major pieces are more "valuable" than minor pieces.
 *
 * Each turn, you can move up to 9 pieces, but you must move at least one.
 * This is so you can move either of your four "battalions" at once (see
 * diagram below).
 *
 * The objective of this game is to kill the opposing Dragon AND the opposing
 * Emperor.
 *
 * TECHNICAL INFORMATION
 * There is an array of 256 elements which describes the position of the
 * pieces on the board. Starting at the top left.
 */

/* INITIAL PIECE POSITIONS
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
16|   |   |   |   |   |   |   | d | e |   |   |   |   |   |   |   |     <-- Black Side
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
15| p | p | p |   | p | p | p |   |   | p | p | p |   | p | p | p |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
14| p | w | p |   | p | w | p |   |   | p | w | p |   | p | w | p |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
13| p | p | p |   | p | p | p |   |   | p | p | p |   | p | p | p |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
12|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
11| k | a | k | a | k | a | k | a | a | k | a | k | a | k | a | k |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
10|   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
9 |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
8 |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
7 |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
6 | K | A | K | A | K | A | K | A | A | K | A | K | A | K | A | K |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
5 |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
4 | P | P | P |   | P | P | P |   |   | P | P | P |   | P | P | P |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
3 | P | W | P |   | P | W | P |   |   | P | W | P |   | P | W | P |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
2 | P | P | P |   | P | P | P |   |   | P | P | P |   | P | P | P |
  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
1 |   |   |   |   |   |   |   | E | D |   |   |   |   |   |   |   |     <-- White Side
Y +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
 X  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16
*/

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

/* Define constants for a black space and the players */
#define EMPTY       ' '
#define BLACK       0
#define WHITE       1

/*
 * This function gets the value of the piece on the board if any. Otherwise it
 * returns EMPTY which is defined above. The x/y coordinates are on the board
 * diagram above. Remember x=horizontal y=vertical. However, due to simplicity
 * the x and y values are inverted, i.e. x=3,y=3 would be board[51]. Also,
 * x=0,y=0 would be the top right.
 */
char get_piece_on_board(unsigned short x, unsigned short y, char board[])
{
    return board[x+(y*16)];
}

/*
 * Set a piece to a value. No error checking done.
 */
void set_piece(unsigned short x, unsigned short y, char piece, char board[])
{
    board[x+(y*16)] = piece;
}

/*
 * This should be obvious, it renders the board onto the screen.
 */
void render_board(char board[])
{
    unsigned short x;
    unsigned short y;

    printf("+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n");

    for (y = 0; y < 16; y++) {
        for (x = 0; x < 16; x++) {
            printf("| %c ", get_piece_on_board(x, y, board));
        }

        printf("|\n+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n");
    }
}

/*
 * Return WHITE or BLACK depending on the colour of a piece.
 */
char get_colour(char piece)
{
    if (piece == EMPTY) {
        return EMPTY;
    }
    return islower(piece) ? BLACK : WHITE;
}

/*
 * Get the strength of a piece, from 0 to 6. We have a strength table here:
 * On the left side of the chart is the attacking piece and the columns
 * represent the piece that is being attacked. Note that this is for melee
 * attacking only. Ranged attacks can hit anything.
 *   D A W P K E
 * D 1 1 1 1 1 1 = 6
 * W   1 1 1 1 1 = 5
 * K     1 1 1 1 = 4
 * P       1 1 1 = 3
 * A             = 0
 * E             = 0
 */
unsigned short get_strength(char piece)
{
    switch (toupper(piece)) {
    case 'D':
    case 'A':
        return 6;
        break;
    case 'W':
        return 5;
        break;
    case 'K':
        return 4;
        break;
    case 'P':
        return 3;
        break;
    default:
        return 0;
    }
}

/*
 * Check if a p1 is stronger than p2, i.e. can piece1 capture piece2?
 */
unsigned short is_stronger_than(unsigned short p1x, unsigned short p1y, unsigned short p2x, unsigned short p2y, char board[])
{
    unsigned short s1 = get_strength(get_piece_on_board(p1x, p1y, board));
    unsigned short s2 = get_strength(get_piece_on_board(p2x, p2y, board));
    return s1 > 0 && s1 >= s2;
}

/*
 * This function moves a piece. If you try to move the opponent's piece, it
 * will display 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 an opponent, it will check if your piece is powerful enough to
 * capture that piece. If so, the function will call set_piece() to capture
 * your opponent's piece. Much of the logic in this program is in here.
 */
void move_piece(unsigned short oldx, unsigned short oldy, unsigned short newx, unsigned short newy, char current_player, char board[])
{
    char oldpiece = get_piece_on_board(oldx, oldy, board);
    char newpiece = get_piece_on_board(newx, newy, board);

    /* First, check if that move is possible */
    if (get_colour(oldpiece) != current_player) {
        printf("Can't control other team member\n");
        return;
    } else if (get_colour(newpiece) == current_player) {
        printf("Refrained from killing own team member\n");
        return;
    } else if (get_colour(oldpiece) == EMPTY) {
        printf("The blank is not listening to you\n");
        return;
    }

    /* These rules only apply for capturing another piece */
    if (get_colour(newpiece) != EMPTY && is_stronger_than(oldx, oldy, newx, newy, board)) {
        printf("Can't kill stronger piece\n");
        return;
    }

    set_piece(newx, newy, get_piece_on_board(oldx, oldy, board), board);
    set_piece(oldx, oldy, EMPTY, board); /* clear out old piece */
}

/*
 * Tells a shooter (shooter x, shooter y) to shoot victim (victim x, victim y)
 * Only archer, dragon and wizard can shoot. Note that shooting is very
 * distinct from capturing, because when shooting anything can be attacked
 * and the shooter does not move
 */
void shoot(unsigned short sx, unsigned short sy, unsigned short vx, unsigned short vy, char current_player, char board[])
{
    char shooter = get_piece_on_board(sx, sy, board);
    char victim = get_piece_on_board(vx, vy, board);

    if (get_piece_on_board(sx, sy, board) != 'w' && get_piece_on_board(sx, sy, board) != 'W' && get_piece_on_board(sx, sy, board) != 'a' && get_piece_on_board(sx, sy, board) != 'A' && get_piece_on_board(sx, sy, board) != 'd' && get_piece_on_board(sx, sy, board) != 'D') {
        printf("The blank will not follow orders\n");
        return;
    } else if (get_colour(shooter) != current_player) {
        printf("Can't control other team member\n");
        return;
    } else if (get_colour(victim) == current_player) {
        printf("Refrained from shooting own team member\n");
        return;
    } else if (get_colour(victim) == EMPTY) {
        printf("Wasted arrow shooting empty square\n");
        return;
    }

    set_piece(vx, vy, EMPTY, board); /* clear out killed piece */
}

/*
 * This function prints out the title in ASCII art.
 */
void print_header(void)
{
    printf("\n");
    printf( "8888888  88   88  8888   88  8888888   8888888\n"
        "88   88  88   88  88 88  88  88       88\n"
        "8888888  88   88  88 88  88  8888888   88888\n"
        "88 88    88   88  88  88 88  88           8888\n"
        "88  88   88   88  88  88 88  88             88\n"
        "88   88  8888888  88   8888  8888888   888888\n");
    printf("\n");
}

/*
 * Print out the instructions for Runes
 */
void print_instructions(void)
{
    printf(
"RUNES INSTRUCTIONS:\n"
"Runes is a strategy game similar to chess. The key differences are the names\n"
"of the pieces, how they move, and that the board size is doubled. This aims\n"
"to create a more interesting game.\n"
"\n"
"Pieces:\n"
"There are 6 types of pieces: Peasant, Knight, Wizard, Emperor, Dragon, Archer.\n"
"All pieces can move 1 square in 8 directions, including diagonals, except for\n"
"the Dragon and the Knight. The Knight moves in the same L-shape as in chess,\n"
"and the Dragon can move 3 squares in a turn. The Knight and the Dragon can fly\n"
"or jump/teleport over pieces.\n"
"\n"
"Abilities:\n"
"The Dragon can breathe fire on a piece and destroy it if it is up to 1 square\n"
"away from it. This includes being right next to it, and being seperated by a\n"
"square. The Archer can shoot arrows at pieces if they are seperated by 2 squares\n"
"with at most 1 other piece between them. The Wizard does the same as the Archer,\n"
"except that the Wizard also shields ally pieces from any kind of damage if they\n"
"are right next to him. The only way to destroy a Battalion (a formation with a\n"
"Wizard in the center of other pieces) is to use a Dragon, Archer or another\n"
"Wizard.\n"
"\n"
"Piece Melee Strength:\n"
"A piece can only destroy other pieces if it is 'stronger' than that piece.\n"
"Here is a list of pieces and their point value:\n"
"Dragon can attack anything.\n"
"Wizard can attack anything except for Dragon.\n"
"Archer cannot attack.\n"
"Peasant can attack Peasant, Knight and Emperor.\n"
"Knight can attack Peasant, Knight, Emperor and Wizard.\n"
"Emperor cannot attack.\n"
"\n"
"Miscellaneous:\n"
"The objective is to destroy the opposing side's empereror and dragon. During\n"
"your nine turns, you cannot move one piece more than once. When shooting, all\n"
"projectiles can kill anything.\n"
);
}

/*
 * Asks the user what to do
 */
void print_menu(char board[])
{
    char choice;

    printf("1. Start Game\n");
    printf("2. Instructions\n");
    printf("3. Exit\n");

    printf("runes n> ");
    choice = getchar();

    switch (choice) {
    case '1':
        render_board(board);
        return;
    case '2':
        print_instructions();
        exit(EXIT_SUCCESS);
    case '3':
        exit(EXIT_SUCCESS);
    default:
        fprintf(stderr, "\nInvalid entry\n");
        exit(EXIT_FAILURE);
    }
}

/*
 * Read a line from stdin for runes commands such as move.
 */
char *read_command_line(void)
{
    char *line = NULL;
    size_t bufsize = 0;

    if (getline(&line, &bufsize, stdin) == -1) {
        perror("getline");
    }

    return line;
}

/*
 * Split the line into tokens.
 */
char **split_line(char *line)
{
    const short int const_bufsize = 64;
    const char delim[] = " \t\r\n\a";
    int bufsize = const_bufsize;
    int position;
    char **tokens = malloc(bufsize * sizeof(char*));
    char *token;

    if (!tokens) {
        perror("malloc");
        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) {
                perror("malloc");
                exit(EXIT_FAILURE);
            }
        }

        token = strtok(NULL, delim);
    }

    tokens[position] = NULL;
    return tokens;
}

/*
 * Check for a winner. If so, print out and exit.
 */
void check_winner(char board[])
{
    /* 4 bytes to tell if the emperors and dragons are present */
    char wempror;
    char bempror;
    char wdragon;
    char bdragon;

    /* Initialise the emperors and dragons */
    wempror = bempror = wdragon = bdragon = 0;

    /* Loop through the board and check for the Emperor and the Dragon */
    for (unsigned short i = 0; i <= 256; i++) {
        if (board[i] == 'D') wdragon = 1;
        if (board[i] == 'd') bdragon = 1;
        if (board[i] == 'E') wempror = 1;
        if (board[i] == 'e') bempror = 1;
    }

    /* Check if a side has lost */
    if (!wdragon && !wempror) {
        printf("White lost, Black won\n");
        exit(EXIT_SUCCESS);
    }

    if (!bdragon && !bempror) {
        printf("Black lost, White won\n");
        exit(EXIT_SUCCESS);
    }
}

/*
 * Run runes commands: move, exit, surrender, pass, flee, help, instructions,
 *                     render, shoot
 * This function returns 1 if a turn was NOT used, 0 otherwise.
 */
unsigned short run_runes_commands(char **args, char current_player, char board[])
{
    /* The pass command means skip current turn */
    if (args[0] == NULL || strcmp(args[0], "pass") == 0) {
        return 0; /* Turn was used */
    } else if (strcmp(args[0], "surrender") == 0 || strcmp(args[0], "flee") == 0) { /* the other player wins */
        if (current_player == WHITE) {
            printf("White has surrendered, Black wins\n");
        } else if (current_player == BLACK) {
            printf("Black has surrendered, White wins\n");
        }

        exit(EXIT_SUCCESS);
    } else if (strcmp(args[0], "exit") == 0) { /* unconditional exit */
        exit(EXIT_SUCCESS);
    } else if (strcmp(args[0], "move") == 0) {
        if (args[1] == NULL || args[2] == NULL || args[3] == NULL || args[4] == NULL) {
            printf("usage: move <oldx> <oldy> <newx> <newy>\n");
            return 1;
        }

        move_piece(atoi(args[1])-1, atoi(args[2])-1, atoi(args[3])-1, atoi(args[4])-1, current_player, board);
        render_board(board);
    } else if (strcmp(args[0], "instructions") == 0) {
        print_instructions();
        return 1;
    } else if (strcmp(args[0], "render") == 0) {
        render_board(board);
        return 1;
    } else if (strcmp(args[0], "shoot") == 0) {
        if (args[1] == NULL || args[2] == NULL || args[3] == NULL || args[4] == NULL) {
            printf("usage: shoot <sx> <sy> <vx> <vy>\n");
            return 1;
        }

        shoot(atoi(args[1])-1, atoi(args[2])-1, atoi(args[3])-1, atoi(args[4])-1, current_player, board);
        render_board(board);
    } else if (strcmp(args[0], "help") == 0) {
        printf( "Runes Commands Help:\n"
            "help - Show this help\n"
            "surrender - lose, other player wins\n"
            "flee - same as surrender\n"
            "move - move a piece\n"
            "pass - skip this move\n"
            "instructions - redisplay instructions\n"
            "render - redisplay board\n"
            "shoot - shoot enemies\n"
            "exit - quit game immediately\n");
        return 1;
    } else {
        printf("Invalid command\n");
        return 1;
    }

    return 0;
}

/*
 * The aim is to put most of the code into the other functions, and keep main
 * as neat as possible.
 */
int main(void)
{
    char *line;
    char **args;
    const char players[] = "bw";
    char board[256] = {
        ' ',' ',' ',' ',' ',' ',' ','d','e',' ',' ',' ',' ',' ',' ',' ',
        'p','p','p',' ','p','p','p',' ',' ','p','p','p',' ','p','p','p',
        'p','w','p',' ','p','w','p',' ',' ','p','w','p',' ','p','w','p',
        'p','p','p',' ','p','p','p',' ',' ','p','p','p',' ','p','p','p',
        ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
        'k','a','k','a','k','a','k','a','a','k','a','k','a','k','a','k',
        ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
        ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
        ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
        ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
        'K','A','K','A','K','A','K','A','A','K','A','K','A','K','A','K',
        ' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',
        'P','P','P',' ','P','P','P',' ',' ','P','P','P',' ','P','P','P',
        'P','W','P',' ','P','W','P',' ',' ','P','W','P',' ','P','W','P',
        'P','P','P',' ','P','P','P',' ',' ','P','P','P',' ','P','P','P',
        ' ',' ',' ',' ',' ',' ',' ','E','D',' ',' ',' ',' ',' ',' ',' '
    };

    print_header();
    print_menu(board);

    /* Main loop */
    for (char current_player = WHITE; ; current_player = WHITE - current_player) {
        for (unsigned short i = 0; i <= 9; i++) {
            printf("runes %c> ", players[(int)current_player]);
            line = read_command_line();
            args = split_line(line);

            if (run_runes_commands(args, current_player, board)) {
                i--;
            }

            free(line);
            free(args);
            check_winner(board);
        }
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ I don't think your pieces attack the same as with the pre-revised version. According to your comments, the peasant can attack a knight or a peasant. In this revised version, a peasant can attack a peasant or an emperor. \$\endgroup\$
    – JS1
    Aug 9, 2015 at 2:20
  • \$\begingroup\$ Doesn't is_stronger_than() enforce your rule? What I'm saying is that the behavior of this version differs than the behavior of the previous version. Is that intended? Now that I reread it, your comments at the top differ from the instructions in the middle of your program. So maybe your current program is more correct. But you need to change the comments at the top to match. Also, your instructions say a Knight can attack a Wizard. \$\endgroup\$
    – JS1
    Aug 9, 2015 at 7:57

2 Answers 2

11
\$\begingroup\$

This code is improved and I see that you've added the command shoot. I think there are still some things you could do to further improve the code.

Enforce the rules

It's a bit disappointing to be able to win in two moves:

shoot 9 16 8 1
shoot 9 16 9 1

This shouldn't be possible because the dragon should not be able to shoot across the entire board. The code should enforce the rules.

Validate the input

Nothing in the code currently prevents moving a piece completely outside the space allocated for the board. The run_runes_commands routine should validate the coordinates or the underlying shoot or move handlers should do so.

Store the game state in a struct

Right now there are two essential things that represent the game state, which are the board itself and the current_player. Additionally, instead of scanning the entire board each time, it would be possible to keep track of whether both sides still had both dragon and emperor pieces. I'd suggest storing all of that information in a single struct and then passing a pointer to the struct to the relevant functions.

Simplify checking for stronger piece

Rather than passing coordinates, pass piece values into is_stronger_than:

unsigned short is_stronger_than(char oldpiece, char newpiece)
{
    unsigned short s1 = get_strength(oldpiece);
    unsigned short s2 = get_strength(newpiece);
    return s2 > s1 || s1 == 0;
}

Now the calling code is simplified as well:

if (is_stronger_than(oldpiece, newpiece)) {

You can remove the need to check explictly for an empty square by making get_strength return the value of 1 for an archer or emperor and 0 for an empty square.

Simplify checking for an empty square

The first if clause of shoot() is overly complex:

if (get_piece_on_board(sx, sy, board) != 'w' && get_piece_on_board(sx, sy, board) != 'W' && get_piece_on_board(sx, sy, board) != 'a' && get_piece_on_board(sx, sy, board) != 'A' && get_piece_on_board(sx, sy, board) != 'd' && get_piece_on_board(sx, sy, board) != 'D') {

It could be greatly simplified:

if (shooter == EMPTY) {

Be careful in how you arrange if...else tests

In the move_piece routine, the message "The blank is not listening to you" will never be printed because of the first if clause:

if (get_colour(oldpiece) != current_player) {

Instead the check for an empty square should be first.

if (oldpiece == EMPTY) {

Use const where practical

In the get_piece_on_board routine, the underlying board is not altered. Make this explicit by declaring that parameter const:

char get_piece_on_board(unsigned short x, unsigned short y, const char board[])

Print square numbers to aid the user

It's somewhat difficult to enter valid moves because the squares are not labeled. Instead, consider printing the needed square numbering as the board is rendered:

void render_board(const char board[])
{
    unsigned short x;
    unsigned short y;
    const char horz[]="  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+";

    puts(horz);
    for (y = 0; y < 16; y++) {
        printf("%2d", y+1);
        for (x = 0; x < 16; x++) {
            printf("| %c ", get_piece_on_board(x, y, board));
        }
        puts("|");
        puts(horz);
    }
    puts(" X->1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16");
}

Note that this also uses a single const char[] for the horizontal line separator and uses puts instead of printf where appropriate.

\$\endgroup\$
3
  • \$\begingroup\$ Essentially, yes. A revised set_piece function, for example, could easily detect when either kind of piece were destroyed. This would avoid having to scan the entire board each time. \$\endgroup\$
    – Edward
    Aug 8, 2015 at 20:59
  • \$\begingroup\$ @Edward if (shooter == EMPTY) { isn't the same as the original code, because the shooter could be a Knight, which the original code would reject but your code would not. \$\endgroup\$
    – JS1
    Aug 9, 2015 at 8:20
  • 1
    \$\begingroup\$ @JS1: True, but then the associated message doesn't make sense. There should be another clause like else if (NULL == strchr("ADWadw", shooter)) { puts("Only archers, dragons and wizards can shoot"); return; } \$\endgroup\$
    – Edward
    Aug 9, 2015 at 11:16
6
\$\begingroup\$

Use bool instead of unsigned short

It would be more clear to use bool, true, and false from #include <stdbool.h> instead of using unsigned short with 1 and 0.

Use game state struct

I second the recommendation by Edward that you use a struct to hold your entire game state. Soon you will need a lot more state, and you don't want to create separate variables. For example, here are some pieces of state that you may need (now or in the future):

  1. The current player.
  2. How many moves that current player has made (out of 9 max).
  3. The pieces that the player has already moved this turn (because you don't want the same piece to move twice).
  4. How many of each piece remain for each side (to check victory conditions and to compute each side's "strength").

Alternative to strength table

If your pieces attack in a defined strength order (i.e stronger pieces attack weaker pieces), the strength table is fine. But it will likely be the case that you need to stray from the strength table in order to allow a weaker piece to kill a stronger piece.

According to your current rules (which I know are in flux), the Archer is the only piece that can kill the Dragon. In your current implementation, the Archer was given the highest strength (6) because it can attack anything, but that means that the Wizard cannot attack it because it has only strength 5. But your rules say that the Wizard should be able to attack an archer. So these rules do not follow a strictly strength order pattern:

D attacks D, W, A
A attacks D, W, A
W attacks W, A

The alternative to a strength table is to have an attack table. The attack table holds the pieces that are allowed to be attacked by each piece. Here is some sample code that you will need to adapt to your own code if you decide to use it:

typedef enum PieceValue {
    Emperor = 0,
    Peasant,
    Knight,
    Archer,
    Wizard,
    Dragon,
    Blank,
    MaxPieces,
} PieceValue;

// The explicit way to specify the attack table:

#define BIT(piece)        (1u << (piece))

const uint32_t AttackTable[MaxPieces] = {
    [Emperor] = 0,
    [Peasant] = BIT(Emperor) | BIT(Peasant),
    [Knight]  = BIT(Emperor) | BIT(Peasant) | BIT(Knight),
    [Archer]  = BIT(Emperor) | BIT(Peasant) | BIT(Knight) | BIT(Archer) |
                BIT(Wizard)  | BIT(Dragon),
    [Wizard]  = BIT(Emperor) | BIT(Peasant) | BIT(Knight) | BIT(Archer) |
                BIT(Wizard),
    [Dragon]  = BIT(Emperor) | BIT(Peasant) | BIT(Knight) | BIT(Archer) |
                BIT(Wizard)  | BIT(Dragon),
    [Blank]   = 0,
};

// The shorter way to do the same thing, if all pieces attack up to
// a certain level of piece:

#define UPTO(piece)        ((1u << (piece+1)) - 1)

const uint32_t AttackTable2[MaxPieces] = {
    [Emperor] = 0,
    [Peasant] = UPTO(Peasant),
    [Knight]  = UPTO(Knight),
    [Archer]  = UPTO(Dragon),
    [Wizard]  = UPTO(Wizard),
    [Dragon]  = UPTO(Dragon),
    [Blank]   = 0,
};

static PieceValue getPieceValue(char piece)
{
    switch (toupper(piece)) {
        case 'E': return Emperor;
        case 'P': return Peasant;
        case 'K': return Knight;
        case 'A': return Archer;
        case 'W': return Wizard;
        case 'D': return Dragon;
        default: return Blank;
    }
}

static bool canAttack(char attacker, char victim)
{
    PieceValue attackValue = getPieceValue(attacker);
    PieceValue victimValue = getPieceValue(victim);

    return (AttackTable[attackValue] & BIT(victimValue)) != 0;
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Another way to slice it would be to have each piece have its own associated struct which could contain such details as the items in your proposed AttackTable as well as other things such as the type of piece, and pointers to code to enforce attack and movement rules. In essence, object oriented programming in C. \$\endgroup\$
    – Edward
    Aug 9, 2015 at 17:14
  • \$\begingroup\$ @Edward I agree, each piece could have its own attributes. If the game were going to be more customizable (like with variant rules and such), this might be the way to go. \$\endgroup\$
    – JS1
    Aug 9, 2015 at 21:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.