chess package for go

chess

GoDoc Coverage Status Go Report Card License

Introduction

chess is a set of go packages which provide common chess utilities such as move generation, turn management, checkmate detection, PGN encoding, UCI interoperability, image generation, opening book exploration, and others. It is well tested and optimized for performance.

rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1

Repo Structure

Package Docs Link Description
chess notnil/chess Move generation, serialization / deserialization, turn management, checkmate detection
image notnil/chess/image SVG chess board image generation
opening notnil/chess/opening Opening book interactivity
uci notnil/chess/uci Universal Chess Interface client

Installation

chess can be installed using "go get".

go get -u github.com/notnil/chess

Usage

Example Random Game

package main

import (
	"fmt"
	"math/rand"

	"github.com/notnil/chess"
)

func main() {
	game := chess.NewGame()
	// generate moves until game is over
	for game.Outcome() == chess.NoOutcome {
		// select a random move
		moves := game.ValidMoves()
		move := moves[rand.Intn(len(moves))]
		game.Move(move)
	}
	// print outcome and game PGN
	fmt.Println(game.Position().Board().Draw())
	fmt.Printf("Game completed. %s by %s.\n", game.Outcome(), game.Method())
	fmt.Println(game.String())
	/*
		Output:

		 A B C D E F G H
		8- - - - - - - -
		7- - - - - - ♚ -
		6- - - - ♗ - - -
		5- - - - - - - -
		4- - - - - - - -
		3♔ - - - - - - -
		2- - - - - - - -
		1- - - - - - - -

		Game completed. 1/2-1/2 by InsufficientMaterial.

		1.Nc3 b6 2.a4 e6 3.d4 Bb7 ...
	*/
}

Example Stockfish v. Stockfish

package main

import (
	"fmt"
	"time"

	"github.com/notnil/chess"
	"github.com/notnil/chess/uci"
)

func main() {
	// set up engine to use stockfish exe
	eng, err := uci.New("stockfish")
	if err != nil {
		panic(err)
	}
	defer eng.Close()
	// initialize uci with new game
	if err := eng.Run(uci.CmdUCI, uci.CmdIsReady, uci.CmdUCINewGame); err != nil {
		panic(err)
	}
	// have stockfish play speed chess against itself (10 msec per move)
	game := chess.NewGame()
	for game.Outcome() == chess.NoOutcome {
		cmdPos := uci.CmdPosition{Position: game.Position()}
		cmdGo := uci.CmdGo{MoveTime: time.Second / 100}
		if err := eng.Run(cmdPos, cmdGo); err != nil {
			panic(err)
		}
		move := eng.SearchResults().BestMove
		if err := game.Move(move); err != nil {
			panic(err)
		}
	}
	fmt.Println(game.String())
	// Output: 
	// 1.c4 c5 2.Nf3 e6 3.Nc3 Nc6 4.d4 cxd4 5.Nxd4 Nf6 6.a3 d5 7.cxd5 exd5 8.Bf4 Bc5 9.Ndb5 O-O 10.Nc7 d4 11.Na4 Be7 12.Nxa8 Bf5 13.g3 Qd5 14.f3 Rxa8 15.Bg2 Rd8 16.b4 Qe6 17.Nc5 Bxc5 18.bxc5 Nd5 19.O-O Nc3 20.Qd2 Nxe2+ 21.Kh1 d3 22.Bd6 Qd7 23.Rab1 h6 24.a4 Re8 25.g4 Bg6 26.a5 Ncd4 27.Qb4 Qe6 28.Qxb7 Nc2 29.Qxa7 Ne3 30.Rb8 Nxf1 31.Qb6 d2 32.Rxe8+ Qxe8 33.Qb3 Ne3 34.h3 Bc2 35.Qxc2 Nxc2 36.Kh2 d1=Q 37.h4 Qg1+ 38.Kh3 Ne1 39.h5 Qxg2+ 40.Kh4 Nxf3#  0-1
}

Movement

Chess exposes two ways of moving: valid move generation and notation parsing. Valid moves are calculated from the current position and are returned from the ValidMoves method. Even if the client isn't a go program (e.g. a web app) the list of moves can be serialized into their string representation and supplied to the client. Once a move is selected the MoveStr method can be used to parse the selected move's string.

Valid Moves

Valid moves generated from the game's current position:

game := chess.NewGame()
moves := game.ValidMoves()
game.Move(moves[0])
fmt.Println(moves[0]) // b1a3

Parse Notation

Game's MoveStr method accepts string input using the default Algebraic Notation:

game := chess.NewGame()
if err := game.MoveStr("e4"); err != nil {
	// handle error
}

Outcome

The outcome of the match is calculated automatically from the inputted moves if possible. Draw agreements, resignations, and other human initiated outcomes can be inputted as well.

Checkmate

