Skip to content

Commit

Permalink
added move history, fixed the dataset underlying openings (#98)
Browse files Browse the repository at this point in the history
Co-authored-by: Logan Spears <[email protected]>
  • Loading branch information
notnil and Logan Spears authored Mar 17, 2022
1 parent b82d4bf commit e9ff96c
Show file tree
Hide file tree
Showing 15 changed files with 6,913 additions and 7,940 deletions.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,51 @@ fmt.Println(game.Position().Board().Draw())
*/
```

### 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.

```go
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](https://golang.org/pkg/runtime/pprof/), with the goal of being fast enough for use by chess bots. The original map based board representation was replaced by [bitboards](https://chessprogramming.wikispaces.com/Bitboards) resulting in a large performance increase.
Expand Down
6 changes: 3 additions & 3 deletions bitboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ type bitboardTestPair struct {

var (
tests = []bitboardTestPair{
bitboardTestPair{
{
uint64(1),
uint64(9223372036854775808),
},
bitboardTestPair{
{
uint64(18446744073709551615),
uint64(18446744073709551615),
},
bitboardTestPair{
{
uint64(0),
uint64(0),
},
Expand Down
10 changes: 0 additions & 10 deletions fen.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,6 @@ func formEnPassant(enPassant string) (Square, error) {
}

var (
fenSkipMap = map[int]string{
1: "1",
2: "2",
3: "3",
4: "4",
5: "5",
6: "6",
7: "7",
8: "8",
}
fenPieceMap = map[string]Piece{
"K": WhiteKing,
"Q": WhiteQueen,
Expand Down
39 changes: 35 additions & 4 deletions game.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func (g *Game) UnmarshalText(text []byte) error {
func (g *Game) Draw(method Method) error {
switch method {
case ThreefoldRepetition:
if g.numOfRepitions() < 3 {
if g.numOfRepetitions() < 3 {
return errors.New("chess: draw by ThreefoldRepetition requires at least three repetitions of the current board state")
}
case FiftyMoveRule:
Expand Down Expand Up @@ -287,7 +287,7 @@ func (g *Game) Resign(color Color) {
// EligibleDraws returns valid inputs for the Draw() method.
func (g *Game) EligibleDraws() []Method {
draws := []Method{DrawOffer}
if g.numOfRepitions() >= 3 {
if g.numOfRepetitions() >= 3 {
draws = append(draws, ThreefoldRepetition)
}
if g.pos.halfMoveClock >= 100 {
Expand Down Expand Up @@ -336,6 +336,37 @@ func (g *Game) RemoveTagPair(k string) bool {
return found
}

// MoveHistory is a move's result from Game's MoveHistory method.
// It contains the move itself, any comments, and the pre and post
// positions.
type MoveHistory struct {
PrePosition *Position
PostPosition *Position
Move *Move
Comments []string
}

// MoveHistory returns the moves in order along with the pre and post
// positions and any comments.
func (g *Game) MoveHistory() []*MoveHistory {
h := []*MoveHistory{}
for i, p := range g.positions {
if i == 0 {
continue
}
m := g.moves[i-1]
c := g.comments[i-1]
mh := &MoveHistory{
PrePosition: g.positions[i-1],
PostPosition: p,
Move: m,
Comments: c,
}
h = append(h, mh)
}
return h
}

func (g *Game) updatePosition() {
method := g.pos.Status()
if method == Stalemate {
Expand All @@ -353,7 +384,7 @@ func (g *Game) updatePosition() {
}

// five fold rep creates automatic draw
if !g.ignoreAutomaticDraws && g.numOfRepitions() >= 5 {
if !g.ignoreAutomaticDraws && g.numOfRepetitions() >= 5 {
g.outcome = Draw
g.method = FivefoldRepetition
}
Expand Down Expand Up @@ -393,7 +424,7 @@ func (g *Game) Clone() *Game {
}
}

func (g *Game) numOfRepitions() int {
func (g *Game) numOfRepetitions() int {
count := 0
for _, pos := range g.Positions() {
if g.pos.samePosition(pos) {
Expand Down
32 changes: 23 additions & 9 deletions game_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ func TestCheckmate(t *testing.T) {
if g.Outcome() != WhiteWon {
t.Fatalf("expected outcome %s but got %s", WhiteWon, g.Outcome())
}

}

func TestCheckmateFromFen(t *testing.T) {
Expand Down Expand Up @@ -92,7 +91,7 @@ func TestInvalidStalemate(t *testing.T) {
}
}

func TestThreeFoldRepition(t *testing.T) {
func TestThreeFoldRepetition(t *testing.T) {
g := NewGame()
moves := []string{
"Nf3", "Nf6", "Ng1", "Ng8",
Expand All @@ -107,11 +106,11 @@ func TestThreeFoldRepition(t *testing.T) {
for _, pos := range g.Positions() {
log.Println(pos.String())
}
t.Fatalf("%s - %d reps", err.Error(), g.numOfRepitions())
t.Fatalf("%s - %d reps", err.Error(), g.numOfRepetitions())
}
}

func TestInvalidThreeFoldRepition(t *testing.T) {
func TestInvalidThreeFoldRepetition(t *testing.T) {
g := NewGame()
moves := []string{
"Nf3", "Nf6", "Ng1", "Ng8",
Expand All @@ -126,7 +125,7 @@ func TestInvalidThreeFoldRepition(t *testing.T) {
}
}

func TestFiveFoldRepition(t *testing.T) {
func TestFiveFoldRepetition(t *testing.T) {
g := NewGame()
moves := []string{
"Nf3", "Nf6", "Ng1", "Ng8",
Expand Down Expand Up @@ -171,7 +170,7 @@ func TestSeventyFiveMoveRule(t *testing.T) {
}
}

func TestInsufficentMaterial(t *testing.T) {
func TestInsufficientMaterial(t *testing.T) {
fens := []string{
"8/2k5/8/8/8/3K4/8/8 w - - 1 1",
"8/2k5/8/8/8/3K1N2/8/8 w - - 1 1",
Expand All @@ -187,12 +186,12 @@ func TestInsufficentMaterial(t *testing.T) {
g := NewGame(fen)
if g.Outcome() != Draw || g.Method() != InsufficientMaterial {
log.Println(g.Position().Board().Draw())
t.Fatalf("%s should automatically draw by insufficent material", f)
t.Fatalf("%s should automatically draw by insufficient material", f)
}
}
}

func TestSufficentMaterial(t *testing.T) {
func TestSufficientMaterial(t *testing.T) {
fens := []string{
"8/2k5/8/8/8/3K1B2/4N3/8 w - - 1 1",
"8/2k5/8/8/8/3KBB2/8/8 w - - 1 1",
Expand All @@ -209,7 +208,7 @@ func TestSufficentMaterial(t *testing.T) {
g := NewGame(fen)
if g.Outcome() != NoOutcome {
log.Println(g.Position().Board().Draw())
t.Fatalf("%s should not find insufficent material", f)
t.Fatalf("%s should not find insufficient material", f)
}
}
}
Expand Down Expand Up @@ -266,6 +265,21 @@ func TestPositionHash(t *testing.T) {
}
}

func TestMoveHistory(t *testing.T) {
lens := []int{89, 89, 5, 26}
for i, test := range validPGNs[0:4] {
pgn, err := PGN(strings.NewReader(test.PGN))
if err != nil {
t.Fatal(err)
}
game := NewGame(pgn)
l := len(game.MoveHistory())
if lens[i] != l {
t.Fatalf("expected history length to be %d but got %d", lens[i], l)
}
}
}

func BenchmarkStalemateStatus(b *testing.B) {
fenStr := "k1K5/8/8/8/8/8/8/1Q6 w - - 0 1"
fen, err := FEN(fenStr)
Expand Down
7 changes: 0 additions & 7 deletions notation.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,3 @@ func pieceTypeFromChar(c string) PieceType {
}
return NoPieceType
}

func removeSubstrings(s string, subs ...string) string {
for _, sub := range subs {
s = strings.Replace(s, sub, "", -1)
}
return s
}
2 changes: 1 addition & 1 deletion opening/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# opening

**opening** provides interactivity to opening books such as [Encyclopaedia of Chess Openings](https://en.wikipedia.org/wiki/Encyclopaedia_of_Chess_Openings) (ECO) which is loadable from the package.
**opening** provides interactivity to opening books such as [Encyclopaedia of Chess Openings](https://en.wikipedia.org/wiki/Encyclopaedia_of_Chess_Openings) (ECO) which is loadable from the package. Source: https://github.com/lichess-org/chess-openings

## Visual

Expand Down
Loading

0 comments on commit e9ff96c

Please sign in to comment.