CQL by Example — Part I

This is the first part of the main line of the CQL by Example document suite. In assembling a large and varied collection of sample queries, we hope to ease users into what amounts to a very steep learning curve. There are no shortcuts. Read the CQL reference documentation. Take a long walk. Read it again. If after all of that one is still in the game, what follows below might have something to contribute.

The document has been through a number of revisions over the years. While there is no "right" way to approach a query, much of this part of the suite was written before we honestly had a clue. In places it's quaint. In other places it's just plain lame. Though we've corrected some outright errors and have improved a few of the examples over time, we've decided to leave things largely in their original state because 1) it tracks more or less with the path that the typical user will follow in climbing the curve, and 2) humility has its place in human affairs.

Examples in the conventional domain

Many of the examples are taken from Wikipedia's glossary of chess terms and index of mate patterns. A more detailed explanation of the themes and patterns may be found on that site.

We begin with some simple things that we hope are also useful, then make a point of introducing the set operations which are at the heart of CQL. We'll also demonstrate a number of static positional patterns before finally moving on to the land of the moving target. That's where the fun begins. We hope the reader enjoys the ride.

Some simple one-liners

Even a one-liner can produce some interesting results. -- H.D. Thoreau (on a bad day)

  • Answering check with mate
  • The simplest of queries, but it turns up some interesting results. Of necessity, the mating move must simultaneously cover the check. The query employs the line filter, which matches a line of play beginning with the current position.
  • cql() line --> check --> mate

  • The royal check
  • Essentially a cross-check where the king moves out of check to reveal a discovered check. Again we employ the line filter to make the match. We also introduce the flipcolor transform — widely used throughout the document — to match positions from either the white or the black perspective.
  • cql() line --> flipcolor {check  move from k} --> check

  • Castling opposite sides
  • White castles king side before black castles queen side. The regular expression in line between the castling moves allows zero or more intervening moves (actually positions) of any kind. And the braces are necessary on the line filter's first constituent because it is a compound filter.
  • cql() line --> {wtm  move o-o} --> .* --> move o-o-o

  • Alekhine's gun
  • A form of battery in which a queen backs up two rooks on the same file, oriented toward the opponent's field. The ray filter finds patterns of pieces arranged along a line, either adjacent to one another or separated only by empty squares. The up parameter to the filter establishes the direction of the ray, the sense of which is reversed by the flipcolor transform when evaluated from black's perspective.
  • cql() flipcolor ray up (Q R R)

  • Double check with capture
  • We're looking for a double check by bishop and knight. We also wish for the move that leads to the double check to be a capture.

    We offer two queries, both of which satisfy our criteria. The line filter probably feels more natural but is not necessary. Note that the two queries match from different positions.

    Also note that the [BN] piece designator does not allow for anything other than knight and bishop since it is impossible for two bishops or two knights to give a double check.

  • cql() flipcolor line --> move capture a --> [BN] attacks k == 2
    
  • cql() flipcolor {[BN] attacks k == 2  move previous capture a}
    

  • A pawn forks both rooks
  • A pattern that is fairly common in over-the-board games. The query matches the pattern with a compound filter of piece designators, which is flipped and shifted across the board.
  • cql() flipcolor shift {ra8 rc8 Pb7}

  • A three-way knight fork
  • The knight is poised to do some serious damage. The fork may be of any combination of queens and rooks. We limit the search to positions in which the knight is not subsequently captured.

    The piece filter iterates over every white knight on the board, matching a position if the number of rooks and queens attacked by the knight is greater than two, and if the move from that position is not a capture of the knight.

    We should point out a subtlety that is often overlooked and can lead to a bit of head scratching. The attack filters return different sets as determined by the left hand argument to the respective filter. We wish to take the cardinality of the set of attacked pieces as opposed to the set of attacking pieces. The attackedby filter gives us that set.

  • cql() piece Knight in N  [qr] attackedby Knight > 2 and not move capture Knight
    

  • Rook pinned to queen
  • A rook is pinned against the queen by the opposing QB pair. Another ray filter example, this time with a direction parameter restricting the ray to diagonal lines. Without that qualification, the ray would also match the pieces arranged orthogonally even though there would be no pin of the rook by the bishop in that case.
  • cql() flipcolor ray diagonal (Q B r q)

  • A skewered king
  • A so-called absolute skewer. The xray filter is a generalized pin. The compound piece designators match a skewer by either a bishop or a rook. Note that in the case of attack style filters (unlike the ray filter), a direction parameter is not necessary as any given line piece can only attack along specific lines.
  • cql() flipcolor xray ([BR] k q)
  • cql() flipcolor pin from [BR] through k to q

  • Fianchettoed bishops
  • The basic query places opposing bishops on a long diagonal, giving the designators for one diagonal and then flipping about the vertical axis.

  • cql() flipvertical {Bb2 bg7}
  • We next extend the query to stipulate that one bishop attacks the other, thus eliminating any intervening pieces from between the bishops.
  • cql() flipvertical {Bb2 attacks bg7}
  • Finally, we insist that the black bishop is pinned against the black king.
  • cql() flipvertical {pin from Bb2 through bg7 to k}

  • The unbalanced exchange
  • An exchange of a queen for some combination of pieces and pawns can lead to a very interesting game. The outcome will often depend on how well the pieces are coordinated. The resulting play can be quite instructive.

    The first query looks for positions with equal material valuation but with only one queen on the board. Here we give no consideration to the composition of the pieces exchanged.

  • cql() power A == power a and [Qq] == 1
    
  • However, we can further stipulate the material makeup of the exchange for the side without a queen. For example, the following query matches a position where a queen has been exchanged for three minor pieces.
  • cql() power A == power a and [Qq] == 1 and abs(#[BN] - #[bn]) == 3
    

Sets and set operations

Many CQL filters operate on sets of squares and may yield a set as the result of the operation. We'll provide a few samples in this section that will highlight these operations, but nearly all of the examples in the document likewise include operations on sets. Sets of squares are at the heart of the language.

  • A pinned rook attacks the king
  • Because we can do no better, we borrow from the online reference what is an excellent demonstration of the intersection of two sets. From the reference:
    The left argument to the & filter is pin whose value is the set of squares on which there is a pinned piece. The right argument to the & filter is {R attacks k} , whose value is the set of squares on which there is a white rook that attacks the black king. The value of the & filter is the intersection of these two sets.
  • cql() pin & {R attacks k}
  • This one simple example pretty much sums up the language.

  • Pawns on the attack
  • We want to search for positions in which some minimum number of pieces is attacked by pawns of the opposing side. In this case, we're looking for pawn attacks against pieces that cannot fight back (rooks and knights), and we're interested in positions with a large number of such simultaneous attacks.

    We give three approaches to the query. The first employs the obvious lone attack filter operating on the two sets of interest, and would be the more efficient of the three. The second query takes the intersection of the set of squares occupied by any rook or knight with the set of squares attacked by any opposing pawn.

    By contrast, the third query employs the not so obvious direction filters in determining the set of squares that are attacked by the pawns; i.e., one square up from each pawn and then on either side of each of those squares. Note that these examples utilize numerous unnecessary groupings to help make the syntax more readable.

  • cql() flipcolor {([rn] attackedby P) >= 3}
    
  • cql() flipcolor {([rn] & (. attackedby P)) >= 3}
    
  • cql() flipcolor {([rn] & (horizontal 1 (up 1 P))) >= 3}
    

  • A passed pawn on the h-file
  • In other words, a passed pawn that intersects with one of the squares of the h-file (given by the square designator h1-8). Version 6 of CQL adds filters for identifying sets of connected, doubled, isolated or passed pawns. The second query expands the passedpawns filter to its raw equivalent. Try counting the number of sets generated.
  • cql() passedpawns & h1-8
    
  • cql() flipcolor {P & ~down horizontal 0 1 p} & h1-8
    

  • King on the line
  • We're looking for positions in which all of the line pieces for one side have the opposing king on their line, and with no intervening friendly pawns. Again, we're working with the intersection of sets but with a special case in which all of the members of one set intersect with the other.

    The query employs the in filter, which is true (matches) if the set given by the left argument is a subset of the set given by the right argument. Each piece of each line piece type must intersect with its respective directional set (line) emanating from the opposing king. Note that it is not sufficient to stipulate [QRB] in anydirection k as that would allow, for example, a rook to lie on the king's diagonal and still qualify.

    The last line of the query employs the intersection of sets to ensure that there are no friendly pawns obstructing the line. The between filter operates on two sets and yields a set of all the squares between any square in the first set and any square in the second set. If there are no friendly pawns on any of those squares, we need not worry about friendly fire.

  • cql() flipcolor {
      [QRB] > 3 
      B in diagonal k
      R in orthogonal k
      Q in anydirection k
      not P & between([QRB] k)
    }
    

  • A blockaded IQP
  • A hypermodern theme of particular interest when a knight is the blockading piece. The pawn protects the blockading piece from some forms of attack, while the blockade prevents the advancement of the pawn.

    The isolatedpawns filter yields the set of all isolated pawns of either color, and we take the intersection of that set with the set of all white pawns on the board to give us the set of all isolated white pawns. The intersection of that set with the set of all squares on the d-file gives us the set of all isolated white pawns on the d-file.

    The up filter yields the set of squares immediately in front of the IQP(s), and the intersection of that set with the set of all black knights gives us the set of all black knights blockading an isolated queen pawn.

  • cql()  flipcolor {n & (up 1 ((P & isolatedpawns) & d1-8))}
    
  • See the companion document on understanding sets for a visualization of this example.

Mate patterns

The Chess Query Language is all about matching patterns. This section focuses on queries for static positions representing mate patterns, some of which are common place and a few of which are rare but are of interest for their esthetic value.

Most mate patterns are easily laid out in terms of piece placement, but then any number of conditions might apply to further qualify the position. Perhaps the mate must be a double check or each of the king's escape squares must be guarded by only one piece or perhaps all obstructing pieces must be friendly. The patterns come in many flavors.

  • Anastasia's mate
  • One finds a variety of competing definitions for this pattern lurking about the nethersphere, some of them self-contradicting. We'll begin with a knight and rook team up to trap the opposing king between the side of the board on one side and a friendly pawn on the other. But we're interested in a very specific pattern that goes beyond that definition: 1) the king must be in check by the rook and by only the rook, and 2) the knight must be guarding exactly two empty squares against the king's escape and must be the only guard.
  • // Anastasia's mate.
    // The rotate90 transform matches the pattern on all four edges.
    cql() mate flipcolor rotate90 {
      // Place the king and pawn in their relative positions.
      ka-h8  p & (down 1 k)
      // Establish that the king is attacked by a rook and by no other piece.
      R attacks k and A attacks k == 1
      // Only the knight guards the two empty escape squares.
      {square Guarded in (_ attackedby (N & down 3 k) & _ attackedby k)
          A attacks Guarded == 1} == 2
    }
    
  • The square filter iterates over a set of empty squares each of which is attacked by both the knight and the mated king. The knight is explicitly placed relative to the king such that those squares are on the rank adjacent to the king's rank. The cardinality of the set returned by the filter is given as exactly two. The body of the filter stipulates that only one white piece attacks the flight square bound to the variable Guarded, and we know that that piece must be the knight.

    Note that, in placing the king and pawn in their relative positions, we could as easily have shifted a pair of designators over the length of the ranks with a transform. Also worth noting is that there is no need to explicitly place the rook on the edge of the board since that is the only vantage from which a rook could attack the king.


  • Anderssen's mate
  • The king is mated on the back rank by a rook or a queen on the corner adjacent to the king. The mating piece is protected by a pawn. The pawn is protected by another piece. The lone flight square is either guarded or obstructed.
  • cql() mate flipcolor flipvertical { kg8 [RQ]h8 Pg7 }
    
  • The query is of a simple pattern given by a compound of piece designators, where the complement of the stated criteria are satisfied implicitly; i.e., the pawn must be protected and the escape square must be guarded or obstructed else the position is not mate.

  • Arabian mate
  • A knight and a rook coordinate an attack on the opposing king on a corner of the board. The rook must be adjacent to the king, guarding one of the flight squares. The knight protects the rook and guards the other flight square. To make this example more interesting, we'll add the condition that the mate is made possible by the deflection of an interposing piece.
  • // Arabian mate.
    cql() mate flipcolor flipvertical {
      // Place the pieces in their relative positions.
      kh8 R[h7,g8] Nf6
      // Both flight squares are empty and the king is checked by a lone piece.
      _ attackedby k == 2 and A attacks k == 1
      // Some opposing piece made a capture from a square in the mating rook's
      // line, making way for the mating move.
      line <-- Dest = R attacks k
           <-- Source = move from R
           <-- a & between(Source Dest) and move capture A
    }
    
  • The line filter looks back two positions to determine whether the mated side had a piece interposed between the rook and the rook's target mating square, and whether that piece was deflected (presumably by the forced capture of a sacrificing piece). The last constituent of that filter asks the question: was there any opposing piece sitting between the mating rook and it's mating square, and did that piece move out of the line of attack with a capture? Note that the capture must have been made by the interposing piece else the mate would not have been possible on the next move.

    This query only scored half a dozen hits out of a database of three million games, but those matching games had some interesting finishing combinations.


  • Bishop and knight mate
  • The king, bishop and knight join forces to drive the opposing king into a corner controlled by the bishop or to a square adjacent to the corner. The mate is arrived at with some difficulty and only with perfect play in the endgame, and is therefore somewhat rare.

    Mate may happen elsewhere on the edge of the board, or even on a corner not controlled by the bishop, but only with inaccurate play on the part of the defense. We'll limit our search to patterns that can be forced.

  • // Bishop and knight mate.
    // Flip the king to all four corners.
    cql() mate flipcolor flip {
      // Stipulate that the four pieces in question
      // are the only pieces on the board.
      [Aa] == 4  B == 1  N == 1
      // Place the mated king on a corner or adjacent square.
      k[h7-8,g8]
      // Sort by the length of the mating maneuver and mark the starting position.
      sort "Maneuver length"
      distance(position 0:find [Aa] == 4 and comment("Start Position") currentposition)
    }
    
  • The query is very simple, only explicitly giving the four pieces on the board and the limited placement of the mated king. That the position is mate implicitly enforces all other criteria. We sort the matching games by the length of the mating maneuver once the board has been reduced to four pieces.

  • Blackburne's mate
  • A knight and two bishops coordinate to mate the king in his castled position. The knight and the bishop on the long diagonal guard the king's flight squares while the other bishop gives mate from the h-file, protected by the knight.

    This mate pattern is so rare that we're going to construct a query that will also match games in which mate is imminently threatened at the terminal position. We'll consider games that have the result we expect while all the pieces are in position to conclude the mate on the opponent's resignation.

  • // Blackburne's mate.
    // Restrict consideration to a terminal position with the expected result.
    cql() terminal flipcolor { result 1-0 
      // Locate the castled king and rook and the cooperating knight.
      kg8 rf8 Ng5
      // Place one bishop on the long diagonal with a clear line to h8.
      {B & diagonal 0 5 a1} attacks h8
      // The other bishop is either on or threatening a move to h7 with mate.
      Bh7 or B attacks h7
      // Only the king defends h7.
      a attacks h7 == 1
    }
    
  • Note that in order for the threatened move to be threatening mate, the king must be the only opposing piece defending against the move. And since the terminal position is not mate, we're careful to keep the bishop on the long diagonal away from the king, which might otherwise capture it.

  • Boden's mate
  • Similar to the Blackburne pattern but with the king castled queen-side, this pattern is characterized by both bishops attacking the king along orthogonal diagonals from a distance (hence, no need for the knight), with the complement of the king's flight squares occupied by friendly obstructing pieces. While the typical pattern manifests after a sacrifice opens up the pawn structure, we'll also give a query covering a more general pattern with the king placed anywhere along the back rank.

    The first query matches a position in which the b-pawn is diverted from its defensive duties with a capture of a [presumably] sacrificing piece. The diversion opens up a window for the mate.

  • // Boden's mate.
    cql() mate flipcolor {
      // Locate the castled king and rook and the obstructing pieces.
      kc8 rd8  ad7-8 == 2
      // Place the mating bishop.
      Ba6 attacks k
      // Place the other bishop on the orthogonal diagonal with a
      // clear line to an empty b8.
      {B & diagonal 0 4 h2} attacks _b8
      // The b-pawn has just abandoned the defense of the king.
      line <-- . <-- move from pb7 capture A previous
    }
    

  • The general case, in which the king may occupy any square along the back rank, is a bit more involved. Because the king may occupy any square along the rank, the placement of the bishops and the orientation of their attacking lines must be relative to the king's location and so there is no point to a flip'n'shift of the piece configuration along the back rank.

    Once we've established that both bishops are attacking the king's field, we ensure that their attack orientation is orthogonal by stipulating that the attack field with diagonal orientation is orthogonally adjacent to the attack field of the other bishop within the king's field.

  • // Boden's mate.
    cql() mate flipcolor {
      // Place the king anywhere along the back rank.
      B attacks ka-h8
      // Assign sets of empty squares in king's field attacked by the bishops.
      BD = {{diagonal 1 k} & _} attackedby B
      BO = {{orthogonal 1 k} & _} attackedby B
      // Stipulate cardinality of sets and orientation of lines of attack.
      BD == 1  BO == 2  (orthogonal 1 BD) & BO == 2
      // Only the bishops are guarding or attacking the king or his escape.
      {_ attackedby k | k} attackedby [KQRNP] == 0
    }
    
  • Note that, rather than explicitly stipulating the presence of the friendly obstructing pieces, we're guessing that something must be obstructing the king's escape and, frankly, what are the odds that the pieces would be hostile.

  • Box mate
  • The mated king is on the edge of the board, the king of the mating side is in [effective] opposition, and the rook delivers mate from any square along the edge. It's not so much the mate pattern that we're interested in as the mating technique that leads to the pattern. The mating side confines the opposing king to a shrinking rectangle to force the king to the edge of the board, boxing in the king. Once the kings are in opposition, the rook delivers the mate.

    To make things more interesting, we'll allow pawns on the board as potential obstacles or spoilers. Pawns of the mated side must all eventually be blocked in order to force the opposition. The query also ensures that the king's field is completely empty, satisfying a condition of the pattern.

  • // Box mate.
    // Rotate the pattern to all edges of the board.
    cql() mate flipcolor rotate90 {
      // Place the king on the edge and attacked by the rook.
      ka-h8   R attacks k
      // Only the one rook and pawns allowed in the position.
      [QRBNqrbn] == 1
      // No pawns obstruct or guard against the king's escape.
      not ([Pp] attackedby k or P attacks (. attackedby k))
      // Sort by the length of the mating maneuver and mark the starting position.
      sort "Maneuver length"
      distance(position 0:find [QRBNqrbn] == 1 and comment("Start Position") currentposition)
    }
    
  • Note that we do not explicitly place the kings in opposition in the terminal position. It's a simple matter to do so, but, as it is the mating technique that we're interested in, all of the patterns are instructive.

  • Cozio's mate
  • The king is mated by a queen that occupies a square adjacent to the king on a diagonal. The queen guards all of the king's flights along three lines, the complement of the king's field being obstructed by friendly pieces.

    The query gives the relative positions of the king, queen and obstructing pieces. There is no need to explicitly stipulate that the queen is protected, since the position would otherwise not be mate. It is necessary to ensure that all of the squares in the king's field that lie on the three lines projecting from the queen are empty, else the pattern fails. We shift and flip to match all possible orientations.

  • // Cozio's mate.
    // Shift and flip the pattern across the board.
    cql() mate flipcolor shift flip {
      // Establish the pattern with two friendly obstructing pieces.
      Qh2 kg3 [a]f3 [a]g4
      // All other squares are empty and only the queen prevents flight.
      _ attackedby k == 5 and A attacks (_ attackedby k) == 1
    }
    

  • Damiano's mate
  • The castled king is mated by a queen and supporting pawn. Because this pattern very rarely goes to mate, we give the expected result at the terminal position and then place the queen on the h-file either on h7 or with no intervening pieces on her line of attack.

    A common mating pattern has a rook sac on h8 leading to the mate. The query matches a number of would-be Damiano's but for the early resignation.

  • // Damiano's mate.
    // Mate or resignation with imminent mate.
    cql() terminal flipcolor { result 1-0 
      // Establish the basic pattern.
      kg-h8 rf8 pg7 Pg6
      // Queen mates on h7 or threatens mate.
      Q attacks h8
      // A rook sacrifice at h8 preps mate with tempo.
      sort "Line length"
      line <-- .{1 6} <-- move from k capture Rh8
    }
    
  • We look back some number of moves from the terminal position for the rook "sacrifice" and sort by the length of the line leading to mate or resignation post-sac. Our guess is that the longer the line, the more interesting it's likely to be.

  • Double bishop mate
  • Unlike the Boden's mate, these bishops attack the king along adjacent diagonals. The bishops are the only pieces participating in the mate, with the mated king in a corner and with his escape obstructed by a friendly pawn.

    As was the case with Boden's mate, the matching query has the bishops attacking empty squares in the king's field. There is no need to explicitly stipulate that the bishops attack on parallel diagonals as the king is in a corner and there is a bishop attacking each of the two empty squares in the king's field. That the position is mate guarantees the parallel orientation of the lines of attack.

  • // Double bishop mate.
    cql() mate flipcolor flipvertical {
      // Place the king in a corner with a friendly obstructing pawn.
      kh8 ph7
      // Both bishops (and only the bishops) participate in the mate.
      B attacks _g7-8 == 2 and A attacks g7-8 == 2
    }
    
  • We include this example not because the mate pattern is particularly interesting but because the lines of play leading to the pattern tend to be difficult to find. One can almost imagine the look of disgust on the opponent's face as the point of the winning combination sinks in.

  • Epaulette mate
  • We recognize this pattern with the king sandwiched between two rooks (the epaulettes) on the back rank and the queen delivering mate along the same file as the king. We'll also allow for a double epaulette with two pawns standing in front of the rooks.

    We're looking for the purest pattern where only the queen participates in the mate and only the epaulettes are obstructing the king's escape.

  • // Epaulette mate.
    // Shift the pattern along the back rank.
    cql() mate flipcolor shifthorizontal {
      // Locate the king and his epaulettes.
      ke8 rd8 rf8
      // King's field on adjacent rank are all empty or hold
      // a double epaulette.
      _ attackedby k == 3  or  p attackedby k == 2
      // The queen (and only the queen) checks.
      Qe1-6 attacks k  and  A attacks k == 1
      // Only the queen guards any of the flight squares.
      A attacks (_ attackedby k) == 1
    }
    
  • With the query above we locate the pieces in absolute terms and then shift the pattern with a transform. As an alternative, we can place the king anywhere along the back rank and then locate all other pieces relative to the king's location using direction filters.
  • // Epaulette mate.
    // Pieces located relative to the king's position.
    cql() mate flipcolor {
      // Locate the king and his epaulettes.
      ka-h8  horizontal 1 k & r == 2
      // King's field on adjacent rank are all empty or hold
      // a double epaulette.
      _ attackedby k == 3  or  p attackedby k == 2
      // The queen (and only the queen) checks.
      down 2 6 k & Q attacks k  and  A attacks k == 1
      // Only the queen guards any of the flight squares.
      A attacks (_ attackedby k) == 1
    }
    

  • Greco's mate
  • The king is mated along an edge-file of the board in or adjacent to the corner. A friendly pawn obstructs one avenue of escape, a hostile bishop guards the other(s), and the mate is delivered by a queen or rook along the edge. There are many different ways to arrive at a Greco but we'll first give a query that matches the basic pattern.
  • // Greco's mate - basic pattern.
    cql()  mate flipcolor flipvertical {
      // Place the king and friendly obstructing pawn.
      kh7-8  pg7
      // The bishop guards the empty flight square on the back rank.
      B attacks _g8
      // A queen or rook on the h-file effects the mate.
      [QR]h1-5 attacks k
      // If the king is on h7, the g6 flight is guarded by the Q or B.
      if k attacks _g6 then [QB] attacks g6
    }
    
  • The Greco often involves a sacrifice on h7 or h8 in order to open up the h-file or to draw the king away from g6 or f7. The line filter looks back four positions for a capture by the king drawing him away from his flights.
  • // Greco's mate - with sacrifice.
    cql()  mate flipcolor flipvertical {
      // Place the king and friendly obstructing pawn.
      kh7-8  pg7
      // The bishop guards the empty flight square on the back rank.
      B attacks _g8
      // A queen or rook on the h-file effects the mate.
      [QR]h1-5 attacks k
      // If the king is on h7, the g6 flight is guarded by the Q or B.
      if k attacks _g6 then [QB] attacks g6
      // The king is deflected away from defense or flight.
      line <-- .{1 4} <-- move from k capture h7-8
    }
    
  • Another common theme with the Greco is mate by discovery as the bishop moves into position at f7. Occasionally, that discovered mate immediately follows the deflection of the king by sacrifice.
  • // Greco's mate - by discovery.
    cql()  mate flipcolor flipvertical {
      // Place the king and friendly obstructing pawn.
      kh7-8  pg7
      // The bishop guards the empty flight square on the back rank.
      B attacks _g8
      // A queen or rook on the h-file effects the mate.
      [QR]h1-5 attacks k
      // If the king is on h7, the g6 flight is guarded by the Q or B.
      if k attacks _g6 then [QB] attacks g6
      // King is deflected away from defense or flight.
      line <-- .{1 4} <-- move from k capture h7-8
      // Mate is by discovery as the bishop moves to f7.
      move previous from B to f7
    }
    

  • Hook mate
  • A configuration of rook, knight and pawn which mates a "hooked" king. The pawn protects the knight which in turn protects the rook, all of which get an assist from an obstructing piece that is friendly to the mated king. What more is there to say? Since the hook is all about the arrangement of pieces, one is tempted to simply shift and flip a collection of piece designators about the board.
  • cql() mate flipcolor flipvertical shift { ke7 af7 Re8 Nf6 Pe5 }
    
  • But that's too easy. And it allows for any number of "impurities" in the pattern. For example, are there other pieces either guarding or obstructing the king's flight? Or are there other pieces that are also defending the pieces in the chain?

    If we consider that a chain of protection lies at the center of the pattern, we can be a bit more imaginative in our approach to the query by employing a nest of attack filters as the foundation of our pursuit of purity.

  • cql() mate flipcolor { (R attackedby (N attackedby P)) attacks k }
    
  • Note that we no longer need to shift'n'flip a pattern about the board. The query draws in a large number of hits not the least bit related to a hook, but we can incrementally lay on further constraints which ultimately get us to the purest of patterns.
  • // Hook mate.
    cql() mate flipcolor {
      // Set up the attack chain.
      (R attackedby (N attackedby P)) attacks k
      // The king has five empty squares in his field.
      _ attackedby k == 5
    }
    
  • The most impactful thing we can do is ensure that the king has five adjacent empty squares. This one simple constraint 1) keeps the king away from the edge of the board (in the final incarnation), and 2) limits the number of obstructions in the king's field to the number specified by the pattern (though it says nothing about the nature of the obstruction).

    That whittles down the matching compositions considerably, but we still have way too many degrees of freedom in occupying the squares that are not empty.

  • // Hook mate.
    cql() mate flipcolor {
      // Set up the attack chain.
      (R attackedby (N attackedby P)) attacks k
      // The king has five empty squares in his field.
      _ attackedby k == 5
      // Exactly three pieces attack the king or his field.
      A attacks (. attackedby k | k) == 3
    }
    
  • But are they the right three? Note that if they are (just by happy accident), this constraint effectively forces the pawn in the chain into the hook formation guarding the odd flight square.

    The three-count very nearly gets the job done. But to be sure that we have the right three pieces, we need to see that the king is hooked.

  • // Hook mate.
    cql() mate flipcolor {
      // Set up the attack chain.
      (R attackedby (N attackedby P)) attacks k
      // The king has five empty squares in his field.
      _ attackedby k == 5
      // Exactly three pieces attack the king or his field.
      A attacks (. attackedby k | k) == 3
      // The king is adjacent to both the rook and the knight.
      [RN] attackedby k == 2
    }
    
  • The last of the filters enforces the archetype by landing the king on the one square that is sure to hook him. Note that all of the conditions given by the query are relative to the king, which may occupy any square on the board away from an edge.


  • King and two bishops mate
  • Two bishops and a king can force mate of a lone king so long as the bishops are on opposite colored squares. The terminal pattern is not of particular interest, but the mating technique that gets us there might be, and so we sort the matching games by the length of the mating maneuver and mark the start.
  • // King and two bishops mate.
    cql() mate flipcolor {
      // Limit the pieces on the board to kings and bishops.
      [Aa] == 4 B == 2
      // Sort by the length of the mating maneuver and mark the starting position.
      sort "Maneuver length"
      distance(position 0:find [Aa] == 4 and comment("Start Position") currentposition)
    }
    

  • King and two knights mate
  • The king with two knights cannot force a mate against a lone king. However, a forced mate is possible if the lesser side has a pawn or two. A search of a large database of GM games can turn up some instructive struggles (and some very long games) as the mating GM tries to find the right pattern to get the king into the corner without stalemate.

    The query only requires a determination of the pieces that are on the board, including any number of pawns on the mated side. We sort the matching games by the length of the mating maneuver and mark the beginning of the maneuver.

  • // King and two knights mate.
    cql() mate flipcolor {
      // Limit the pieces on the board to kings and knights and
      // any number of pawns on the mated side.
      [Aa]&~p == 4  N == 2
      // Sort by the length of the mating maneuver and mark the starting position.
      sort "Maneuver length"
      distance(position 0:find [Aa]&~p == 4 and comment("Start Position") currentposition)
    }
    

  • Mayet's mate
  • This mate takes place on the back rank by a rook protected by a bishop along the long diagonal, with an assist by an obstructing pawn. The mate is often set up by a sacrifice either clearing the h-file or forcing the capture by a defending piece.
  • // Mayet's mate.
    cql() mate flipcolor {
      // Locate the key pieces.
      kg8 pf7  B attacks Rh8
      // A capture of the [likely] sacrificing piece.
      sort "Line length"
      line <-- .{1 6} <-- move capture Ah7-8
    }
    

  • Morphy's/Pillsbury's mate
  • Sligtly different patterns, same query. The bishop and rook join forces to mate the king in or adjacent to the corner. One mates while the other guards the flight. The pattern has the rook on the g-file, the bishop on the long diagonal and a pawn obstructing the king on the h-file.
  • cql() mate flipcolor {
      ph7  Rg1-6 attacks g8  B attacks h8
      kh8 or (kg8 and not A attacks _f7-8)
    }
    
  • The attack filters ensure that it is either the rook or the bishop delivering mate and that they are working in concert. If the king is mated on g8 (Pillsbury's), we ensure that f7-8 are obstructed rather than guarded.

  • Opera mate
  • A mate pattern named after the famous Morphy game with the Duke at the Paris opera. The mate takes place on the back rank against an uncastled king and is realized with the aid of an obstructing piece friendly to the mated king.

    We give the piece designators for the two mating pieces and then align the partnering bishop by stipulating that it simultaneously attacks both the mating rook and the king's escape. We also stipulate that the bishop is the only piece guarding the escape or defending the rook.

  • // Opera mate.
    cql() mate flipcolor {
      // Place the king and obstructing piece.
      ke8 [a]f7
      // The bishop and only the bishop protects the rook.
      B attacks Rd8 and A attacks d8 == 1
      // The bishop and only the bishop guards the flight.
      B attacks _e7 and A attacks e7 == 1
    }
    

  • Smothered mate
  • A smothered mate is a simple pattern where the king is completely surrounded by friendly pieces, unable to escape a knight check. It is most common with the king on a corner square, but can technically occur anyplace on the board.

    The query is a one liner checking that the position is a mate and that the king has no hostile piece on any adjacent square.

  • cql() mate flipcolor { btm  [_A] attackedby k == 0 }
    
  • The mating pattern typically involves a sacrifice or two which draws the friendlies into the smothering configuration with the king in the corner. If one grows weary of the pattern and would like to examine a different flavor of smothered mate, one can take the king out of the corners with the following.
  • cql() mate flipcolor { btm  [_A] attackedby k == 0  not (ka8 or kh8)}
    

  • Suffocation mate
  • Suffocation is similar to smothering but the mate is carried out by a knight and a bishop. The knight mates while the king is confined by a combination of his own obstructing pieces and the bishop guarding his escape.
  • // Suffocation mate.
    cql() mate flipcolor {
      // The knight and only the knight checks the king.
      N attacks k and A attacks k == 1
      // There are no hostile pieces in the king's field.
      A attackedby k == 0
      // There is at least one flight square...
      Escape =? _ attackedby k
      // ...and the bishop and only the bishop guards the flights.
      B attacks Escape and A attacks Escape == 1
    }
    

  • Swine's mate
  • The term swine refers to doubled rooks on their 7th rank. A swine's mate takes place on the back rank on the corner and the square adjacent to the corner, with the rooks teaming up in a series of checks that ultimately leave the king with no escape. The mate requires the assistance of an obstructing rook on the back rank.

    It's easy enough to give the final pattern as a compound of piece designators, but it is also necessary to ensure that the mating rooks are, indeed, swine. The line filter serves that very purpose. Note that the 7th rank pawns may be present or not.

    The query does not explicitly say anything about an obstructing piece, but that piece must be in place and it must be a rook, else we would not have mate.

  • // Swine's mate.
    cql() mate flipcolor flipvertical {
      // Place the rooks in the pattern.
      Rg-h7 == 2
      // Only the rooks participate in the mate.
      A attacks (_ attackedby k | k) == 2
      // The mating sequence ensures that the rooks are swine.
      line <-- (kg-h8 <-- move from Ra-h7 to g-h7) {2 3}
    }
    

Tactical patterns

Some of the simpler tactics have already been covered in earlier examples, but below are a few with a slightly more complicated theme or with numerous variations on a theme. In some cases we'll even endeavor to ascertain the immediate motive for the tactic.

  • The sacrifice
  • A move or capture resulting in loss of material with compensation in the form of an advantage in space or development, or with an imminent attack in the works.

    Of particular interest is the exchange sac, which may have been Petrosian's favorite flavor. In his hands it became a tactical device with deep positional implications. The exchange contrasts with other sacrifices in that during the early middle game the board is sufficiently congested that the rook is not as relatively effective as an active knight or a "good" bishop. From a Petrosian-esque perspective, pieces are assigned value as much for their long term strategic relevance as for their material significance.

    What we are not interested in is the so-called sham sacrifice, where the material is quickly recovered or the opposing king is quickly forced to mate. We'll first show how to avoid matching such positions, and then we'll look at queries which will match an exchange that either improves a pawn structure or degrades a pawn structure. We'll also look at matching exchanges that are made with a motive of increasing the effectiveness (value) of some other piece or pieces.

    But before we get into any of that, we'll give a simple example matching the capture-recapture pattern with a couple of additional conditions that will filter out some unwanted noise.

  • // The exchange sac - basic pattern.
    cql()  flipcolor { result 1-0
      // The presumed sacrifice on the move leading to the current position.
      // The assignment identifies the rook that occupies the square
      // on which the capture was made.
      piece Rook = move to [bn] from R previous
      // The sacrificing side did not have a material advantage.
      power A <= power a + 3
      // The rook is not protected.
      not A attacks Rook
      // The rook is recaptured with the next move.
      move from [a] capture Rook
    }
    
  • The previous parameter of the move filter allows us to look back to the move that gives us the current position. That would be the rook move capturing a minor piece. We save the identification of that rook - with a piece assignment - for later use because we want to avoid matching captures or attacks on other rooks that might be on the board.

    A match is confined to those positions in which the sacrificing side does not already have a material advantage. We further limit the query to only match games with a positive result for the side that is the exchange down, thinking this ensures that the exchange is sound. The query is crude but fairly effective.

    It's worth pointing out a fine subtlety of the piece assignment in the query above. While the various move filter parameters are evaluated in the context of the previous position, piece identification is evaluated in the context of the current position. Thus, while the assignment as given above does work as intended, the assignment piece Rook = move from R capture [bn] previous does not work. Quoting Lewis Stiller:

    Yes, the interaction between "piece" and "move previous" can seem counterintuitive.

    Suppose White moves 10 Ra3-a5, moving a rook from a3 to a5. Suppose we call the position before White's 10th move, X. Let us call the position after white's 10th move, Y.

    Now suppose the current position is X, the position before White moves the rook.

    Then
    move from R
    
    has the value "a3".

    Thus,
    piece z = move from R
    
    would be the same as
    piece z = a3
    
    which would set the piece variable z to the rook on a3.

    Now suppose the current position is Y.

    Then as before,
    move from R previous
    
    would have a value of a3.

    However, since the current position is Y,
    piece z = a3
    
    would not match, as there is no piece on a3 in Y. That piece just moved, it's now on a5.

    Thus,
    piece z = move from R previous
    
    has the same effect as
    piece z = a3
    
    which doesn't match.

  • We turn to the line filter to demonstrate a method of filtering out a sham sacrifice where, ultimately, there is no sacrifice at all. For a sac to be the real deal the material loss must persist even after what might be a long sequence of exchanges. The query filters out positions in which a before and after measure of the relative material count for each side exposes the sham.

    We look out some number of moves from the recapture to see if the "sacrificing" side has recovered the material. Note that we stipulate that the material loss must persist for some minimum number of consecutive positions to account for oscillation.

  • // The exchange sac - filter out shams.
    cql()  flipcolor { result 1-0
      // The sacrificing side does not have a material advantage.
      power A <= power a
      // The sac is not a sham... the material loss persists.
      line
        --> {
            piece Rook = move from R capture [bn]
            PowerDiff = power A - power a
            }
        --> move from [a] capture Rook
        --> .{1 4}
        --> {power A - power a < PowerDiff}{4}
    }
    
  • Remember that the repetition range qualifier of the line filter is greedy. That is, even though the following constituent might match for the first iteration of the range, the ultimate match might run all the way out to the maximum extent of the range. It's also important to remember that with the range set several positions out, the following constituent might succeed, then fail, then succeed again as the filter iterates through the range. The point at which one is satisfied that the sac is not a sham is somewhat arbitrary.

  • An exchange intended to expose the opposing king or to disrupt the opponent's pawn structure is a fairly common maneuver. Here we'll match an exchange that accomplishes both objectives at once.
  • // The exchange sac - disrupt the pawn structure and expose the king.
    cql()  flipcolor { result 1-0
      // The rook is recaptured by a pawn, doubling and isolating,
      // and opening the file in front of the king.
      line
        --> piece Rook = move from R capture [bn]
        --> piece Pawn = move from (p & down 1 (k & a-h8)) capture Rook
        --> doubledpawns & isolatedpawns & Pawn and not p & down k
    }
    
  • The king must be on the back rank and the recapturing pawn is directly in front of the king. The last constituent of the line filter places the recapturing pawn at the intersection of all doubled and isolated pawns and ensures that the king's file is completely cleared of friendly pawns.

  • It is not at all unusual for a sacrifice to pave the way for a promotion. The following query matches a position where the recapturing pawn is drawn away from a file to allow an opposition pawn to pass. The passed pawn must have been on an adjacent file prior to the recapture, but not on the same file as the sac'd rook. The find filter will match any position in which the passed pawn later promotes. Note that this query will not match doubled passed pawns because the piece assignment fails if the cardinality of the assigned set is greater than one and because, well, we're lazy.
  • // The exchange sac - promote a passed pawn.
    cql()  flipcolor { result 1-0
      // The rook is recaptured by a pawn, making way for promotion.
      line
        --> {
            piece Rook = move from R capture [bn]
            SacPosition = currentposition
            }
        --> {
            piece Pawn = move from [p] capture Rook
            // The [potentially] passed pawn must be on a file adjacent to
            // the capturing pawn and opposite the captured rook.
            piece PassedPawn = (down horizontal 1 Pawn) & P & ~vertical Rook
            }
        --> connectedpawns & passedpawns & PassedPawn
      // Determine whether the passed pawn promotes.
      find move from PassedPawn promote A previous
           and comment("Promotion at " PassedPawn)
           and sort min "Promotion line length"
                 distance(SacPosition currentposition)
    }
    
  • We sort the matching games by the length of the line from sacrifice to promotion. The sort is in ascending order based on an assumption that the nearer the promotion is to the sac, the more likely that the sac had the promotion in mind. We believe the assumption holds for the most part, though we have seen some very long lines where promotion was clearly the motivation for the sacrifice.

  • Now we move into the realm of the speculative sacrifice, where the motivation for a sac might derive from a desire to weaken the opponent's color complex or to strengthen our own. Elimination of a hostile bishop that controls the same diagonals as our own bishop could be adequate compensation for the exchange.

    We'll consider a special case in which — subsequent to the exchange — a pawn promotes on a corner square supported by the surviving bishop. Note that the query takes the queens off the board.

  • // The exchange sac - eliminate a same-colored bishop.
    cql()  flipcolor { result 1-0
      // Limit the pieces on the board.
      [Qq] == 0  [Bb] == 2
      // The captured bishop controls the same diagonals as the friendly.
      sort min "Line length"
      line firstmatch
        --> {
            piece Rook = move from R capture b
            piece Bishop = move to b from R
            (light Bishop and light B) or (dark Bishop and dark B)
            }
        --> move from [a] capture Rook
        --> .*
        --> move to [a8,h8] from P & diagonal diagonal B
    }
    
  • The nested diagonal filters yield the set of all squares having the same color as that occupied by the bishop. If the pawn promotion takes place on the right corner, it will lie at the intersection with that set.

    The firstmatch parameter to the line filter short-circuits the natural greediness of the filter. We sort the matching games in ascending order so that the lines with the shortest distance from exchange to pawn promotion bubble to the top of the list.


  • Another possible motive for the exchange might be to acquire the bishop pair in an open position. Note that a cheap but fairly reliable means for opening up the position is to put an upper bound on the number of pawns on the board.
  • // The exchange sac - acquire the bishop pair.
    cql()  flipcolor { result 1-0
      // The sacrificing side does not have a material advantage.
      power A <= power a
      // Limit the pieces on the board.
      [Qq] == 0  [Pp] <= 8
      // The exchange leaves the down side with the two bishops.
      sort "Line length"
      line
        --> piece Rook = move from R capture b
        --> move from [a] capture Rook
        --> not move promote A {7 100}
        --> terminal and B == 2 and [RB] attacks k
    }
    
  • We allow the sham sacrifice since the play is nearly always interesting, regardless. No promotions are allowed post-sac and we set a lower bound on the length of the line to end of game since it is the bishop play that we're interested in studying. In the terminal position the bishops are still on the board and the hostile king is under attack by either a rook or a bishop.

  • We should not neglect the possibility that the exchange could be offered rather than initiated. This class of exchange is often intended either to create a passed pawn or to clear out the piece guarding the sac square, making way for an attack that follows immediately from that square.
  • // The exchange sac - offered.
    cql()  flipcolor { result 1-0
      // The sacrificing side does not have a material advantage.
      power A <= power a
      // Offer the exchange.
      line
        --> piece Rook = move from R to _
        --> piece Minor = move from [bn] capture Rook
        --> move from A capture Minor
    }
    

  • The block
  • A block is a defensive tactic in which a piece interposes between a hostile attacking piece and the piece being attacked. The query below matches the block of an attack on a queen by a rook or bishop, in which the opposing king is left in check either by the blocking piece or by discovery.
  • // The block.
    cql()  check  flipcolor {
      // The move previous filter gives us check in the current position.
      move from [RBNP] to between([br] attacks Q  Q attackedby [br]) previous
    }
    
  • The query is something of a shortcut, in that there could be a double attack on the queen or, for that matter, there could be multiple queens on the board. But, in practice, the query is sufficient for demonstration purposes.

  • Interference
  • Sacrificially interposing a piece between an attacked piece and its defender, or forcing the opponent to interpose one of its own pieces. The interposition disrupts the cooperation between pieces and might even interfere with a pending attack.

    The following query matches a position in which the opposing king is forced into interfering between two among his forces, leaving one of them en prise.

  • // Interference - by a friendly.
    cql() check flipcolor
      // Iterate over potential targets of the interference.
      piece Target in [qr]
        // Iterate over potential defenders of the targets.
        piece Defender in [rb]
          line
            --> // The king moves between the defender and the target.
                Defender attacks Target and
                move from k to between(Defender Target)
            --> // The target is defenseless and is captured on the next move.
                not a attacks Target and move capture Target
    

  • The next demonstration of interference is a bit complicated. The interfering piece is not a friendly, and we must take into consideration and eliminate instances of counterplay that represent unwanted noise in the pattern.

    The interposing piece must either pose or introduce a threat — directly or indirectly — or it must interrupt two lines simultaneously such that capturing the piece will necessarily break one of the lines of defense. (The latter case will be dealt with in Part II of this document.) The defended piece is most often more valuable than the interposing piece.

  • // Interference - by a hostile.
    cql() flipcolor
      // Iterate over potential targets of the interference.
      piece Target in [qr]
        // Iterate over potential defenders of the targets.
        piece Defender in [rb]
          line
            --> // The defender defends the target and the value of the 
                // interposing piece is less than the value of the target.
                {
                Line = between(Defender Target)
                Defender attacks Target
                piece Interposer = move from [RBN] to Line
                power Interposer < power Target
                }
            --> // Some piece other than the interposing piece attacks the
                // target; the interposing piece introduces a compelling threat.
                {
                (A & ~Interposer) attacks Target
                not move from Target
                check or Interposer attacks ([qr] & ~Target)
                }
            --> // The target is undefended and is captured on the next move.
                not a attacks Target and move capture Target
            --> // Eliminate lines where the target is captured by the king,
                // which is then left in check by discovery from the
                // defending piece.
                not (move from K previous and move from Line)
    
  • The first constituent of the line filter is as in the previous query, plus the stipulation that the [sacrificial] interposing piece must be of less value than the target of the interference. Else what is to be gained by the sacrifice?

    The main point of the second constituent is to ensure that the interfering piece has introduced a compelling threat that cannot be ignored. The reasoning is that if the target of the interference could move out of harm's way, it would move out of harm's way.

    The third constituent stipulates that — once the threat has been dealt with — the target is yet undefended and is captured on the next move. A recapture scenario has been eliminated.

    The last constituent filters out any line of play in which: 1) the interposing piece is captured by piece X (this will have happened as the move from the second position), 2) the target of the interference is captured by the king (move from K previous), and 3) piece X moves out of the interference lane with discovered check (move from Line). Such a sequence is not uncommon and is not what we're looking for. Note that it is possible (though highly unlikely) that piece X moved into the line without capture.


  • Clearance
  • There are two flavors of clearance that interest us. The first is a sacrifice or a deflection that clears a line of attack of a hostile piece, making way for an attack on the opposing king. The second is a sacrifice with tempo that clears a square of a friendly that can then be occupied in an attack on the king.

    The following query searches for a line of play matching the first flavor.

  • // Clearance - of a hostile piece.
    cql() flipcolor {
      line
        --> // The capture of the sacrificing piece.
            move capture (move to _ from [QRBN] previous)
        --> // The square just cleared is in the line of the next move.
            move from . previous & between(move from . move to .)
        --> // That move results in an attack on the king.
            check and not move capture (move to . previous)
    }
    

  • The next query matches a line of play in which a square is first cleared with tempo and is then occupied by another piece initiating a fatal attack on the opposing king.
  • // Clearance - of a friendly piece.
    cql() flipcolor { result 1-0
      line
        --> // The capture of the "sacrificing" piece.
            {
            move to (move to _ from [QRBN] previous)
            Square = move from A previous
            }
        --> // The "attack" begins on the square just cleared.
            move to Square
        --> // Mate or resignation with an upper bound on the line length.
            .{0 5}
        --> terminal
    }
    

  • NB: The reader was duly prepared up top for a few "naive" examples that have been left in place for humanity's amusement. A somewhat less strained approach to clearance may be found in the examples section of the online reference.

  • Discovered check
  • This example borrows from the online reference for the discovery mechanism. The idea is simple. Any piece that is attacking the opposing king — but did not make the move leading to the position — must be doing so by discovery.
  • cql() (A & ~move to . previous) attacks k
  • If we load that logic into a function definition, we can easily match a line of play where discovered check is answered by discovered check. The reversecolor transform frees us from the burden of manually editing the filter to reverse the white and black roles.
  • cql() flipcolor {
      function Discovery() { (A & ~move to . previous) attacks k }
      line
        --> Discovery()
        --> reversecolor Discovery()
    }
    
  • Note that the flipcolor filter includes the identity transform but the reversecolor filter does not.

  • Overloading
  • When a piece has more defensive assignments than it can handle, it is overloaded or overworked. The overloaded piece may be subject to a deflection, forcing or coercing the piece into abandoning one or more of its original defensive assignments.

    The exploitation of an overloaded piece can occur in a variety of ways. Capturing one of the defended pieces might draw the overloaded piece away from defending another piece or a key square. A check or threatened mate or a sacrifice might draw the piece away from its defending duties.

    It's easy enough to look for a piece that is overloaded defending other pieces, but it's a bit more of a challenge when deciding whether an overloaded piece is defending a key square or a rank or a file. However, we may infer one or the other based on the ensuing line of play. If the opposing side moves into a square or line which defense has been abandoned by the deflection, we might safely assume that the abandoned line is the point of the deflection.

    We'll be searching for positions in which each of three or more defended pieces has only the overloaded piece as a defender and is being attacked by the opposing side. There may be more than one attacking piece threatening any one defended piece but — in order to ensure that the overloaded piece is, indeed, overworked — any given attacking piece may only be attacking one of the defended pieces.

    We will also stipulate that the next move from any matching position is either a capture of one of the pieces defended by the overload, or that some piece is moving into the attack field of the overload. Our assumption in the latter case is that there's a good chance the move is intended to deflect the overload. If the deflection is followed by a move into the original field of attack of the overload and is not a capture, the odds are very good that the overload has abandoned the defense of a key square or line.

    Finally - and we believe this is not too much of a stretch - we shall guess that if the side with the overload is the losing side, the reason for the loss is at least not too distantly a consequence of the overload. Therefore, we limit the matching games to those in which the result is in favor of the opposing side.

  • // Overloading.
    // For the identity transform, a white piece is overloaded
    // with black to move and black to win and black is not in check.
    cql() ply > 20  not check  flipcolor { btm  result 0-1
      Defenders=[QRBN] Defendeds=[QRBN] Attackers=[qrbn]
      // Assign the set of squares occupied by overloaded pieces.
      Overloads =
        piece Defender in Defenders
          // An overloaded piece is defending at least three pieces.
          (piece Defended in Defendeds
            // Only the overloaded piece defends.
            (A attacks Defended) == 1  and
            piece Attacker in Attackers
              // Establish the chain of attack.
              ((Defended attackedby Defender) attackedby Attacker)  and
              // The attacking piece only attacks one defended piece.
              ((Defendeds attackedby Defender) attackedby Attacker) == 1
           ) > 2
      comment("Overload at "  Overloads)
      FoA = . attackedby Overloads
      // The overload is deflected from at least some of its defensive duties.
      line quiet
        --> Deflect = move to FoA
        --> move from Overloads capture Deflect
        --> Abandoned = FoA & ~(. attackedby Deflect | Deflect)
                  and
              move capture Abandoned comment("Capture")
                  or
              move to Abandoned comment("Control")
    }
    
  • The nested piece filters conduct the bulk of the work for the query. Iterating over their respective sets of pieces (actually squares, but never mind), the nested construct ensures that: 1) there is one and only one piece defending the defended piece, 2) the defended piece is attacked by a piece of the opposing side, and 3) the attacker attacks that one piece and no other piece.

    Note that the last constituent of the line filter relies on the fact that the logical or is implemented as a short circuit evaluation. If the move is a capture, evaluation stops at that point. Else the move is onto an empty square that was defended by the overloaded piece prior to the deflection.


    Out of a database of four millions games, this query matched roughly one hundred. Given the compute-intensive nature of the query, one might wish to pare down the size of the database with a preliminary query that will run much faster on the larger base. The following snippet will match about one percent of a typical base.

  • // Eliminate the first 20 ply from evaluation.
    cql() ply > 20  not check  flipcolor { btm  result 0-1
      Defenders=[QRBN] Defendeds=[QRBN] Attackers=[qrbn]
      piece Defender in Defenders
        (piece Defended in Defendeds
          Defender attacks Defended and Attackers attacks Defended
        ) > 2
    }
    

  • A deflected king
  • A deflected piece is drawn away from defending a piece, a square, or a rank or file. Deflections are somewhat related to overloading, but not all deflections are of overloaded pieces. Here we look for a king that is deflected by a piece of lesser value than the piece being defended by the king. The defended piece is captured immediately following the deflection.
  • // Deflection.
    cql() flipcolor {
      // Iterate over designated pieces of interest in the king's field.
      piece Defended in ([qrbn] attackedby k)
        // Iterate over all potential deflecting pieces.
        piece Deflector in A
          // The value of the deflecting piece is less than that of the target.
          power Deflector < power Defended  and
          // The move sequence.
          line
            --> move from Deflector
            --> move from k capture Deflector
            --> move from A capture Defended and
                  not Defended attackedby k
    }
    
  • The body of the innermost piece filter first asserts that the deflection yields a material gain. The line filter then stipulates a specific move sequence of the deflecting piece moving within the field of the king (which may be a capture), followed by a capture of the deflecting piece by the king, and finally by the capture of the piece that is the target of the deflection. We also ensure that the king cannot recapture.

  • A decoyed king
  • A decoyed piece (usually a king or a queen) is drawn to a poisoned square (usually by a sacrifice) where it becomes the object of a tactical maneuver such as a fork or of a mating sequence. Below we give an example for both cases where the decoyed piece is a king.

    In the case of a decoy followed by a mating sequence, we set an upper bound on the number of moves once the king has captured the decoy. The further away we get from the capture, the less likely that the decoy was the decisive factor in the result.

  • // Decoy - followed by mate.
    cql() flipcolor { result 1-0
      // The move sequence, with the king capturing the decoy.
      sort min "Line length"
      line singlecolor
        --> not check and move from k capture (move to _ previous)
        --> .{0 4}
        --> mate  // or terminal
    }
    
  • We stipulate that the decoy has not placed the king in check, as those cases tend to be more interesting. The results are sorted in ascending order, moving the shortest mating lines to the front.

  • Next we'll consider a decoy followed by a fork of king and queen. One will often find this maneuver employed — with queen as decoy — in forcing the queens off the board when the opponent is apparently reluctant to do so.
  • // Decoy - followed by fork.
    cql() flipcolor {
      // The move sequence.
      line singlecolor
        --> // The king captures the decoy.
            move from k capture (move to _ previous)
        --> // The forking piece is not captured.
            check and 
            piece Fork in [RBNP]
              [qk] attackedby Fork > 1 and not move capture Fork
                   and sort min "Fork type" type Fork
    }
    
  • The pawn fork is rare but it's fun to see the lowly piece get in on the action. Sorting by the type of the forking piece will move those matching games up front.

  • Desperado
  • TODO This example is due a major revision as a multi-pass query with multiPV engine analysis of tagged positions. The difficulty in characterizing a capture by a hanging piece as an act of desperation (versus just a run-of-the-mill capture) is in not knowing what the alternatives are. The point of the engine analysis — expanding the game tree with alternate lines of play — is to help determine whether the desperado candidate is indeed a lost piece no matter what, and that the main line move (capture before being captured) is evaluated as being the best (least worst) move at the tagged position.

    The term means different things to different people. For some it descibes a generally desperate circumstance on the board and for others it is in reference to a specific piece that is in a desperate situation. We'll address a couple of such scenarios in the following queries.

    The first query matches positions in which both sides have one or more pieces en prise and a sequence of captures ensues in which the desperate side either equalizes or [presumably] minimizes the material loss. In order to enforce the sense of "desperation", the side initiating the flury of captures must have at least two more pieces en prise than the opposition.

  • // Desperado - en prise.
    // The wtm filter is redundant but significantly improves search time.
    cql() flipcolor { wtm
      // Return the set of squares occupied by pieces en prise.
      function EnPrise() {
        // Iterate over the en prise candidates.
        piece enPrise in [QRBN]
          // Iterate over the attacking pieces.
          piece Attacker in [qrbnp]
            enPrise attackedby Attacker and
            // Either the value of the attacker is less than the attacked
            // or the attacked is undefended directly or indirectly (hanging).
            (power Attacker < power enPrise  or
              (not (A attacks enPrise or xray(A Attacker enPrise))))
      }
    
      PowerDiff = power A - power a
      // Return the last position in the line of play.
      { line nestban lastposition
          --> {not (check or pin) and abs PowerDiff < 2
               wEnPrise =? EnPrise()
               bEnPrise =? reversecolor EnPrise()
              // The desperate side has more pieces en prise.
               #wEnPrise > #bEnPrise + 1
              // No piece attacks more than a single piece en prise.
               piece all X in [qrbn] { wEnPrise attackedby X <= 1 }
              // The first capture is of a piece that is not en prise.  
               move from wEnPrise capture ~bEnPrise and
                 comment("(diff: " PowerDiff " en prise: " wEnPrise " " bEnPrise ")")}
          --> // The bloodbath — with a lower bound on the extent.
              move capture [Aa]{4 100}
          --> // Tag the end of the flurry.
              comment("(position: " currentposition " diff: " power A - power a ")")
      // The line of play results in a loss or equalization of material.
      } : {PowerDiff >= power A - power a}
    }
    
  • Minor adjustments in matching criteria can produce a matching set of games with significant differences in character. For example, setting a relational threshold on the sums of the values of the pieces en prise could intensify the sense of desperation on the board. Also note that in identifying pieces en prise, we could have included under-defended pieces but at some considerable cost in cpu cycles.


    Moving along, we'll look for positions in which a piece is desperately both en prise and trapped. The following query matches a line of play wherein the trapped piece is rescued in a flurry of captures initiated by a fellow comrade, leaving the trapped piece unscathed and the material balance undiminished.

  • // Desperado - trapped.
    // The wtm filter is redundant but significantly improves search time.
    cql() flipcolor { wtm
      // Return the set of squares occupied by pieces en prise.
      function EnPrise(S) {
        // Iterate over the en prise candidates.
        piece enPrise in S
          // Iterate over the attacking pieces.
          piece Attacker in [qrbnp]
            enPrise attackedby Attacker and
              // Either the value of the attacker is less than the attacked
              // or the attacked is undefended directly or indirectly (hanging).
              (power Attacker < power enPrise  or
                (not (A attacks enPrise or xray(A Attacker enPrise))))
      }
    
      // Return the set of squares occupied by pieces trapped.
      function Trapped() {
        // Iterate over the trapped candidates.
        piece enTrapped in [QRBN] {
          // All flight squares are guarded.
          square all Flight in move to _ from enTrapped legal
            piece Guard in [a] attacks Flight
              power Guard < power enTrapped  or
                not A attacks Flight
          // Iterate over the pieces attacked by the entrapped piece.
          not piece Attacked in [qrbnp] attackedby enTrapped
              // Either the value of the attacked is greater than the trapped
              // or the attacked piece is en prise.
              power Attacked >= power enTrapped or
                reversecolor EnPrise(Attacked)
        }
      }
    
      PowerDiff = power A - power a
      // Return the last position in the line of play.
      { line nestban lastposition
          --> {not (check or pin or move capture A previous)
               piece Piece = Trapped() & EnPrise([QRBN])
              // A friendly makes the first capture.
               move from ~Piece capture (~move to . previous) and
                 not move enpassant and
                 comment("(diff: " PowerDiff " trapped: " Piece ")")}
          --> // The bloodbath — with a lower bound on the extent.
              // The trapped piece does not participate in the bath.
              move from ~Piece capture [Aa]{4 100}
          --> comment("(position: " currentposition " diff: " power A - power a ")")
      // The balance of material is undiminished and the piece survived...
      } : {PowerDiff <= power A - power a and Piece and
             // ... and the piece has been freed (--> btm? --> does not work) .
             if btm
             then line --> btm --> not (Piece & Trapped())
             else line --> not (Piece & Trapped())}
    }
    
  • The query will not match positions in which there is more than one piece trapped (what are the odds?), as the piece assignment will then fail. Also note that the conditional if btm accounts for the fact that the Trapped() function is side-to-move dependent and side-to-move coming out of the bloodbath is unknown.

  • The windmill
  • The king is stuck in a trap of sorts, alternately being placed in check by a piece that is gobbling up material and a cohort that is patiently slapping the king around by discovery. The latter is usually a bishop (though it could be a rook) while the former is commonly a rook or a knight.

    The following query, though imprecise, is remarkably effective and is much faster in the execution of a search than the alternative. We give a lower bound on the ply of the positions evaluated to further save on search time.

  • // Windmill - quick'n'dirty.
    // Imprecise but effective.
    cql() ply > 40
      flipcolor
        sort "Line length"
        line singlecolor
          --> ([RN] attacks k and move from k
            --> [B] attacks k and move from k
                  and move from [RN] capture . previous){2 100}
    
  • The alternative (and more precise) query iterates over the potential participants, substituting piece variables for the catch-all piece designators employed above. We also allow for a windmilling bishop, though they are very rare. The query is far more compute intensive than the less precise version above.
  • // Windmill - nested piece filters.
    // Ensure that there are only two pieces employed in the tactic.
    cql() ply > 40
      flipcolor
        // Iterate over the windmilling pieces.
        piece Mill in [RBN]
          // Iterate over the stationary pieces giving discovery.
          piece Disc in [RB] {
            Disc != Mill
            sort "Line length"
            line singlecolor
              --> (Mill attacks k and move from k
                --> Disc attacks k and move from k
                      and move from Mill capture . previous){2 100}
          }
    

  • Zwischenzug
  • A move interposed during an exchange or a series of exchanges, which introduces an immediate threat that must be answered. The interposed (and unexpected) move is often a check of the hostile king and commonly leads to material gain or positional advantage which would not otherwise obtain. The delayed recapture may occur many moves post-capture.
  • // Zwischenzug.
    cql() flipcolor {
      sort min "Line length"
      line
        --> // A capture that is not by a pawn and is not a recapture.
            {
            Square =? move capture a from ~P
            not move capture Square previous
            PowerDiff = power A - power a
            }
        --> // The recapture is delayed at least one move.
            {
            // The next move is not a recapture.
            not check and move to ~Square
            // Assign the [attacked] piece that is to be recaptured.
            piece Piece = Square attackedby a
            // That piece has fewer guards than attackers.
            #(Square attackedby a) > #(Square attackedby A)
            }
        --> not move from Piece {1 5}
        --> // The piece is eventually recaptured...
            move to Square capture Piece
        --> // ... resulting in material gain.
            PowerDiff > power A - power a and not move capture .
    }
    
  • The query does a decent job picking up on the pattern without going too deeply into the weeds. Note that the query stipulates a material gain for the side playing the zwischenzug, but we could pick up many more positional gains by stipulating only that there is no material loss.

    We place an upper bound on the length of the intermezzo since the longer it is the less forcing the line feels. Sorting the matching games in ascending order puts the most natural lines up front.

Hypermodern patterns

Many have challenged the notion that hypermodernism was a theory at all or that there was anything new about it. According to Edward Winter, Frank Marshall expressed a heartfelt disdain for hypermodernist practice when he described it as the tendency on the part of many of the grand masters to adopt an extremely close style of play, which involves a keen desire on the part of both players to avoid incurring the slightest risk.

At the very least it was a jab at the existing orthodoxy, and was formalised by Nimzowitsch in his widely read My System. Think what one might of the practitioners of the day, the ideas persist in one form or another and are probably worthy of study.

  • The hypermodern center
  • After the first move 1.e4 white's game is in the last throes. — Gyula Breyer

    Departing from the classical view that the center must be occupied by pawns, the hypermodernist school advocates controlling the center with distant pieces. Inviting the opponent to occupy the void, the overextended center pawns can become objects of attack and the center can be gradually broken down. Alekhine's Defence, for example, translates this strategy into practice.

    We look for positions in which the center is controlled from a distance and the opponent has been drawn into it. Furthermore, we isolate those positions in which the side in control has decided the time is right to begin the assault on the center. It should come as no surprse that the vast majority of matching positions derive from the hypermodern openings.

  • // The hypermodern center.
    cql() ply < 40  flipcolor {
      // Tally the number of times a center square is attacked by a piece.
      function CenterCalc() {
        Score = 0
        piece Piece in [QRBN]
          Score += #{d-e4-5 attackedby Piece}
        Score
      }
    
      // Advancing a pawn to challenge the center.
      move to c-f4 from P
      // A lower bound on the opponent's pawns in the center, our pawns have
      // never advanced into the center, and our control of the center is
      // from a distance.
      pc-f4-5 >= 2 and not find <-- Pc-f4 and not [QRBN] & d-e4-5
      // A lower bound on attention to the center and control is established.
      6 < CenterCalc() >= (reversecolor CenterCalc())*2
    }
    
  • One avoids matching the odd middlegame position by limiting evaluation (ply < 40) beyond that point where we are just coming out of the opening. The pattern that interests us is established in opening play.

    The various parameters governing the query could be adjusted to suit one's taste. For example, the center could be expanded to include some or all of the extended center. Or one might have a different notion of what it is that comprises a challenge to the center. The query above is highly subjective and is only one of many possibilities.


  • Overprotection
  • A theme about which Nimzowitsch was quite adamant and which he actually practiced in competitive play. An important element of overprotection — besides control of the square — is that with defenders outnumbering attackers, the defenders should have greater flexibility and/or mobility.

    But center pawns? So who cares about overprotected center pawns anyways, since they're not supposed to be in the center in the first place (control from a distance)? Okay, so we're allowing that the novice has forgotten the one rule but — once overextended in the center — has suddenly remembered about overprotection. Whatever.

    In any event, we should consider the question of exactly what qualifies as a defender or an attacker. Obviously, we include any piece that directly defends or attacks. But it's not at all unusual for a line piece to be "defending" through any number of obstructing pieces, friendly or hostile.

    We'll employ direction filters to establish the lines and then stipulate that certain numbers and types of pieces are not allowed on the line. Probably the one piece that presents the most serious obstruction is the friendly pawn, moreso along orthogonal lines than along diagonals. Hostile pawns along the line might [arguably] represent an impediment, but that seems less likely. Fortunately, the parameters governing what is and is not an obstruction may be easily adjusted.

  • // Overprotected center pawns.
    cql() 40 < ply < 80
    flipcolor {
      // Return the pieces attacking a given square.
      function Countem(Square) {
        [NP] attacks Square | 
        (square Orthos in orthogonal Square & [QR]
           between(Orthos Square) & [P] < 1) |
        (square Diagos in diagonal Square & [QB]
           between(Diagos Square) & [P] < 2)
      }
    
      // Iterate over all the candidate squares.
      square Candidate in Pd-e4-5 {
        Defenders = Countem(Candidate)
        Attackers = reversecolor Countem(Candidate)
        (Defenders >= 4) > Attackers
        comment("Candidate at " Candidate
                " Attackers: " Attackers
                " Defenders: " Defenders)
      } > 1
    }
    
  • We look for positions in which the number of defenders of the candidate square is at least four, an indication that there's a good chance overprotection is intentional and not incidental. And then, if only to whittle down the number of matching games, we isolate positions where the number of overprotected squares is greater than one.

    The query suggests a general framework for detecting overprotection amongst any arbitrary set of candidate squares, occupied or otherwise. One of the more common applications of overprotection is the counter of a pawn break. If we knew anything at all about the game of chess (we don't), we might guess maybe an empty b5 or f5 with a legal push pending.

  • // Overprotected breakout squares.
    cql() 40 < ply < 80
    flipcolor {
      // Return the pieces attacking a given square.
      function Countem(Square) {
        [NP] attacks Square | 
        (square Orthos in orthogonal Square & [QR]
           between(Orthos Square) & [P] < 1) |
        (square Diagos in diagonal Square & [QB]
           between(Diagos Square) & [P] < 2)
      }
    
      // Iterate over all the candidate squares.
      square Candidate in _[b5,f5] {
        Defenders = Countem(Candidate)
        Attackers = reversecolor Countem(Candidate)
        (Defenders >= 4) > Attackers
        move from p to Candidate legal
        comment("Candidate at " Candidate
                " Attackers: " Attackers
                " Defenders: " Defenders)
      } > 1
    }
    
  • Here we're protecting against a break at both b5 and f5. Bishops and queens protect through at most one friendly pawn.

  • A knight outpost
  • An outpost is a square that is on or near the opponent's side of the board, usually in a "hole" created by the pawn structure where there is no pawn that can be advanced to attack and clear the outpost. Particularly in a closed position, a knight on an outpost can seriously cramp the enemy position.

    We'll match positions with a three-sided outpost on the fifth rank. If there is an immediate direct or indirect attack on the post then there must also be two pawns protecting the knight.

  • // A knight outpost.
    cql() flipcolor
      // Iterate over eligible knights.
      square Knight in Na-h5 {
        // The post is three-sided.
        p & (up 1 Knight | horizontal 1 Knight) == 3
        // The post cannot be cleared by an advancing pawn.
        not p & up (horizontal 1 Knight)
        // If the post is attacked then it must be defended by two pawns.
        if a attacks Knight or xray([qb] [rn] Knight)
          then P attacks Knight == 2
        comment("Post at " Knight)
      }
    

  • Undermining
  • A defending piece is captured, leaving one of its friendlies undefended or under-defended and opening the way for a net material gain for the undermining side. The initial capturing piece is commonly recaptured and is often of greater value than the piece captured.
  • // Undermining.
    cql() flipcolor {
      // Return the set of squares occupied by pieces en prise.
      function enPrise(EPC) {
        // Iterate over the en prise candidates.
        square epc in EPC
          // Iterate over the attacking pieces.
          {square Attacker in [QRBN]
            epc attackedby Attacker and
            // Either the value of the attacker is less than the attacked
            // or the attacked is undefended directly or indirectly.
            (power Attacker < power epc  or
              (not (a attacks epc or xray(a Attacker epc))))
          // Or the piece is under-defended.
          } or #(A attacks epc) > #(a attacks epc)
      }
    
      PowerDiff = power A - power a
      {line lastposition quiet
         --> {
             // We have a capture of a piece...
             piece Defender = move capture a
             piece Underminer = move from [QRBN]
             // ... but not an exchange.
             not move from Defender capture . previous
             // The undermining piece is of greater value than the defender.
             power Underminer > power Defender
             // The captured piece was guarding one or more pieces.
             Guardeds =? [qrbn] attackedby Defender
             }
         --> {
             // Is the undermining piece subject to recapture?
             move capture Underminer
             // Are any of the guarded pieces now en prise?
             Undermined =? enPrise(Guardeds) and
                comment("Undermined at " Undermined)
             }
         --> // The capture of an undermined piece.
             move capture Undermined from ~P
      // We have a material gain with no immediate capture to follow.
      }:child:{PowerDiff < power A - power a and not move capture .}
    }
    
  • While the captured defender is not necessarily defended, itself, the pattern tends to be more coherent if the undermining piece has a negative static exchange evaluation and is subject to recapture. Then the material gain is secured with the capture of the undermined piece.

  • Prophylaxis
  • Prophylaxis in chess amounts to interference in an opponent's plan. A prophylactic move is a move that discourages the opponent from carrying out that plan, perhaps for fear of some negative consequence. Of course, in order to interfere in a plan one must first be able to anticipate the plan. A typical prophylactic maneuver would be the advance of the rook pawn near a castled king to counter the possibility of a back rank mate or to prevent pins. This we will refer to as shallow prophylaxis.

    The Nimzowitschian or hypermodern notion of prophylaxis is a deep positional consideration aimed at not just improving one's own position, but at denying the opponent the opportunity of improving theirs. Identifying a deeply prophylactic move is a challenge of a higher order. We'll address that flavor of counter-plan in a separate companion document.


    We start out with a search that anticipates a threatened knight fork of king and/or queen(s). The query iterates over each knight in the position and tests the body of the square filter for each square 1) that is guarded by the opposing piece whose move resulted in the current position, and 2) to which the knight might make a legal move.

  • // Prophylaxis - anticipated knight fork.
    cql()  flipcolor { btm
      // Iterate over every hostile knight on the board.
      piece Knight in n
        // For each square at the intersection of the knights field
        // and the field of the piece making the last move.
        square aiSquare in
          move to (. attackedby (move to . previous)) from Knight legal {
          // Eliminate positions in which the square was already defended
          // or was occupied by the piece now defending the square.
          piece Defender = A attacks aiSquare
          not parent:{aiSquare attackedby Defender or aiSquare & Defender}
          // The square has the king and/or queen(s) in the attack field
          // of a hypothetical knight.
          rotate90 {horizontal 1 up 2 aiSquare} & [KQ] >= 2
          // More defenders than attackers.
          #(a attacks aiSquare) >= #(A attacks aiSquare)
          // No immediate capture of the prophylactic piece.
          not move capture Defender
          // The knight does not move to the square.
          not move from Knight to aiSquare and comment("aiSquare: " aiSquare)
        }
    }
    
  • For each of the squares at the attack intersection, we ask a number of questions:
    1. Is the piece that made the last move the only piece defending the square (the piece assignment will fail if the attack filter yields a set with cardinality greater than one)? Did that piece already defend the square or occupy the square in the parent position? If so, the move hardly qualifies.
    2. If the knight were to move to occupy the square, would it fork a king and/or queen(s) of the opposition? Note that we do not actually have an existing position from which to answer that question with an attack filter, so we must improvise by counting the squares at the intersection of the piece designator (king and queen) and the set of squares representing a knight's legal moves at the attack intersection.
    3. Are there enough defenders of the ai square to represent this fork as a serious threat? The threatened fork is not really a threat, of course, unless the square on which the attack might take place has more defenders than attackers. We'll just count the number of defenders versus attackers without taking into account the respective cumulative material value.
    4. Is the prophylactic candidate immediately captured? If so, that would seem to be disqualifying.
    5. Has the anticipated fork been prevented (or at least discouraged) by the presumed prophylactic move?


    Anticipating a double attack by a line piece raises a set of considerations that are a bit different from those of a knight fork. For example, in evaluating whether a defensive move is made in anticipation of a knight fork, we don't need to worry about one of the targets of a fork being mistaken for the piece making the prophylactic move. And given the geometry of a knight's field, we also need not worry about one of the potential targets of the fork hitting back.

    We'll handle both rooks and bishops with the single query below. A couple of the query's parameters are conditioned on the type of the line piece, but the bulk of the query is independent of that consideration. Considerations that are unique to the line pieces (versus the knight fork above) are handled in the body of the nested piece filters.

  • // Prophylaxis - anticipated double attack by line pieces.
    cql()  flipcolor { btm
      // Iterate over every hostile line piece on the board.
      piece Threat in [rb]
        // For each square at the intersection of the line piece's
        // field and the field of the piece making the last move.
        square aiSquare in
          move to (. attackedby (move to . previous)) from Threat legal {
          // The attack intersect square has the targets in the line of
          // attack of a hypothetical line piece.
          if type Threat == 3
            then Targets = [KQR]  // for bishops
            else Targets = [KQ]   // for rooks
          Inner = Targets
          // Pair off the targets.
          piece P1 in Targets {
            Inner = Inner & ~P1  // "Shift" the inner set.1
            piece P2 in Inner {
              if type Threat == 3
                then ray diagonal (P1 P2) and between(P1 P2) & aiSquare
                else ray orthogonal (P1 P2) and between(P1 P2) & aiSquare
              // Eliminate "prophylactic" move by a target.
              aiSquare attackedby move to . from ~(P1|P2) previous
              // Eliminate positions in which the square was already defended
              // or was occupied by the piece now defending the square.
              piece Guard = (A&~(P1|P2)) attacks aiSquare
              not parent:{aiSquare attackedby Guard or aiSquare & Guard}
            }
          }
          // More defenders than attackers.
          #(a attacks aiSquare) >= #(A attacks aiSquare)
          // No immediate capture of the prophylactic piece.
          not move capture move to . previous
          // The threatened move is prevented.
          not move from Threat to aiSquare
          if type Threat == 3
            then comment("aiSquare " aiSquare " threat by bishop")
            else comment("aiSquare " aiSquare " threat by rook")
        }
    }
    

  • Finally, we'll look for a prophylactic pawn move which counters an imminent threat to the king on the back rank. The threat is anticipated to be by a hostile rook or queen against a king which has no escape.

  • // Prophylaxis - opening an avenue of escape.
    cql() flipcolor {
      line quiet
        --> // The king has no escape and an adjacent pawn moves out.
            not ((_ attackedby K) & a-h2) and ((move from P) attackedby K)
        --> // Iterate over the potential threats.
            piece Threat in [qr] {
              // For each legal move to the king's rank.
              Threats =?
              square Square in move to a-h1 from Threat legal {
                // The square is undefended.
                not ((A attacks Square) or xray(A Threat Square))
                // There are no interfering pieces between the
                // square and the king.
                ray horizontal (Square K)
              }
              comment("Threat square(s) at " Threats " by " Threat)
            }
    }
    

Miscellaneous

Most of the examples of this section are what we might call ill-defined. A few of the patterns enter into a rather vague realm, e.g., what exactly does it mean for a position to be cramped and how does one go about evaluating such a position? Or at what point does king harassment transition into a king hunt? Judgement call. We know it when we see it.

  • Blind swine
  • Two rooks on the seventh rank are deemed to be pigs due to their tendency to gobble up all the material on that rank. Chess lore has it that those swine must be blind if they are not able to find mate despite their considerable powers arrayed against the opposing back rank king.

    That seems like a bit of a bad rap since without the aid of additional pieces a mate is rather difficult unless the mated side assists in the mate by placing an obstructing rook on just the right square (see the swine's mate pattern above). Nonetheless, the gobbling up of material by the pigs is of some interest, especially if that material gain makes a difference in the outcome of the game.

  • // Blind swine.
    cql() initial flipcolor { result 1-0
      sort "Gobble count"
      // Returns the number of matching positions found.
      find all { ka-h8  Ra-h7 == 2
        move from Ra-h7 capture [a]a-h7
          and comment("CQL") // signals current ply setting for Scid
      } > 3
    }
    
  • We set a lower bound on the number of captures made by the swine on the seventh rank. We also stipulate that the side with the marauding swine wins the game, possibly as a consequence of all the gobbling.

  • The Greek gift
  • The gift is a bishop sacrifice on h7. The sac can be considered successful from white's perspective if it leads to mate or to a material grab as a consequence of the threat of mate.

    With this query we're not particularly interested in dictating a specific pattern so much as we are in learning what the pattern is. What are the conditions on the board that lend themselves to a positive result after such a sacrifice? And what does the kill pattern look like and the technique driving the pattern?

    We can set the stage prior to the sac and also the conditions on the board at the terminal position, directing our focus in a particular direction. The terminal configuration in the kill zone will serve as a significant indicator of the technique that got us there.

  • // Greek gift.
    cql() result 1-0  ply < 40
    pg7  killzone = f-h6-8
    line quiet
      --> move from B capture ph7
      --> move from k capture h7
      --> .{2 12}
      --> terminal and
          (Ne-h5-8 == 2 or (R attacks killzone == 2 and not Q attacks killzone))
    
  • Here we only stipulate that there is a black pawn at g7 prior to the sac, which might give the king something to hide behind. The configuration that we give for the terminal position has either 1) two knights heavily involved in the attack, or 2) two rooks participating and with the queen out of play in the end.

    In addition to the configuration of the white pieces in the terminal position, one might consider stipulating, for example, that the king got flushed way out of his corner before meeting his end, or perhaps that the black queen was in the thick of it and threw her king under the bus in her haste to save herself. The possibilities are endless.


  • The king hunt
  • The king hunt is typically characterized by two or three defining elements:
    1. The side on the hunt will often sacrifice material to flush the opposing king out into the open.
    2. The king is subject to a sustained attack, driving the piece far away from its initial position.
    3. The long walk typically ends in mate or resignation on the opposite side of the board.

    The following query stipulates all three of those elements looking backward from the terminal position. We sort the matching games first by the length of the walk and then by the number of decoys.

  • // The king hunt.
    cql() terminal flipcolor { result 1-0  ka-h1-3 
      // Board congestion has never dropped below some threshold and
      // the king has never felt secure enough to go on a casual walk.
      not find <-- power [Aa] < 30 or not Q
      // Consecutive checks with a lower bound.
      sort "Checks"
      line quiet singlecolor
        <-- check {4 100}
        <-- check and posBC = currentposition and comment("CQL begin checks")
      // The king has been "drawn out" by decoys.
      sort "Decoys"
      find <-- 2 100 {
        // The "decoy" is not too far distant from the checks.
        currentposition >= posBC or distance(currentposition posBC) < 24
        move from k capture (move to . previous)
          and comment("Decoy")
      }
    }
    
  • When we go looking for decoys, we don't want to count any that are unrelated to the long king walk. We initially thought that something like 6 to 8 ply prior to the beginning of the series of checks would be a reasonable range, but that guess was way off the mark. The most interesting legitimate decoys are drawing out the king long before the hunt begins in earnest.

  • Railroaded
  • A technique with a very cool visual effect that alternates between a kill box pattern and a triangle pattern. The queen and rook work in concert up the track in mutually supporting maneuvers. The opposing king is typically driven in a forced walk to the edge of the board or up against an obstacle.

  • // Railroaded.
    cql() flipcolor rotate90 { result 1-0
      sort "Line length"
      line  // Walk the king up between the tracks.
        --> (check and move from k to vertical between(Q R)
          --> // Alternate between rook and queen moves.
              move from R to diagonal 2 Q    or
              move from Q to horizontal 2 R) {3 100}
    }
    
  • Note that we do not distinguish between what might be multiple rooks or queens on the board, which would normally undermine one's intentions. The maneuver, in this case, apparently is so exacting over multiple repetitions that other pieces on the board are relegated to irrelevance.

    To verify this, one can reduce the lower bound on the repetition qualifier and, indeed, one will find the occasional match where a second rook interferes with the pattern on account of the between filter. We could correct this deficiency but at the cost of some added compute time, and with little to gain.


  • Pawn chains
  • Our first shot at this pattern some years back was pathetic. Overly complex. Underly functional. No imagination. We decided to get serious about the task at hand and go after the arbitrarily complex pawn chain. Multiple bases. Multiple peaks. Bifurcations left and right. Game on.

    Always looking to steal somebody else's ideas, we first checked out the Chess Programming Wiki for algorithmic help. No luck there. We then tried the Stockfish Evaluation Guide, figuring that the fish knows a thing or two about chains. Still no help. Going straight to the source, we wandered around in the Stockfish tree for a while wondering where is that pawn chain bitboard? Good luck with that. In a last act of desperation, we even googled our way through some pretty heady graph theory stuff looking for ideas related to connectedness. Just shoot us, please.

    Finally, we resorted to that old reliable standby, brute force. Turns out, the brute has its place in the larger scheme of things. Simple. No frills. Elegance be damned.

  • // Pawn chains.
    cql()
    function BasePawns(S) { S & ~(S attackedby S) }
    function PeakPawns(S) { S & ~(S attacks S) }
    
    Chain = []
    square BP in BasePawns(P) {
      not BP & Chain
      Chain = BP  lastChain = []
      while (Chain != lastChain) {
        lastChain = Chain
        Chain |= P attackedby Chain | P attacks Chain
      }
      Chain > 5 and BasePawns(Chain) > 1 and PeakPawns(Chain) > 2
      comment("Chain at " Chain)
    }
    
  • No shame. No regrets. It is what it is.

  • Space and mobility
  • TODO This overhaul will have to wait until we have the better part of a bottle of Tequila in us. (What were we thinking?!)

    This example is due a major revision as a multi-pass query with multiPV engine analysis of tagged positions. The difficulty in characterizing a capture by a hanging piece as an act of desperation (versus just a run-of-the-mill capture) is in not knowing what the alternatives are. The point of the engine analysis — expanding the game tree with alternate lines of play — is to help determine whether the desperado candidate is indeed a lost piece no matter what, and that the main line move (capture before being captured) is evaluated as being the best (least worst) move at the tagged position. The side controlling more squares than the other is said to have a spatial advantage. That advantage should translate to a superior mobility of forces and that superior mobility can often determine the outcome of a game.

    Evaluation of the relative mobility of either side's pieces is a subjective undertaking, but we might look to the chess programming domain for some help with a number of related definitions. Drawing on the Chess Programming Wiki (CPW) as a resource:

    • Space: a measure of the amount of area of the chess board controlled by either side. Closely related to square control, in particular center control.
    • Square Control: refers to which and how many pieces attack or defend a particular square, independently from its occupancy state. Squares diagonally controlled by a pawn are usually taboo for opponent pieces. Square control is considered in evaluating piece mobility.
    • Center Control: the control or occupation of the center and extended center of the board, which gains space and allows pieces fast access to most of the board areas.
    • Mobility: a measure of the number of choices (legal or pseudo-legal moves) a player has in a given position. The more choices one has at one's disposal, the stronger one's position. Correlations have been established between a side's mobility — with the position in material balance — and the outcome of the game.

    We're interested in matching positions in which one side has a marked (even extreme) advantage in space and mobility over the opponent coming out of the opening phase of the game. In fact, by stipulating a ply range for the query one can narrow the phase very specifically. The ultimate end is toward understanding which opening lines lead consistently to a lopsided advantage in space and mobility.


    We'll begin by looking at center control. The following query utilizes a simple calculation which satisfies the definition given above; i.e., that the center is controlled by pieces either defending or occupying that space. We somewhat arbitrarily define control as one side having quadrupled the attention to the center as the opponent.

  • // Center control.
    cql() 
    // Calculate and return a score for center control.
    function CenterCalc() {
      Score = 0  Center = d-e4-5
      // For each piece, tally the number of center squares attacked by
      // that piece and add a bonus if the center is occupied by the piece.
      piece Piece in [QRBNP]
        Score += #{Center attackedby Piece} + #{Center & Piece} * 2
      Score
    }
    
    wCenterControl = CenterCalc()
    bCenterControl = reversecolor CenterCalc()
    
    // One side must exceed a lower bound and quadruple the score of the other side.
    function Qualify(SideA SideB) { 12 < SideA >= SideB*4 }
    if Qualify(wCenterControl bCenterControl)
      then comment("White controls:  " wCenterControl "  " bCenterControl)
      else
        if Qualify(bCenterControl wCenterControl)
          then comment("Black controls:  " bCenterControl "  " wCenterControl)
          else false
    

  • Square control is less about control of an area as of a specific square, and is less concerned about occupancy than about whether or not the square may be occupied. For the following query we'll expand the area of interest to include the extended center, and exclude from consideration those squares that are attacked by a hostile pawn.

    When we were looking at center control, we calculated a score based on the forces arrayed against an area of the board. Here we're interested in which side is in control of the greater number of squares within an area, a subtle distinction. The function calculating the score increments the score for each white piece attacking a given square and decrements the score for each black piece attacking the square. Either or both sides may be denied control if an opposing pawn is defending the square.

    Note that we are only interested in positions with a minimum amount of congestion (as measured by the piece count), since the relative scores become less interesting as the position opens up and both sides have virtually unlimited mobility.

  • // Square control.
    cql()  [Aa] > 24
    // Calculate and return the score for a given square, excluding xray's.
    // Every white piece attacking the square adds to the score, every black piece
    // subtracts from the score. An attack on a square by an opposing pawn nullifies
    // the score.
    function SquareCalc(Sq) {
      Score = 0
      if not (Sq attackedby p)
        then Score += #{[QRBNP] attacks Sq}
      if not (Sq attackedby P)
        then Score -= #{[qrbnp] attacks Sq}
      Score
    }
    
    eCenter = c-f3-6  wSquareControl = 0  bSquareControl = 0
    // Initialize the set variables to the empty set.
    wSquares = makesquare(0 0) bSquares = makesquare(0 0)
    // Tally the number of squares controlled by each side
    // and accumulate their respective sets of controlled squares.
    square Square in eCenter {
      Tally = SquareCalc(Square)
      Tally != 0
      if Tally > 0
        then wSquareControl += 1 and wSquares = wSquares | Square
        else bSquareControl += 1 and bSquares = bSquares | Square
    }
    
    // One side must exceed a lower bound and quadruple the score of the other side.
    function Qualify(SideA SideB) { 8 < SideA >= SideB*4 }
    if Qualify(wSquareControl bSquareControl)
      then comment("White controls:  " wSquareControl  " " bSquareControl) and
           comment(wSquares " " bSquares)
      else
        if Qualify(bSquareControl wSquareControl)
          then comment("Black controls:  " bSquareControl "  " wSquareControl) and
               comment(bSquares " " wSquares)
          else false
    

  • Most chess engines implement algorithms evaluating the spatial qualities of a position. According to CPW, Stockfish defines a space area bonus by the number of safe squares for minor pieces on the central four files on ranks 2 to 4, counting twice if on a rearspan of an own pawn. The space area bonus is multiplied by a weight, determined by the number of own pieces minus the number of open files. Two points: 1) a bonus for the number of safe squares (mobility), and 2) minus the number of open files (the more closed a position the more important a spatial advantage). We should borrow from that heuristic and run with it.

    The Stockfish Evaluation Guide (SEG) defines a safe square as having no opposing pawn attacking the square and having no friendly pawn occupying the square. Safe squares one, two or three squares behind a friendly pawn are counted twice. Additionally, the SEG indicates that the space bonus is to be multiplied by the number of our pieces minus two times the number of open files. Looking at the evaluation code, the weighting is heavily in favor of congested, closed positions. If more than a quarter of the non-pawn material is off the board, the space evaluation is completely ignored. That is, whatever spatial advantage one side might have over the other is far more relevant when space is generally scarce. Furthermore, the space under consideration is especially important in the latter phase of the opening.

    The following query implements the Stockfish notion of space evaluation. In calculating the bonus, the square filter yields the set of squares for which there is a bonus (i.e., for which the body of the filter is true). We tag each matching position with a comment that lists that square set. Note that right up front we limit consideration of positions to those with a lower bound on material (roughly 75%) and on pawn count (roughly translating to the potential for open files). This is in keeping with the SEG.

  • // Space evaluation.
    // A lower bound on congestion and pawns on the board.
    cql() power [QRBNqrbn] > 45  [Pp] >= 12
    
    // Calculate and return the space area bonus.
    // Count the number of safe squares within a critical
    // space that might be occupied by a piece.  Add a bonus
    // if that square is in the rear-span of a friendly pawn.
    function BonusCalc() {
      Bonus = 0
      Squares =
        // Iterate over each square in the bonus area.
        square Square in c-f2-4
          // Neither occupied by a friendly pawn nor
          // attacked by a hostile pawn.
          if not (Square & P or Square attackedby p)
            then {
              Bonus += 1
              // Double the bonus if behind a friendly pawn.
              if up 1 3 Square & P then Bonus += 1
            } else false
      comment("Bonus squares: " Squares)
      Bonus
    }
    
    // Calculate and return the space evaluation.
    // Weighted positively by the number of pieces on board
    // for a side and negatively for open files. Note that the
    // evaluation is non-linear by weight. That is, the more
    // congested and closed a position, the more relevant is
    // the bonus calculated above.
    function SpaceEval() {
      Weight = #A
      // Iterate over every file, docking the weight for open files.
      square Square in a-h1
        if not ((up Square) & [Pp]) then  Weight -= 2
      Score = BonusCalc()
      comment("Bonus " Score " with Weight " Weight)
      // Return the evaluation.
      Score * Weight * Weight
    }
    
    comment("WHITE")
    wSpaceEval = SpaceEval()
    comment("BLACK")
    bSpaceEval = reversecolor SpaceEval()
    
    // One side must quadruple the score of the other side.
    function Qualify(SideA SideB) { SideA >= SideB*4 }
    if Qualify(wSpaceEval bSpaceEval)
      then comment("||| White advantage:  " wSpaceEval  " " bSpaceEval)
      else
        if Qualify(bSpaceEval wSpaceEval)
          then comment("||| Black advantage:  " bSpaceEval "  " wSpaceEval)
          else false
    
  • There is some magic happening with this query that is worthy of explanation. There are two invocations of the SpaceEval() function, one for the white side and one for the black. That function, in turn, invokes the BonusCalc() function. The implementation of both of those functions is from the white perspective. When we invoke that nest of functions for space evaluation from the black perspective, we apply the reversecolor transform to the call.

    This all just happens to work because the CQL function facility behaves much like macro expansion. Therefore, the application of a transform will cascade through an arbitrary depth of nested function calls. Expanding the "macros" in cascading fashion effectively transforms the entire series of code blocks represented by the function nest. Though the authors of the language casually observe that the facility merely behaves as expected, the genius behind this semantic is difficult to overstate.


  • Our evaluation of mobility also will mirror that of the SEG, which is a bit more involved than simply summing up the number of legal or pseudo-legal moves. We'll not be evaluating so-called safe mobility — counting only those squares where a piece can move without being en prise. Nor is occupancy by a hostile piece a consideration. We yield to the judgement of the SEG in those determinations which are likely the consequence of long experience in the discipline.

    We'll also ignore the middle game vs endgame weighting, since we have no interest in measuring wide-open endgame mobility and our position-matching never reaches that phase of the game. As is the case in evaluation of space, differences in mobility between the two sides are most relevant when mobility is at a premium.

    Quoting from the SEG, the area under consideration for mobility excludes squares protected by enemy pawns, or occupied by our blocked pawns or king. Pawns blocked or on ranks 2 and 3 will be excluded from the mobility area. And mobility is measured by the number of attacked squares in the Mobility area. For a queen, the squares defended by an opponent's knight, bishop or rook are ignored. For minor pieces, squares occupied by our queen are ignored.

    The code for the SEG mobility evaluation also gives some special consideration for line pieces in that, if their line is behind a queen (or queen or rook), their attack will xray through the queen or rook to the square under consideration.

  • // Mobility evaluation.
    // Well into the opening and a lower bound on congestion
    // with rough material balance.
    cql()  ply > 16  power [QRBN] == power [qrbn] > 24
    // Decide whether a given square is eligible for evaluation.
    // Squares occupied by a friendly king or queen are not eligible.
    // Squares attacked by an opposing pawn are not eligible.
    // Squares occupied by a friendly pawn that is blocked or that
    // occupies the second or third ranks is not eligible.
    function MobilityArea(Sq) {
      // Occupied by a friendly king or queen or
      // attacked by a hostile pawn...
      if Sq & [KQ]  or  p attacks Sq  or
        // ... or occupied by a friendly pawn either
        // back of the fourth rank or blocked.
        (Sq & P and (Sq & a-h2-3 or not up 1 Sq & _))
        then false
        else true
    }
    
    // Calculate a mobility score that is the cumulation of the mobility of
    // each non-pawn piece for a side.  A piece's mobility is determined by the
    // number of eligible squares that it attacks, with a few special per-piece
    // considerations. Bishop mobility includes an xray through a friendly queen.
    // Rook mobility includes an xray through a friendly queen or rook. And the
    // queen's mobility excludes those eligible squares that are attacked by
    // a minor hostile piece.
    function Mobility() {
      Score = 0
      // Iterate over every piece.
      piece Piece in [QRBN] {
      Squares = 
        // Iterate over every square attacked by or xray'd by the piece.
        square Square in {. attackedby Piece | xray(Piece [QR] .)}
          if MobilityArea(Square)
            then {
              // No score for a queen if the square is attacked by...
              not (type Piece == 5 and Square attackedby [rbn]) // queen
              // ... else score if the square is attacked by the piece.
              Score += #{Square attackedby Piece}
              // Bishops score an xray through a queen.
              if type Piece == 3   // bishop
                then Score += #{xray (Piece Q Square)}
              // Rooks score an xray through a queen or rook.
              if type Piece == 4   // rook
                then Score += #{xray (Piece [QR] Square)}
            } else false
        comment("[" Piece " mobile on " Squares "]")
      }
      // Return the score.
      Score
    }
    
    comment("WHITE")
    wMobility = Mobility()
    comment("BLACK")
    bMobility = reversecolor Mobility()
    
    // One side must quadruple the score of the other side.
    function Qualify(SideA SideB) { SideA >= SideB*4 }
    if Qualify(wMobility bMobility)
      then comment("||| White advantage:  " wMobility  " " bMobility)
      else
        if Qualify(bMobility wMobility)
          then comment("||| Black advantage:  " bMobility "  " wMobility)
          else false
    

  • Finally, we'll give a weighted composite of all of our measures of space and mobility. Those measures are combined to conditionally match a position if the composite measure gives a fairly extreme advantage in space and mobility to one side or the other. Each of the evaluation sections of the query is more or less a copy-n-paste from the queries above.
  • // Space and mobility - composite evaluation.
    cql(quiet)
    power [QRBNqrbn] > 45  [Pp] >= 12
    Center = d-e4-5  eCenter = c-f3-6
    
    // Skip over positions that are a capture followed by recapture. Such positions
    // are prone to score oscillation and rarely represent an interesting match.
    // (The move from the current position would be the recapturing move.)
    not line <-- Recap = move capture .
             <-- move capture Recap
    
    // Evaluate center control.
    
    // Calculate and return a score for center control.
    function CenterCalc() {
      Score = 0
      // For each piece, tally the number of center squares attacked by
      // that piece and add a bonus if the center is occupied by the piece.
      piece Piece in [QRBNP]
        Score += #{Center attackedby Piece} + #{Center & Piece} * 2
      Score
    }
    
    wCenterControl = CenterCalc()
    bCenterControl = reversecolor CenterCalc()
    
    // Evaluate square control.
    
    function SquareCalc(Sq) {
      Score = 0
      if not (Sq attackedby p)
        then Score += #{[QRBNP] attacks Sq}
      if not (Sq attackedby P)
        then Score -= #{[qrbnp] attacks Sq}
      Score
    }
    
    wSquareControl = 0  bSquareControl = 0
    // Tally the number of squares controlled by each side.
    square Square in eCenter {
      Tally = SquareCalc(Square)
      Tally != 0
      if Tally > 0
        then wSquareControl += 1
        else bSquareControl += 1
    }
    
    // Evaluate space (SEG).
    
    // Evaluation is weighted in favor of a congested and somewhat closed positon.
    // (See also the constraints imposed at the top of this query.)
    // This evaluation of space can be considered a measure of mobility on the
    // front ranks which is especially important in the early stages of the game.
    
    // Calculate and return the space area bonus.
    // Count the number of safe squares within a critical
    // space that might be occupied by a piece.  Add a bonus
    // if that square is in the rear-span of a friendly pawn.
    function BonusCalc() {
      Bonus = 0
      // Iterate over each square in the bonus area.
      square Square in c-f2-4
        // Neither occupied by a friendly pawn nor
        // attacked by a hostile pawn.
        if not (Square & P or Square attackedby p)
          then {
            Bonus += 1
            // Double the bonus if behind a friendly pawn.
            if up 1 3 Square & P then Bonus += 1
          } else false
      Bonus
    }
    
    // Calculate and return the space evaluation.
    // Weighted positively by the number of pieces on board
    // for a side and negatively for open files. Note that the
    // evaluation is non-linear by weight. That is, the more
    // congested and closed a position, the more relevant is
    // the bonus calculated above.
    function SpaceEval() {
      Weight = #A
      // Iterate over every file, docking the weight for open files.
      square Square in a-h1
        if not ((up Square) & [Pp]) then  Weight -= 2
      Score = BonusCalc()
      // Return the evaluation.
      Score * Weight * Weight
    }
    
    wSpaceEval = SpaceEval()
    bSpaceEval = reversecolor SpaceEval()
    
    // Evaluate mobility (SEG).
    
    // Decide whether a given square is eligible for evaluation.
    // Squares occupied by a friendly king or queen are not eligible.
    // Squares attacked by an opposing pawn are not eligible.
    // Squares occupied by a friendly pawn that is blocked or that
    // occupies the second or third ranks is not eligible.
    function MobilityArea(Sq) {
      // Occupied by a friendly king or queen or
      // attacked by a hostile pawn...
      if Sq & [KQ]  or  p attacks Sq  or
        // ... or occupied by a friendly pawn either
        // back of the fourth rank or blocked.
        (Sq & P and (Sq & a-h2-3 or not up 1 Sq & _))
        then false
        else true
    }
    
    // Calculate a mobility score that is the cumulation of the mobility of
    // each non-pawn piece for a side.  A piece's mobility is determined by the
    // number of eligible squares that it attacks, with a few special per-piece
    // considerations. Bishop mobility includes an xray through a friendly queen.
    // Rook mobility includes an xray through a friendly queen or rook. And the
    // queen's mobility excludes those eligible squares that are attacked by
    // a minor hostile piece.
    function Mobility() {
      Score = 0
      // Iterate over every piece.
      piece Piece in [QRBN]
        // Iterate over every square attacked by or xray'd by the piece.
        square Square in {. attackedby Piece | xray(Piece [QR] .)}
          if MobilityArea(Square)
            then {
              // No score for a queen if the square is attacked by...
              not (type Piece == 5 and Square attackedby [rbn]) // queen
              // ... else score if the square is attacked by the piece.
              Score += #{Square attackedby Piece}
              // Bishops score an xray through a queen.
              if type Piece == 3   // bishop
                then Score += #{xray (Piece Q Square)}
              // Rooks score an xray through a queen or rook.
              if type Piece == 4   // rook
                then Score += #{xray (Piece [QR] Square)}
            } else false
      // Return the score.
      Score
    }
    
    wMobility = Mobility()
    bMobility = reversecolor Mobility()
    
    // Match the side with an overwhelming advantage.
    // The lower bounds and relative scores are somewhat arbitrary,
    // but the positions matched speak for themselves... the side
    // flagged with an advantage is immediately apparent even on
    // casual inspection.
    
    function Qualify(CenterControlA CenterControlB
                     SquareControlA SquareControlB
                     SpaceEvalA     SpaceEvalB
                     MobilityA      MobilityB) {
      12 < CenterControlA > CenterControlB*2  and
      8 < SquareControlA > SquareControlB*2  and
      2000 < SpaceEvalA > SpaceEvalB*3/2  and
      MobilityA > MobilityB*3/2
    }
    
    if Qualify(wCenterControl bCenterControl
               wSquareControl bSquareControl
               wSpaceEval     bSpaceEval
               wMobility      bMobility)
      then comment("CQL White advantage::  ")
      else
        if Qualify(bCenterControl wCenterControl
                   bSquareControl wSquareControl
                   bSpaceEval     wSpaceEval
                   bMobility      wMobility)
          then comment("CQL Black advantage::  ")
          else false
    
    // For matching positions, dump the scores and evaluations.
    
    comment("center control: " wCenterControl "  " bCenterControl)
    comment("square control: " wSquareControl "  " bSquareControl)
    comment("space: " wSpaceEval "  " bSpaceEval)
    comment("mobility: " wMobility "  " bMobility)
    

  • While this is obviously a compute-intensive query, there are ways of mitigating the downside of executing the search on a very large database.

    First trim the base down to a reasonable size with the following snippet. The resulting base will contain approximately 10% of the larger base, but will contain 100% of the games that would have matched the compute-intensive monster above. Queries run against the smaller base will obviously execute much more quickly.

  • cql()
    power [QRBNqrbn] > 45  [Pp] >= 12
    Center = d-e4-5
    
    function CenterCalc() {
      Score = 0
      // For each piece, tally the number of center squares attacked by
      // that piece and add a bonus if the center is occupied by the piece.
      piece Piece in [QRBNP]
        Score += #{Center attackedby Piece} + #{Center & Piece} * 2
      Score
    }
    
    wCenterControl = CenterCalc()
    bCenterControl = reversecolor CenterCalc()
    
    12 < wCenterControl > bCenterControl*2  or
    12 < bCenterControl > wCenterControl*2
    

Extreme sport

On occasion we'll receive an inquiry from a dedicated skeptic which boils down to something like, "Yeah, but can CQL do this!?" The question is invariably framed as something of a challenge and the motivation seems to be that no other application's search facility can even come close to meeting that challenge. In our experience there's not much that CQL can't do, even if it's only as a very close approximation. But the doubters just keep on coming.

Most of these inquiries fall into the "Why would any sane person care?" category. There's a lot of that going around. But contrary to the challenger's apparent expectations, the proposed task tends to be surprisingly easy for CQL to handle, and there is that rare exception which is actually interesting and possibly even useful. We'll set this section aside to answer a few of those inquiries as they filter into our inbox.

  • Move order mania
  • A certain project manager/developer from the upstream sent us this little gem: Taking into account every position in a database, identify that position having the greatest number of unique move orders arriving at the position and list the move orders.

    On its face, one might question the utility of such a query when executed on some arbitrary database. But with a bit of pre-filtering or with a few supplemental search criteria, one could imagine probing some fairly serious questions. At the very least, one might find the numbers of move orders discovered for the maximal position(s) to be just a little bit shocking.

    The challenger seemed pretty confident that he had stumped us, and had he thrown the question at us prior to the release of CQL v6.1 he very well might have. It took us a couple of tries to come up with a query that didn't require an overnight wait for the results (very cool but very dumb), and even the latter round bumped up against a segfault in the GNU standard library. The upside is that it ran through a 1M game database pretty darned fast.

    Per the usual, one Lewis Stiller — having been falsely accused of being responsible for the core dump — came to our rescue with a solution that not only sidestepped the segfault but was a much smarter approach to the problem. We'll not embarrass ourselves by putting our original works on display, only to mention that we have incorporated much (most) of Stiller's recommendation/example into our radically revised query.

  • // Move order mania.  (CQLv6.2)
    cql()
    
    // Query parameters may be assigned from the command line.
    if isunbound maxGameNumber then maxGameNumber = 14750
    if isunbound minPly then minPly = 8
    if isunbound maxPly then maxPly = 24
    
    dictionary PathsSeen // true if a moveorder was seen
    dictionary Paths     // fen -> string of all moveorders
    dictionary numPaths  // fen -> number of unique moveorders
    
    if initial {
      persistent MaxUniques += 0
      if fen == "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
        then StandardStart = 1 else StandardStart = 0
      ThisPath=""
    }
    
    not initial
    gamenumber ≤ maxGameNumber
    StandardStart == 1
    
    // How much game state do we include with the board layout?
    //CurrentFen = fen
    //CurrentFen = fen ~~ "^([^ ]+)"
    CurrentFen = fen ~~ "^[^ ]+ [wb] [^ ]+"
    
    ThisPath += if wtm then " " else str(" " movenumber ".")
    ThisPath += str(from to)
    
    minPly ≤ ply ≤ maxPly
    
    // If we have a new path, register the move order and add it to the paths
    // that have been seen for the current position.
    if not PathsSeen[ThisPath] {
      PathsSeen[ThisPath] = ""
      if not numPaths[CurrentFen] then numPaths[CurrentFen] = 0
      if not Paths[CurrentFen] then Paths[CurrentFen] = ""
      numPaths[CurrentFen] = numPaths[CurrentFen] + 1
      MaxUniques = max(MaxUniques  numPaths[CurrentFen])
      Paths[CurrentFen] = str(Paths[CurrentFen] ThisPath \n)
    }
    
    // Our "END clause"...
    if (terminal or ply == maxPly) and gamenumber == maxGameNumber {
      message quiet (\n "Max unique paths to a position: " MaxUniques)
      Target ∊ numPaths
        if numPaths[Target] == MaxUniques {
          message quiet (\n "Maximal position: " Target)
          message quiet (\n Paths[Target] \n\n)
        }
    }    
      
    false  // no need to spit out any games
    
  • Note that the correct idiom for declaring a persistent numeric variable is:
    persistent NumericVar += 0
    since with CQL v6.1, persistent numeric variables are automatically initialized to zero.

    The FEN string represents not just the board layout but also some additional state info, e.g., side to move, castling availability and en passant target. So what exactly do we mean by "position"? And how much of this state do we wish to take into account when determining the uniqueness of a move order for a given position? One could argue that none of the state beyond the layout is particularly relevant. In fact, the en passant targeting info should almost certainly be ignored since an ep-qualifying pawn push as the last move in the order would give us a "bogus" position. The query includes a number of options for determining the CurrentFen... uncomment whichever one that suits.

    We also limit the position search to a given range of ply, else the engine's memory footprint may get a bit out of hand. As it happens, though, the maximal position is very likely to fall within a narrow range of ply, in any event, and so the constraint is reasonable on multiple counts assuming that execution time is a concern. The ply range search parameters may be edited or may be assigned from the command line.

    Since CQL provides no native mechanism for determining that we have processed the last position of the last game, we need to improvise an awk-like END clause by explicitly giving the number of games in the database. This can be automated from the command line (see the example below), but when executing the query from Scid's CQL search facility one must edit the maxGameNumber assignment to establish that parameter.


    As it stands, the query will dump the qualifying move order list(s) to stdout after processing the last game. One could as easily write the list to file by substituting the writefile() filter for the various message() filters, or one could redirect the output to file when executing the query from the command line. For example:

  • export DB=/tmp/db.pgn
    cat $DB | cql6 -silent -guipgnstdin \
      -assign minPly 10 -assign maxPly 16 \
      -assign maxGameNumber `cat $DB | grep '\[Result' | wc -l` \
      moveorders.cql \
      | grep '^ 1.\|^Max\|^$' > moveorders.txt
    
  • The command pipeline given above demonstrates the CQL engine participating as a constituent in that line while assigning query parameters on the command line. The number of games in the database may conveniently be determined by command substitution (the backticks). We pipe the output from the engine through grep for a bit of cleanup and then direct the output to file.

    The -guipgnstdin switch directs the engine to take input from stdin. We could also direct the engine to dump matching games to stdout with the -guipgnstdout switch, though that is not relevant in the context of this particular query.


Credits

This document is Copyright (c) 2019-2022 Lionel Hampton. The Chess Query Language was originally developed by Gady Costeff and Lewis Stiller.

Footnotes

1 To avoid duplicate work in pairing up the targets, we effectively shift the inner set for each iteration of the outer set. Measurements suggest that there is little if any reduction in search time, probably because the average cardinality for the set in a typical database is so insignificant that the "shift" ends up costing as much as the duplicate work.