PGN Auditor

What does this do?

If you have a collection of games in PGN format, this script will sort each game by how it ended. Unique files will be created for games that ended in Checkmate, Stalemate, 3 Move Repetition, 50 move rule, or insufficient material.

And then....?

You can use this to test your end game logic, for instance 3 move repetition. You can isolate millions of games that end in 3 move repetition and then feed them into your chess engine. If all the games are recognized as 3 move repetition draws by your engine, then you can be confident your end game logic is working similar to PyChess.

How do I use it?

You must have Python and PyChess installed to use this. Copy this script into the PyChess folder and invoke it with the path of the PGN file you want to audit. 5 PGN files will be created to store each type of game ending. The script appends to these files so you can call it over and over on many PGN's to build a collection of endings. The game termination priority for these files is checkmate, stalemate, 50 move rule, 3-fold repetition, insufficient material.

Where do I get random games?

You can try this collection of random games generated by Steven Edwards.

The best way to generate random games is by implementing a command called rgstat in your engine. The rgstat command is a good tool for benchmarking and for debugging end game logic.

The Code.

import sys
from imp import load_module, find_module
pychess = load_module("pychess", *find_module("pychess",["lib"]))
from pychess.Savers import pgn
from pychess.Utils.lutils import lmovegen
from pychess.Utils.lutils import ldraw
from pychess.Utils.const import *

def getStatus (board):
    lboard = board.board
    board_clone = lboard.clone()
    hasMove = False
    for move in lmovegen.genAllMoves (board_clone):
        board_clone.applyMove(move)
        if board_clone.opIsChecked():
            board_clone.popMove()
            continue
        board_clone.popMove()
        hasMove = True
        break 

    if not hasMove:
        if lboard.isChecked():
            if board.color == WHITE:
                status = BLACKWON
            else:
                status = WHITEWON
            return status, WON_MATE
        else:
            return DRAW, DRAW_STALEMATE

    if ldraw.testFifty (lboard):
        return DRAW, DRAW_50MOVES
        
    if ldraw.repetitionCount (lboard) >= 3:
        return DRAW, DRAW_REPITITION

    return RUNNING, UNKNOWN_REASON

if len(sys.argv) != 2:
 sys.exit("must provide pgn file as parameter")

source_file = sys.argv[1]

cntr = 0
gf = open(source_file, 'r')

checkmates = 0
stalemates = 0
fiftymoves = 0
repetitions = 0

f = pgn.load(gf)
for i in range(len(f)):
 m = f.loadToModel(i,-1,None,False)
 m.players = ["-", "-"]
 status, reason = getStatus(m.boards[-1])
 if (reason == WON_MATE):
  checkmates += 1
  filename = 'checkmates.pgn'
 elif (reason == DRAW_STALEMATE):
  stalemates += 1
  filename = 'stalemates.pgn'
 elif (reason == DRAW_50MOVES):
  fiftymoves += 1
  filename = 'fiftymove.pgn'
 elif (reason == DRAW_REPITITION):
  repetitions += 1
  filename = 'repetition.pgn'
 else:
  filename = 'unknown_results.pgn'

 if (reason == DRAW_CALLFLAG):
  print "DRAW_CALLFLAG"
 if (reason == DRAW_INSUFFICIENT):
  print "Insufficient Material"
 if (reason == DRAW_LENGTH):
  print "DRAW LENGTH"

 savefile = open(filename, 'a')
 print >> savefile
 pgn.save(savefile, m)

 print str(i) + ": " + str(checkmates) + " checkmates, " + str(stalemates) + " stalemates, " + str(fiftymoves) + " fifty moves, " + str(repetitions) + " repetitions, status: " + str(status) + " reson: " + str(reason)