Computing a Measure of the Overall Sharpness of a Chess Game Using Stockfish

I’ve been looking at evaluating chess positions using the Stockfish chess engine. I decided to write some code that computes the overall sharpness of a complete chess game.

Assuming the existence of a function that computes the sharpness of a single chess position, I figured it wouldn’t be too difficult to compute an overall measure of a complete game — just add up the sharpness values for all positions. But then I realized it wasn’t so simple. For one thing, the sum of all position sharpness values will increase as the length of a game increases. So, a more reasonable metric is the average sharpness of all the moves in a game.

But a simple average isn’t a great idea for very long games where the sharpness values of positions late in the game are typically very low, which would drag the overall average sharpness down.

So, a better idea is to compute the average sharpness of, say, moves 10 through 30. The idea here is that opening moves are often standard and shouldn’t count. This idea is reasonable but then I decided it might make more sense to define the overall sharpness of a complete chess game as the average of the 20 largest position chess position sharpness values. So, that’s what I implemented.

For a demo, I used a chess game Euwe vs. Colle from 1929. The game in PGN notation is:

[Event "Karlsbad"]
[Site "Karlsbad CSR"]
[Date "1929.08.18"]
[Round "15"]
[White "Max Euwe"]
[Black "Edgar Colle"]
[Result "1-0"]

1.Nf3 Nf6 2.d4 e6 3.c4 b6 4.g3 Bb7 5.Bg2 Bb4+ 6.Bd2 Bxd2+
7.Nbxd2 d6 8.O-O O-O 9.Re1 Nbd7 10.Qc2 e5 11.Nxe5 Bxg2 12.Nxd7
Bh3 13.Nxf8 1-0

So there are a total of 26 individual positions, including the starting position. I used a short game because computing the sharpness of chess positions using Stockfish is quite slow.

My function that computes the sharpness of an individual chess position requires positions in FEN format. I have a tool that converts a game file in PGN notation to a file of FEN strings. See https://jamesmccaffreyblog.com/2024/05/15/programmatically-converting-chess-pgn-to-fen/ for an explanation. The tool code is on GitHub at https://github.com/jdmccaffrey/convert-pgn-to-fen.

The output of my game-sharpness demo looks like:

Begin game sharpness analysis

Stockfish engine parameters:
{'Debug Log File': '', 'Contempt': 0, 'Min Split Depth': 0,
 'Ponder': 'false', 'MultiPV': 1, 'Skill Level': 20,
 'Move Overhead': 10, 'Minimum Thinking Time': 20,
 'Slow Mover': 100, 'UCI_Chess960': 'false',
 'UCI_LimitStrength': 'true', 'UCI_Elo': 2000,
 'Threads': 1, 'Hash': 16}

Computing game sharpness for
.\Data\euwe_colle_karlsbad_1929.fen

rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 | sharpness = 7.4
rnbqkbnr/pppppppp/8/8/8/5N2/PPPPPPPP/RNBQKB1R b KQkq - 1 1 | sharpness = 13.4
rnbqkb1r/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKB1R w KQkq - 2 2 | sharpness = 10.8
rnbqkb1r/pppppppp/5n2/8/3P4/5N2/PPP1PPPP/RNBQKB1R b KQkq d3 0 2 | sharpness = 5.6
rnbqkb1r/pppp1ppp/4pn2/8/3P4/5N2/PPP1PPPP/RNBQKB1R w KQkq - 0 3 | sharpness = 10.4
rnbqkb1r/pppp1ppp/4pn2/8/2PP4/5N2/PP2PPPP/RNBQKB1R b KQkq c3 0 3 | sharpness = 2.6
rnbqkb1r/p1pp1ppp/1p2pn2/8/2PP4/5N2/PP2PPPP/RNBQKB1R w KQkq - 0 4 | sharpness = 23.4
rnbqkb1r/p1pp1ppp/1p2pn2/8/2PP4/5NP1/PP2PP1P/RNBQKB1R b KQkq - 0 4 | sharpness = 8.6
rn1qkb1r/pbpp1ppp/1p2pn2/8/2PP4/5NP1/PP2PP1P/RNBQKB1R w KQkq - 1 5 | sharpness = 27.2
rn1qkb1r/pbpp1ppp/1p2pn2/8/2PP4/5NP1/PP2PPBP/RNBQK2R b KQkq - 2 5 | sharpness = 19.8
rn1qk2r/pbpp1ppp/1p2pn2/8/1bPP4/5NP1/PP2PPBP/RNBQK2R w KQkq - 3 6 | sharpness = 257.8
rn1qk2r/pbpp1ppp/1p2pn2/8/1bPP4/5NP1/PP1BPPBP/RN1QK2R b KQkq - 4 6 | sharpness = 11.8
rn1qk2r/pbpp1ppp/1p2pn2/8/2PP4/5NP1/PP1bPPBP/RN1QK2R w KQkq - 0 7 | sharpness = 469.2
rn1qk2r/pbpp1ppp/1p2pn2/8/2PP4/5NP1/PP1NPPBP/R2QK2R b KQkq - 0 7 | sharpness = 9.8
rn1qk2r/pbp2ppp/1p1ppn2/8/2PP4/5NP1/PP1NPPBP/R2QK2R w KQkq - 0 8 | sharpness = 19.2
rn1qk2r/pbp2ppp/1p1ppn2/8/2PP4/5NP1/PP1NPPBP/R2Q1RK1 b kq - 1 8 | sharpness = 22.8
rn1q1rk1/pbp2ppp/1p1ppn2/8/2PP4/5NP1/PP1NPPBP/R2Q1RK1 w - - 2 9 | sharpness = 8.4
rn1q1rk1/pbp2ppp/1p1ppn2/8/2PP4/5NP1/PP1NPPBP/R2QR1K1 b - - 3 9 | sharpness = 25.8
r2q1rk1/pbpn1ppp/1p1ppn2/8/2PP4/5NP1/PP1NPPBP/R2QR1K1 w - - 4 10 | sharpness = 24.4
r2q1rk1/pbpn1ppp/1p1ppn2/8/2PP4/5NP1/PPQNPPBP/R3R1K1 b - - 5 10 | sharpness = 47.0
r2q1rk1/pbpn1ppp/1p1p1n2/4p3/2PP4/5NP1/PPQNPPBP/R3R1K1 w - - 0 11 | sharpness = 83.4
r2q1rk1/pbpn1ppp/1p1p1n2/4N3/2PP4/6P1/PPQNPPBP/R3R1K1 b - - 0 11 | sharpness = 64.0
r2q1rk1/p1pn1ppp/1p1p1n2/4N3/2PP4/6P1/PPQNPPbP/R3R1K1 w - - 0 12 | sharpness = 847.0
r2q1rk1/p1pN1ppp/1p1p1n2/8/2PP4/6P1/PPQNPPbP/R3R1K1 b - - 0 12 | sharpness = 123.6
r2q1rk1/p1pN1ppp/1p1p1n2/8/2PP4/6Pb/PPQNPP1P/R3R1K1 w - - 1 13 | sharpness = 941.8
r2q1Nk1/p1p2ppp/1p1p1n2/8/2PP4/6Pb/PPQNPP1P/R3R1K1 b - - 0 13 | sharpness = 18.2

Game sharpness = 153.1

End analysis

The top 20 sharpness values are (941.8, 847.0, 469.2, 257.8, 123.6, 83.4, 64.0, 47.0, 27.2, 25.8, 24.4, 23.4, 22.8, 19.8, 19.2, 18.2, 13.4, 11.8, 10.8, 10.4). The average sharpness is (941.8 + 847.0 + . . + 10.4) / 20 = 153.1.

To implement my idea, first I downloaded the open source Stockfish engine (for Windows) from https://stockfishchess.org/download/. The Stockfish engine is like a server — it accepts commands in text format using a protocol called UCI (Universal Chess Interface) and returns results as text. Using the Stockfish engine directly from a command line is very awkward and so hobbyists have created several libraries on GitHub that allow you to use a standard programming language (Python, Java, C#, JavaScript, etc.) to interact with Stockfish.

