1
\$\begingroup\$

I am currently writing a chess engine to improve my C++. I was able to improve my first results in terms of performance. (It is still really weak, but it does not lose a queen in one move)

I ended up having massive problems regarding namespaces in move generation.

It is really not great.

Here is some code:

#pragma once
#include <list>
#include <memory>
#include <string>
#include <vector>
#include <thread>
#include <functional>
#include "Board.h"
#include "Node.h"
#include "Utility.h"

namespace OdinConstants {
    static const std::string standardBoardFen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
    static const double cpuct = 2.0f;
}

class Node;

class Odin {
public:
    Odin();

    long positions_calculated_{0};
    std::shared_ptr<Node> start_node_;

    inline void searchOn() {
        searching_ = true;
        setUpForCalculations();
    }


    inline void searchOff() {
        searching_ = false;
    }

    void search();
    void setPosition(const std::string& fen, const std::vector<std::string>& moves);
    inline void setPosition(const Board& board) { 
        start_node_ =
          std::make_shared<Node>(board,std::nullopt, std::nullopt, nullptr);
      positions_calculated_ = 0;
    }
    static double evaluatePosition(const Board &board);

    std::tuple<int, int, Figure> bestMove() const;


private:
    std::thread computingThread_{};
    bool searching_{false};
    bool in_chess_{true};

    void setUpForCalculations();
    void computeNext();


};

 /* //////////////////////////////////////////////////
 * Move Generation
 *///////////////////////////////////////////////////

Board makeMove(const Board &b, std::tuple<int, int, Figure>);
/*
 * Checks if king could be "captured" in the next move and would therefore be in
 * check.
 */
bool isCheck(const Board &board, Color to_be_checked);

//all moves disregardingCheck
void generateAllMoves(std::vector<std::tuple<int, int, Figure>>& moves, const Board& board);
//Checks if any move, can reach this field
bool hasMoveToField(std::vector<std::tuple<int, int, Figure>>& moves, const Board& board, int to_field);
//all moves regarding check
void generateAllLegalMoves(std::vector<std::tuple<int, int, Figure>> &moves, const Board& board);
/*
 * filters all Moves which would be illegal, because color would check itself or
 * not escape a check
 */
void extractLegalMoves(std::vector<std::tuple<int, int, Figure>>& moves, const Board & board,
                       std::function<void(std::vector<std::tuple<int, int, Figure>>&, const Board &)> generator);

namespace PAWNMOVES {
void generateAllPawnMovesWithWhite(std::vector<std::tuple<int, int, Figure>> &pawn_moves, const Board &board, int field_num);
void generateAllPawnMovesWithBlack(std::vector<std::tuple<int, int, Figure>> &pawn_moves, const Board &board, int field_num);
void generateAllPawnMoves(std::vector<std::tuple<int, int, Figure>>& , const Board& board);
}
namespace KNIGHTMOVES {
void addIfMoveable(std::vector<std::tuple<int, int, Figure>>& moves, const int fromi, const int fromj, const int toi, const int toj, const Board& b);
void generateAllKnightMoves(std::vector<std::tuple<int, int, Figure>> &, const Board &board);
}
namespace LONGRANGEPIECEMOVES {
template <int dX, int dY> void generateMoves(std::vector<std::tuple<int, int, Figure>>& moves, const Board& board, const int y, const int x);
void generateAllBishopMoves(std::vector<std::tuple<int, int, Figure>>& , const Board& board);
void generateAllRookMoves(std::vector<std::tuple<int, int, Figure>>& , const Board& board);
void generateAllQueenMoves(std::vector<std::tuple<int, int, Figure>>&, const Board& board);
}

namespace KINGMOVES {
/*
* Generates a king step in any direction. Where dX, dY is the directrion.
 * For some weird reason, I do not fully understand: generateOneSteps needs to be header defined.
*/
template<int dX, int dY>
void generateOneSteps(const int j, const int i,
                                 std::vector<std::tuple<int, int, Figure>> &moves,
                                 const Board &board) {

  int toi = i + dY;
  int toj = j + dX;
  if (!inBounds(toi, toj)) {
    return;
  }
  if (board[toi][toj] == EMPTY.value()) {
    moves.push_back(std::make_tuple((8 * i + j), (8 * toi + toj), EMPTY));
  } else if (board[toi][toj] * (static_cast<int>(board.to_move_)) <=
      EMPTY.value()) {
    moves.push_back(std::make_tuple((8 * i + j), (8 * toi + toj), EMPTY));
  }
}
void generateAllCastling(int i, int j, std::vector<std::tuple<int, int, Figure>> &moves, const Board &board);
void generateAllKingMoves(std::vector<std::tuple<int, int, Figure>>&, const Board& board);
}

inline bool hasNoFigure(const Board& board, const int rank, const int line) {
  if (!inBounds(rank, line)) {
    return false;
  }  
  return board[rank][line] == 0;
}

inline bool hasBlackFigure(const Board& board, const int rank, const int line) {
  if (!inBounds(rank, line)) {
    return false;
  }
  return board[rank][line] < 0;
}

inline bool hasWhiteFigure(const Board& board, const int rank, const int line) {
  if (!inBounds(rank, line)) {
     return false;
  }
  return board[rank][line] > 0;
}

/*
 * This method checks if the given move, does not make the king takeable, ensures the King is still protected.
 */
bool checkIfMoveIsIllegalDueCheck(const Board &b, std::tuple<int, int, Figure> move);

/*
 * This currys checkIfMoveIsIllegalDueCheck(const Board &b, std::tuple<int, int, Figure> move);
 */
inline std::function<bool(std::tuple<int, int, Figure>)> checkIfMoveIsIllegalDueCheck(const Board& b){
  return [b](std::tuple<int, int, Figure> move) -> bool {
    return checkIfMoveIsIllegalDueCheck(b, move);
  };
}

bool isCheckMate(const Board& b);
bool isStaleMate(const Board& b);

And here are additional files.

// Pawnlogic.cc
// Created by Niclas Schwalbe on 30.05.21.
//
#include "Odin.h"

inline void generatePawnPromotion(std::vector<std::tuple<int, int, Figure>> &seq,
                                  const int field,
                                  const int new_field) {
  seq.push_back(std::make_tuple(field, new_field, BKNIGHT));
  seq.push_back(std::make_tuple(field, new_field, BBISHOP));
  seq.push_back(std::make_tuple(field, new_field, BROOK));
  seq.push_back(std::make_tuple(field, new_field, BQUEEN));
}

void PAWNMOVES::generateAllPawnMovesWithWhite(std::vector<std::tuple<int, int, Figure>> &pawn_moves,
                                   const Board &board,
                                   int field_num) {
  int rank = field_num / 8;
  int line = field_num % 8;

  if (hasNoFigure(board, rank + 1, line)) {
    int new_field{field_num + 8};
    if (rank == 6) {
      generatePawnPromotion(pawn_moves, field_num, new_field);
    } else {
      pawn_moves.push_back(std::make_tuple(field_num, new_field, EMPTY));
    }
  }

  if (rank == 1 && hasNoFigure(board, rank + 2, line) && hasNoFigure(board, rank + 1, line)) {
    pawn_moves.push_back(std::make_tuple(field_num, field_num + 16, EMPTY));
  }

  int left = line - 1;
  int right = line + 1;

  if (0 <= left && left < 8 && hasBlackFigure(board, rank + 1, left)) {
    int new_field{field_num + 7};
    if (rank == 6) {
      generatePawnPromotion(pawn_moves, field_num, new_field);
    } else {
      pawn_moves.push_back(std::make_tuple(field_num, new_field, EMPTY));
    }
  }
  if (0 <= right && right < 8 && hasBlackFigure(board, rank + 1, right)) {
    int new_field{field_num + 9};
    if (rank == 6) {
      generatePawnPromotion(pawn_moves, field_num, new_field);
    } else {
      pawn_moves.push_back(std::make_tuple(field_num, new_field, EMPTY));
    }
  }
}

void PAWNMOVES::generateAllPawnMovesWithBlack(std::vector<std::tuple<int, int, Figure>> &pawn_moves,
                                   const Board &board,
                                   int field_num) {
  int rank = field_num / 8;
  int line = field_num % 8;

  if (hasNoFigure(board, rank - 1, line)) {
    int new_field{field_num - 8};
    if (rank == 1) {
      generatePawnPromotion(pawn_moves, field_num, new_field);
    } else {
      pawn_moves.push_back(std::make_tuple(field_num, new_field, EMPTY));
    }
  }

  if (rank == 6 && hasNoFigure(board, rank - 2, line) && hasNoFigure(board, rank - 1, line)) {
    pawn_moves.push_back(std::make_tuple(field_num, field_num - 16, EMPTY));
  }

  int left = line - 1;
  int right = line + 1;

  if (0 <= left && left < 8 && hasWhiteFigure(board, rank - 1, left)) {
    int new_field{field_num - 9};
    if (rank == 1) {
      generatePawnPromotion(pawn_moves, field_num, new_field);
    } else {
      pawn_moves.push_back(std::make_tuple(field_num, new_field, EMPTY));
    }
  }
  if (0 <= right && right < 8 && hasWhiteFigure(board, rank - 1, right)) {
    int new_field{field_num - 7};
    if (rank == 1) {
      generatePawnPromotion(pawn_moves, field_num, new_field);
    } else {
      pawn_moves.push_back(std::make_tuple(field_num, new_field, EMPTY));
    }
  }

}


void PAWNMOVES::generateAllPawnMoves(std::vector<std::tuple<int, int, Figure>> &moves, const Board &board) {

  auto pawn = board.to_move_ == Color::WHITE ? WPAWN : BPAWN;

  for(int fieldnum = 0; fieldnum < 64; fieldnum++){
    if(board(fieldnum) == pawn.value()){
      switch (board.to_move_) {
        case Color::BLACK: generateAllPawnMovesWithBlack(moves, board, fieldnum);
          break;
        case Color::WHITE: generateAllPawnMovesWithWhite(moves, board, fieldnum);
          break;
      }
    }
  }

}



Movegeneration.cc


//
// Created by Niclas Schwalbe on 13.07.21.
//

#include "Odin.h"

Board makeMove(const Board &old_b, std::tuple<int, int, Figure> t) {
  // copy Board
  Board new_b{old_b};
  const auto from_field{std::get<0>(t)};
  const auto to_field{std::get<1>(t)};
  const auto promotion{std::get<2>(t)};

  // value of the piece to be moved.
  int temp = new_b(from_field);
  new_b(from_field) = EMPTY.value();

  // is the move an en passant?
  auto pawn = old_b.to_move_ == Color::WHITE ? WPAWN : BPAWN;
  if (old_b(from_field) == pawn.value() &&
      old_b.en_passant_field_ == to_field) {
    if (old_b.to_move_ == Color::WHITE) {
      new_b(to_field - 8) = EMPTY.value();
    } else {
      new_b(to_field + 8) = EMPTY.value();
    }
  }

  // is it pawn move with 2 steps, if yes set en_passant
  if (old_b(from_field) == pawn.value() && abs(from_field - to_field) == 16) {
    new_b.en_passant_field_ =
        new_b.to_move_ == Color::WHITE ? from_field + 8 : from_field - 8;
  } else {
    new_b.en_passant_field_ = -1;
  }

  // if move is castle, then set rook and remove castling rights
  auto king = old_b.to_move_ == Color::WHITE ? WKING : BKING;
  if (old_b(from_field) == king.value() && abs(from_field - to_field) == 2) {
    if (to_field % 8 < 5) {
      new_b((from_field / 8) * 8) = EMPTY.value();
      new_b((from_field / 8) * 8 + 3) = king.color() * WROOK.value();
    } else {
      new_b((from_field / 8) * 8 + 7) = EMPTY.value();
      new_b((from_field / 8) * 8 + 5) = king.color() * WROOK.value();
    }
    if (old_b.to_move_ == Color::WHITE) {
      new_b.long_castle_white_ = false;
      new_b.short_castle_white_ = false;
    } else {
      new_b.long_castle_black_ = false;
      new_b.short_castle_black_ = false;
    }
  }

  // Set new position, promote pawn if necessary
  if (promotion.value() == 0) {
    new_b(to_field) = temp;
  } else {
    new_b(to_field) = promotion.value();
  }

  // change color
  new_b.to_move_ = old_b.to_move_ == Color::WHITE ? Color::BLACK : Color::WHITE;

  return new_b;
}

void generateAllLegalMoves(std::vector<std::tuple<int, int, Figure>> &moves,
                           const Board &board) {
  extractLegalMoves(moves, board, generateAllMoves);
}

void generateAllMoves(std::vector<std::tuple<int, int, Figure>> &moves,
                      const Board &board) {
  bool colb = Color::WHITE == board.to_move_;
  auto pawn = colb ? WPAWN : BPAWN;
  auto knight = colb ? WKNIGHT : BKNIGHT;
  auto bishop = colb ? WBISHOP : BBISHOP;
  auto rook = colb ? WROOK : BROOK;
  auto queen = colb ? WQUEEN : BQUEEN;
  auto king = colb ? WKING : BKING;

  for (int fieldnum = 0; fieldnum < 64; fieldnum++) {
    auto val = board(fieldnum);
    int y = fieldnum / 8;
    int x = fieldnum % 8;

    if (val == pawn.value()) {

      switch (board.to_move_) {
        case Color::WHITE: PAWNMOVES::generateAllPawnMovesWithWhite(moves, board, fieldnum);
                           break;
        case Color::BLACK: PAWNMOVES::generateAllPawnMovesWithBlack(moves, board, fieldnum);
      }

    } else if (val == knight.value()) {

      KNIGHTMOVES::addIfMoveable(moves, y, x, y - 2, x + 1, board);
      KNIGHTMOVES::addIfMoveable(moves, y, x, y - 2, x - 1, board);
      KNIGHTMOVES::addIfMoveable(moves, y, x, y + 2, x + 1, board);
      KNIGHTMOVES::addIfMoveable(moves, y, x, y + 2, x - 1, board);
      KNIGHTMOVES::addIfMoveable(moves, y, x, y - 1, x + 2, board);
      KNIGHTMOVES::addIfMoveable(moves, y, x, y - 1, x - 2, board);
      KNIGHTMOVES::addIfMoveable(moves, y, x, y + 1, x + 2, board);
      KNIGHTMOVES::addIfMoveable(moves, y, x, y + 1, x - 2, board);

    } else if (val == bishop.value()) {

      LONGRANGEPIECEMOVES::generateMoves<1, 1>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<1, -1>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<-1, -1>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<-1, 1>(moves, board, y, x);

    } else if (val == rook.value()) {

      LONGRANGEPIECEMOVES::generateMoves<1, 0>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<0, -1>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<-1, 0>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<0, 1>(moves, board, y, x);

    } else if (val == queen.value()) {

      LONGRANGEPIECEMOVES::generateMoves<1, 1>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<1, 0>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<1, -1>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<-1, -1>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<-1, 0>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<-1, 1>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<0, -1>(moves, board, y, x);
      LONGRANGEPIECEMOVES::generateMoves<0, 1>(moves, board, y, x);

    } else if (val == king.value()) {


      KINGMOVES::generateOneSteps<1, 0>(x, y, moves, board);
      KINGMOVES::generateOneSteps<1, 1>(x, y, moves, board);
      KINGMOVES::generateOneSteps<1, -1>(x, y, moves, board);
      KINGMOVES::generateOneSteps<-1, 0>(x, y, moves, board);
      KINGMOVES::generateOneSteps<-1, -1>(x, y, moves, board);
      KINGMOVES::generateOneSteps<-1, 1>(x, y, moves, board);
      KINGMOVES::generateOneSteps<0, -1>(x, y, moves, board);
      KINGMOVES::generateOneSteps<0, 1>(x, y, moves, board);
      KINGMOVES::generateAllCastling(x, y, moves, board);

    }
  }
}

void extractLegalMoves(
    std::vector<std::tuple<int, int, Figure>> &moves, const Board &board,
    std::function<void(std::vector<std::tuple<int, int, Figure>> &,
                       const Board &)>
    generator) {
  std::vector<std::tuple<int, int, Figure>> new_moves;
  generator(new_moves, board);

  for (auto &t : new_moves) {
    if (!checkIfMoveIsIllegalDueCheck(board, t)) {
      moves.push_back(t);
    }
  }
}

bool checkIfMoveIsIllegalDueCheck(const Board &b,
                                  std::tuple<int, int, Figure> move) {
  Board new_board = makeMove(b, move);
  return isCheck(new_board, b.to_move_);
}



bool hasMoveToField(const Board &old_b, int to_field) {
  auto color = old_b.to_move_ == Color::WHITE ? 1 : -1;
  int x = to_field % 8;
  int y = to_field / 8;
  Figure pawn{WPAWN.value(), color};
  Figure knight{WKNIGHT.value(), color};
  Figure bishop{WBISHOP.value(), color};
  Figure rook{WROOK.value(), color};
  Figure queen{WQUEEN.value(), color};
  Figure king{WKING.value(), color};
  Board board{old_b};
  board.to_move_ = (board.to_move_ == Color::WHITE) ? Color::BLACK : Color::WHITE;

  std::vector<std::tuple<int, int, Figure>> knightmoves;
  KNIGHTMOVES::addIfMoveable(knightmoves, y, x, y - 2, x + 1, board);
  KNIGHTMOVES::addIfMoveable(knightmoves, y, x, y - 2, x - 1, board);
  KNIGHTMOVES::addIfMoveable(knightmoves, y, x, y + 2, x + 1, board);
  KNIGHTMOVES::addIfMoveable(knightmoves, y, x, y + 2, x - 1, board);
  KNIGHTMOVES::addIfMoveable(knightmoves, y, x, y - 1, x + 2, board);
  KNIGHTMOVES::addIfMoveable(knightmoves, y, x, y - 1, x - 2, board);
  KNIGHTMOVES::addIfMoveable(knightmoves, y, x, y + 1, x + 2, board);
  KNIGHTMOVES::addIfMoveable(knightmoves, y, x, y + 1, x - 2, board);

  for(auto& [from, to, E] : knightmoves){
    if(board(to) == knight.value()){
      return true;
    }
  }

  std::vector<std::tuple<int, int, Figure>> bishopmoves;
  LONGRANGEPIECEMOVES::generateMoves<1, 1>(bishopmoves, board, y, x);
  LONGRANGEPIECEMOVES::generateMoves<1, -1>(bishopmoves, board, y, x);
  LONGRANGEPIECEMOVES::generateMoves<-1, -1>(bishopmoves, board, y, x);
  LONGRANGEPIECEMOVES::generateMoves<-1, 1>(bishopmoves, board, y, x);

  for(auto& [from, to, E] : bishopmoves){
    if(board(to) == bishop.value() || board(to) == queen.value()){
      return true;
    }
  }

  std::vector<std::tuple<int, int, Figure>> rookmoves;
  LONGRANGEPIECEMOVES::generateMoves<1, 0>(rookmoves, board, y, x);
  LONGRANGEPIECEMOVES::generateMoves<0, -1>(rookmoves, board, y, x);
  LONGRANGEPIECEMOVES::generateMoves<-1, 0>(rookmoves, board, y, x);
  LONGRANGEPIECEMOVES::generateMoves<0, 1>(rookmoves, board, y, x);

  for(auto& [from, to, E] : rookmoves){
    if(board(to) == rook.value() || board(to) == queen.value()){
      return true;
    }
  }

  std::vector<std::tuple<int, int, Figure>> kingmoves;
  KINGMOVES::generateOneSteps<1, 0>(x, y, kingmoves, board);
  KINGMOVES::generateOneSteps<1, 1>(x, y, kingmoves, board);
  KINGMOVES::generateOneSteps<1, -1>(x, y, kingmoves, board);
  KINGMOVES::generateOneSteps<-1, 0>(x, y, kingmoves, board);
  KINGMOVES::generateOneSteps<-1, -1>(x, y, kingmoves, board);
  KINGMOVES::generateOneSteps<-1, 1>(x, y, kingmoves, board);
  KINGMOVES::generateOneSteps<0, -1>(x, y, kingmoves, board);
  KINGMOVES::generateOneSteps<0, 1>(x, y, kingmoves, board);

  for(auto& [from, to, E] : kingmoves){
    if(board(to) == king.value()){
      return true;
    }
  }

  switch (old_b.to_move_) {
    case Color::WHITE: return (inBounds(x-1,y-1) && board[y-1][x-1] == pawn.value()) || (inBounds(x+1,y-1) && board[y-1][x+1] == pawn.value());
    case Color::BLACK: return (inBounds(x-1,y+1) && board[y+1][x-1] == pawn.value()) || (inBounds(x+1,y+1) && board[y+1][x+1] == pawn.value());
  }


}

bool isCheck(const Board &b, Color color_to_be_checked) {
  std::vector<std::tuple<int, int, Figure>> moves;
  if (b.to_move_ == color_to_be_checked) {
    Board passTurn{b};
    passTurn.to_move_ =
        b.to_move_ == Color::WHITE ? Color::BLACK : Color::WHITE;
    return isCheck(passTurn, color_to_be_checked);
  } else {

    int field_num{0};

    auto figure = b.to_move_ == Color::WHITE ? BKING : WKING;

    for (auto p : b) {
      if (p == figure.value()) {
        break;
      }
      field_num++;
    }

    return hasMoveToField(b, field_num);
  }
}

bool isCheckMate(const Board &board) {
  if (!isCheck(board, board.to_move_)) {
    return false;
  }

  //smarter way
  if (board.is_end_position.has_value()) {
    return board.is_end_position.value();
  }  
  //else Bruteforce
  std::vector<std::tuple<int, int, Figure>> vec{};
  generateAllLegalMoves(vec, board);
  return vec.size() == 0;
}

bool isStaleMate(const Board &board) {
  if (isCheck(board, board.to_move_)) {
    return false;
  }
  //smarter way
  if (board.is_end_position.has_value()) {
    return board.is_end_position.value();
  }  
  //else Brutforce
  std::vector<std::tuple<int, int, Figure>> vec{};
  generateAllLegalMoves(vec, board);
  return vec.size() == 0;
}

Longrangepiecelogic.cc

#include "Odin.h"

/*
* Generates long range piece moves. Where dX, dY is the direction the piece is going to.
* A rook and a rook need 4 directions
* A queen needs 6 directions
*/
template <int dX, int dY>
void LONGRANGEPIECEMOVES::generateMoves(std::vector<std::tuple<int, int, Figure>>& moves,
                   const Board& board, const int y, const int x) {
  int tox = x;
  int toy = y;
  for (int i = 0; i < 8 && inBounds(tox + dX, toy + dY); i++) {
    tox += dX;
    toy += dY;
    if (board[toy][tox] == EMPTY.value()) {
      moves.push_back(std::make_tuple((8 * y + x), (8 * toy + tox), EMPTY));
      continue;
    } else if (board[toy][tox] * (static_cast<int>(board.to_move_)) <=
        EMPTY.value()) {
      moves.push_back(std::make_tuple((8 * y + x), (8 * toy + tox), EMPTY));
      break;
    }
    break;
  }
}



void LONGRANGEPIECEMOVES::generateAllBishopMoves(std::vector<std::tuple<int, int, Figure>>& moves,
                            const Board& board) {
  auto piece = board.to_move_ == Color::WHITE ? WBISHOP : BBISHOP;
  for (int y = 0; y < 8; y++) {
    for (int x = 0; x < 8; x++) {
      if (board[y][x] == piece.value()) {
        LONGRANGEPIECEMOVES::generateMoves<1, 1>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<1, -1>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<-1, -1>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<-1, 1>(moves, board, y, x);
      }
    }
  }
}

void LONGRANGEPIECEMOVES::generateAllRookMoves(std::vector<std::tuple<int, int, Figure>>& moves,
                          const Board& board) {
  auto piece = board.to_move_ == Color::WHITE ? WROOK : BROOK;
  for (int y = 0; y < 8; y++) {
    for (int x = 0; x < 8; x++) {
      if (board[y][x] == piece.value()) {
        LONGRANGEPIECEMOVES::generateMoves<1, 0>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<0, -1>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<-1, 0>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<0, 1>(moves, board, y, x);
      }
    }
  }
}

void LONGRANGEPIECEMOVES::generateAllQueenMoves(std::vector<std::tuple<int, int, Figure>>& moves,
                          const Board& board) {
  auto piece = board.to_move_ == Color::WHITE ? WQUEEN : BQUEEN;
  for (int y = 0; y < 8; y++) {
    for (int x = 0; x < 8; x++) {
      if (board[y][x] == piece.value()) {
        LONGRANGEPIECEMOVES::generateMoves<1, 1>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<1, -1>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<-1, -1>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<-1, 1>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<1, 0>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<0, -1>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<-1, 0>(moves, board, y, x);
        LONGRANGEPIECEMOVES::generateMoves<0, 1>(moves, board, y, x);
      }
    }
  }
}

Kinglogic.cc

#include "Odin.h"


/*
* Generates all castling moves. If the king is in check after the castling, the move will be generated and can be filtered out later.
* However, if the king would be in check while crossing, the move would not be added.
*/
void KINGMOVES::generateAllCastling(int j, int i,
                         std::vector<std::tuple<int, int, Figure>> &moves,
                         const Board &board) {
  //check if King is at original position.
  if (!((board.to_move_ == Color::BLACK && i == 7 && j == 4) || (board.to_move_ == Color::WHITE && i == 0 && j == 4))) {
    return;
  }

  switch (board.to_move_) {
    /*
    * If the side still has castling sides, the space between king and rook is free, the rook still is at original position
    * and the king could not be captured while moving to its desired position.
    */


    //case WHITE
    case Color::WHITE:
      if (board.long_castle_white_ &&
          !checkIfMoveIsIllegalDueCheck(board, std::make_tuple(4, 3, EMPTY)) &&
          board[0][1] == EMPTY.value() &&
          board[0][2] == EMPTY.value() &&
          board[0][3] == EMPTY.value() &&
          board[0][0] == WROOK.value()) {
        moves.push_back(std::make_tuple(4, 2, EMPTY));
      }
      if (board.short_castle_white_ &&
          !checkIfMoveIsIllegalDueCheck(board, std::make_tuple(4, 5, EMPTY)) &&
          board[0][5] == EMPTY.value() &&
          board[0][6] == EMPTY.value() &&
          board[0][7] == WROOK.value()) {
        moves.push_back(std::make_tuple(4, 6, EMPTY));
      }
      break;
      //case BLACK
    default:
      if (board.long_castle_black_ &&
          !checkIfMoveIsIllegalDueCheck(board, std::make_tuple(60, 59, EMPTY)) &&
          board[7][1] == EMPTY.value() &&
          board[7][2] == EMPTY.value() &&
          board[7][3] == EMPTY.value() &&
          board[7][0] == BROOK.value()) {
        moves.push_back(std::make_tuple(60, 58, EMPTY));
      }
      if (board.short_castle_black_ &&
          !checkIfMoveIsIllegalDueCheck(board, std::make_tuple(60, 61, EMPTY)) &&
          board[7][5] == EMPTY.value() &&
          board[7][6] == EMPTY.value() &&
          board[7][7] == BROOK.value()) {
        moves.push_back(std::make_tuple(60, 62, EMPTY));
      }
  }
}


void KINGMOVES::generateAllKingMoves(std::vector<std::tuple<int, int, Figure>> &moves,
                          const Board &board) {
  auto piece = board.to_move_ == Color::WHITE ? WKING : BKING;
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {
      if (board[i][j] == piece.value()) {
        KINGMOVES::generateOneSteps<1, 0>(j, i, moves, board);
        KINGMOVES::generateOneSteps<1, 1>(j, i, moves, board);
        KINGMOVES::generateOneSteps<1, -1>(j, i, moves, board);
        KINGMOVES::generateOneSteps<-1, 0>(j, i, moves, board);
        KINGMOVES::generateOneSteps<-1, -1>(j, i, moves, board);
        KINGMOVES::generateOneSteps<-1, 1>(j, i, moves, board);
        KINGMOVES::generateOneSteps<0, -1>(j, i, moves, board);
        KINGMOVES::generateOneSteps<0, 1>(j, i, moves, board);
        KINGMOVES::generateAllCastling(j, i, moves, board);
        return;
      }
    }
  }
}

Knightlogic.cc

#include <vector>
#include <tuple>
#include "Odin.h"

//adds Knight move if moveable
inline void KNIGHTMOVES::addIfMoveable(std::vector<std::tuple<int, int, Figure>>& moves, const int fromi,
                          const int fromj, const int toi, const int toj, const Board& b) {

  int opposite = b.to_move_ == Color::WHITE ? -1 : 1;
  if (!inBounds(toi, toj)) {
    return;
  }
  if (opposite * b[toi][toj] >= 0) {
    int old_field = fromi * 8 + fromj;
    int new_field = toi * 8 + toj;
    moves.push_back(std::make_tuple(old_field, new_field, EMPTY));
  }
  return;
}


