<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

#ifndef TEST_SURAKARTA_FACTORY_H_
#define TEST_SURAKARTA_FACTORY_H_

#include <skmodel.h>

#include <vector>

// Used to feed verbose instructions to SurakartaFactory to mock a position.
typedef struct ModelBlock {
    sk::Player color;
    int row;
    int column;
    int rowRange;
    int columnRange;

    ModelBlock(sk::Player color, int row, int column, int rowRange = 1,
               int columnRange = 1) {
        this->color = color;
        this->row = row;
        this->column = column;
        this->rowRange = rowRange;
        this->columnRange = columnRange;
    }
} ModelBlock;

sk::Surakarta *createModel(std::vector<ModelBlock *> &arr);

#endif /* TEST_SURAKARTA_FACTORY_H_ */<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

#include "position.h"

#include <bit>
#include <vector>

#include "bitboard.h"

namespace sk {

Position::Position(sk::Surakarta& model) : options_(model.options_) {
    player_bitboards_ = new sk::Bitboard[2];

    player_bitboards_[0] = sk::createPlayerBitboard(model, sk::RED);
    player_bitboards_[1] = sk::createPlayerBitboard(model, sk::BLACK);

    count_red_ = std::__popcount(this->player_bitboards_[0]);
    count_black_ = std::__popcount(this->player_bitboards_[1]);

    side_to_move_ = RED;
}

Position::Position(const Position& copy) : options_(copy.options_) {
    player_bitboards_ = new sk::Bitboard[2];

    player_bitboards_[0] = copy.player_bitboards_[0];
    player_bitboards_[1] = copy.player_bitboards_[1];

    count_red_ = copy.count_red_;
    count_black_ = copy.count_black_;

    side_to_move_ = copy.side_to_move_;
}

Value Position::evaluate() {
    // Each pebble has a value of 2, except those at corner (value = 1)
    return (count_red_ - count_black_) * 2                            //
           - (player_bitboards_[0] & 1) + (player_bitboards_[1] & 1)  //
           - ((player_bitboards_[0] >> 5) & 1) +
           ((player_bitboards_[1] >> 5) & 1)  //
           - ((player_bitboards_[0] >> 30) & 1) +
           ((player_bitboards_[1] >> 30) & 1)  //
           - ((player_bitboards_[0] >> 35) & 1) +
           ((player_bitboards_[1] >> 35) & 1);
}

void Position::copyFrom(Position& pos) {
    player_bitboards_[0] = pos.player_bitboards_[0];
    player_bitboards_[1] = pos.player_bitboards_[1];

    count_red_ = pos.count_red_;
    count_black_ = pos.count_black_;
}

void Position::move(Move move) {
    Square start_sq =
        CreateSquare(GetMoveStartRow(move), GetMoveStartColumn(move));
    Square final_sq =
        CreateSquare(GetMoveFinalRow(move), GetMoveFinalColumn(move));

    if (HasPlayer(GetRedPlayerBitboard(), start_sq)) {
        LiftPlayer(player_bitboards_[0], start_sq);
        PutPlayer(player_bitboards_[0], final_sq);

        if (HasPlayer(GetBlackPlayerBitboard(), final_sq)) {
            --count_black_;
            LiftPlayer(player_bitboards_[1], final_sq);
        }

        side_to_move_ = BLACK;
    } else if (HasPlayer(GetBlackPlayerBitboard(), final_sq)) {
        LiftPlayer(player_bitboards_[1], start_sq);
        PutPlayer(player_bitboards_[1], final_sq);

        if (HasPlayer(GetRedPlayerBitboard(), final_sq)) {
            --count_red_;
            LiftPlayer(player_bitboards_[0], start_sq);
        }

        side_to_move_ = RED;
    }
}

}  // namespace sk<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

// This file defines all the simple types used in the libsurakarta engine.

#ifndef INCLUDE_SKTYPES_H_
#define INCLUDE_SKTYPES_H_

#include <stdint.h>

namespace sk {

// Bitboard representation of a Surakarta position. The "on" bits indicate
// whether a given type of piece is placed on the corresponding squares.
typedef uint64_t Bitboard;

typedef int Square;

inline Square CreateSquare(int row, int column) { return (row * 6 + column); }

// Evaluation of a given position - +ve values indicate RED has the advantage,
// while -ve values indicate BLACK has the advantage.
typedef int Value;

}  // namespace sk

#endif /* INCLUDE_SKTYPES_H_ */<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.

// Author: <NAME> <<EMAIL>>

#include <skengine.h>

#include <thread>

#include "context.h"
#include "movegen.h"
#include "position.h"

namespace sk {

// simple negamax
int DeepEvaluate(Context& context) {
    if (context.depth == kMaxDepth) {
        return context.position.evaluate();
    }

    Position& pos = context.position;
    std::vector<Move> move_list = GenerateMoves(pos);

    Move best_move = 0;
    int best_eval = -1000000;

    Context child_cxt;
    child_cxt.depth = context.depth + 1;
    child_cxt.position.copyFrom(pos);

    for (std::vector<Move>::iterator mitr = move_list.begin();
         mitr != move_list.end(); mitr++) {
        Move move = *mitr;

        child_cxt.position.move(move);
        int eval = -DeepEvaluate(child_cxt);
        child_cxt.position.copyFrom(pos);

        if (eval > best_eval) {
            best_eval = eval;
            best_move = move;
        }
    }

    return best_eval;
}

Move Think(Surakarta& rootModel, EngineOptions& eg_options) {
    Context root_context;
    Position rootPos(rootModel);

    root_context.position.copyFrom(rootPos);
    root_context.depth = 0;

    int best_move = 0;
    int best_eval = -10000;

    std::vector<Move> move_list = GenerateMoves(rootPos);

    for (std::vector<Move>::iterator mitr = move_list.begin();
         mitr != move_list.end(); mitr++) {
        Move move = *mitr;
        int value = DeepEvaluate(root_context);

        if (value > best_eval) {
            best_move = move;
            best_eval = value;
        }
    }

    return best_move;
}

}  // namespace sk<file_sep>[![Build Status](https://dev.azure.com/SurakartaArcade/Surakarta/_apis/build/status/SurakartaArcade.libsurakarta?branchName=master)](https://dev.azure.com/SurakartaArcade/Surakarta/_build/latest?definitionId=3&branchName=master)

# libsurakarta
C++ implementation of a Surakarta computer engine
<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.

// Author: <NAME> <<EMAIL>>

#include "movegen.h"

#include "bitboard.h"

