Chess and Engine Programming Continued.
Handling Left Mouse Presses
My previous chess post was becoming far too long, so naturally this post is a continuation of that. Today I’ll be showing my function that handles left mouse presses.
I’m still working on getting the game of chess working as I’ve refactored the same code about a million times to get it working with bitboards, which was the whole point of using them. They make things very easy once a suitable foundation is established. For some reason I got the movement and left mouse button (LMB) working without them and had to go back when trying newer stuff because it just didn’t’ make sense.
I cleaned up the file structure so now I have a nice build, src and include folder for respective files. As well as CMake working nicely with my assets and building correctly, I did have some issues where they were not being loaded because CMake builds from /build and the executable was also in there meaning it was looking for the assets in /build/. This is now fixed temporarily by putting the executable in the root directory.
Resized Images
Before this change I was just choosing the best size image for the size of window that the user selected. This meant the size of the pieces in the squares would vary, which was fine but probably not ideal considering we want to support most window sizes.
This was changed to have the piece .pngs resized depending on this value instead. Meaning the piece fills out the square regardless of the size of the window. Hopefully you can trust that this is the case as getting two different size pics in here is going to be a nightmare, I also can’t be bothered to get a picture of how it was before. Very sorry. Trust me its way better.
The only problem right now with this is the pixelation at much bigger sizes say greater than 1600x1600px, but first world problems eh.
Handling Left Mouse Presses
This may sound quite simple but for my program at least a lot needs to happen when the user presses the left mouse.
What follows is a bit of a tree of parent and child functions, I will try to give an explanation for most of them below.
Starting with the main mouse press handling function shown here.
1
2
3
4
5
6
7
8
9
10
void Board::on_mouse_press(sf::Event &event) {
auto mouse_press = event.mouseButton.button;
switch (mouse_press) {
case sf::Mouse::Left:
on_left_mouse_press();
break;
}
}
A very simple switch statement for our mouse press, upon a left mouse press we call on_left_mouse() shown below. This is the function with a few child functions, I’m quite proud of its un-indented and guard-filled structure.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void Board::on_left_mouse_press() {
uint8_t clicked_bit = mouse_win_pos_to_bit();
if (!selected_piece) {
// If we dont have a selected piece, try and get one.
selected_piece = select_piece(clicked_bit);
return;
}
// Save this bit so we can reset the color later in deselect_piece().
uint8_t old_bit = selected_piece->bit;
// If our click is not an attack then go again/reset.
if (!BitboardHelper::get_bit(selected_piece->attacks, clicked_bit)) {
// would like deselect_piece()
deselect_piece(old_bit);
return;
}
// Will be executed if we have a selected piece and its an attack.
handle_piece_move(clicked_bit);
deselect_piece(old_bit);
}
There is quite a lot going on here so bare with me, I’m going to break it down into steps. There are a few guarding if statements and upon passing those we progress.
It handles multiple LMB presses too as you will see from these guarding if-statements.
- Getting the bit that the user has clicked on.
- Checking if we have a piece selected, if not we try and get one. If we do have a selected piece then we can continue.
- We now save the bit of the selected piece so we can reset the square color later.
- If our click is not in our pieces attacks bitboard then we will also reset and deselect the selected piece.
- If we’ve got this far then we have selected a piece and then selected a valid square to attack, we can thus handle moving the piece there and reset the square highlights.
Step 1: mouse_win_pos_to_bit()
Here is the function, its not too complex SFML does make it fairly easy as we can query the mouses window position by adding the window as an argument.
1
2
3
4
5
6
7
8
9
int Board::mouse_win_pos_to_bit() {
// May need to be broken up into smaller functions.
sf::Vector2i mouse_window_pos = sf::Mouse::getPosition(main_window);
sf::Vector2i mouse_square_pos((mouse_window_pos.x / board_square_size),
(mouse_window_pos.y / board_square_size));
int square = (mouse_square_pos.y * GRID_SZ) + mouse_square_pos.x;
return BitboardHelper::square_to_bit(square);
}
And from there its fairly simple to convert this into the correct bit the user will be modifying.
Where bit 0 is H1 and bit 63 is A8 on the chess board. I know this isn’t a standard mapping but its what made sense to me.
This will obviously become more interesting when I add the player and turns.
Step 2: Selecting a Piece If We Can
This logic in the parent function is just checking if a pointer to a selected_piece in our pieces vector is not null. If it is null we will try and select a piece on the square clicked and return early ready for the next left mouse click.
1
2
3
4
if (!selected_piece) {
selected_piece = select_piece(clicked_bit);
return;
}
Here is how a piece is selected, the piece if we find one returns a pointer. The function get_piece just loops through all pieces and finds the piece on the same bit as the square or bit we clicked and returns it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Piece* Board::select_piece(uint8_t clicked_bit) {
Piece* piece = get_piece(clicked_bit);
if (!piece) return nullptr;
// If not your turn you can't select a piece.
// Temporary turn solution.
if (piece->color == Color::BLACK && is_whites_turn) return nullptr;
if (piece->color == Color::WHITE && !is_whites_turn) return nullptr;
squares[clicked_bit].setFillColor(TURQOISE);
piece->attacks = piece->get_legal_moves(white_occupancy(), black_occupancy());
piece->highlight_legal_moves(piece->attacks, squares);
return piece;
}
We then check who can make a move black or white and allow them to do so if its their turn. This logic is temporary as I want a GameState class eventually which will handle this and take a lot of Board class members to further modularise.
We’ll highlight the square the selected piece is on, calculate all its legal moves by passing get_legal_moves() the white and black occupancy bitboards.
Once we have the legal attacks, or pseudo legal attacks that doesn’t take king checks into account yet, we highlight these move squares. This works well for now and I like it but I do want a Move class perhaps where we can store moves, log and undo them. But this is a problem for future me.
Step 3: Save the Bit of the Selected Piece
The snippet below just saves the selected pieces bit which represents where it is on the board, this is so we can change the selected_piece->bit before resetting the square highlight. Not ideal perhaps but its only one 8 bit integer, not trying to send a ship to space here.
1
uint8_t old_bit = selected_piece->bit;
Step 4: Handling Resets
This check is hopefully self-documenting, but we’re just checking whether the bit we’ve clicked on is an attack. We can do this because at this point in the flow of the program as by now selected_piece will not be null, and have some generated pseudo-legal moves.
1
2
3
4
5
if (!BitboardHelper::get_bit(selected_piece->attacks, clicked_bit)) {
// would like deselect_piece()
deselect_piece(old_bit);
return;
}
Here is what deselect piece looks like, it simply resets the square highlighting by taking in the bit we want to reset.
1
2
3
4
5
6
7
8
9
10
11
12
13
void Board::deselect_piece(uint8_t old_bit) {
// Old bit is bit of piece we clicked on.
reset_square_color(old_bit);
// Reset square color of its attacks now its not selected.
for (int i = H1; i <= A8; i++) {
if (BitboardHelper::get_bit(selected_piece->attacks, i)) {
reset_square_color(i);
}
}
selected_piece = nullptr;
}
At the end we set selected_piece to nullptr as at this point we have selected a piece and then clicked on a non-valid move square so we reset selected_piece in anticipation of the next user click.
Step 5: Handling the Move.
And finally if we get through everything up to this point we can move the piece to the clicked_bit before resetting.
1
2
3
// Will be executed if we have a selected piece and its an attack.
handle_piece_move(clicked_bit);
deselect_piece(old_bit);
Here is handle_piece_move() if you are interested, the comment I left myself I feel explains the functional adequately.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void Board::handle_piece_move(uint8_t clicked_bit) {
/*
This function is a bit funny. For it to be called selected_piece
must not be null so no check needed.
Essentially we find the bitboard of the selected piece type and update
it.
Find the bitboard of the clicked bit if any, clear the bitboard bit and
place the selected_piece there by updating its piece->bit.
If a piece is selected we cannot attack/move to a friendly piece, so no
need for that logic.
*/
for (auto& bitboard: bitboards) {
// TODO: Log the move into a move class.
// TODO: Allow moves to be undone.
if (BitboardHelper::get_bit(bitboard, selected_piece->bit)){
bitboard = BitboardHelper::clear_bit(bitboard, selected_piece->bit);
bitboard = BitboardHelper::set_bit(bitboard, clicked_bit);
} else if (BitboardHelper::get_bit(bitboard, clicked_bit)) {
bitboard = BitboardHelper::clear_bit(bitboard, clicked_bit);
// Find piece and remove piece from pieces vector.
auto it = std::find_if(pieces.begin(), pieces.end(), [clicked_bit](Piece* p) {
return p->bit == clicked_bit;
});
if (it != pieces.end()) {
delete *it; // free the memory
pieces.erase(it); // remove from vector
}
}
}
selected_piece->bit = clicked_bit;
}
That is the end of this post, hopefully you learned something from that or found it vaguely interesting. Regardless thanks for reading, these posts will keep coming over the next few weeks as I continue.
As of right now I only have get_legal_moves() for knights so the next order of business will be giving all of the other pieces some moves.
One of the pieces moves will likely be the next post here!