Black wins by checkmate (Fool's Mate):

game := chess.NewGame()
game.MoveStr("f3")
game.MoveStr("e6")
game.MoveStr("g4")
game.MoveStr("Qh4")
fmt.Println(game.Outcome()) // 0-1
fmt.Println(game.Method()) // Checkmate
/*
 A B C D E F G H
8♜ ♞ ♝ - ♚ ♝ ♞ ♜
7♟ ♟ ♟ ♟ - ♟ ♟ ♟
6- - - - ♟ - - -
5- - - - - - - -
4- - - - - - ♙ ♛
3- - - - - ♙ - -
2♙ ♙ ♙ ♙ ♙ - - ♙
1♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
*/

Stalemate

Black king has no safe move:

fenStr := "k1K5/8/8/8/8/8/8/1Q6 w - - 0 1"
fen, _ := chess.FEN(fenStr)
game := chess.NewGame(fen)
game.MoveStr("Qb6")
fmt.Println(game.Outcome()) // 1/2-1/2
fmt.Println(game.Method())  // Stalemate
/*
 A B C D E F G H
8♚ - ♔ - - - - -
7- - - - - - - -
6- ♕ - - - - - -
5- - - - - - - -
4- - - - - - - -
3- - - - - - - -
2- - - - - - - -
1- - - - - - - -
*/

Resignation

Black resigns and white wins:

game := chess.NewGame()
game.MoveStr("f3")
game.Resign(chess.Black)
fmt.Println(game.Outcome()) // 1-0
fmt.Println(game.Method()) // Resignation

Draw Offer

Draw by mutual agreement:

game := chess.NewGame()
game.Draw(chess.DrawOffer)
fmt.Println(game.Outcome()) // 1/2-1/2
fmt.Println(game.Method())  // DrawOffer

Threefold Repetition

Threefold repetition occurs when the position repeats three times (not necessarily in a row). If this occurs both players have the option of taking a draw, but aren't required until Fivefold Repetition.

game := chess.NewGame()
moves := []string{"Nf3", "Nf6", "Ng1", "Ng8", "Nf3", "Nf6", "Ng1", "Ng8"}
for _, m := range moves {
	game.MoveStr(m)
}
fmt.Println(game.EligibleDraws()) //  [DrawOffer ThreefoldRepetition]

Fivefold Repetition

According to the FIDE Laws of Chess if a position repeats five times then the game is drawn automatically.

game := chess.NewGame()
moves := []string{
	"Nf3", "Nf6", "Ng1", "Ng8",
	"Nf3", "Nf6", "Ng1", "Ng8",
	"Nf3", "Nf6", "Ng1", "Ng8",
	"Nf3", "Nf6", "Ng1", "Ng8",
}
for _, m := range moves {
	game.MoveStr(m)
}
fmt.Println(game.Outcome()) // 1/2-1/2
fmt.Println(game.Method()) // FivefoldRepetition

Fifty Move Rule

Fifty-move rule allows either player to claim a draw if no capture has been made and no pawn has been moved in the last fifty moves.

fen, _ := chess.FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 100 23")
game := chess.NewGame(fen)
game.Draw(chess.FiftyMoveRule)
fmt.Println(game.Outcome()) // 1/2-1/2
fmt.Println(game.Method()) // FiftyMoveRule

Seventy Five Move Rule

According to FIDE Laws of Chess Rule 9.6b if 75 consecutive moves have been made without movement of any pawn or any capture, the game is drawn unless the last move was checkmate.

fen, _ := chess.FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 149 23")
game := chess.NewGame(fen)
game.MoveStr("Kf8")
fmt.Println(game.Outcome()) // 1/2-1/2
fmt.Println(game.Method()) // SeventyFiveMoveRule

Insufficient Material

Impossibility of checkmate, or insufficient material, results when neither white or black has the pieces remaining to checkmate the opponent.

fen, _ := chess.FEN("8/2k5/8/8/8/3K4/8/8 w - - 1 1")
game := chess.NewGame(fen)
fmt.Println(game.Outcome()) // 1/2-1/2
fmt.Println(game.Method()) // InsufficientMaterial

PGN

PGN, or Portable Game Notation, is the most common serialization format for chess matches. PGNs include move history and metadata about the match. Chess includes the ability to read and write the PGN format.

Example PGN

[Event "F/S Return Match"]
[Site "Belgrade, Serbia JUG"]
[Date "1992.11.04"]
[Round "29"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]

1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 {This opening is called the Ruy Lopez.}
4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3 O-O 9. h3 Nb8 10. d4 Nbd7
11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 b4 15. Nb1 h6 16. Bh4 c5 17. dxe5
Nxe4 18. Bxe7 Qxe7 19. exd6 Qf6 20. Nbd2 Nxd6 21. Nc4 Nxc4 22. Bxc4 Nb6
23. Ne5 Rae8 24. Bxf7+ Rxf7 25. Nxf7 Rxe1+ 26. Qxe1 Kxf7 27. Qe3 Qg5 28. Qxg5
hxg5 29. b3 Ke6 30. a3 Kd6 31. axb4 cxb4 32. Ra5 Nd5 33. f3 Bc8 34. Kf2 Bf5
35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5 40. Rd6 Kc5 41. Ra6
Nf2 42. g4 Bd3 43. Re6 1/2-1/2

Read PGN

PGN supplied as an optional parameter to the NewGame constructor:

pgn, err := chess.PGN(pgnReader)
if err != nil {
	// handle error
}
game := chess.NewGame(pgn)

Write PGN

Moves and tag pairs added to the PGN output:

game := chess.NewGame()
game.AddTagPair("Event", "F/S Return Match")
game.MoveStr("e4")
game.MoveStr("e5")
fmt.Println(game)
/*
[Event "F/S Return Match"]

1.e4 e5  *
*/

Scan PGN

For parsing large PGN database files use Scanner:

f, err := os.Open("lichess_db_standard_rated_2013-01.pgn")
if err != nil {
	panic(err)
}
defer f.Close()

scanner := chess.NewScanner(f)
for scanner.Scan() {
	game := scanner.Next()
	fmt.Println(game.GetTagPair("Site"))
	// Output &{Site https://lichess.org/8jb5kiqw}
}

FEN

FEN, or Forsyth–Edwards Notation, is the standard notation for describing a board position. FENs include piece positions, turn, castle rights, en passant square, half move counter (for 50 move rule), and full move counter.

Read FEN

FEN supplied as an optional parameter to the NewGame constructor:

fen, err := chess.FEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
if err != nil {
	// handle error
}
game := chess.NewGame(fen)

Write FEN

Game's current position outputted in FEN notation:

game := chess.NewGame()
pos := game.Position()
fmt.Println(pos.String()) // rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

Notations

Chess Notation define how moves are encoded in a serialized format. Chess uses a notation when converting to and from PGN and for accepting move text.

Algebraic Notation

Algebraic Notation (or Standard Algebraic Notation) is the official chess notation used by FIDE. Examples: e2, e5, O-O (short castling), e8=Q (promotion)

game := chess.NewGame(chess.UseNotation(chess.AlgebraicNotation{}))
game.MoveStr("e4")
game.MoveStr("e5")
fmt.Println(game) // 1.e4 e5  *

Long Algebraic Notation

Long Algebraic Notation LongAlgebraicNotation is a more beginner friendly alternative to algebraic notation, where the origin of the piece is visible as well as the destination. Examples: Rd1xd8+, Ng8f6.

game := chess.NewGame(chess.UseNotation(chess.LongAlgebraicNotation{}))
game.MoveStr("f2f3")
game.MoveStr("e7e5")
game.MoveStr("g2g4")
game.MoveStr("Qd8h4")
fmt.Println(game) // 1.f2f3 e7e5 2.g2g4 Qd8h4#  0-1

UCI Notation

UCI notation is a more computer friendly alternative to algebraic notation. This notation is the Universal Chess Interface notation. Examples: e2e4, e7e5, e1g1 (white short castling), e7e8q (for promotion)

game := chess.NewGame(chess.UseNotation(chess.UCINotation{}))
game.MoveStr("e2e4")
game.MoveStr("e7e5")
fmt.Println(game) // 1.e2e4 e7e5  *

Text Representation

Board's Draw() method can be used to visualize a position using unicode chess symbols.

game := chess.NewGame()
fmt.Println(game.Position().Board().Draw())
/*
 A B C D E F G H
8♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
7♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
6- - - - - - - -
5- - - - - - - -
4- - - - - - - -
3- - - - - - - -
2♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
1♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
*/

Performance

Chess has been performance tuned, using pprof, with the goal of being fast enough for use by chess bots. The original map based board representation was replaced by bitboards resulting in a large performance increase.

Benchmarks

The benchmarks can be run with the following command:

go test -bench=.

Results from the baseline 2015 MBP:

BenchmarkBitboardReverse-4              2000000000               1.01 ns/op
BenchmarkStalemateStatus-4                500000              3116 ns/op
BenchmarkInvalidStalemateStatus-4         500000              2290 ns/op
BenchmarkPositionHash-4                  1000000              1864 ns/op
BenchmarkValidMoves-4                     100000             13445 ns/op
BenchmarkPGN-4                               300           5549192 ns/op
Owner
Comments
  • Pertf tests

    Pertf tests

    I have added a test function to compare your ValidMoves function against the Perft Results obtained from https://www.chessprogramming.org/Perft_Results.

    > go test
    --- FAIL: TestPerfResults (0.04s)
        move_test.go:266: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
        move_test.go:267:
             A B C D E F G H
            8♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜
            7♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟
            6- - - - - - - -
            5- - - - - - - -
            4- - - - - - - -
            3- - - - - - - -
            2♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙
            1♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
    
        move_test.go:268: Depth: 3 Expected: 8902 Got: 8000
        move_test.go:266: r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1
        move_test.go:267:
             A B C D E F G H
            8♜ - - - ♚ - - ♜
            7♟ - ♟ ♟ ♛ ♟ ♝ -
            6♝ ♞ - - ♟ ♞ ♟ -
            5- - - ♙ ♘ - - -
            4- ♟ - - ♙ - - -
            3- - ♘ - - ♕ - ♟
            2♙ ♙ ♙ ♗ ♗ ♙ ♙ ♙
            1♖ - - - ♔ - - ♖
    
        move_test.go:268: Depth: 2 Expected: 2039 Got: 2304
        move_test.go:266: 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1
        move_test.go:267:
             A B C D E F G H
            8- - - - - - - -
            7- - ♟ - - - - -
            6- - - ♟ - - - -
            5♔ ♙ - - - - - ♜
            4- ♖ - - - ♟ - ♚
            3- - - - - - - -
            2- - - - ♙ - ♙ -
            1- - - - - - - -
    
        move_test.go:268: Depth: 2 Expected: 191 Got: 196
        move_test.go:266: 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1
        move_test.go:267:
             A B C D E F G H
            8- - - - - - - -
            7- - ♟ - - - - -
            6- - - ♟ - - - -
            5♔ ♙ - - - - - ♜
            4- ♖ - - - ♟ - ♚
            3- - - - - - - -
            2- - - - ♙ - ♙ -
            1- - - - - - - -
    
        move_test.go:268: Depth: 3 Expected: 2812 Got: 2744
        move_test.go:266: r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1
        move_test.go:267:
             A B C D E F G H
            8♜ - - - ♚ - - ♜
            7♙ ♟ ♟ ♟ - ♟ ♟ ♟
            6- ♝ - - - ♞ ♝ ♘
            5♞ ♙ - - - - - -
            4♗ ♗ ♙ - ♙ - - -
            3♛ - - - - ♘ - -
            2♙ ♟ - ♙ - - ♙ ♙
            1♖ - - ♕ - ♖ ♔ -
    
        move_test.go:268: Depth: 2 Expected: 264 Got: 36
        move_test.go:266: r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1
        move_test.go:267:
             A B C D E F G H
            8♜ - - - ♚ - - ♜
            7♙ ♟ ♟ ♟ - ♟ ♟ ♟
            6- ♝ - - - ♞ ♝ ♘
            5♞ ♙ - - - - - -
            4♗ ♗ ♙ - ♙ - - -
            3♛ - - - - ♘ - -
            2♙ ♟ - ♙ - - ♙ ♙
            1♖ - - ♕ - ♖ ♔ -
    
        move_test.go:268: Depth: 3 Expected: 9467 Got: 216
        move_test.go:266: r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1
        move_test.go:267:
             A B C D E F G H
            8♜ - - ♛ - ♜ ♚ -
            7♟ ♙ - ♟ - - ♟ ♟
            6♕ - - - - ♞ - -
            5♝ ♝ ♟ - ♟ - - -
            4♘ ♟ - - - - - -
            3- ♗ - - - ♘ ♗ ♞
            2♟ ♙ ♙ ♙ - ♙ ♙ ♙
            1♖ - - - ♔ - - ♖
    
        move_test.go:268: Depth: 2 Expected: 264 Got: 36
        move_test.go:266: r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1
        move_test.go:267:
             A B C D E F G H
            8♜ - - ♛ - ♜ ♚ -
            7♟ ♙ - ♟ - - ♟ ♟
            6♕ - - - - ♞ - -
            5♝ ♝ ♟ - ♟ - - -
            4♘ ♟ - - - - - -
            3- ♗ - - - ♘ ♗ ♞
            2♟ ♙ ♙ ♙ - ♙ ♙ ♙
            1♖ - - - ♔ - - ♖
    
        move_test.go:268: Depth: 3 Expected: 9467 Got: 216
        move_test.go:266: rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8
        move_test.go:267:
             A B C D E F G H
            8♜ ♞ ♝ ♛ - ♚ - ♜
            7♟ ♟ - ♙ ♝ ♟ ♟ ♟
            6- - ♟ - - - - -
            5- - - - - - - -
            4- - ♗ - - - - -
            3- - - - - - - -
            2♙ ♙ ♙ - ♘ ♞ ♙ ♙
            1♖ ♘ ♗ ♕ ♔ - - ♖
    
        move_test.go:268: Depth: 2 Expected: 1486 Got: 1936
        move_test.go:266: r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10
        move_test.go:267:
             A B C D E F G H
            8♜ - - - - ♜ ♚ -
            7- ♟ ♟ - ♛ ♟ ♟ ♟
            6♟ - ♞ ♟ - ♞ - -
            5- - ♝ - ♟ - ♗ -
            4- - ♗ - ♙ - ♝ -
            3♙ - ♘ ♙ - ♘ - -
            2- ♙ ♙ - ♕ ♙ ♙ ♙
            1♖ - - - - ♖ ♔ -
    
        move_test.go:268: Depth: 2 Expected: 2079 Got: 2116
    FAIL
    exit status 1
    FAIL	github.com/Kerrigan29a/chess	0.285s
    
  • PGN decode error: could not decode algebraic notation Rd1d2

    PGN decode error: could not decode algebraic notation Rd1d2

    I am parsing the 2013-01 database file from Lichess (15Mb download), and encountered the following error:

    chess: pgn decode error chess: could not decode algebraic notation Rd1d2 for position 3r1rk1/1p1bqp2/p1pR1p1p/8/4P3/P4B2/1PP1QPP1/3R3K w - - 2 22 on move 22

    FEN on Lichess

    The typical short notation would be R1d2, which is acceptable without error, but the long form error not accepted.

    How to Reproduce

    PGN:

    [Event "Rated Blitz game"]
    [Site "https://lichess.org/9opx3qh7"]
    [White "adamsrj"]
    [Black "hamiakaz"]
    [Result "0-1"]
    [UTCDate "2012.12.31"]
    [UTCTime "23:02:48"]
    [WhiteElo "1522"]
    [BlackElo "1428"]
    [WhiteRatingDiff "-14"]
    [BlackRatingDiff "+14"]
    [ECO "A40"]
    [Opening "Englund Gambit Complex: Hartlaub-Charlick Gambit"]
    [TimeControl "180+5"]
    [Termination "Normal"]
    
    1. d4 e5 2. dxe5 d6 3. exd6 Bxd6 4. Nf3 Nf6 5. Nc3 O-O 6. a3 Nc6 7. e3 a6 8. Be2 h6 9. O-O Ne5 10. Bd2 Nxf3+ 11. Bxf3 Be5 12. Rc1 c6 13. Qe2 Qd6 14. Rfd1 Bxh2+ 15. Kh1 Be5 16. e4 Bxc3 17. Bxc3 Qe6 18. Rd3 Bd7 19. Rcd1 Rad8 20. Bxf6 gxf6 21. Rd6 Qe7 22. Rd1d2 Be6 23. Rxd8 Rxd8 24. Rxd8+ Qxd8 25. c4 Qd4 26. c5 Qxc5 27. Qd2 f5 28. exf5 Bxf5 29. Qxh6 Bg6 30. Be4 Bxe4 31. Qh4 Bg6 32. Qd8+ Kg7 33. Qc7 b5 34. b4 Qc1+ 35. Kh2 Qxa3 36. Qe5+ Kg8 37. Qe8+ Kg7 38. Qxc6 Qxb4 39. Qxa6 Qh4+ 40. Kg1 b4 41. Qa1+ Qf6 42. Qa4 Qc3 43. f3 b3 44. Qa3 Qc2 45. Kh2 b2 0-1
    

    I've confirmed the test failed by adding this line to assets/valid_notation_tests.json :

    {
        "pos1" : "3r1rk1/1p1bqp2/p1pR1p1p/8/4P3/P4B2/1PP1QPP1/3R3K w - - 2 22",
        "pos2" : "3r1rk1/1p1bqp2/p1pR1p1p/8/4P3/P4B2/1PPRQPP1/7K b - - 3 22",
        "algText" : "R1d2",
        "longAlgText" : "Rd1d2",
        "description" : "https://lichess.org/editor/3r1rk1/1p1bqp2/p1pR1p1p/8/4P3/P4B2/1PP1QPP1/3R3K_w_-_-_2_22"
    }    
    

    Test Output:

    --- FAIL: TestValidDecoding (0.01s)
        --- FAIL: TestValidDecoding/https://lichess.org/editor/3r1rk1/1p1bqp2/p1pR1p1p/8/4P3/P4B2/1PPRQPP1/7K_w_-_-_2_22 (0.00s)
            notation_test.go:45: starting from board
    
                 A B C D E F G H
                8- - - ♜ - ♜ ♚ -
                7- ♟ - ♝ ♛ ♟ - -
                6♟ - ♟ ♖ - ♟ - ♟
                5- - - - - - - -
                4- - - - ♙ - - -
                3♙ - - - - ♗ - -
                2- ♙ ♙ - ♕ ♙ ♙ -
                1- - - ♖ - - - ♔
    
                 expected move to be valid error - chess: failed to decode long algebraic notation text "Rd1d2" for position 3r1rk1/1p1bqp2/p1pR1p1p/8/4P3/P4B2/1PP1QPP1/3R3K w - - 2 22 h1g1,h1h2,e2e1,e2f1,e2d2,e2d3,e2e3,e2c4,e2b5,e2a6,d1a1,d1b1,d1c1,d1e1,d1f1,d1g1,d1d2,d1d3,d1d4,d1d5,d6d2,d6d3,d6d4,d6d5,d6c6,d6e6,d6f6,d6d7,f3g4,f3h5,b2b3,b2b4,c2c3,c2c4,g2g3,g2g4,a3a4,e4e5
    FAIL
    exit status 1
    FAIL    github.com/notnil/chess 2.767s
    
  • Which Piece has moved?

    Which Piece has moved?

    I am trying to find the first queen move in a game. Looping over the Move slice returned by game.Moves() seemed like a good idea, but I don't find a way to correlate the move to the piece being on the start square before or the target square after it was executed. Is there a good way to do this using the Positions data?

    Thank you for any pointer to the right direction here :)

  • Parser Error? (N5f6)

    Parser Error? (N5f6)

    I am trying to parse this World Championship game

    [Event "WCh"]
    [Site "Elista RUS"]
    [Date "2006.10.05"]
    [Round "8"]
    [White "Kramnik,V"]
    [Black "Topalov,V"]
    [Result "0-1"]
    [WhiteElo "2743"]
    [BlackElo "2813"]
    [EventDate "2006.09.23"]
    [ECO "D47"]
    
    1. d4 d5 2. c4 c6 3. Nf3 Nf6 4. Nc3 e6 5. e3 Nbd7 6. Bd3 dxc4 7. Bxc4 b5 8.
    Be2 Bb7 9. O-O b4 10. Na4 c5 11. dxc5 Nxc5 12. Bb5+ Ncd7 13. Ne5 Qc7 14.
    Qd4 Rd8 15. Bd2 Qa5 16. Bc6 Be7 17. Rfc1 Bxc6 18. Nxc6 Qxa4 19. Nxd8 Bxd8
    20. Qxb4 Qxb4 21. Bxb4 Nd5 22. Bd6 f5 23. Rc8 N5b6 24. Rc6 Be7 25. Rd1 Kf7
    26. Rc7 Ra8 27. Rb7 Ke8 28. Bxe7 Kxe7 29. Rc1 a5 30. Rc6 Nd5 31. h4 h6 32.
    a4 g5 33. hxg5 hxg5 34. Kf1 g4 35. Ke2 N5f6 36. b3 Ne8 37. f3 g3 38. Rc1
    Nef6 39. f4 Kd6 40. Kf3 Nd5 41. Kxg3 Nc5 42. Rg7 Rb8 43. Ra7 Rg8+ 44. Kf3
    Ne4 45. Ra6+ Ke7 46. Rxa5 Rg3+ 47. Ke2 Rxe3+ 48. Kf1 Rxb3 49. Ra7+ Kf6 50.
    Ra8 Nxf4 51. Ra1 Rb2 52. a5 Rf2+ 0-1
    

    But it results in this:

    chess: pgn decode error chess: failed to decode notation text "N5f6" for position r7/1R1nk3/2R1p3/p2n1p2/P5p1/4P3/1P2KPP1/8 b - - 1 35 on move 35

    I assume it might be a problem because of that slightly uncommon case where the 5 is required to distinguish the knights on the same file when both could jump to f6.

    Another World Championship example that fails to parse:

    chess: pgn decode error chess: failed to decode notation text "Ndxb5" for position rn2k2r/pp3ppp/4pB2/qb6/1b1NP3/2N5/PP3PPP/R2QK2R w KQkq - 0 11 on move 11
    
  • Optimize some loops with math/bits package

    Optimize some loops with math/bits package

    Benchmarks master:

    go test -bench ValidMoves -benchmem
    goos: windows
    goarch: amd64
    pkg: github.com/notnil/chess
    BenchmarkValidMoves-12            200000              8303 ns/op            5304 B/op         69 allocs/op
    BenchmarkValidMoves2-12           100000             18452 ns/op           12112 B/op        152 allocs/op
    PASS
    ok      github.com/notnil/chess 6.165s
    

    This branch:

    go test -bench ValidMoves -benchmem
    goos: windows
    goarch: amd64
    pkg: github.com/notnil/chess
    BenchmarkValidMoves-12            200000              7761 ns/op            5320 B/op         85 allocs/op
    BenchmarkValidMoves2-12           100000             17485 ns/op           12160 B/op        170 allocs/op
    PASS
    ok      github.com/notnil/chess 6.073s
    

    It isn't as dramatic as I had hoped, but eliminates looping over every square potentially multiple times per piece. Will continue benchmarking and profiling for further gains.

    EDIT: With improved bitboard reverse:

    go test -bench=ValidMoves -benchmem
    goos: windows
    goarch: amd64
    pkg: github.com/notnil/chess
    BenchmarkValidMoves-12            200000              6992 ns/op            5320 B/op         85 allocs/op
    BenchmarkValidMoves2-12           100000             16368 ns/op           12160 B/op        170 allocs/op
    PASS
    ok      github.com/notnil/chess 5.382s
    
  • notation: remove extraneous disambiguators when decoding (L)AR

    notation: remove extraneous disambiguators when decoding (L)AR

    Closes #82

    This PR adds support for removing unnecessary move disambiguators when decoding a move.

    It also makes some refactors to use regexp, and use the same decode logic for long algebraic notation and algebraic notation.

  • PGN's Reader?

    PGN's Reader?

    `

    func main() {

        pgn, err := chess.PGN(strings.NewReader("\n1. d4 d5 2. Bf4 Nc6 3. e3 Qd6 4. Bxd6 exd6 5. Nc3 Nf6 6. Qf3 Bg4 7. Qf4 O-O-O 8. Nb1 Nb4 9. c3 Nxa2 10. Rxa2 Re8 11. f3 Bd7 12. h3 Nh5 13. Qxf7 Rxe3+ 14. Be2 Ng3 15. b4 Nxh1 16. Rxa7 Ng3 17. Ra8# 1-0"))
    
        if err != nil {
    
                fmt.Println(err.Error())
    
        }
    
        game := chess.NewGame(pgn)
    
        a := game.String()
    
        fmt.Println(a)
    
        _, err = chess.PGN(strings.NewReader(a))
    
        fmt.Println(err.Error())
    

    } `

    now, its output:

    ` 1.d4 d5 2.Bf4 Nc6 3.e3 Qd6 4.Bxd6 exd6 5.Nc3 Nf6 6.Qf3 Bg4 7.Qf4 O-O-O 8.Nb1 Nb4 9.c3 Nxa2 10.Rxa2 Re8 11.f3 Bd7 12.h3 Nh5 13.Qxf7 Rxe3+ 14.Be2 Ng3 15.b4 Nxh1 16.Rxa7 Ng3 17.Ra8# 1-0

    chess: pgn decode error chess: failed to decode notation text "1.d4" for position rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 on move 1

    `

  • Added NewSquare function

    Added NewSquare function

    In a project of mine using this library, I had to make a Square from a File and a Rank with this hack chess.Square(int8(file)*8 + int8(rank)) so I thought that maybe it would make sense to have it be a part of the library

  • Fix Scan() pgn parse bugs, fix FEN move clock bug, add Scan() option to expand variations

    Fix Scan() pgn parse bugs, fix FEN move clock bug, add Scan() option to expand variations

    1. When parsing a PGN file with multiple games, Scanner.Scan() assumes that each game begins after a set number of blank lines. However, PGN files allow for an arbitrary number of blank lines. On a practical basis, PGN files created by exporting lichess.org studies contain 2 blank lines between each chapter. This change includes a fix to have Scanner.Scan() treat "1." as the canonical indictor of a new game.

    2. moveList() employs regular expression matching in order to remove extraneous text from PGN files. This includes comments, tag pairs, and variations. However, since PGN allows for variations to be nested, regular expression matching is insufficient as (.*?) will match the close paren token of the deepest variation. This change includes a fix to moveList() to remove variations in a nest-aware fashion.

    3. Added missing unit test for Scanner.Scan() including test cases for the above 2 issues.

  • List valid moves for the non

    List valid moves for the non "current" color?

    Hi.

    Writing a UI using this library, and I'm running into a smallish issue, maybe you have a solution for me!

    In my game, whenever you drag one of your pieces around, it will highlight the squares you can move it to.

    When it's the opponent's turn, I can still drag my pieces around (but not drop them obviously), and I'd like to still highlight the possible moves for that piece while my opponent is thinking.

    Using the previous position works great, however I can't get the "new" valid moves from the piece I just moved. It'll be easier to show screenshots of what I mean.

    This is the position before I make move, you can see the white king is being dragged around and the squares where I can move my king are highlighted

    https://jpleau.ca/before_move.png

    This is the position after I move (and it's now my opponent's turn). My king is being dragged around. I would like to get the possible moves for my king, but ValidMoves() only lists moves for black until they make their move.

    https://jpleau.ca/after_move.png

    Is there something I can do to list the valid moves for the opposite color of Position().Turn() ? I was thinking being able to call Position().Update(nil) to simulate a no-op move for the current player, and so I could use that position to list the valid moves I need. I have no idea of the feasibility of this, as adding invalid moves could break things...

    Do you have a suggestion for me ?

    Thanks !

  • Replaced MD5 hash with Zobrist hash

    Replaced MD5 hash with Zobrist hash

    Version with Zobrist hashing (original paper) instead of MD5.

    I also have memoized the hash calculation.

    $> benchstat bench_old.txt bench_new_v2.txt
    name            old time/op    new time/op    delta
    PositionHash-8    14.7µs ± 3%    13.2µs ± 3%  -10.32%  (p=0.000 n=10+9)
    
    name            old alloc/op   new alloc/op   delta
    PositionHash-8    9.63kB ± 1%    8.31kB ± 1%  -13.69%  (p=0.000 n=10+10)
    
    name            old allocs/op  new allocs/op  delta
    PositionHash-8       103 ± 0%        80 ± 0%  -22.33%  (p=0.000 n=10+10)
    

    The difference between the 2 hashing methods will improve in the future with some changes like storing Castling rights as bitmaps and not as strings. One example can be found here

    To search for collision due to bugs in my implementation I have also created a tool to compute hashes.

  • multidecoder when decode PGN cause invalid board state

    multidecoder when decode PGN cause invalid board state

    g := chess.NewGame(chess.UseNotation(chess.UCINotation{}))
    pie(g.MoveStr("g1f3"))
    pie(g.MoveStr("e7e5"))
    
    fmt.Println(g.String()) // 1. g1f3 e7e5  *
    
    pgn, err := chess.PGN(strings.NewReader(g.String()))
    if err != nil {
       panic(err)
    }
    
    g2 := chess.NewGame(chess.UseNotation(chess.UCINotation{}), pgn)
    fmt.Println(g2.String()) // 1. f2f3 e7e5  *
    

    When decoding https://github.com/notnil/chess/blob/e9ff96c9f2d309a51c820bc3068f2c4ec473664c/pgn.go#L168, it tries every possible decoder and stop at not nil one. Above example shows that it can cause invalid board state.

  • No method to pop a move from MoveHistory

    No method to pop a move from MoveHistory

    I'm trying to implement a bot with this library powering the game. However, instead of deep copying the board while constructing the tree there should be a method to pop the last move off the MoveHistory as it would be a lot faster than copying the board for each node of the minimax tree.

    Is there a function that exists currently?

  • Add MultiPV support to uci.SearchResults

    Add MultiPV support to uci.SearchResults

    I added some more commits to PR #100 closes #99

    In kmorrison's PR, I noticed is that if you do a MultiPV search, eng.SearchResults().Info shows the score of the last MultiPV result which is the worst move, so the score is wrong. I fixed this issue.

    I also changed the type of MultiPV from []*Info to []Info.

  • Prevent group signals to kill engine processes

    Prevent group signals to kill engine processes

    The issue happens when implementing cancellation and graceful shutdown for interrupt signals, for example in a CLI when a user presses CTRL+C. We capture the signals and prevent the immediate exit. The expected behavior would be for the parent application to have control over when the engine subprocess ends and eventually kill it with uci.Engine.Close() while handling its shutdown.

    However, the shell signal the entire process group when pressing CTRL+C. Therefore the engine subprocess is killed immediately and the parent process loses control. If waiting on engine output, it is left hanging.

    To prevent the shell from signaling the children, we need to start the command in its own process group. This PR implements this.

    If having the engine subprocess in the same process group as the parent is sometimes desirable, then alternatively it would be nice to have an option to control the behavior.

  • Add support for nil move in Position.Update

    Add support for nil move in Position.Update

    First, thanks for the awesome library! I'm using it to write a toy chess engine.

    Then, this PR:

    Position.Update is useful for game engines when also using the ValidMoves method as it skips redundant validation. It would also be useful to add support for nil move to make it act like passing is a thing in chess.

    This could be used in the context of implementing null move pruning or in quiescence search to check if not capturing is better than capturing.

    Currently, when passed nil Position.Update panics. The proposal is that when passed nil, it returns the same position with the opposite turn.

  • MoveHistory() panics regarding Comments when built programatically

    MoveHistory() panics regarding Comments when built programatically

    To reproduce:

      func TestMoveHistory(t *testing.T) {
        game := chess.NewGame()
        game.MoveStr("e4")
        game.MoveStr("e5")
        game.Resign(chess.Black)
        history := game.MoveHistory() // Panics here
        if len(history) != 2 {
          t.Fatal("Didn't retrieve full history")
        }
      }
    

    That there's also no way to make a move with comments.... well, I guess that's #89

