For the ruleset, it is not very difficult.
In abstract way, you define a coordinate system. You will have your logic to move pieces around, also capture mouse in precise "squares" and also draw them.
If they are hexagons, there is a math overhead for that, but that's pretty much it. You define moves according to the degrees of freedom in that particular coordinate system.
So say you have a small fellow called pawn, just make this fellow
struct
{
Axis top_left{-1,-1};
Axis top{0,-1};
Axis top_right{+1,-1};
}Pawn_Move_White;
`Axis is just the object of your coord sys that you will make your coord sys logic with
struct Axis
{
Axis() : q(-1), r(-1) {}
Axis(int8_t q, int8_t r) : q(q), r(r) {}
int8_t q, r;
bool operator==(const Axis other) const;
bool operator!=(const Axis other) const;
Axis operator+(const Axis other) const;
Axis operator-(const Axis other) const;
};
Ideally you want this sort of stuff small because of minmax search.
Ok, back to rules. The first struct is nothing but a "physical" rule set for moves that fellow can perform. As opposed to "legal" moves. That fellow can go to "square" {q,r} physically (poetic license) if it is in square {q,r+1}, say, because top{0,-1} is a move, but it may not be legal if there is another friendly pawn in that square.
So now we define some other layer of legal stuff, who captures who, who can do special stuff with who, and so on and on, all the rules are nothing but a bunch of branching.
Some of them are not so trivial. For instance, if you have something like the Rook in chess (moves in straight "lines"), you want to do a check like this (hope I didn't screw up lol)
static bool rook_path_is_free(GPiece piece, Axis begin, Axis end)
{
/// trivial case
if (end == begin) return true;
Axis diff = end - begin;
Axis dir = Axis((diff.q>0) - (diff.q<0), (diff.r>0) - (diff.r<0));
Axis current = begin;
/// use do{}while(); instead of while(){} because we need to check when current == end
do {
current = current + dir;
if(Board.get_piece(axis_to_idx(current)).id != Pid::NO_PIECE) {
if (Board.get_piece(axis_to_idx(current)).color == piece.color) {
return false;
}
/// if the piece is an enemy piece before the end square, don't let it move beyond it,
/// but this check will return ok if the piece is at the destination (end) square
else {
if (current != end) {
return false;
}
}
}
} while (current != end);
return true;
}
And other stuff can get equally complicated, perhaps more.
So that's it for the game logic abstract layer, it is actually fun. There is only some extra complication for me cuz I use bitboards because of minmax search (e.g. axis_to_idx()
is such a func that will get the axis and actually retrieve from a uint64 bitboard a color, from other uint64 the piece kind, but that's not necessary to make any board game, prob a map w/ "axis" as key will do??)