Skip to content

Instantly share code, notes, and snippets.

@Muaath5
Created January 22, 2022 14:53
Show Gist options
  • Save Muaath5/dd1ce77e579832b5cb222fa3e117c6b8 to your computer and use it in GitHub Desktop.
Save Muaath5/dd1ce77e579832b5cb222fa3e117c6b8 to your computer and use it in GitHub Desktop.
C++ chess game in console, not completed needs kings checks
// Copyright 2022
// Written by: mdubaisi, Muaath_5
// A C++ console app to play chess with a friend offline
#include <bits/stdc++.h>
#define endl '\n'
#define E8 Pos('E', 8)
#define E1 Pos('E', 1)
// Outputs x and returns false
#define error(x) { cout << "ERROR: " << x << endl; return false; }
#define ask(x, var) cout << x; cin >> var;
using namespace std;
// BLACK=small, WHITE=capital
const int BOARD_SIZE = 8, WHITE = 1, BLACK = -1;
const char EMPTY = '.';
const char W_PAWN = 'P', W_ROOK = 'R', W_KNIGHT = 'N', W_BISHOP = 'B', W_QUEEN = 'Q', W_KING = 'K';
const char B_PAWN = 'p', B_ROOK = 'r', B_KNIGHT = 'n', B_BISHOP = 'b', B_QUEEN = 'q', B_KING = 'k';
struct Pos {
char col;
int row;
Pos() : row(1), col('A') {}
Pos(char c, int r) : row(r), col(c) {}
bool operator==(const Pos& r)
{
return (this->col == r.col && this->row == r.row);
}
};
struct Piece {
char type;
int color = 0, moved = 0;
Piece(char t, int c) : type(t), color(get_color()) {}
Piece(char t) : type(t), color(get_color()) {}
Piece() : type(EMPTY), color(0) {}
int get_color()
{
return (type >= 'a' ? BLACK : WHITE);
}
};
struct Board {
protected:
// grid[0][0] == A1 == bottom-left
Piece board[BOARD_SIZE][BOARD_SIZE];
// King positions, used for getting checkmates
Pos whiteKing = E1, blackKing = E8;
int turn = WHITE;
bool checkmate = false;
inline void move_piece_internal(Pos from, Pos to)
{
auto piece = get_piece(from);
piece.moved++;
board[to.col - 'A'][to.row] = piece;
board[from.col - 'A'][from.row] = Piece(); // Convert previous place to null
history = history + from.col + (char)(from.row + '1') + "=>" + to.col + (char)(to.row + '1') + ' ';
if (piece.type == B_KING)
blackKing = to;
if (piece.type == W_KING)
whiteKing = to;
next_turn();
}
inline void next_turn()
{
turn *= -1;
}
// For input validation, must be zero based
bool is_valid_pos(Pos pos)
{
int x = pos.col - 'A';
int y = pos.row;
if (x < 0 || x >= BOARD_SIZE || y < 0 || y > BOARD_SIZE)
return false;
return true;
}
inline bool will_remove_check(Pos king, Pos from, Pos to)
{
Board b;
b.import_board(board);
b.move_piece(from, to);
return !b.is_checked(king);
}
public:
// Stores all previous moves
string history = "";
bool enable_history = true;
inline Piece& get_piece(Pos pos)
{
return board[pos.col - 'A'][pos.row];
}
inline string get_turn_name()
{
return (turn == WHITE ? "White" : "Black");
}
inline void import_board(Piece newBoard[BOARD_SIZE][BOARD_SIZE])
{
memcpy(newBoard, board, sizeof(newBoard));
}
void initialize(GameMode mode) {
const int blackRow = BOARD_SIZE - 1;
const int whiteRow = 0;
for (int x = 0; x < BOARD_SIZE; x++) {
board[x][whiteRow+1] = Piece(W_PAWN);
board[x][blackRow-1] = Piece(B_PAWN);
}
board[0][whiteRow] = Piece(W_ROOK); board[BOARD_SIZE - 1][whiteRow] = Piece(W_ROOK);
board[1][whiteRow] = Piece(W_KNIGHT); board[BOARD_SIZE - 2][whiteRow] = Piece(W_KNIGHT);
board[2][whiteRow] = Piece(W_BISHOP); board[BOARD_SIZE - 3][whiteRow] = Piece(W_BISHOP);
board[3][whiteRow] = Piece(W_QUEEN); board[BOARD_SIZE - 4][whiteRow] = Piece(W_KING);
// Placing in the two sides
board[0][blackRow] = Piece(B_ROOK); board[BOARD_SIZE - 1][blackRow] = Piece(B_ROOK);
board[1][blackRow] = Piece(B_KNIGHT); board[BOARD_SIZE - 2][blackRow] = Piece(B_KNIGHT);
board[2][blackRow] = Piece(B_BISHOP); board[BOARD_SIZE - 3][blackRow] = Piece(B_BISHOP);
board[3][blackRow] = Piece(B_QUEEN); board[BOARD_SIZE - 4][blackRow] = Piece(B_KING);
// 360 mode
if (mode == GameMode::Mode360)
{
vector<Piece> row;
for (int i = 0; i < BOARD_SIZE; i++)
{
row.push_back(board[i][whiteRow]);
}
std::random_shuffle(row.begin(), row.end());
for (int i = 0; i < BOARD_SIZE; i++)
{
board[i][whiteRow] = row[i];
board[i][blackRow] = row[i];
board[i][blackRow].color = BLACK;
board[i][blackRow].type = (char)tolower(board[i][blackRow].type);
}
}
}
// Prints the board
void print_board() {
if (enable_history && history != "")
{
cout << "History: " << history << endl << endl;
}
for (int i = BOARD_SIZE-1; i >= 0; i--) {
cout << (i+1) << " ";
for (int j = 0; j < BOARD_SIZE; j++) {
//if (tolower(board[j][i].type) == B_KING && is_checked(Pos(j+'A', i)))
//system("Color 1A");
#ifdef DEBUG
cout << (board[j][i].type == EMPTY ? (board[j][i].color == BLACK ? '#' : board[j][i].color == WHITE ? '*' : EMPTY) : board[j][i].type) << ' ';
#else
cout << board[j][i].type << ' ';
#endif
}
cout << endl;
}
cout << endl << " ";
for (int i = 0; i < BOARD_SIZE; i++)
cout << " " << (char)('A'+i);
cout << endl;
}
// Only for pawns
void promote(int x, int y) {
char promotedPawn;
cout << "promte pawn to (r, n, b, q): ";
cin >> promotedPawn;
while (promotedPawn != 'r' && promotedPawn != 'n' && promotedPawn != 'b' && promotedPawn != 'q') {
cout << "invalid input!" << endl;
cout << "promte pawn to(r, n, b, q): ";
cin >> promotedPawn;
}
switch (promotedPawn) {
case 'r':
board[y][x] = y == 1 ? Piece(W_ROOK, WHITE) : Piece(B_ROOK, BLACK);
break;
case 'n':
board[y][x] = y == 1 ? Piece(W_KNIGHT, WHITE) : Piece(B_KNIGHT, BLACK);
break;
case 'b':
board[y][x] = y == 1 ? Piece(W_BISHOP, WHITE) : Piece(B_BISHOP, BLACK);
break;
case 'q':
board[y][x] = y == 1 ? Piece(W_QUEEN, WHITE) : Piece(B_QUEEN, BLACK);
break;
}
}
// This method usually used with kings
// [UNCOMPLETED]
bool is_checked(Pos pos)
{
bool result = false;
int eniemy = get_piece(pos).color * -1;
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j].color = eniemy)
{
switch ((char)tolower(board[i][j].type))
{
case B_ROOK:
if (j == pos.row)
{
int r = j;
result = true;
while (r != pos.col-'A')
{
if (board[i][r].type != EMPTY)
result = false;
(pos.col - 'A' > r ? r++ : r--);
}
}
if (i == pos.col-'A')
{
int c = i;
result = true;
while (c != pos.row)
{
if (board[i][c].type != EMPTY)
{
result = false;
break;
}
(pos.row > c ? c++ : c--);
}
}
break;
}
}
}
}
return result;
}
void undo_last_move()
{
// Here we may process the history, or save last board only
}
bool move_piece(Pos from, Pos to) {
// Input validation
if (!is_valid_pos(from) || !is_valid_pos(to))
error("outside of the board!");
if (from == to)
error("can't move to same place!");
// {from} & {to} must be zero based
from.row--, to.row--;
int from_x = from.col - 'A', from_y = from.row;
int to_x = to.col - 'A', to_y = to.row;
Piece& piece = get_piece(from);
Piece& newPlace = get_piece(to);
// Checks if it was correct turn
if (piece.color == 0)
error("the place you choosed is empty!");
if (piece.color != turn)
error("you can only move your own pieces!");
if (newPlace.color == turn)
error("can't eat your pieces!");
Pos king = (turn == BLACK ? blackKing : whiteKing);
if (is_checked(king) && false)
{
if (!will_remove_check(king, from, to))
error("you must move to remove the check on your king!");
}
switch (piece.type) {
case W_PAWN:
if ((!piece.moved && from_x == to_x && to_y == from_y + 2) || (from_x == to_x && to_y == from_y + 1) ||
(abs(from_x - to_x) == 1 && to_y == from_y - 1 && (board[to_y][to_x].color == BLACK || (from_y == 3 && board[from_y][to_x].color == BLACK && board[from_y][to_x].moved == 1)))) {
if (to_y == 0)
promote(from_x, from_y);
if (from_y == 3 && board[from_y][to_x].color == BLACK && board[from_y][to_x].moved == 1)
board[from_y][to_x] = Piece();
move_piece_internal(from, to);
return true;
}
return false;
case B_PAWN:
if ((!piece.moved && from_x == to_x && to_y == from_y - 2) || (from_x == to_x && to_y == from_y - 1) ||
(abs(from_x - to_x) == 1 && to_y == from_y + 1 && (board[to_y][to_x].color == WHITE || (from_y == 4 && board[from_y][to_x].color == WHITE && board[from_y][to_x].moved == 1)))) {
if (to_y == BOARD_SIZE - 1)
promote(from_x, from_y);
if (from_y == 4 && board[from_y][to_x].color == WHITE && board[from_y][to_x].moved == 1)
board[from_y][to_x] = Piece();
move_piece_internal(from, to);
return true;
}
return false;
case W_KNIGHT:
if ((abs(from_x - to_x) == 2 && abs(from_y - to_y) == 1) || (abs(from_x - to_x) == 1 && abs(from_y - to_y) == 2)) {
move_piece_internal(from, to);
return true;
}
return false;
case B_KNIGHT:
if ((abs(from_x - to_x) == 2 && abs(from_y - to_y) == 1) || (abs(from_x - to_x) == 1 && abs(from_y - to_y) == 2)) {
move_piece_internal(from, to);
return true;
}
return false;
case W_BISHOP:
if (abs(from_x - to_x) == abs(from_y - to_y)) {
int x = from_x, y = from_y;
x += from_x < to_x ? 1 : -1;
y += from_y < to_y ? 1 : -1;
while (x != to_x && y != to_y) {
// If there is someone in between already
if (board[x][y].type != EMPTY)
return false;
x += from_x < to_x ? 1 : -1;
y += from_y < to_y ? 1 : -1;
}
move_piece_internal(from, to);
return true;
}
return false;
case B_BISHOP:
if (abs(from_x - to_x) == abs(from_y - to_y)) {
int x = from_x, y = from_y;
x += from_x < to_x ? 1 : -1;
y += from_y < to_y ? 1 : -1;
while (x != to_x && y != to_y) {
if (board[x][y].type != EMPTY)
return false;
x += from_x < to_x ? 1 : -1;
y += from_y < to_y ? 1 : -1;
}
move_piece_internal(from, to);
return true;
}
return false;
case W_ROOK:
if ((from_x == to_x && from_y != to_y) || (from_y == to_y && from_x != to_x)) {
int x = from_x, y = from_y;
x += from_x < to_x ? 1 : from_x == to_x ? 0 : -1;
y += from_y < to_y ? 1 : from_y == to_y ? 0 : -1;
while (x != to_x || y != to_y) {
if (board[x][y].type != EMPTY)
return false;
x += from_x < to_x ? 1 : from_x == to_x ? 0 : -1;
y += from_y < to_y ? 1 : from_y == to_y ? 0 : -1;
}
move_piece_internal(from, to);
return true;
}
return false;
case B_ROOK:
if ((from_x == to_x && from_y != to_y) || (from_y == to_y && from_x != to_x)) {
int x = from_x, y = from_y;
x += from_x < to_x ? 1 : from_x == to_x ? 0 : -1;
y += from_y < to_y ? 1 : from_y == to_y ? 0 : -1;
while (x != to_x || y != to_y) {
if (board[x][y].type != EMPTY)
return false;
x += from_x < to_x ? 1 : from_x == to_x ? 0 : -1;
y += from_y < to_y ? 1 : from_y == to_y ? 0 : -1;
}
move_piece_internal(from, to);
return true;
}
return false;
case W_QUEEN:
if ((from_x == to_x && from_y != to_y) || (from_y == to_y && from_x != to_x)) {
int x = from_x, y = from_y;
x += from_x < to_x ? 1 : from_x == to_x ? 0 : -1;
y += from_y < to_y ? 1 : from_y == to_y ? 0 : -1;
while (x != to_x || y != to_y) {
if (board[x][y].type != EMPTY)
return false;
x += from_x < to_x ? 1 : from_x == to_x ? 0 : -1;
y += from_y < to_y ? 1 : from_y == to_y ? 0 : -1;
}
move_piece_internal(from, to);
return true;
}
else if (abs(from_x - to_x) == abs(from_y - to_y)) {
int x = from_x, y = from_y;
x += from_x < to_x ? 1 : -1;
y += from_y < to_y ? 1 : -1;
while (x != to_x && y != to_y) {
if (board[x][y].type != EMPTY)
return false;
x += from_x < to_x ? 1 : -1;
y += from_y < to_y ? 1 : -1;
}
move_piece_internal(from, to);
return true;
}
return false;
case B_QUEEN:
if ((from_x == to_x && from_y != to_y) || (from_y == to_y && from_x != to_x)) {
int x = from_x, y = from_y;
x += from_x < to_x ? 1 : from_x == to_x ? 0 : -1;
y += from_y < to_y ? 1 : from_y == to_y ? 0 : -1;
while (x != to_x || y != to_y) {
if (board[x][y].type != EMPTY)
return false;
x += from_x < to_x ? 1 : from_x == to_x ? 0 : -1;
y += from_y < to_y ? 1 : from_y == to_y ? 0 : -1;
}
move_piece_internal(from, to);
return true;
}
else if (abs(from_x - to_x) == abs(from_y - to_y)) {
int x = from_x, y = from_y;
x += from_x < to_x ? 1 : -1;
y += from_y < to_y ? 1 : -1;
while (x != to_x && y != to_y) {
if (board[x][y].type != EMPTY)
return false;
x += from_x < to_x ? 1 : -1;
y += from_y < to_y ? 1 : -1;
}
move_piece_internal(from, to);
return true;
}
return false;
}
if (is_checked(whiteKing))
{
cout << "White king is checked!" << endl;
}
if (is_checked(blackKing))
{
cout << "Black king is checked!" << endl;
}
}
inline bool checkmated()
{
return checkmate && false;
}
};
inline void clear_console()
{
#ifdef _WIN32 || _WIN64
system("cls");
#endif
}
enum GameMode { Normal=0, Mode360 };
enum HistoryMode { Positions = 0, Mode360 };
int main()
{
Board board;
string modeStr;
GameMode mode = GameMode::Normal;
ask("Enter game mode: ", modeStr);
if (modeStr == "360" || modeStr == "random" || modeStr == "r" || modeStr == "rand")
mode = GameMode::Mode360;
board.initialize(mode);
while (!board.checkmated())
{
board.print_board();
cout << endl << "Current turn is for " << board.get_turn_name() << endl;
Pos from, to;
ask("Enter a move in format (E6): ", from.col >> from.row >> to.col >> to.row);
from.col = toupper(from.col), to.col = toupper(to.col);
clear_console();
if (!board.move_piece(from, to))
{
cout << "Invalid move!" << endl;
continue;
}
}
cout << board.get_turn_name() << " won!" << endl;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment