Created
January 22, 2022 14:53
-
-
Save Muaath5/dd1ce77e579832b5cb222fa3e117c6b8 to your computer and use it in GitHub Desktop.
C++ chess game in console, not completed needs kings checks
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
// 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