This document (still under development) is a companion to the CQL by Example document presented on the Scid++ project site. As we graduate from the relatively straight-forward searches in the conventional domain to the more challenging queries in the problem domain, we require a solid understanding of the structure of the game tree and of the language techniques for traversing the tree.
We are now in the business of matching compositional themes expressed across variations (branches of the tree) in the line of play. In representing the various phases of a solution, the game tree can become quite large and complex. The material that follows sets about to demonstrate techniques for isolating those phases, for identifying relationships between the nodes of the tree, and for matching patterns in the play.
The first order of business is to establish a clear understanding of the terminology used in referring to the game tree. The online CQL documentation includes a couple of relevant introductory sections: 1) a review of the formal structure of the game tree, and 2) an explanation of how the current position is managed as the CQL engine steps through the game tree. Throughout this document we will employ the same terminology as is used in those reference sections. We'll also expand on that terminology to include the notion of an inner and an outer traversal of the tree.
For any given query, traversals of the game tree may be many and may be nested to an arbitrary depth. An outer traversal of the tree takes place as the engine visits each node in the tree in a prescribed order in the course of query evaluation. If the variations parameter is given in the CQL header, the outer traversal visits each node for variations as well as for the mainline.
Within the context of the outer traversal, inner (nested) traversals may take place as a function of a particular CQL filter having traversing capabilities: namely, the find, line, and echo filters. The inner traversals may be intraphase or interphase or may span the entire solution tree.
The position operator allows for the evaluation of a [compound] filter in the context of an arbitrary position. The operator is typically employed in matching interphase patterns or themes in the course of an inner traversal, as is demonstrated in several of the examples below. We should clarify one point in particular with regard to the operator. The position filter (left of the operator) may be (and usually is) a position variable, in which case the variable is evaluated as true if it holds a valid position id. It matters not at all that the evaluation takes place in the context of the current position, whereas the variable assignment may have taken place in a different context.
The PGN standard looks on a chess game as a structured collection of moves. CQL, on the other hand, views the game as a tree structure in which every node represents a position. Each position, then, is associated with a move (or moves) leading to the next position(s). It's largely a matter of emphasis, but with consequences that can lead to a great deal of head-scratching.
When a move filter matches a position, by default it matches the position from which the move was made. With [potentially] multiple moves emanating from any one position, one might match any or all of them and still match only the one position. So how does one distinguish between moves in a complex game tree laden with scores of branches? By matching the positions to which the moves are made, i.e., the positions resulting from the moves. With the variations parameter given in the CQL header, the move previous filter (move filter with the previous parameter) is typically the flavor of choice. Several examples are given below.
Our aim with the examples that follow is to demonstrate some of the essential language techniques employed in the traversal of a game tree, in the course of constructing a CQL query specific to the problem domain. We'll first cover the basics of node recovery and tree traversal, and we'll then give a few examples of the application of those techniques.
The queries have been tested against one or more of the project's support problem databases, with solutions generated by the Scid++ problem solving extension. Those solutions adopt a consistent phase structure of the game tree with some relevant problem-specific metadata registered with each problem's PGN header.
[Event "American Chess Nuts"] [Site "EduSadier Direct Mate in Two"] [Date "1858.??.??"] [Round "80854"] [White "LOYD Samuel"] [Black "#2 -- Actual+Virtual+Set Play"] [Result "1-0"] [Stipulation "#2"] [Solver "Popeye v4.85"] [FEN "5b2/1p2p1p1/1Q1pR1P1/6P1/k2p1K1p/1p1N3P/r1n3P1/nN1B4 w - - 0 1"]
The structure of the solution tree has the key move in the mainline, followed by virtual play and finally by set play. For helpmates the solution(s) lead the way, trailed by set play if present. Set play and virtual play are appropriately tagged with leading PGN comments.
Any given move that is a threat or a block is labeled as such, and conventions are observed for the use of NAGs in solutions. The '!' NAG identifies both the key move and a refutation to a try, while the '?' NAG identifies the try. Note that a '--' sequence in the game tree represents a null move, most commonly found 1) at the root of set play, and 2) in primary play following a threat.
One need not stare for very long at a PGN-formatted solution tree with hundreds (thousands) of nodes to begin to feel a serious headache coming on. The PGN representation of the tree can be virtually unreadable. We'll employ an alternative presentation format that is much less a threat to our health, rendering the typically broad and shallow solution in a vertical orientation. (The tree rendering extension is implemented on the project's utility branch.)
Before we get into the fine detail of tree traversal, we should introduce a few techniques for identifying positions of interest, selected lines of play, solution phases, and classes of problem in the orthodox domain. Filtering may be based on metadata, on node properties, or on characteristics of the solution tree. The examples that follow assume that the problem database has been solved by the Scid++ solving extension.
We can lump self and reflex mates into a single class for the sake of filtering, giving us just three qualifying sets of filters separating out the problems in the database.
// Direct mate. cql() initial wtm result 1-0
// Help mate. cql() initial btm result 1-0
// Self/reflex mate. cql() initial wtm result 0-1
Stalemates are slightly more involved, as we need to further distinguish between the direct and self/reflex classes beyond the commonly held result, since both are white-to-move from the starting position. As it happens, we have a property of the solution tree for either class that will help to separate them out; namely, the side-to-move at the terminal position.
// Direct stalemate. cql() initial wtm find {stalemate btm}
// Help stalemate. cql() initial btm result 1/2-1/2
// Self/reflex stalemate. cql() initial wtm find {stalemate wtm}
We should probably also take into account the white-to-move helpmate (e.g., H#3.5), which is technically indistinguishable from the direct mate in terms of qualifying characteristics that we can glean from the game tree. We'll need to turn to metadata to make that distinction.
By convention — for all databases that have been solved by the Scid++ solving extension — the PGN Black tag carries metadata which includes the problem's stipulation. CQL can match strings against any of the PGN tags in the standard seven tag roster. Thus, we can very easily filter for problems with a specific stipulation, or even a particular substring of the stipulation.
cql() player black "H#3.5"
Update: With the release of version 6.1 of the language, we have a very well done string implementation with nicely integrated regular expressions. A regex may be applied to any PGN header tag, giving us ample flexibility in distinguishing classes of composition.
For example, suppose we're interested in finding all white-to-move help mates or help stalemates with a stipulation length that lies within a specific range. Then matching substrings in metadata just doesn't get the job done.
cql(quiet) initial wtm player black ".5" find {terminal btm mate or stalemate 5 < ply < 11}
Before moving on beyond class filtering, we should point out that at times it may be inconvenient or even downright impractical to filter problems by class on the fly by anchoring the outer traversal at the starting position. One should consider whether it might be simpler to execute two passes on the database, the first to filter on the class and the second to match on the query-proper, where an inner traversal might not even be necessary.
First, we'll take a look at the wrong way of achieving our end.
cql(quiet variations) initial wtm result 1-0 move primary comment("(Key)") move to ~K secondary comment("(Try)") move null secondary comment("(Set)")
The query appears to match all of the correct nodes, tagging the positions with their respective comments as expected. But we have fallen victim to a slightly misleading artifact of a comment filter associated with a move filter, in that the comment does not tag the matching position. In fact, the query matches only the initial position on all three counts of the move filter.
1. Kc2 $1 {threat (Key)} ( {try} 1. Ne4+ $2 {(Try)} 1. ... Kb1 ( 1. ... Rxe5 $1 ) 2. Nd2# ) ( {set} 1. -- {(Set)} 1. ... R1g2 2. N1e2# ( 2. Nd3# ) ( 2. N1a2# ) ) 1. ... -- ( 1. ... R1g2+ 2. N1e2# ) ( 1. ... Bf5+ 2. Ne4# ) ( 1. ... bxc3 2. Bxc3# ) ( 1. ... R5g2+ 2. N3e2# ) 2. Nb3# 1-0 ┌ -- ──── Nb3# ├ R1g2+ ─ N1e2# ┌ Kc2! threat (Key) ┼ Bf5+ ── Ne4# │ ├ bxc3 ── Bxc3# │ └ R5g2+ ─ N3e2# Start ┤ │ ┌ Kb1 ─── Nd2# ├ Ne4+? (Try) ──────┤ │ └ Rxe5! │ ┌ N1e2# └ -- (Set) ────────── R1g2 ─┼ Nd3# └ N1a2#
What we have, then, is a query that tells us only that a matching direct-mate has a solution that includes all three phases of play. If we wish to match and acquire the positions at the root of the respective branches, we must identify the move that is made from the starting position and that leads to the root position.
cql(quiet variations) initial wtm result 1-0 find all { ply == 1 move primary previous and comment("(Key)") or move secondary previous and hascomment "$2" and comment("(Try)") or move null secondary previous and comment("(Set)") }
A special case of phase recovery entails the isolation of cooks. One might wish to eliminate from consideration any solution that is cooked (of which there are thousands in a typical problem database), or one might wish to specifically target intentionally cooked solutions (e.g., Vlagsma (Probleemblad - 1978)).
The following snippet identifies every key position of a solution — including cooks — while employing just the outer traversal.
cql(quiet variations) ply == 1 and hascomment "$1"
cql(quiet variations) not find {ply == 1 move secondary previous hascomment "$1"}
cql(quiet variations) initial btm result 1-0 find all { ply == 1 move null secondary previous and comment("(Set)") or not move null secondary previous and comment("(Solution)") }
1. ... h1=R {(Solution)} ( 1. ... h1=B {(Solution)} 2. Nf3 Bg2 3. Ne5 Bb7 4. Nc6 Ba6 5. Na7# ) ( {set} 1. ... -- {(Set)} 2. Nf3 h1=B 3. Ne5 Bb7 4. Nc6 Ba6 5. Na7# ) 2. Kb2 Kxa4 3. Ne2 Rb1+ 4. Ka2 Rb4 5. Nc3# 1-0 ┌ h1=R (Solution) ─ Kb2 ─ Kxa4 ─ Ne2 ─ Rb1+ ─ Ka2 ─ Rb4 ─ Nc3# Start ┼ h1=B (Solution) ─ Nf3 ─ Bg2 ── Ne5 ─ Bb7 ── Nc6 ─ Ba6 ─ Na7# └ -- (Set) ──────── Nf3 ─ h1=B ─ Ne5 ─ Bb7 ── Nc6 ─ Ba6 ─ Na7#
cql(quiet variations) hascomment "threat" and comment("(Threat)") or hascomment "block" and comment("(Block)")
1. e8=R $1 {block (Block)} ( {try} 1. e8=Q $2 {block (Block)} 1. ... Kf6 ( 1. ... f6 $1 ) 2. Kg4 {threat (Threat)} 2. ... -- ( 2. ... Kg7 3. Kf5# ) 3. Bb2# ) 1. ... Kf6 ( 1. ... f6 2. Re3 {block (Block)} 2. ... Kg5 3. Re5# ) 2. Kg4 {threat (Threat)} 2. ... -- ( 2. ... Kg7 3. Kf5# ) 3. Bb2# 1-0 ┌ -- ── Bb2# ┌ Kf6 ─ Kg4 threat (Threat) ┤ ┌ e8=R! block (Block) ┤ └ Kg7 ─ Kf5# │ └ f6 ── Re3 block (Block) ─── Kg5 ─ Re5# Start ┤ │ ┌ -- ── Bb2# │ ┌ Kf6 ─ Kg4 threat (Threat) ┤ └ e8=Q? block (Block) ┤ └ Kg7 ─ Kf5# └ f6!
cql(quiet variations) terminal hascomment "$1" and comment("(Refute)")
1. Qd3 $1 {block} ( {try} 1. Nf3+ $2 Ke4 $1 {(Refute)} ) ( {try} 1. Qb8 $2 {block} 1. ... Kd5 ( 1. ... f4 $1 {(Refute)} ) 2. Qb5# ) ( {try} 1. Qb5+ $2 Rd5 $1 {(Refute)} ) ( {try} 1. Qg3+ $2 f4 ( 1. ... Kd5 $1 {(Refute)} ) 2. Qg5# ) ( {try} 1. Qf3 $2 {threat} 1. ... -- ( 1. ... f4 2. Qh5# ( 2. Qe4# ) ) ( 1. ... Rc6 $1 {(Refute)} ) 2. Nc4# ) ( {try} 1. Qe3+ $2 Kd5 $1 {(Refute)} ) 1. ... Kd5 ( 1. ... f4 2. Qe4# ) ( 1. ... Rd5 2. Nc4# ) ( 1. ... Ra6 2. Qxd4# ) ( 1. ... Rb6 2. Qxd4# ) ( 1. ... Rc6 2. Qxd4# ) 2. Qb5# 1-0 ┌ Kd5 ─────────── Qb5# ├ f4 ──────────── Qe4# ├ Rd5 ─────────── Nc4# ┌ Qd3! block ─┤ │ ├ Ra6 ─────────── Qxd4# │ ├ Rb6 ─────────── Qxd4# │ └ Rc6 ─────────── Qxd4# ├ Nf3+? ─────── Ke4! (Refute) │ ┌ Kd5 ─────────── Qb5# ├ Qb8? block ─┤ Start ┤ └ f4! (Refute) ├ Qb5+? ─────── Rd5! (Refute) │ ┌ f4 ──────────── Qg5# ├ Qg3+? ──────┤ │ └ Kd5! (Refute) │ ┌ -- ──────────── Nc4# │ │ ┌ Qh5# ├ Qf3? threat ┼ f4 ───────────┤ │ │ └ Qe4# │ └ Rc6! (Refute) └ Qe3+? ─────── Kd5! (Refute)
cql(quiet variations) move null previous and comment("(Null)") if move previous primary then comment("(Primary)") else comment("(Secondary)")
1. Kc2 $1 {threat} ( {try} 1. Ne4+ $2 Kb1 ( 1. ... Rxe5 $1 ) 2. Nd2# ) ( {set} 1. -- {(Null) (Secondary)} 1. ... R1g2 2. N1e2# ( 2. Nd3# ) ( 2. N1a2# ) ) 1. ... -- {(Null) (Primary)} ( 1. ... R1g2+ 2. N1e2# ) ( 1. ... Bf5+ 2. Ne4# ) ( 1. ... bxc3 2. Bxc3# ) ( 1. ... R5g2+ 2. N3e2# ) 2. Nb3# 1-0 ┌ -- (Null) (Primary) ─ Nb3# ├ R1g2+ ─────────────── N1e2# ┌ Kc2! threat ──────────┼ Bf5+ ──────────────── Ne4# │ ├ bxc3 ──────────────── Bxc3# │ └ R5g2+ ─────────────── N3e2# Start ┤ │ ┌ Kb1 ───────────────── Nd2# ├ Ne4+? ────────────────┤ │ └ Rxe5! │ ┌ N1e2# └ -- (Null) (Secondary) ─ R1g2 ───────────────┼ Nd3# └ N1a2#
cql(quiet variations) mainline and comment("(M)") or variation and comment("(V" depth ")") move primary previous and comment("(P)") or move secondary previous and comment("(S)")
1. Kc2 $1 {threat (M) (P)} ( {try} 1. Ne4+ $2 {(V1) (S)} 1. ... Kb1 {(V1) (P)} ( 1. ... Rxe5 $1 {(V2) (S)} ) 2. Nd2# {(V1) (P)} ) ( {set} 1. -- {(V1) (S)} 1. ... R1g2 {(V1) (P)} 2. N1e2# {(V1) (P)} ( 2. Nd3# {(V2) (S)} ) ( 2. N1a2# {(V2) (S)} ) ) 1. ... -- {(M) (P)} ( 1. ... R1g2+ {(V1) (S)} 2. N1e2# {(V1) (P)} ) ( 1. ... Bf5+ {(V1) (S)} 2. Ne4# {(V1) (P)} ) ( 1. ... bxc3 {(V1) (S)} 2. Bxc3# {(V1) (P)} ) ( 1. ... R5g2+ {(V1) (S)} 2. N3e2# {(V1) (P)} ) 2. Nb3# {(M) (P)} 1-0 ┌ -- (M) (P) ───── Nb3# (M) (P) ├ R1g2+ (V1) (S) ─ N1e2# (V1) (P) ┌ Kc2! threat (M) (P) ┼ Bf5+ (V1) (S) ── Ne4# (V1) (P) │ ├ bxc3 (V1) (S) ── Bxc3# (V1) (P) │ └ R5g2+ (V1) (S) ─ N3e2# (V1) (P) Start ┤ │ ┌ Kb1 (V1) (P) ─── Nd2# (V1) (P) ├ Ne4+? (V1) (S) ─────┤ │ └ Rxe5! (V2) (S) │ ┌ N1e2# (V1) (P) └ -- (V1) (S) ───────── R1g2 (V1) (P) ─┼ Nd3# (V2) (S) └ N1a2# (V2) (S)
CQL includes a number of filters that provide for an inner or nested traversal of the game tree. That nesting can actually take place to an arbitrary depth with, for example, a line filter given as a constituent of another line filter.
When nesting traversals, one can look on the leading qualifying filters of the outer traversal as establishing an anchor from which to conduct the inner traversal. If there are no anchoring filters, then every node in the tree becomes an anchor point. The anchor will, of course, be established by a filter that matches the anchored position. The inner traversal may take place over the entire tree, over just the branch rooted by the anchor, or over an entirely different branch (i.e., solution phase).
In demonstrating the mechanics of the various filters, we'll employ the comment and message filters as an assist in visualizing the inner and outer traversals of the tree. And to eliminate excess noise in the tree, we'll tag only the anchor point for the outer traversal even though that traversal might walk the entire tree.
But before we get to the filters, we should probably demonstrate the outer traversal of the tree as a reference point for the inner traversals.
cql(quiet variations) comment("position " positionid) message("position " positionid)
┌ -- position 2 ───── Nb3# position 3 ├ R1g2+ position 4 ── N1e2# position 5 ┌ Kc2! threat position 1 ┼ Bf5+ position 6 ─── Ne4# position 7 │ ├ bxc3 position 8 ─── Bxc3# position 9 │ └ R5g2+ position 10 ─ N3e2# position 11 Start ┤ │ ┌ Kb1 position 13 ─── Nd2# position 14 ├ Ne4+? position 12 ─────┤ │ └ Rxe5! position 15 │ ┌ N1e2# position 18 └ -- position 16 ───────── R1g2 position 17 ─┼ Nd3# position 19 └ N1a2# position 20
Game: 1 move 1(wtm): position 0 Game: 1 move 1(btm): position 1 Game: 1 move 2(wtm): position 2 Game: 1 move 2(btm): position 3 Game: 1 move 2(wtm)[4]: position 4 Game: 1 move 2(btm)[5]: position 5 Game: 1 move 2(wtm)[6]: position 6 Game: 1 move 2(btm)[7]: position 7 Game: 1 move 2(wtm)[8]: position 8 Game: 1 move 2(btm)[9]: position 9 Game: 1 move 2(wtm)[10]: position 10 Game: 1 move 2(btm)[11]: position 11 Game: 1 move 1(btm)[12]: position 12 Game: 1 move 2(wtm)[13]: position 13 Game: 1 move 2(btm)[14]: position 14 Game: 1 move 2(wtm)[15]: position 15 Game: 1 move 1(btm)[16]: position 16 Game: 1 move 2(wtm)[17]: position 17 Game: 1 move 2(btm)[18]: position 18 Game: 1 move 2(btm)[19]: position 19 Game: 1 move 2(btm)[20]: position 20
cql(quiet variations) initial find all {comment("find " positionid)}
┌ -- find 2 ───── Nb3# find 3 ├ R1g2+ find 4 ── N1e2# find 5 ┌ Kc2! threat find 1 ┼ Bf5+ find 6 ─── Ne4# find 7 │ ├ bxc3 find 8 ─── Bxc3# find 9 │ └ R5g2+ find 10 ─ N3e2# find 11 Start ┤ │ ┌ Kb1 find 13 ─── Nd2# find 14 ├ Ne4+? find 12 ─────┤ │ └ Rxe5! find 15 │ ┌ N1e2# find 18 └ -- find 16 ───────── R1g2 find 17 ─┼ Nd3# find 19 └ N1a2# find 20
cql(quiet variations) move null previous secondary and comment("outer " positionid) find all {comment("inner " positionid)}
┌ -- ──────────── Nb3# ├ R1g2+ ───────── N1e2# ┌ Kc2! threat ─────────┼ Bf5+ ────────── Ne4# │ ├ bxc3 ────────── Bxc3# │ └ R5g2+ ───────── N3e2# Start ┤ │ ┌ Kb1 ─────────── Nd2# ├ Ne4+? ───────────────┤ │ └ Rxe5! │ ┌ N1e2# inner 18 └ -- outer 16 inner 16 ─ R1g2 inner 17 ┼ Nd3# inner 19 └ N1a2# inner 20
More precisely, the filter will traverse all positions for which the current position is an ancestor; that is, all positions in the tree having the current position at the [possibly distant] root of the visited node's branch. For example:
cql(quiet variations) ply == 1 move to ~K secondary previous and comment("outer " positionid) find all {comment("inner " positionid)}
┌ -- ───────────── Nb3# ├ R1g2+ ────────── N1e2# ┌ Kc2! threat ────────────┼ Bf5+ ─────────── Ne4# │ ├ bxc3 ─────────── Bxc3# │ └ R5g2+ ────────── N3e2# Start ┤ │ ┌ Kb1 inner 13 ─── Nd2# inner 14 ├ Ne4+? outer 12 inner 12 ┤ │ └ Rxe5! inner 15 │ ┌ N1e2# └ -- ────────────────────── R1g2 ──────────┼ Nd3# └ N1a2#
cql(quiet variations alwayscomment) initial line --> {comment("line " positionid)}{*}
┌ -- line 2 ───── Nb3# line 3 ├ R1g2+ line 4 ── N1e2# line 5 ┌ Kc2! threat line 1 ┼ Bf5+ line 6 ─── Ne4# line 7 │ ├ bxc3 line 8 ─── Bxc3# line 9 │ └ R5g2+ line 10 ─ N3e2# line 11 Start ┤ │ ┌ Kb1 line 13 ─── Nd2# line 14 ├ Ne4+? line 12 ─────┤ │ └ Rxe5! line 15 │ ┌ N1e2# line 18 └ -- line 16 ───────── R1g2 line 17 ─┼ Nd3# line 19 └ N1a2# line 20
cql(quiet variations alwayscomment) hascomment "$2" and comment("outer " positionid) line --> {comment("inner " positionid)}{*}
┌ -- ────────────── Nf6# ├ Rxc3 ──────────── Qxd4# ┌ Bb6! threat ───────────────────┤ │ ├ Rxe4 ──────────── Qc5# │ └ Nxb6+ ─────────── Nxb6# │ ┌ Qxd4# inner 12 │ ┌ -- inner 11 ────┤ │ │ └ Rxd4# inner 13 ├ Rxd3? threat outer 10 inner 10 ┼ Rxd3 inner 14 ─── Qxd3# inner 15 │ ├ Nb6+ inner 16 ─── Nxb6# inner 17 │ └ Kxc4+! inner 18 │ ┌ -- inner 20 ───── Ne3# inner 21 Start ┼ Qxd3? threat outer 19 inner 19 ┼ Nb6+ inner 22 ─── Nxb6# inner 23 │ └ Rxd3! inner 24 ├ Nb6+? outer 25 inner 25 ──────── Nxb6+! inner 26 ├ Nf6+? outer 27 inner 27 ──────── Kc5+! inner 28 │ ┌ -- inner 30 ───── Nf6# inner 31 │ ├ Rxe4 inner 32 ─── Qc5# inner 33 ├ Bd6? threat outer 29 inner 29 ─┤ │ ├ Nb6+ inner 34 ─── Nxb6# inner 35 │ └ Rxc3! inner 36 │ ┌ Rxc4 ──────────── Nf6# └ -- ────────────────────────────┼ Nb6+ ──────────── Nxb6# └ Nxc7 ──────────── Nb6#
cql(quiet variations alwayscomment) line --> hascomment "$2" and comment("anchor " positionid) --> {comment("repeat " positionid)}{*}
┌ -- ────────────── Nb3# ├ R1g2+ ─────────── N1e2# ┌ Kc2! threat ────┼ Bf5+ ──────────── Ne4# │ ├ bxc3 ──────────── Bxc3# │ └ R5g2+ ─────────── N3e2# Start ┤ │ ┌ Kb1 repeat 13 ─── Nd2# repeat 14 ├ Ne4+? anchor 12 ┤ │ └ Rxe5! repeat 15 │ ┌ N1e2# └ -- ────────────── R1g2 ───────────┼ Nd3# └ N1a2#
At this point, it's reasonable to ask whether an inner filter is even necessary for simple queries. After all, the outer traversal walks the tree in the same fashion as a line filter anchored to the starting position (having a single repeating constituent that always matches).
The answer to the question is... yes and no.
Taking the following seriously-contrived query as a starting point and executing it against our favorite solution tree, we can see that the lines matched are both in actual play. Note that we have a preamble that limits matching games to the direct-mate-in-2 class, and that the outer traversal is anchored at the initial position.
cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" line --> move primary // the key --> currentposition: {move to [f5,c3,b1,e5] comment("Match")} == 2 and comment("Found")
┌ -- ───────── Nb3# ├ R1g2+ ────── N1e2# ┌ Kc2! threat Found ┼ Bf5+ Match ─ Ne4# │ ├ bxc3 Match ─ Bxc3# │ └ R5g2+ ────── N3e2# Start ┤ │ ┌ Kb1 ──────── Nd2# ├ Ne4+? ────────────┤ │ └ Rxe5! │ ┌ N1e2# └ -- ──────────────── R1g2 ──────┼ Nd3# └ N1a2#
But we still need to stipulate that the problem has white-to-move at the initial position, as part of the preamble filtering by class. Since we can no longer anchor the outer traversal at that position, what to do? The position operator allows us to test for white-to-move at position 0, the starting position, without anchoring the outer traversal.
cql(quiet variations) position 0:wtm result 1-0 player black "#2"
cql(quiet variations) position 0:wtm result 1-0 player black "#2" {move to [f5,c3,b1,e5] comment("Match")} == 2 and comment("Found")
The result returned by the engine shows that we matched four lines, two in actual play and two in virtual play. Not quite what we were hoping for. The "line" is not anchored in actual play as it was with the line filter.
┌ -- ────────── Nb3# ├ R1g2+ ─────── N1e2# ┌ Kc2! threat Found ┼ Bf5+ Match ── Ne4# │ ├ bxc3 Match ── Bxc3# │ └ R5g2+ ─────── N3e2# Start ┤ │ ┌ Kb1 Match ─── Nd2# ├ Ne4+? Found ──────┤ │ └ Rxe5! Match │ ┌ N1e2# └ -- ──────────────── R1g2 ───────┼ Nd3# └ N1a2#
cql(quiet variations) position 0:wtm result 1-0 player black "#2" {move to [f5,c3,b1,e5] comment("Match")} == 2 and comment("Found") find <-- hascomment "$1"
┌ -- ───────── Nb3# ├ R1g2+ ────── N1e2# ┌ Kc2! threat Found ┼ Bf5+ Match ─ Ne4# │ ├ bxc3 Match ─ Bxc3# │ └ R5g2+ ────── N3e2# Start ┤ │ ┌ Kb1 ──────── Nd2# ├ Ne4+? ────────────┤ │ └ Rxe5! │ ┌ N1e2# └ -- ──────────────── R1g2 ──────┼ Nd3# └ N1a2#
As indicated above, in a variations context we need to be aware of the linearization rule associated with this filter. The online reference addresses the issue at some length, and we dedicate a section below to its application.
cql(quiet variations) initial echo(source target) {comment("echo " positionid)}
┌ -- echo 2 ───── Nb3# echo 3 ├ R1g2+ echo 4 ── N1e2# echo 5 ┌ Kc2! threat echo 1 ┼ Bf5+ echo 6 ─── Ne4# echo 7 │ ├ bxc3 echo 8 ─── Bxc3# echo 9 │ └ R5g2+ echo 10 ─ N3e2# echo 11 Start ┤ │ ┌ Kb1 echo 13 ─── Nd2# echo 14 ├ Ne4+? echo 12 ─────┤ │ └ Rxe5! echo 15 │ ┌ N1e2# echo 18 └ -- echo 16 ───────── R1g2 echo 17 ─┼ Nd3# echo 19 └ N1a2# echo 20
cql(quiet variations) move previous primary and hascomment "threat" comment("source " positionid) echo(source target) {ancestor(source target) comment("target " positionid)}
┌ -- target 2 ───── Nb3# target 3 ├ R1g2+ target 4 ── N1e2# target 5 ┌ Kc2! threat source 1 ┼ Bf5+ target 6 ─── Ne4# target 7 │ ├ bxc3 target 8 ─── Bxc3# target 9 │ └ R5g2+ target 10 ─ N3e2# target 11 Start ┤ │ ┌ Kb1 ───────────── Nd2# ├ Ne4+? ───────────────┤ │ └ Rxe5! │ ┌ N1e2# └ -- ─────────────────── R1g2 ───────────┼ Nd3# └ N1a2#
We'll show a few simple examples of the practical use of the techniques demonstrated in the previous sections. The same techniques are also abundantly in use throughout the latter half of the main document.
cql(quiet variations alwayscomment) initial wtm result 1-0 line --> move primary // the key --> hascomment "threat" and find all {move secondary and comment("(Match)")}
┌ -- ──── Nb3# ├ R1g2+ ─ N1e2# ┌ Kc2! threat (Match) ┼ Bf5+ ── Ne4# │ ├ bxc3 ── Bxc3# │ └ R5g2+ ─ N3e2# Start ┤ │ ┌ Kb1 ─── Nd2# ├ Ne4+? ──────────────┤ │ └ Rxe5! │ ┌ N1e2# └ -- ────────────────── R1g2 ─┼ Nd3# └ N1a2#
The query matches a single position regardless of the number of variations in actual play. In fact, the find all filter returns a value of only 1 because all post-key secondary moves are made from that one position.
Remembering that CQL matches positions, we'll modify the query ever so slightly to get the result we're looking for.
cql(quiet variations alwayscomment) initial wtm result 1-0 line --> move primary // the key --> hascomment "threat" and find all {move secondary previous and comment("(Match)")}
┌ -- ──────────── Nb3# ├ R1g2+ (Match) ─ N1e2# ┌ Kc2! threat ┼ Bf5+ (Match) ── Ne4# │ ├ bxc3 (Match) ── Bxc3# │ └ R5g2+ (Match) ─ N3e2# Start ┤ │ ┌ Kb1 ─────────── Nd2# ├ Ne4+? ──────┤ │ └ Rxe5! │ ┌ N1e2# └ -- ────────── R1g2 ─────────┼ Nd3# └ N1a2#
As an example, the query below yields a collection of direct-mates-in-two in which both set play and actual play have some number of dual-free lines of play falling within a range, for those problems in which the key is a block.
cql(quiet variations) initial wtm result 1-0 player black "#2" Min = 4 Max = 4 function Countem() { // Line's of play are dual-free. not find {terminal and move secondary previous} // Lines of play fall within a range. Min <= {find all {terminal}} <= Max } line // actual play --> move primary --> hascomment "block" and Countem() line // set play --> move null --> Countem()
The query stipulates nothing specific about thematic play, and yet, on little more than casual inspection of one of the matching problems, we can pick up on a rather common theme in the direct-mate genre (Amirov, Talip (To Mat - 1962)). The filtered problems are prime candidates for interphase traversals, matching themes with some very specific properties.
┌ c3 ──── Qb4# ├ e3 ──── Qf4# ┌ Qxd6! block ┤ │ ├ b5 ──── Qc5# │ └ f5 ──── Qe5# │ ┌ c3 ──── Qa4# │ ├ e3 ──── Qg4# ├ h6? block ──┼ b5 ──── Qxa7# │ ├ f5 ──── Qxg7# │ └ gxh6! │ ┌ -- ──── Qd1# ├ Qa4? threat ┤ │ └ e3! │ ┌ Qxc3# Start ┤ ┌ c3 ───┤ │ │ └ bxc3# ├ Qc7? block ─┼ b5 ──── Qxa7# │ ├ f5 ──── Qxg7# │ └ e3! │ ┌ e3 ──── Qxe3# │ ├ b5 ──── Qxa7# ├ Qe7? block ─┤ │ ├ f5 ──── Qxg7# │ └ c3! │ ┌ c3 ──── Qa4# │ ├ e3 ──── Qg4# └ -- ─────────┤ ├ b5 ──── Qxa7# └ f5 ──── Qxg7#
We give a generic language construct for interphase traversal of direct-mate solutions, allowing for the detection of thematic play across phases. The construct employs nested line filters to achieve the effect, where the level of nesting may extend well beyond that indicated (see the Lacny example from the online reference).
cql(quiet variations alwayscomment) initial wtm result 1-0 // Recover set play. line --> move null --> Set = currentposition // Actual play with nested set play traversal. line --> move primary // the key --> . --> {C = currentposition comment("Actual Play") Set:{line --> . --> {comment("Set Play") message(C)}}}
The CQL engine has tagged every matching position with a comment indicating the respective position id. This is a side effect of the message filter.
┌ c3 Actual Play ────── Qb4# ├ e3 Actual Play id:4 ─ Qf4# ┌ Qxd6! block ┤ │ ├ b5 Actual Play id:6 ─ Qc5# │ └ f5 Actual Play id:8 ─ Qe5# │ ┌ c3 ────────────────── Qa4# │ ├ e3 ────────────────── Qg4# ├ h6? block ──┼ b5 ────────────────── Qxa7# │ ├ f5 ────────────────── Qxg7# │ └ gxh6! │ ┌ -- ────────────────── Qd1# ├ Qa4? threat ┤ │ └ e3! │ ┌ Qxc3# Start ┤ ┌ c3 ─────────────────┤ │ │ └ bxc3# ├ Qc7? block ─┼ b5 ────────────────── Qxa7# │ ├ f5 ────────────────── Qxg7# │ └ e3! │ ┌ e3 ────────────────── Qxe3# │ ├ b5 ────────────────── Qxa7# ├ Qe7? block ─┤ │ ├ f5 ────────────────── Qxg7# │ └ c3! │ ┌ c3 Set Play id:42 ─── Qa4# │ ├ e3 Set Play id:44 ─── Qg4# └ -- ─────────┤ ├ b5 Set Play id:46 ─── Qxa7# └ f5 Set Play id:48 ─── Qxg7#
Game: 1 move 2(wtm)[42]: move 2(wtm) Game: 1 move 2(wtm)[44]: move 2(wtm) Game: 1 move 2(wtm)[46]: move 2(wtm) Game: 1 move 2(wtm)[48]: move 2(wtm) Game: 1 move 2(wtm)[42]: move 2(wtm)[4] Game: 1 move 2(wtm)[44]: move 2(wtm)[4] Game: 1 move 2(wtm)[46]: move 2(wtm)[4] Game: 1 move 2(wtm)[48]: move 2(wtm)[4] Game: 1 move 2(wtm)[42]: move 2(wtm)[6] Game: 1 move 2(wtm)[44]: move 2(wtm)[6] Game: 1 move 2(wtm)[46]: move 2(wtm)[6] Game: 1 move 2(wtm)[48]: move 2(wtm)[6] Game: 1 move 2(wtm)[42]: move 2(wtm)[8] Game: 1 move 2(wtm)[44]: move 2(wtm)[8] Game: 1 move 2(wtm)[46]: move 2(wtm)[8] Game: 1 move 2(wtm)[48]: move 2(wtm)[8]
cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" // Count the number of legal moves in the current position (btm). function Countem() { Count = 0 piece Piece in a { Count += #(move to . from Piece legal) // Adjust the count to include all possible promotions. Count += #(move to [a-h1] from (Piece&p) legal) * 3 } Count } // The key is not a waiting move. line --> move primary // the key --> hascomment "threat" // All legal moves are set. SetMoves = 0 line --> move null --> LegalMoves = Countem() --> SetMoves += 1 LegalMoves == SetMoves
The solution for the composition depicted in the diagram (Sándor Boros (Magyar Sakkvilág - 1926)) matches the query, with the rendered solution tree showing all legal black moves set with mate.
┌ -- ───── Qxe7# ├ Ne6 ──── Qg6# ┌ Nh6! threat ┤ │ ├ Ng6 ──── Qxg6# │ └ Rg8 ──── Qxg8# ├ Nf6+? ─────── exf6! │ ┌ Nd7 ──── Qg6# │ ├ Ne6 ──── Qg6# ├ Ne5? block ─┼ Ng6 ──── Qxg6# │ ├ Rg8 ──── Qh6# │ └ e6! Start ┤ ├ Qh6+? ─────── Kg8! ├ Qxe7+? ────── Kg8+! ├ Qg7+? ─────── Kxg7+! ├ Qg6+? ─────── Nxg6! │ ┌ e5 ───── Nf6# │ ├ e6 ───── Nf6# │ ├ Nd7 ──── Qg6# └ -- ─────────┤ ├ Ne6 ──── Qg6# ├ Ng6 ──── Qxg6# └ Rg8 ──── Qh6#
syntax
result
syntax
result
// Intraphase traversal. cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" line --> move primary // the key --> Actual = currentposition // root of actual play --> not move null previous // no threat lines --> echo(source target) { terminal and ancestor(Actual target) parent:{not move null previous} message("source:" source) }
Note that if we were employing nested line filters, the anchor of the inner line would have to be at or before the terminal positions' common LCA. With the echo filter traversing the entire tree, we need only to ensure that the LCA (with the Actual variable as a reference) is an ancestor of each of our targets of interest.
┌ -- ──── Nb3# ├ R1g2+ ─ N1e2# id:5 ┌ Kc2! threat ┼ Bf5+ ── Ne4# id:7 │ ├ bxc3 ── Bxc3# id:9 │ └ R5g2+ ─ N3e2# id:11 Start ┤ │ ┌ Kb1 ─── Nd2# ├ Ne4+? ──────┤ │ └ Rxe5! │ ┌ N1e2# └ -- ────────── R1g2 ─┼ Nd3# └ N1a2#
Game: 1 move 2(btm)[7]: source:move 2(btm)[5] Game: 1 move 2(btm)[9]: source:move 2(btm)[5] Game: 1 move 2(btm)[11]: source:move 2(btm)[5] Game: 1 move 2(btm)[5]: source:move 2(btm)[7] Game: 1 move 2(btm)[9]: source:move 2(btm)[7] Game: 1 move 2(btm)[11]: source:move 2(btm)[7] Game: 1 move 2(btm)[5]: source:move 2(btm)[9] Game: 1 move 2(btm)[7]: source:move 2(btm)[9] Game: 1 move 2(btm)[11]: source:move 2(btm)[9] Game: 1 move 2(btm)[5]: source:move 2(btm)[11] Game: 1 move 2(btm)[7]: source:move 2(btm)[11] Game: 1 move 2(btm)[9]: source:move 2(btm)[11]
The next query expands on the basic query above to search for problems having all mates in actual play coming from a queen and coming on a unique square. We'll set a lower bound on the number of mating lines and sort the matching problems by that number.
// Intraphase traversal. cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" Q == 1 // only one queen on the board Terms = 0 Uniqs = 0 line --> move primary // the key --> { // actual play Actual = currentposition // All mates are by the queen. not find {terminal move from ~Q previous} } --> not move null previous // threat lines are irrelevant --> { // the terminal position Terms += 1 // Eliminate discoveries. k attackedby Q // Is the mating square unique? not echo(source target) { terminal and ancestor(Actual target) parent:{not move null previous} source < target source:Q & target:Q } Uniqs += 1 comment("Unique") } // All mating squares are unique with a lower bound. 6 <= Terms == Uniqs sort "Unique mating squares" Uniqs
Also observe that we've added a condition to the qualification of the echo filter's target position, namely source < target. Without that condition, we would be repeating evaluations of the balance of the body of the filter for some number of source/target pairs. (See the message dump above for verification.)
The query matches a composition by Benko (Magyar Sakkélet - 1984), where pawn play is also a part of the theme. Eight lines. Clean. Efficient. Disgusting.
┌ a2 ──── Qb2# Unique ├ b3 ──── Qc3# Unique ├ e3 ──── Qxe3# Unique ├ c4 ──── Qd4# Unique ┌ Nb5! block ─┤ │ ├ g4 ──── Qf4# Unique │ ├ cxb5 ── Qd5# Unique │ ├ h5 ──── Qxg5# Unique Start ┤ └ e6 ──── Qd6# Unique │ ┌ -- ──── Qxc5# ├ Qe3? threat ┤ │ └ Kd6! ├ Qd5+? ─────── cxd5! │ ┌ Ng4# │ ┌ -- ───┤ │ │ └ Nc4# └ Ne3? threat ┤ ├ h5 ──── Nc4# └ Kf4!
While we make no excuses for the echo's inefficiencies, there are a couple of intangibles which we shall offer up without explanation. The filter is clean. The filter is clear. And the filter manages our position variables for us, while automatically eliminating the current position from the traversal. And we are, by our nature, lazy critters with an affinity for clean things. In short, it boils down to a matter of taste.
We should finish with the footnote that an essential element in employing the echo is the quick dismissal of unwanted targets. In that regard, the ordering of the leading filters matters. Filters with the least overhead and with the greatest impact should go up front.
We might also annoyingly point out the obvious... that if the theme manifests strictly in actual play, then solutions with very little virtual play suffer a less severe penalty. Solutions with no virtual play are even better. Toward that end — if one finds that one is irresistibly drawn to the echo filter — one might consider solving the database to include just one phase in the solution (actual play).
All of that said, this query would look very different and would likely (maybe) be much more efficient if the find all filter could (optionally) return a set of positions. Then nested iterating position filters (much like the square filter) might be employed, at least in our fantasies, to achieve our ends without all of this repetitious traversing of the tree. While acknowledging that CQL was never designed or intended for the problem domain with its zillions of variations, it is a remarkably adaptable language. We're just thinking out loud (influenced, no doubt, by that tiresome kibbitzing niece), thinking ahead, to that version 7 release when our CQL developers are retired and have nothing better to do than to go trout fishing on some pristine lake in the Himalayas. Bring along your laptops, gentlemen.
// Intraphase traversal. cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" function Unique(Square Set) { not Square in Set Set = Square | Set // insert Square into Set } Terms = 0 Uniqs = ~. // the empty set (not Uniqs = []) line --> move primary // the key --> { // actual play Actual = currentposition Q == 1 // only one queen on the board // All mates are by the queen. not find {terminal move from ~Q previous} } --> not move null previous // threat lines are irrelevant --> { // the terminal position Terms += 1 // Eliminate discoveries. k attackedby Q // Is the mating square unique? Unique(Q Uniqs) comment("Unique") } // All mating squares are unique with a lower bound. 6 <= Terms == Uniqs sort "Unique mating squares" Uniqs
cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" // Compare two moves. function SameMove(X Y) { X:{move from . previous} == Y:{move from . previous} X:{move to . previous} == Y:{move to . previous} // Account for promotions. X:{type move to . previous} == Y:{type move to . previous} } // Eliminate trees with duals in threats. not find {move null primary previous and move secondary} // Recover the key threat. line primary --> . // the key --> hascomment "threat" --> move null previous --> mate and Threat = currentposition matchCount = 0 threatCount = 0 // Match threats in virtual play with the key threat. line --> move secondary --> hascomment "$2" and hascomment "threat" --> move null previous and threatCount += 1 --> {mate SameMove(currentposition Threat) matchCount += 1 comment("Match")} // Every threat matches and the counts are in range. 4 <= matchCount == threatCount <= 8
┌ -- ─── Bc5# ┌ Nf2! threat ─┤ │ └ Kd4 ── Qc5# │ ┌ -- ─── Bc5# Match ├ Nc3? threat ─┤ │ └ Kd4! │ ┌ -- ─── Bc5# Match ├ Nd2? threat ─┤ │ └ Kd4! Start ┤ ┌ -- ─── Bc5# Match ├ Nxg3? threat ┤ │ └ Kd4! │ ┌ -- ─── Bc5# Match ├ Ng5? threat ─┤ │ └ Kd4! │ ┌ -- ─── Bc5# Match ├ Nf6? threat ─┤ │ └ Kd4! └ Bc5+? ──────── Kd3!
Of course, a pursuit of this line of inquiry can be a decadent passtime that might well shamefully occupy an entire lifetime. We like to put the blame on CQL for enabling it in the first place. ("Sweetheart, could you take out the trash?")
Linearization is a complex topic, and in most situations one need be only vaguely aware of it. Nonetheless, to fully appreciate the traversal patterns that one might witness with the line filter, it can be useful to have at least a superficial understanding of the rule.
The online reference includes a section on linearization that will help to explain the rationale for the rule. We'll expand on that material to demonstrate the rule's impact on a full solution tree. Then we'll explore some of the reasons that one might wish to disable linearization and give a couple of relevant examples.
cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" line --> {message("pid:" positionid) move to c2 comment("Match")} --> {message("pid:" positionid) move to g2 comment("Match")} --> {message("pid:" positionid) move to e2 comment("Match")} --> {message("pid:" positionid) mate}
┌ -- ──────────────── Nb3# ├ R1g2+ Match id:4 ── N1e2# Match id:5 ┌ Kc2! threat Match ┼ Bf5+ ────────────── Ne4# │ ├ bxc3 ────────────── Bxc3# │ └ R5g2+ Match id:10 ─ N3e2# Match id:11 Start ┤ │ ┌ Kb1 ─────────────── Nd2# ├ Ne4+? ────────────┤ │ └ Rxe5! │ ┌ N1e2# └ -- ──────────────── R1g2 ─────────────┼ Nd3# └ N1a2#
Game: 1 move 1(wtm): pid:0 Game: 1 move 1(btm): pid:1 Game: 1 move 1(btm): pid:1 Game: 1 move 2(wtm)[4]: pid:4 Game: 1 move 2(btm)[5]: pid:5 Game: 1 move 1(btm): pid:1 Game: 1 move 1(btm): pid:1 Game: 1 move 1(btm): pid:1 Game: 1 move 2(wtm)[10]: pid:10 Game: 1 move 2(btm)[11]: pid:11 Game: 1 move 1(wtm): pid:0 Game: 1 move 1(wtm): pid:0
cql(quiet variations alwayscomment nolinearize) initial wtm result 1-0 player black "#2" line --> {message("pid:" positionid) move to c2 comment("Match")} --> {message("pid:" positionid) move to g2 comment("Match")} --> {message("pid:" positionid) move to e2 comment("Match")} --> {message("pid:" positionid) mate}
┌ -- ──────────────── Nb3# ├ R1g2+ Match id:4 ── N1e2# Match id:5 ┌ Kc2! threat Match ┼ Bf5+ id:6 ───────── Ne4# │ ├ bxc3 id:8 ───────── Bxc3# │ └ R5g2+ Match id:10 ─ N3e2# Match id:11 Start ┤ │ ┌ Kb1 ─────────────── Nd2# ├ Ne4+? id:12 ──────┤ │ └ Rxe5! │ ┌ N1e2# Match id:18 └ -- id:16 ────────── R1g2 Match id:17 ─┼ Nd3# id:19 └ N1a2# id:20
Game: 1 move 1(wtm): pid:0 Game: 1 move 1(btm): pid:1 Game: 1 move 2(wtm): pid:2 Game: 1 move 2(wtm)[4]: pid:4 Game: 1 move 2(btm)[5]: pid:5 Game: 1 move 2(wtm)[6]: pid:6 Game: 1 move 2(wtm)[8]: pid:8 Game: 1 move 2(wtm)[10]: pid:10 Game: 1 move 2(btm)[11]: pid:11 Game: 1 move 1(btm)[12]: pid:12 Game: 1 move 1(btm)[16]: pid:16 Game: 1 move 2(wtm)[17]: pid:17 Game: 1 move 2(btm)[18]: pid:18 Game: 1 move 2(btm)[19]: pid:19 Game: 1 move 2(btm)[20]: pid:20
cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" line --> {message("pid:" positionid) move to c2 comment("Match")} --> {message("pid:" positionid) move to g2 comment("Match")} --> {message("pid:" positionid) move to c3 comment("Match")} --> {message("pid:" positionid) mate}
cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" line --> {message("pid:" positionid) move to c2 comment("Match")} --> currentposition:{message("pid:" positionid) move to g2 comment("Match")} --> {message("pid:" positionid) move to c3 comment("Match")} --> {message("pid:" positionid) mate}
Remember that the line filter continues to visit every node until some constituent in the line fails to match a position. With linearization enabled, that failure occurs at the second constituent for three of the five lines of play. Without isolation of each line, the second constituent is a match as the engine evaluates the third constituent for each of its respective positions.
┌ -- ──────────────── Nb3# ├ R1g2+ Match id:4 ── N1e2# ┌ Kc2! threat Match ┼ Bf5+ id:6 ───────── Ne4# │ ├ bxc3 id:8 ───────── Bxc3# Match id:9 │ └ R5g2+ Match id:10 ─ N3e2# Start ┤ │ ┌ Kb1 ─────────────── Nd2# ├ Ne4+? ────────────┤ │ └ Rxe5! │ ┌ N1e2# └ -- ──────────────── R1g2 ─────────────┼ Nd3# └ N1a2#
Game: 1 move 1(wtm): pid:0 Game: 1 move 1(btm): pid:1 Game: 1 move 2(wtm): pid:2 Game: 1 move 2(wtm)[4]: pid:4 Game: 1 move 2(wtm)[6]: pid:6 Game: 1 move 2(wtm)[8]: pid:8 Game: 1 move 2(btm)[9]: pid:9 Game: 1 move 2(wtm)[10]: pid:10 Game: 1 move 1(wtm): pid:0 Game: 1 move 1(wtm): pid:0
cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" line --> currentposition:{message("pid:" positionid) move to c2 comment("Match")} --> currentposition:{message("pid:" positionid) move to e5 comment("Match")} --> {message("pid:" positionid) move to d2 comment("Match")} --> {message("pid:" positionid) mate}
┌ -- ──────────────── Nb3# ├ R1g2+ ───────────── N1e2# ┌ Kc2! threat Match ┼ Bf5+ ────────────── Ne4# │ ├ bxc3 ────────────── Bxc3# │ └ R5g2+ ───────────── N3e2# Start ┤ │ ┌ Kb1 id:13 ───────── Nd2# Match id:14 ├ Ne4+? id:12 ──────┤ │ └ Rxe5! Match id:15 │ ┌ N1e2# └ -- id:16 ────────── R1g2 ─────────────┼ Nd3# └ N1a2#
Game: 1 move 1(wtm): pid:0 Game: 1 move 1(btm): pid:1 Game: 1 move 1(btm)[12]: pid:12 Game: 1 move 2(wtm)[13]: pid:13 Game: 1 move 2(btm)[14]: pid:14 Game: 1 move 2(wtm)[15]: pid:15 Game: 1 move 1(btm)[16]: pid:16
So why would one ever want to disable linearization? There will be times when we wish to know that some specific set of moves emanate from a single position and — though we could almost certainly achieve our ends with linearization in place — it might be simpler without.
cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#2" n == 1 line --> move primary --> find {move to (orthogonal 1 orthogonal 2 n) from n} == 8 line --> move null --> find {move to (orthogonal 1 orthogonal 2 n) from n} == 8
The move filters return the number of knight moves (actually, the set of squares to which the moves are made) that are tallied across lines of play, with linearization disabled for their respective filters due to their presence in the body of a find filter. The nested orthogonal filters yield a set that is slightly outside the range of the knight's legal moves, but with no harm done. (By the way, one could as easily generate the precise set by substituting (move to . from n legal) for the nested orthogonals, but that would be less interesting.)
The query matches a complete block by Antipov (Sa ogneupory - 1987) where the wheeling knight leaves actual play unchanged from the set mates.
┌ Na3 ─── Qd6# ├ Nb2 ─── Qd6# ├ Nd2 ─── Qd6# ├ Ne3 ─── Qd6# ┌ Ka1! block ┤ │ ├ Nxe5 ── d5# │ ├ Nd6 ─── Qxd6# │ ├ Nb6 ─── Qd6# │ └ Na5 ─── Qd6# ├ d5+? ─────── Kxe5! ├ Qd6+? ────── Nxd6! Start ┤ ├ Qd7+? ────── Kxd7! ├ Qe8+? ────── Kd5! │ ┌ Na3 ─── Qd6# │ ├ Nb2 ─── Qd6# │ ├ Nd2 ─── Qd6# │ ├ Ne3 ─── Qd6# └ -- ────────┤ ├ Nxe5 ── d5# ├ Nd6 ─── Qxd6# ├ Nb6 ─── Qd6# └ Na5 ─── Qd6#
cql(quiet variations alwayscomment) initial wtm result 1-0 player black "#3" line --> move primary // the key --> currentposition:{ move promote Q move promote R move promote B move promote N } --> currentposition:{ move promote Q move promote R move promote B move promote N }
Problems exhibiting such counter promotion play are rare, so we should not be surprised that one of the matching compositions belongs to that master of promotion play, Peter Hoffmann (Die Schwalbe - 2014).
The solution tree for this composition is quite large, so we present just a slice of it showing white's response to black's promotion to rook. The full solution may be found in the meson problem database on the SourceForge project site.
│ ┌ Qd5# │ ├ Qxh7# │ ┌ -- ──┤ │ │ ├ Qxg5# │ │ └ Ne7# │ │ ┌ Qxh7# │ ├ Rd1 ─┼ Qxg5# │ │ └ Ne7# │ ├ Bb5+ ─ Qxb5# │ │ ┌ Qxh7# │ ├ Bc4 ─┼ Qxg5# │ │ └ Ne7# │ │ ┌ Qd5# │ ├ Rxc7 ┼ Qxg5# │ │ └ Be4# │ ┌ fxg8=Q threat ┤ ┌ Qd5# │ │ │ ├ Qh7# │ │ ├ h6 ──┤ │ │ │ ├ Qg6# │ │ │ └ Ne7# │ │ │ ┌ Qe6# │ │ │ ├ Qf7# │ │ ├ Nc6 ─┤ │ │ │ ├ Qxh7# │ │ │ └ Qxg5# │ │ │ ┌ Qxe6# │ │ │ ├ Qf7# │ │ ├ Ne6 ─┤ │ │ │ ├ Qxh7# │ │ │ └ Ne7# ├ e1=R ─┤ │ ┌ Qxf7# │ │ └ Nf7 ─┼ Qxh7# │ │ └ Ne7# │ │ ┌ Nge7# │ │ ┌ -- ──┼ Nh6# │ │ │ └ Nce7# │ │ ├ Bb5+ ─ Qxb5# │ ├ fxg8=N threat ┤ ┌ Nh6# │ │ ├ Rxc7 ┤ │ │ │ └ Be4# ┌ Rg4! threat ───┤ │ ├ Nc6 ── Nh6# │ │ │ │ ┌ Nge7# │ │ │ └ Nf7 ─┤ │ │ │ └ Nce7# │ │ │ ┌ R8xg5# │ │ │ ┌ -- ──┤ │ │ │ │ └ Ne7# │ │ │ ├ Bb5+ ─ Qxb5# │ │ │ │ ┌ R8xg5# │ │ │ ├ Rxc7 ┤ │ │ ├ fxg8=R threat ┤ └ Be4# │ │ │ ├ h6 ─── Ne7# │ │ │ ├ Nc6 ── R8xg5# │ │ │ ├ Ne6 ── Ne7# │ │ │ └ Nf7 ── Ne7# │ │ │ ┌ Bxh7# │ │ │ ┌ -- ──┤ │ │ │ │ └ Ne7# │ │ │ ├ Bb5+ ─ Qxb5# │ │ └ fxg8=B threat ┤ │ │ ├ Rxc7 ─ Be4# │ │ │ ┌ Be6# │ │ └ Nc6 ─┤ │ │ └ Bxh7#
This document is Copyright (c) 2019-2024 Lionel Hampton. The Chess Query Language was originally developed by Gady Costeff and Lewis Stiller. The upstream Scid vs PC project is managed by Steven Atkinson.
The diagrams appearing in this document were created with the Scid vs PC application's board screenshot facility. The piece set is included with that application and is credited on the project's site.