Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 138 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@

![rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1](example.png)

## Recent Updates

**Comprehensive Move Validation**: All move methods now properly validate moves according to chess rules, returning descriptive errors for invalid moves. This ensures consistent game correctness across all move APIs.

**Performance Options**: Added unsafe variants for high-performance scenarios:
- `UnsafeMove()` - ~1.5x faster than `Move()`
- `UnsafePushNotationMove()` - ~1.1x faster than `PushNotationMove()`

**API Consistency**: Refactored move methods for clear validation behavior and consistent performance options across all move APIs.

## Why I Forked
I forked the original ![notnil/chess](https://github.com/notnil/chess) package for several reasons:

Expand Down Expand Up @@ -86,7 +96,9 @@ func main() {
// select a random move
moves := game.ValidMoves()
move := moves[rand.Intn(len(moves))]
game.Move(&move, nil)
if err := game.Move(&move, nil); err != nil {
panic(err) // Should not happen with valid moves
}
}
// print outcome and game PGN
fmt.Println(game.Position().Board().Draw())
Expand Down Expand Up @@ -156,7 +168,56 @@ func main() {

### 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.
Chess provides multiple ways of making moves: direct move execution, valid move generation, and notation parsing. All move methods include proper validation to ensure game correctness.

#### Move Methods

The library offers two move execution methods to balance safety and performance:

**Move()** - Validates moves before execution (recommended for general use):
```go
game := chess.NewGame()
moves := game.ValidMoves()
err := game.Move(&moves[0], nil)
if err != nil {
// Handle invalid move error
}
```

**UnsafeMove()** - High-performance move execution without validation:
```go
game := chess.NewGame()
moves := game.ValidMoves()
// Only use when you're certain the move is valid
err := game.UnsafeMove(&moves[0], nil)
if err != nil {
// Handle error (should not occur with valid moves)
}
```

**PushNotationMove()** - Validates moves using any notation (recommended for general use):
```go
game := chess.NewGame()
err := game.PushNotationMove("e4", chess.AlgebraicNotation{}, nil)
if err != nil {
// Handle invalid move or notation error
}
```

**UnsafePushNotationMove()** - High-performance notation parsing without move validation:
```go
game := chess.NewGame()
// Only use when you're certain the move is valid
err := game.UnsafePushNotationMove("e4", chess.AlgebraicNotation{}, nil)
if err != nil {
// Handle notation parsing error (should not occur with valid notation)
}
```

> **Performance Note**:
> - `UnsafeMove()` provides ~1.5x performance improvement over `Move()` by skipping validation
> - `UnsafePushNotationMove()` provides ~1.1x performance improvement over `PushNotationMove()` by skipping move validation
> - Use unsafe variants only when moves are pre-validated or known to be legal

#### Valid Moves

Expand All @@ -165,21 +226,71 @@ Valid moves generated from the game's current position:
```go
game := chess.NewGame()
moves := game.ValidMoves()
game.Move(moves[0])
game.Move(&moves[0], nil)
fmt.Println(moves[0]) // b1a3
```

#### Parse Notation

Game's MoveStr method accepts string input using the default Algebraic Notation:
PushNotationMove method accepts string input using any supported notation:

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

#### Move Validation

All move methods automatically validate moves according to chess rules. The `Move()` method validates moves before execution and returns descriptive errors for invalid moves:

```go
game := chess.NewGame()

// Get valid moves from current position
validMoves := game.ValidMoves()
if len(validMoves) > 0 {
// This will succeed - move is known to be valid
if err := game.Move(&validMoves[0], nil); err != nil {
fmt.Println("Move failed:", err)
} else {
fmt.Println("Move succeeded")
}
}

// Using notation parsing with validation
if err := game.PushNotationMove("e4", chess.AlgebraicNotation{}, nil); err != nil {
fmt.Println("Move failed:", err)
} else {
fmt.Println("e4 move succeeded")
}

// Invalid notation will be caught
if err := game.PushNotationMove("e5", chess.AlgebraicNotation{}, nil); err != nil {
fmt.Println("Move failed:", err)
// Output: Move failed: [invalid move error]
}
```

For scenarios requiring maximum performance where moves are already validated:

```go
game := chess.NewGame()

// Option 1: Using Move structs directly (~1.5x faster)
validMoves := game.ValidMoves()
selectedMove := &validMoves[0] // We know this is valid
if err := game.UnsafeMove(selectedMove, nil); err != nil {
panic(err) // Should not happen with pre-validated moves
}

// Option 2: Using notation (~1.1x faster)
if err := game.UnsafePushNotationMove("e4", chess.AlgebraicNotation{}, nil); err != nil {
panic(err) // Should not happen with valid notation/moves
}
```

### 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.
Expand All @@ -190,10 +301,10 @@ Black wins by checkmate (Fool's Mate):

```go
game := chess.NewGame()
game.MoveStr("f3")
game.MoveStr("e6")
game.MoveStr("g4")
game.MoveStr("Qh4")
game.PushNotationMove("f3", chess.AlgebraicNotation{}, nil)
game.PushNotationMove("e6", chess.AlgebraicNotation{}, nil)
game.PushNotationMove("g4", chess.AlgebraicNotation{}, nil)
game.PushNotationMove("Qh4", chess.AlgebraicNotation{}, nil)
fmt.Println(game.Outcome()) // 0-1
fmt.Println(game.Method()) // Checkmate
/*
Expand All @@ -217,7 +328,7 @@ 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")
game.PushNotationMove("Qb6", chess.AlgebraicNotation{}, nil)
fmt.Println(game.Outcome()) // 1/2-1/2
fmt.Println(game.Method()) // Stalemate
/*
Expand All @@ -239,7 +350,7 @@ Black resigns and white wins:

```go
game := chess.NewGame()
game.MoveStr("f3")
game.PushNotationMove("f3", chess.AlgebraicNotation{}, nil)
game.Resign(chess.Black)
fmt.Println(game.Outcome()) // 1-0
fmt.Println(game.Method()) // Resignation
Expand All @@ -264,7 +375,7 @@ fmt.Println(game.Method()) // DrawOffer
game := chess.NewGame()
moves := []string{"Nf3", "Nf6", "Ng1", "Ng8", "Nf3", "Nf6", "Ng1", "Ng8"}
for _, m := range moves {
game.MoveStr(m)
game.PushNotationMove(m, chess.AlgebraicNotation{}, nil)
}
fmt.Println(game.EligibleDraws()) // [DrawOffer ThreefoldRepetition]
```
Expand All @@ -282,7 +393,7 @@ moves := []string{
"Nf3", "Nf6", "Ng1", "Ng8",
}
for _, m := range moves {
game.MoveStr(m)
game.PushNotationMove(m, chess.AlgebraicNotation{}, nil)
}
fmt.Println(game.Outcome()) // 1/2-1/2
fmt.Println(game.Method()) // FivefoldRepetition
Expand All @@ -307,7 +418,7 @@ According to [FIDE Laws of Chess Rule 9.6b](http://www.fide.com/component/handbo
```go
fen, _ := chess.FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 149 23")
game := chess.NewGame(fen)
game.MoveStr("Kf8")
game.PushNotationMove("Kf8", chess.AlgebraicNotation{}, nil)
fmt.Println(game.Outcome()) // 1/2-1/2
fmt.Println(game.Method()) // SeventyFiveMoveRule
```
Expand Down Expand Up @@ -367,8 +478,8 @@ Moves and tag pairs added to the PGN output:
```go
game := chess.NewGame()
game.AddTagPair("Event", "F/S Return Match")
game.MoveStr("e4")
game.MoveStr("e5")
game.PushNotationMove("e4", chess.AlgebraicNotation{}, nil)
game.PushNotationMove("e5", chess.AlgebraicNotation{}, nil)
fmt.Println(game)
/*
[Event "F/S Return Match"]
Expand Down Expand Up @@ -457,9 +568,9 @@ fmt.Println(pos.String()) // rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq
[Algebraic Notation](https://en.wikipedia.org/wiki/Algebraic_notation_(chess)) (or Standard Algebraic Notation) is the official chess notation used by FIDE. Examples: e2, e5, O-O (short castling), e8=Q (promotion)

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

Expand All @@ -468,11 +579,11 @@ fmt.Println(game) // 1.e4 e5 *
[Long Algebraic Notation](https://en.wikipedia.org/wiki/Algebraic_notation_(chess)#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.

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

Expand All @@ -481,9 +592,9 @@ fmt.Println(game) // 1.f2f3 e7e5 2.g2g4 Qd8h4# 0-1
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)

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

Expand Down
Loading
Loading