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);
}
}
}
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\$