void KNIGHTMOVES::generateAllKnightMoves(std::vector<std::tuple<int, int, Figure>>& moves,
                            const Board& board) {
  auto piece = board.to_move_ == Color::WHITE ? WKNIGHT : BKNIGHT;
  for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 8; j++) {
      if (board[i][j] == piece.value()) {
        KNIGHTMOVES::addIfMoveable(moves, i, j, i - 2, j + 1, board);
        KNIGHTMOVES::addIfMoveable(moves, i, j, i - 2, j - 1, board);
        KNIGHTMOVES::addIfMoveable(moves, i, j, i + 2, j + 1, board);
        KNIGHTMOVES::addIfMoveable(moves, i, j, i + 2, j - 1, board);
        KNIGHTMOVES::addIfMoveable(moves, i, j, i - 1, j + 2, board);
        KNIGHTMOVES::addIfMoveable(moves, i, j, i - 1, j - 2, board);
        KNIGHTMOVES::addIfMoveable(moves, i, j, i + 1, j + 2, board);
        KNIGHTMOVES::addIfMoveable(moves, i, j, i + 1, j - 2, board);
      }  

    }
  }
}

I have some general questions:

  1. As you see in the Odin.h file, namespaces are declared. However, for some weird reasons, I had to include the KINGMOVES::generateOneStep into the header file and could not define it in the kinglogic.cc, while this was possible with the similar generatesMoves from the longrangepiecelogic.cc. How to fix that?

  2. How could a better structure of this move generation look like?

  3. There are some functions, where White and Black do different things (Pawnlogic.cc) This make it really clumsy. Any idea on how to fix that?

  4. What else would you improve?

\$\endgroup\$
2
  • \$\begingroup\$ Where is EMPTY defined? \$\endgroup\$
    – JDługosz
    Commented Jul 16, 2021 at 15:22
  • \$\begingroup\$ In an additional file, I left it out. EMPTY is an Piece with color white and value 0. So you can check if the board is clear with Empty.value() \$\endgroup\$
    – Niclas
    Commented Jul 16, 2021 at 22:14

2 Answers 2

1
\$\begingroup\$

For some weird reason, I do not fully understand: generateOneSteps needs to be header defined.

template<int dX, int dY>
void generateOneSteps(const int j, const int i,
                                 std::vector<std::tuple<int, int, Figure>> &moves,
                                 const Board &board) {

Because it's a template! If you declared it only, without any body, then when you call it the compiler can't specialize it since the body is in a different source file from the call. So it codes the call but doesn't generate the specialization's code. Meanwhile, in the file that does have the body, nothing causes any specialization. So in the end you get a linker error because the function is never generated anywhere.

You would have to add a line to the file where the body is defined, for each form that you want to be generated. See explicit instantiation in Cppreference. But, we normally just put templates in the header.


I don't see why generateOneSteps needs to have dX and dY given as template arguments. It looks like it's just used in arithmetic, and would work just fine as normal parameters.

\$\endgroup\$
3
  • \$\begingroup\$ But why is LONGRANGEPIECEMOVES::generateMoves working ? You see it above the KINGMOVES \$\endgroup\$
    – Niclas
    Commented Jul 16, 2021 at 22:16
  • \$\begingroup\$ In other words, a template is just a safer macro than #define. In newer C++ convention, a project has many ".hpp" files and a few ".cpp" files instead of the pairs of ".h/.hpp" files and ".cpp" files. \$\endgroup\$ Commented Jul 16, 2021 at 23:32
  • \$\begingroup\$ @Niclas because it is defined in Longrangepiecelogic.cc before it is called. Do you understand that .h files are simply text spliced in at that point, and the compiler sees everything put together in a translation unit ? The overriding principle is that the template definition must be seen before the call is made. Putting them in different translation units, that is not going to happen. Putting the definition before the use in the same cpp file, or in a .h file that's included before the use, accomplishes that. \$\endgroup\$
    – JDługosz
    Commented Jul 20, 2021 at 14:04
1
\$\begingroup\$
  1. There are some functions, where White and Black do different things (Pawnlogic.cc) This make it really clumsy. Any idea on how to fix that?

I prefer functional programming, and I usually write closures. But C++ has a more statical solution. You can reduce codes by template and alias. Here is an example:

template <int x>
int add_x(int y) {
    return y + x;
}

constexpr auto add_2 = add_x<2>;
constexpr auto add_5 = add_x<5>;

I referred to the following source: How do I assign an alias to a function name in C++?

\$\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.