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, by adding proper error checking and by fixing any bugs. (This is probably not called 'broken code' because the main functions work fine.) Also, there is a large amount of spaghetti code in the middle and I don't know how to fix it.
/*
* 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 can attack anything with up to 2 squares between it and the target.
* 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.
*
* Misc Abilities:
* The Wizard can protect all friendly pieces right next to him.
*
* Capturing 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).
*
* 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>
/* Don't use constants, it will be very messy and they're not defined yet
* anyway. We initialise the board so we can render it later. */
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',' ',' ',' ',' ',' ',' ',' '
};
/* Global variable saying which player you are. */
char current_player = 'w';
/* Define constants for pieces, white pieces capitalised */
#define BWIZARD 'w'
#define BPEASANT 'p'
#define BKNIGHT 'k'
#define BEMPEROR 'e'
#define BDRAGON 'd'
#define BARCHER 'a'
#define WWIZARD 'W'
#define WPEASANT 'P'
#define WKNIGHT 'K'
#define WEMPEROR 'E'
#define WDRAGON 'D'
#define WARCHER 'A'
#define EMPTY ' '
/*
* 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 int x, unsigned short int y)
{
return board[x+(y*16)];
}
/*
* Set a piece to a value. No error checking done.
*/
void set_piece(unsigned short int x, unsigned short int y, char piece)
{
board[x+(y*16)] = piece;
return;
}
/*
* This should be obvious, it renders the board onto the screen.
*/
void render_board(void)
{
unsigned short int x;
unsigned short int y;
printf("+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n");
for (y = 0; y < 16; y++) {
for (x = 0; x < 16; x++) {
printf("| %c ", get_piece_on_board(x, y));
}
printf("|\n+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+\n");
}
return;
}
/*
* This function returns 1 or 0 depending on whether the specified piece
* belongs to white or black. 1 if it belongs to the current player,
* 0 otherwise. -1 if it is blank.
*/
short int piece_belongs_to_current_player(unsigned short int x, unsigned short int y)
{
switch (get_piece_on_board(x, y)) {
case BWIZARD: /* Square belongs to black */
case BPEASANT:
case BKNIGHT:
case BEMPEROR:
case BDRAGON:
case BARCHER:
if (current_player == 'w') {
return 0;
} else if (current_player == 'b') {
return 1;
}
break;
case WWIZARD: /* Square belongs to white */
case WPEASANT:
case WKNIGHT:
case WEMPEROR:
case WDRAGON:
case WARCHER:
if (current_player == 'w') {
return 1;
} else if (current_player == 'b') {
return 0;
}
break;
case EMPTY: /* There is nothing in that square */
return -1;
break;
}
return 0;
}
/*
* Check if a p1 is stronger than p2, i.e. can piece1 capture piece2? If
* you read this, get ready for spaghetti...
*/
unsigned short int is_stronger_than(unsigned short int p1x, unsigned short int p1y, unsigned short int p2x, unsigned short int p2y)
{
char p1;
char p2;
p1 = get_piece_on_board(p1x, p1y);
p2 = get_piece_on_board(p2x, p2y);
/* Worst spaghetti code ever! Well, at least I tried... */
if ((p1 == WDRAGON || p1 == BDRAGON) || ((p1 == WWIZARD || p1 == BWIZARD) && (p2 != BDRAGON && p2 != WDRAGON)) || ((p1 == WPEASANT || p1 == BPEASANT) && (p2 == BKNIGHT || p2 == WKNIGHT || p2 == WPEASANT || p2 == BPEASANT)) || ((p1 == WKNIGHT || p1 == BKNIGHT) && (p2 == BPEASANT || p2 == WPEASANT || p2 == BKNIGHT || p2 == WKNIGHT || p2 == WEMPEROR || p2 == BEMPEROR)) || (p1 == BARCHER || p1 == WARCHER)) {
return 1;
}
return 0;
}
/*
* 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 int oldx, unsigned short int oldy, unsigned short int newx, unsigned short int newy)
{
/* Checks to make sure insane things are not done */
if (piece_belongs_to_current_player(oldx, oldy) == 0) {
fprintf(stderr, "Cannot move piece belonging to other player\n");
return;
} else if (piece_belongs_to_current_player(newx, newy) == 1) {
fprintf(stderr, "Cannot capture own piece\n");
return;
} else if (get_piece_on_board(oldx, oldy) == EMPTY) {
fprintf(stderr, "Cannot move empty square\n");
return;
} else if (is_stronger_than(oldx, oldy, newx, newy)) {
fprintf(stderr, "Piece is too strong to capture");
return;
}
set_piece(newx, newy, get_piece_on_board(oldx, oldy));
set_piece(oldx, oldy, EMPTY); /* clear out old piece */
return;
}
/*
* 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");
return;
}
/*
* Print out the instructions for Runes
*/
void print_instructions(void)
{
printf( "Runes: Instructions\n"
"===================\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"
"------\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"
"---------\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 Strength\n"
"--------------\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 and Archer can attack anything.\n"
"Wizard can attack anything except for Dragon.\n"
"Peasant can attack Peasant, Knight and Emperor.\n"
"Knight can attack Peasant, Knight, Emperor and Wizard.\n"
"Emperor cannot attack.\n");
return;
}
/*
* Asks the user what to do
*/
void print_menu(void)
{
char choice;
printf("1. Start Game\n");
printf("2. Instructions\n");
printf("3. Exit\n");
choice = getchar();
switch (choice) {
case '1':
render_board();
return;
case '2':
print_instructions();
return;
case '3':
exit(EXIT_SUCCESS);
default:
fprintf(stderr, "\nInvalid entry\n");
exit(EXIT_FAILURE);
break;
}
return;
}
/*
* 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;
}
/*
* Run runes commands: move, exit, surrender, pass, flee, help
* This function returns 1 if a turn was not used, 0 otherwise.
*/
unsigned short int run_runes_commands(char **args)
{
/* 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 == 'w') {
printf("White has surrendered, Black wins\n");
} else if (current_player == 'b') {
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);
render_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"
"exit - unconditional exit\n");
return 1;
} else {
printf("Invalid Command = pass\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;
unsigned short int i;
print_header();
print_menu();
/* Main loop */
for (;;) {
for (i = 0; i <= 9; i++) {
printf("runes %c> ", current_player);
line = read_command_line();
args = split_line(line);
if (run_runes_commands(args)) {
i--;
}
}
/* Switch player */
if (current_player == 'w') {
current_player = 'b';
} else if (current_player == 'b') {
current_player = 'w';
}
}
return 0;
}