Простая консольная шахматная игра C ++

Поэтому я реализовал простую консольную автономную игру «игрок против игрока в шахматы» на C ++ 17. Я знаю, что есть несколько специфических для шахмат функций, которые я еще не добавил, но основной игровой процесс работает отлично. Это довольно большой проект для обзора, но я был бы очень признателен за любые советы. Код разделен на 3 класса:

  1. Cchess_game отвечает за основной игровой цикл.
  2. Cchess_board это, безусловно, самый большой класс, поскольку он отвечает за все, что связано с игровым полем, то есть за всю логику игры.
  3. Cplayer честно говоря, существует только для возможных расширений в будущем, таких как таймеры для конкретных игроков. На данный момент он управляет только вводом пользователя.

Дополнительно есть заголовок game_constants.h, который включает глобальные константы, используемые в игре. Это код:

source.cpp

#include "Cchess_game.h"

int main()
{
    chess::Cchess_game chess;
    int32_t winner = chess.play();

    std::cout << "nPlayer " << winner << " won!n";

    return 0;
} 

Cchess_game.h

#ifndef CCHESS_GAME_H
#define CCHESS_GAME_H

#include "Cchess_board.h"
#include "Cplayer.h"

namespace chess
{
    class Cchess_game
    {
    public:

        Cchess_game() : m_player1{ Cplayer() }, m_player2{ Cplayer() }, m_board{ Cchess_board() }, m_player_switch{ true }{};
        game_result play(); // play single game of chess and return 1,2 for winner and 0 for draw

    private:

        Cplayer m_player1;
        Cplayer m_player2;
        Cchess_board m_board;
        bool m_player_switch;

    };
#endif
}

Cchess_game.cpp

#include "Cchess_game.h"

namespace chess
{
    game_result Cchess_game::play()
    {
        game_result result;
        std::pair<int32_t, int32_t> from_coords;
        std::pair<int32_t, int32_t> to_coords;
        Cplayer* player = nullptr;

        do
        {
            // get pointer to current player to avoid repetition
            if (m_player_switch)
            {
                player = &m_player1;
            }
            else
            {
                player = &m_player2;
            }

            // display current game state
            m_board.display();
            std::cout << "nn";

            std::cout << "Player " << !m_player_switch + 1 << "'s turn: nn";

            // get move info until move is valid
            do
            {
                std::cout << "Please enter the position of the figure you want to move: ";
                from_coords = player->get_pos();
                std::cout << "Please enter the position you want your figure to move to: ";
                to_coords = player->get_pos();

            } while (!m_board.is_valid(from_coords, to_coords, m_player_switch));

            // execute move and update board
            m_board.move(from_coords, to_coords);

            // switch player
            m_player_switch = !m_player_switch;

            // clear console
            std::cout << std::flush;
            system("CLS");

        } while ((result = m_board.game_state()) == still_playing);

        return result;
    }
}

Cchess_board.h

#ifndef CCHESS_BOARD_H
#define CCHESS_BOARD_H

#include "game_constants.h"
#include <array>
#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <stdlib.h>

namespace chess
{
    class Cchess_board
    {
    public:

        Cchess_board(); // initialise default chess board
        game_result game_state() const; // return current game state
        void display() const; // display current board in console
        bool is_valid(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to, bool player_switch) const; // return true if move is valid
        void move(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to); // execute move

    private:

        bool find_figure(chess_board_state figure) const; // return true if figure is in board
        bool is_relative_field(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to, int32_t x_shift, int32_t y_shift) const; // return true if position "to" is relative to "from" with shifts
        bool is_mate(std::pair<int32_t, int32_t> field, std::pair<chess_board_state, chess_board_state> figure_range) const; // return true if field is mate
        bool check_linear_move(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to) const; // return true if move is linear and valid
        bool check_diagonal_move(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to) const; // return true if move is diagonal and valid

