broken?

Discussion of chess software programming and technical issues.

Moderator: Ras

flok

broken?

Post by flok »

Hi,

As you people might have noticed, I've been working on my chess engine.
It'll become the original POS in a deep (multithreaded) brute force way.
First step is implementing a brute-force thingy, then bolting on the evaluator of the original pos. That last step won't be too difficult, but this brute force alpha/beta code; something is wrong with it.
I googled a little and it seems all implementations are different. Not just naming of variables and such, but how they work too. For example the implementation on wikipedia (english) seems to totally wrong.
So maybe you people could take a look at my code and tell me what is wrong?

Code: Select all

int alphaBeta(final Scene scene, final PlayerColor side, final PlayerColor rootSide, final double finishBefore, int depth, final int maxExtension, final IO io, int alpha, int beta) throws IOException {
	// keep track of the number of processed nodes
	nodeCount.addAndGet(1);

	final PlayerColor otherSide = Statics.opponentColor(side);
	if (depth <= 0) {
		// if the bottom of the tree is check, search a little deeper
		if (depth > maxExtension && scene.isKingUnderAttack(side, otherSide) && !scene.isCheckMate(side)) {
			// if check then extend the searchdepth
			// by entering this if, the 'depth--' is not invoked and also the next 'depth == 0' is not triggered
			//io.progressCallback("Search depth extension due to check " + depth);
			long now = System.currentTimeMillis();
			if (now - prev.get() > 5000) { // emit output after 5s
				double took = Statics.getTimestamp() - startTs;
				io.progressCallbackDepthReached(maxDepth - depth);
				prev.set(now);
			}
		}
		else {
			return getShannonValue(scene, side); // score
		}
	}
	depth--;

	final List<Move> moves = scene.getMoveList(side);
	int movesListSize = moves.size();
	totalNMoves.addAndGet(movesListSize);
	totalNodes.addAndGet(1);
	for(int index=0; index<movesListSize; index++) {

		// "do a move", 'afterMove' is the "situation" after 'currentMove' was executed
		Scene afterMove = null;
		final Move currentMove = moves.get(index);
		try {
			afterMove = currentMove.getScene();
			if (afterMove == null)
				afterMove = scene.Move(currentMove);
			else
				currentMove.setScene(null); // help GC, link is not required
			afterMove.validateMoves(otherSide);
		}
		catch(Exception e) {
			System.out.println(" side: " + side + " alpha " + alpha + " beta " + beta);
			System.out.println(" " + (depth + 1) + " " + currentMove);
			Statics.displayBoard(afterMove.getBoard());
			System.out.println("" + e);
			e.printStackTrace();
			System.exit(1);
		}

		final int score = alphaBeta(afterMove, otherSide, rootSide, finishBefore, depth, maxExtension, io, alpha, beta);
		if (score == -Integer.MAX_VALUE || score == Integer.MAX_VALUE)
			continue;
		afterMove.shrink();

		if (side == rootSide) { // MAX
			if (score >= beta)
				return beta;
			if (score > alpha)
				alpha = score;
		}
		else { // opposite side: MIN
			if (score <= alpha)
				return alpha;
			if (score < beta)
				beta = score;
		}

		double nowTs = Statics.getTimestamp();
		if (finishBefore > 0 && finishBefore <= nowTs) {
			io.progressCallback("Abort search due to time limit");
			break;
		}
	}

	if (side == rootSide)
		return alpha;
	
	return beta;
}
User avatar
Codesquid
Posts: 138
Joined: Tue Aug 23, 2011 10:25 pm
Location: Germany

Re: broken?

Post by Codesquid »

but this brute force alpha/beta code; something is wrong with it.[...]
So maybe you people could take a look at my code and tell me what is wrong?
At first glance your alpha-beta implementation looks fine.

You mention that there's something wrong with it, but don't mention what is actually wrong. Can you please describe in more detail what the problem appears to be?


Update: How are you handling terminal nodes? That is checkmates and draws.
nanos gigantium humeris insidentes
zongli
Posts: 13
Joined: Sat May 12, 2012 9:45 pm

Re: broken?

Post by zongli »

The Wikipedia implementation looks alright as well. What problems are you observing? It may be easier to just switch to negamax, you'll probably do so eventually. Also, I'd advise against allowing values such as Integer.MAX_VALUE to be propagated in search; they're prone to causing bugs due to overflow (negating Integer.MIN_VALUE has no effect, etc).
Sven
Posts: 4052
Joined: Thu May 15, 2008 9:57 pm
Location: Berlin, Germany
Full name: Sven Schüle

Re: broken?

Post by Sven »

Hi Folkert,

1. why don't you use alphaBeta with negamax framework? It is much easier to read and to debug.

2. Could you please check whether your comment

Code: Select all

// by entering this if, the 'depth--' is not invoked and also the next 'depth == 0' is not triggered
is really valid? It seems "depth--" is in fact NOT skipped.

3. Have you tested your code without check extension and also without time control? Whenever you observe dubious or buggy behaviour in very basic parts like alphaBeta search, check whether it works without everything that has only been added as kind of an optimization. If the problem persists then it usually means your basic algorithm and/or data structures are faulty but if it goes away then the "optimization" has introduced a bug. Isolate the problem.

4. What exactly do you believe is "totally wrong" in the alphaBeta pseudocode (note: it is not an "implementation") at wikipedia (I assume you mean this link)? It looks fully correct to me.

5. Please, don't even think of starting to work on multi-threaded search until you have a fully working single-threaded implementation with most of today's standard search features.

Sven
flok

Re: broken?

Post by flok »

Codesquid wrote:
but this brute force alpha/beta code; something is wrong with it.[...] So maybe you people could take a look at my code and tell me what is wrong?
At first glance your alpha-beta implementation looks fine.
You mention that there's something wrong with it, but don't mention what is actually wrong. Can you please describe in more detail what the problem appears to be?
Well it plays incredibly weak!
And it is enormously slow. It takes an hour to reach 7 plies. So I thought maybe there's something wrong with cutoffs
Update: How are you handling terminal nodes? That is checkmates and draws.
The evaluation routine returns -10000/+10000 in such case.
flok

Re: broken?

Post by flok »

zongli wrote:The Wikipedia implementation looks alright as well. What problems are you observing?
Well, one thing was that it was missing a return-parameter but that has been fixed since I looked, dammit :-)
It may be easier to just switch to negamax, you'll probably do so eventually. Also, I'd advise against allowing values such as Integer.MAX_VALUE to be propagated in search; they're prone to causing bugs due to overflow (negating Integer.MIN_VALUE has no effect, etc).
Good one, will change that.
flok

Re: broken?

Post by flok »

Sven Schüle wrote:Hi Folkert,
1. why don't you use alphaBeta with negamax framework? It is much easier to read and to debug.
Never heard of it to be honest! Will look into it.
2. Could you please check whether your comment

Code: Select all

// by entering this if, the 'depth--' is not invoked and also the next 'depth == 0' is not triggered
is really valid? It seems "depth--" is in fact NOT skipped.
I have no idea what I meant with it. Old comment. Now removed.
3. Have you tested your code without check extension and also without time control? Whenever you observe dubious or buggy behaviour in very basic parts like alphaBeta search, check whether it works without everything that has only been added as kind of an optimization. If the problem persists then it usually means your basic algorithm and/or data structures are faulty but if it goes away then the "optimization" has introduced a bug. Isolate the problem.
Hmmm. I could indeed let for example gnuchess calculate what should be the best move in 3 plies and then see what my program does.
5. Please, don't even think of starting to work on multi-threaded search until you have a fully working single-threaded implementation with most of today's standard search features. Sven
Oh the behaviour in single thread is the same. Also, I did not implement advanced scheduling and queues: I simple split the list of moves at the top so that each thread has a couple of them to work on. Well unless, there are less than the number of processors.
zongli
Posts: 13
Joined: Sat May 12, 2012 9:45 pm

Re: broken?

Post by zongli »

So the code is fully functional; there's no bugs? With a high branching factor, taking a few hours to reach depth 7 is not unreasonable. However, I remember playing around with your engine a while back, and I was surprised at how slow it searched. I remember it was on the order of thousands of nodes per second. Hopefully you've improved in that regard. :)

I suggest to not bother with parallel search at this time, as it's much easier to see massive improvements by lowering the branching factor. With improved move ordering (killer heuristic, hash move, MVV/LVA), a depth 7 search takes under a second. With theoretically unsound techniques (null move, LMR, futility pruning), you'll go even farther.
flok

Re: broken?

Post by flok »

zongli wrote:So the code is fully functional; there's no bugs?
The move-generator and such has played almost 100.000 games on fics now with 0 crashes and 0 illegal moves.
With a high branching factor, taking a few hours to reach depth 7 is not unreasonable.
I was hoping that this alpha-beta-pruning fixed that :-/
However, I remember playing around with your engine a while back, and I was surprised at how slow it searched. I remember it was on the order of thousands of nodes per second. Hopefully you've improved in that regard. :)
Not improvements. The 6 core computer does around 45k nodes/s.
Ran it through a profiler (http://yourkit.com/) and the current implementation can't be made faster. It is oo-code so it allocates a new object for each move etc. which is slow. The only solution is better algorithms.
User avatar
Codesquid
Posts: 138
Joined: Tue Aug 23, 2011 10:25 pm
Location: Germany

Re: broken?

Post by Codesquid »

flok wrote:Not improvements. The 6 core computer does around 45k nodes/s.
That is very slow.
Ran it through a profiler (http://yourkit.com/) and the current implementation can't be made faster.
Would you wager on that?
It is oo-code so it allocates a new object for each move etc. which is slow. The only solution is better algorithms.
Object oriented code in general can be written to be extremely fast. Not sure whether that statement holds for Java though.

Code: Select all

 final List<Move> moves = scene.getMoveList(side); 
...
final Move currentMove = moves.get(index); 
I'm not familiar with Java lists, but accessing the n-th element of a linked list is typically O(n). I would recommend using an iterator to traverse the list.
nanos gigantium humeris insidentes