Problem with bitboard knight attack generator

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Problem with bitboard knight attack generator

Post by bob »

Gerd Isenberg wrote:
bob wrote: I don't follow the bit patterns you are using... Since this only needs to be done at startup, I use this:

Code: Select all

int64_t knight_attacks[64];
static const int knightsq[8] = { -17, -15, -10, -6, 6, 10, 15, 17 };

/*
 initialize knight attack board
 */
  for &#40;i = 0; i < 64; i++) &#123;
    knight_attacks&#91;i&#93; = 0;
    frank = Rank&#40;i&#41;;
    ffile = File&#40;i&#41;;
    for &#40;j = 0; j < 8; j++) &#123;
      sq = i + knightsq&#91;j&#93;;
      if (&#40;sq < 0&#41; || &#40;sq > 63&#41;)
        continue;
      trank = Rank&#40;sq&#41;;
      tfile = File&#40;sq&#41;;
      if (&#40;Abs&#40;frank - trank&#41; > 2&#41; || &#40;Abs&#40;ffile - tfile&#41; > 2&#41;)
        continue;
      knight_attacks&#91;i&#93; = knight_attacks&#91;i&#93; | &#40;uint64_t&#41; 1 << sq;
    &#125;
  &#125;
Bahh, what a mess, horrorfull loops and conditions, even if init stuff only ;-)

Are you a bitboarder? I guess you need some knight fillstuff anyway, so why not using it for simple initialization?

Code: Select all

U64 knightAttacks&#40;U64 knights&#41; &#123;
   U64 l1 = &#40;knights >> 1&#41; & C64&#40;0x7f7f7f7f7f7f7f7f&#41;;
   U64 l2 = &#40;knights >> 2&#41; & C64&#40;0x3f3f3f3f3f3f3f3f&#41;;
   U64 r1 = &#40;knights << 1&#41; & C64&#40;0xfefefefefefefefe&#41;;
   U64 r2 = &#40;knights << 2&#41; & C64&#40;0xfcfcfcfcfcfcfcfc&#41;;
   U64 h1 = l1 | r1;
   U64 h2 = l2 | r2;
   return &#40;h1<<16&#41; | &#40;h1>>16&#41; | &#40;h2<<8&#41; | &#40;h2>>8&#41;;
&#125;
My "initialization code" was written in 1994 when I started to use bitboards. Since it worked, I never went back to clean it up. It is only done once and never during the search. :) I've made it a practice to not touch what works unless it is in the search itself where efficiency is important...
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Pascal

Post by sje »

ZirconiumX wrote:@ Steven: Hate to offend you, but PASCAL is about as much use to me as a plastic shoelace is to a plastic shoelace.
No offense taken. However, Pascal as a successor to Algol, is a good language for algorithmic description and that's why I use it at times.

In CookieCat, there is the constant value advance array initialized very early at start-up:

Code: Select all

    advance&#58; array &#91;sqtype, dirtype&#93; of sqxtype; &#123; Next square along any direction &#125;
It's very handy, as is its constant value pal compass:

Code: Select all

    compass&#58; array &#91;sqtype, sqtype&#93; of dirxtype; &#123; Direction from first to second square &#125;
User avatar
lucasart
Posts: 3232
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: Problem with bitboard knight attack generator

Post by lucasart »

ZirconiumX wrote:I have been trying to make a bitboard knight attack generator to fill an array of knight attacks.

Code: Select all

Bitboard KnightMoves&#40;int Square&#41;
&#123;
	Bitboard b = 1 << Square, Targets = 0;
	
	Targets		|= &#40;b & 0x7F7F7F7F7F7F7F7FULL&#41; << 17;
	Targets		|= &#40;b & 0x3F3F3F3F3F3F3F3FULL&#41; << 10;
	Targets		|= &#40;b & 0x3F3F3F3F3F3F3F3FULL&#41; >> 6;
	Targets		|= &#40;b & 0x7F7F7F7F7F7F7F7FULL&#41; >> 15;
	Targets		|= &#40;b & 0xFEFEFEFEFEFEFEFEULL&#41; << 15;
	Targets		|= &#40;b & 0xFCFCFCFCFCFCFCFCULL&#41; << 6;
	Targets		|= &#40;b & 0xFCFCFCFCFCFCFCFCULL&#41; >> 10;
	Targets		|= &#40;b & 0xFEFEFEFEFEFEFEFEULL&#41; >> 17;

	return Targets;
&#125;
When I try and generate the array I get some strange figures given, e.g. for square a1 I get 0x132096...

Something at the back of my mind says that I'm suffering from wrap-around issues.

Matthew:out
OK I rewrote my code with the same idea. This code is 100% correct and generates King, Knight, and Pawn attacks:

Code: Select all

static uint64_t calc_KAttacks&#40;int sq&#41;
&#123;
	const uint64_t b = Bit&#91;sq&#93;;
	return (&#40;b & ~File&#91;FileA&#93;) << 7&#41;
		| (&#40;b & ~File&#91;FileA&#93;) >> 1&#41;
		| (&#40;b & ~File&#91;FileA&#93;) >> 9&#41;
		| (&#40;b & ~File&#91;FileH&#93;) << 9&#41;
		| (&#40;b & ~File&#91;FileH&#93;) << 1&#41;
		| (&#40;b & ~File&#91;FileH&#93;) >> 7&#41;
		| &#40;b << 8&#41;
		| &#40;b >> 8&#41;;
&#125;

static uint64_t calc_NAttacks&#40;int sq&#41;
&#123;
	const uint64_t b = Bit&#91;sq&#93;;
	return	(&#40;b & ~File&#91;FileA&#93;) << 15&#41;
		|	(&#40;b & ~File&#91;FileH&#93;) << 17&#41;
		|	(&#40;b & ~File&#91;FileA&#93;) >> 17&#41;
		|	(&#40;b & ~File&#91;FileH&#93;) >> 15&#41;
		|	(&#40;b & ~&#40;File&#91;FileA&#93; | File&#91;FileB&#93;)) << 6&#41;
		|	(&#40;b & ~&#40;File&#91;FileA&#93; | File&#91;FileB&#93;)) >> 10&#41;
		|	(&#40;b & ~&#40;File&#91;FileG&#93; | File&#91;FileH&#93;)) << 10&#41;
		|	(&#40;b & ~&#40;File&#91;FileG&#93; | File&#91;FileH&#93;)) >> 6&#41;;
&#125;

static uint64_t calc_PAttacks&#40;int color, int sq&#41;
&#123;
	const uint64_t b = Bit&#91;sq&#93;;
	return	shift_bit&#40;b & ~File&#91;FileA&#93;, color ? -9 &#58; +7&#41;
		|	shift_bit&#40;b & ~File&#91;FileH&#93;, color ? -7 &#58; +9&#41;;
&#125;

static void init_KNP_attacks&#40;)
&#123;
	for &#40;int sq = A1; sq <= H8; sq++) &#123;
		KAttacks&#91;sq&#93; = calc_KAttacks&#40;sq&#41;;
		NAttacks&#91;sq&#93; = calc_NAttacks&#40;sq&#41;;
		for &#40;int color = White; color <= Black; color++)
			PAttacks&#91;color&#93;&#91;sq&#93; = calc_PAttacks&#40;color, sq&#41;;
	&#125;
&#125;
When I do non-functional changes like this, I *always* check the equality of node counts on a benchmark test suite. That's a guideline I got from Marco Costalba, and he was definitely right about that. No non functional commits are accepted before the node count test is passed :)

Note that my code is far more readable because I avoid using hardcoded hexadecimal numbers, as much as possible. Replacing the bitboard "filters" by their values makes the code illegible for no good reason:
1/ this code is not performance critical, so there's no need to do that
2/ even if it was, you could define constants for the bitboards File(A...H) and have them evaluated at compile time.

The function shift_bit allows me to do positive (<<) or negative (>>) shifts the same way,. This is particularly nice to make the code more condensed and more readable in areas that are not performance critical.

Although the above code is longer (in number of lines) than my previous code, I find it much more pleasant to read. My previous code used way too many imbricated loops:

Code: Select all

static void init_KNP_attacks&#40;)
&#123;
	const int K_dir&#91;8&#93;&#91;2&#93; = &#123;&#123;-1,-1&#125;,&#123;-1,0&#125;,&#123;-1,1&#125;,&#123;0,-1&#125;,&#123;0,1&#125;,&#123;1,-1&#125;,&#123;1,0&#125;,&#123;1,1&#125;&#125;;
	const int N_dir&#91;8&#93;&#91;2&#93; = &#123;&#123;-2,-1&#125;,&#123;-2,1&#125;,&#123;-1,-2&#125;,&#123;-1,2&#125;,&#123;1,-2&#125;,&#123;1,2&#125;,&#123;2,-1&#125;,&#123;2,1&#125;&#125;;
	const int P_dir&#91;NB_COLOR&#93;&#91;2&#93;&#91;2&#93; = &#123; &#123;&#123;1,-1&#125;,&#123;1,1&#125;&#125;, &#123;&#123;-1,-1&#125;,&#123;-1,1&#125;&#125; &#125;;

	for &#40;int sq = A1; sq <= H8; sq++) &#123;
		int r = rank&#40;sq&#41;, f = file&#40;sq&#41;;
		KAttacks&#91;sq&#93; = NAttacks&#91;sq&#93; = 0ULL;

		for &#40;int i = 0; i < 8; i++) &#123;
			int dr = K_dir&#91;i&#93;&#91;0&#93;, df = K_dir&#91;i&#93;&#91;1&#93;;
			if &#40;rank_file_ok&#40;r+dr,f+df&#41;)
				set_bit&#40;&KAttacks&#91;sq&#93;, square&#40;r+dr,f+df&#41;);

			dr = N_dir&#91;i&#93;&#91;0&#93;, df = N_dir&#91;i&#93;&#91;1&#93;;
			if &#40;rank_file_ok&#40;r+dr,f+df&#41;)
				set_bit&#40;&NAttacks&#91;sq&#93;, square&#40;r+dr,f+df&#41;);
		&#125;

		PAttacks&#91;White&#93;&#91;sq&#93; = PAttacks&#91;Black&#93;&#91;sq&#93; = 0ULL;
		for &#40;int i = 0; i < 2; i++)
			for &#40;int c = White; c <= Black; c++) &#123;
				int dr = P_dir&#91;c&#93;&#91;i&#93;&#91;0&#93;, df = P_dir&#91;c&#93;&#91;i&#93;&#91;1&#93;;
				if &#40;rank_file_ok&#40;r+dr,f+df&#41;)
					set_bit&#40;&PAttacks&#91;c&#93;&#91;sq&#93;, square&#40;r+dr,f+df&#41;);
			&#125;
	&#125;
&#125;
Gerd Isenberg
Posts: 2250
Joined: Wed Mar 08, 2006 8:47 pm
Location: Hattingen, Germany

Re: Problem with bitboard knight attack generator

Post by Gerd Isenberg »

lucasart wrote: Note that my code is far more readable because I avoid using hardcoded hexadecimal numbers, as much as possible. Replacing the bitboard "filters" by their values makes the code illegible for no good reason:
1/ this code is not performance critical, so there's no need to do that
2/ even if it was, you could define constants for the bitboards File(A...H) and have them evaluated at compile time.
However, with eight-fold symmetric knights and kings, hexadecimal constants have the advantage to don't "discriminate" other orthogonal bit-square mappings. Using FILEA in one mapping might be FILEH or RANK1 or 8 in other mappings and source of confusion. What would be a good board mapping independent identifier for those post- or pre-wrap masks {fe, fc, 7f, 3f}, only based on arithmetical order of bits? Left and right is problematic as well, due to possible confusing board and arithmetical shift left/right.
mcostalba
Posts: 2684
Joined: Sat Jun 14, 2008 9:17 pm

Re: Problem with bitboard knight attack generator

Post by mcostalba »

This is the SF code to init pawns, knight and king attacks in one go:

Code: Select all

  int steps&#91;&#93;&#91;9&#93; = &#123; &#123;&#125;, &#123; 7, 9 &#125;, &#123; 17, 15, 10, 6, -6, -10, -15, -17 &#125;,
                     &#123;&#125;, &#123;&#125;, &#123;&#125;, &#123; 9, 7, -7, -9, 8, 1, -1, -8 &#125; &#125;;

  for &#40;Color c = WHITE; c <= BLACK; c++)
      for &#40;PieceType pt = PAWN; pt <= KING; pt++)
          for &#40;Square s = SQ_A1; s <= SQ_H8; s++)
              for &#40;int k = 0; steps&#91;pt&#93;&#91;k&#93;; k++)
              &#123;
                  Square to = s + Square&#40;c == WHITE ? steps&#91;pt&#93;&#91;k&#93; &#58; -steps&#91;pt&#93;&#91;k&#93;);

                  if &#40;square_is_ok&#40;to&#41; && square_distance&#40;s, to&#41; < 3&#41;
                      set_bit&#40;&StepAttacksBB&#91;make_piece&#40;c, pt&#41;&#93;&#91;s&#93;, to&#41;;
              &#125;
But I like more the Lucas's one :-)
Aleks Peshkov
Posts: 892
Joined: Sun Nov 19, 2006 9:16 pm
Location: Russia

Re: Problem with bitboard knight attack generator

Post by Aleks Peshkov »

Code: Select all

    template <> Bb attack<Knight>&#40;Square sq&#41; &#123;
        return sq&#40;-2, -1&#41; + sq&#40;-2, +1&#41; + sq&#40;-1, -2&#41; + sq&#40;-1, +2&#41;
             + sq&#40;+2, +1&#41; + sq&#40;+2, -1&#41; + sq&#40;+1, +2&#41; + sq&#40;+1, -2&#41;;
    &#125;

Code: Select all

Bb Square&#58;&#58;operator&#40;) &#40;signed fileOffset, signed rankOffset&#41; const &#123;
    signed x88 = *this + (*this & 070&#41; + fileOffset + 16*rankOffset;
    if &#40;x88 & 0x88&#41; &#123;
        return Bb&#58;&#58;empty&#40;); //offside
    &#125; else &#123;
        Square sq = static_cast<square_t>(&#40;x88 + &#40;x88 & 7&#41;) >> 1&#41;;
        return Bb&#40;sq&#41;;
    &#125;
&#125;
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Re: Problem with bitboard knight attack generator

Post by sje »

Mirror, mirror, on the wall
Whose code is the simplest of them all?
ZirconiumX
Posts: 1334
Joined: Sun Jul 17, 2011 11:14 am

Re: Problem with bitboard knight attack generator

Post by ZirconiumX »

I think what has just happened is I've entered a code clarity contest with Lucas Braesch...

Here goes nothing...

(Note the transformation into psuedo-nothingness)

Code: Select all

void InitializeKnightAttacks&#40;)
start
	Bitboard b;
	for &#40;int Square equals a1; Square is_less_than h8; Increment&#40;Square&#41;) start
		b equals 1ULL left_shift Square;
		
		KnightAttacks&#91;Square&#93; equals (&#40;b bitand compl&#40;FileMask&#40;A_FILE&#41;)) left_shift 17&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;G_FILE&#41; bitor FileMask&#40;H_FILE&#41;)) left_shift 10&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;G_FILE&#41; bitor FileMask&#40;H_FILE&#41;)) right_shift 6&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;H_FILE&#41;)) right_shift 15&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;A_FILE&#41;)) left_shift 15&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;A_FILE&#41; bitor FileMask&#40;B_FILE&#41;)) left_shift 6&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;A_FILE&#41; bitor FileMask&#40;B_FILE&#41;)) right_shift 10&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;A_FILE&#41;)) right_shift 17&#41;;
	end
end
Ooops...

Matthew:out
Some believe in the almighty dollar.

I believe in the almighty printf statement.
ZirconiumX
Posts: 1334
Joined: Sun Jul 17, 2011 11:14 am

Re: Problem with bitboard knight attack generator

Post by ZirconiumX »

Let's try something a little more C++-like:

Code: Select all

void InitializeKnightAttacks&#40;)
&#123;
	Bitboard b;
	for &#40;int Square = a1; Square < h8; Square++) &#123;
		b = 1ULL << Square;
		
		KnightAttacks&#91;i&#93; = (&#40;b bitand compl&#40;FileMask&#40;FILE_A&#41;)) << 17&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;G_FILE&#41; bitor FileMask&#40;H_FILE&#41;)) << 10&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;G_FILE&#41; bitor FileMask&#40;H_FILE&#41;)) >> 6&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;H_FILE&#41;)) >> 15&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;A_FILE&#41;)) << 15&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;A_FILE&#41; bitor FileMask&#40;B_FILE&#41;)) << 6&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;A_FILE&#41; bitor FileMask&#40;B_FILE&#41;)) >> 10&#41;
					bitor (&#40;b bitand compl&#40;FileMask&#40;A_FILE&#41;)) >> 17&#41;;
	&#125;
&#125;
Using only C++ stuff (the Bitboard is typedef'd to unsigned long long)!

Somethig tells me the last version may be something of a hit to Mr Edwards...

Matthew : out

(That was deliberate, a certain PASCAL addict should notice it)
Some believe in the almighty dollar.

I believe in the almighty printf statement.
User avatar
lucasart
Posts: 3232
Joined: Mon May 31, 2010 1:29 pm
Full name: lucasart

Re: Problem with bitboard knight attack generator

Post by lucasart »

ZirconiumX wrote:I think what has just happened is I've entered a code clarity contest with Lucas Braesch...
But we can do an obfuscated code contest if you prefer ;-)

As for PASCAL, there's at least one good thing about this language (although I don't really like it for many other reasons). It doesn't easily allow you to write obfuscated code. And it's good for educational purposes, because any non specialist can easily understand Pascal. On the other hand C++ wizardry is a lot more complicated, and there's nothing worse than bad C++ code written by noobs who don't have a clue about what the C++ compiler really does. That's why I prefer to stick to plain C: since I don't master the C++ language enough I thought it was best to use plain C.