Last active
December 6, 2018 23:14
-
-
Save nbervar21/ddf7741a2755d7a4039a4a5066c1077c to your computer and use it in GitHub Desktop.
chess.c - semi-working prototype
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// chess.c by Nick Bervar | |
#include <cs50.h> | |
#include <stdio.h> | |
// only used for printBox, temporary | |
#include <string.h> | |
// ============================== | |
// === preprocessor constants === | |
// ============================== | |
#define BOARD_WIDTH 8 | |
#define BOARD_HEIGHT 8 | |
#define WHITE 1 | |
#define BLACK 2 | |
// used for both piece types and colors | |
#define EMPTY 0 | |
#define WPAWN 1 | |
#define WKNIGHT 2 | |
#define WBISHOP 3 | |
#define WROOK 4 | |
#define WQUEEN 5 | |
#define WKING 6 | |
#define BPAWN 7 | |
#define BKNIGHT 8 | |
#define BBISHOP 9 | |
#define BROOK 10 | |
#define BQUEEN 11 // yass queen | |
#define BKING 12 | |
// highlight eligible squares, treated as its own piece | |
#define MOVE_HIGHLIGHT 13 | |
// piece types, colorless | |
// will be drawn as white pieces if actually used | |
#define TYPE_PAWN 1 | |
#define TYPE_KNIGHT 2 | |
#define TYPE_BISHOP 3 | |
#define TYPE_ROOK 4 | |
#define TYPE_QUEEN 5 | |
#define TYPE_KING 6 | |
// if a special message is queued up to be displayed | |
#define PRINTQUEUE_DEFAULT 0 | |
#define PRINTQUEUE_QUEENPAWN 1 | |
#define PRINTQUEUE_QUITGAME 2 | |
// used to track if any of the pieces have moved, for castling | |
// more about this below the global variables section | |
#define CASTLE_WROOK_L_MOVED 0 | |
#define CASTLE_WROOK_R_MOVED 1 | |
#define CASTLE_WKING_MOVED 2 | |
#define CASTLE_BROOK_L_MOVED 3 | |
#define CASTLE_BROOK_R_MOVED 4 | |
#define CASTLE_BKING_MOVED 5 | |
// how many saved games you can have | |
#define MAX_SAVES 20 | |
// how the board is indexed | |
// everybody knows arrays start from 1 | |
// 1 2 3 4 5 6 7 8 | |
// 9 10 11 12 13 14 15 16 | |
// 17 18 19 20 21 22 23 24 | |
// 25 26 27 28 29 30 31 32 | |
// 33 34 35 36 37 38 39 40 | |
// 41 42 43 44 45 46 47 48 | |
// 49 50 51 52 53 54 55 56 | |
// 57 58 59 60 61 62 63 64 | |
// coords on the board (user control POV) | |
// 1 2 3 4 5 6 7 8 | |
// 2 | |
// 3 | |
// 4 | |
// 5 | |
// 6 | |
// 7 | |
// 8 | |
// ======================== | |
// === global variables === | |
// ======================== | |
// board array, contains enums for each piece | |
int board[BOARD_WIDTH][BOARD_HEIGHT]; | |
// whose turn it is, WHITE 1, BLACK 2 | |
int whoseTurn; | |
// each letter of this (0-12) corresponds to a piece, including EMPTY and MOVE_HIGHLIGHT | |
// const char pieceChar[14] = "▢♙♘♗♖♕♔♟♞♝♜♛♚#"; | |
const char pieceChar[14] = "-PNBRQKpnbrqk#"; | |
// move counter, used for preliminary checks and round counter | |
int movesMade; | |
// castling system | |
// these correspond to pieces that need to not move | |
// when they move, they're set to 1 | |
int castleCheck[6] = {0, 0, 0, 0, 0, 0}; | |
// saved games | |
// currently takes up lots of memory | |
int savedGames[MAX_SAVES][64]; | |
// to escape the loop | |
bool gameEnded; | |
// ============================ | |
// === declarations n stuff === | |
// ============================ | |
// indexing and coords | |
int getX(int n); | |
int getY(int n); | |
int getIndex(int x, int y); | |
int indexBoard(int n); | |
// piece data | |
char getPieceChar(int piece); | |
char getPiece(int n); | |
bool isValidPiece(int piece); | |
int getPieceType(int piece); | |
int getPieceColor(int piece); | |
int getPieceColor(int piece); | |
// game mechanics | |
void nextTurn(void); | |
bool squareInCheck(int start); | |
void checkForQueenedPawns(void); | |
bool canAnyMovesBeMade(int start); | |
// piece moving | |
int movePieceCoords(int sx, int sy, int dx, int dy); | |
bool movePiece(int start, int dest); | |
bool canMoveToSquare(int start, int dest); | |
void changeBoardByIndex(int n, int piece); | |
bool piecesBetween(int start, int dest); | |
bool canPieceSpecificMove(int start, int dest); | |
void moveHighlight(int start); | |
// move validation | |
bool canMoveToSquare(int start, int dest); | |
bool isLegalMove(int start, int dest); | |
bool validSelectedPiece(int start); | |
// prompts | |
void promptQueenPawn(int start, bool tryAgain); | |
void promptInput(int printQueue); | |
bool promptQuit(void); | |
void promptMove(void); | |
// board | |
void printBoard(void); | |
void clearBoard(void); | |
void setupBoard(void); | |
// printing | |
void printStartMessage(void); | |
// debug | |
void printBox(char *txt); | |
// ============ | |
// === MAIN === | |
// ============ | |
int main(void) | |
{ | |
gameEnded = false; | |
printStartMessage(); | |
setupBoard(); | |
printBoard(); | |
do | |
{ | |
// no idea what to do with print queue | |
promptInput(PRINTQUEUE_DEFAULT); | |
} | |
while (!gameEnded); | |
} | |
// ======================== | |
// === global functions === | |
// ======================== | |
int getX(int n) | |
{ | |
return ((n - 1) % BOARD_WIDTH); | |
} | |
int getY(int n) | |
{ | |
return ((n - 1) / BOARD_HEIGHT); | |
} | |
// fixme: uses 8 instead of BOARD_* constants | |
// fixme: inconsistent, returns 0-7 not 1-8 | |
int getIndex(int x, int y) | |
{ | |
return ((y - 1) * 8) + x; | |
} | |
int indexBoard(int n) | |
{ | |
return board[getX(n)][getY(n)]; | |
} | |
char getPieceChar(int piece) | |
{ | |
return pieceChar[piece]; | |
} | |
void changeBoardByIndex(int n, int piece) | |
{ | |
board[getX(n)][getY(n)] = piece; | |
} | |
// useless function | |
char getPiece(int n) | |
{ | |
return getPieceChar(indexBoard(n)); | |
} | |
bool isValidPiece(int piece) | |
{ | |
return (piece != EMPTY && piece != MOVE_HIGHLIGHT); | |
} | |
int getPieceType(int piece) | |
{ | |
if (!isValidPiece(piece)) | |
{ | |
return piece; | |
} | |
// converts black piece enums into white piece enums | |
// like michael jackson but with chess pieces | |
if (piece > 6) | |
{ | |
piece -= 6; | |
} | |
return piece; | |
} | |
int getPieceColor(int piece) | |
{ | |
// two special cases | |
if (!isValidPiece(piece)) | |
{ | |
return piece; | |
} | |
else if (piece < 7) | |
{ | |
return WHITE; | |
} | |
else | |
{ | |
return BLACK; | |
} | |
} | |
void nextTurn(void) | |
{ | |
if (whoseTurn == WHITE) | |
{ | |
whoseTurn = BLACK; | |
} | |
else | |
{ | |
whoseTurn = WHITE; | |
} | |
} | |
// todo: checks if a square is in check so king cant move into it | |
bool squareInCheck(int start) | |
{ | |
return false; | |
} | |
// run a trace to check if there are pieces between two squares | |
bool piecesBetween(int start, int dest) | |
{ | |
// coordinates for start and destination | |
int sx = getX(start); | |
int sy = getY(start); | |
int dx = getX(dest); | |
int dy = getY(dest); | |
// if somehow the trace is on top of itself, return 1 to try again | |
if (sx == dx && sy == dy) | |
{ | |
return true; | |
} | |
// differences in the coordinates | |
int diffx = abs(dx - sx); | |
int diffy = abs(dy - sy); | |
// trace iterator {x, y} - how the checked coordinates are modified every square of the trace | |
// for example: {1, 0} would go to the right, {-1, -1} would go diagonally up and to the left | |
int traceIterator[2] = {0, 0}; | |
int traceDistance = 0; | |
// check if we're diagonal | |
if (diffx == diffy) | |
{ | |
// check which of the 4 directions to go | |
// we can get away with not using >= or <= cuz we know its a diff square | |
// left or right | |
if (dx > sx) | |
{ | |
traceIterator[0] = 1; | |
} | |
else | |
{ | |
traceIterator[0] = -1; | |
} | |
// up or down | |
if (dy > sy) | |
{ | |
traceIterator[1] = 1; | |
} | |
else | |
{ | |
traceIterator[1] = -1; | |
} | |
// trace distance is difference between either coordinate | |
traceDistance = diffx; | |
} | |
else | |
{ | |
// if its not diagonal, it must be straight | |
// if its not straight, drop it | |
if (dx != sx && dy != sy) | |
{ | |
return true; | |
} | |
// going right | |
if (dx > sx) | |
{ | |
traceIterator[0] = 1; | |
} | |
// going left | |
else if (dx < sx) | |
{ | |
traceIterator[0] = -1; | |
} | |
// going up | |
if (dy > sy) | |
{ | |
traceIterator[1] = 1; | |
} | |
// going down | |
else if (dy < sy) | |
{ | |
traceIterator[1] = -1; | |
} | |
// at least one diff must be 0 | |
traceDistance = diffx + diffy; | |
} | |
// i starts as 1 to skip the start square, which should have our piece | |
// < is used so we dont check the destination square | |
for (int i = 1; i < traceDistance; i++) | |
{ | |
// check these coordinates for obstructions in the trace | |
// the first and last coordinate pairs are ommited, obstructions are expected in them | |
int checkX = sx + (traceIterator[0] * i); | |
int checkY = sy + (traceIterator[1] * i); | |
// if we're obstructed, stop the trace | |
if (isValidPiece(board[checkX][checkY])) | |
{ | |
return true; | |
} | |
} | |
// no obstructions | |
return false; | |
} | |
bool canPieceSpecificMove(int start, int dest) | |
{ | |
// getPieceType means this function is colorblind (aside from pawns) | |
// if you remove the checks in isLegalMove you could take your own pieces | |
// (aside from pawns, which are hardcoded) | |
int piece = getPieceType(indexBoard(start)); | |
int enemy = getPieceType(indexBoard(dest)); | |
bool validEnemy = isValidPiece(enemy); | |
// coordinates for start and destination | |
int sx = getX(start); | |
int sy = getY(start); | |
int dx = getX(dest); | |
int dy = getY(dest); | |
// differences in the coordinates | |
int diffx = abs(dx - sx); | |
int diffy = abs(dy - sy); | |
if (!isValidPiece(piece)) | |
{ | |
return false; | |
} | |
// pawn moving fixed! | |
else if (piece == TYPE_PAWN) | |
{ | |
// special case for pawns | |
int pawn = indexBoard(start); | |
// this stops them from moving backwards | |
if ((pawn == WPAWN && dy >= sy) || (pawn == BPAWN && dy <= sy)) | |
{ | |
return false; | |
} | |
// pawns moving 1 forward if nothing is there | |
if ((pawn == WPAWN && dy == sy - 1 && dx == sx && !validEnemy) || (pawn == BPAWN && dy == sy + 1 && dx == sx && !validEnemy)) | |
{ | |
return true; | |
} | |
// pawns moving 2 forward on their first move | |
if ((pawn == WPAWN && dy == 4 && sy == 6 && dx == sx && !validEnemy) || (pawn == BPAWN && dy == 3 && sy == 1 && dx == sx && !validEnemy)) | |
{ | |
return true; | |
} | |
if (validEnemy) | |
{ | |
// so we know we're attacking | |
int enemyColor = getPieceColor(indexBoard(dest)); | |
// white pawns attacking | |
if (pawn == WPAWN && dy == sy + 1 && (dx == sx - 1 || dx == sx + 1) && enemyColor == BLACK) | |
{ | |
return true; | |
} | |
// black pawns attacking | |
if (pawn == BPAWN && dy == sy - 1 && (dx == sx - 1 || dx == sx + 1) && enemyColor == WHITE) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
else if (piece == TYPE_KNIGHT) | |
{ | |
// return ((diffx == 1 && diffy == 2) || (diffx == 2 && diffy == 1)); | |
return (diffx && diffy && diffx + diffy == 3); | |
} | |
else if (piece == TYPE_BISHOP) | |
{ | |
// if the coordinate diffs are the same | |
// thats how we know we're going diagonally | |
return (diffx == diffy); | |
} | |
else if (piece == TYPE_ROOK) | |
{ | |
// just as long as it stays the same on one axis | |
// no ai for castling yet | |
return (dy == sy || dx == sx); | |
} | |
else if (piece == TYPE_QUEEN) | |
{ | |
// combine the rook and bishop ai | |
return (dy == sy || dx == sx || diffx == diffy); | |
} | |
else if (piece == TYPE_KING) | |
{ | |
// one square any direction | |
return (diffx + diffy == 1); | |
} | |
// something went wrong | |
return false; | |
} | |
// turn all pawns at the end of board to a piece of their choice | |
void promptQueenPawn(int start, bool tryAgain) | |
{ | |
char convertPiece = get_char(tryAgain ? "convert this pawn to what piece? (q/r/b/n) " : "invalid piece, retry: "); | |
int pieceColor = getPieceColor(start); | |
switch (convertPiece) | |
{ | |
case 'q': | |
return changeBoardByIndex(start, pieceColor == WHITE ? WQUEEN : BQUEEN); | |
case 'r': | |
return changeBoardByIndex(start, pieceColor == WHITE ? WROOK : BROOK); | |
case 'b': | |
return changeBoardByIndex(start, pieceColor == WHITE ? WBISHOP : BBISHOP); | |
case 'n': | |
return changeBoardByIndex(start, pieceColor == WHITE ? WKNIGHT : BKNIGHT); | |
} | |
promptQueenPawn(start, true); | |
} | |
// scan the board before asking | |
void checkForQueenedPawns() | |
{ | |
for (int i = 0; i < 8; i++) | |
{ | |
if (board[i][0] == WPAWN) | |
{ | |
promptQueenPawn(getIndex(i, 0), false); | |
} | |
if (board[i][7] == BPAWN) | |
{ | |
promptQueenPawn(getIndex(i, 7), false); | |
} | |
} | |
} | |
// check if a move can be made | |
// run after getting both start AND dest x and y | |
bool isLegalMove(int start, int dest) | |
{ | |
if (start == dest) | |
{ | |
printf("uhhhh fam u arent movin anywhere\n"); | |
return false; | |
} | |
if (!canPieceSpecificMove(start, dest)) | |
{ | |
printf("this piece (%c) cant move like that!\n", getPiece(start)); | |
return false; | |
} | |
if (getPieceType(indexBoard(start)) != TYPE_KNIGHT && piecesBetween(start, dest)) | |
{ | |
printf("movement obstructed!\n"); | |
return false; | |
} | |
if (getPieceColor(indexBoard(start)) == getPieceColor(indexBoard(dest))) | |
{ | |
printf("you cant capture your own pieces!\n"); | |
return false; | |
} | |
return true; | |
} | |
// like isLegalMove but less expensive and without user error messages | |
// used for move highlighting | |
bool canMoveToSquare(int start, int dest) | |
{ | |
// something iffy with the obstruction detection here | |
bool notObstructed = getPieceType(indexBoard(start)) == TYPE_KNIGHT || !piecesBetween(start, dest); | |
return (start != dest && !isValidPiece(indexBoard(dest)) && canPieceSpecificMove(start, dest) && notObstructed); | |
} | |
// highlight possible moves for piece of a certain index | |
// relatively expensive function, but who cares | |
void moveHighlight(int start) | |
{ | |
// if -1 is entered, turn off highlighting | |
if (start < 0) | |
{ | |
for (int i = 0; i < 8; i++) | |
{ | |
for (int j = 0; j < 8; j++) | |
{ | |
if (board[i][j] == MOVE_HIGHLIGHT) | |
{ | |
board[i][j] = EMPTY; | |
} | |
} | |
} | |
} | |
else if (isValidPiece(indexBoard(start))) | |
{ | |
// check if there is a valid move for every single square | |
// 1 is added to i and j because getIndex is like that | |
for (int i = 0; i < 8; i++) | |
{ | |
for (int j = 0; j < 8; j++) | |
{ | |
if (canMoveToSquare(start, getIndex(i + 1, j + 1))) | |
{ | |
board[i][j] = MOVE_HIGHLIGHT; | |
} | |
} | |
} | |
} | |
} | |
// check if a piece can make any moves whatsoever | |
// used for move pre-selection in validSelectedPiece | |
bool canAnyMovesBeMade(int start) | |
{ | |
if (isValidPiece(indexBoard(start))) | |
{ | |
for (int i = 1; i <= 8; i++) | |
{ | |
for (int j = 1; j <= 8; j++) | |
{ | |
if (canMoveToSquare(start, getIndex(i, j))) | |
{ | |
return true; | |
} | |
} | |
} | |
} | |
return false; | |
} | |
// check if a selected piece is valid | |
// run after getting only *start* x and y | |
bool validSelectedPiece(int start) | |
{ | |
if (!isValidPiece(getPieceType(indexBoard(start)))) | |
{ | |
printf("selected piece does not exist\n"); | |
return false; | |
} | |
if (whoseTurn != getPieceColor(indexBoard(start))) | |
{ | |
printf("you cant move the enemy's pieces!\n"); | |
return false; | |
} | |
if (!canAnyMovesBeMade(start)) | |
{ | |
printf("this piece cant move anywhere!\n"); | |
return false; | |
} | |
return true; | |
} | |
bool movePiece(int start, int dest) | |
{ | |
if (!isLegalMove(start, dest)) | |
{ | |
return false; | |
} | |
changeBoardByIndex(dest, indexBoard(start)); | |
changeBoardByIndex(start, EMPTY); | |
return true; | |
} | |
int movePieceCoords(int sx, int sy, int dx, int dy) | |
{ | |
// IMPORTANT: function is user-friendly | |
// meaning that the values are 1-8 NOT 0-7 | |
return movePiece(getIndex(sx, sy), getIndex(dx, dy)); | |
} | |
void clearBoard(void) | |
{ | |
for (int i = 0; i < 8; i++) | |
{ | |
for (int j = 0; j < 8; j++) | |
{ | |
board[i][j] = EMPTY; | |
} | |
} | |
} | |
void setupBoard(void) | |
{ | |
// board already empty by default - delete this? | |
clearBoard(); | |
movesMade = 0; | |
board[0][0] = BROOK; | |
board[1][0] = BKNIGHT; | |
board[2][0] = BBISHOP; | |
board[3][0] = BQUEEN; | |
board[4][0] = BKING; | |
board[5][0] = BBISHOP; | |
board[6][0] = BKNIGHT; | |
board[7][0] = BROOK; | |
board[0][7] = WROOK; | |
board[1][7] = WKNIGHT; | |
board[2][7] = WBISHOP; | |
board[3][7] = WQUEEN; | |
board[4][7] = WKING; | |
board[5][7] = WBISHOP; | |
board[6][7] = WKNIGHT; | |
board[7][7] = WROOK; | |
// place pawns | |
for (int i = 0; i < 8; i++) | |
{ | |
board[i][1] = BPAWN; | |
board[i][6] = WPAWN; | |
} | |
whoseTurn = WHITE; | |
} | |
void printBoard(void) | |
{ | |
printf("X-----------------X\n"); | |
for (int y = 0; y < 8; y++) | |
{ | |
printf("| "); | |
for (int x = 0; x < 8; x++) | |
{ | |
printf("%c ", getPieceChar(board[x][y])); | |
} | |
printf("|\n"); | |
} | |
printf("X-----------------X\n"); | |
} | |
void promptMove(void) | |
{ | |
printf("%s's turn, do ctrl+c to end game\n", whoseTurn == WHITE ? "white" : "black"); | |
int sx, sy, dx, dy = 0; | |
do | |
{ | |
sx = get_int("x pos of piece: "); | |
} | |
while (sx < 1 || sx > 8); | |
do | |
{ | |
sy = get_int("y pos of piece: "); | |
} | |
while (sy < 1 || sy > 8); | |
// now that we have the start coordinates, check if they're any good | |
if (!validSelectedPiece(getIndex(sx, sy))) | |
{ | |
return; | |
} | |
// turn on move highlighting | |
moveHighlight(getIndex(sx, sy)); | |
printBoard(); | |
do | |
{ | |
dx = get_int("x pos of destination: "); | |
} | |
while (dx < 1 || dx > 8); | |
do | |
{ | |
dy = get_int("y pos of destination: "); | |
} | |
while (dy < 1 || dy > 8); | |
if (movePieceCoords(sx, sy, dx, dy)) | |
{ | |
// only check if its possible for pawns to have moved that far | |
if (movesMade > 10) | |
{ | |
checkForQueenedPawns(); | |
} | |
// increment after the check | |
movesMade++; | |
nextTurn(); | |
} | |
// turn off move highlighting even if move failed | |
moveHighlight(-1); | |
checkForQueenedPawns(); | |
} | |
bool promptQuit(void) | |
{ | |
printf("quit game? (y/n)\n"); | |
char chQuit; | |
do | |
{ | |
chQuit = get_char("quit: "); | |
} | |
while (chQuit != 'y' && chQuit != 'n'); | |
bool bQuit = chQuit == 'y'; | |
if (bQuit) | |
{ | |
gameEnded = true; | |
} | |
return bQuit; | |
} | |
void promptInput(int printQueue) | |
{ | |
switch (printQueue) | |
{ | |
case PRINTQUEUE_QUEENPAWN: | |
promptQueenPawn(1, false); | |
break; | |
case PRINTQUEUE_QUITGAME: | |
promptQuit(); | |
break; | |
default: | |
promptMove(); | |
} | |
// todo: print what went wrong *after* the board is printed | |
printBoard(); | |
} | |
// test/meme function | |
void printBox(char *txt) | |
{ | |
printf("X-"); | |
for (int i = 0, n = strlen(txt); i < n; i++) | |
{ | |
printf("-"); | |
} | |
printf("-X\n"); | |
printf("| "); | |
printf("%s", txt); | |
printf(" |\n"); | |
printf("X-"); | |
for (int i = 0, n = strlen(txt); i < n; i++) | |
{ | |
printf("-"); | |
} | |
printf("-X\n\n"); | |
} | |
#define DEBUG | |
void printStartMessage(void) | |
{ | |
printf("\n"); | |
printf("X-------- CHESS --------X\n"); | |
printf("| UPPERCASE -> WHITE |\n"); | |
printf("| LOWERCASE -> BLACK |\n"); | |
printf("| BOARD COORDINATES: |\n"); | |
printf("| 1 ---> 8x |\n"); | |
printf("| | |\n"); | |
printf("| | |\n"); | |
printf("| V |\n"); | |
printf("| 8y |\n"); | |
printf("| HAVE FUN |\n"); | |
printf("X---- BY NICK BERVAR ---X\n"); | |
printf("\n"); | |
#ifdef DEBUG | |
printBox("WARNING: VERY BUGGY"); | |
#endif | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
*steals*