        std::array<std::array<chess_board_state, chess_board_width>, chess_board_height> m_board;
        int32_t m_invert_y; // help value to distract from to invert index

    };
#endif
}

Cchess_board.cpp

#include "Cchess_board.h"

namespace chess
{
    Cchess_board::Cchess_board()  
    {
        m_board = std::array<std::array<chess_board_state, chess_board_width>, chess_board_height>{ {
            { p2_rook, p2_knight, p2_bishop, p2_queen, p2_king, p2_bishop, p2_knight, p2_rook },
            { p2_pawn, p2_pawn,   p2_pawn,   p2_pawn,  p2_pawn, p2_pawn,   p2_pawn,   p2_pawn },
            { empty,   empty,     empty,     empty,    empty,   empty,     empty,     empty },
            { empty,   empty,     empty,     empty,    empty,   empty,     empty,     empty },
            { empty,   empty,     empty,     empty,    empty,   empty,     empty,     empty },
            { empty,   empty,     empty,     empty,    empty,   empty,     empty,     empty },
            { p1_pawn, p1_pawn,   p1_pawn,   p1_pawn,  p1_pawn, p1_pawn,   p1_pawn,   p1_pawn },
            { p1_rook, p1_knight, p1_bishop, p1_queen, p1_king, p1_bishop, p1_knight, p1_rook }
            }};

        m_invert_y = chess_board_height - 1;
    }

    game_result Cchess_board::game_state() const
    {
        // check for p1 win
        if (!find_figure(p2_king))
        {
            return p1_win;
        }

        // check for p2 win
        if (!find_figure(p1_king))
        {
            return p2_win;
        }

        return still_playing;
    }

    void Cchess_board::display() const
    {
        std::unordered_map<chess_board_state, std::string> figure_to_string = {
        {empty,"empty       "}, {p1_pawn,"white_pawn  "}, {p1_knight,"white_knight"}, {p1_bishop,"white_bishop"}, {p1_rook,"white_rook  "}, {p1_queen,"white_queen "}, {p1_king,"white_king  "},
                                {p2_pawn,"black_pawn  "}, {p2_knight,"black_knight"}, {p2_bishop,"black_bishop"}, {p2_rook,"black_rook  "}, {p2_queen,"black_queen "}, {p2_king,"black_king  "}
        };

        std::array<int32_t, chess_board_height> y_axis_descr = { 8, 7, 6, 5, 4, 3, 2, 1 };

        // print x-axis description
        std::cout << "  a             b             c             d             e             f             g             hn";

        // print line by line
        for (int32_t i = 0; i < chess_board_height; i++)
        {
            std::cout << static_cast<int>(y_axis_descr[i]) << " ";
            for (chess_board_state state : m_board[i])
            {
                std::cout << figure_to_string[state].c_str() << "  ";
            }
            std::cout << "n";
        }

    }