I used the very slick, but somewhat confusingly named, Python language “stockfish” library. I installed it directly over the Internet by issuing the command “pip install stockfish” on my command line. See https://github.com/zhelyabuzhsky/stockfish.

To summarize, I wrote a Python language program that calls into functions in the stockfish library, which sends UCI commands to the Stockfish engine on my machine.

An interesting exploration. A possible next step will be to compute the average sharpness of games played by different chess players to get an indication of their playing styles.



The designs of chess sets used in world championship matches have varied greatly.

Left: A very Victorian-looking set was used in the Lasker-Schlechter match in 1910. The match was a tie at 5-5 and so Lasker retained his title.

Center: A very Soviet-looking set was used in the Tal-Botvinnik match in 1960. Tal won 12.5 – 8.5 to win the championship.

Right: A modernistic Scandinavian-looking set was used in the Carlsen-Caruana match in 2018. The match was tied 12-12 then Carlsen won in a three-game rapid chess playoff to retain his title.


Demo program. Replace “lt” (less than) and “gt” with Boolean operator symbols (my blog editor chokes on symbols).

# game_sharpness.py
# Anaconda3-2023.09-0  Python 3.11.5
# engine: Stockfish 16.1
# API library: stockfish-3.28.0
# Windows 10/11

from stockfish import Stockfish
import numpy as np

# -----------------------------------------------------------

def compute_sharpness(pos_fen):
  # sharpness of a valid FEN position
  stockfish.set_fen_position(pos_fen)
  curr_eval = stockfish.get_evaluation()
  if curr_eval is None: return 0.0
  curr_val = curr_eval["value"]

  bms = stockfish.get_top_moves(5)
  if bms is None or len(bms) == 0:
    return 0.0

  sum = 0
  for i in range(len(bms)):  # could be less than 5
    if bms[i]["Mate"] is not None:  # forced mate
      print("a forced mate exists")
      if bms[i]["Mate"] "lt" 0:  # black mates white
        poss_val = -400
      elif bms[i]["Mate"] "gt" 0:  # white mates black
        poss_val = 400
    else:
      poss_val = bms[i]["Centipawn"]

    delta = np.abs(curr_val - poss_val)
    if (curr_val "gt" 0 and poss_val "lt" 0) or \
      (curr_val "lt" 0 and poss_val "gt" 0):
      delta *= 2
    sum += delta
  return sum / len(bms)

# -----------------------------------------------------------

def game_sharpness(game_fen_file):
  # average sharpness of top 20 sharpest positions
  sharpness_list = []
  f = open(game_fen_file, "r")
  for fen_position in f:
    fen_position = fen_position.strip()
    pos_sharpness = compute_sharpness(fen_position)
    # print("-----")
    print(fen_position, end="")
    print(" | sharpness = %0.1f" % pos_sharpness)
    sharpness_list.append(pos_sharpness)
  f.close()
  # print(sharpness_list)

  if len(sharpness_list) "lt" 20:  # short game
    n = len(sharpness_list)
  else:
    n = 20

  sum = 0.0
  sharpness_list.sort(reverse=True)  # sort in-place
  print(sharpness_list)
  for i in range(n):
    sum += sharpness_list[i]

  return sum / n

# -----------------------------------------------------------

print("\nBegin game sharpness analysis ")

loc = "C:\\Python\\Stockfish\\" + \
  "stockfish-windows-x86-64\\stockfish\\" + \
  "stockfish-windows-x86-64.exe"
stockfish = Stockfish(path=loc)

stockfish.update_engine_parameters({"UCI_Elo": 2000})
p = stockfish.get_parameters()
print("\nStockfish engine parameters: ")
print(p)

game_fen_file = ".\\Data\\euwe_colle_karlsbad_1929.fen"
# game_fen_file = ".\\Data\\fischer_spassky_belgrade_1992.fen"
# game_fen_file = ".\\Data\\amateur_expert_issaquah_2024.fen"

print("\nComputing game sharpness for ")
print(game_fen_file + "\n")
gs = game_sharpness(game_fen_file)
print("\nGame sharpness = %0.1f " % gs)

print("\nEnd analysis ")
This entry was posted in Programmatic Chess. Bookmark the permalink.

Leave a Reply