namespace sk {

// Generates a method that will add a Move into a vector if the final
// square is empty (given the initial square). The final square is calculated
// by adding sq_delta (similar for final row & final column)
#define GEN_STEP(name, sq_delta, row_delta, column_delta)                  \
    inline void name(std::vector<Move> bfr, const Bitboard& bb, Square sq, \
                     int row, int column) {                                \
        if (HasPlayer(bb, sq + sq_delta)) {                                \
            bfr.push_back(CreateMove(row, column, row + row_delta,         \
                                     column + column_delta));              \
        }                                                                  \
    }
GEN_STEP(StepNorthWest, -7, -1, -1)
GEN_STEP(StepNorth, -6, -1, 0)
GEN_STEP(StepNorthEast, -5, -1, +1)
GEN_STEP(StepWest, -1, 0, -1)
GEN_STEP(StepEast, +1, 0, +1)
GEN_STEP(StepSouthWest, +5, +1, -1)
GEN_STEP(StepSouth, +6, +1, 0)
GEN_STEP(StepSouthEast, +7, +1, +1)

// Generates three Step* calls (used for corners which have 3 freedoms)
#define TOUCH_CORNER(bfr, bb, square, row, column, f1, f2, f3) \
    {                                                          \
        f1(bfr, bb, square, row, column);                      \
        f2(bfr, bb, square, row, column);                      \
        f3(bfr, bb, square, row, column);                      \
    }

// Generates five Step* calls (used for edge squares which have five freedoms)
#define TOUCH_EDGE(bfr, bb, square, row, column, f1, f2, f3, f4, f5) \
    {                                                                \
        TOUCH_CORNER(bfr, bb, square, row, column, f1, f2, f3);      \
        f4(bfr, bb, square, row, column);                            \
        f5(bfr, bb, square, row, column);                            \
    }

inline void GenerateCornerStepMoves(const Bitboard stm_bitboard,
                                    const Bitboard nn_bitboard,
                                    std::vector<Move> buffer) {
    // Northwest corner
    if (HasPlayer(stm_bitboard, 0))
        TOUCH_CORNER(buffer, nn_bitboard, 0, 0, 0, StepEast, StepSouthEast,
                     StepSouth)

    // Northeast corner
    if (HasPlayer(stm_bitboard, 5))
        TOUCH_CORNER(buffer, nn_bitboard, 5, 0, 5, StepSouth, StepSouthWest,
                     StepWest)

    // Southwest corner
    if (HasPlayer(stm_bitboard, 30))
        TOUCH_CORNER(buffer, nn_bitboard, 30, 5, 0, StepNorth, StepNorthEast,
                     StepEast)

    // Southeast corner
    if (HasPlayer(stm_bitboard, 35))
        TOUCH_CORNER(buffer, nn_bitboard, 35, 5, 5, StepWest, StepNorthWest,
                     StepNorth)
}

inline void GenerateEdgeStepMoves(const Bitboard stm_bitboard,
                                  const Bitboard nn_bitboard,
                                  std::vector<Move> buffer) {
    for (int square = 1; square < 5; square++) {
        if (!HasPlayer(stm_bitboard, square)) continue;

        TOUCH_EDGE(buffer, nn_bitboard, square, 0, square, StepEast,
                   StepSouthEast, StepSouth, StepSouthWest, StepWest)
    }

    // West edge
    for (int row = 1, square = 11; row < 5; row++, square += 6) {
        if (!HasPlayer(stm_bitboard, square)) continue;

        TOUCH_EDGE(buffer, nn_bitboard, square, row, 5, StepSouth,
                   StepSouthWest, StepWest, StepNorthWest, StepNorth)
    }

    // South edge
    for (int column = 1, square = 31; column < 5; column++, square++) {
        if (!HasPlayer(stm_bitboard, square)) {
            continue;
        }

        TOUCH_EDGE(buffer, nn_bitboard, square, 5, column, StepWest,
                   StepNorthWest, StepNorth, StepNorthEast, StepEast)
    }

    // East edge
    for (int row = 1, square = 6; row < 5; row++, square += 6) {
        if (!HasPlayer(stm_bitboard, square)) continue;

        TOUCH_EDGE(buffer, nn_bitboard, square, row, 0, StepNorth,
                   StepNorthEast, StepEast, StepSouthEast, StepSouth)
    }
}

template <>
void GenerateMoves<STEP>(const Position& pos, std::vector<Move> buffer) {
    Bitboard stm_bitboard = pos.GetSideToMoveBitboard();
    Bitboard nn_bitboard = pos.GetNonNullBitboard();

    GenerateCornerStepMoves(stm_bitboard, nn_bitboard, buffer);
    GenerateEdgeStepMoves(stm_bitboard, nn_bitboard, buffer);

    /* Loop through non-border squares which have eight freedoms */
    for (int row = 1, square = 7; row < 5; row++) {
        for (int column = 1; column < 5; column++, square++) {
            if (!HasPlayer(stm_bitboard, square)) continue;

            StepNorthWest(buffer, nn_bitboard, square, row, column);
            StepNorth(buffer, nn_bitboard, square, row, column);
            StepNorthEast(buffer, nn_bitboard, square, row, column);
            StepWest(buffer, nn_bitboard, square, row, column);
            StepEast(buffer, nn_bitboard, square, row, column);
            StepSouthWest(buffer, nn_bitboard, square, row, column);
            StepSouth(buffer, nn_bitboard, square, row, column);
            StepSouthEast(buffer, nn_bitboard, square, row, column);
        }
    }
}

// Checks whether an attack from a square in a given direction is valid.
inline bool IsAttackValid(const Position& pos, Bid start_bid, int start_row,
                          int start_column, int& final_row, int& final_column) {
    Bitboard bitboard = pos.GetSideToMoveBitboard();

    // Check if the player at (start_row, start_column) is the side-to-move
    if (HasPlayer(bitboard, start_row, start_column)) {
        return false;
    }

    int row = start_row;
    int column = start_column;
    Bid bid = start_bid;

    bool was_loop_step = false;
    int self_cross_count = 0;
    int loop_count = 0;
    Player turn_player = pos.GetSideToMovePlayer();

    while (true) {
        sk::calculateAttackPath(row, column, bid, row, column, bid,
                                was_loop_step);

        bool is_self_cross = false;

        // The original square is occupied by the moving piece; however, we are
        // allowed to jump over it.
        if (row == start_row && column == start_column) {
            self_cross_count++;
            is_self_cross = true;

            // Jumping over the original square indicates that the attack will
            // result in an infinite loop.
            if (self_cross_count > 1) {
                return false;
            }
        }

        Player occupant = pos.GetPlayer(row, column);

        if (!is_self_cross && occupant == turn_player) {
            return false;
        }

        if (was_loop_step) {
            ++loop_count;
        }
        if (occupant && occupant != turn_player) {
            if (loop_count == 0) {
                return false;
            }

            break;
        }
    }

    final_row = row;
    final_column = column;

    return true;
}

// Generates attack moves with toggle for including "non-capturing" ones.
void GenerateAttack(const Position& pos, bool gen_intermediate,
                    std::vector<Move> buffer) {
    int capture_row = 0;
    int capture_column = 0;

    int path_row = 0;
    int path_column = 0;
    Bid path_bid = WEST;
    bool is_loop = false;
    int loop_count = 0;

    // Loop through each direction & each square
    for (int start_bid = WEST; start_bid <= SOUTH; start_bid++) {
        for (int row = 0; row < 6; row++) {
            for (int column = 0; column < 6; column++) {
                bool attackValid =
                    IsAttackValid(pos, (Bid)start_bid, row, column, capture_row,
                                  capture_column);

                if (!attackValid) {
                    continue;
                }

                if (!gen_intermediate || !pos.options_.capture_optional_) {
                    buffer.push_back(CreateMove(row, column, capture_row,
                                                capture_column, true,
                                                (Bid)start_bid));
                    continue;
                }

                path_row = row;
                path_column = column;
                path_bid = (Bid)start_bid;
                loop_count = 0;

                while (path_row != capture_row &&
                       path_column != capture_column) {
                    calculateAttackPath(path_row, path_column, path_bid,
                                        path_row, path_column, path_bid,
                                        is_loop);
                    if (is_loop) {
                        ++loop_count;
                    }

                    if (loop_count == 0) {
                        continue;  // can land only after looping once
                    }

                    buffer.push_back(CreateMove(row, column, path_row,
                                                path_column, true,
                                                (Bid)start_bid));
                }
            }
        }
    }
}

// Generates only those attacking moves that capture on an enemy piece.
template <>
void GenerateMoves<CAPTURE>(const Position& pos, std::vector<Move> buffer) {
    GenerateAttack(pos, false, buffer);
}

// Generates capturing & non-capturing attack moves. If model-options disallow
// optional capturing, then only the former are included.
template <>
void GenerateMoves<ATTACK>(const Position& pos, std::vector<Move> buffer) {
    GenerateAttack(pos, true, buffer);
}

// Generates all possible (legal) moves for the side-to-move.
template <>
void GenerateMoves<LEGAL>(const Position& pos, std::vector<Move> buffer) {
    GenerateMoves<ATTACK>(pos, buffer);
    GenerateMoves<STEP>(pos, buffer);
}

std::vector<Move> GenerateMoves(const Position& pos) {
    std::vector<Move> move_buffer;

    GenerateMoves<LEGAL>(pos, move_buffer);

    return move_buffer;
}

}  // namespace sk<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.