    bool Cchess_board::is_valid(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to, bool player_switch) const
    {
        int32_t from_x = from.first;
        int32_t from_y = m_invert_y - from.second;
        int32_t to_x = to.first;
        int32_t to_y = m_invert_y - to.second;

        std::pair<chess_board_state, chess_board_state> figure_range;

        // change range of own figures depending on player switch
        if (player_switch)
        {
            figure_range.first = p1_pawn;
            figure_range.second = p1_king;
        }
        else
        {
            figure_range.first = p2_pawn;
            figure_range.second = p2_king;
        }
        
        // check if move is valid based on figure
        // switch statement was not possible, because it requires compile-time constants

        // pawn
        if (m_board[from_y][from_x] == figure_range.first)
        {      
            if ((is_relative_field(from, to,  0, -1) && m_board[to_y][to_x] == empty && player_switch)                                  || // 1 forward - player 1
                (is_relative_field(from, to,  0, -2) && m_board[to_y][to_x] == empty && from_y == 6                  && player_switch)  || // 2 forward from start - player 1
                (is_relative_field(from, to, -1, -1) && !is_mate(to, figure_range)   && m_board[to_y][to_x] != empty && player_switch)  || // attack diagonal - player 1
                (is_relative_field(from, to,  1, -1) && !is_mate(to, figure_range)   && m_board[to_y][to_x] != empty && player_switch)  || // attack diagonal - player 1

                (is_relative_field(from, to,  0,  1) && m_board[to_y][to_x] == empty && !player_switch)                                 || // 1 forward - player 2
                (is_relative_field(from, to,  0,  2) && m_board[to_y][to_x] == empty && from_y == 1                  && !player_switch) || // 2 forward from start - player 2
                (is_relative_field(from, to, -1,  1) && !is_mate(to, figure_range)   && m_board[to_y][to_x] != empty && !player_switch) || // attack diagonal - player 2
                (is_relative_field(from, to,  1,  1) && !is_mate(to, figure_range)   && m_board[to_y][to_x] != empty && !player_switch))   // attack diagonal - player 2 
            {
                return true;
            }
        }
        // knight
        else if (m_board[from_y][from_x] == figure_range.first + 1)
        {
            if ((is_relative_field(from, to, -1, -2) && !is_mate(to, figure_range)) || // 2 up, 1 left
                (is_relative_field(from, to,  1, -2) && !is_mate(to, figure_range)) || // 2 up, 1 right
                (is_relative_field(from, to, -1,  2) && !is_mate(to, figure_range)) || // 2 down, 1 left
                (is_relative_field(from, to,  1,  2) && !is_mate(to, figure_range)) || // 2 down, 1 right
                (is_relative_field(from, to,  2, -1) && !is_mate(to, figure_range)) || // 2 right, 1 up
                (is_relative_field(from, to,  2,  1) && !is_mate(to, figure_range)) || // 2 right, 1 down
                (is_relative_field(from, to, -2, -1) && !is_mate(to, figure_range)) || // 2 left, 1 up
                (is_relative_field(from, to, -2,  1) && !is_mate(to, figure_range)))   // 2 left, 1 down
            {
                return true;
            }
        }
        // bishop
        else if (m_board[from_y][from_x] == figure_range.first + 2)
        {
            if (check_diagonal_move(from, to) && !is_mate(to, figure_range))
            {
                return true;
            }
        }
        // rook
        else if (m_board[from_y][from_x] == figure_range.first + 3)
        {
            if (check_linear_move(from, to) && !is_mate(to, figure_range))
            {
                return true;
            }
        }
        // queen
        else if (m_board[from_y][from_x] == figure_range.first + 4)
        {
            if ((check_diagonal_move(from, to) || check_linear_move(from, to)) && !is_mate(to, figure_range))
            {
                return true;
            }
        }
        // king
        else if (m_board[from_y][from_x] == figure_range.first + 5)
        {

            if ((is_relative_field(from, to,  0, -1) && !is_mate(to, figure_range)) || // 1 up
                (is_relative_field(from, to,  1, -1) && !is_mate(to, figure_range)) || // 1 up, 1 right
                (is_relative_field(from, to, -1, -1) && !is_mate(to, figure_range)) || // 1 up, 1 left
                (is_relative_field(from, to,  1,  0) && !is_mate(to, figure_range)) || // 1 right
                (is_relative_field(from, to, -1,  0) && !is_mate(to, figure_range)) || // 1 left
                (is_relative_field(from, to,  0,  1) && !is_mate(to, figure_range)) || // 1 down
                (is_relative_field(from, to,  1,  1) && !is_mate(to, figure_range)) || // 1 down, 1 right
                (is_relative_field(from, to, -1,  1) && !is_mate(to, figure_range)))   // 1 down, 1 left
            {
                return true;
            }
        }
        else
        {
            std::cout << "nThis is not one of your figures.nn";
            return false;
        }

        std::cout << "nNot a valid move.nn";

        return false;
    }

