2
\$\begingroup\$

This app only creates a chess board set to the start-of-game state. It does nothing else. I'd appreciate feedback on the files that are relevant to this board setup (all shown below: board.cpp, pieces.cpp, board_view.cpp). Although the project uses SFML, I'm hoping for C++ feedback, not SFML best practices per se.

Below the files I will give a link just in case you want the two .cpp files I didn't include (main.cpp and game_loop.cpp), which don't control how the board is actually built at all. (The link also has all the header files, the pieces fonts, and the CMakeLists file... for whatever it's worth, everything will "just work" only if you have SFML installed).

board.h

#pragma once

#include <SFML/Graphics.hpp>

class board {
public:
  explicit board(sf::Vector2u windowSize);
  explicit board(sf::Event &event);
  void render_board();
  void draw_board(sf::RenderWindow &render_window) const;

private:
  std::unordered_map<std::string, std::string> board_state{};
  std::unordered_map<int, std::string> row_index_to_alg_notation_num{};
  std::unordered_map<int, std::string> column_index_to_alg_notation_letter{};

  sf::Vector2u windowSize;
  sf::RenderTexture render_texture;
  sf::Color white_square_color = sf::Color(238, 238, 211);
  sf::Color black_square_color = sf::Color(118, 150, 86);
  sf::Color window_background_color = sf::Color(169, 169, 169);

  sf::Font open_sans_font;
  std::unordered_map<int, std::string> alg_notation_letter_map;
  std::unordered_map<int, int> alg_notation_number_map;
  float x_offset = 0.0;
  float y_offset = 0.0;
  float square_size{};
  void render_square(int i, int j);
  void render_piece(int i, int j);
  void render_algebraic_notation(int i, int j);

  void set_open_sans_font();
  void set_alg_notation_letter_map();
  void set_alg_notation_number_map();
  void set_initial_board_state();
  void set_indices_to_alg_notation();
  std::string get_algebraic_notation(int i, int j);
  std::string get_piece_for_square(const std::string &);
  void init_();

};

board.cpp

#include <SFML/Graphics.hpp>
#include <cmath>

#include "board.h"
#include "pieces.h"

board::board(sf::Vector2u windowSize) : windowSize(windowSize) {
  init_();
}

board::board(sf::Event &event) {
  windowSize = {event.size.width, event.size.height};
  init_();
}

void board::render_board() {

  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {

      render_square(i, j);

      render_algebraic_notation(i, j);

      render_piece(i, j);
    }
  }
}
void board::render_square(int i, int j) {

  sf::RectangleShape currSquare({square_size, square_size});
  currSquare.setFillColor((i + j) % 2 == 0 ? white_square_color : black_square_color);
  currSquare.setPosition(x_offset + static_cast<float>(j) * square_size,
                         y_offset + static_cast<float>(i) * square_size);

  render_texture.draw(currSquare);
  render_texture.display();
}
void board::render_piece(int i, int j) {

  std::string square_alg_notation = get_algebraic_notation(i, j);
  std::string piece_for_square = get_piece_for_square(square_alg_notation);

  if (!piece_for_square.empty()) {
    pieces p;
    sf::Text
        sfml_text_for_piece =
        p.get_positioned_sfml_text_graphic_for_piece(piece_for_square, square_size, x_offset, y_offset, i, j);

    render_texture.draw(sfml_text_for_piece);
    render_texture.display();
  }

}

void board::render_algebraic_notation(int i, int j) {

  if (i == 7) {
    sf::Text text(alg_notation_letter_map.at(j), open_sans_font, std::floor(square_size / 5.8f));
    auto letter_color = j % 2 == 0 ? white_square_color : black_square_color;
    text.setFillColor(letter_color);
    text.setPosition(square_size / 1.2f + x_offset + static_cast<float>(j) * square_size,
                     square_size / 1.3f + y_offset + static_cast<float>(i) * square_size);

    render_texture.draw(text);
    render_texture.display();
  }

  if (j == 0) {
    sf::Text text(std::to_string(alg_notation_number_map.at(i)), open_sans_font, std::floor(square_size / 5.8f));
    auto letter_color = i % 2 == 1 ? sf::Color(254, 232, 209) : sf::Color(83, 120, 99);
    text.setFillColor(letter_color);
    text.setPosition(square_size / 18.f + x_offset + static_cast<float>(j) * square_size,
                     square_size / 25.f + y_offset + static_cast<float>(i) * square_size);

    render_texture.draw(text);
    render_texture.display();
  }

}

void board::draw_board(sf::RenderWindow &render_window) const {
  render_window.draw(sf::Sprite(render_texture.getTexture()));
}

std::string board::get_algebraic_notation(int i, int j) {
  return column_index_to_alg_notation_letter.at(j) + row_index_to_alg_notation_num.at(i);
}

/**
 * get_piece_for_square Takes the algebraic notation for a square and returns the piece belonging on that square.
 * @param sqr_alg_notation Algebraic notation for the square of interest.
 * @return Piece belonging on the square provided as input.
 */
std::string board::get_piece_for_square(const std::string &sqr_alg_notation) {
  return board_state.at(sqr_alg_notation);
};

void board::init_() {

  x_offset = 0;
  square_size = static_cast<float>(windowSize.y) / 8.f;
  render_texture.create(windowSize.x, windowSize.y);

  set_open_sans_font();
  set_alg_notation_letter_map();
  set_alg_notation_number_map();
  set_initial_board_state();
  set_indices_to_alg_notation();
}

/**
 * Methods called by `init_()` appear below
 */

void board::set_open_sans_font() {
  if (!open_sans_font.loadFromFile("font/OpenSans-ExtraBold.ttf")) {
    // error...
  }
}

void board::set_alg_notation_letter_map() {
  alg_notation_letter_map = {{0, "a"}, {1, "b"}, {2, "c"}, {3, "d"}, {4, "e"}, {5, "f"}, {6, "g"}, {7, "h"}};
}

void board::set_alg_notation_number_map() {
  alg_notation_number_map = {{0, 8}, {1, 7}, {2, 6}, {3, 5}, {4, 4}, {5, 3}, {6, 2}, {7, 1}};
}

void board::set_initial_board_state() {
  board_state = {
      {"a1", "white_rook"},
      {"b1", "white_knight"},
      {"c1", "white_bishop"},
      {"d1", "white_queen"},
      {"e1", "white_king"},
      {"f1", "white_bishop"},
      {"g1", "white_knight"},
      {"h1", "white_rook"},

      {"a2", "white_pawn"},
      {"b2", "white_pawn"},
      {"c2", "white_pawn"},
      {"d2", "white_pawn"},
      {"e2", "white_pawn"},
      {"f2", "white_pawn"},
      {"g2", "white_pawn"},
      {"h2", "white_pawn"},

      {"a3", ""},
      {"b3", ""},
      {"c3", ""},
      {"d3", ""},
      {"e3", ""},
      {"f3", ""},
      {"g3", ""},
      {"h3", ""},

      {"a4", ""},
      {"b4", ""},
      {"c4", ""},
      {"d4", ""},
      {"e4", ""},
      {"f4", ""},
      {"g4", ""},
      {"h4", ""},

      {"a5", ""},
      {"b5", ""},
      {"c5", ""},
      {"d5", ""},
      {"e5", ""},
      {"f5", ""},
      {"g5", ""},
      {"h5", ""},

      {"a6", ""},
      {"b6", ""},
      {"c6", ""},
      {"d6", ""},
      {"e6", ""},
      {"f6", ""},
      {"g6", ""},
      {"h6", ""},

      {"a7", "black_pawn"},
      {"b7", "black_pawn"},
      {"c7", "black_pawn"},
      {"d7", "black_pawn"},
      {"e7", "black_pawn"},
      {"f7", "black_pawn"},
      {"g7", "black_pawn"},
      {"h7", "black_pawn"},

      {"a8", "black_rook"},
      {"b8", "black_knight"},
      {"c8", "black_bishop"},
      {"d8", "black_queen"},
      {"e8", "black_king"},
      {"f8", "black_bishop"},
      {"g8", "black_knight"},
      {"h8", "black_rook"}

  };
}

void board::set_indices_to_alg_notation() {
  row_index_to_alg_notation_num = {
      {0, "8"},
      {1, "7"},
      {2, "6"},
      {3, "5"},
      {4, "4"},
      {5, "3"},
      {6, "2"},
      {7, "1"}
  };
  column_index_to_alg_notation_letter = {
      {0, "a"},
      {1, "b"},
      {2, "c"},
      {3, "d"},
      {4, "e"},
      {5, "f"},
      {6, "g"},
      {7, "h"}
  };
}

pieces.h

#pragma once

#include "../include/game_loop.h"

#include <iostream>
#include <codecvt>

class pieces {
public:
  pieces();
  sf::Color white_piece_color = sf::Color(248, 248, 248);
  sf::Color black_piece_color = sf::Color(86, 83, 82);

  char32_t king{};
  char32_t queen{};
  char32_t rook{};
  char32_t bishop{};
  char32_t knight{};
  char32_t pawn{};

  sf::Text get_positioned_sfml_text_graphic_for_piece(const std::string &piece_for_square,
                                                      float square_size,
                                                      float x_offset,
                                                      float y_offset,
                                                      int i,
                                                      int j);

private:
  sf::Font free_sarif_font;
  char32_t get_char32_piece_code(const std::string &piece_name) const;
  void set_free_sarif_font();
  void init_();

  sf::Color get_piece_color(const std::string &piece_for_square) const;
  static std::string get_piece_name(const std::string &piece_for_square);
};

pieces.cpp

#include <SFML/Graphics.hpp>
#include <cmath>

#include "pieces.h"

pieces::pieces() {
  init_();
}

sf::Text pieces::get_positioned_sfml_text_graphic_for_piece(const std::string &piece_for_square,
                                                            float square_size,
                                                            float x_offset,
                                                            float y_offset,
                                                            int i,
                                                            int j) {

  sf::Text text(get_char32_piece_code(get_piece_name(piece_for_square)), free_sarif_font, std::floor(square_size));
  text.setFillColor(get_piece_color(piece_for_square));
  text.setOutlineColor(sf::Color(0, 0, 0));
  text.setOutlineThickness(1);
  text.setPosition(square_size / 8.f + x_offset + static_cast<float>(j) * square_size,
                   -square_size / 8.f + y_offset + static_cast<float>(i) * square_size);
  return text;
}
std::string pieces::get_piece_name(const std::string &piece_for_square) {
  size_t index_of_first_piece_letter = piece_for_square.find('_') + 1;
  std::string piece_name = piece_for_square.substr(index_of_first_piece_letter, piece_for_square.size());
  return piece_name;
}
sf::Color pieces::get_piece_color(const std::string &piece_for_square) const {
  sf::Color piece_color;
  if (piece_for_square.find("white") != std::string::npos) {
    piece_color = white_piece_color;
  } else {
    piece_color = black_piece_color;
  };
  return piece_color;
}

char32_t pieces::get_char32_piece_code(const std::string &piece_name) const {
  if (piece_name == "king")
    return king;
  if (piece_name == "queen")
    return queen;
  if (piece_name == "rook")
    return rook;
  if (piece_name == "bishop")
    return bishop;
  if (piece_name == "knight")
    return knight;
  if (piece_name == "pawn")
    return pawn;
}

void pieces::init_() {

  set_free_sarif_font();

  king = std::stoul("265A", nullptr, 16);
  queen = std::stoul("265B", nullptr, 16);
  rook = std::stoul("265C", nullptr, 16);
  bishop = std::stoul("265D", nullptr, 16);
  knight = std::stoul("265E", nullptr, 16);
  pawn = std::stoul("265F", nullptr, 16);
}

/**
 * Methods called by `init_()` appear below
 */

void pieces::set_free_sarif_font() {
  if (!free_sarif_font.loadFromFile("font/FreeSerif-4aeK.ttf")) {
    // error...
  };
}

board_view.h

#pragma once

#include <SFML/Graphics.hpp>

class board_view {
public:
  board_view(const sf::Event &event, sf::RenderWindow &render_window);
  void configure_and_set_view();

private:
  const sf::Event &event;
  sf::RenderWindow &render_window;
  sf::View view;
};

board_view.cpp

#include "board_view.h"

board_view::board_view(const sf::Event &event, sf::RenderWindow &render_window)
    : event(event), render_window(render_window) {}

void board_view::configure_and_set_view() {
  auto w = static_cast<float>(event.size.width);
  auto h = static_cast<float>(event.size.height);
  view.setSize({w, h});
  view.setCenter({
                     w / 2.f, h / 2.f});
  render_window.setView(view);
}

https://github.com/tarstevs/chess

\$\endgroup\$
3
  • \$\begingroup\$ Does board.h and board.cpp compile for you, I had to add #include <unordered_map> to get it to compile for me. pieces.cpp is missing the include for string as well. \$\endgroup\$
    – pacmaninbw
    Commented May 14, 2022 at 2:00
  • \$\begingroup\$ @pacmaninbw That's interesting. So yes, they compile... I didn't even think about it, but I must have "just used" #include <SFML/Graphics.hpp> (which apparently gave me the includes you mentioned) before I got around to adding my own includes... and then I forgot to come back to adding includes. Is this common? \$\endgroup\$
    – tarstevs
    Commented May 15, 2022 at 0:26
  • \$\begingroup\$ @pacmaninbw Ah, just finished and accepted your nice review. I'll be explicit about the includes, and work on SOLID (with ideas of reducing coupling and reducing the complexity of board_state)... \$\endgroup\$
    – tarstevs
    Commented May 15, 2022 at 0:34

1 Answer 1

2
\$\begingroup\$

General Observations

Right now there is a serious Object Oriented Design flaw, the model of the game and the display are too closely coupled. There are many reasons to separate the game itself from the visual representation. The visual representation only needs to display the board and the pieces, the model needs to know what the pieces are, how those pieces move, any strategy that needs to be known such as basic openings, is this a tournament game so clocks should be shown, etc. One of the reasons for this separation is that it allows you to use the same game engine with different display engines. A second reason for this is to keep each of software piece of the program simpler and reusable. Use the SOLID design principles. SOLID is 5 object oriented design principles. SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible and maintainable. This will help you design your objects and classes better.

  1. The Single Responsibility Principle - A class should only have a single responsibility, that is, only changes to one part of the software's specification should be able to affect the specification of the class.
  2. The Open–closed Principle - states software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
  3. The Liskov Substitution Principle - Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
  4. The Interface segregation principle - states that no client should be forced to depend on methods it does not use.
  5. The Dependency Inversion Principle - is a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details.

There are multiple design patterns to help prevent tight coupling of the model and the presentation:

The board_view class should contain all presentation code for the board, the board class should only contain the model for the board. The board_state, row_index_to_alg_notation_num and column_index_to_alg_notation_letter members should remain in the board class, most of the other members belong in the board_view class.

There should be a piece_view class for presenting the pieces. The current piece class should probably be an abstract base class that all piece classes can inherit from, some of the possible abstractions are the name of the piece, and how the piece moves.

The current presentation can't handle tournament play, since there isn't any way to display time clocks.

Code Observations

Include What is Necessary

Right now board.h is missing an include for unordered_map, this is hidden by the fact that SMFL/Graphics.hpp is including it. Don't depend on all inclusive files, make it clear in the code what is necessary. SMFL/Graphics.hpp also seems to include the string header in pieces.cpp. While the game_loop class is not presented it became necessary to look at it, the default constructor for game_loop should be in the header file rather than the game_loop.cpp file.

Don't Include What Isn't Necessary

It isn't clear why game_loop.h is included in pieces.h, there really shouldn't be any dependency on the game loop for any of the pieces.

Complexity

The board_state variable is too complex, rather than store all 64 squares, just store the piece with X, and Y positions. The current complexity is required due to the coupling between the display and the model, it isn't necessary for the game.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.