Position(board) requests. Where to begin?

Discussion of chess software programming and technical issues.

Moderator: Ras

Dmi3
Posts: 4
Joined: Thu Sep 11, 2025 11:15 am
Location: Russia
Full name: Dmitry Molodov

Position(board) requests. Where to begin?

Post by Dmi3 »

Good afternoon.
I'm new :? to chess programming, but I'm good at C++ programming. I've wanted to write my own engine for a long time. So far, I've made a universal representation of the position (23 bytes). Help me figure out what request types to position representation the program needs. I do not support history-related queries (triple repetition and/or the 50-move rule). I'm giving you the C++ code just in case.

Code: Select all

#pragma once/*Position.hpp*/
#include"fwd.hpp"
#include"Move.hpp"
namespace Chess{
class Position{/*компактное представление позиции (нет дублирования информации)*/
/*поля доски могут быть пустыми и\или занятыми. маска Exist выделяет занятые
поля. фигуры на занятых полях могут быть активными и\или нет. маска Active
выделяет поля активных фигур среди занятых. поля могут быть заняты пешкой и\или
не пешкой. маска Pawns выделяет поля пешек среди занятых.*/
	friend Chess::Search; friend Chess::Node;
	using BitBoard = u64;
	BitBoard Exist;/*маска занятых полей без учёта королей*/
	/*активный и пассивный короли не входят в маски*/
	struct __attribute__((packed)) KingInfo{Cell Place:s3*2;u8 HasCastling:2;}Kings[2];
	Move::type MoveType;/*тип последнего полу-хода*/
	u8 reserved;/*можно использовать как продолжение поля NBRQ*/
	/*размеры масок равны количеству установленных в единицу бит в Exist*/
	using OccupedFields = u32;/*дополнительные битовые атрибуты занятых полей*/
	/*TODO: объединить эти две маскм в одну*/
	OccupedFields Active;/*маска активной стороны.*/
	OccupedFields Pawns;/*маска пешек независимо от стороны.*/
	enum:u8{N,B,R,Q};/*порядок типов фигур*/
	static_assert(Queen-Knight==Q-N);enum:u8{nbrq=N|B|R|Q};
	/*позиция не рассчитана на 19+ не пешек*/
	using NonPawnTag = u32;
	NonPawnTag NBRQ;/*маска фигур. каждые 2 бита определяют тип фигуры*/
public:
	static Cell constexpr EnPassantCell[1<<(s3+2)]{
		/*поля прохода для двойных ходов чёрных пешек и рокировок*/
		A6,B6,C6,D6,E6,F6,G6,H6, F8,D8, O64,O64,O64,O64,O64,O64,
		/*поля прохода для двойных ходов белых пешек и рокировок*/
		A3,B3,C3,D3,E3,F3,G3,H3, F1,D1, O64,O64,O64,O64,O64,O64};
#ifdef INVARIANT
	void INVARIANT()const noexcept;
#endif
	void swap(Position&)noexcept;/*=default*/
	[[nodiscard]]HashKey constexpr hash()const noexcept;/**/
	[[nodiscard]]bool constexpr operator==(Position const&)const noexcept;/*=default;*/
	[[nodiscard]]bool constexpr operator!=(Position const&)const noexcept;/*=default;*/
	constexpr Position()noexcept=default;/*invalid empty position*/
	Position(Position const&,Move)noexcept;
	void constexpr setStart()noexcept;
	void constexpr inspect(auto)const noexcept;/*without kings*/
	struct FieldInfo{u8 piece:3;u8 active:1;};
	FieldInfo constexpr inspect(Cell)const noexcept;
	Piece erase(Cell const)noexcept;
	void insert(Cell const,Piece const)noexcept;/*active piece*/
	[[nodiscard]] Inline KingInfo aKing(u8 n=0)const noexcept{return Kings[n];}
	[[nodiscard]] Inline Move::type aMove()const noexcept{return MoveType;}
	u8 make(Move[])const noexcept;/*create moves without considering the check. return "mate" when taking the opponent's king*/
	Position& change_side()noexcept;/*apply null-move*/
/*pawn material*/
/*short constexpr pawns_diff()const noexcept{return __builtin_popcount(Pawns&Active)-__builtin_popcount(Pawns&~Active);}*/
	void flipV()noexcept;
	void flipH()noexcept;
	void norm()noexcept;/*приведение с учётом симметрий к виду (активный король справа-внизу)*/
};

[[nodiscard]] Inline bool Position::operator==(Position const&p)const noexcept/*=default;*/
{auto a=&Exist,b=&p.Exist;return*a==*b&&a[1]==b[1]&&a[2]==b[2];}
[[nodiscard]] Inline bool Position::operator!=(Position const&p)const noexcept/*=default;*/
{auto a=&Exist,b=&p.Exist;return*a!=*b||a[1]!=b[1]||a[2]!=b[2];}

/* FNV-1a. Zobrist is not effecive*/
[[nodiscard]] Inline HashKey Position::hash()const noexcept{
	u64 const*a=&Exist;/*хеш на основе FNV-1a*/
	HashKey key=(14695981039346656037ULL^*a++)*1099511628211ULL;
	key^=*a++; return key*1099511628211ULL^*a;
}

Inline void Position::setStart()noexcept{
	Exist=0xEFFF00000000FFEFull,
	Kings[0]={E1,3}, Kings[1]={E8,3}, MoveType=Irreversible, reserved=0,
	Active=0x7FFF, Pawns=0x7FFF80, NBRQ = 0x874A1D2;
}

[[nodiscard]] Inline Position::FieldInfo Position::inspect(Cell fld)const noexcept{
	if(1&~Exist>>fld){
		if(fld==Kings[0].Place)return{King,1};
		if(fld==Kings[1].Place)return{King,0};
		return{Empty,0};
	}
	u8 idx=u8(__builtin_popcountll(Exist&((1ull<<fld)-1)));
	bool isActive=1&Active>>idx;
	if(1&Pawns>>idx)return{Pawn,isActive};
	idx=u8(__builtin_popcount(Pawns&((1u<<idx)-1)));
	return{u8(Knight+(NBRQ>>idx*2&0b11)),isActive};
}

Inline void Position::inspect(auto Inspector)const noexcept{
	u64 e=Exist; u32 a=Active,p=Pawns,z=NBRQ;
	for(;e;p>>=1,a>>=1,e&=e-1){
		Piece Pc=Pawn;
		if(1&~p)Pc=Piece(Knight+(nbrq&z)),z>>=2;
		Inspector(Pc,bool(1&a),Cell(__builtin_ctzll(e)));
	}
}

}/*namespace Chess*/
Sorry. I'm use wrong English language.
Dmi3
Posts: 4
Joined: Thu Sep 11, 2025 11:15 am
Location: Russia
Full name: Dmitry Molodov

Re: Position(board) requests. Where to begin?

Post by Dmi3 »

Compact representation of chess positions: Why copying and non-Zobrist query hashing speed up multicore search.

Hello, colleagues.

I want to share the results of my work on a very compact representation of chess positions (22-24 bytes), designed with the following goals:

Convenience of using the L1 cache: Positions are placed in one line of the L1 cache, which should significantly reduce the number of cache misses during move generation and search.
The "copy-make" paradigm: Instead of the classic move creation/cancellation operations, I use copy-make — I just copy the entire position structure and apply the move. Due to the small size of the position, this is trivial compared to the complexity of the cancellation logic.
Fast hashing on request: Instead of calculating (clogging the cache) and storing the Zobrist hash value in a position, I calculate a fast hash (based on FNV-1a) on request. This approach avoids the additional cost of updating and minimizes the size of the position structure.
Multi-core scalability: The copy function combined with compact data reduces synchronization costs and simplifies parallel search algorithms such as Lazy SMP or YBWC. This approach provides acceleration due to simple copying of positions and fewer memory bottlenecks.
Simplicity and reliability: The logic is simple — there are no undo stacks, no complicated additional status updates. This reduces errors and maintenance costs.

If you are working with chess engines or similar game tree search systems, consider creating compact position states with the ability to copy and hash on demand. This is a practical way to use modern processor architectures and multi-core parallelism.

I would like to hear your opinion or questions. :oops:
Aleks Peshkov
Posts: 916
Joined: Sun Nov 19, 2006 9:16 pm
Location: Russia
Full name: Aleks Peshkov

Re: Position(board) requests. Where to begin?

Post by Aleks Peshkov »

Conventinal chess engine needs efficiently perform two operations: 1) generate a sequence of all possible moves out of the given position and 2) transform one chess position into another according to a given move.

Implement PERFT and compare speed of your solution against known implementations. 20, 200 or 2000 bytes needed for a position representation does not matter, speed does matter.
Dmi3
Posts: 4
Joined: Thu Sep 11, 2025 11:15 am
Location: Russia
Full name: Dmitry Molodov

Re: Position(board) requests. Where to begin?

Post by Dmi3 »

Aleks Peshkov wrote: Sat Sep 13, 2025 2:05 am Implement PERFT and compare speed of your solution against known implementations. 20, 200 or 2000 bytes needed for a position representation does not matter, speed does matter.
Thanks for the reply. Is it possible to use this:
Stockfish 17.1 wrote: bench
~~~log skipped~~~
===========================
Total time (ms) : 2155
Nodes searched : 2030154
Nodes/second : 942066

speedtest 1 4 256
info string Using 1 thread
Warmup position 3/3
Position 258/258
===========================
Version : Stockfish 17.1
Compiled by : g++ (GNUC) 14.2.0 on MinGW64
Compilation architecture : x86-64-avx2
Compilation settings : 64bit AVX2 SSE41 SSSE3 SSE2 POPCNT
Large pages : no
~~~log skipped~~~
Thread count : 1
Thread binding : none
TT size [MiB] : 4
Hash max, avg [per mille] :
single search : 999, 805
single game : 1000, 999
Total nodes searched : 226755708
Total search time [s] : 262.146
Nodes/second : 864997
It seems to me that the speed of "PERFT" is poorly correlated with the speed of the search.
For example, SO FAR I have to form 1 layer more than is necessary for the correct operation of "PERFT" and have only NPS=4,500,000.
If do not check the shahs in the leaf nodes, then NPS=140,000,000 (without mass counting, hashing and manual use of SSE\AVX). In my opinion, StockFish's "PERFT" is even faster.
Aleks Peshkov
Posts: 916
Joined: Sun Nov 19, 2006 9:16 pm
Location: Russia
Full name: Aleks Peshkov

Re: Position(board) requests. Where to begin?

Post by Aleks Peshkov »

You may use stockfish command "go perft <Depth>". Note "Nodes (number of makeMove() calls)" per second is not equal to "perft number" per second that many report. In my young engine perft real nps is only 25% higher then search nps (5-10M on modern laptop depending on position).