    bool Cchess_board::is_relative_field(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to, int32_t x_shift, int32_t y_shift) const
    {
        return (m_invert_y - from.second + y_shift == m_invert_y - to.second &&
                from.first               + x_shift == to.first              );
    }

    bool Cchess_board::is_mate(std::pair<int32_t, int32_t> field, std::pair<chess_board_state, chess_board_state> figure_range) const
    {
        return !(m_board[m_invert_y - field.second][field.first] < figure_range.first ||
                 m_board[m_invert_y - field.second][field.first] > figure_range.second);
    }

    bool Cchess_board::check_linear_move(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to) const
    {
        // is linear?
        if (from.first == to.first || m_invert_y - from.second == m_invert_y - to.second)
        {
            int32_t temp_x = from.first;
            int32_t temp_y = m_invert_y - from.second;

            // is valid?
            // x-axis case
            while (temp_x != to.first)
            {
                if (temp_x > to.first)
                {
                    temp_x--;
                }
                else
                {
                    temp_x++;
                }

                if (m_board[m_invert_y - from.second][temp_x] != empty && temp_x != to.first)
                {
                    return false;
                }
            }
            

            // y-axis case
            while (temp_y != m_invert_y - to.second)
            {
                if (temp_y > m_invert_y - to.second)
                {
                    temp_y--;
                }
                else
                {
                    temp_y++;
                }

                if (m_board[temp_y][from.first] != empty && temp_y != m_invert_y - to.second)
                {
                    return false;
                }
            }

            return true;
        }

        return false;
    }

    bool Cchess_board::check_diagonal_move(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to) const
    {
        int32_t temp_x = from.first;
        int32_t temp_y = m_invert_y - from.second;

        // is diagonal?
        if (abs((m_invert_y - from.second) - (m_invert_y - to.second)) == abs(from.first - to.first))
        {
            // is valid?
            while (temp_x != to.first)
            {
                if (temp_x > to.first)
                {
                    temp_x--;
                }
                else
                {
                    temp_x++;
                }

                if (temp_y > m_invert_y - to.second)
                {
                    temp_y--;
                }
                else
                {
                    temp_y++;
                }

                if (m_board[temp_y][temp_x] != empty && temp_x != to.first)
                {
                    return false;
                }
            }

            return true;
        }

        return false;
    }

    void Cchess_board::move(std::pair<int32_t, int32_t> from, std::pair<int32_t, int32_t> to)
    {
        // assign figure to new position
        m_board[m_invert_y - to.second][to.first] = m_board[m_invert_y - from.second][from.first];

        // clear old position
        m_board[m_invert_y - from.second][from.first] = empty;
    }

    bool Cchess_board::find_figure(chess_board_state figure) const
    {
        for (auto& row : m_board)
        {
            if (std::find(row.begin(), row.end(), figure) != row.end())
            {
                return true;
            }
        }
        return false;
    }
}

Cplayer.h

#ifndef CPLAYER_H
#define CPLAYER_H

#include "game_constants.h"
#include <utility>
#include <string>
#include <iostream>
#include <ctype.h>
#include <unordered_map>

namespace chess
{
    class Cplayer
    {
    public:

        std::pair<int32_t, int32_t> get_pos() const;

    };
#endif
}

Cplayer.cpp

#include "Cplayer.h"

namespace chess
{
    std::pair<int32_t, int32_t> Cplayer::get_pos() const
    {
        // dictionary to convert x-axis char to integer index
        std::unordered_map<char, int32_t> convert_to_int({ {'a',0}, {'b',1}, {'c',2}, {'d',3}, {'e',4}, {'f',5}, {'g',6}, {'h',7} });

        std::string pos;

        // get valid user input
        for (;;)
        {
            std::cin >> pos;
            // check for
            //  size               a-h                                                    <= 8               >= 1               
            if (pos.size() == 2 && convert_to_int.find(pos[0]) != convert_to_int.end() && pos[1] - '0' <= 8 && pos[1] - '0' >= 1)
            {
                return { convert_to_int[pos[0]], pos[1] - '0' - 1 };
            }
            std::cout << "nInvalid input format.nn";
        }
    }
}

game_constants.h

#ifndef GAME_CONSTANTS_H
#define GAME_CONSTANTS_H

#include <stdint.h>

constexpr int32_t chess_board_height = 8;
constexpr int32_t chess_board_width = 8;

enum chess_board_state
{
    empty = 0,

    p1_pawn = 1, 
    p1_knight,
    p1_bishop,
    p1_rook,
    p1_queen,
    p1_king,

    p2_pawn = 11,
    p2_knight,
    p2_bishop,
    p2_rook,
    p2_queen,
    p2_king,
};

enum game_result
{
    draw = 0, p1_win, p2_win, still_playing
};

#endif

Я был бы особенно признателен за отзывы о Cchess_board::is_valid так как я действительно не удовлетворен своим решением.

1 ответ
1

дизайн

У вас есть две большие обязанности в одном классе (доске).

  1. Зачем плате нужно знать, как себя отображать.
    Было бы неплохо внедрить класс отображения в плату во время строительства.
    Это позволит в будущем усовершенствовать интерфейс без модификации платы.
  • Разделение платы и дисплея позволяет лучше разделить логику и, следовательно, проводить тестирование.
  1. Вход игрока не обязательно должен быть частью доски.
    Было бы неплохо добавить объекты игроков в доску, что позволит вам специализироваться позже.
  • Разделение доски и игрока позволяет лучше разделить логику и, таким образом, провести тестирование.
  • Это сделает добавление AI тривиальным (после того, как вы напишете AI player).

Почему бы не использовать объектно-ориентированный дизайн для деталей. Вы могли бы так же легко написать эту реализацию на C.

В целом я считаю, что связь в коде очень сильная.

Проверка кода

