Problem with bitboard knight attack generator

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

Rein Halbersma
Posts: 741
Joined: Tue May 22, 2007 11:13 am

Re: Problem with bitboard knight attack generator

Post by Rein Halbersma »

Sven Schüle wrote: From my experience it is simply the wrong view to say that "fixing some error checking is a waste of time". It is always cheaper to include error checking immediately when designing the algorithm. If you add it later then you add much more testing effort, and there is a high risk that you introduce new errors by changing the algorithm (or its implementation) to include error checking. Better include it right from the beginning.

Part of that "error checking" topic can also be to include ASSERT's, which can sometimes help to reduce the amount of error checking code of the release version drastically. (But ASSERT's are not always the right solution, e.g. not always applicable for external input.)

To a certain degree this does also apply to chess programs, where an FEN parser is only one (perhaps less important) example.

Underestimating the need for error checking and/or ASSERT's is one of the best ways to unreliable code.

Sven
I also like the "if you haven't got the time to do it right, where are you going to find the time to do it over?" philosophy. For my draughts program, I have automated unittesting (using Boost.Test, and previously GoogleTest) in place.

All the low-level routines (bit-twiddling, move generation, hash lookup/storage) are automatically checked exhaustively (at least the corner cases) on *each* compile. It's simply a post-build event in Visual Studio, so you cannot ever break code without noticing immediately. The code simply fails to build successfully if any unittest fails! It only takes a few seconds (on a compile time of a minute) to run this. The more time consuming tests (search, perft) are not done on every build, but are automatically scheduled for an overnight build (they tend to take between 2 and 6 hours).

Surprisingly, I never read about anyone at this forum doing unittesting. Is that because people consider this a hobby and don't like the effort, or is it because of lack of familiarity with the concept?
Dave_N
Posts: 153
Joined: Fri Sep 30, 2011 7:48 am

Re: Problem with bitboard knight attack generator

Post by Dave_N »

ZirconiumX wrote:David,

stop.

Matthew:out
sorry didn't mean to hijack the thread with a discussion about error checking.

{classic example, MyQuad square; square.x = calculated_number; assert(square.x != -INFINITY);}
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: error checking

Post by Sven »

Dave_N wrote:Probably the best practice, although in any app there are likely to be non-error checked assignments and variable usage. If for example you have a class called Foo ...

Code: Select all

class Foo
{
public:
   Foo()
{
    mem = new SmallBlockOfMemory(this); // new fails because the //system is out of memory
}
~Foo()
{
if( mem )
{
    delete mem;
   mem = NULL;
}
}

SmallBlockOfMemory *mem; // with own constructor
};
and "new" isn't overridden until much later when Foo has become a major project.
Sorry, I don't quite get the point here. Which "non error-checked assignment" did you want to show with that code snippet?

Btw, you can omit NULL-checking the argument to "delete", it does always do that check on its own (you may rely on it, since even an overridden version of operator delete is required to do that check). Also resetting a pointer member to NULL in a destructor is not really necessary, even though static code analysis tools like "PCLint" emit warnings if you don't, for reasons that are hard to grasp (maybe to increase protection against accidental double destruction of objects holding pointers to other objects, but that has always appeared far-fetched to me).

Sven
Dave_N
Posts: 153
Joined: Fri Sep 30, 2011 7:48 am

Re: error checking

Post by Dave_N »

The example explains why the "new" keyword is often overridden late in a programs development, the memory allocation with "new" is often used without checking that "new" succeeded in allocating memory. This is obviously an idyllic design achievement and most code isn't that easy to patch in the late stages of a project.
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: error checking

Post by Sven »

Dave_N wrote:The example explains why the "new" keyword is often overridden late in a programs development, the memory allocation with "new" is often used without checking that "new" succeeded in allocating memory. This is obviously an idyllic design achievement and most code isn't that easy to patch in the late stages of a project.
In general I agree, where you mention the "late project stage" aspect.

But checking the result of "new" is really a separate issue, even more in a case like your example above where "new" is used within a constructor. Recently we had a brief discussion about it, also in the context of C++ exception handling (was it even a different thread?). There would be much more to say about it but it would not fit here.

Btw, "new" is a keyword but what you can override is not the keyword but the underlying "operator new" function. What the "new" keyword presents to the developer is a typed pointer while the operator always returns "void *".

