Skip to content

Commit

Permalink
A lot of non functional changes - #59
Browse files Browse the repository at this point in the history
* Add hash full
* Add move overhead uci option
* Disable pretty output by default and improve readme
* Network visualiser
* User sanitizer
* Fix format
* Refactor uci.h
* Improve readme and add logos
* Add neuron indices
* Fix bench speed
* Add stat tracker
* Very pretty output
* Add images
* Fix user input breaking the pretty output
* Limit the pv to 10 moves/line
* Fake SAN pretty output
* Add true SAN

Bench: 2134743
  • Loading branch information
SzilBalazs authored Aug 17, 2023
1 parent 5fec92c commit 0310796
Show file tree
Hide file tree
Showing 30 changed files with 554 additions and 163 deletions.
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,17 @@ endif
# Native build with debug symbols
ifeq ($(build), debug)
TARGET_FLAGS = -g3 -fno-omit-frame-pointer
ARCH_FLAGS = -march=native
DEFINE_FLAGS = -DNATIVE
else
DEFINE_FLAGS += -DNDEBUG
endif

ifeq ($(build), stats)
DEFINE_FLAGS += -DTRACK_STATS
endif

EVALFILE = weights/master.bin
TMP_EVALFILE = tmp.bin
DEFINE_FLAGS += -DVERSION=\"v$(VERSION_MAJOR).$(VERSION_MINOR).$(HASH)\" -DNDEBUG -D_CRT_SECURE_NO_WARNINGS
DEFINE_FLAGS += -DVERSION=\"v$(VERSION_MAJOR).$(VERSION_MINOR).$(HASH)\" -D_CRT_SECURE_NO_WARNINGS
CXXFLAGS = $(DEFINE_FLAGS) $(ARCH_FLAGS) -std=c++20 -O3 -flto=auto -pthread -Wall
EXE = $(NAME)
OUTPUT_BINARY = $(EXE)$(SUFFIX)
Expand Down
35 changes: 22 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
<h1 align="center">WhiteCore</h1>

<p align="center">
<a href="https://www.runpod.io/">
<img
Expand All @@ -15,6 +13,15 @@
WhiteCore stands as a strong C++ chess engine using efficiently-updatable neural networks for positional evaluation.
It's the successor of BlackCore reimagined with focus on originality.

## Table of Contents

- [Overview](#overview)
- [Getting Started](#getting-started)
- [Usage](#usage)
- [Custom UCI Commands](#custom-uci-commands)
- [Thanks to](#thanks-to)
- [License](#license)

## Getting Started

The following commands can be used to build a native binary for the best performance.
Expand All @@ -33,17 +40,18 @@ WhiteCore in itself is a command line program, and requires a UCI compatible
Chess GUI (like <a href="https://github.com/cutechess/cutechess">Cute Chess</a>
or <a href="http://www.playwitharena.de/">Arena</a>) for the best user experience.

## Files
## Custom UCI Commands

This project contains the following files:

- **README.md** the file that you are reading.
- **LICENSE** containing the license of this repository.
- **src** folder contains the source code of WhiteCore
- **scripts** folder contains scripts
- **weights** folder contains neural networks
- **train** is used for training NNUE
- **.github** folder contains automated GitHub workflows like building this project.
| Command | Description |
|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `pretty` | Enables the pretty output option for the console. This improves the readability of the search reports by adding indentation and colors. Can be disabled by sending the \"uci\" command. |
| `display` | Displays the current board status. |
| `eval` | Evaluates and displays the current board state using nnue. |
| `gen` | Generates self-play games using specific parameters. |
| `split` | Splits input data into two output datasets in a particular proportion. |
| `quantize` | Quantizes the neural network weights for performance reasons. |
| `train` | Trains a neural network with specific parameters. |
| `perft` | Used for performance testing and validation of the move generator. |

## Thanks to...

Expand All @@ -58,5 +66,6 @@ FindingChess is a fork of the OpenBench framework, a platform that has played an
in the development process. It allows us to test ideas easily
using a distributed computational framework.

## License


This project is licensed under GPL-3. For the full license, see LICENSE.
Binary file added img/go_report.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/logo0.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/logo1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/weights0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/weights1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 73 additions & 0 deletions scripts/network_to_img.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# WhiteCore is a C++ chess engine
# Copyright (c) 2023 Balázs Szilágyi
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#

import argparse
import struct
import numpy as np
import matplotlib.pyplot as plt


col_labels = ["WK", "WP", "WN", "WB", "WR", "WQ",
"BK", "BP", "BN", "BB", "BR", "BQ"]


def main():
parser = argparse.ArgumentParser()
parser.add_argument('input_file', help='Path to the network file')
args = parser.parse_args()

with open(args.input_file, 'rb') as f:
magic_raw = f.read(4)
magic = struct.unpack('i', magic_raw)[0]
assert magic == -4

data = f.read(256 * 2)
biases = struct.unpack('256h', data)

data = f.read(768 * 256 * 2)
weights = struct.unpack('196608h', data)
weights = np.array(weights)
weights = weights.reshape((768, 256))
fig, axs = plt.subplots(8, 12, figsize=(6, 4))
plt.subplots_adjust(hspace=0.1, wspace=0.1)
for base in range(32):
for neuron in range(8):
numbers = weights[:, neuron + base * 8]
numbers = np.array(numbers).reshape((96, 8))

row_start = 0
row_end = 7
for i in range(12):
x = numbers[row_start:row_end, :]
img = np.interp(x, (-128, 128), (0, 255)).astype(np.uint8)
axs[neuron][i].set_xticks([])
axs[neuron][i].set_yticks([])
axs[neuron][i].imshow(img, cmap='copper')
row_start += 8
row_end += 8

for i in range(12):
axs[7][i].set_xlabel(col_labels[i])

for i in range(8):
axs[i][0].set_ylabel(i + base * 8 + 1)

plt.savefig(f'img/network_{base}.png')


if __name__ == '__main__':
main()
32 changes: 22 additions & 10 deletions src/chess/board.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "move.h"

#include <algorithm>
#include <regex>
#include <sstream>
#include <vector>

Expand Down Expand Up @@ -207,15 +208,7 @@ namespace chess {
}

if (move.is_promo()) {
if (move.eq_flag(Move::PROMO_BISHOP) || move.eq_flag(Move::PROMO_CAPTURE_BISHOP)) {
piece_moved.type = BISHOP;
} else if (move.eq_flag(Move::PROMO_KNIGHT) || move.eq_flag(Move::PROMO_CAPTURE_KNIGHT)) {
piece_moved.type = KNIGHT;
} else if (move.eq_flag(Move::PROMO_ROOK) || move.eq_flag(Move::PROMO_CAPTURE_ROOK)) {
piece_moved.type = ROOK;
} else if (move.eq_flag(Move::PROMO_QUEEN) || move.eq_flag(Move::PROMO_CAPTURE_QUEEN)) {
piece_moved.type = QUEEN;
}
piece_moved.type = move.get_promo_type();
}

move_piece(piece_moved, from, to, nnue);
Expand Down Expand Up @@ -292,7 +285,13 @@ namespace chess {
states.pop_back();
}

void load(const std::string &fen) {
void load(const std::string &fen, bool validate_fen = false) {

if (validate_fen && !is_valid_fen(fen)) {
print("info", "error", "Invalid fen:", fen);
return;
}

board_clear();

std::stringstream ss(fen);
Expand Down Expand Up @@ -322,6 +321,7 @@ namespace chess {

state.rights = CastlingRights(rights);
state.ep = square_from_string(ep);

if (!move50.empty() && std::all_of(move50.begin(), move50.end(), ::isdigit)) {
state.move50 = std::stoi(move50);
} else {
Expand Down Expand Up @@ -473,6 +473,18 @@ namespace chess {
states.clear();
states.emplace_back();
}

static bool is_valid_fen(const std::string &fen) {
const static std::regex fen_regex("^"
"([rnbqkpRNBQKP1-8]+\\/){7}"
"([rnbqkpRNBQKP1-8]+)"
" [bw]"
" ([-KQkq]+|)"
" (([a-h][36])|-)"
" \\d+"
".*");
return std::regex_match(fen, fen_regex);
}
};


Expand Down
1 change: 0 additions & 1 deletion src/chess/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ using Ply = int8_t;

constexpr int64_t INF_TIME = 1'000'000'000'000'000;
constexpr int64_t INF_NODES = 1'000'000'000'000'000;
constexpr int64_t MOVE_OVERHEAD = 30;
constexpr Score UNKNOWN_SCORE = 300000;
constexpr Score INF_SCORE = 200000;
constexpr Score MATE_VALUE = 100000;
Expand Down
14 changes: 14 additions & 0 deletions src/chess/move.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ namespace chess {
return is_flag(PROMO_FLAG);
}

// Returns the promoted piece type
[[nodiscard]] constexpr PieceType get_promo_type() const {
if (eq_flag(Move::PROMO_BISHOP) || eq_flag(Move::PROMO_CAPTURE_BISHOP)) {
return BISHOP;
} else if (eq_flag(Move::PROMO_KNIGHT) || eq_flag(Move::PROMO_CAPTURE_KNIGHT)) {
return KNIGHT;
} else if (eq_flag(Move::PROMO_ROOK) || eq_flag(Move::PROMO_CAPTURE_ROOK)) {
return ROOK;
} else if (eq_flag(Move::PROMO_QUEEN) || eq_flag(Move::PROMO_CAPTURE_QUEEN)) {
return QUEEN;
}
return PIECE_EMPTY;
}

// Returns true if the move has the SPECIAL1_FLAG set
[[nodiscard]] constexpr bool is_special_1() const {
return is_flag(SPECIAL1_FLAG);
Expand Down
11 changes: 11 additions & 0 deletions src/chess/move_generation.h
Original file line number Diff line number Diff line change
Expand Up @@ -546,4 +546,15 @@ namespace chess {
return bool(chess::get_attackers(*this, pieces<KING>(get_stm()).lsb()));
}

chess::Move move_from_string(const chess::Board &board, const std::string &str) {
chess::Move moves[200];
chess::Move *moves_end = chess::gen_moves(board, moves, false);
for (chess::Move *it = moves; it != moves_end; it++) {
if (it->to_uci() == str) {
return *it;
}
}
return chess::NULL_MOVE;
}

} // namespace chess
14 changes: 13 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,22 @@ namespace chess {

namespace search {
Depth lmr_reductions[200][MAX_PLY + 1];
}
int64_t TimeManager::MOVE_OVERHEAD = 30;
} // namespace search

void init_all() {
chess::init_masks();
chess::init_magic();
search::init_lmr();

stat_tracker::add_stat("tt_hit");
stat_tracker::add_stat("tt_cutoff");
stat_tracker::add_stat("nmp");
stat_tracker::add_stat("rfp");
stat_tracker::add_stat("pvs_see_quiet");
stat_tracker::add_stat("pvs_see_capture");
stat_tracker::add_stat("qsearch_see");
stat_tracker::add_stat("skip_quiets");
}

int main(int argc, char *argv[]) {
Expand Down Expand Up @@ -67,5 +77,7 @@ int main(int argc, char *argv[]) {
protocol.start();
}

stat_tracker::print_stats();

return 0;
}
2 changes: 1 addition & 1 deletion src/network/data_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ namespace nn {
file.open(path, std::ios::in);

if (!file.is_open()) {
Logger("Unable to open:", path);
print("Unable to open:", path);
throw std::runtime_error("Unable to open: " + path);
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/network/network.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ namespace nn {
Network(const std::string &network_path) {
std::ifstream file(network_path, std::ios::in | std::ios::binary);
if (!file.is_open()) {
Logger("Unable to open: ", network_path);
print("Unable to open: ", network_path);
randomize();
return;
}
Expand All @@ -61,14 +61,14 @@ namespace nn {
file.read(reinterpret_cast<char *>(&magic), sizeof(magic));

if (magic != MAGIC) {
Logger("Invalid network file: ", network_path, " with magic: ", magic);
print("Invalid network file: ", network_path, " with magic: ", magic);
throw std::invalid_argument("Invalid network file with magic: " + std::to_string(magic));
}

l0.load_from_file(file);
l1.load_from_file(file);

Logger("Loaded network file: ", network_path);
print("Loaded network file: ", network_path);
}

Network() {
Expand All @@ -90,7 +90,7 @@ namespace nn {
void write_to_file(const std::string &output_path) {
std::ofstream file(output_path, std::ios::out | std::ios::binary);
if (!file.is_open()) {
Logger("Unable to open:", output_path);
print("Unable to open:", output_path);
throw std::runtime_error("Unable to open: " + output_path);
}

Expand All @@ -107,7 +107,7 @@ namespace nn {
void quantize(const std::string &output_path) {
std::ofstream file(output_path, std::ios::out | std::ios::binary);
if (!file.is_open()) {
Logger("Unable to open:", output_path);
print("Unable to open:", output_path);
throw std::runtime_error("Unable to open: " + output_path);
}

Expand Down
4 changes: 2 additions & 2 deletions src/network/nnue.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

#include "../chess/constants.h"
#include "../external/incbin/incbin.h"
#include "../utils/logger.h"
#include "../utils/utilities.h"
#include "activations/crelu.h"
#include "layers/accumulator.h"
#include "layers/dense_layer.h"
Expand All @@ -43,7 +43,7 @@ namespace nn {
int offset = sizeof(int);

if (magic != MAGIC) {
Logger("Invalid default network file with magic", magic);
print("Invalid default network file with magic", magic);
throw std::invalid_argument("Invalid default network file with magic" + std::to_string(magic));
}

Expand Down
6 changes: 3 additions & 3 deletions src/network/train.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

#pragma once

#include "../utils/logger.h"
#include "../utils/utilities.h"
#include "adam.h"
#include "data_parser.h"

Expand Down Expand Up @@ -217,7 +217,7 @@ namespace nn {
}

void index_training_data(const std::string &training_data) {
Logger("Indexing training data...");
print("Indexing training data...");

std::string tmp;
std::ifstream file(training_data, std::ios::in);
Expand All @@ -226,7 +226,7 @@ namespace nn {
}
file.close();

Logger("Found", entry_count, "positions");
print("Found", entry_count, "positions");
}
};
} // namespace nn
Loading

0 comments on commit 0310796

Please sign in to comment.