bench
contains benchmarks. Each subdirectory corresponds to benchmark for a separate modulecmake
contains CMake scripts which are useful to build the projectcmake/scripts
contains executable CMake scripts. These ones are designed to be run withcmake -P
command and are used while building the projectcmake/overrides
contains CMake override files to use in various build modes. Currently, files from here are used in CI builds
docs
contains documentation for the project and its various utilitiesgen
contains generators for autogenerated files. Each subdirectory corresponds to generators for a specific moduleselftest
contains a testing framework to check the correctness of the rules implementationsrc
contains various directories, each of them represents a module. See below on module structuresrc/config.h.in
contains configuration flags for the entire project
third-party
contains third-party libraries used in the project. Each library is contained in a separate foldertools
contains some useful scripts
src/<module_name>/
is the root of the module. It contains the public API headers and the implementation of the methods declared theresrc/<module_name>/private
contains private API and its implementationsrc/<module_name>/test
can contain unit tests and other stuff for testing
Each module has its own namespace. E.g. core
has namespace SoFCore
. For private API and testing
routines, there are sub-namespaces, Private
and Test
respectively.
Each module can have its private API and public API. The latter is used by other modules, and the
former is entirely internal. As mentioned above, private APIs live under the separate Private
namespace and their implementation is located in private/
subdirectory.
The following rules apply:
- public headers must not depend on private headers. Though, implementation of public functionality may depend on private API
- any module should not use private API of another module
- public API should be carefully documented (see below), while private API may be left undocumented
util
implements helper utilities. It must not depend on any other modulecore
is a fast implementation of basic chess rulesbot_api
contains APIs for communication between the engine and the GUI. The implementation of these APIs is located in other modulesbot_api/clients
contains various implementations of engine communication protocols. This module is useful for developing chess engines. It uses the interfaces described inbot_api
module. Currently, only UCI is implemented, but support for more protocols will be added later
eval
contains the implementation of position cost evaluationeval/feat
allows to tune the position cost weights. It contains utilities to extract the features from positions to perform such tuning, and to apply the updated weights to the engine
gameset
contains APIs to work with SoFGameSet formatsearch
contains the chess engine implementation. It uses the interface described inbot_api
search/bin
contains the chess engine executable
version
is a tiny module that contains the version info. This information is taken from Git at build time. It is designed as a separate module to make recompilation faster, as the version info may change fast (on every commit)- more modules coming soon...
- public APIs must be clear and understandable. Private APIs and implementations are allowed to use
any dirty hacks and even depend on some details of the module's public API (e.g. the order of
constants in enums, the order of fields in struct, the value of public constants). But it's
disregarded when the implementation depends on such details of another module
- the steps above may make the public API inflexible to minor changes, so be careful
- performance is better than clean implementation code. If it's necessary to overcome the linter
warnings to write faster code, then it can be done
- the code is optimized primarily for modern x86_64 systems. For them, CPUs are required to have SSE4.2 and POPCNT and encouraged to have BMI2. The code should not throw compile errors on other platforms, though I'm not planning to focus on optimizing it for such targets
- to detect platform features, use flags in CMake and detect in
CMakeLists.txt
if this feature is available - benchmarks, profilers and assembly listings (with
gcc -S
or other tools) are encouraged to find a faster solution - though it's not a very good idea to optimize a function which is called rarely and doesn't affect timings much
I don't have any policy for testing, so any approach can be used.
The chess rules are tested against other implementation in selftest
directory. This includes:
- the same behaviour between implementations
- consistency check (e. g. that the board is updated, and the moves are valid)
- checks between different functions (e. g. that
isMoveLegal
returns true if and only if this move is generated bygenAllMove
)
The project uses C++17. Below there is a comment about specific C++ features.
constexpr
,inline
methods and templates help produce faster code, so use them!- exceptions are not allowed
- class inheritance is not allowed in general, with some exceptions
- you can only inherit from an abstract class without any fields (not all of the methods them must be abstract, some can have a default implementation)
When making substantial changes to the engine, I need to ensure that they won't make the engine
play worse. So, matches between new and old versions of the engine are conducted before committing
the changes into master
branch. Currently, BattleField is used to conduct such matches.
The acceptance criteria are as follows:
- small changes which supposedly don't affect playing strength can be accepted without testing
- if changes make the engine play better, they are accepted
- if changes don't make the engine play worse and are good in some aspect (nicer behaviour in certain rare positions, various refactorings, and so on), they are accepted
- otherwise, the changes are not accepted, and we must either debug it or just don't apply
There are also tests that ensure that the engine doesn't break (see above). Though, it's only a basic check and passing tests doesn't guarantee that there are no subtle bugs that descrease playing strength significantly.
- SoFCheck => совчик => small owl :)
- "check" part has relationship to chess
- SoF ::= Strategy of Figurines (though I'm not sure whether SoF part even has any meaning, it's just an attempt to give any meaning to this part)
The project uses Gepardo code style. It's much like Java code style, with 2 space wide
indentations. To follow the code style, clang-format
is configured for the project. clang-tidy
will help you to avoid the style errors related to naming convention. Here are the rules:
- methods and variables are
camelCase
- types and namespaces are
BigCamelCase
- aliases for primitives are
snake_case_t
- enum members are
BigCamelCase
- constants are
ANGRY_SNAKE_CASE
- file names are
snake_case.cpp
(orsnake_case.h
) - global variables are
g_camelCase
- private members are
snakeCase_
- defines are
ANGRY_SNAKE_CASE
- to create a value
make...()
. E. g.:makeCell()
- prefer the form
<objectName><action>
instead of<action><objectName>
. E. g.:moveMake()
instead ofmakeMove()
- for function returning booleans, you can use the form
is<objectName><action>
. E. g.:isMoveValid()
Comments start from uppercase letters. Note that the dot in the end of the comment can be omitted.
It's required to document all the methods in the public API, if it's not entirely clear from their signature what they do. There is no strict form of how this documentation must be written. Note that the following things must be taken into account when writing documentation:
- behaviour in exceptional cases or invalid data: is it a panic, error code or the behaviour is undefined? By default, it's supposed that you will pass valid parameters into functions, but better be explicit
- which parameter values considered valid for a function, and which ones considered invalid
- meaning of the parameters and return values, if it's not clear
- please, use full paths (starting from
src
) for header files. For example, the header insrc/core/board.h
must be included as#include "core/board.h"
. - naming of defines in header guards also has strict rules, which I'm too lazy to describe. Anyway,
tools/fix_header_guards.py
will fix them if necessary.