chess package for go
chess package for go

chess Introduction chess is a set of go packages which provide common chess utilities such as move generation, turn management, checkmate detection, P

Dec 26, 2022
Play chess with Go, HTML5, WebSockets and random strangers!

ChessBuddy Play chess with Go, HTML5, WebSockets and random strangers! Demo: http://chess.tux21b.org:8000/ Hint: Open the page in two different tabs,

Nov 10, 2022
♛♔ Play chess against UCI engines in your terminal.
♛♔ Play chess against UCI engines in your terminal.

uchess ♛♔ Play chess in your terminal. Introduction uchess is an interactive terminal chess client designed to allow gameplay and move analysis in con

Nov 17, 2022
A chess engine written in golang
A chess engine written in golang

Weasel Art graciously provided by Alex Table of Contents: About Installing and Compiling from Source Contributing License About Weasel is an 0x88 and

Dec 30, 2022
Currently in beta testing. A chess engine written in golang
Currently in beta testing. A chess engine written in golang

Weasel Art graciously provided by Alex Table of Contents: About Installing and Compiling from Source Contributing License About Weasel is an 0x88 and

Dec 30, 2022
A chess GUI build using the Fyne toolkit.
A chess GUI build using the Fyne toolkit.

Chess The subject of my current live stream series. A simple chess UI built with Fyne. Thanks to: Chess Go library by Logan Spears github.com/notnil/c

Dec 20, 2022
Blunder is an open-source UCI compatible chess engine.

A UCI compatible chess engine written in Golang

Dec 30, 2022
Play chess in your terminal
Play chess in your terminal

Gambit Chess board in your terminal. Warning gambit does not have many features at the moment. I plan on adding a chess engine, mouse support, timers,

Dec 21, 2022
A small go program that solves the Queen's Gambit chess puzzle.

Queen's Gambit Solver This program attempts to solve the Queen's Gambit each time it is run. The Queen's Gambit is a chess-based puzzle where eight qu

Jun 27, 2022
A cli to play chess against an an UCI engine of your choice, written in go
A cli to play chess against an an UCI engine of your choice, written in go

chess-cli chess-cli is a cli for playing chess against an UCI compatible engine written in go Note: Chess pieces are in unicode, so the color may seem

Mar 1, 2022
A cli to play chess against an an UCI engine of your choice, written in go
A cli to play chess against an an UCI engine of your choice, written in go

chess-cli chess-cli is a cli for playing chess against an UCI compatible engine written in go Note: Chess pieces are in unicode, so the color may seem

Dec 24, 2021
A simple chess engine for experiment, made in Golang

chess-engine A simple chess engine for experiment, made in Golang Build the engine Run the command make or make build Run the engine Run the command f

Dec 26, 2021
chess.com api wrapper for go

Chessdotcom-go An unofficial, simple, lighweight API wrapper for chess.com written in go Usage go get -u "github.com/ATTron/chessdotcom-go" im

Dec 31, 2021
A chess server built with Temporal.
A chess server built with Temporal.

chesstempo A chess server built with Temporal. Dependencies The activity worker runs Stockfish in the background. In Debian-based distros, you can ins

Nov 21, 2022
A performance oriented 2D/3D math package for Go

Package go3d is a performance oriented vector and matrix math package for 2D and 3D graphics. Every type has its own sub-package and is named T. So ve

Dec 16, 2022
Chesscode - Chesscode: a way of encoding messages using a chess board and the starting set of chess pieces
Chesscode - Chesscode: a way of encoding messages using a chess board and the starting set of chess pieces

chesscode Are you a spy that needs to leave an urgent top secret message for an

Jan 1, 2022
chess package for go
chess package for go

chess Introduction chess is a set of go packages which provide common chess utilities such as move generation, turn management, checkmate detection, P

Dec 26, 2022
Play chess with Go, HTML5, WebSockets and random strangers!

ChessBuddy Play chess with Go, HTML5, WebSockets and random strangers! Demo: http://chess.tux21b.org:8000/ Hint: Open the page in two different tabs,

Nov 10, 2022
♛♔ Play chess against UCI engines in your terminal.
♛♔ Play chess against UCI engines in your terminal.

uchess ♛♔ Play chess in your terminal. Introduction uchess is an interactive terminal chess client designed to allow gameplay and move analysis in con

Nov 17, 2022
A chess engine written in golang
A chess engine written in golang

Weasel Art graciously provided by Alex Table of Contents: About Installing and Compiling from Source Contributing License About Weasel is an 0x88 and

Dec 30, 2022