Bent riders in XBetza bracket notation - #1035
fhoudebert wants to merge 2 commits into
Conversation
|
Note that the preferred notation for the Snake should (in analogy with the Ship) be v[W?B] not [vW?B]. Although it could work in the case of an orthogonal atom, directional modifiers in the first leg of a bracket notation are in general troublesome, as the symmetry class of the entire path can be lower than that of the first leg, and interpretation of the directional modifiers depends on symmetry class. E.g. what would [fsW?B] mean? frW in itself would mean fW+rW. In that interpretation each of these would splint into two B slides, leaving 4 slides, But the distant moves of the slide are oblique, and in the oblique system fr would select only a single path. fr[W?B] unambiguously selects the single path. Nothing implements this yet, but I believe it is the proper future of this notation. In my 'piece' branch I already interpret prefixes of the bent-slider notation (such as fryafsF) as if they act on obliques, rather than on F (what the a notation, and thus XBoard currently assume). So in fact my FSF Griffon is now incompatible with that of XBoard for making asymmetric subsets. Which would be corrected if both switch to bracket notation. |
|
Following HGMuller's comment, the Snake notation has been modified to v[W?B] in commit 0dbf730 and the PR description updated. |
|
Note that [W-R] denotes a LAME Ski-Rook; a true Ski-Rook would jump over the first square, i.e. [D?R]. I am a bit on the fence of whether this development is good or bad. It is certainly nice to see developers interested in expanding FSF's piece repertoire. But there are a lot of fundamental bottlenecks in the current implementation that severely hinder that. And I am afraid that work-arounds to those for implementing specific pieces would make it harder to address these later, so that we run the risk of painting ourselves into a corner. So even though it is very nice to have bracket notation and some bent riders it might be better to introduce some generally useful changes that help implementing these first. The way to transfer move information from the parser to the (initialization of the) bitboard move generator is one such bottleneck. In this commit the problem of conveying bent riders was is solved by introducing a pi->bent next to the leaper, slider and hopper, but to keep that up would lead to proliferation of such direction sets. Bent riders are sliders, and there also exist bent hoppers. It would be more generally useful if the way move paths are encoded in the existing (lame) leaper / slider / hopper sets was not limited to straight paths, but could also indicate single bent paths of various corner locations and bend angles, and perhaps even doubly-bent paths of various types. In other words, generalize the direction encoding used in these sets. Which at the same time could increade the maximal size the leaps can havem which now is quite inconveniently limited to 3 squares. |
|
Please note my proposal #1038 , which would be yet an alternative way for implementing bent riders. |
|
The proposal seems interesting. |
Bent riders in XBetza bracket notation
Adds bent riders to the Betza parser: a piece that takes one leg, may stop
there, and otherwise rides on without limit in the outward direction. Seven
pieces fall out of it, none of them expressible today, all declarable through
customPieceN:[F?R][W?B][D?B]v[F?R]v[W?B][F-B][W-R]420 insertions across 7 files, none of them
position.cpp.Related: #962, where @HGMuller implemented a superset of this on the
piecesbranch of his repository, and #966 / #967 — the crashes reported there turned
out to be the #966 bug fixed by #967, so there is no known blocker left on top
of master.
PieceInfocurrently has onlysteps,sliderandhopper, none of whichcan encode a change of direction mid-move, so this is a data-model addition
rather than a parser tweak.
The notation
This follows the bracket notation @HGMuller has been implementing in the
Interactive Diagram, rather than the older
a-modifier spelling:[A?B]is aleg with atom
A, which the piece may stop on, followed by a ride with atomBin the outward direction;[A-B]is the same with the leg square onlypassed through — still blocking the ride when occupied, just not a
destination.
Three things follow, and all three earn their keep:
family is a 45 degree turn and forks both ways; riding with the same family
carries straight on. So
[F-B]and[W-R]— the picket and the ski-rook —come out of the same code as the griffon, with no special case.
irrelevant, which is what makes
[D?B]an osprey rather than a lame one.whole displacement, the brackets standing in for an oblique atom.
vkeeps the moves travelling further along a file than across one,
stheother way round, and a move going equally far both ways is kept by either.
That last rule is what makes
v[F?R]the ship andv[W?B]the snake, by thesame reading rather than by two conventions. It has to apply to the leg square
as well as to the ride — the leg square is part of the compound — which is why
the snake keeps neither the sideways rides nor the sideways steps that would
start them.
It is also cheap. The comparison never changes as the ride lengthens: for a
ride along a file or a rank one component is fixed while the other grows, and
for a diagonal ride off an orthogonal leg both grow at the same rate, so the
leg's own offset decides once and for all. One step of the ride is enough to
settle it, at parse time.
Modifiers written inside the brackets are ordinary directional modifiers on
the leg atom, so
[vW?B]reaches the snake by the other route and gives theidentical move set.
v[W?B]reads better and is what the documentation uses.Not supported: a second leg carrying its own directional modifiers,
[W?sR]. That would be a turn of something other than 45 degrees, whichbreaks the single-lookup construction below, since the continuations would no
longer share a family. The parser leaves such a group alone rather than
reading it as something it is not, and
variants.inisays so.How the attacks are computed
No new magic table. A griffon's relevant-occupancy mask spans two whole
files and two whole ranks — around 24 squares on 8x8 before edge trimming, and
worse on 12x10 — so a dedicated magic would be memory-hungry for a piece most
variants never use. The attacks are composed from the rook and bishop magics
instead.
The construction rests on one observation about
QueenDirections, which thispatch adds in rotational order, so that stepping ±1 through the table is a
45 degree turn and the diagonals sit at the odd indices:
That is what makes a single lookup enough. For each leg:
t. If it is off the board, wrapped(
distance(s, t) != len), or occupied, the leg contributes nothingfurther — an occupied corner blocks the ride, which is the rule.
t,attacks_bb<ROOK>orattacks_bb<BISHOP>according to the parity of the continuationdirection. One probe covers every fork at once, and it already applies the
blocking rules along them, which are exactly a bent rider's.
RayBBentries of the continuations actually taken, whichdiscards the rays pointing back toward the origin.
The only new table is
RayBB[8][SQUARE_NB], the rays to the board edge ineach queen direction: 8 KB, or 16 KB with
LARGEBOARDS.Cost per attack query: four legs, one rook or bishop lookup each. Since the
rook is split into H and V magics, a griffon costs 8 magic probes against 2
for a queen; a rhinoceros or osprey rides diagonally and costs 4.
Per-quadrant magics — mask being one partial file plus one partial rank from
the corner, hence small — are the obvious follow-up if this ever shows up in a
profile.
The rider bit carries no shape. One
RIDER_BENTbit covers all sevenpieces; the shape lives in
BentAttacks[COLOR_NB][PIECE_TYPE_NB], holding perleg a direction index, a leg length and a three-bit continuation mask. Black
is the same table with each leg turned through 180 degrees, a half-turn in the
direction table, which leaves the mask unchanged. The bit is placed after
every magic-backed rider and stripped by a new
MAGIC_RIDERSmask beforethe loop in
rider_attacks_bb, which indexesmagics[]by bit position andwould otherwise run off the end.
Why bent riders cannot use the existing fast paths
Reachability is symmetric, but the path is not. A griffon on a1 reaches b5
via b2-b3-b4; a griffon on b5 reaches a1 via a4-a3-a2. The blocking sets are
disjoint. Three consequences:
checkSquares[pt], computed from the king square, assumes reversibility.BetweenBB, built as the intersection of the two reciprocal attack sets,gives
{c2}for a1/b5 instead of{a2, a3, a4}.slider_blockers()would compute pins along the wrong squares.Putting
RIDER_BENTinNON_SLIDING_RIDERSandASYMMETRICAL_RIDERSisenough, and nothing else was needed — which matches what @HGMuller reported in
#962.
NON_SLIDING_RIDERSmakesgenerate_all<EVASIONS>fall back totarget = ~pos.pieces(Us)and makeslegal()skip itsbetween_bbshortcut,leaving the full
attackers_to()at the end oflegal()to cover pins;ASYMMETRICAL_RIDERSmakesslider_blockers(),attackers_to()andgives_check()recompute from each candidate square rather than reversing.bent_path_bb()supplies a directedbetween_bb(s1, s2, pt), traced fromthe piece towards the target.
checkSquaresstays the approximate forwardset, exactly as it already is for
HORSE, which is also inASYMMETRICAL_RIDERS; it only feedsQUIET_CHECKS, so the cost is a littlesearch quality, not correctness.
One incidental fix:
psqt.cppreads onlysliderandhopperwhen decidingisSlider, so a piece whose movement lives inbentwas silently classifiedas a non-slider. Each continuation now counts as three quarters of a slider
direction, which puts the griffon slightly below the queen — matching its
empty-board mobility of 21.4 squares against 22.75 — and values the
half-versions at about half the full piece.
Testing
No regression on standard chess.
benchgives 6180480 with and withoutthe patch. Perft from the start position to depth 6 = 119060324 and Kiwipete
to depth 5 = 193690690.
Move generation cross-checked against an independent path-walking
reference over 600 random 12x10 positions with friendly and enemy blockers,
spread across four of the shapes: 0 disagreements. On an empty 8x8 board from
d4 the seven pieces give 24, 24, 16, 12, 14, 8 and 10 moves, each also
verified by hand.
v[W?B]and[vW?B]give not just the same count but thesame twelve squares.
Check detection. Two positions, same pieces, the blocker moved from b3 to
a3, in a variant with
customPiece1 = g:[F?R]:7k/8/8/1g6/8/1P6/8/K7 w - - 0 1Checkers: b5, perft 1 = 27k/8/8/1g6/8/P7/8/K7 w - - 0 1An implementation treating the griffon as symmetric gets both backwards.
Worth keeping as a regression test.
The osprey leaps. With a knight on e4 all 16 moves from d4 remain,
including d4f4 and the ride g5/h6; with the landing square f4 occupied, d4f4
stays as a capture but the ride beyond disappears.
Stress. 18,000 plies of random games on a
debug=yesbuild in a 12x10variant holding all seven shapes at once — both spellings of the snake
included — plus
go depth 22on K+G vs K endgames, which is where #962crashed. No assertion failures.
Two example variants are added to
variants.ini:griffonchess(perft 4 =187940) and
ospreychess(perft 4 = 593048).Cost
Move generation only, which is where the whole cost sits and the only figure I
could measure repeatably:
go perft 5from the start position runs at 19.3Mn/s for a chess variant with a queen against 8.9 Mn/s for the same variant
with a griffon, so roughly half the speed. In search the gap is smaller,
since evaluation dilutes it, but my machine was too noisy to pin a number down
and I would rather quote the reproducible one. It only affects variants that
actually use a bent rider.
Questions
piecesbranch as a whole now thatSet piece value for black custom pieces #967 has landed? It is broader — ski sliders, long-stride riders — and this
PR should be dropped in its favour if so.
NNUE does not know these pieces, so variants using them run on classical eval.