Пространство имен chess подразумевает, что тогда вам не нужно добавлять `шахматы ко всему в этом пространстве имен (теперь мы знаем, что все связано с шахматами).

    chess::Cchess_game chess;
    ^^^^^   ^^^^^            OK. Its chesss we get it.

Почему C впереди здесь. Вы делаете обратную полировку названий типов? C=>class. Это был хороший стиль 30 лет назад. Сейчас это считается излишним. Я не собираюсь говорить плохо, но я думаю, что это ужасно.


Здесь вы меняете тип победителя:

    int32_t winner = chess.play();

    // The interface to `plat()` is
    game_result play();

Почему ты не получаешь game_result Вот?


Было бы неплохо с более понятным для человека результатом «Белый» или «Черный» или именем игрока?

    std::cout << "nPlayer " << winner << " won!n";

Резервный:

    return 0;

Не требуется в C ++. Большинство людей возвращают здесь значение только в конце, если есть возможность неудачи (и, следовательно, может быть другой результат). Если я увижу return 0 в конце я начинаю искать случаи отказа в main().


Ничего из этого не требуется.

        Cchess_game() : m_player1{ Cplayer() }, m_player2{ Cplayer() }, m_board{ Cchess_board() }, m_player_switch{ true }{};

Одно определение переменной на строку, пожалуйста.

        Cchess_game() 
            : m_player1{ Cplayer() }     // Default Create temporary object to initialize member!!
            , m_player2{ Cplayer() }
            , m_board{ Cchess_board() }
            , m_player_switch{ true }
        {};  // Now I can see the un-needed ';' at the end.

Нет приза за использование наименьшего количества линий. Все дело в том, чтобы сделать это как можно более читаемым и понятным для следующего человека, который придет, чтобы исправить это (которым, вероятно, будете вы). Сэкономьте время в будущем, уделив время сейчас.

Конструкторы по умолчанию для каждого члена позаботятся об этом. Лично я бы изменил это так, чтобы игра брала ссылки на игроков и доску, а затем передавала эти ссылки в конструктор. Таким образом мой main выглядел бы так:

 int main()
 {
     Chess::PlayerHuman   player1;
     Chess::PlayerAI      player2;
     Chess::Board         board;
     Chess::Display       display(board);

     Chess::Game          game(player1, player2, board, display);

     game.play();
}

Комментарии противоречат коду:

        game_result play(); // play single game of chess and return 1,2 for winner and 0 for draw

Исправляет ли сопровождающий код в соответствии с комментарием или исправляет комментарий. Дополнительное время. Вот почему плохие комментарии хуже, чем нулевые. Объясните в комментариях, почему вы что-то делаете, а не как (код делает как).

Хотя перечисление можно преобразовать в int, это не то же самое, что возвращение int.


Это похоже на ОШИБКУ:

    };
#endif
}

Опасно иметь неинициализированные переменные.

        game_result result;

Эти переменные не используются до следующего блока области видимости.

        std::pair<int32_t, int32_t> from_coords;
        std::pair<int32_t, int32_t> to_coords;
        Cplayer* player = nullptr;

Объявляемые переменные максимально приближены к точке использования.


Это ужасная именованная переменная (m_player_switch).

            // get pointer to current player to avoid repetition
            if (m_player_switch)
            {
                player = &m_player1;
            }
            else
            {
                player = &m_player2;
            }

Дело не в переключении игрока. Если это правда, мы используем первого игрока. Если это ложь, мы используем второго игрока. Почему бы не назвать это идентификатором игрока?

Я бы упростил это: bool isWhiteTurn = true;

           .... 
           Player& currentPlayer = isWhiteTurn ? m_player1 : m_player2;
           ....
           isWhiteTurn = !isWhiteTurn;

Этот цикл должен быть частью Player Code.

            {
                std::cout << "Please enter the position of the figure you want to move: ";
                from_coords = player->get_pos();
                std::cout << "Please enter the position you want your figure to move to: ";
                to_coords = player->get_pos();

            } while (!m_board.is_valid(from_coords, to_coords, m_player_switch));

Если вы замените людей искусственным интеллектом, то запись в вывод сбивает игрока с толку. Тип вывода зависит от типа игрока. Так что переместите это в код игрока.

Также вы не просите игрока о позиции. Вы просите игрока сделать ход. Дважды запрашивать у игрока позицию без контекста — плохой выбор API.


Очистка экрана не является частью игры. Это должно быть частью кода дисплея. Конечно, вы должны сказать дисплею, что текущий ход окончен. Но то, что делает дисплей, не является частью игры.

            // clear console
            std::cout << std::flush;
            system("CLS");

Похоже, еще одна ошибка

    };
#endif
}

По возможности используйте список инициализаторов:

    Cchess_board::Cchess_board()  
    {
        m_board = std::array<std::array<chess_board_state, chess_board_width>, chess_board_height>{ {
            { p2_rook, p2_knight, p2_bishop, p2_queen, p2_king, p2_bishop, p2_knight, p2_rook },
            { p2_pawn, p2_pawn,   p2_pawn,   p2_pawn,  p2_pawn, p2_pawn,   p2_pawn,   p2_pawn },
            { empty,   empty,     empty,     empty,    empty,   empty,     empty,     empty },
            { empty,   empty,     empty,     empty,    empty,   empty,     empty,     empty },
            { empty,   empty,     empty,     empty,    empty,   empty,     empty,     empty },
            { empty,   empty,     empty,     empty,    empty,   empty,     empty,     empty },
            { p1_pawn, p1_pawn,   p1_pawn,   p1_pawn,  p1_pawn, p1_pawn,   p1_pawn,   p1_pawn },
            { p1_rook, p1_knight, p1_bishop, p1_queen, p1_king, p1_bishop, p1_knight, p1_rook }
            }};

        m_invert_y = chess_board_height - 1;
    }

    void Cchess_board::display() const
    {

Эта структура инициализируется каждый раз при вызове этой функции.

        std::unordered_map<chess_board_state, std::string> figure_to_string = {
        {empty,"empty       "}, {p1_pawn,"white_pawn  "}, {p1_knight,"white_knight"}, {p1_bishop,"white_bishop"}, {p1_rook,"white_rook  "}, {p1_queen,"white_queen "}, {p1_king,"white_king  "},
                                {p2_pawn,"black_pawn  "}, {p2_knight,"black_knight"}, {p2_bishop,"black_bishop"}, {p2_rook,"black_rook  "}, {p2_queen,"black_queen "}, {p2_king,"black_king  "}
        };

Он должен быть статическим членом (внутри функции (хотя, если вы сделали его статическим членом класса, чтобы его можно было использовать в других функциях, которые меня не беспокоят)).


Не уверен, что это требуется (но если это был другой статический член):

        std::array<int32_t, chess_board_height> y_axis_descr = { 8, 7, 6, 5, 4, 3, 2, 1 };

Вы используете это ниже, чтобы распечатать номера строк.

 std::cout << static_cast<int>(y_axis_descr[i]) << " ";

 // Could we not just do:
 std::cout << (8-i) << " ";

Этот раздел while можно было упростить с помощью объектно-ориентированного программирования.

       m_board[from_y][from_x]->isGoodMove(to_x, to_y);

Но даже используя enum, вы можете упростить его, используя переключатель s:

       switch(m_board[from_y][from_x]) {
           case pawn:     checkMovePawn(from_y, form_x, to_y, to_x);  break;
           case castle:   checkMoveCastle(from_y, form_x, to_y, to_x);break;
           ....
       }

Вы проверяете мат при каждой комбинации ходов (пешка / конь / король). Я бы предпочел сделать чек на мат один раз, после того как выясню, что ход правильный (слон / ладья / ферзь).


Ваш тест на «белую» и черную версии фигурок означает, что типы должны быть заказаны. Но это не очень логично организовано. Смотри ниже. когда мы смотрим на кусок enum.


Линейные и диагностические проверки — это одно и то же, и их можно значительно упростить.

    xDist = to_x - from_x;
    yDist = to_y - from_y;


    if (xDist == 0 || yDist == 0 || abs(xDist) == abs(yDist)) {
        // Valid move
        xInc = xDist / abs(xDist); // 1 or -1
        yInc = yDist / abs(yDist); // 1 or -1
        
        int xStart = from_x + xInc;
        int yStart = from_y + yInc;
        for(; xStart != to_x && yStart != to_y;xStart += xInc, yStart += yInc) {
            // check for empty square.
        }
    }    

Это упростит все ваши проверки сканирования.

Особенно, если вы параметризуете вызов, если им разрешен этот тип движения.

 queen:  checkScanMove(true, true, 8, from, to); break;
 rook:   checkScanMove(true, false,8, from, to); break;
 bishop: checkScanMove(false,true, 8, from, to); break;
 king:   checkScanMove(true, true, 1, from ,to); break;

Это квадратная доска?

constexpr int32_t chess_board_height = 8;
constexpr int32_t chess_board_width = 8;

Это можно было сделать лучше.

enum chess_board_state
{
    empty = 0,

    p1_pawn = 1, 
    p1_knight,
    p1_bishop,
    p1_rook,
    p1_queen,
    p1_king,

    p2_pawn = 11,  // Why 11? 9 would have been better
    p2_knight,
    p2_bishop,
    p2_rook,
    p2_queen,
    p2_king,
};

Если было девять: тогда вы просто проверяете, является ли бит 3 (значение 8) черным или белым, вы просто проверяете бит 3.

Также, а не p1_ и p2_, почему не White_ или Black_

Хорошая идея

enum game_result
{
    draw = 0, p1_win, p2_win, still_playing
};

Но вы никогда не используете это.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *