Giter Site home page Giter Site logo

chess's Introduction

chess

Build and Test 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♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖
*/

Move History

Move History is a convenient API for accessing aligned positions, moves, and comments. Move History is useful when trying to understand detailed information about a game. Below is an example showing how to see which side castled first.

package main

import (
	"fmt"
	"os"

	"github.com/notnil/chess"
)

func main() {
	f, err := os.Open("fixtures/pgns/0001.pgn")
	if err != nil {
		panic(err)
	}
	defer f.Close()
	pgn, err := chess.PGN(f)
	if err != nil {
		panic(err)
	}
	game := chess.NewGame(pgn)
	color := chess.NoColor
	for _, mh := range game.MoveHistory() {
		if mh.Move.HasTag(chess.KingSideCastle) || mh.Move.HasTag(chess.QueenSideCastle) {
			color = mh.PrePosition.Turn()
			break
		}
	}
	switch color {
	case chess.White:
		fmt.Println("white castled first")
	case chess.Black:
		fmt.Println("black castled first")
	default:
		fmt.Println("no side castled")
	}
}

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

chess's People

Contributors

barakmich avatar bbars avatar captncraig avatar grmorozov avatar husainaloos avatar katajisto avatar kerrigan29a avatar kgigitdev avatar levidurfee avatar lginn2 avatar lorenzobotti avatar neofight78 avatar notnil avatar sumnerevans avatar theletterjeff avatar tilps avatar vladimish avatar xaionaro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

chess's Issues

How to get a square's color ?

Hi !

Learning chess and go at the same time, I'm trying to build a small game for myself to practice on, and found your library, which looks very interesting, thank you for building it!

I have a small question, this is my code in my game loop:

game = chess.NewGame()
squares := game.Position().Board().SquareMap()

for square, _ := range squares {
    color := square.color() // ???
}

You can see what problem I have here. I would like my UI is as "dumb" as possible and rely on your library for all the logic, but unfortunately I cannot get a square's color.

Is there another way to achieve this ? Or would making the Color() function publicly available be the way to do this ?

I can look at https://github.com/notnil/chessimg/blob/master/chessimg.go#L124, but obviously here making the Color() function public could be helpful as well there.

Thanks !

How do we set UCI options

Hi, it appears that stockfish uses 1 thread by default. How would I set the UCI thread option?

I've tried manually changing engine.New as follows but it didn't make any difference

func New(path string, opts ...func(e *Engine)) (*Engine, error) {
	path, err := exec.LookPath(path)
	if err != nil {
		return nil, fmt.Errorf("uci: executable not found at path %s %w", path, err)
	}
	rIn, wIn := io.Pipe()
	rOut, wOut := io.Pipe()
	cmd := exec.Command(path)
	cmd.Stdin = rIn
	cmd.Stdout = wOut
	var options = make(map[string] Option)
	options["Threads"] = Option{
		Name: "Threads",
		Type:  OptionSpin,
		Default: "2",
		Min: "2",
		Max: "4",
		Vars: []string{},
	}
	e := &Engine{cmd: cmd, in: wIn, out: rOut, mu: &sync.RWMutex{}, logger: log.New(os.Stdout, "uci", log.LstdFlags), options: options}
	for _, opt := range opts {
		opt(e)
	}
	go e.cmd.Run()
	return e, nil
}

Multi Notation Support

Parsing and validating algebraic notation is slow and certain applications could benefit from faster encoding / decoding.

improve alg notation parse error message

The array that starts with a7a6 represents valid moves, but you have to dig into the code to figure that out. The list should either be removed or labeled.

chess: could not decode algebraic notation c6��� for position rnbqkbnr/pppppppp/8/8/
1P6/8/P1PPPPPP/RNBQKBNR b KQkq b3 0 1 [a7a6 b7b6 c7c6 d7d6 e7e6 f7f6 g7g6 h7h6 a7a5 b7b5 c7c5 d7d5 e7e5 f7f5 g7g5 h7h
5 b8c6 g8h6 b8a6 g8f6] on move 1 - Tag Pairs: []

Server address points to a fritzbox

When you run the client (doesn't matter if compiled or downloaded) the client isn't working. It points to a fritzbox which denies access. When you execute it with ./client --hostname=http://lczero.org --user=foo --password=bar it works.

Please consider changing the default address to lczero.org so it works out of the box.

PGN reader is not compatible with cutechess-cli

Reading this PGN:

[Event "self-play-1"]
[Site "?"]
[Date "2021.09.06"]
[Round "98"]
[White "zahak-linux-amd64-6.2"]
[Black "zahak-linux-amd64-6.2"]
[Result "0-1"]
[FEN "r1bqkbnr/1pppp1pp/5p2/p3n3/P1P3PP/8/1P1PPP2/RNBQKBNR w KQkq - 0 1"]
[GameDuration "00:00:02"]
[GameEndTime "2021-09-06T17:23:16.165 EDT"]
[GameStartTime "2021-09-06T17:23:14.028 EDT"]
[PlyCount "77"]
[SetUp "1"]
[Termination "adjudication"]
[TimeControl "inf"]

1. Nf3 {-0.17/9 0.041s} Nxc4 {+0.09/9 0.023s} 2. Nc3 {-0.23/9 0.020s}
d5 {+0.12/9 0.017s} 3. d3 {-0.42/9 0.019s} Ne5 {+0.38/9 0.012s}
4. Nxe5 {-0.02/9 0.016s} fxe5 {+0.28/9 0.023s} 5. g5 {-0.12/9 0.033s}
e6 {+0.30/9 0.042s} 6. h5 {-0.55/9 0.085s} Bc5 {+0.48/9 0.045s}
7. h6 {-0.67/9 0.077s} gxh6 {+0.92/9 0.019s} 8. e4 {-0.30/9 0.027s}
d4 {+0.90/9 0.052s} 9. Na2 {-0.67/9 0.017s} Kd7 {+0.12/9 0.039s}
10. gxh6 {-0.30/9 0.028s} Nf6 {+0.29/9 0.023s} 11. Rg1 {-0.05/9 0.015s}
Rg8 {+0.31/9 0.020s} 12. Rg7+ {-0.21/9 0.017s} Rxg7 {-0.03/9 0.078s}
13. hxg7 {-0.07/9 0.016s} Qe7 {+0.20/9 0.014s} 14. Bh6 {-0.37/9 0.034s}
b6 {+0.20/9 0.028s} 15. Qd2 {-0.49/9 0.047s} Qf7 {+0.53/9 0.062s}
16. Be2 {-1.03/9 0.15s} Ng8 {+0.87/9 0.038s} 17. Qc1 {-1.23/9 0.037s}
Qf6 {+1.76/9 0.010s} 18. Bg5 {-1.53/9 0.027s} Qxg7 {+1.64/9 0.010s}
19. Bh4 {-1.85/9 0.034s} Bb7 {+2.01/9 0.013s} 20. Bg3 {-1.83/9 0.034s}
Ne7 {+1.96/9 0.033s} 21. Bf1 {-2.08/9 0.048s} Ng6 {+2.28/9 0.031s}
22. b3 {-2.65/9 0.052s} Rg8 {+2.68/9 0.026s} 23. Bh3 {-2.61/9 0.058s}
Ba6 {+3.02/9 0.030s} 24. Bf1 {-3.14/9 0.022s} h5 {+3.10/9 0.014s}
25. Bh2 {-3.24/9 0.013s} h4 {+3.24/9 0.012s} 26. Qd2 {-2.91/9 0.018s}
Qf8 {+3.32/9 0.015s} 27. Qg5 {-3.59/9 0.031s} Bb4+ {+4.06/9 0.013s}
28. Nxb4 {-3.54/9 0.012s} Qxb4+ {+3.53/9 0.018s} 29. Qd2 {-3.48/9 0.017s}
Qxb3 {+3.53/9 0.007s} 30. Rc1 {-3.55/9 0.015s} c6 {+3.73/9 0.014s}
31. Qc2 {-3.67/9 0.020s} Qxc2 {+3.32/9 0.008s} 32. Rxc2 {-3.63/9 0.007s}
Nf4 {+3.53/9 0.011s} 33. Bxf4 {-3.74/9 0.009s} exf4 {+4.21/9 0.006s}
34. Ke2 {-3.77/9 0.006s} Rg1 {+3.76/9 0.008s} 35. Bh3 {-4.45/9 0.014s}
Ra1 {+4.52/9 0.019s} 36. e5 {-4.63/9 0.027s} Ra3 {+4.49/9 0.014s}
37. Rd2 {-4.74/9 0.021s} Rxa4 {+4.70/9 0.003s} 38. Kf3 {-4.52/9 0.006s}
Ra3 {+4.58/9 0.015s} 39. Bf1 {-4.55/9 0.009s, Black wins by adjudication} 0-1

Will result in this error:

panic: chess: pgn decode error chess: failed to decode notation text "{+0.40/9" for position rnbqkbnr/2pp1p1p/1p2p3/p5p1/PP6/N6N/1BPPPPPP/R2QKB1R b KQkq - 1 1 on move 1

goroutine 1 [running]:
main.main()
	/home/amanj/Documents/fengen/fengen.go:47 +0xbfa

How to generate a flipped svg file?

Hello! I am trying to generate a flipped SVG file for the black player.

I write code like this:

	board := pos.Board()
	if pos.Turn() == chess.Black {
		board = board.Flip(chess.UpDown).Flip(chess.LeftRight)
	}
	if err := image.SVG(f, board); err != nil {
		log.Fatal(err)
	}

But I got this, where the a1 should be at the left top

image

What should I do to generate a correct SVG file? Or is this a bug?

50-move draw is using 50-half moves, not 50-moves

Eg. 8/8/8/8/4k3/8/3K2p1/5r2 w - - 50 119 should not be a draw, but is with the current rules.

Quote from wikipedia on this:

The fifty-move rule in chess states that a player can claim a draw if no capture has been made and no pawn has been moved in the last fifty moves (for this purpose a "move" consists of a player completing their turn followed by the opponent completing their turn).

Perft result

Hi, it would be really interesting if you could provide PERFT results in the README file.

Disambiguation bug

When reading a large PGN file, I stumbled across this error. Looks like it's a problem with the disambiguation:

chess: pgn decode error chess: could not decode algebraic notation Q1f1 for position 2k5/6K1/8/8/8/8/4qq2/4q3 b - - 9 55 on move 55

Comments Not Written to PGN

The Game struct is not writing out comments back out in its PGN. Input of

[Event "Rated Bullet tournament https://lichess.org/tournament/yc1WW2Ox"]
[Site "https://lichess.org/PpwPOZMq"]
[Date "2017.04.01"]
[Round "-"]
[White "Abbot"]
[Black "Costello"]
[Result "0-1"]
[UTCDate "2017.04.01"]
[UTCTime "11:32:01"]
[WhiteElo "2100"]
[BlackElo "2000"]
[WhiteRatingDiff "-4"]
[BlackRatingDiff "+1"]
[WhiteTitle "FM"]
[ECO "B30"]
[Opening "Sicilian Defense: Old Sicilian"]
[TimeControl "300+0"]
[Termination "Time forfeit"]

1. e4 { [%eval 0.17] [%clk 0:00:30] } 1... c5 { [%eval 0.19] [%clk 0:00:30] }
2. Nf3 { [%eval 0.25] [%clk 0:00:29] } 2... Nc6 { [%eval 0.33] [%clk 0:00:30] }
3. Bc4 { [%eval -0.13] [%clk 0:00:28] } 3... e6 { [%eval -0.04] [%clk 0:00:30] }
4. c3 { [%eval -0.4] [%clk 0:00:27] } 4... b5? { [%eval 1.18] [%clk 0:00:30] }
5. Bb3?! { [%eval 0.21] [%clk 0:00:26] } 5... c4 { [%eval 0.32] [%clk 0:00:29] }
6. Bc2 { [%eval 0.2] [%clk 0:00:25] } 6... a5 { [%eval 0.6] [%clk 0:00:29] }
7. d4 { [%eval 0.29] [%clk 0:00:23] } 7... cxd3 { [%eval 0.6] [%clk 0:00:27] }
8. Qxd3 { [%eval 0.12] [%clk 0:00:22] } 8... Nf6 { [%eval 0.52] [%clk 0:00:26] }
9. e5 { [%eval 0.39] [%clk 0:00:21] } 9... Nd5 { [%eval 0.45] [%clk 0:00:25] }
10. Bg5?! { [%eval -0.44] [%clk 0:00:18] } 10... Qc7 { [%eval -0.12] [%clk 0:00:23] }
11. Nbd2?? { [%eval -3.15] [%clk 0:00:14] } 11... h6 { [%eval -2.99] [%clk 0:00:23] }
12. Bh4 { [%eval -3.0] [%clk 0:00:11] } 12... Ba6? { [%eval -0.12] [%clk 0:00:23] }
13. b3?? { [%eval -4.14] [%clk 0:00:02] } 13... Nf4? { [%eval -2.73] [%clk 0:00:21] } 0-1

becomes

[Event "Rated Bullet tournament https://lichess.org/tournament/yc1WW2Ox"]
[Site "https://lichess.org/PpwPOZMq"]
[Date "2017.04.01"]
[Round "-"]
[White "Abbot"]
[Black "Costello"]
[Result "0-1"]
[UTCDate "2017.04.01"]
[UTCTime "11:32:01"]
[WhiteElo "2100"]
[BlackElo "2000"]
[WhiteRatingDiff "-4"]
[BlackRatingDiff "+1"]
[WhiteTitle "FM"]
[ECO "B30"]
[Opening "Sicilian Defense: Old Sicilian"]
[TimeControl "300+0"]
[Termination "Time forfeit"]

1. e4 c5 2. Nf3 Nc6 3. Bc4 e6 4. c3 b5 5. Bb3 c4 6. Bc2 a5 7. d4 cxd3 8. Qxd3 Nf6 9. e5 Nd5 10. Bg5 Qc7 11. Nbd2 h6 12. Bh4 Ba6 13. b3 Nf4  0-1

losing the comments

https://go.dev/play/p/r42jPTXb81M

Game History is Clunky

Right now you have to query every aspect of the game separately:

  • Moves via game.Moves()
  • Positions via game.Positions()
  • Comments via game.Comments()

The package needs an iterator or combined game history list that ties all of these elements together.

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

Chess Game using notnil/chess

Hi,

I'm writing to extend a huge thanks for this module and to let you know that I've just released a terminal based chess game that makes heavy use of notnil/chess. Your module was hugely helpful in permitting me to focus on the core functionality of the game rather than dealing with all of the idiosyncrasies of chess rules. Thank you!

https://github.com/tmountain/uchess

Chess960 support?

First of all, great work with the library!

One issue (or a missing feature) I encountered: it does not handle Chess960 castling correctly.

E.g.

package main

import (
	"fmt"

	"github.com/notnil/chess"
)

func main() {
	fen, _ := chess.FEN("bnqbnrkr/pppppppp/8/8/8/8/PPPPPPPP/BNQBNRKR w KQkq - 0 1")
	game := chess.NewGame(chess.UseNotation(chess.UCINotation{}), fen)

	game.MoveStr("b1a3")
	game.MoveStr("f7f6")
	game.MoveStr("f2f4")
	game.MoveStr("f6f5")
	game.MoveStr("f1f3")
	game.MoveStr("g7g6")
	game.MoveStr("g1g1") // King-side castling

	// 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())
}

The PGN does not have the castling move present: 1. b1a3 f7f6 2. f2f4 f6f5 3. f1f3 g7g6 *

Same with the printed board.

Using X-FEN (here bnqbnrkr/pppppppp/8/8/8/8/PPPPPPPP/BNQBNRKR w HFhf - 0 1) to encode the starting position does not help.

Are there any plans to support Chess960?

Reading large PGN files (1M+ games)

I'd like to use this library to read really large PGN files. There's two suggestions I'd like to make to improve this. If you agree with my suggestions, then I'm happy to have a crack at implementing them:

  1. Stop logging import messages, this slows things down.
  2. Create a PGN reader class, similar to LimitedReader, that allows you to read up to a fixed number of games at a time so that you don't have to have all the games in memory at once.

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 :)

Is go get the recommended way to import the packge?

I did this first to go get the package:

go get -u github.com/notnil/chess

Then I run my start.go script and get this error:

go run -v start.go 

start.go:6:2: no required module provides package github.com/notnil/chess: go.mod file not found in current directory or any parent directory; see 'go help modules'
go version
go version go1.16.2 linux/amd64

Illegal Pawn Capture

Pawn on e6 moved to e5 capturing my Bishop. Pawns should not be able to capture without moving diagonally.

2b1r3/2k2pBB/p2np3/8/8/5N2/PP1K1PPP/3R4 w - - 1 1
Enter Move in Algebraic Notation (ex. 'e4'):
Be5

 A B C D E F G H
8- - ♝ - ♜ - - - 
7- - ♚ - - ♟ - ♗ 
6♟ - - ♞ ♟ - - - 
5- - - - ♗ - - - 
4- - - - - - - - 
3- - - - - ♘ - - 
2♙ ♙ - ♔ - ♙ ♙ ♙ 
1- - - ♖ - - - - 

2b1r3/2k2p1B/p2np3/4B3/8/5N2/PP1K1PPP/3R4 b - - 2 1

 A B C D E F G H
8- - ♝ - ♜ - - - 
7- - ♚ - - ♟ - ♗ 
6♟ - - ♞ - - - - 
5- - - - ♟ - - - 
4- - - - - - - - 
3- - - - - ♘ - - 
2♙ ♙ - ♔ - ♙ ♙ ♙ 
1- - - ♖ - - - - 

2b1r3/2k2p1B/p2n4/4p3/8/5N2/PP1K1PPP/3R4 w - - 0 1
Enter Move in Algebraic Notation (ex. 'e4'):

Parsing PGN with FEN tag

Creation of a Game from PGN fails if it doesn't start from initial position. If FEN tag is present in PGN game description, it should be used to init a game.

Example.
Parsing of pgn with content:

[Site "?"]
[Date "????.??.??"]
[Round "?"]
[White "N.N."]
[Black "N.N."]
[Result "1-0"]
[Annotator "T1R"]
[SetUp "1"]
[FEN "2r2rk1/pp1bqpp1/2nppn1p/2p3N1/1bP5/1PN3P1/PBQPPPBP/3R1RK1 w - - 0 1"]
[PlyCount "5"]
[EventType "swiss"]

1. Nd5 exd5 (1... hxg5 2. Nxe7+ Nxe7) 2. Bxf6 hxg5 3. Bxe7 1-0

returns error: chess: pgn decode error chess: could not decode algebraic notation Nd5 for position rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 on move 1

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

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

setting the result (Outcome) of a game

Having some issues in creating a game using game.moveStr() as there seems no way to set the Outcome of the game other than if the last move made is mate (#). So far the only way I see of setting the result is to use game.PGN and read all the moves + result and then newGame(pgn).

As you have methods to enter individual moves am I missing something to set the result/Outcome ?

Thanks!

OK just realized that you have Resign method.....please ignore.

Implement UCI communication

Implementing UCI communication for chess engines would be a great improvement. Could be worth adding it if possible.

NewGame doesn't check for nil

Anyone of the options can be nil

// NewGame defaults to returning a game in the standard
// opening position.  Options can be given to configure
// the game's initial state.
func NewGame(options ...func(*Game)) *Game {
    pos, _ := decodeFEN(startFEN)
    game := &Game{
        notation:  AlgebraicNotation{},
        moves:     []*Move{},
        pos:       pos,
        positions: []*Position{pos},
        outcome:   NoOutcome,
        method:    NoMethod,
    }
    for _, f := range options {
        f(game)
    }
    return game
}

Replace Hashing method

The hashing method it's very expensive (because of the MD5 dependency) to use it in any cache or transposition table. I suggest to implement Zobrist hashing wich is the de-facto standard.

Possible reference implementations:

Comments Need Docs

Support for comments isn't mentioned in the readme and doesn't have examples. This should be remedied.

Undoing a move pushed to chess.Game?

I would like to use this Go library for an AlphaBeta pruning look ahead algorithm, but I don't understand how to neatly undo a move. Say we pushed a move to a game like so

game := chess.NewGame()
moves := game.ValidMoves()
game.Move(moves[0])

How can we then undo the last move that was made?

Exporting methods in notnil/chess

Hi there!

I'm the author of kgigitdev/godgt, which I'm currently rewriting (not uploaded yet) to use your excellent library for legal move generation.

godgt is a command line utility to record games from DGT's electronic chess boards.

I was wondering if you would be open to exporting some of the currently non-exported methods in your library.

The DGT board generates low-level piece events ("e4 is now empty", "d5 now contains a white pawn"), which due to the speed and order in which the squares are scanned, you can sometimes receive out of order.

My (probably inefficient) technique is therefore to ask for a complete board dump every time I receive an event, then compare that to every legal next position from the most recently accepted legal move.

One big problem I have is sliding piece moves. For example, when executing a "Bb5" move, a player might slide the bishop over d2, e3 and c4, and if the board happens to scan the piece in that position, then Bd3/Be4/Bd4 becomes the accepted move, and the later "Bb5" is rejected as being an invalid move (because the same player is seemingly playing twice).

One workaround might be to store the previous game state (the state immediately before the most recently accepted legal move) and also see if the new position is a legal move in that game state as well.

But that approach requires the ability to copy a game object. Game does goes have an unexported copy() method, but that's obviously not available.

There are a handful of other methods that are in a "would be nice to export" category, but which can be worked around. I can't recall them at the moment.

If you're open to such changes, let me know, and I'll see if I can get together a PR.

Checkmate not detected

Expected result: Game completed. 1-0 by Checkmate.
Actual result: Game completed. 1/2-1/2 by Stalemate.

package main

import (
        "fmt"
        "math/rand"

        "github.com/notnil/chess"
)

func main() {
        fen, _ := chess.FEN("rn1qkbnr/pbpp1Qpp/1p6/4p3/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 0 1")
        game := chess.NewGame(fen)
        // 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())
}

Implement BinaryMarshaler / BinaryUnmarshaler

It would be good to implement the BinaryMarshaler / BinaryUnmarshaler interfaces for game, position & moves for the efficient storage / transmission of data. If you are happy with the idea, I'd be happy to have a go at implementing it.

Support MultiPV UCI search

I'm using the UCI library to explore certain chess positions. AFAICT, the SearchResults populator doesn't respect MultiPV search, and will give simply the most recent info-line in the SearchResults().Info. Would it be possible to consume Info as an array, or perhaps have a separate variable in SearchResults called MultiPVInfo?

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

`

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 !

Parse Issue with 1.7

@sumnerevans I found a parsing issue with the latest regex code. It can't parse the following pgn:

[Event "Rated Blitz game"]
[Site "https://lichess.org/JXpwpOJf"]
[White "georgekontos"]
[Black "zev105"]
[Result "1-0"]
[UTCDate "2014.08.31"]
[UTCTime "22:03:44"]
[WhiteElo "1292"]
[BlackElo "1429"]
[WhiteRatingDiff "+15"]
[BlackRatingDiff "-15"]
[ECO "C42"]
[Opening "Russian Game: Three Knights Game"]
[TimeControl "300+3"]
[Termination "Normal"]

1. e4 e5 2. Nf3 Nf6 3. Nc3 Bc5 4. Nxe5 Qe7 5. Bd3 Qxe5 6. O-O O-O 7. a3 d6 8. b4 Bd4 9. Bb2 Nbd7 10. Bc4 Nb6 11. Qe2 Nxc4 12. Qxc4 Be6 13. Qxc7 Nxe4 14. Nxe4 Bxb2 15. Rae1 Rac8 16. Qxd6 Bc4 17. Qxe5 Bxe5 18. d3 Be6 19. Nc5 Bxh2+ 20. Kxh2 Bd5 21. c4 Bc6 22. Re7 Rcd8 23. Rd1 b6 24. b5 Bxb5 25. cxb5 bxc5 26. Rxa7 Rd6 27. Rc7 Rh6+ 28. Kg1 Re8 29. Rxc5 f5 30. b6 f4 31. b7 Rb8 32. Rc8+ Rxc8 33. bxc8=Q+ Kf7 34. Qf5+ Rf6 35. Qxh7 f3 36. g3 Ra6 37. Qh5+ g6 38. Qh7+ Kf6 39. Qh8+ Kg5 40. Re1 Rxa3 41. Re5+ Kg4 42. Qh4# 1-0

I'm having trouble reasoning about the long regex. Any way you can take a look?

Castling Rights Being Removed

Here is an excerpt from the game I was playing against my AI. Notice how the KQkq in the FEN is missing after the second move.

rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
Enter Move in Algebraic Notation (ex. 'e4'):
e4

 A B C D E F G H
8♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ 
7♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟ 
6- - - - - - - - 
5- - - - - - - - 
4- - - - ♙ - - - 
3- - - - - - - - 
2♙ ♙ ♙ ♙ - ♙ ♙ ♙ 
1♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ 

rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1

 A B C D E F G H
8♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜ 
7♟ ♟ - ♟ ♟ ♟ ♟ ♟ 
6- - - - - - - - 
5- - ♟ - - - - - 
4- - - - ♙ - - - 
3- - - - - - - - 
2♙ ♙ ♙ ♙ - ♙ ♙ ♙ 
1♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ 

rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w - c6 0 1

How to check if a move is a capture or check?

so I'm trying to get some move ordering for my bot but I am not entirely sure how to check if a move is capture or a check.

There is a chess.Capture and a chess.Check obj and they say they require a movetag in order to work?

I was like okay how do I get a movetag and then I saw there is chess.Movetag() but IDK how that works

All I want to know if a move is capture or check

how do I check if a thing like this
game.MoveStr("e2e4")
is a check or a capture or neither?

Would love to have an easier implementation like a simple command is_check or is_capture done for this libary.

add test to threefoldRepetition

Here is a pull request I made to add more test in ThreeFoldRepetition in the file "game_test.go"

note that I also changed "repition" to "repetition"

// test Threefold repetition
func TestThreeFoldRepetition(t *testing.T) {
testCases := []struct {
name string
moves []string
want bool
}{
{"knight back home one times", []string{
"Nf3", "Nf6", "Ng1", "Ng8",
}, false},
{"knight back home two times", []string{
"Nf3", "Nf6", "Ng1", "Ng8",
"Nf3", "Nf6", "Ng1", "Ng8",
}, true},
{"change king side castle right", []string{
"e4", "e5", "Bc4", "Bc5",
"Nf3", "Nf6",
"Rf1", "Rf8", "Rh1", "Rh8",
"Ng1", "Ng8",
"Nf3", "Nf6", "Ng1", "Ng8",
}, false},
{"change en-passant right", []string{
"e4", "e6", "e5", "d5",
"Nf3", "Ne7", "Ng1", "Ng8",
"Nf3", "Ne7", "Ng1", "Ng8",
}, false},
{"change side to move", []string{
"e4", "e5",
"Nf3", "Nf6", "Ng1", "Ng8",
"Bb5", "Nf6", "Bc4", "Ng8",
"Bf1",
}, false},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("%s=>%t", tc.name, tc.want), func(t *testing.T) {
g := NewGame()
for _, m := range tc.moves {
if err := g.MoveStr(m); err != nil {
t.Fatal(err)
}
}
if err := g.Draw(ThreefoldRepetition); (tc.want == true && err != nil) || (tc.want == false && err == nil) {
for _, pos := range g.Positions() {
log.Println(pos.String())
}
t.Fatalf("%s - %d reps", err.Error(), g.numOfRepetitions())
}
})
}
}

Issue Specifying SearchMoves for CmdGo

I'm trying to specify SearchMoves for the CmdGo struct in the UCI package, but SearchMoves expects []*chess.Move for its value. I can't find a way to construct a Move from outside of the chess package, as the Move structs members are all private. You have some boilerplate that is close to what I need via the strToSquareMap var; however, I'd need to cargo that into my code, as it is private as well, and that still doesn't solve the issue that the chess.Move members are private.

Can you tell me how to construct a Move from outside of the chess package? If that's not possible, can a constructor be added to make this possible?

Invalid Moves can be added

Here is a reference from the code.

// Move updates the game with the given move.  An error is returned
// if the move is invalid or the game has already been completed.
func (g *Game) Move(m *Move) error {
    // TODO there is a bug here.  The move pointer shouldn't
    // be used directly.  It could be later set to nil or
    // it might have incomplete tag information from
    // encodings.
    if !moveSlice(g.ValidMoves()).contains(m) {
        return fmt.Errorf("chess: invalid move %s", m)
    }
    g.moves = append(g.moves, m)
    g.pos = g.pos.Update(m)
    g.positions = append(g.positions, g.pos)
    g.updatePosition()
    return nil
}

We can't trust moves coming from previous valid move calls or encoded directly with a notation. Also we aren't checking for nil.

Clarify licensing terms

Right now there are no license details in the repo, whether MIT, GPL or Apache. Would appreciate a clarification.

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.