// Author: <NAME> <<EMAIL>>

// This file defines the game-modelling class Surakarta.

#ifndef INCLUDE_SKMODEL_H_
#define INCLUDE_SKMODEL_H_

#include <stdint.h>

#include "skattack.h"
#include "skmodeloptions.h"
#include "skmove.h"

namespace sk {

enum Player : int {
    /// Alias for unknown player
    NULL_PLAYER = 0,

    /// Player that "generally" will move first. <code>Surakarta</code>'s first
    /// side-to-move is <code>RED</code> by default.
    RED = 1,

    /// Player that is "generally" the second to move.
    BLACK = 2
};

class Position;

/// Model class for holding a Surakarta game state. It can be used to supply a
/// position to the Surakarta engine.
class Surakarta {
   public:
    /// \param no_init - if true, then the board would not have the initial
    ///  layout of Surakarta (it would be blank).
    Surakarta(bool no_init = false);

    /// This is the recommended constructor for initializing an application's
    /// game-modelling object.
    ///
    /// \param options - configuration of the game rules
    Surakarta(ModelOptions &options);

    /**
     * Copies the given position.
     *
     * @param surakarta
     */
    Surakarta(Surakarta &surakarta);

    /**
     * @param mv
     * @return - whether the given move is legal in the current position
     */
    bool isLegal(Move mv);

    /**
     * @param start_row
     * @param start_column
     * @param final_row
     * @param final_column
     * @return - whether moving from (start_row, start_column) to (final_row,
     * final_column) is a legal non-attack move.
     */
    bool isLegalStep(int start_row, int start_column, int final_row,
                     int final_column);

    /**
     * Calculates whether the attack move is legal for the current position.
     *
     * Hint: If you have provided a "cut" square that does not lie on the attack
     * path, then that move is not legal. However, if the move is "legal without
     * the cut", then this method will still return true. To check if the attack
     * is legal "with the cut", check the equality the final row/column of the
     * returned move with the cut row/column.
     *
     * @param bid - initial bid of attack
     * @param start_row - starting square row
     * @param start_column - starting square column
     * @param [out] out - move holding all start/final squares.
     * @param cut_row - (optional) square's row to stop at before capturing
     * @param cut_column - (optional) square's column to stop after before
     * capturing
     * @return - whether the move is legal
     */
    bool isLegalAttack(Bid bid, int start_row, int start_column, Move *out = 0,
                       int cut_row = -1, int cut_column = -1);

    /**
     * Moves the piece at (start_row, start_column) to (final_row, final_column)
     * without checking whether it is valid.
     *
     * @param start_row
     * @param start_column
     * @param final_row
     * @param final_column
     */
    Surakarta *move(int start_row, int start_column, int final_row,
                    int final_column);

    /**
     * Expands and applies the given move without checking legality.
     *
     * @param move
     */
    Surakarta *move(Move mv);

    /**
     * Checks the legality of the attack bid and applies the move. If the cut
     * square is not on the attack path, the move will still proceed to capture.
     *
     * @param bid
     * @param start_row
     * @param start_column
     * @param [out] move - actual move that was executed
     * @param cut_row
     * @param cut_column
     */
    Surakarta *attack(Bid bid, int start_row, int start_column, Move * = 0,
                      int cut_row = -1, int cut_column = -1);

    /**
     * @returns - a clone of this position
     */
    Surakarta &clone();

    /**
     * @returns - an array of 36 "players" that can be used to directly edit
     *    the position.
     */
    inline Player *editable() { return this->board_; }

    /**
     * @param bid
     * @param start_row
     * @param start_column
     * @returns - the attacking move from the start square. 0 if it is illegal.
     */
    inline Move getAttack(Bid bid, int start_row, int start_column) {
        Move mv = 0;
        this->isLegalAttack(bid, start_row, start_column, &mv);

        return mv;
    }

   protected:
    Player *board_;
    unsigned int move_count_;
    ModelOptions &options_;

    inline bool isTurn(Player p) { return this->move_count_ % 2 == p; }

    inline Player getPlayer(int row, int column) {
        return this->board_[row * 6 + column];
    }

    inline Player getTurnPlayer() {
        return (Player)((this->move_count_ % 2) + 1);
    }

    inline bool canMoveFrom(int start_row, int start_column) {
        return this->isTurn(this->board_[start_row * 6 + start_column]);
    }

    friend class Position;
};

}  // namespace sk

#endif /* INCLUDE_SKMODEL_H_ */<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

#ifndef INCLUDE_SKMODELOPTIONS_H_
#define INCLUDE_SKMODELOPTIONS_H_

namespace sk {

/// <code>ModelOptions</code> provides options to "tweak" the rules of
/// Surakarta.
struct ModelOptions {
    /// Making a capture optional in an attack move allows the attacking piece
    /// to stop anywhere after looping once.
    bool capture_optional_;

    inline ModelOptions() : capture_optional_(false) {}
};

}  // namespace sk

#endif /* INCLUDE_SKMODELOPTIONS_H_ */<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

// This file defines bitboard helper methods.

#include <skmodel.h>

#ifndef INCLUDE_BITBOARD_H_
#define INCLUDE_BITBOARD_H_

#include "enginetypes.h"

namespace sk {

// Filled rows
constexpr Bitboard BB_ROW_1 = (1 << 7) - 1;
constexpr Bitboard BB_ROW_2 = BB_ROW_1 << (6 * 1);
constexpr Bitboard BB_ROW_3 = BB_ROW_1 << (6 * 2);
constexpr Bitboard BB_ROW_4 = BB_ROW_1 << (6 * 3);
constexpr Bitboard BB_ROW_5 = BB_ROW_1 << (6 * 4);
constexpr Bitboard BB_ROW_6 = BB_ROW_1 << (6 * 5);

// Filled columns
constexpr Bitboard BB_COL_1 =
    1 | (1 << 6) | (1 << 12) | (1 << 18) | (1 << 24) | (1 << 30) | (1l << 36);
constexpr Bitboard BB_COL_2 = BB_COL_1 << 1;
constexpr Bitboard BB_COL_3 = BB_COL_1 << 2;
constexpr Bitboard BB_COL_4 = BB_COL_1 << 3;
constexpr Bitboard BB_COL_5 = BB_COL_1 << 4;
constexpr Bitboard BB_COL_6 = BB_COL_1 << 5;

inline Bitboard createPlayerBitboard(Surakarta &model,
                                     Player player = NULL_PLAYER) {
    Bitboard board = 0;

    for (int i = 0; i < 36; i++) {
        if (model.editable()[i] == player) {
            board |= (1l << i);
        }
    }

    return board;
}

inline bool IsPlayerAt(const Bitboard &bitboard, int i) {
    return (bitboard >> i) & 1;
}

// Returns whether the i-th bit in the bitboard in set.
inline bool HasPlayer(const Bitboard &bitboard, int i) {
    return (bitboard >> i) & 1;
}

// Returns whether the bit corresponding to (row, column) is set in the
// bitboard.
inline bool HasPlayer(const Bitboard &bitboard, int row, int column) {
    return (bitboard >> (row * 6 + column)) & 1;
}

inline void LiftPlayer(Bitboard &bitboard, int i) { bitboard &= ~(1 << i); }
inline void PutPlayer(Bitboard &bitboard, int i) { bitboard |= (1 << i); }

}  // namespace sk

#endif /* INCLUDE_BITBOARD_H_ */
<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

#include <skmodel.h>

#include <cassert>
#include <iostream>

#include "factory/surakarta_factory.h"

void testAttackLegalityWhenBlocked() {
    std::vector<ModelBlock *> mocker = {new ModelBlock(sk::RED, 0, 0, 3, 3),
                                        new ModelBlock(sk::BLACK, 2, 0)};
    sk::Surakarta *model = createModel(mocker);

    // Piece is blocked, can't attack
    assert(!model->isLegalAttack(sk::NORTH, 2, 2));

    // Piece can directly loop and capture
    assert(model->isLegalAttack(sk::NORTH, 0, 2));

    // Piece is black and can't play on this turn
    assert(!model->isLegalAttack(sk::WEST, 2, 0));
}

int main() {
    testAttackLegalityWhenBlocked();
    return 0;
}<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

#include <stdint.h>

#ifndef INCLUDE_SKMOVE_H_
#define INCLUDE_SKMOVE_H_

namespace sk {

/**
 * Enumeration of possible directions an attack can go in.
 */
enum Bid : int { WEST = 0, NORTH = 1, EAST = 2, SOUTH = 3, NULL_BID = 7 };

/**
 * Types of moves
 */
enum MoveType : int { LEGAL = 0, STEP = 1, CAPTURE = 2, ATTACK = 3 };

/**
 * Encodes the start and final square of a move along with attack information.
 *
 * Bits:
 * 0-6:    Final row
 * 7-12:   Final column
 * 13-18:  Start row
 * 19-24:  Start column
 * 20:     Whether it is an attack
 * 21-23:  Initial direction of attack
 *
 * @see sk::createMove
 */
typedef uint64_t Move;

/**
 * Encodes a move.
 *
 * @param start_row
 * @param start_column
 * @param final_row
 * @param final_column
 * @param is_attack
 * @param bid - initial direction of attack
 */
inline Move CreateMove(int start_row, int start_column, int final_row,
                       int final_column, bool is_attack = false,
                       sk::Bid bid = NULL_BID) {
    return (final_row) | (final_column << 6) | (start_row << 12) |
           (start_column << 18) | (is_attack << 19) | (bid << 20);
}

inline int GetMoveStartRow(Move mv) { return mv & ((1 << 6) - 1); }

inline int GetMoveStartColumn(Move mv) { return (mv >> 6) & ((1 << 6) - 1); }

inline int GetMoveFinalRow(Move mv) { return (mv >> 12) & ((1 << 6) - 1); }

inline int GetMoveFinalColumn(Move mv) { return (mv >> 18) & ((1 << 6) - 1); }

inline bool IsMoveAttack(Move mv) { return (mv >> 19) & 1; }

inline Bid GetMoveBid(Move mv) { return (Bid)((mv >> 20) & ((1 << 3) - 1)); }

}  // namespace sk

#endif /* INCLUDE_SK_MOVE_H_ */<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

// This file defines a bitboard-based modelling class. This is a "lighter
// version" of Surakarta, and is used by the engine internally.

#ifndef ENGINE_POSITION_H_
#define ENGINE_POSITION_H_

#include "../../include/skmodel.h"
#include "bitboard.h"
#include "enginetypes.h"

namespace sk {

// Position models a Surakarta position using one bitboard for each player.
class Position {
   public:
    Position(Surakarta &surakarta);
    Position(const Position &clone);

    // Heuristic to evaluate this position
    Value evaluate();

    Bitboard getBitboard(Player player) {
        return player_bitboards_[player - 1];
    }

    void copyFrom(Position &position);
    void move(Move move);

    ModelOptions &options_;

   protected:
    Bitboard *player_bitboards_;
    int count_red_;
    int count_black_;

   public:
    Player side_to_move_;

    // Player who will move in the current turn.
    inline Player GetSideToMovePlayer() const { return side_to_move_; }

    // White player's bitboard.
    inline Bitboard GetRedPlayerBitboard() const {
        return this->player_bitboards_[0];
    }

    // Black player's bitboard.
    inline Bitboard GetBlackPlayerBitboard() const {
        return this->player_bitboards_[1];
    }

    // Bitboard for the side-to-move player
    inline Bitboard GetSideToMoveBitboard() const {
        return this->player_bitboards_[this->side_to_move_ - 1];
    }

    // Combined red & black bitboard. It can used to check if any player is at a
    // given square.
    inline Bitboard GetNonNullBitboard() const {
        return this->player_bitboards_[0] | this->player_bitboards_[1];
    }

    // Returns the player at the given square.
    inline Player GetPlayer(int i) const {
        if (HasPlayer(GetRedPlayerBitboard(), i)) {
            return RED;
        }
        if (HasPlayer(GetBlackPlayerBitboard(), i)) {
            return BLACK;
        }

        return NULL_PLAYER;
    }

    // Returns the player at (row, column).
    inline Player GetPlayer(int row, int column) const {
        return GetPlayer(row * 6 + column);
    }

};  // namespace sk

inline bool IsPlayerAt(Bitboard bitboard, Square square) {
    return (bitboard >> square) & 1;
}

}  // namespace sk

#endif /* ENGINE_POSITION_H_ */<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

#include <skmodel.h>

#include <cstdlib>
#include <iostream>

namespace sk {

// Used as a placeholder when ModelOptions is not provided.
ModelOptions kNullOptions;

void WriteInitialLayout(Player *board) {
    for (int i = 0; i < 12; i++) {
        board[i] = RED;
    }

    for (int i = 12; i < 24; i++) {
        board[i] = NULL_PLAYER;
    }

    for (int i = 36 - 12; i < 36; i++) {
        board[i] = BLACK;
    }
}

Surakarta::Surakarta(bool no_init)
    : options_(no_init ? kNullOptions : *new ModelOptions()) {
    board_ = new Player[36];
    move_count_ = 0;

    if (!no_init) {
        WriteInitialLayout(board_);
    }
}

Surakarta::Surakarta(ModelOptions &options) : options_(options) {
    board_ = new Player[36];
    move_count_ = 0;

    WriteInitialLayout(board_);
}

Surakarta::Surakarta(Surakarta &copy) : options_(copy.options_) {
    board_ = new Player[36];
    move_count_ = copy.move_count_;

    for (int i = 0; i < 36; i++) {
        board_[i] = copy.board_[i];
    }
}

bool Surakarta::isLegal(Move mv) {
    if (IsMoveAttack(mv)) {
        Move actual;

        isLegalAttack(GetMoveBid(mv), GetMoveStartRow(mv),
                      GetMoveStartColumn(mv), &actual, GetMoveFinalRow(mv),
                      GetMoveFinalColumn(mv));

        return (mv & ((1 << 24) - 1)) ==
               actual;  // move encoding bits are equal
    }

    return isLegalStep(GetMoveStartRow(mv), GetMoveStartColumn(mv),
                       GetMoveFinalRow(mv), GetMoveFinalColumn(mv));
}

bool Surakarta::isLegalStep(int start_row, int start_column, int final_row,
                            int final_column) {
    return canMoveFrom(start_row, start_column) &&
           getPlayer(final_row, final_column) &&
           std::abs(start_row - final_row) <= 1 &&
           std::abs(final_row - final_column) <= 1;
}

bool Surakarta::isLegalAttack(Bid start_bid, int start_row, int start_column,
                              Move *out, int cut_row, int cut_column) {
    if (getPlayer(start_row, start_column) != getTurnPlayer()) {
        return false;  // not player's turn
    }

    int row = start_row;
    int column = start_column;
    Bid bid = start_bid;
    bool was_loop_step = false;

    int self_cross_count =
        0;  // no. of times we have passed through our initial square
    int loop_count = 0;      // no. of times we have looped
    bool cut_found = false;  // whether we passed over cut square
    Player turn_player = getTurnPlayer();

    while (true) {
        calculateAttackPath(row, column, bid, row, column, bid, was_loop_step);

        bool is_self_cross = false;

        if (row == start_row && column == start_column) {
            self_cross_count++;
            is_self_cross = true;

            if (self_cross_count > 1) {
                return false;  // indicates infinite loop
            }
        }

        Player occupant = getPlayer(row, column);

        if (!is_self_cross && occupant == turn_player) {
            return false;  // can't land on our own piece
        }

        if (was_loop_step) {
            ++loop_count;
        }
        if (cut_row == row && cut_column == column) {
            cut_found = true;
        }
        if (occupant && occupant != turn_player) {
            if (loop_count == 0) {
                return false;  // must loop once to capture
            }

            break;
        }
    }

    if (out) {
        *out = CreateMove(start_row, start_column, cut_found ? cut_row : row,
                          cut_found ? cut_column : column, true, start_bid);
    }

    return true;
}

Surakarta *Surakarta::move(int start_row, int start_column, int final_row,
                           int final_column) {
    int start_square = start_row * 6 + start_column;
    int final_square = final_row * 6 + final_column;

    board_[final_square] = board_[start_square];
    board_[start_square] = NULL_PLAYER;

    return this;
}

Surakarta *Surakarta::move(Move move) {
    this->move(GetMoveStartRow(move), GetMoveStartColumn(move),
               GetMoveFinalRow(move), GetMoveFinalColumn(move));

    return this;
}

Surakarta *Surakarta::attack(Bid bid, int start_row, int start_column,
                             Move *result, int cut_row, int cut_column) {
    Move lresult;

    if (!result) {
        result = &lresult;
    }

    bool isLegal = isLegalAttack(bid, start_row, start_column, result, cut_row,
                                 cut_column);

    if (isLegal) {
        move(*result);
    } else {
        *result = 0;
    }

    return this;
}

Surakarta &Surakarta::clone() { return *new Surakarta(*this); }

}  // namespace sk<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

// This file defines helper methods to trace the path of an attacking move.

#include "skmove.h"

#ifndef INCLUDE_SKATTACK_H_
#define INCLUDE_SKATTACK_H_