Sven
Dave_N
Posts: 153
Joined: Fri Sep 30, 2011 7:48 am

Re: error checking

Post by Dave_N »

Well I'll be creating 2 architectures for the Fen (and any other program feature)
a) the error checking user facade pattern (the user is always wrong!)
b) and the internal error recovery effort.

These functions needed to be optimized / tweaked at some stage anyway (PGN loading and Fen) for various reasons, and so I am repairing multiple tasks at the same time for both.
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 »

mcostalba wrote:
Dave_N wrote:Here is how I handled the fen
Not bad, but C++ is more powerful ;-)

Code: Select all

  const string PieceToChar(".PNBRQK  pnbrqk  ");
  std::istringstream fen(fenStr);
  Square sq = SQ_A8;
  char token;

  while ((fen >> token) && !isspace(token))
  {
      if (token == '/')
          sq -= Square(16); // Jump back of 2 rows

      else if (isdigit(token))
          sq += Square(token - '0'); // Skip the given number of files

      else if ((p = PieceToChar.find(token)) != string::npos)
      {
          put_piece(Piece(p), sq);
          sq++;
      }
  }
Nice ! C++ (if used well) can be pretty awesome... I have to admit ;-)
Now here's my code, so everyone can have a good laugh. I would say my code is *state of the 1970's* at least 8-)

Code: Select all

void load_fen(Board *B, const char *fen)
{
	assert(fen);
	int r, f;
	char c = *fen++;

	// load the board
	reset_board(B);
	for (r = Rank8, f = FileA; c != ' ' && r >= Rank1; c = *fen++) {
		// empty squares
		if ('1' <= c && c <= '8') &#123;
			f += c - '0';
			assert&#40;f <= NB_RANK_FILE&#41;;
		&#125;
		// EOL separator
		else if &#40;c == '/') &#123;
			assert&#40;f == NB_RANK_FILE&#41;;
			r--;
			f = FileA;
		&#125;
		// piece
		else &#123;
			int sq = square&#40;r, f++), piece;
			for &#40;piece = Pawn; piece <= King; piece++) &#123;
				if &#40;c == PieceLabel&#91;isupper&#40;c&#41; ? White &#58; Black&#93;&#91;piece&#93;)
					break;
			&#125;
			assert&#40;piece_ok&#40;piece&#41;);
			int color = isupper&#40;c&#41; ? White &#58; Black;
			set_square&#40;B, color, piece, sq, true&#41;;
			if &#40;piece == King&#41;
				B->king_pos&#91;color&#93; = sq;
		&#125;
	&#125;

	// turn of play
	c = *fen++;
	assert&#40;c == 'w' || c == 'b');
	B->turn = c == 'w' ? White &#58; Black;
	B->st->key ^= B->turn ? zobrist_turn &#58; 0ULL;
	const int us = B->turn, them = opp_color&#40;us&#41;;

	// Castling rights
	if (*fen++ != ' ') assert&#40;0&#41;;
	c = *fen++;
	B->st->crights = 0;

	if &#40;c == '-')	// no castling rights
		c = *fen++;
	else &#123;			// set castling rights &#40;in reading order KQkq&#41;
		if &#40;c == 'K') &#123;
			B->st->crights ^= make_crights&#40;White, OO&#41;;
			c = *fen++;
		&#125;
		if &#40;c == 'Q') &#123;
			B->st->crights ^= make_crights&#40;White, OOO&#41;;
			c = *fen++;
		&#125;
		if &#40;c == 'k') &#123;
			B->st->crights ^= make_crights&#40;Black, OO&#41;;
			c = *fen++;
		&#125;
		if &#40;c == 'q') &#123;
			B->st->crights ^= make_crights&#40;Black, OOO&#41;;
			c = *fen++;
		&#125;
	&#125;

	// en passant square
	assert&#40;c == ' ');
	c = *fen++;
	if &#40;c == '-') &#123;
		// no en passant square
		c = *fen++;
		B->st->epsq = NoSquare;
	&#125; else &#123;
		int r, f;
		assert&#40;'a' <= c && c <= 'h');
		f = c - 'a';
		c = *fen++;
		assert&#40;isdigit&#40;c&#41;);
		r = c - '1';
		B->st->epsq = square &#40;r, f&#41;;
	&#125;

	B->st->pinned = hidden_checkers&#40;B, 1, us&#41;;
	B->st->dcheckers = hidden_checkers&#40;B, 0, us&#41;;
	B->st->attacks = calc_attacks&#40;B, them&#41;;
	B->st->checkers = test_bit&#40;B->st->attacks, B->king_pos&#91;us&#93;) ? calc_checkers&#40;B, us&#41; &#58; 0ULL;

	assert&#40;calc_key&#40;B&#41; == B->st->key&#41;;
	assert&#40;calc_kpkey&#40;B&#41; == B->st->kpkey&#41;;
	assert&#40;psq_ok&#40;B&#41;);
&#125;
However to be perfectly fair, you hid some of the code in your operator >> (that extracts std::string tokens from a std::stringstream or sth like that)
Dave_N
Posts: 153
Joined: Fri Sep 30, 2011 7:48 am

Re: Problem with bitboard knight attack generator

Post by Dave_N »

lucasart wrote:
mcostalba wrote:
Dave_N wrote:Here is how I handled the fen
Not bad, but C++ is more powerful ;-)

Code: Select all

  const string PieceToChar&#40;".PNBRQK  pnbrqk  ");
  std&#58;&#58;istringstream fen&#40;fenStr&#41;;
  Square sq = SQ_A8;
  char token;

  while (&#40;fen >> token&#41; && !isspace&#40;token&#41;)
  &#123;
      if &#40;token == '/')
          sq -= Square&#40;16&#41;; // Jump back of 2 rows

      else if &#40;isdigit&#40;token&#41;)
          sq += Square&#40;token - '0'); // Skip the given number of files

      else if (&#40;p = PieceToChar.find&#40;token&#41;) != string&#58;&#58;npos&#41;
      &#123;
          put_piece&#40;Piece&#40;p&#41;, sq&#41;;
          sq++;
      &#125;
  &#125;
Nice ! C++ (if used well) can be pretty awesome... I have to admit ;-)
Now here's my code, so everyone can have a good laugh. I would say my code is *state of the 1970's* at least 8-)

Code: Select all

void load_fen&#40;Board *B, const char *fen&#41;
&#123;
	assert&#40;fen&#41;;
	int r, f;
	char c = *fen++;

	// load the board
	reset_board&#40;B&#41;;
	for &#40;r = Rank8, f = FileA; c != ' ' && r >= Rank1; c = *fen++) &#123;
		// empty squares
		if ('1' <= c && c <= '8') &#123;
			f += c - '0';
			assert&#40;f <= NB_RANK_FILE&#41;;
		&#125;
		// EOL separator
		else if &#40;c == '/') &#123;
			assert&#40;f == NB_RANK_FILE&#41;;
			r--;
			f = FileA;
		&#125;
		// piece
		else &#123;
			int sq = square&#40;r, f++), piece;
			for &#40;piece = Pawn; piece <= King; piece++) &#123;
				if &#40;c == PieceLabel&#91;isupper&#40;c&#41; ? White &#58; Black&#93;&#91;piece&#93;)
					break;
			&#125;
			assert&#40;piece_ok&#40;piece&#41;);
			int color = isupper&#40;c&#41; ? White &#58; Black;
			set_square&#40;B, color, piece, sq, true&#41;;
			if &#40;piece == King&#41;
				B->king_pos&#91;color&#93; = sq;
		&#125;
	&#125;

	// turn of play
	c = *fen++;
	assert&#40;c == 'w' || c == 'b');
	B->turn = c == 'w' ? White &#58; Black;
	B->st->key ^= B->turn ? zobrist_turn &#58; 0ULL;
	const int us = B->turn, them = opp_color&#40;us&#41;;

	// Castling rights
	if (*fen++ != ' ') assert&#40;0&#41;;
	c = *fen++;
	B->st->crights = 0;

	if &#40;c == '-')	// no castling rights
		c = *fen++;
	else &#123;			// set castling rights &#40;in reading order KQkq&#41;
		if &#40;c == 'K') &#123;
			B->st->crights ^= make_crights&#40;White, OO&#41;;
			c = *fen++;
		&#125;
		if &#40;c == 'Q') &#123;
			B->st->crights ^= make_crights&#40;White, OOO&#41;;
			c = *fen++;
		&#125;
		if &#40;c == 'k') &#123;
			B->st->crights ^= make_crights&#40;Black, OO&#41;;
			c = *fen++;
		&#125;
		if &#40;c == 'q') &#123;
			B->st->crights ^= make_crights&#40;Black, OOO&#41;;
			c = *fen++;
		&#125;
	&#125;

	// en passant square
	assert&#40;c == ' ');
	c = *fen++;
	if &#40;c == '-') &#123;
		// no en passant square
		c = *fen++;
		B->st->epsq = NoSquare;
	&#125; else &#123;
		int r, f;
		assert&#40;'a' <= c && c <= 'h');
		f = c - 'a';
		c = *fen++;
		assert&#40;isdigit&#40;c&#41;);
		r = c - '1';
		B->st->epsq = square &#40;r, f&#41;;
	&#125;

	B->st->pinned = hidden_checkers&#40;B, 1, us&#41;;
	B->st->dcheckers = hidden_checkers&#40;B, 0, us&#41;;
	B->st->attacks = calc_attacks&#40;B, them&#41;;
	B->st->checkers = test_bit&#40;B->st->attacks, B->king_pos&#91;us&#93;) ? calc_checkers&#40;B, us&#41; &#58; 0ULL;

	assert&#40;calc_key&#40;B&#41; == B->st->key&#41;;
	assert&#40;calc_kpkey&#40;B&#41; == B->st->kpkey&#41;;
	assert&#40;psq_ok&#40;B&#41;);
&#125;
However to be perfectly fair, you hid some of the code in your operator >> (that extracts std::string tokens from a std::stringstream or sth like that)
In fact checking for a perfect FEN pasted into a program isn't as simple as that.
There is nothing pretty about my code however ... it works. The amount of rigorous checking needed isn't what I have in mind for internal board setting functions and so it is abstracted.

I simply want all the information about promotions, piece numbers first, I want to detect fantasy positions, I want to report errors to the user. So
the code simply checks every case I can think of in explicit form so that I can print the error easily ...
like this ...

Code: Select all

        // after reading the FEN ...

	if&#40; badChar )
	&#123;
		ERROR_MESSAGE&#40; "Error, Bad Char found in first part of FEN string, board cleared!");
		return;
	&#125;

	if&#40; rank > 7 )
	&#123;
		ERROR_MESSAGE&#40; "Error, too many ranks (/) detected in FEN string");
		return;
	&#125;

	for&#40; int col = 0; col < 2; col++ )
	&#123;
		if&#40; pawnsCount&#91;col&#93; > 8 )
		&#123;
			ERROR_MESSAGE&#40; "Error, Too many Pawns!");
			return;
		&#125;

		if&#40; KingsCount&#91;col&#93; == 0 )
		&#123;
			ERROR_MESSAGE&#40; "Error, Both Sides Must Have Kings!");
			return;
		&#125;
		if&#40; KingsCount&#91;col&#93; != 1&#41;
		&#123;
			ERROR_MESSAGE&#40; "Error, Too many Kings!");
			return;
		&#125;

// ... etc
&#125;
Dave_N
Posts: 153
Joined: Fri Sep 30, 2011 7:48 am

Re: Problem with bitboard knight attack generator

Post by Dave_N »

and if the FEN has 6 substrings separated by spaces I have code like this ...

Code: Select all

			if&#40; vecFen&#91;4&#93;.length&#40;) > 0 && vecFen&#91;4&#93;.length&#40;) <= 2&#41;
			&#123;
				int fiftyMov = atoi&#40;vecFen&#91;4&#93;.c_str&#40;));
				if&#40; fiftyMov > 50  || fiftyMov < 0 )
				&#123;
					ERROR_MESSAGE&#40; "Error, bad 50 move string, setting defaults!");
					fixMovNums = true;
				&#125;
			&#125;
			if&#40; vecFen&#91;5&#93;.length&#40;) > 4 || vecFen&#91;4&#93;.length&#40;) < 1 )
			&#123;
				ERROR_MESSAGE&#40; "Error, move number string was corrupted, setting defaults!");
				fixMovNums = true;
			&#125;
so each substring is checked for problems.

The internal FEN is treated like a bitboard.
User avatar
sje
Posts: 4675
Joined: Mon Mar 13, 2006 7:43 pm

Re: Problem with bitboard knight attack generator

Post by sje »

The FEN half move counter field is a count of ply, not a count of full moves. So its value can go to one hundred (or beyond if a fifty move draw goes unclaimed).