Skip to content

Instantly share code, notes, and snippets.

@nbervar21
Last active December 6, 2018 23:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nbervar21/ddf7741a2755d7a4039a4a5066c1077c to your computer and use it in GitHub Desktop.
Save nbervar21/ddf7741a2755d7a4039a4a5066c1077c to your computer and use it in GitHub Desktop.
chess.c - semi-working prototype
// 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
}
@irefl
Copy link

irefl commented Nov 19, 2018

*steals*

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment