Explaining Chess and Go in Natural Language

tl;dr: We compare two moves, then explain in natural language why the stronger move is better.

The Goal: explain chess engine moves in natural language

Chess engines look at millions of positions and evaluate each one with a neural network. We want to explain why an engine likes one move better than another in simple language.

When playing chess, both humans and engines do two things:

  1. Look a few moves ahead
  2. Compare the resulting positions and choose the best move

The starting position

Let’s go through an example: you’re playing Black. Compare two moves from this position

Two possible continuations

If you take with the pawn:Pawn takes:

If you take with the knight:Knight takes:

Possible replies:

Your opponent’s best reply in each case:

How do we decide which position is better?
Let’s compare the material.

Pawn takes:

Black (you)

White

Black has more material.

Knight takes:

Black (you)

White

Material is equal.

Explanation:

Taking with the pawn leads to a material advantage. Taking with the knight leaves material equal.

We use chess concepts (motifs) to explain positions.

Motifs are patterns, like material advantage, that appear more often in winning positions than in losing ones.

For example, in our dataset, a material advantage appears in 61.5% of winning positions, but only 5.5% of losing ones.

Our method, BinProp, automatically discovers motifs by asking language models to come up with them, over and over again. See BinProp in action:

BinProp (our method)
System

Propose a general motif that appears more often in winning positions than in losing ones.

If you need some help, here are a few sampled winning and losing positions.

Winning positions

8
7
6
5
4
3
2
1a
b
c
d
e
f
g
h
1
2
3
4
5
6
7
8h
g
f
e
d
c
b
a

Losing positions

8
7
6
5
4
3
2
1a
b
c
d
e
f
g
h
8
7
6
5
4
3
2
1a
b
c
d
e
f
g
h
Language Model

In both winning positions, the player to move has two bishops against at most one.

import chess

def bishop_pair(board):
    us = board.turn
    ours = len(board.pieces(chess.BISHOP, us))
    theirs = len(board.pieces(chess.BISHOP, not us))
    return ours == 2 and theirs <= 1
System

Motif accepted

Across the full sample: 17.76% of winning positions and 15.15% of losing positions.

Current motifs

  1. Bishop-pair advantage

Propose another motif. Here are some more sampled positions.

Winning positions

8
7
6
5
4
3
2
1a
b
c
d
e
f
g
h
1
2
3
4
5
6
7
8h
g
f
e
d
c
b
a

Losing positions

8
7
6
5
4
3
2
1a
b
c
d
e
f
g
h
1
2
3
4
5
6
7
8h
g
f
e
d
c
b
a
Language Model

Another idea is a passed pawn: a pawn with no enemy pawn ahead on its own file or either neighboring file.

import chess

def passed_pawn(board):
    us = board.turn
    forward = 1 if us == chess.WHITE else -1
    enemies = board.pieces(chess.PAWN, not us)
    for pawn in board.pieces(chess.PAWN, us):
        file = chess.square_file(pawn)
        rank = chess.square_rank(pawn)
        if not any(
            abs(chess.square_file(p) - file) <= 1
            and (chess.square_rank(p) - rank) * forward > 0
            for p in enemies
        ):
            return True
    return False
System

Motif accepted

Across the full sample: 15.62% of winning positions and 9.32% of losing positions.

Current motifs

  1. Bishop-pair advantage
  2. Passed pawn

Propose another motif. Here are some more sampled positions.

Winning positions

8
7
6
5
4
3
2
1a
b
c
d
e
f
g
h
1
2
3
4
5
6
7
8h
g
f
e
d
c
b
a

Losing positions

8
7
6
5
4
3
2
1a
b
c
d
e
f
g
h
1
2
3
4
5
6
7
8h
g
f
e
d
c
b
a
Language Model

What about doubled pawns: at least two of the player’s pawns on the same file?

import chess

def doubled_pawns(board):
    pawns = board.pieces(chess.PAWN, board.turn)
    files = [chess.square_file(pawn) for pawn in pawns]
    return len(files) > len(set(files))
System

Motif rejected

Across the full sample: 19.56% of winning positions and 19.81% of losing positions.

It appears more often in losing positions, so we don’t keep it.

Current motifs

  1. Bishop-pair advantage
  2. Passed pawn

We now have a list of motifs. How do we explain a move?

We found two motifs: bishop-pair advantage and passed pawn. Now we check which appears after the engine’s preferred move, but not after the alternative.

Black to move: Re2 or Nd6?
Best move: Re2After 8 moves by each side
25… Re2 26. Re1 Nh4 27. Ne3 Nf3+ 28. gxf3 Qg5+ 29. Qg2 Qxg2+ 30. Kxg2 Rxd2 31. Nd5 Rc2 32. Rd1 d2 33. f4
  • Bishop-pair advantage
  • Passed pawn
Alternative: Nd6After 8 moves by each side
25… Nd6 26. Ne3 Qxb3 27. Qxd3 Ne4 28. Be1 Kh7 29. a5 c4 30. Qxc4 Qxc4 31. Nxc4 Nxc3 32. Kf1 Re4 33. Ne3
  • Bishop-pair advantage
  • Passed pawn

Explanation:

The best move, Re2, keeps Black’s passed pawn. After the alternative, Nd6, that passed pawn is lost.

Let’s try a real example

Carlsen–Anand · 25 May 2019
Best move: c4After 3 moves by each side
19… c4 20. Bf2 Ne5 21. Bg1 Nd3 22. Rb1
  • Knight outpost
  • Rook activity
  • Piece activity
Played move: d4After 3 moves by each side
19… d4 20. Na4 Qe7 21. Nb6 Rc7 22. e4
  • Knight outpost
  • Rook activity
  • Piece activity

Explanation:

Knight outpost
Black's knight has an outpost on d3, where White's pawns cannot chase it.
Rook activity
Black's rook has a semi-open file; White's rooks sit behind their pawns.
Piece activity
Black's pieces average 4.1 reachable squares vs White's 2.3, excluding enemy pawn attacks.