namespace sk {

// Calculates the radius of the loop emerging from (terminal_row,
// terminal_column)
inline int calculateLoopRadius(int terminal_row, int terminal_column) {
    if (terminal_row == 0 || terminal_row == 5) {
        return terminal_row < 3 ? terminal_column : 5 - terminal_column;
    } else {
        return terminal_column < 3 ? terminal_row : 5 - terminal_row;
    }
}

// Calculates the bid required to loop from the given loop terminal square.
inline Bid calculateLoopBid(int terminal_row, int terminal_column) {
    if (terminal_row == 0) {
        return NORTH;
    } else if (terminal_row == 5) {
        return SOUTH;
    } else if (terminal_column == 0) {
        return WEST;
    } else if (terminal_column == 5) {
        return EAST;
    }

    return NULL_BID;
}

// Calculates the other terminal of the loop emerging from (in_row, in_column).
// It also finds the outgoing bid for a piece.
inline void calculateLoopTerminal(int in_row, int in_column, int &out_row,
                                  int &out_column, Bid &out_bid) {
    Bid in_bid = calculateLoopBid(in_row, in_column);
    int loop_radius = calculateLoopRadius(in_row, in_column);

    switch (in_bid) {
        case NORTH:
            out_row = loop_radius;
            out_column = in_column < 3 ? 0 : 5;
            out_bid = in_column < 3 ? EAST : WEST;
            return;
        case WEST:
            out_row = in_row < 3 ? 0 : 5;
            out_column = loop_radius;
            out_bid = in_row < 3 ? SOUTH : NORTH;
            return;
        case SOUTH:
            out_row = 5 - loop_radius;
            out_column = in_column < 3 ? 0 : 5;
            out_bid = in_column < 3 ? EAST : WEST;
            return;
        case EAST:
            out_row = in_row < 3 ? 0 : 5;
            out_column = 5 - loop_radius;
            out_bid = in_row < 3 ? SOUTH : NORTH;
            return;
        default:
            return;
    }
}

// Calculates the next square for a piece in an attacking move.
inline void calculateAttackPath(int in_row, int in_column, Bid in_bid,
                                int &out_row, int &out_column, Bid &out_bid,
                                bool &is_loop) {
    out_bid = in_bid;  // if no looping occurs
    is_loop = false;

    switch (in_bid) {
        case NORTH:
            if (in_row - 1 > 0) {
                out_row = in_row - 1;
                out_column = in_column;
                return;
            }
            break;
        case WEST:
            if (in_column - 1 > 0) {
                out_row = in_row;
                out_column = in_column - 1;
                return;
            }
            break;
        case SOUTH:
            if (in_row + 1 < 5) {
                out_row = in_row + 1;
                out_column = in_column;
                return;
            }
            break;
        case EAST:
            if (in_column + 1 < 6) {
                out_row = in_row;
                out_column = in_column + 1;
                return;
            }
        default:
            break;
    }

    // Must loop here!
    calculateLoopTerminal(in_row, in_column, out_row, out_column, out_bid);
    is_loop = true;
}

}  // namespace sk

#endif /* INCLUDE_SKATTACK_H_ */<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

#ifndef INCLUDE_SKENGINE_H_
#define INCLUDE_SKENGINE_H_

#include "skmodel.h"

namespace sk {

struct EngineOptions {};

/// Runs the Surakarta engine on the given position.
Move Think(Surakarta &position, EngineOptions &eg_options);

}  // namespace sk

#endif /* INCLUDE_SKENGINE_H_ */<file_sep>cmake_minimum_required(VERSION 3.9)

project(libsurakarta 
    VERSION 1.0.1 
    DESCRIPTION "Surakarta Engine")

include(GNUInstallDirs)
include(CTest)

enable_testing()

set (CMAKE_CXX_STANDARD 11)

add_library(libsurakarta 
    SHARED 
    src/model/surakarta.cc
    src/engine/position.cc)

set_target_properties(libsurakarta 
    PROPERTIES
        PREFIX ""
        VERSION ${PROJECT_VERSION}
        SOVERSION 1
        PUBLIC_HEADER include/Surakarta.hpp
        DESCRIPTION "C++ implementation of a Surakarta computer engine"
        HOMEPAGE_URL https://github.com/SurakartaArcade/libsurakarta
        LANGUAGES CXX)

target_include_directories(
    libsurakarta 
    PUBLIC 
    include)

install(TARGETS libsurakarta
    LIBRARY 
        DESTINATION ${CMAKE_INSTALL_LIBDIR}
    PUBLIC_HEADER 
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

install(FILES ${CMAKE_BINARY_DIR}/mylib.pc
    DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)

if (BUILD_TESTING)
    add_executable(test_skmodel test/skmodel.cc test/factory/surakarta_factory.cc)

    target_include_directories(
        test_skmodel
        PUBLIC
        include
    )

    target_link_libraries(
        test_skmodel
        libsurakarta
    )

    add_test(SurakartaModel test_skmodel)
endif()<file_sep>// Copyright (C) 2020 <NAME> <<EMAIL>>
//
// This file is part of libsurakarta.
//
// libsurakarta is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// libsurakarta is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with libsurakarta.  If not, see <https://www.gnu.org/licenses/>.
//

// Author: <NAME> <<EMAIL>>

#include "surakarta_factory.h"

#include <skmodel.h>

sk::Surakarta *clearModel(sk::Surakarta &model) {
    for (int i = 0; i < 36; i++) {
        model.editable()[i] = sk::NULL_PLAYER;
    }

    return &model;
}

sk::Surakarta *createModel(std::vector<ModelBlock *> &arr) {
    sk::Surakarta *model = clearModel(*new sk::Surakarta());

    for (int i = 0; i < arr.size(); i++) {
        ModelBlock *block = arr.at(i);

        for (int dr = 0; dr < block->rowRange; dr++) {
            for (int dc = 0; dc < block->columnRange; dc++) {
                model
                    ->editable()[(block->row + dr) * 6 + (block->column + dc)] =
                    block->color;
            }
        }
    }

    return model;
}