<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Recent changes to StalemateBug</title><link>https://sourceforge.net/p/project-invincible/wiki/StalemateBug/</link><description>Recent changes to StalemateBug</description><atom:link href="https://sourceforge.net/p/project-invincible/wiki/StalemateBug/feed" rel="self"/><language>en</language><lastBuildDate>Mon, 23 Mar 2015 13:31:15 -0000</lastBuildDate><atom:link href="https://sourceforge.net/p/project-invincible/wiki/StalemateBug/feed" rel="self" type="application/rss+xml"/><item><title>StalemateBug modified by Anonymous</title><link>https://sourceforge.net/p/project-invincible/wiki/StalemateBug/</link><description>&lt;div class="markdown_content"&gt;&lt;p&gt;Consider the following position: &lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="http://s19.postimage.org/tafce8zhf/Don_t_stalemate.png" rel="nofollow" /&gt;&lt;/p&gt;
&lt;p&gt;Black is a queen and a pawn ahead. Black will win... unless it plays like Project Invincible's AI did. &lt;/p&gt;
&lt;p&gt;In this position, the AI played either &lt;strong&gt;Ke5-f5&lt;/strong&gt; or &lt;strong&gt;Ke5-f4&lt;/strong&gt;. Both moves result in instant stalemates. &lt;/p&gt;
&lt;p&gt;A clue about what was wrong was that the bug triggered, and was apparently caused by, an optimization which I had made few days earlier. When the AI found a checkmate, it stopped searching and played the move leading to the checkmate. The optimization saved time and caused the AI to always pick the fastest mate. I saw that when the bug occurred, the AI never used all time it was given, so the optimization was triggered. So, the AI somehow believed that the moves will actually lead to checkmates. &lt;/p&gt;
&lt;p&gt;Project Invincible detects checkmates both in leaf nodes... &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="c1"&gt;// Check if the position is a checkmate.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;whiteHasWon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spaceForMoves&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;spaceForPosition&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mh"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blackHasWon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spaceForMoves&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;spaceForPosition&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mh"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;...and in other nodes. &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;countMoves&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Game has ended&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousPosition&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;_toMove&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;WHITE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;// White lost or draw&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousPosition&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;blackHasWon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spaceForMoves&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;MAX_MOVES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentPosition&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// White has lost&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Draw&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Black lost or draw&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;previousPosition&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;whiteHasWon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spaceForMoves&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;MAX_MOVES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;currentPosition&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Black has lost&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Draw&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Both snippets seem correct. It's the fault of the whiteHasWon() and blackHasWon() functions, right? &lt;/p&gt;
&lt;p&gt;Wrong. &lt;/p&gt;
&lt;p&gt;When debugging, I found what the problem was: in frontier nodes, so-called defence moves are generated. Defence moves are illegal moves where pieces capture friendly pieces. They are generated for both players, not only the one who is to move. They are used for Static Exchange Evaluation. &lt;/p&gt;
&lt;p&gt;As a result, in frontier nodes countMoves was never zero. In the stalemate positions (which are frontier nodes in a 2-ply search) it was around 35. &lt;/p&gt;
&lt;p&gt;So, let's see how the AI ended up thinking that the stalemate positions are actually checkmates. First, it proceeded to the main loop. &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;countMoves&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generateDefenceMoves&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;spaceForMoves&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;isLegal&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Naturally, illegal moves are ignored because they can't be played. The algorithm ignored all moves and went to the code following the loop. &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Alpha means the highest score that the white player can achieve regardless of how black plays. For example, it alpha is 1234, the white player will achieve a position worth at least 1234 points if he/she/it plays perfectly. &lt;/p&gt;
&lt;p&gt;In this case, the value of alpha wasn't altered at all. The function returned the initial value of alpha. It is -100 000, meaning "no matter how well black plays, white will achieve at least a position where white is mated". That makes sense: black can't possibly achieve any better position. &lt;/p&gt;
&lt;p&gt;So, the function returned a score of -100 000 to the main AI function. The main AI function got excited: "Wooohooo! &lt;strong&gt;Ke5-f4&lt;/strong&gt; will lead to a checkmate! :D" The checkmate optimization was triggered and the AI ended up playing &lt;strong&gt;Ke5-f5&lt;/strong&gt; or &lt;strong&gt;Ke5-f4&lt;/strong&gt;. &lt;/p&gt;
&lt;p&gt;Needless to say, this is now fixed. &lt;/p&gt;&lt;/div&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Anonymous</dc:creator><pubDate>Mon, 23 Mar 2015 13:31:15 -0000</pubDate><guid>https://sourceforge.net443616149bd26ef636da2b8260b30dfff3fcbb6f</guid></item></channel></rss>