commit 369af2097d8c25609d806c5b30d738c537aa14d9 Author: Gregory Rudolph Date: Fri Oct 30 12:09:15 2020 -0400 Initial commit from fork diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0460667 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +# VS Code +.vscode + +# Benchmarking +cpu.out + +# testing directories +cmd \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a31aebc --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Logan Spears + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e3928c --- /dev/null +++ b/README.md @@ -0,0 +1,396 @@ +# chess +[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://godoc.org/github.com/notnil/chess) +[![Coverage Status](https://coveralls.io/repos/notnil/chess/badge.svg?branch=master&service=github)](https://coveralls.io/github/notnil/chess?branch=master) +[![Go Report Card](https://goreportcard.com/badge/notnil/chess)](https://goreportcard.com/report/notnil/chess) +[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/notnil/chess/master/LICENSE) + +## Introduction + +Chess is a go library which provides common chess utilities such as move generation, turn management, checkmate detection, PGN encoding, and others. It is well tested and optimized for performance. + +## Installation + +The package can be installed using "go get". + +```bash +go get -u github.com/notnil/chess +``` + +## Usage + +### 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: + +```go +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: + +```go +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): + +```go +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: + +```go +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: + +```go +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: + +```go +game := chess.NewGame() +game.Draw(chess.DrawOffer) +fmt.Println(game.Outcome()) // 1/2-1/2 +fmt.Println(game.Method()) // DrawOffer +``` + +#### Threefold Repetition + +[Threefold repetition](https://en.wikipedia.org/wiki/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. + +```go +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](http://www.fide.com/component/handbook/?id=171&view=article) if a position repeats five times then the game is drawn automatically. + +```go +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](https://en.wikipedia.org/wiki/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. + +```go +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](http://www.fide.com/component/handbook/?id=171&view=article) 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. + +```go +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](https://en.wikipedia.org/wiki/Draw_%28chess%29#Draws_in_all_games), or insufficient material, results when neither white or black has the pieces remaining to checkmate the opponent. + +```go +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](https://en.wikipedia.org/wiki/Portable_Game_Notation), 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: + +```go +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: + +```go +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 * +*/ +``` + +### FEN + +[FEN](https://en.wikipedia.org/wiki/Forsyth–Edwards_Notation), 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](https://en.wikipedia.org/wiki/Fifty-move_rule)), and full move counter. + +#### Read FEN + +FEN supplied as an optional parameter to the NewGame constructor: + +```go +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: + +```go +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](https://en.wikipedia.org/wiki/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](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") +fmt.Println(game) // 1.e4 e5 * +``` + +#### Long AlgebraicNotation Notation + +LongAlgebraicNotation is a more computer friendly alternative to algebraic notation. This notation uses the same format as the UCI (Universal Chess Interface). Examples: e2e4, e7e5, e1g1 (white short castling), e7e8q (for promotion) + +```go +game := chess.NewGame(chess.UseNotation(chess.LongAlgebraicNotation{})) +game.MoveStr("e2e4") +game.MoveStr("e7e5") +fmt.Println(game) // 1.e2e4 e7e5 * +``` + +### Visualization + +Chess has multiple ways to visualize board positions for debugging and presentation. + +#### Text Representation + +Board's Draw() method can be used to visualize a position using unicode chess symbols. + +```go +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♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖ +*/ +``` + +#### SVG Representation + +[chess/image](https://github.com/notnil/chess/image) is an image utility designed to work with the chess package to render an SVG of a chess position. It presents configuration options such as dark and white square colors and squares to highlight. + +```go +// create file +f, err := os.Create("example.svg") +if err != nil { + // handle error +} +defer f.Close() + +// create board position +fenStr := "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1" +pos := &chess.Position{} +if err := pos.UnmarshalText([]byte(fenStr)); err != nil { + // handle error +} + +// write board SVG to file +yellow := color.RGBA{255, 255, 0, 1} +mark := image.MarkSquares(yellow, chess.D2, chess.D4) +if err := image.SVG(f, pos.Board(), mark); err != nil { + // handle error +} +``` + +![rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1](example.png) + +### Example Program + +Valid moves are randomly selected until the game is over: +```go +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()) +} +``` + +## 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. + +### 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 +``` diff --git a/assets/valid_notation_tests.json b/assets/valid_notation_tests.json new file mode 100644 index 0000000..b8ac006 --- /dev/null +++ b/assets/valid_notation_tests.json @@ -0,0 +1,135 @@ +[ + { + "pos1" : "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "pos2" : "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", + "algText" : "e4", + "longAlgText" : "e2e4", + "description" : "opening move" + }, + { + "pos1" : "rn1qkb1r/pp3ppp/2p1pn2/3p4/2PP4/2NQPN2/PP3PPP/R1B1K2R b KQkq - 0 7", + "pos2" : "r2qkb1r/pp1n1ppp/2p1pn2/3p4/2PP4/2NQPN2/PP3PPP/R1B1K2R w KQkq - 1 8", + "algText" : "Nbd7", + "longAlgText" : "b8d7", + "description" : "http://en.lichess.org/W91M4jms#14" + }, + { + "pos1" : "r2qk2r/pp1n1ppp/2pbpn2/3p4/2PP4/1PNQPN2/P4PPP/R1B1K2R w KQkq - 1 9", + "pos2" : "r2qk2r/pp1n1ppp/2pbpn2/3p4/2PP4/1PNQPN2/P4PPP/R1B2RK1 b kq - 0 9", + "algText" : "O-O", + "longAlgText" : "e1g1", + "description" : "http://en.lichess.org/W91M4jms#17" + }, + { + "pos1" : "3r1rk1/pp1nqppp/2pbpn2/3p4/2PP4/1PNQPN2/PB3PPP/3RR1K1 b - - 5 12", + "pos2" : "3r1rk1/pp1nqppp/2pbpn2/8/2pP4/1PNQPN2/PB3PPP/3RR1K1 w - - 0 13", + "algText" : "dxc4", + "longAlgText" : "d5c4", + "description" : "http://en.lichess.org/W91M4jms#24" + }, + { + "pos1" : "3r2k1/pp2qppp/2p2n2/4b3/2P5/2N1P3/PB2QPPP/3R2K1 w - - 1 18", + "pos2" : "3R2k1/pp2qppp/2p2n2/4b3/2P5/2N1P3/PB2QPPP/6K1 b - - 0 18", + "algText" : "Rxd8+", + "longAlgText" : "d1d8", + "description" : "http://en.lichess.org/W91M4jms#35" + }, + { + "pos1" : "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq d3 0 1", + "pos2" : "rnbqkb1r/pppppppp/5n2/8/3P4/8/PPP1PPPP/RNBQKBNR w KQkq - 1 2", + "algText" : "Nf6", + "longAlgText" : "g8f6", + "description" : "https://en.lichess.org/W91M4jms#2" + }, + { + "pos1" : "r4rk1/ppbn1p2/2p1pn1p/q2p2pb/7N/PP1PP1PP/1BPN1PB1/R3QRK1 w - g6 0 14", + "pos2" : "r4rk1/ppbn1p2/2p1pn1p/q2p2pb/8/PP1PPNPP/1BPN1PB1/R3QRK1 b - - 1 14", + "algText" : "Nhf3", + "longAlgText" : "h4f3", + "description" : "http://en.lichess.org/4HXJOtpN#27" + }, + { + "pos1" : "4r3/8/2p2PPk/1p1r4/pP2p1R1/P1B5/2P2K2/8 b - - 0 44", + "pos2" : "4r3/8/2p2PPk/1p6/pP2p1R1/P1B5/2P2K2/3r4 w - - 1 45", + "algText" : "Rd1??", + "longAlgText" : "d5d1", + "description" : "http://en.lichess.org/4HXJOtpN#87" + }, + { + "pos1" : "8/3k2Kp/p7/1p1r4/6P1/8/Pn1p4/7R b - - 0 40", + "pos2" : "8/3k2Kp/p7/1p1r4/6P1/8/Pn6/3q3R w - - 0 41", + "algText" : "d1=Q", + "longAlgText" : "d2d1q", + "description" : "http://en.lichess.org/YXPuk8kg#80" + }, + { + "pos1" : "rnbk1b1r/p3pppp/5n2/2p1p3/5B2/2N2P2/PPP3PP/R3KBNR w KQ - 0 10", + "pos2" : "rnbk1b1r/p3pppp/5n2/2p1p3/5B2/2N2P2/PPP3PP/2KR1BNR b - - 0 10", + "algText" : "O-O-O+", + "longAlgText" : "e1c1", + "description" : "http://en.lichess.org/dimuEVR0#19" + }, + { + "pos1" : "4R3/1r1k2pp/p1p5/1pP5/8/8/1PP3PP/2K1Rr2 w - - 5 32", + "pos2" : "8/1r1kR1pp/p1p5/1pP5/8/8/1PP3PP/2K1Rr2 b - - 6 32", + "algText" : "Re7+", + "longAlgText" : "e8e7", + "description" : "only 1 rook can move because of pin http://en.lichess.org/JCRBhXH7#62" + }, + { + "pos1" : "rn1qkb1r/pp3ppp/2p1pn2/3p4/2PP4/2NbPN2/PP3PPP/R1BQK2R w KQkq - 0 7", + "pos2" : "rn1qkb1r/pp3ppp/2p1pn2/3p4/2PP4/2NQPN2/PP3PPP/R1B1K2R b KQkq - 0 7", + "algText" : "Qxd3", + "longAlgText" : "d1d3", + "description" : "https://en.lichess.org/editor/rn1qkb1r/pp3ppp/2p1pn2/3p4/2PP4/2NbPN2/PP3PPP/R1BQK2R_w_KQkq_-_0_7" + }, + { + "pos1" : "7k/pBp2Rb1/4Q2p/4p3/1P4p1/2P4q/P2r1P2/6K1 b - - 1 38", + "pos2" : "7k/pBp2Rb1/4Q2p/4p3/1P4p1/2P4q/P4P2/3r2K1 w - - 2 39", + "algText" : "Rd1#", + "longAlgText" : "d2d1", + "description" : "https://en.lichess.org/editor/7k/pBp2Rb1/4Q2p/4p3/1P4p1/2P4q/P2r1P2/6K1_b_-_-_1_38" + }, + { + "pos1" : "r1b2rk1/pp2b1pp/1qn1p3/3pPp2/1P1P4/P2BPN2/6PP/RN1Q1RK1 w - f6 0 13", + "pos2" : "r1b2rk1/pp2b1pp/1qn1pP2/3p4/1P1P4/P2BPN2/6PP/RN1Q1RK1 b - - 0 13", + "algText" : "exf6", + "longAlgText" : "e5f6", + "description" : "https://en.lichess.org/editor/r1b2rk1/pp2b1pp/1qn1p3/3pPp2/1P1P4/P2BPN2/6PP/RN1Q1RK1_w_-_f6_0_13" + }, + { + "pos1" : "3r2k1/b4p2/p1p4p/6p1/4pNb1/1P2P2P/PB3PP1/R5K1 w - g6 0 27", + "pos2" : "3r2k1/b4p2/p1p3Np/6p1/4p1b1/1P2P2P/PB3PP1/R5K1 b - - 1 27", + "algText" : "Ng6", + "longAlgText" : "f4g6", + "description" : "https://en.lichess.org/editor/3r2k1/b4p2/p1p4p/6p1/4pNb1/1P2P2P/PB3PP1/R5K1_w_-_g6_0_27" + }, + { + "pos1" : "r1b1k2r/ppp2ppp/2p2n2/4N3/4P3/2P5/PPP2PPP/R1BK3R b kq - 0 8", + "pos2" : "r1b1k2r/ppp2ppp/2p5/4N3/4n3/2P5/PPP2PPP/R1BK3R w kq - 0 9", + "algText" : "Nxe4", + "longAlgText" : "f6e4", + "description" : "https://en.lichess.org/editor/r1b1k2r/ppp2ppp/2p2n2/4N3/4P3/2P5/PPP2PPP/R1BK3R_b_kq_-_0_8" + }, + { + "pos1" : "r1bqk2r/ppp2ppp/2p2n2/4N3/4P3/2P5/PPP2PPP/R1BQK2R b KQkq - 0 7", + "pos2" : "r1b1k2r/ppp2ppp/2p2n2/4N3/4P3/2P5/PPP2PPP/R1BqK2R w KQkq - 0 8", + "algText" : "Qxd1", + "longAlgText" : "d8d1", + "description" : "https://en.lichess.org/editor/r1bqk2r/ppp2ppp/2p2n2/4N3/4P3/2P5/PPP2PPP/R1BQK2R_b_KQkq_-_0_7" + }, + { + "pos1" : "2k5/6K1/8/8/8/8/4qq2/4q3 b - - 9 55", + "pos2" : "2k5/6K1/8/8/8/8/4qq2/5q2 w - - 10 56", + "algText" : "Q1f1", + "longAlgText" : "e1f1", + "description" : "https://lichess.org/editor/2k5/6K1/8/8/8/8/4qq2/4q3_b_-_-_9_55" + }, + { + "pos1" : "8/2B4R/1N6/2Q5/1N1N4/2N5/5k2/1K5R w - - 17 72", + "pos2" : "8/2B4R/8/2QN4/1N1N4/2N5/5k2/1K5R b - - 18 72", + "algText" : "N6d5", + "longAlgText" : "b6d5", + "description" : "https://lichess.org/editor/8/2B4R/1N6/2Q5/1N1N4/2N5/5k2/1K5R_w_-_-_17_72" + } +] diff --git a/bitboard.go b/bitboard.go new file mode 100644 index 0000000..f685b5d --- /dev/null +++ b/bitboard.go @@ -0,0 +1,61 @@ +package chess + +import ( + "strconv" + "strings" +) + +// bitboard is a board representation encoded in an unsigned 64-bit integer. The +// 64 board positions begin with A1 as the most significant bit and H8 as the least. +type bitboard uint64 + +func newBitboard(m map[Square]bool) bitboard { + s := "" + for sq := 0; sq < numOfSquaresInBoard; sq++ { + if m[Square(sq)] { + s += "1" + } else { + s += "0" + } + } + bb, err := strconv.ParseUint(s, 2, 64) + if err != nil { + panic(err) + } + return bitboard(bb) +} + +func (b bitboard) Mapping() map[Square]bool { + m := map[Square]bool{} + for sq := 0; sq < numOfSquaresInBoard; sq++ { + if b&bbForSquare(Square(sq)) > 0 { + m[Square(sq)] = true + } + } + return m +} + +// String returns a 64 character string of 1s and 0s starting with the most significant bit. +func (b bitboard) String() string { + s := strconv.FormatUint(uint64(b), 2) + return strings.Repeat("0", numOfSquaresInBoard-len(s)) + s +} + +// Draw returns visual representation of the bitboard useful for debugging. +func (b bitboard) Draw() string { + s := "\n A B C D E F G H\n" + for r := 7; r >= 0; r-- { + s += Rank(r).String() + for f := 0; f < numOfSquaresInRow; f++ { + sq := getSquare(File(f), Rank(r)) + if b.Occupied(sq) { + s += "1" + } else { + s += "0" + } + s += " " + } + s += "\n" + } + return s +} diff --git a/bitboard_postbits.go b/bitboard_postbits.go new file mode 100644 index 0000000..5ea2bdb --- /dev/null +++ b/bitboard_postbits.go @@ -0,0 +1,15 @@ +// +build go1.9 + +package chess + +import "math/bits" + +// Reverse returns a bitboard where the bit order is reversed. +func (b bitboard) Reverse() bitboard { + return bitboard(bits.Reverse64(uint64(b))) +} + +// Occupied returns true if the square's bitboard position is 1. +func (b bitboard) Occupied(sq Square) bool { + return (bits.RotateLeft64(uint64(b), int(sq)+1) & 1) == 1 +} diff --git a/bitboard_prebits.go b/bitboard_prebits.go new file mode 100644 index 0000000..c29443f --- /dev/null +++ b/bitboard_prebits.go @@ -0,0 +1,42 @@ +// +build !go1.9 + +package chess + +// Reverse returns a bitboard where the bit order is reversed. +// Implementation from: http://stackoverflow.com/questions/746171/best-algorithm-for-bit-reversal-from-msb-lsb-to-lsb-msb-in-c +func (b bitboard) Reverse() bitboard { + return bitboard((bitReverseLookupTable[b&0xff] << 56) | + (bitReverseLookupTable[(b>>8)&0xff] << 48) | + (bitReverseLookupTable[(b>>16)&0xff] << 40) | + (bitReverseLookupTable[(b>>24)&0xff] << 32) | + (bitReverseLookupTable[(b>>32)&0xff] << 24) | + (bitReverseLookupTable[(b>>40)&0xff] << 16) | + (bitReverseLookupTable[(b>>48)&0xff] << 8) | + (bitReverseLookupTable[(b>>56)&0xff])) +} + +var bitReverseLookupTable = []uint64{ + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF, +} + +// Occupied returns true if the square's bitboard position is 1. +func (b bitboard) Occupied(sq Square) bool { + return (uint64(b) >> uint64(63-sq) & 1) == 1 +} + +// diff --git a/bitboard_test.go b/bitboard_test.go new file mode 100644 index 0000000..ecc486b --- /dev/null +++ b/bitboard_test.go @@ -0,0 +1,55 @@ +package chess + +import "testing" + +type bitboardTestPair struct { + initial uint64 + reversed uint64 +} + +var ( + tests = []bitboardTestPair{ + bitboardTestPair{ + uint64(1), + uint64(9223372036854775808), + }, + bitboardTestPair{ + uint64(18446744073709551615), + uint64(18446744073709551615), + }, + bitboardTestPair{ + uint64(0), + uint64(0), + }, + } +) + +func TestBitboardReverse(t *testing.T) { + for _, p := range tests { + r := uint64(bitboard(p.initial).Reverse()) + if r != p.reversed { + t.Fatalf("bitboard reverse of %s expected %s but got %s", intStr(p.initial), intStr(p.reversed), intStr(r)) + } + } +} + +func TestBitboardOccupied(t *testing.T) { + m := map[Square]bool{ + B3: true, + } + bb := newBitboard(m) + if bb.Occupied(B3) != true { + t.Fatalf("bitboard occupied of %s expected %t but got %t", bb, true, false) + } +} + +func BenchmarkBitboardReverse(b *testing.B) { + for i := 0; i < b.N; i++ { + u := uint64(9223372036854775807) + bitboard(u).Reverse() + } +} + +func intStr(i uint64) string { + return bitboard(i).String() +} diff --git a/board.go b/board.go new file mode 100644 index 0000000..980ff07 --- /dev/null +++ b/board.go @@ -0,0 +1,424 @@ +package chess + +import ( + "bytes" + "encoding/binary" + "errors" + "strconv" + "strings" +) + +// A Board represents a chess board and its relationship between squares and pieces. +type Board struct { + bbWhiteKing bitboard + bbWhiteQueen bitboard + bbWhiteRook bitboard + bbWhiteBishop bitboard + bbWhiteKnight bitboard + bbWhitePawn bitboard + bbBlackKing bitboard + bbBlackQueen bitboard + bbBlackRook bitboard + bbBlackBishop bitboard + bbBlackKnight bitboard + bbBlackPawn bitboard + whiteSqs bitboard + blackSqs bitboard + emptySqs bitboard + whiteKingSq Square + blackKingSq Square +} + +// NewBoard returns a board from a square to piece mapping. +func NewBoard(m map[Square]Piece) *Board { + b := &Board{} + for _, p1 := range allPieces { + bm := map[Square]bool{} + for sq, p2 := range m { + if p1 == p2 { + bm[sq] = true + } + } + bb := newBitboard(bm) + b.setBBForPiece(p1, bb) + } + b.calcConvienceBBs(nil) + return b +} + +// SquareMap returns a mapping of squares to pieces. A square is only added to the map if it is occupied. +func (b *Board) SquareMap() map[Square]Piece { + m := map[Square]Piece{} + for sq := 0; sq < numOfSquaresInBoard; sq++ { + p := b.Piece(Square(sq)) + if p != NoPiece { + m[Square(sq)] = p + } + } + return m +} + +// Rotate rotates the board 90 degrees clockwise. +func (b *Board) Rotate() *Board { + return b.Flip(UpDown).Transpose() +} + +// FlipDirection is the direction for the Board.Flip method +type FlipDirection int + +const ( + // UpDown flips the board's rank values + UpDown FlipDirection = iota + // LeftRight flips the board's file values + LeftRight +) + +// Flip flips the board over the vertical or hoizontal +// center line. +func (b *Board) Flip(fd FlipDirection) *Board { + m := map[Square]Piece{} + for sq := 0; sq < numOfSquaresInBoard; sq++ { + var mv Square + switch fd { + case UpDown: + file := Square(sq).File() + rank := Rank(7 - Square(sq).Rank()) + mv = getSquare(file, rank) + case LeftRight: + file := File(7 - Square(sq).File()) + rank := Square(sq).Rank() + mv = getSquare(file, rank) + } + m[mv] = b.Piece(Square(sq)) + } + return NewBoard(m) +} + +// Transpose flips the board over the A8 to H1 diagonal. +func (b *Board) Transpose() *Board { + m := map[Square]Piece{} + for sq := 0; sq < numOfSquaresInBoard; sq++ { + file := File(7 - Square(sq).Rank()) + rank := Rank(7 - Square(sq).File()) + mv := getSquare(file, rank) + m[mv] = b.Piece(Square(sq)) + } + return NewBoard(m) +} + +// Draw returns visual representation of the board useful for debugging. +func (b *Board) Draw() string { + s := "\n A B C D E F G H\n" + for r := 7; r >= 0; r-- { + s += Rank(r).String() + for f := 0; f < numOfSquaresInRow; f++ { + p := b.Piece(getSquare(File(f), Rank(r))) + if p == NoPiece { + s += "-" + } else { + s += p.String() + } + s += " " + } + s += "\n" + } + return s +} + +// String implements the fmt.Stringer interface and returns +// a string in the FEN board format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR +func (b *Board) String() string { + fen := "" + for r := 7; r >= 0; r-- { + for f := 0; f < numOfSquaresInRow; f++ { + sq := getSquare(File(f), Rank(r)) + p := b.Piece(sq) + if p != NoPiece { + fen += p.getFENChar() + } else { + fen += "1" + } + } + if r != 0 { + fen += "/" + } + } + for i := 8; i > 1; i-- { + repeatStr := strings.Repeat("1", i) + countStr := strconv.Itoa(i) + fen = strings.Replace(fen, repeatStr, countStr, -1) + } + return fen +} + +// Piece returns the piece for the given square. +func (b *Board) Piece(sq Square) Piece { + for _, p := range allPieces { + bb := b.bbForPiece(p) + if bb.Occupied(sq) { + return p + } + } + return NoPiece +} + +// MarshalText implements the encoding.TextMarshaler interface and returns +// a string in the FEN board format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR +func (b *Board) MarshalText() (text []byte, err error) { + return []byte(b.String()), nil +} + +// UnmarshalText implements the encoding.TextUnarshaler interface and takes +// a string in the FEN board format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR +func (b *Board) UnmarshalText(text []byte) error { + cp, err := fenBoard(string(text)) + if err != nil { + return err + } + *b = *cp + return nil +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface and returns +// the bitboard representations as a array of bytes. Bitboads are encoded +// in the following order: WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight +// WhitePawn, BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn +func (b *Board) MarshalBinary() (data []byte, err error) { + bbs := []bitboard{b.bbWhiteKing, b.bbWhiteQueen, b.bbWhiteRook, b.bbWhiteBishop, b.bbWhiteKnight, b.bbWhitePawn, + b.bbBlackKing, b.bbBlackQueen, b.bbBlackRook, b.bbBlackBishop, b.bbBlackKnight, b.bbBlackPawn} + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, bbs) + return buf.Bytes(), err +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface and parses +// the bitboard representations as a array of bytes. Bitboads are decoded +// in the following order: WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight +// WhitePawn, BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn +func (b *Board) UnmarshalBinary(data []byte) error { + if len(data) != 96 { + return errors.New("chess: invalid number of bytes for board unmarshal binary") + } + b.bbWhiteKing = bitboard(binary.BigEndian.Uint64(data[:8])) + b.bbWhiteQueen = bitboard(binary.BigEndian.Uint64(data[8:16])) + b.bbWhiteRook = bitboard(binary.BigEndian.Uint64(data[16:24])) + b.bbWhiteBishop = bitboard(binary.BigEndian.Uint64(data[24:32])) + b.bbWhiteKnight = bitboard(binary.BigEndian.Uint64(data[32:40])) + b.bbWhitePawn = bitboard(binary.BigEndian.Uint64(data[40:48])) + b.bbBlackKing = bitboard(binary.BigEndian.Uint64(data[48:56])) + b.bbBlackQueen = bitboard(binary.BigEndian.Uint64(data[56:64])) + b.bbBlackRook = bitboard(binary.BigEndian.Uint64(data[64:72])) + b.bbBlackBishop = bitboard(binary.BigEndian.Uint64(data[72:80])) + b.bbBlackKnight = bitboard(binary.BigEndian.Uint64(data[80:88])) + b.bbBlackPawn = bitboard(binary.BigEndian.Uint64(data[88:96])) + b.calcConvienceBBs(nil) + return nil +} + +func (b *Board) update(m *Move) { + p1 := b.Piece(m.s1) + s1BB := bbForSquare(m.s1) + s2BB := bbForSquare(m.s2) + + // move s1 piece to s2 + for _, p := range allPieces { + bb := b.bbForPiece(p) + // remove what was at s2 + b.setBBForPiece(p, bb & ^s2BB) + // move what was at s1 to s2 + if bb.Occupied(m.s1) { + bb = b.bbForPiece(p) + b.setBBForPiece(p, (bb & ^s1BB)|s2BB) + } + } + // check promotion + if m.promo != NoPieceType { + newPiece := getPiece(m.promo, p1.Color()) + // remove pawn + bbPawn := b.bbForPiece(p1) + b.setBBForPiece(p1, bbPawn & ^s2BB) + // add promo piece + bbPromo := b.bbForPiece(newPiece) + b.setBBForPiece(newPiece, bbPromo|s2BB) + } + // remove captured en passant piece + if m.HasTag(EnPassant) { + if p1.Color() == White { + b.bbBlackPawn = ^(bbForSquare(m.s2) << 8) & b.bbBlackPawn + } else { + b.bbWhitePawn = ^(bbForSquare(m.s2) >> 8) & b.bbWhitePawn + } + } + // move rook for castle + if p1.Color() == White && m.HasTag(KingSideCastle) { + b.bbWhiteRook = (b.bbWhiteRook & ^bbForSquare(H1) | bbForSquare(F1)) + } else if p1.Color() == White && m.HasTag(QueenSideCastle) { + b.bbWhiteRook = (b.bbWhiteRook & ^bbForSquare(A1)) | bbForSquare(D1) + } else if p1.Color() == Black && m.HasTag(KingSideCastle) { + b.bbBlackRook = (b.bbBlackRook & ^bbForSquare(H8) | bbForSquare(F8)) + } else if p1.Color() == Black && m.HasTag(QueenSideCastle) { + b.bbBlackRook = (b.bbBlackRook & ^bbForSquare(A8)) | bbForSquare(D8) + } + b.calcConvienceBBs(m) +} + +func (b *Board) calcConvienceBBs(m *Move) { + whiteSqs := b.bbWhiteKing | b.bbWhiteQueen | b.bbWhiteRook | b.bbWhiteBishop | b.bbWhiteKnight | b.bbWhitePawn + blackSqs := b.bbBlackKing | b.bbBlackQueen | b.bbBlackRook | b.bbBlackBishop | b.bbBlackKnight | b.bbBlackPawn + emptySqs := ^(whiteSqs | blackSqs) + b.whiteSqs = whiteSqs + b.blackSqs = blackSqs + b.emptySqs = emptySqs + if m == nil { + b.whiteKingSq = NoSquare + b.blackKingSq = NoSquare + + for sq := 0; sq < numOfSquaresInBoard; sq++ { + sqr := Square(sq) + if b.bbWhiteKing.Occupied(sqr) { + b.whiteKingSq = sqr + } else if b.bbBlackKing.Occupied(sqr) { + b.blackKingSq = sqr + } + } + } else if m.s1 == b.whiteKingSq { + b.whiteKingSq = m.s2 + } else if m.s1 == b.blackKingSq { + b.blackKingSq = m.s2 + } +} + +func (b *Board) copy() *Board { + return &Board{ + whiteSqs: b.whiteSqs, + blackSqs: b.blackSqs, + emptySqs: b.emptySqs, + whiteKingSq: b.whiteKingSq, + blackKingSq: b.blackKingSq, + bbWhiteKing: b.bbWhiteKing, + bbWhiteQueen: b.bbWhiteQueen, + bbWhiteRook: b.bbWhiteRook, + bbWhiteBishop: b.bbWhiteBishop, + bbWhiteKnight: b.bbWhiteKnight, + bbWhitePawn: b.bbWhitePawn, + bbBlackKing: b.bbBlackKing, + bbBlackQueen: b.bbBlackQueen, + bbBlackRook: b.bbBlackRook, + bbBlackBishop: b.bbBlackBishop, + bbBlackKnight: b.bbBlackKnight, + bbBlackPawn: b.bbBlackPawn, + } +} + +func (b *Board) isOccupied(sq Square) bool { + return !b.emptySqs.Occupied(sq) +} + +func (b *Board) hasSufficientMaterial() bool { + // queen, rook, or pawn exist + if (b.bbWhiteQueen | b.bbWhiteRook | b.bbWhitePawn | + b.bbBlackQueen | b.bbBlackRook | b.bbBlackPawn) > 0 { + return true + } + // if king is missing then it is a test + if b.bbWhiteKing == 0 || b.bbBlackKing == 0 { + return true + } + count := map[PieceType]int{} + pieceMap := b.SquareMap() + for _, p := range pieceMap { + count[p.Type()]++ + } + // king versus king + if count[Bishop] == 0 && count[Knight] == 0 { + return false + } + // king and bishop versus king + if count[Bishop] == 1 && count[Knight] == 0 { + return false + } + // king and knight versus king + if count[Bishop] == 0 && count[Knight] == 1 { + return false + } + // king and bishop(s) versus king and bishop(s) with the bishops on the same colour. + if count[Knight] == 0 { + whiteCount := 0 + blackCount := 0 + for sq, p := range pieceMap { + if p.Type() == Bishop { + switch sq.color() { + case White: + whiteCount++ + case Black: + blackCount++ + } + } + } + if whiteCount == 0 || blackCount == 0 { + return false + } + } + return true +} + +func (b *Board) bbForPiece(p Piece) bitboard { + switch p { + case WhiteKing: + return b.bbWhiteKing + case WhiteQueen: + return b.bbWhiteQueen + case WhiteRook: + return b.bbWhiteRook + case WhiteBishop: + return b.bbWhiteBishop + case WhiteKnight: + return b.bbWhiteKnight + case WhitePawn: + return b.bbWhitePawn + case BlackKing: + return b.bbBlackKing + case BlackQueen: + return b.bbBlackQueen + case BlackRook: + return b.bbBlackRook + case BlackBishop: + return b.bbBlackBishop + case BlackKnight: + return b.bbBlackKnight + case BlackPawn: + return b.bbBlackPawn + } + return bitboard(0) +} + +func (b *Board) setBBForPiece(p Piece, bb bitboard) { + switch p { + case WhiteKing: + b.bbWhiteKing = bb + case WhiteQueen: + b.bbWhiteQueen = bb + case WhiteRook: + b.bbWhiteRook = bb + case WhiteBishop: + b.bbWhiteBishop = bb + case WhiteKnight: + b.bbWhiteKnight = bb + case WhitePawn: + b.bbWhitePawn = bb + case BlackKing: + b.bbBlackKing = bb + case BlackQueen: + b.bbBlackQueen = bb + case BlackRook: + b.bbBlackRook = bb + case BlackBishop: + b.bbBlackBishop = bb + case BlackKnight: + b.bbBlackKnight = bb + case BlackPawn: + b.bbBlackPawn = bb + default: + panic("invalid piece") + } +} diff --git a/board_test.go b/board_test.go new file mode 100644 index 0000000..275988e --- /dev/null +++ b/board_test.go @@ -0,0 +1,90 @@ +package chess + +import ( + "testing" +) + +func TestBoardTextSerialization(t *testing.T) { + fen := "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR" + b := &Board{} + if err := b.UnmarshalText([]byte(fen)); err != nil { + t.Fatal("recieved unexpected error", err) + } + txt, err := b.MarshalText() + if err != nil { + t.Fatal("recieved unexpected error", err) + } + if fen != string(txt) { + t.Fatalf("fen expected board string %s but got %s", fen, string(txt)) + } +} + +func TestBoardBinarySerialization(t *testing.T) { + g := NewGame() + board := g.Position().Board() + b, err := board.MarshalBinary() + if err != nil { + t.Fatal("recieved unexpected error", err) + } + cpBoard := &Board{} + err = cpBoard.UnmarshalBinary(b) + if err != nil { + t.Fatal("recieved unexpected error", err) + } + s := "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR" + if s != cpBoard.String() { + t.Fatalf("expected board string %s but got %s", s, cpBoard.String()) + } +} + +func TestBoardRotation(t *testing.T) { + fens := []string{ + "RP4pr/NP4pn/BP4pb/QP4pq/KP4pk/BP4pb/NP4pn/RP4pr", + "RNBKQBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbkqbnr", + "rp4PR/np4PN/bp4PB/kp4PK/qp4PQ/bp4PB/np4PN/rp4PR", + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR", + } + g := NewGame() + board := g.Position().Board() + for i := 0; i < 4; i++ { + board = board.Rotate() + if fens[i] != board.String() { + t.Fatalf("expected board string %s but got %s", fens[i], board.String()) + } + } +} + +func TestBoardFlip(t *testing.T) { + g := NewGame() + board := g.Position().Board() + board = board.Flip(UpDown) + b := "RNBQKBNR/PPPPPPPP/8/8/8/8/pppppppp/rnbqkbnr" + if b != board.String() { + t.Fatalf("expected board string %s but got %s", b, board.String()) + } + board = board.Flip(UpDown) + b = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR" + if b != board.String() { + t.Fatalf("expected board string %s but got %s", b, board.String()) + } + board = board.Flip(LeftRight) + b = "rnbkqbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBKQBNR" + if b != board.String() { + t.Fatalf("expected board string %s but got %s", b, board.String()) + } + board = board.Flip(LeftRight) + b = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR" + if b != board.String() { + t.Fatalf("expected board string %s but got %s", b, board.String()) + } +} + +func TestBoardTranspose(t *testing.T) { + g := NewGame() + board := g.Position().Board() + board = board.Transpose() + b := "rp4PR/np4PN/bp4PB/qp4PQ/kp4PK/bp4PB/np4PN/rp4PR" + if b != board.String() { + t.Fatalf("expected board string %s but got %s", b, board.String()) + } +} diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..f555f07 --- /dev/null +++ b/doc.go @@ -0,0 +1,50 @@ +/* +Package chess is a go library designed to accomplish the following: + - chess game / turn management + - move validation + - PGN encoding / decoding + - FEN encoding / decoding + +Using Moves + game := chess.NewGame() + moves := game.ValidMoves() + game.Move(moves[0]) + +Using Algebraic Notation + game := chess.NewGame() + game.MoveStr("e4") + +Using PGN + pgn, _ := chess.PGN(pgnReader) + game := chess.NewGame(pgn) + +Using FEN + fen, _ := chess.FEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + game := chess.NewGame(fen) + +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()) + } +*/ +package chess diff --git a/engine.go b/engine.go new file mode 100644 index 0000000..35a5eba --- /dev/null +++ b/engine.go @@ -0,0 +1,336 @@ +package chess + +type engine struct{} + +func (engine) CalcMoves(pos *Position, first bool) []*Move { + // generate possible moves + moves := standardMoves(pos, first) + // return moves including castles + return append(moves, castleMoves(pos)...) +} + +func (engine) Status(pos *Position) Method { + hasMove := false + if pos.validMoves != nil { + hasMove = len(pos.validMoves) > 0 + } else { + hasMove = len(engine{}.CalcMoves(pos, true)) > 0 + } + if !pos.inCheck && !hasMove { + return Stalemate + } else if pos.inCheck && !hasMove { + return Checkmate + } + return NoMethod +} + +var ( + promoPieceTypes = []PieceType{Queen, Rook, Bishop, Knight} +) + +func standardMoves(pos *Position, first bool) []*Move { + // compute allowed destination bitboard + bbAllowed := ^pos.board.whiteSqs + if pos.Turn() == Black { + bbAllowed = ^pos.board.blackSqs + } + moves := []*Move{} + // iterate through pieces to find possible moves + for _, p := range allPieces { + if pos.Turn() != p.Color() { + continue + } + // iterate through possible starting squares for piece + s1BB := pos.board.bbForPiece(p) + if s1BB == 0 { + continue + } + for s1 := 0; s1 < numOfSquaresInBoard; s1++ { + if s1BB&bbForSquare(Square(s1)) == 0 { + continue + } + // iterate through possible destination squares for piece + s2BB := bbForPossibleMoves(pos, p.Type(), Square(s1)) & bbAllowed + if s2BB == 0 { + continue + } + for s2 := 0; s2 < numOfSquaresInBoard; s2++ { + if s2BB&bbForSquare(Square(s2)) == 0 { + continue + } + // add promotions if pawn on promo square + if (p == WhitePawn && Square(s2).Rank() == Rank8) || (p == BlackPawn && Square(s2).Rank() == Rank1) { + for _, pt := range promoPieceTypes { + m := &Move{s1: Square(s1), s2: Square(s2), promo: pt} + addTags(m, pos) + // filter out moves that put king into check + if !m.HasTag(inCheck) { + moves = append(moves, m) + if first { + return moves + } + } + } + } else { + m := &Move{s1: Square(s1), s2: Square(s2)} + addTags(m, pos) + // filter out moves that put king into check + if !m.HasTag(inCheck) { + moves = append(moves, m) + if first { + return moves + } + } + } + } + } + } + return moves +} + +func addTags(m *Move, pos *Position) { + p := pos.board.Piece(m.s1) + if pos.board.isOccupied(m.s2) { + m.addTag(Capture) + } else if m.s2 == pos.enPassantSquare && p.Type() == Pawn { + m.addTag(EnPassant) + } + // determine if in check after move (makes move invalid) + cp := pos.copy() + cp.board.update(m) + if isInCheck(cp) { + m.addTag(inCheck) + } + // determine if opponent in check after move + cp.turn = cp.turn.Other() + if isInCheck(cp) { + m.addTag(Check) + } +} + +func isInCheck(pos *Position) bool { + kingSq := pos.board.whiteKingSq + if pos.Turn() == Black { + kingSq = pos.board.blackKingSq + } + // king should only be missing in tests / examples + if kingSq == NoSquare { + return false + } + return squaresAreAttacked(pos, kingSq) +} + +func squaresAreAttacked(pos *Position, sqs ...Square) bool { + otherColor := pos.Turn().Other() + occ := ^pos.board.emptySqs + for _, sq := range sqs { + // hot path check to see if attack vector is possible + s2BB := pos.board.blackSqs + if pos.Turn() == Black { + s2BB = pos.board.whiteSqs + } + if ((diaAttack(occ, sq)|hvAttack(occ, sq))&s2BB)|(bbKnightMoves[sq]&s2BB) == 0 { + continue + } + // check queen attack vector + queenBB := pos.board.bbForPiece(getPiece(Queen, otherColor)) + bb := (diaAttack(occ, sq) | hvAttack(occ, sq)) & queenBB + if bb != 0 { + return true + } + // check rook attack vector + rookBB := pos.board.bbForPiece(getPiece(Rook, otherColor)) + bb = hvAttack(occ, sq) & rookBB + if bb != 0 { + return true + } + // check bishop attack vector + bishopBB := pos.board.bbForPiece(getPiece(Bishop, otherColor)) + bb = diaAttack(occ, sq) & bishopBB + if bb != 0 { + return true + } + // check knight attack vector + knightBB := pos.board.bbForPiece(getPiece(Knight, otherColor)) + bb = bbKnightMoves[sq] & knightBB + if bb != 0 { + return true + } + // check pawn attack vector + if pos.Turn() == White { + capRight := (pos.board.bbBlackPawn & ^bbFileH & ^bbRank1) << 7 + capLeft := (pos.board.bbBlackPawn & ^bbFileA & ^bbRank1) << 9 + bb = (capRight | capLeft) & bbForSquare(sq) + if bb != 0 { + return true + } + } else { + capRight := (pos.board.bbWhitePawn & ^bbFileH & ^bbRank8) >> 9 + capLeft := (pos.board.bbWhitePawn & ^bbFileA & ^bbRank8) >> 7 + bb = (capRight | capLeft) & bbForSquare(sq) + if bb != 0 { + return true + } + } + // check king attack vector + kingBB := pos.board.bbForPiece(getPiece(King, otherColor)) + bb = bbKingMoves[sq] & kingBB + if bb != 0 { + return true + } + } + return false +} + +func bbForPossibleMoves(pos *Position, pt PieceType, sq Square) bitboard { + switch pt { + case King: + return bbKingMoves[sq] + case Queen: + return diaAttack(^pos.board.emptySqs, sq) | hvAttack(^pos.board.emptySqs, sq) + case Rook: + return hvAttack(^pos.board.emptySqs, sq) + case Bishop: + return diaAttack(^pos.board.emptySqs, sq) + case Knight: + return bbKnightMoves[sq] + case Pawn: + return pawnMoves(pos, sq) + } + return bitboard(0) +} + +// TODO can calc isInCheck twice +func castleMoves(pos *Position) []*Move { + moves := []*Move{} + kingSide := pos.castleRights.CanCastle(pos.Turn(), KingSide) + queenSide := pos.castleRights.CanCastle(pos.Turn(), QueenSide) + // white king side + if pos.turn == White && kingSide && + (^pos.board.emptySqs&(bbForSquare(F1)|bbForSquare(G1))) == 0 && + !squaresAreAttacked(pos, F1, G1) && + !pos.inCheck { + m := &Move{s1: E1, s2: G1} + m.addTag(KingSideCastle) + moves = append(moves, m) + } + // white queen side + if pos.turn == White && queenSide && + (^pos.board.emptySqs&(bbForSquare(B1)|bbForSquare(C1)|bbForSquare(D1))) == 0 && + !squaresAreAttacked(pos, C1, D1) && + !pos.inCheck { + m := &Move{s1: E1, s2: C1} + m.addTag(QueenSideCastle) + moves = append(moves, m) + } + // black king side + if pos.turn == Black && kingSide && + (^pos.board.emptySqs&(bbForSquare(F8)|bbForSquare(G8))) == 0 && + !squaresAreAttacked(pos, F8, G8) && + !pos.inCheck { + m := &Move{s1: E8, s2: G8} + m.addTag(KingSideCastle) + moves = append(moves, m) + } + // black queen side + if pos.turn == Black && queenSide && + (^pos.board.emptySqs&(bbForSquare(B8)|bbForSquare(C8)|bbForSquare(D8))) == 0 && + !squaresAreAttacked(pos, C8, D8) && + !pos.inCheck { + m := &Move{s1: E8, s2: C8} + m.addTag(QueenSideCastle) + moves = append(moves, m) + } + return moves +} + +func pawnMoves(pos *Position, sq Square) bitboard { + bb := bbForSquare(sq) + var bbEnPassant bitboard + if pos.enPassantSquare != NoSquare { + bbEnPassant = bbForSquare(pos.enPassantSquare) + } + if pos.Turn() == White { + capRight := ((bb & ^bbFileH & ^bbRank8) >> 9) & (pos.board.blackSqs | bbEnPassant) + capLeft := ((bb & ^bbFileA & ^bbRank8) >> 7) & (pos.board.blackSqs | bbEnPassant) + upOne := ((bb & ^bbRank8) >> 8) & pos.board.emptySqs + upTwo := ((upOne & bbRank3) >> 8) & pos.board.emptySqs + return capRight | capLeft | upOne | upTwo + } + capRight := ((bb & ^bbFileH & ^bbRank1) << 7) & (pos.board.whiteSqs | bbEnPassant) + capLeft := ((bb & ^bbFileA & ^bbRank1) << 9) & (pos.board.whiteSqs | bbEnPassant) + upOne := ((bb & ^bbRank1) << 8) & pos.board.emptySqs + upTwo := ((upOne & bbRank6) << 8) & pos.board.emptySqs + return capRight | capLeft | upOne | upTwo +} + +func diaAttack(occupied bitboard, sq Square) bitboard { + pos := bbForSquare(sq) + dMask := bbDiagonals[sq] + adMask := bbAntiDiagonals[sq] + return linearAttack(occupied, pos, dMask) | linearAttack(occupied, pos, adMask) +} + +func hvAttack(occupied bitboard, sq Square) bitboard { + pos := bbForSquare(sq) + rankMask := bbRanks[Square(sq).Rank()] + fileMask := bbFiles[Square(sq).File()] + return linearAttack(occupied, pos, rankMask) | linearAttack(occupied, pos, fileMask) +} + +func linearAttack(occupied, pos, mask bitboard) bitboard { + oInMask := occupied & mask + return ((oInMask - 2*pos) ^ (oInMask.Reverse() - 2*pos.Reverse()).Reverse()) & mask +} + +const ( + bbFileA bitboard = 9259542123273814144 + bbFileB bitboard = 4629771061636907072 + bbFileC bitboard = 2314885530818453536 + bbFileD bitboard = 1157442765409226768 + bbFileE bitboard = 578721382704613384 + bbFileF bitboard = 289360691352306692 + bbFileG bitboard = 144680345676153346 + bbFileH bitboard = 72340172838076673 + + bbRank1 bitboard = 18374686479671623680 + bbRank2 bitboard = 71776119061217280 + bbRank3 bitboard = 280375465082880 + bbRank4 bitboard = 1095216660480 + bbRank5 bitboard = 4278190080 + bbRank6 bitboard = 16711680 + bbRank7 bitboard = 65280 + bbRank8 bitboard = 255 +) + +// TODO make method on Square +func bbForSquare(sq Square) bitboard { + return bbSquares[sq] +} + +var ( + bbFiles = [8]bitboard{bbFileA, bbFileB, bbFileC, bbFileD, bbFileE, bbFileF, bbFileG, bbFileH} + bbRanks = [8]bitboard{bbRank1, bbRank2, bbRank3, bbRank4, bbRank5, bbRank6, bbRank7, bbRank8} + + bbDiagonals = [64]bitboard{9241421688590303745, 4620710844295151872, 2310355422147575808, 1155177711073755136, 577588855528488960, 288794425616760832, 144396663052566528, 72057594037927936, 36099303471055874, 9241421688590303745, 4620710844295151872, 2310355422147575808, 1155177711073755136, 577588855528488960, 288794425616760832, 144396663052566528, 141012904183812, 36099303471055874, 9241421688590303745, 4620710844295151872, 2310355422147575808, 1155177711073755136, 577588855528488960, 288794425616760832, 550831656968, 141012904183812, 36099303471055874, 9241421688590303745, 4620710844295151872, 2310355422147575808, 1155177711073755136, 577588855528488960, 2151686160, 550831656968, 141012904183812, 36099303471055874, 9241421688590303745, 4620710844295151872, 2310355422147575808, 1155177711073755136, 8405024, 2151686160, 550831656968, 141012904183812, 36099303471055874, 9241421688590303745, 4620710844295151872, 2310355422147575808, 32832, 8405024, 2151686160, 550831656968, 141012904183812, 36099303471055874, 9241421688590303745, 4620710844295151872, 128, 32832, 8405024, 2151686160, 550831656968, 141012904183812, 36099303471055874, 9241421688590303745} + + bbAntiDiagonals = [64]bitboard{9223372036854775808, 4647714815446351872, 2323998145211531264, 1161999622361579520, 580999813328273408, 290499906672525312, 145249953336295424, 72624976668147840, 4647714815446351872, 2323998145211531264, 1161999622361579520, 580999813328273408, 290499906672525312, 145249953336295424, 72624976668147840, 283691315109952, 2323998145211531264, 1161999622361579520, 580999813328273408, 290499906672525312, 145249953336295424, 72624976668147840, 283691315109952, 1108169199648, 1161999622361579520, 580999813328273408, 290499906672525312, 145249953336295424, 72624976668147840, 283691315109952, 1108169199648, 4328785936, 580999813328273408, 290499906672525312, 145249953336295424, 72624976668147840, 283691315109952, 1108169199648, 4328785936, 16909320, 290499906672525312, 145249953336295424, 72624976668147840, 283691315109952, 1108169199648, 4328785936, 16909320, 66052, 145249953336295424, 72624976668147840, 283691315109952, 1108169199648, 4328785936, 16909320, 66052, 258, 72624976668147840, 283691315109952, 1108169199648, 4328785936, 16909320, 66052, 258, 1} + + bbKnightMoves = [64]bitboard{9077567998918656, 4679521487814656, 38368557762871296, 19184278881435648, 9592139440717824, 4796069720358912, 2257297371824128, 1128098930098176, 2305878468463689728, 1152939783987658752, 9799982666336960512, 4899991333168480256, 2449995666584240128, 1224997833292120064, 576469569871282176, 288234782788157440, 4620693356194824192, 11533718717099671552, 5802888705324613632, 2901444352662306816, 1450722176331153408, 725361088165576704, 362539804446949376, 145241105196122112, 18049583422636032, 45053588738670592, 22667534005174272, 11333767002587136, 5666883501293568, 2833441750646784, 1416171111120896, 567348067172352, 70506185244672, 175990581010432, 88545054707712, 44272527353856, 22136263676928, 11068131838464, 5531918402816, 2216203387392, 275414786112, 687463207072, 345879119952, 172939559976, 86469779988, 43234889994, 21609056261, 8657044482, 1075839008, 2685403152, 1351090312, 675545156, 337772578, 168886289, 84410376, 33816580, 4202496, 10489856, 5277696, 2638848, 1319424, 659712, 329728, 132096} + + bbBishopMoves = [64]bitboard{18049651735527937, 45053622886727936, 22667548931719168, 11334324221640704, 5667164249915392, 2833579985862656, 1416240237150208, 567382630219904, 4611756524879479810, 11529391036782871041, 5764696068147249408, 2882348036221108224, 1441174018118909952, 720587009051099136, 360293502378066048, 144117404414255168, 2323857683139004420, 1197958188344280066, 9822351133174399489, 4911175566595588352, 2455587783297826816, 1227793891648880768, 577868148797087808, 288793334762704928, 1161999073681608712, 581140276476643332, 326598935265674242, 9386671504487645697, 4693335752243822976, 2310639079102947392, 1155178802063085600, 577588851267340304, 580999811184992272, 290500455356698632, 145390965166737412, 108724279602332802, 9241705379636978241, 4620711952330133792, 2310355426409252880, 1155177711057110024, 290499906664153120, 145249955479592976, 72625527495610504, 424704217196612, 36100411639206946, 9241421692918565393, 4620710844311799048, 2310355422147510788, 145249953336262720, 72624976676520096, 283693466779728, 1659000848424, 141017232965652, 36099303487963146, 9241421688590368773, 4620710844295151618, 72624976668147712, 283691315142656, 1108177604608, 6480472064, 550848566272, 141012904249856, 36099303471056128, 9241421688590303744} + + bbRookMoves = [64]bitboard{9187484529235886208, 13781085504453754944, 16077885992062689312, 17226286235867156496, 17800486357769390088, 18087586418720506884, 18231136449196065282, 18302911464433844481, 9259260648297103488, 4665518383679160384, 2368647251370188832, 1220211685215703056, 645993902138460168, 358885010599838724, 215330564830528002, 143553341945872641, 9259541023762186368, 4629910699613634624, 2315095537539358752, 1157687956502220816, 578984165983651848, 289632270724367364, 144956323094725122, 72618349279904001, 9259542118978846848, 4629771607097753664, 2314886351157207072, 1157443723186933776, 578722409201797128, 289361752209228804, 144681423712944642, 72341259464802561, 9259542123257036928, 4629771063767613504, 2314885534022901792, 1157442769150545936, 578721386714368008, 289360695496279044, 144680349887234562, 72340177082712321, 9259542123273748608, 4629771061645230144, 2314885530830970912, 1157442765423841296, 578721382720276488, 289360691368494084, 144680345692602882, 72340172854657281, 9259542123273813888, 4629771061636939584, 2314885530818502432, 1157442765409283856, 578721382704674568, 289360691352369924, 144680345676217602, 72340172838141441, 9259542123273814143, 4629771061636907199, 2314885530818453727, 1157442765409226991, 578721382704613623, 289360691352306939, 144680345676153597, 72340172838076926} + + bbQueenMoves = [64]bitboard{9205534180971414145, 13826139127340482880, 16100553540994408480, 17237620560088797200, 17806153522019305480, 18090419998706369540, 18232552689433215490, 18303478847064064385, 13871017173176583298, 16194909420462031425, 8133343319517438240, 4102559721436811280, 2087167920257370120, 1079472019650937860, 575624067208594050, 287670746360127809, 11583398706901190788, 5827868887957914690, 12137446670713758241, 6068863523097809168, 3034571949281478664, 1517426162373248132, 722824471891812930, 361411684042608929, 10421541192660455560, 5210911883574396996, 2641485286422881314, 10544115227674579473, 5272058161445620104, 2600000831312176196, 1299860225776030242, 649930110732142865, 9840541934442029200, 4920271519124312136, 2460276499189639204, 1266167048752878738, 9820426766351346249, 4910072647826412836, 2455035776296487442, 1227517888139822345, 9550042029937901728, 4775021017124823120, 2387511058326581416, 1157867469641037908, 614821794359483434, 9530782384287059477, 4765391190004401930, 2382695595002168069, 9404792076610076608, 4702396038313459680, 2315169224285282160, 1157444424410132280, 578862399937640220, 325459994840333070, 9386102034266586375, 4693051017133293059, 9332167099941961855, 4630054752952049855, 2314886638996058335, 1157442771889699055, 578721933553179895, 289501704256556795, 180779649147209725, 9313761861428380670} + + bbKingMoves = [64]bitboard{4665729213955833856, 11592265440851656704, 5796132720425828352, 2898066360212914176, 1449033180106457088, 724516590053228544, 362258295026614272, 144959613005987840, 13853283560024178688, 16186183351374184448, 8093091675687092224, 4046545837843546112, 2023272918921773056, 1011636459460886528, 505818229730443264, 216739030602088448, 54114388906344448, 63227278716305408, 31613639358152704, 15806819679076352, 7903409839538176, 3951704919769088, 1975852459884544, 846636838289408, 211384331665408, 246981557485568, 123490778742784, 61745389371392, 30872694685696, 15436347342848, 7718173671424, 3307175149568, 825720045568, 964771708928, 482385854464, 241192927232, 120596463616, 60298231808, 30149115904, 12918652928, 3225468928, 3768639488, 1884319744, 942159872, 471079936, 235539968, 117769984, 50463488, 12599488, 14721248, 7360624, 3680312, 1840156, 920078, 460039, 197123, 49216, 57504, 28752, 14376, 7188, 3594, 1797, 770} + + bbSquares = [64]bitboard{} +) + +func init() { + for sq := 0; sq < 64; sq++ { + bbSquares[sq] = bitboard(uint64(1) << (uint8(63) - uint8(sq))) + } +} diff --git a/example.png b/example.png new file mode 100644 index 0000000..bea9a16 Binary files /dev/null and b/example.png differ diff --git a/fen.go b/fen.go new file mode 100644 index 0000000..4195da6 --- /dev/null +++ b/fen.go @@ -0,0 +1,155 @@ +package chess + +import ( + "fmt" + "strconv" + "strings" +) + +// Decodes FEN notation into a GameState. An error is returned +// if there is a parsing error. FEN notation format: +// rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 +func decodeFEN(fen string) (*Position, error) { + fen = strings.TrimSpace(fen) + parts := strings.Split(fen, " ") + if len(parts) != 6 { + return nil, fmt.Errorf("chess: fen invalid notiation %s must have 6 sections", fen) + } + b, err := fenBoard(parts[0]) + if err != nil { + return nil, err + } + turn, ok := fenTurnMap[parts[1]] + if !ok { + return nil, fmt.Errorf("chess: fen invalid turn %s", parts[1]) + } + rights, err := formCastleRights(parts[2]) + if err != nil { + return nil, err + } + sq, err := formEnPassant(parts[3]) + if err != nil { + return nil, err + } + halfMoveClock, err := strconv.Atoi(parts[4]) + if err != nil || halfMoveClock < 0 { + return nil, fmt.Errorf("chess: fen invalid half move clock %s", parts[4]) + } + moveCount, err := strconv.Atoi(parts[5]) + if err != nil || moveCount < 1 { + return nil, fmt.Errorf("chess: fen invalid move count %s", parts[5]) + } + return &Position{ + board: b, + turn: turn, + castleRights: rights, + enPassantSquare: sq, + halfMoveClock: halfMoveClock, + moveCount: moveCount, + }, nil +} + +// generates board from fen format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR +func fenBoard(boardStr string) (*Board, error) { + rankStrs := strings.Split(boardStr, "/") + if len(rankStrs) != 8 { + return nil, fmt.Errorf("chess: fen invalid board %s", boardStr) + } + m := map[Square]Piece{} + for i, rankStr := range rankStrs { + rank := Rank(7 - i) + fileMap, err := fenFormRank(rankStr) + if err != nil { + return nil, err + } + for file, piece := range fileMap { + m[getSquare(file, rank)] = piece + } + } + return NewBoard(m), nil +} + +func fenFormRank(rankStr string) (map[File]Piece, error) { + count := 0 + m := map[File]Piece{} + err := fmt.Errorf("chess: fen invalid rank %s", rankStr) + for _, r := range rankStr { + c := fmt.Sprintf("%c", r) + piece := fenPieceMap[c] + if piece == NoPiece { + skip, err := strconv.Atoi(c) + if err != nil { + return nil, err + } + count += skip + continue + } + m[File(count)] = piece + count++ + } + if count != 8 { + return nil, err + } + return m, nil +} + +func formCastleRights(castleStr string) (CastleRights, error) { + // check for duplicates aka. KKkq right now is valid + for _, s := range []string{"K", "Q", "k", "q", "-"} { + if strings.Count(castleStr, s) > 1 { + return "-", fmt.Errorf("chess: fen invalid castle rights %s", castleStr) + } + } + for _, r := range castleStr { + c := fmt.Sprintf("%c", r) + switch c { + case "K", "Q", "k", "q", "-": + default: + return "-", fmt.Errorf("chess: fen invalid castle rights %s", castleStr) + } + } + return CastleRights(castleStr), nil +} + +func formEnPassant(enPassant string) (Square, error) { + if enPassant == "-" { + return NoSquare, nil + } + sq := strToSquareMap[enPassant] + if sq == NoSquare || !(sq.Rank() == Rank3 || sq.Rank() == Rank6) { + return NoSquare, fmt.Errorf("chess: fen invalid En Passant square %s", enPassant) + } + return sq, nil +} + +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, + "R": WhiteRook, + "B": WhiteBishop, + "N": WhiteKnight, + "P": WhitePawn, + "k": BlackKing, + "q": BlackQueen, + "r": BlackRook, + "b": BlackBishop, + "n": BlackKnight, + "p": BlackPawn, + } + + fenTurnMap = map[string]Color{ + "w": White, + "b": Black, + } +) diff --git a/fen_test.go b/fen_test.go new file mode 100644 index 0000000..ee6dd98 --- /dev/null +++ b/fen_test.go @@ -0,0 +1,54 @@ +package chess + +import "testing" + +var ( + validFENs = []string{ + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", + "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2", + "rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2", + "7k/8/8/8/8/8/8/R6K w - - 0 1", + "7k/8/8/8/8/8/8/2B1KB2 w - - 0 1", + "8/8/8/4k3/8/8/8/R3K2R w KQ - 0 1", + "8/8/8/8/4k3/8/3KP3/8 w - - 0 1", + "8/8/5k2/8/5K2/8/4P3/8 w - - 0 1", + "r4rk1/1b2bppp/ppq1p3/2pp3n/5P2/1P1BP3/PBPPQ1PP/R4RK1 w - - 0 1", + "3r1rk1/p3qppp/2bb4/2p5/3p4/1P2P3/PBQN1PPP/2R2RK1 w - - 0 1", + "4r1k1/1b3p1p/ppq3p1/2p5/8/1P3R1Q/PBP3PP/7K w - - 0 1", + "5k2/ppp5/4P3/3R3p/6P1/1K2Nr2/PP3P2/8 b - - 1 32", + } + + invalidFENs = []string{ + "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPP/RNBQKBNR w KQkq - 0 1", + "rnbqkbnr/pppppppp/8/8/4P2/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1", + "rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KKkq c6 0 2", + "rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq c12 1 2", + "7k/8/8/8/8/8/8/R6K w - - 0 -1", + "7k/8/8/8/8/8/8/2B1KB2 w - - -1 1", + "8/8/8/4k3/8/8/8/R3K2R w KQ - 0 0", + "8/8/8/8/4k3/8/3KP3/8 c - - 0 1", + "8/8/5k2/8/5K2/8/4P3P/8 w - - 0 1", + "r4rk1/1b2bppp/ppq1p3/2pp3n/5P2/1P1BP3/PBPPQ1PP/R4RK1 w e4 - 0 1", + } +) + +func TestValidFENs(t *testing.T) { + for _, f := range validFENs { + state, err := decodeFEN(f) + if err != nil { + t.Fatal("recieved unexpected error", err) + } + if f != state.String() { + t.Fatalf("fen expected board string %s but got %s", f, state.String()) + } + } +} + +func TestInvalidFENs(t *testing.T) { + for _, f := range invalidFENs { + if _, err := decodeFEN(f); err == nil { + t.Fatal("fen expected error from ", f) + } + } +} diff --git a/game.go b/game.go new file mode 100644 index 0000000..84ea26a --- /dev/null +++ b/game.go @@ -0,0 +1,402 @@ +package chess + +import ( + "errors" + "fmt" + "io" + "io/ioutil" +) + +const ( + startFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" +) + +// A Outcome is the result of a game. +type Outcome string + +const ( + // NoOutcome indicates that a game is in progress or ended without a result. + NoOutcome Outcome = "*" + // WhiteWon indicates that white won the game. + WhiteWon Outcome = "1-0" + // BlackWon indicates that black won the game. + BlackWon Outcome = "0-1" + // Draw indicates that game was a draw. + Draw Outcome = "1/2-1/2" +) + +// String implements the fmt.Stringer interface +func (o Outcome) String() string { + return string(o) +} + +// A Method is the method that generated the outcome. +type Method uint8 + +const ( + // NoMethod indicates that an outcome hasn't occurred or that the method can't be determined. + NoMethod Method = iota + // Checkmate indicates that the game was won checkmate. + Checkmate + // Resignation indicates that the game was won by resignation. + Resignation + // DrawOffer indicates that the game was drawn by a draw offer. + DrawOffer + // Stalemate indicates that the game was drawn by stalemate. + Stalemate + // ThreefoldRepetition indicates that the game was drawn when the game + // state was repeated three times and a player requested a draw. + ThreefoldRepetition + // FivefoldRepetition indicates that the game was automatically drawn + // by the game state being repeated five times. + FivefoldRepetition + // FiftyMoveRule indicates that the game was drawn by the half + // move clock being one hundred or greater when a player requested a draw. + FiftyMoveRule + // SeventyFiveMoveRule indicates that the game was automatically drawn + // when the half move clock was one hundred and fifty or greater. + SeventyFiveMoveRule + // InsufficientMaterial indicates that the game was automatically drawn + // because there was insufficient material for checkmate. + InsufficientMaterial +) + +// TagPair represents metadata in a key value pairing used in the PGN format. +type TagPair struct { + Key string + Value string +} + +// A Game represents a single chess game. +type Game struct { + Notation Notation + TagPairs []*TagPair + Moves []*Move + Positions []*Position + Pos *Position + Outcome Outcome + Method Method + IgnoreAutomaticDraws bool +} + +// PGN takes a reader and returns a function that updates +// the game to reflect the PGN data. The PGN can use any +// move notation supported by this package. The returned +// function is designed to be used in the NewGame constructor. +// An error is returned if there is a problem parsing the PGN data. +func PGN(r io.Reader) (func(*Game), error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + game, err := decodePGN(string(b)) + if err != nil { + return nil, err + } + return func(g *Game) { + g.copy(game) + }, nil +} + +// FEN takes a string and returns a function that updates +// the game to reflect the FEN data. Since FEN doesn't encode +// prior moves, the move list will be empty. The returned +// function is designed to be used in the NewGame constructor. +// An error is returned if there is a problem parsing the FEN data. +func FEN(fen string) (func(*Game), error) { + pos, err := decodeFEN(fen) + if err != nil { + return nil, err + } + return func(g *Game) { + pos.inCheck = isInCheck(pos) + g.Pos = pos + g.Positions = []*Position{pos} + g.updatePosition() + }, nil +} + +// TagPairs returns a function that sets the tag pairs +// to the given value. The returned function is designed +// to be used in the NewGame constructor. +func TagPairs(tagPairs []*TagPair) func(*Game) { + return func(g *Game) { + g.TagPairs = append([]*TagPair(nil), tagPairs...) + } +} + +// UseNotation returns a function that sets the game's notation +// to the given value. The notation is used to parse the +// string supplied to the MoveStr() method as well as the +// any PGN output. The returned function is designed +// to be used in the NewGame constructor. +func UseNotation(n Notation) func(*Game) { + return func(g *Game) { + g.Notation = n + } +} + +// 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 { + if f != nil { + f(game) + } + } + return game +} + +// 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 { + valid := moveSlice(g.ValidMoves()).find(m) + if valid == nil { + return fmt.Errorf("chess: invalid move %s", m) + } + g.Moves = append(g.Moves, valid) + g.Pos = g.Pos.Update(valid) + g.Positions = append(g.Positions, g.Pos) + g.updatePosition() + return nil +} + +// MoveStr decodes the given string in game's notation +// and calls the Move function. An error is returned if +// the move can't be decoded or the move is invalid. +func (g *Game) MoveStr(s string) error { + m, err := g.Notation.Decode(g.Pos, s) + if err != nil { + return err + } + return g.Move(m) +} + +// ValidMoves returns a list of valid moves in the +// current position. +func (g *Game) ValidMoves() []*Move { + return g.Pos.ValidMoves() +} + +// PositionsHistory returns the position history of the game. +func (g *Game) PositionsHistory() []*Position { + return append([]*Position(nil), g.Positions...) +} + +// MovesHistory returns the move history of the game. +func (g *Game) MovesHistory() []*Move { + return append([]*Move(nil), g.Moves...) +} + +// GetTagPairs returns the game's tag pairs. +func (g *Game) GetTagPairs() []*TagPair { + return append([]*TagPair(nil), g.TagPairs...) +} + +// Position returns the game's current position. +func (g *Game) Position() *Position { + return g.Pos +} + +// GameOutcome returns the game outcome. +func (g *Game) GameOutcome() Outcome { + return g.Outcome +} + +// OutcomeMethod returns the method in which the outcome occurred. +func (g *Game) OutcomeMethod() Method { + return g.Method +} + +// FEN returns the FEN notation of the current position. +func (g *Game) FEN() string { + return g.Pos.String() +} + +// String implements the fmt.Stringer interface and returns +// the game's PGN. +func (g *Game) String() string { + return encodePGN(g) +} + +// MarshalText implements the encoding.TextMarshaler interface and +// encodes the game's PGN. +func (g *Game) MarshalText() (text []byte, err error) { + return []byte(encodePGN(g)), nil +} + +// UnmarshalText implements the encoding.TextUnarshaler interface and +// assumes the data is in the PGN format. +func (g *Game) UnmarshalText(text []byte) error { + game, err := decodePGN(string(text)) + if err != nil { + return err + } + g.copy(game) + return nil +} + +// Draw attempts to draw the game by the given method. If the +// method is valid, then the game is updated to a draw by that +// method. If the method isn't valid then an error is returned. +func (g *Game) Draw(method Method) error { + switch method { + case ThreefoldRepetition: + if g.numOfRepitions() < 3 { + return errors.New("chess: draw by ThreefoldRepetition requires at least three repetitions of the current board state") + } + case FiftyMoveRule: + if g.Pos.halfMoveClock < 100 { + return fmt.Errorf("chess: draw by FiftyMoveRule requires the half move clock to be at 100 or greater but is %d", g.Pos.halfMoveClock) + } + case DrawOffer: + default: + return fmt.Errorf("chess: unsupported draw method %s", method.String()) + } + g.Outcome = Draw + g.Method = method + return nil +} + +// Resign resigns the game for the given color. If the game has +// already been completed then the game is not updated. +func (g *Game) Resign(color Color) { + if g.Outcome != NoOutcome || color == NoColor { + return + } + if color == White { + g.Outcome = BlackWon + } else { + g.Outcome = WhiteWon + } + g.Method = Resignation +} + +// EligibleDraws returns valid inputs for the Draw() method. +func (g *Game) EligibleDraws() []Method { + draws := []Method{DrawOffer} + if g.numOfRepitions() >= 3 { + draws = append(draws, ThreefoldRepetition) + } + if g.Pos.halfMoveClock >= 100 { + draws = append(draws, FiftyMoveRule) + } + return draws +} + +// AddTagPair adds or updates a tag pair with the given key and +// value and returns true if the value is overwritten. +func (g *Game) AddTagPair(k, v string) bool { + for i, tag := range g.TagPairs { + if tag.Key == k { + g.TagPairs[i].Value = v + return true + } + } + g.TagPairs = append(g.TagPairs, &TagPair{Key: k, Value: v}) + return false +} + +// GetTagPair returns the tag pair for the given key or nil +// if it is not present. +func (g *Game) GetTagPair(k string) *TagPair { + for _, tag := range g.TagPairs { + if tag.Key == k { + return tag + } + } + return nil +} + +// RemoveTagPair removes the tag pair for the given key and +// returns true if a tag pair was removed. +func (g *Game) RemoveTagPair(k string) bool { + cp := []*TagPair{} + found := false + for _, tag := range g.TagPairs { + if tag.Key == k { + found = true + } else { + cp = append(cp, tag) + } + } + g.TagPairs = cp + return found +} + +func (g *Game) updatePosition() { + method := g.Pos.Status() + if method == Stalemate { + g.Method = Stalemate + g.Outcome = Draw + } else if method == Checkmate { + g.Method = Checkmate + g.Outcome = WhiteWon + if g.Pos.Turn() == White { + g.Outcome = BlackWon + } + } + if g.Outcome != NoOutcome { + return + } + + // five fold rep creates automatic draw + if !g.IgnoreAutomaticDraws && g.numOfRepitions() >= 5 { + g.Outcome = Draw + g.Method = FivefoldRepetition + } + + // 75 move rule creates automatic draw + if !g.IgnoreAutomaticDraws && g.Pos.halfMoveClock >= 150 && g.Method != Checkmate { + g.Outcome = Draw + g.Method = SeventyFiveMoveRule + } + + // insufficient material creates automatic draw + if !g.IgnoreAutomaticDraws && !g.Pos.board.hasSufficientMaterial() { + g.Outcome = Draw + g.Method = InsufficientMaterial + } +} + +func (g *Game) copy(game *Game) { + g.TagPairs = game.GetTagPairs() + g.Moves = game.MovesHistory() + g.Positions = game.PositionsHistory() + g.Pos = game.Pos + g.Outcome = game.Outcome + g.Method = game.Method +} + +// Clone clones a game +func (g *Game) Clone() *Game { + return &Game{ + TagPairs: g.GetTagPairs(), + Notation: g.Notation, + Moves: g.MovesHistory(), + Positions: g.PositionsHistory(), + Pos: g.Pos, + Outcome: g.Outcome, + Method: g.Method, + } +} + +func (g *Game) numOfRepitions() int { + count := 0 + for _, pos := range g.PositionsHistory() { + if g.Pos.samePosition(pos) { + count++ + } + } + return count +} diff --git a/game_test.go b/game_test.go new file mode 100644 index 0000000..848bcb8 --- /dev/null +++ b/game_test.go @@ -0,0 +1,292 @@ +package chess + +import ( + "log" + "strings" + "testing" +) + +func TestCheckmate(t *testing.T) { + fenStr := "rn1qkbnr/pbpp1ppp/1p6/4p3/2B1P3/5Q2/PPPP1PPP/RNB1K1NR w KQkq - 0 1" + fen, err := FEN(fenStr) + if err != nil { + t.Fatal(err) + } + g := NewGame(fen) + if err := g.MoveStr("Qxf7#"); err != nil { + t.Fatal(err) + } + if g.Method() != Checkmate { + t.Fatalf("expected method %s but got %s", Checkmate, g.Method()) + } + if g.Outcome() != WhiteWon { + t.Fatalf("expected outcome %s but got %s", WhiteWon, g.Outcome()) + } +} + +func TestCheckmateFromFen(t *testing.T) { + fenStr := "rn1qkbnr/pbpp1Qpp/1p6/4p3/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 0 1" + fen, err := FEN(fenStr) + if err != nil { + t.Fatal(err) + } + g := NewGame(fen) + if g.Method() != Checkmate { + t.Error(g.Position().Board().Draw()) + t.Fatalf("expected method %s but got %s", Checkmate, g.Method()) + } + if g.Outcome() != WhiteWon { + t.Fatalf("expected outcome %s but got %s", WhiteWon, g.Outcome()) + } +} + +func TestStalemate(t *testing.T) { + fenStr := "k1K5/8/8/8/8/8/8/1Q6 w - - 0 1" + fen, err := FEN(fenStr) + if err != nil { + t.Fatal(err) + } + g := NewGame(fen) + if err := g.MoveStr("Qb6"); err != nil { + t.Fatal(err) + } + if g.Method() != Stalemate { + t.Fatalf("expected method %s but got %s", Stalemate, g.Method()) + } + if g.Outcome() != Draw { + t.Fatalf("expected outcome %s but got %s", Draw, g.Outcome()) + } +} + +// position shouldn't result in stalemate because pawn can move http://en.lichess.org/Pc6mJDZN#138 +func TestInvalidStalemate(t *testing.T) { + fenStr := "8/3P4/8/8/8/7k/7p/7K w - - 2 70" + fen, err := FEN(fenStr) + if err != nil { + t.Fatal(err) + } + g := NewGame(fen) + if err := g.MoveStr("d8=Q"); err != nil { + t.Fatal(err) + } + if g.Outcome() != NoOutcome { + t.Fatalf("expected outcome %s but got %s", NoOutcome, g.Outcome()) + } +} + +func TestThreeFoldRepition(t *testing.T) { + g := NewGame() + moves := []string{ + "Nf3", "Nf6", "Ng1", "Ng8", + "Nf3", "Nf6", "Ng1", "Ng8", + } + for _, m := range moves { + if err := g.MoveStr(m); err != nil { + t.Fatal(err) + } + } + if err := g.Draw(ThreefoldRepetition); err != nil { + for _, pos := range g.Positions() { + log.Println(pos.String()) + } + t.Fatalf("%s - %d reps", err.Error(), g.numOfRepitions()) + } +} + +func TestInvalidThreeFoldRepition(t *testing.T) { + g := NewGame() + moves := []string{ + "Nf3", "Nf6", "Ng1", "Ng8", + } + for _, m := range moves { + if err := g.MoveStr(m); err != nil { + t.Fatal(err) + } + } + if err := g.Draw(ThreefoldRepetition); err == nil { + t.Fatal("should require three repeated board states") + } +} + +func TestFiveFoldRepition(t *testing.T) { + g := NewGame() + moves := []string{ + "Nf3", "Nf6", "Ng1", "Ng8", + "Nf3", "Nf6", "Ng1", "Ng8", + "Nf3", "Nf6", "Ng1", "Ng8", + "Nf3", "Nf6", "Ng1", "Ng8", + } + for _, m := range moves { + if err := g.MoveStr(m); err != nil { + t.Fatal(err) + } + } + if g.Outcome() != Draw || g.Method() != FivefoldRepetition { + t.Fatal("should automatically draw after five repetitions") + } +} + +func TestFiftyMoveRule(t *testing.T) { + fen, _ := FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 100 60") + g := NewGame(fen) + if err := g.Draw(FiftyMoveRule); err != nil { + t.Fatal(err) + } +} + +func TestInvalidFiftyMoveRule(t *testing.T) { + fen, _ := FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 99 60") + g := NewGame(fen) + if err := g.Draw(FiftyMoveRule); err == nil { + t.Fatal("should require fifty moves") + } +} + +func TestSeventyFiveMoveRule(t *testing.T) { + fen, _ := FEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 149 80") + g := NewGame(fen) + if err := g.MoveStr("Kf8"); err != nil { + t.Fatal(err) + } + if g.Outcome() != Draw || g.Method() != SeventyFiveMoveRule { + t.Fatal("should automatically draw after seventy five moves w/ no pawn move or capture") + } +} + +func TestInsufficentMaterial(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", + "8/2k5/8/8/8/3K1B2/8/8 w - - 1 1", + "8/2k5/2b5/8/8/3K1B2/8/8 w - - 1 1", + "4b3/2k5/2b5/8/8/3K1B2/8/8 w - - 1 1", + } + for _, f := range fens { + fen, err := FEN(f) + if err != nil { + t.Fatal(err) + } + 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) + } + } +} + +func TestSufficentMaterial(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", + "8/2k1b3/8/8/8/3K1B2/8/8 w - - 1 1", + "8/2k5/8/8/4P3/3K4/8/8 w - - 1 1", + "8/2k5/8/8/8/3KQ3/8/8 w - - 1 1", + "8/2k5/8/8/8/3KR3/8/8 w - - 1 1", + } + for _, f := range fens { + fen, err := FEN(f) + if err != nil { + t.Fatal(err) + } + g := NewGame(fen) + if g.Outcome() != NoOutcome { + log.Println(g.Position().Board().Draw()) + t.Fatalf("%s should not find insufficent material", f) + } + } +} + +func TestSerializationCycle(t *testing.T) { + g := NewGame() + pgn, err := PGN(strings.NewReader(g.String())) + if err != nil { + t.Fatal(err) + } + cp := NewGame(pgn) + if cp.String() != g.String() { + t.Fatalf("expected %s but got %s", g.String(), cp.String()) + } +} + +func TestInitialNumOfValidMoves(t *testing.T) { + g := NewGame() + if len(g.ValidMoves()) != 20 { + t.Fatal("should find 20 valid moves from the initial position") + } +} + +func TestTagPairs(t *testing.T) { + g := NewGame() + g.AddTagPair("Draw Offer", "White") + tagPair := g.GetTagPair("Draw Offer") + if tagPair == nil { + t.Fatalf("expected %s but got %s", "White", "nil") + } + if tagPair.Value != "White" { + t.Fatalf("expected %s but got %s", "White", tagPair.Value) + } + g.RemoveTagPair("Draw Offer") + tagPair = g.GetTagPair("Draw Offer") + if tagPair != nil { + t.Fatalf("expected %s but got %s", "nil", "not nil") + } +} + +func TestPositionHash(t *testing.T) { + g1 := NewGame() + for _, s := range []string{"Nc3", "e5", "Nf3"} { + g1.MoveStr(s) + } + g2 := NewGame() + for _, s := range []string{"Nf3", "e5", "Nc3"} { + g2.MoveStr(s) + } + if g1.Position().Hash() != g2.Position().Hash() { + t.Fatalf("expected position hashes to be equal but got %s and %s", g1.Position().Hash(), g2.Position().Hash()) + } +} + +func BenchmarkStalemateStatus(b *testing.B) { + fenStr := "k1K5/8/8/8/8/8/8/1Q6 w - - 0 1" + fen, err := FEN(fenStr) + if err != nil { + b.Fatal(err) + } + g := NewGame(fen) + if err := g.MoveStr("Qb6"); err != nil { + b.Fatal(err) + } + b.ResetTimer() + for n := 0; n < b.N; n++ { + g.Position().Status() + } +} + +func BenchmarkInvalidStalemateStatus(b *testing.B) { + fenStr := "8/3P4/8/8/8/7k/7p/7K w - - 2 70" + fen, err := FEN(fenStr) + if err != nil { + b.Fatal(err) + } + g := NewGame(fen) + if err := g.MoveStr("d8=Q"); err != nil { + b.Fatal(err) + } + b.ResetTimer() + for n := 0; n < b.N; n++ { + g.Position().Status() + } +} + +func BenchmarkPositionHash(b *testing.B) { + fenStr := "8/3P4/8/8/8/7k/7p/7K w - - 2 70" + fen, err := FEN(fenStr) + if err != nil { + b.Fatal(err) + } + g := NewGame(fen) + b.ResetTimer() + for n := 0; n < b.N; n++ { + g.Position().Hash() + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..436a177 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.nightmare.haus/rudi/chessv2 + +go 1.15 + +require github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..658a07a --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb h1:EVl3FJLQCzSbgBezKo/1A4ADnJ4mtJZ0RvnNzDJ44nY= +github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= diff --git a/image/.DS_Store b/image/.DS_Store new file mode 100644 index 0000000..ac3d620 Binary files /dev/null and b/image/.DS_Store differ diff --git a/image/README.md b/image/README.md new file mode 100644 index 0000000..0340b87 --- /dev/null +++ b/image/README.md @@ -0,0 +1,84 @@ +# image + +## Introduction + +Image is an chess image utility that converts board positions into [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics), or Scalable Vector Graphics, images. [svgo](https://github.com/ajstarks/svgo), the only outside dependency, is used to construct the SVG document. + +## Usage + +### SVG + +The SVG function is the primary exported function of the package. It writes an SVG document to the io.Writer given. + +```go +file, _ := os.Open("output.svg") +defer file.Close() +fenStr := "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1" +pos := &chess.Position{} +pos.UnmarshalText([]byte(fenStr)) +image.SVG(file, pos.Board()) +``` + +### Dark / Light Square Customization + +The default colors, shown in the example SVG below, are (235, 209, 166) for light squares and (165, 117, 81) for dark squares. The light and dark squares can be customized using the SquareColors() option. + +```go +white := color.RGBA{255, 255, 255, 1} +gray := color.RGBA{120, 120, 120, 1} +sqrs := image.SquareColors(white, gray) +image.SVG(file, pos.Board(), sqrs) +``` + +### Marked Squares + +MarkSquares is designed to be used as an optional argument to the SVG function. It marks the given squares with the color. A possible usage includes marking squares of the previous move. + +```go +yellow := color.RGBA{255, 255, 0, 1} +mark := image.MarkSquares(yellow, chess.D2, chess.D4) +image.SVG(file, pos.Board(), mark) +``` + +### Example Program + +```go +package main + +import ( + "image/color" + "log" + "os" + + "github.com/notnil/chess" + "github.com/notnil/chess/image" +) + +func main() { + // create file + f, err := os.Create("example.svg") + if err != nil { + log.Fatal(err) + } + defer f.Close() + + // create board position + fenStr := "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1" + pos := &chess.Position{} + if err := pos.UnmarshalText([]byte(fenStr)); err != nil { + log.Fatal(err) + } + + // write board SVG to file + yellow := color.RGBA{255, 255, 0, 1} + mark := image.MarkSquares(yellow, chess.D2, chess.D4) + if err := image.SVG(f, pos.Board(), mark); err != nil { + log.Fatal(err) + } +} +``` + +### Example Program Result + +![rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1](/example.svg) + diff --git a/image/example.png b/image/example.png new file mode 100644 index 0000000..bea9a16 Binary files /dev/null and b/image/example.png differ diff --git a/image/example.svg b/image/example.svg new file mode 100644 index 0000000..1674249 --- /dev/null +++ b/image/example.svg @@ -0,0 +1,534 @@ + + + + + + + + + + + + + + + + +1 +a + + + + + + + + + +b + + + + + + + + + + + +c + + + + + + + + + + + + + + +d + + + + + + + + + + + + +e + + + + + + + + + + + +f + + + + + + + + + +g + + + + + + + + + + + + +h + + + + +2 + + + + + + + + + + + + + + + + + + + + + + + + + + + +3 + + + + + + + + +4 + + + + + + + + + + + + +5 + + + + + + + + +6 + + + + + + + + + + + +7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/image/image.go b/image/image.go new file mode 100755 index 0000000..ce7500c --- /dev/null +++ b/image/image.go @@ -0,0 +1,168 @@ +// Package image is a go library that creates images from board positions +package image + +import ( + "fmt" + "image/color" + "io" + "strings" + + svg "github.com/ajstarks/svgo" + "git.nightmare.haus/rudi/chessv2" + "git.nightmare.haus/rudi/chessv2/image/internal" +) + +// SVG writes the board SVG representation into the writer. +// An error is returned if there is there is an error writing data. +// SVG also takes options which can customize the image output. +func SVG(w io.Writer, b *chess.Board, opts ...func(*encoder)) error { + e := new(w, opts) + return e.EncodeSVG(b) +} + +// SquareColors is designed to be used as an optional argument +// to the SVG function. It changes the default light and +// dark square colors to the colors given. +func SquareColors(light, dark color.Color) func(*encoder) { + return func(e *encoder) { + e.light = light + e.dark = dark + } +} + +// MarkSquares is designed to be used as an optional argument +// to the SVG function. It marks the given squares with the +// color. A possible usage includes marking squares of the +// previous move. +func MarkSquares(c color.Color, sqs ...chess.Square) func(*encoder) { + return func(e *encoder) { + for _, sq := range sqs { + e.marks[sq] = c + } + } +} + +// A Encoder encodes chess boards into images. +type encoder struct { + w io.Writer + light color.Color + dark color.Color + marks map[chess.Square]color.Color +} + +// New returns an encoder that writes to the given writer. +// New also takes options which can customize the image +// output. +func new(w io.Writer, options []func(*encoder)) *encoder { + e := &encoder{ + w: w, + light: color.RGBA{235, 209, 166, 1}, + dark: color.RGBA{165, 117, 81, 1}, + marks: map[chess.Square]color.Color{}, + } + for _, op := range options { + op(e) + } + return e +} + +const ( + sqWidth = 45 + sqHeight = 45 + boardWidth = 8 * sqWidth + boardHeight = 8 * sqHeight +) + +var ( + orderOfRanks = []chess.Rank{chess.Rank8, chess.Rank7, chess.Rank6, chess.Rank5, chess.Rank4, chess.Rank3, chess.Rank2, chess.Rank1} + orderOfFiles = []chess.File{chess.FileA, chess.FileB, chess.FileC, chess.FileD, chess.FileE, chess.FileF, chess.FileG, chess.FileH} +) + +// EncodeSVG writes the board SVG representation into +// the Encoder's writer. An error is returned if there +// is there is an error writing data. +func (e *encoder) EncodeSVG(b *chess.Board) error { + boardMap := b.SquareMap() + canvas := svg.New(e.w) + canvas.Start(boardWidth, boardHeight) + canvas.Rect(0, 0, boardWidth, boardHeight) + + for i := 0; i < 64; i++ { + sq := chess.Square(i) + x, y := xyForSquare(sq) + // draw square + c := e.colorForSquare(sq) + canvas.Rect(x, y, sqWidth, sqHeight, "fill: "+colorToHex(c)) + markColor, ok := e.marks[sq] + if ok { + canvas.Rect(x, y, sqWidth, sqHeight, "fill-opacity:0.2;fill: "+colorToHex(markColor)) + } + // draw piece + p := boardMap[sq] + if p != chess.NoPiece { + xml := pieceXML(x, y, p) + if _, err := io.WriteString(canvas.Writer, xml); err != nil { + return err + } + } + // draw rank text on file A + txtColor := e.colorForText(sq) + if sq.File() == chess.FileA { + style := "font-size:11px;fill: " + colorToHex(txtColor) + canvas.Text(x+(sqWidth*1/20), y+(sqHeight*5/20), sq.Rank().String(), style) + } + // draw file text on rank 1 + if sq.Rank() == chess.Rank1 { + style := "text-anchor:end;font-size:11px;fill: " + colorToHex(txtColor) + canvas.Text(x+(sqWidth*19/20), y+sqHeight-(sqHeight*1/15), sq.File().String(), style) + } + } + canvas.End() + return nil +} + +func (e *encoder) colorForSquare(sq chess.Square) color.Color { + sqSum := int(sq.File()) + int(sq.Rank()) + if sqSum%2 == 0 { + return e.dark + } + return e.light +} + +func (e *encoder) colorForText(sq chess.Square) color.Color { + sqSum := int(sq.File()) + int(sq.Rank()) + if sqSum%2 == 0 { + return e.light + } + return e.dark +} + +func xyForSquare(sq chess.Square) (x, y int) { + fileIndex := int(sq.File()) + rankIndex := 7 - int(sq.Rank()) + return fileIndex * sqWidth, rankIndex * sqHeight +} + +func colorToHex(c color.Color) string { + r, g, b, _ := c.RGBA() + return fmt.Sprintf("#%02x%02x%02x", uint8(float64(r)+0.5), uint8(float64(g)*1.0+0.5), uint8(float64(b)*1.0+0.5)) +} + +func pieceXML(x, y int, p chess.Piece) string { + fileName := fmt.Sprintf("pieces/%s%s.svg", p.Color().String(), pieceTypeMap[p.Type()]) + svgStr := string(internal.MustAsset(fileName)) + old := `` + new := fmt.Sprintf(``, (-1 * x), (-1 * y)) + return strings.Replace(svgStr, old, new, 1) +} + +var ( + pieceTypeMap = map[chess.PieceType]string{ + chess.King: "K", + chess.Queen: "Q", + chess.Rook: "R", + chess.Bishop: "B", + chess.Knight: "N", + chess.Pawn: "P", + } +) diff --git a/image/image_test.go b/image/image_test.go new file mode 100755 index 0000000..607edbc --- /dev/null +++ b/image/image_test.go @@ -0,0 +1,48 @@ +package image_test + +import ( + "bytes" + "crypto/md5" + "fmt" + "image/color" + "io" + "os" + "strings" + "testing" + + "git.nightmare.haus/rudi/chessv2" + "git.nightmare.haus/rudi/chessv2/image" +) + +const expectedMD5 = "da140af8b83ce7903915ee39973e36dd" + +func TestSVG(t *testing.T) { + // create buffer of actual svg + buf := bytes.NewBuffer([]byte{}) + fenStr := "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1" + pos := &chess.Position{} + if err := pos.UnmarshalText([]byte(fenStr)); err != nil { + t.Error(err) + } + mark := image.MarkSquares(color.RGBA{255, 255, 0, 1}, chess.D2, chess.D4) + if err := image.SVG(buf, pos.Board(), mark); err != nil { + t.Error(err) + } + + // compare to expected svg + actualSVG := strings.TrimSpace(buf.String()) + actualMD5 := fmt.Sprintf("%x", md5.Sum([]byte(actualSVG))) + if actualMD5 != expectedMD5 { + t.Errorf("expected actual md5 hash to be %s but got %s", expectedMD5, actualMD5) + } + + // create actual svg file for visualization + f, err := os.Create("example.svg") + defer f.Close() + if err != nil { + t.Error(err) + } + if _, err := io.Copy(f, bytes.NewBufferString(actualSVG)); err != nil { + t.Error(err) + } +} diff --git a/image/internal/bindata.go b/image/internal/bindata.go new file mode 100755 index 0000000..3b48b65 --- /dev/null +++ b/image/internal/bindata.go @@ -0,0 +1,513 @@ +// Code generated by go-bindata. +// sources: +// pieces/.DS_Store +// pieces/bB.svg +// pieces/bK.svg +// pieces/bN.svg +// pieces/bP.svg +// pieces/bQ.svg +// pieces/bR.svg +// pieces/wB.svg +// pieces/wK.svg +// pieces/wN.svg +// pieces/wP.svg +// pieces/wQ.svg +// pieces/wR.svg +// DO NOT EDIT! + +package internal + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _piecesDs_store = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xec\x98\x3b\x0e\xc2\x30\x10\x44\x77\x8c\x0b\x4b\x34\x2e\x29\xdd\x70\x00\x6e\x60\x45\xe1\x04\x5c\x80\x82\x2b\xd0\xfb\xe8\x24\xda\x11\xb2\x14\x52\x50\x25\x82\x79\x92\xf5\x56\x8a\x9d\x4f\xe3\xec\xd8\xcc\x30\x3c\x1f\x17\xb3\x3c\x95\xc9\xdc\x76\xb6\x8f\x24\x8e\x05\xa1\xab\xc1\x7b\x08\x21\x84\x10\x62\xdf\xc0\x95\x8e\xdb\xbe\x86\x10\x62\x87\xcc\xfb\x43\xa1\x2b\xdd\xdc\xe0\xf5\x40\xc7\x6e\x4d\xa6\x0b\x5d\xe9\xe6\x06\xe7\x05\x3a\xd2\x89\xce\x74\xa1\x2b\xdd\xdc\xdc\xb4\xc0\xf0\x01\x3e\x19\x4c\x28\x60\x0a\x41\xa1\xeb\x97\x1f\x2d\xc4\x9f\x70\x70\xe5\xf9\xff\x7f\xb5\xd5\xfc\x2f\x84\xf8\x61\x10\xc7\xdb\x38\xd8\x3b\x10\x2c\x27\x4c\xe3\xde\xd5\xcd\xd6\x9b\x80\xe0\x87\x85\xa7\x6e\x6d\xa1\x2b\xdd\xdc\x6a\x04\x84\xd8\x8a\x57\x00\x00\x00\xff\xff\x6a\x00\x88\x6d\x04\x18\x00\x00") + +func piecesDs_storeBytes() ([]byte, error) { + return bindataRead( + _piecesDs_store, + "pieces/.DS_Store", + ) +} + +func piecesDs_store() (*asset, error) { + bytes, err := piecesDs_storeBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/.DS_Store", size: 6148, mode: os.FileMode(420), modTime: time.Unix(1445781173, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesBbSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x84\x54\xed\x72\x9b\x30\x10\xfc\x9f\xa7\xb8\x51\xfe\x3a\x42\x1f\x48\x7c\xc4\xce\x4c\xa7\x7f\xe3\x87\xa0\x41\x31\xb4\x04\x3c\x20\x9b\xba\x4f\x5f\x9d\x24\x08\x4d\x3a\x2d\x99\xa0\xf3\x6a\x6f\x77\x4f\x63\x79\x3f\x5d\x4f\xf0\xf3\xad\xeb\xa7\x03\x69\xac\x3d\x97\x49\x32\xcf\x33\x9d\x25\x1d\xc6\x53\x22\x18\x63\x89\x63\x10\xb8\x9a\x71\x6a\x87\xfe\x40\x38\xe5\x04\xe6\xb6\xb6\xcd\x81\xa4\x8a\x40\x63\xda\x53\x63\x7d\xfd\x74\x07\xb0\x3f\xc1\x64\x6f\x9d\x39\x90\xe1\x5c\xbd\xb4\xf6\x56\xf2\x47\x78\x6d\xbb\xae\xec\x87\xde\x84\xf2\x61\xbc\x74\xa6\x34\x57\xd3\x0f\x75\x1d\xa1\x0d\x7b\xb2\xe3\xf0\xc3\x94\xf7\xcc\x3f\xcb\xe7\x07\xef\x59\x72\xaa\x56\xa4\x6b\x7b\xf3\x52\x9d\xcb\x71\xb8\xf4\xf5\x1f\xe8\xf7\xa1\xed\x3f\xc0\x6f\xad\x35\x63\xd7\xba\xa5\x4c\x57\xb0\xae\xa6\xa6\x1a\xc7\xea\x16\xd3\x45\xf8\x3d\x8c\x9f\x69\x3b\x95\x1f\xe5\x43\xb4\x4f\x51\x97\x60\xdf\x2e\xd6\x2e\x12\x4e\xe4\x5c\xd9\x26\xd6\x00\xf5\x81\x1c\xa1\xd8\x49\x0d\x5f\x81\x0b\x2a\x5d\xa9\x28\x93\xc0\x0b\xca\xb9\x83\x69\x2a\x41\x08\xaa\x76\x32\x75\x0c\xa1\x68\x5e\x44\x54\x0a\xaa\x79\x64\x4b\x1d\x14\xc2\x2a\x33\xaa\x15\xb2\x54\x0a\x28\x98\xe3\x4e\x4e\xa5\x70\x25\x2d\x32\xdc\x97\xca\xd7\x85\xef\xc8\xa9\x42\x46\xd0\xcb\xa8\x92\x8b\x8f\x63\xe8\xe8\x9e\x79\x4e\x4c\xe5\xf1\x98\xd6\xf3\x8b\x45\xc4\x39\xa7\x3a\x4a\x6b\xaa\xb3\x2c\x5a\xea\x90\x02\x8d\x53\x4c\xc6\x74\x18\xda\xbf\x7e\x11\x48\xfe\x71\x3a\xdc\xd9\x0b\x34\xcf\xfc\x31\x38\x1f\xb1\x56\x92\x85\x3d\xc9\x10\x61\x11\x61\xf1\x8d\xf8\x0e\xc9\xa1\x43\xe8\x75\x75\x3b\x72\x27\xbc\x82\x74\x08\xf7\xaa\x38\x28\x67\x61\x50\xbe\xa0\x5c\x04\x1e\x5f\x3b\x97\xca\xe5\xf2\xda\x98\x0f\xbd\xc2\x8a\x4d\x21\x49\xc8\xfd\x9f\xe1\x84\x82\x1c\xbe\x80\x40\x7f\xf7\xef\xfa\xdd\x1f\x08\xb6\xfb\x1b\x8a\xdc\x77\xbd\x7d\x72\x8a\xc5\x56\x36\x1c\x59\x8c\xf8\xbc\x0e\x7c\x8c\xf1\x9e\xe3\xd1\x1c\xe3\xb8\xca\xc9\x3f\x87\x5a\x60\xe8\x23\x7a\xf3\x1c\x31\xb7\x9b\x93\x45\x76\xfb\xbd\xdf\x5e\x92\xf2\xfe\xd5\x3f\x9f\xef\x9d\xbf\x69\x8f\x31\xad\xcf\xba\xc7\xdf\x90\xa7\xbb\xdf\x01\x00\x00\xff\xff\x88\x7d\xb5\x19\x6c\x04\x00\x00") + +func piecesBbSvgBytes() ([]byte, error) { + return bindataRead( + _piecesBbSvg, + "pieces/bB.svg", + ) +} + +func piecesBbSvg() (*asset, error) { + bytes, err := piecesBbSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/bB.svg", size: 1132, mode: os.FileMode(420), modTime: time.Unix(1445797333, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesBkSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xa4\x54\xcb\x92\xdb\x20\x10\xbc\xfb\x2b\x28\x72\xd5\x22\x5e\x7a\xfa\x71\xf1\xd5\xf9\x08\x65\xc5\x4a\x24\x5a\xc9\x25\x61\x2b\xce\xd7\x07\x10\x60\xaf\x13\x3b\x49\x45\x07\xd3\x30\xc3\xd0\xdd\x33\xe5\xcd\x74\x6e\xc0\xf7\xf7\xae\x9f\xb6\xb0\x55\xea\x58\xc6\xf1\x3c\xcf\x68\x66\x68\x18\x9b\x98\x62\x8c\x63\x9d\x01\xc1\x59\x8c\x93\x1c\xfa\x2d\x24\x88\x40\x30\xcb\x5a\xb5\x5b\xc8\x13\x08\x5a\x21\x9b\x56\x59\xbc\x5b\x01\xb0\x69\xc0\xa4\x2e\x9d\xd8\xc2\x37\xd9\x75\x65\x3f\xf4\x62\x0d\x0c\x7c\x19\x8e\xd5\xab\x54\x97\x92\xb8\xfd\x78\xea\x44\x29\xce\xa2\x1f\xea\x7a\xad\x2f\x8d\xc3\x37\x51\x7e\xc2\xf6\xf3\xfb\x17\xfb\x50\x49\x50\x12\x4e\x3a\xd9\x8b\xd7\xea\x58\x8e\xc3\xa9\xaf\xd7\x37\x87\x5f\x07\xd9\x7f\x3c\x7d\x97\x4a\x8c\x9d\xd4\x4b\xc9\xc3\xfd\xba\x9a\xda\x6a\x1c\xab\x8b\xe3\xe6\x8e\xaf\xec\xac\x0c\x2d\xe4\x58\xa9\xd6\x22\xfd\xd5\x5b\xf8\x19\x50\x8a\x92\x88\x10\x94\x32\x70\x58\x36\x29\xf4\x09\xbf\x6a\x7e\x20\x28\x30\xb5\xe4\xd6\xa1\x80\xd4\x4f\x98\x17\xd3\x24\xc3\x10\xc4\x4f\x39\xd0\x04\xec\x03\xa2\x59\x44\x32\xa4\xd7\xc4\xb0\xe3\xc8\xc6\x02\xa6\xdc\x20\xea\xb8\x53\x13\xc3\x0b\x22\x85\xcf\xb9\xa2\x3d\x20\xb9\xab\xe6\xab\x2f\xeb\x6f\x75\x7a\x69\xf7\xdd\xbd\x6b\xd4\x97\x93\x52\x0f\xe5\x3f\x56\xaa\x8d\x4e\x22\x96\x19\x4e\x59\xc4\x31\xb2\x4a\xed\xca\xe8\x12\x39\x38\x84\x75\x8e\x47\x9c\x58\xbe\x26\x2b\x37\xaa\x0a\xab\x8a\x59\x17\x98\xf6\x25\x22\xa9\xd3\xc4\x74\xc4\xb5\x91\x66\x01\xb1\xc5\x85\xc2\xe4\x15\xcb\xa5\xf4\xa6\x8e\x2f\x6e\xc9\xd1\xe2\x03\x3a\x04\xca\x3f\xc0\x53\xbf\xee\x47\xe3\x59\xbb\x71\x94\x1b\x6e\x49\x94\xff\xff\xa8\x3d\x7e\x86\xd1\x45\xc2\x3e\x20\x63\x74\xf0\x11\x33\x63\x40\xee\x9c\x24\x66\x58\xac\x95\xb9\x33\x8d\x07\x2b\x49\x44\x53\x94\x06\x37\xdd\x34\x62\x93\x5a\xa0\x02\xa7\xe6\x66\x8a\x8a\x22\x0b\x05\xf9\x8d\xa5\xb9\x86\xb9\x39\xbe\xc1\x7f\x21\xfb\xcd\x7e\x7f\x9e\x25\xbc\xcc\x92\xee\x36\xb5\xbf\x7e\x66\x7c\xdc\xf5\x3e\xd3\x67\xcb\xb4\xb1\xeb\xb4\x99\xd8\xdd\x4c\x32\x6e\x73\xb8\x9f\xc7\x7f\x66\xba\x89\x9b\xdd\x6a\x63\xfe\x5e\x77\xab\x9f\x01\x00\x00\xff\xff\x43\xf4\x20\xfb\x87\x05\x00\x00") + +func piecesBkSvgBytes() ([]byte, error) { + return bindataRead( + _piecesBkSvg, + "pieces/bK.svg", + ) +} + +func piecesBkSvg() (*asset, error) { + bytes, err := piecesBkSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/bK.svg", size: 1415, mode: os.FileMode(420), modTime: time.Unix(1445797339, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesBnSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xac\x54\xcb\x6e\xe2\x4a\x10\xdd\xe7\x2b\x4a\xbe\x9b\x7b\xa5\xa6\xe8\x87\xbb\xed\x76\x20\xd2\x15\xdb\xcc\x47\x58\xc1\x01\xcf\x80\x8d\x8c\x13\x92\x7c\xfd\x9c\xb2\x0d\x64\xa2\x44\x9a\x45\x90\xe8\x7a\x75\x57\x9d\x7a\x79\x71\x7c\xde\xd0\xcb\x7e\xd7\x1c\x97\xc9\xb6\xef\x0f\xc5\x7c\x7e\x3a\x9d\xf8\xe4\xb8\xed\x36\x73\xab\xb5\x9e\xe3\x46\x42\xcf\x55\x77\xac\xdb\x66\x99\x18\x36\x09\x9d\xea\x75\xbf\x5d\x26\xa9\x4f\x68\x5b\xd5\x9b\x6d\x3f\xf0\x77\x37\x44\x8b\x0d\x1d\xfb\xd7\x5d\xb5\x4c\xda\x43\xf9\x50\xf7\xaf\x85\xb9\xa5\xc7\x7a\xb7\x2b\x9a\xb6\xa9\x46\x76\xf6\xc1\x34\xeb\x9e\x76\x55\x51\x3d\x57\x4d\xbb\x5e\xdf\xe2\x7d\xd7\xfe\xaa\x8a\x7f\xf4\xf0\x3b\xcb\xb3\x21\x66\x61\xd8\x5f\x34\xbb\xba\xa9\x1e\xca\x43\xd1\xb5\x4f\xcd\xfa\xf6\x9d\xf2\x67\x5b\x37\x7f\x6a\xf7\x75\x5f\x75\xbb\x1a\xa4\x48\x2f\xef\xd7\xe5\x71\x5b\x76\x5d\xf9\x3a\x61\x9b\xd4\x57\x74\x43\x46\xc8\xe9\x50\xf6\xdb\x81\x23\x5a\x2f\x93\x1f\x64\xad\x32\x9a\x56\xe4\x2c\x7b\x65\x0c\xb9\x5c\x68\x0e\xaa\x5c\xa4\x7b\x32\x5e\xe8\x6a\xa0\x9a\x2c\x4e\x5c\x24\xeb\x70\x27\x99\xfc\x4c\x45\x1a\x2a\xf3\x21\xd3\x8b\x9c\xd0\xfc\x2b\x00\xa9\x84\x5b\x81\x32\x62\x5a\xcd\xd1\x90\xc9\x39\xf5\xca\x7a\x76\x19\x99\xa0\x6c\x26\x00\x9c\xb2\x11\x27\x1b\x20\x33\xec\x52\x32\x06\x0c\x2c\x91\xa3\x87\x4e\xb3\x0e\x64\x2c\xa7\x06\x0f\x38\x06\xb1\x5b\xf1\x6c\xb4\x50\x83\x6e\x47\xb8\x60\xeb\x44\xe3\x24\xe9\x28\xc4\x73\x8c\x99\x78\x42\xa0\x00\x25\x08\x7c\xa3\x2c\xe7\x73\x75\x96\x1c\xe7\x51\x21\x02\x00\x02\xb4\x46\x1d\x04\x16\xdb\x4c\x45\xf6\x12\xdc\xa1\x76\xa8\xdf\xc8\x64\xa3\x3d\x05\x1b\x44\x17\xa4\xb0\xfa\x42\xef\x25\x4b\x3f\x16\xff\xcc\x19\xa0\xcb\xe1\x41\xeb\x9c\xac\x51\x92\x36\xfa\x93\x4d\x4d\x1a\xce\xef\x29\x7a\x94\x1e\x7a\x1c\xff\x93\xa4\x31\xfc\x09\x69\x91\x00\xf9\xd4\x70\x79\xf1\xf6\x29\x82\xc7\xe1\x77\x45\x30\xc9\x5f\x23\x30\xa8\xc8\x35\x8e\xb9\xc4\x19\x4a\xfb\xb9\x61\x7a\x71\x01\xd0\x77\x65\x73\x7c\x6c\xbb\xfd\x32\xd9\x97\x7d\x57\xbf\xfc\xab\x39\x0f\x41\xe1\x9d\x9a\xc9\x31\x8a\x91\x43\x74\x6a\xe6\xd9\x64\xee\xbf\xef\x01\x8f\x59\xf5\xd2\x30\x4e\xd1\x46\x08\x06\x9b\xc3\xb9\x1f\x85\x80\x11\x91\xc6\x65\x9c\xe1\x8e\x23\x0c\x26\xd6\x06\x73\x90\xc6\x69\xcb\x72\x58\x64\xe5\x52\xb9\x61\x1d\x6b\xac\x9d\x1f\xf8\x28\x33\x0c\xde\xfa\x71\xfb\x84\x05\xc7\xe2\xda\x65\xb2\x14\x57\x61\x5a\x4c\x59\x9a\x9c\x63\x4a\x2e\x70\xb0\xca\x62\x3c\xbd\xb8\x96\x98\x19\x07\x99\x68\xac\x4b\x9e\x2b\x59\x1d\x8c\x12\x56\x2b\x08\x5c\x6d\xa5\x9f\x3a\x8c\x83\x7c\xff\x3e\xa7\x37\xfa\x9b\x32\x0d\x1f\x9a\xa9\x46\x8b\xf9\xe6\xee\x66\x21\x9f\xd7\xbb\x9b\xdf\x01\x00\x00\xff\xff\x7e\xb7\x48\xaf\x87\x05\x00\x00") + +func piecesBnSvgBytes() ([]byte, error) { + return bindataRead( + _piecesBnSvg, + "pieces/bN.svg", + ) +} + +func piecesBnSvg() (*asset, error) { + bytes, err := piecesBnSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/bN.svg", size: 1415, mode: os.FileMode(420), modTime: time.Unix(1445797343, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesBpSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x5c\x92\xcd\x72\x9b\x30\x10\xc7\xef\x7e\x8a\x1d\xf5\x8a\x05\xfa\x02\xa4\x18\x5f\x72\x6d\x1f\x82\x89\x55\xa3\x96\x20\x46\x28\xa1\xce\xd3\x77\x25\x08\x99\x96\x03\xfa\xed\x7f\x3f\xb4\xbb\x70\x59\xde\xef\xf0\xe7\x75\x9c\x96\x8e\x0c\x31\xce\xa6\x2c\xd7\x75\xa5\xab\xa0\x3e\xdc\x4b\x5e\x55\x55\x89\x11\x04\xde\x6d\x58\x9c\x9f\x3a\xc2\x28\x23\xb0\xba\x5b\x1c\x3a\x22\x15\x81\xc1\xba\xfb\x10\x33\x5f\x4f\x00\x97\xb9\x8f\x03\x9e\x00\xb7\x8e\xfc\x00\xce\x0b\x0d\xcf\xc0\x34\x6d\x34\x12\x6b\x0b\x56\x21\x66\x10\xc9\x91\x4e\xda\x26\x81\x72\x5d\x30\x49\x1b\x96\xb8\x41\x5d\x51\xd1\xa6\x90\x9a\xb6\xa2\xc0\xb7\x02\x94\x54\x81\x5e\xa5\x37\xe4\x2c\xf9\x33\x09\x5a\x89\x14\x2a\x65\xc1\x25\x6d\x25\xb0\x86\x6a\x56\xf0\x3a\xe9\x18\x24\xb3\xd5\xd0\x0a\x53\x2b\x4c\x10\x8c\xaa\x76\x47\x8d\xa5\xbf\x83\x10\x9f\xfc\xbc\x73\x0e\xe1\x1a\x73\xf6\xcc\x54\x4d\x1f\x35\x51\x53\xf5\x7e\x1b\x6f\x8f\x26\x36\x4c\x9d\x65\xda\xda\xc5\x58\xd6\x6c\x43\x70\x45\x71\x2b\x9f\xd3\xa1\xd5\xb0\x7d\x6e\x5e\xef\xcb\xc8\x90\x9c\xf5\xbe\x2e\xbc\x84\x33\xdc\x5f\x5e\xe7\x07\x90\xbc\xe0\x25\x3e\x46\xdb\x11\x3f\xf7\x2f\x2e\x3e\x0c\x7b\x82\x9f\x6e\x1c\xcd\xb7\x2a\x3f\x9b\x75\xfe\xcf\x7b\x0e\x6f\xa3\x35\x93\x9f\x3e\x6c\xf0\x4f\x58\x22\xf8\xdf\xf6\x2b\x65\xb3\xcf\xf9\xf3\x1a\x1c\xff\x50\x46\x37\xd9\x97\x7e\x36\xc1\xbf\x4d\xb7\x7f\xd4\x5f\xde\x4d\xe6\xd5\x45\x1b\x0e\x39\x5b\xa3\xc3\xc3\xc8\x43\xbc\xf5\xcb\xd0\x87\xd0\x3f\xd2\xed\xf6\x90\xbf\xfa\x23\x50\x5e\x4f\x97\xf4\xb7\x5d\x4f\x7f\x03\x00\x00\xff\xff\x4f\x2d\x50\x9e\x96\x02\x00\x00") + +func piecesBpSvgBytes() ([]byte, error) { + return bindataRead( + _piecesBpSvg, + "pieces/bP.svg", + ) +} + +func piecesBpSvg() (*asset, error) { + bytes, err := piecesBpSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/bP.svg", size: 662, mode: os.FileMode(420), modTime: time.Unix(1445797348, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesBqSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xac\x55\xc1\x72\x9b\x30\x10\xbd\xe7\x2b\x34\xea\x15\x0b\x49\x0b\xd8\x60\x93\x99\x8e\xaf\xe9\x47\x50\x50\x0c\x2d\x01\x0f\xc6\x76\xdc\xaf\xef\x4a\x48\xe0\x90\xd6\x71\x66\x82\x0f\xbb\xda\x7d\xda\x7d\xfb\x24\xf0\xe6\x70\xda\x91\xd7\x97\xba\x39\xa4\xb4\xec\xfb\x7d\xe2\xfb\xe7\xf3\x99\x9d\x81\xb5\xdd\xce\x97\x9c\x73\x1f\x11\x94\x9c\x54\x77\xa8\xda\x26\xa5\x82\x09\x4a\xce\x55\xd1\x97\x29\x0d\x42\x4a\x4a\x55\xed\xca\xde\xf8\x8f\x0f\x84\x6c\x76\xe4\xd0\x5f\x6a\x95\xd2\x76\x9f\xe5\x55\x7f\x49\xc4\x9a\x3c\x57\x75\x9d\x70\xf3\x0c\x8b\xc5\x2c\xb9\xe8\x8e\xb5\x4a\xd4\x49\x35\x6d\x51\xac\xb1\x42\xd7\xfe\x56\xc9\x37\xb7\x65\x58\x2f\x4c\xd7\x44\xb0\x70\x8c\xd4\x55\xa3\xf2\x6c\x9f\x74\xed\xb1\x29\xd6\x57\xc1\x5f\x6d\xd5\xbc\x8d\xbe\x54\xbd\xea\xea\x0a\x4d\x12\x8c\xfb\x8b\xec\x50\x66\x5d\x97\x5d\x92\xa6\x6d\xd4\x18\x9e\xd8\x99\x99\xae\xa7\x32\xa3\xcc\x88\x0d\x9b\x2d\x14\xc1\x79\xd5\xe5\xb5\x22\xf9\x6b\x4a\x23\xaa\x23\xf9\x05\x75\x93\x94\x74\x29\x95\x6c\x89\xa2\xf9\xff\xc2\x8a\x80\x5a\x6c\x8c\xce\x6d\xac\x94\x0c\x53\x1a\xbb\xfa\x10\x0b\xe2\xfe\xba\x10\xd3\x1b\x7c\x37\xfe\xce\x3a\xfb\xac\x2f\xed\x76\x52\xa4\xf4\x07\x89\x3d\x19\x91\x2d\x11\x4b\x16\x7a\x32\x60\x21\x01\x6e\x6d\xa4\x33\x4f\x04\x56\x98\x11\x80\x11\xf4\x85\x27\x8d\xe5\x6c\xe9\x09\xce\x62\xf4\x65\xe8\x76\xa2\x2f\x35\x96\xa3\x27\xe2\x29\x2a\x02\x06\x0e\x2d\x82\xa1\x42\x34\x15\x35\x0c\xfe\x50\xc7\xca\x9e\xd7\xec\xa2\xfc\x3c\xf6\xfd\xbb\xfb\x35\xcd\xf7\xbf\xb1\xd0\xac\x08\xb6\x0e\x8d\xc5\x1b\xe8\x01\xd7\xd3\x6a\x9e\x80\x4b\xe7\xa1\xf5\x40\xd3\xd9\x0e\x68\xd0\xcc\x07\x2f\x1a\xad\x2e\x07\x4b\x9d\x10\x9e\x56\x65\xb4\x56\x3e\xc0\x99\x89\x1c\x3d\x08\x5c\xd6\x79\xa0\xa5\x32\x15\x74\x24\xb2\x99\x88\xe8\x6e\x43\x4b\x00\x47\x03\x2c\x31\x18\xa9\xea\xc4\x40\xdf\xe0\x71\x20\x7d\x44\x2b\x7b\x50\xdb\xa1\xb3\x91\x7c\x3a\xcc\xfb\xb5\xbd\xa1\xa5\x1b\xf3\x3b\x0e\xe0\x01\x96\x27\x1c\x7f\x76\xa8\x79\x6d\xf3\x9e\x5d\xbf\x94\xef\xbe\x06\x9f\x68\x2b\xe3\x37\x4d\x85\x6e\x2a\xe3\x3b\x5a\x3e\x9b\xe7\x66\xf5\x51\xd7\xa7\x49\xe3\xaf\xa9\x2c\xdc\x69\xce\x04\x03\x1b\xff\x9a\x2e\xdc\xdd\xa5\xf9\xb1\xd8\xf8\xa7\xbb\x98\x8f\xc4\x46\xff\x6b\x3c\x3e\xfc\x0d\x00\x00\xff\xff\xd0\x03\xeb\x88\x5e\x06\x00\x00") + +func piecesBqSvgBytes() ([]byte, error) { + return bindataRead( + _piecesBqSvg, + "pieces/bQ.svg", + ) +} + +func piecesBqSvg() (*asset, error) { + bytes, err := piecesBqSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/bQ.svg", size: 1630, mode: os.FileMode(420), modTime: time.Unix(1445797352, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesBrSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x55\xd1\xb2\x9b\x20\x10\x7d\xcf\x57\xec\x70\x5f\x13\x15\x51\x67\x44\xcd\x17\xb4\x1f\x61\xaf\x5c\xa5\xf5\x82\x83\x24\x36\xfd\xfa\x22\x2a\x1a\x67\xda\xc9\x4c\xda\xf8\x72\x0e\x0b\xec\xd9\x65\x77\xc7\xbc\xbf\xd6\xf0\xf3\xb3\x15\x7d\x81\x1a\xad\x3b\xea\xfb\xc3\x30\x78\x03\xf1\xa4\xaa\xfd\x30\x08\x02\xdf\x9c\x40\x70\x65\xaa\xe7\x52\x14\x08\x7b\x18\xc1\xc0\x2b\xdd\x14\x28\x8a\x11\x34\x8c\xd7\x8d\xb6\xfc\x7c\x00\xc8\x6b\xe8\xf5\xad\x65\x05\x92\x5d\xf9\xce\xf5\x8d\xe2\x0c\x3e\x78\xdb\xd2\xc0\x7e\xd3\xe2\xb4\xdb\x3c\xa9\x4b\xcb\x28\xbb\x32\x21\xab\x2a\x33\x1e\x94\xfc\xc1\xe8\xdb\x72\x65\x5a\x9f\xac\x2a\xc5\x5e\xec\x2c\x2d\x17\xec\xbd\xec\xa8\x92\x17\x51\x65\x1b\xe3\x77\xc9\xc5\xbd\xf5\x93\x6b\xa6\x5a\x6e\x80\x46\xee\x7e\x55\xf6\x4d\xa9\x54\x79\xa3\x42\x0a\xe6\xcc\x6b\x74\x36\x27\x93\x55\x57\xea\xc6\x32\x80\xaa\x40\x5f\x21\x3d\x92\x14\xbe\x00\x49\x56\x4c\x0c\xa6\x0e\x52\xf8\x05\x68\xbe\x31\x3f\xc8\x2e\xe6\x6f\x17\xad\x33\x04\xfe\x1f\x14\x70\xe8\xc5\x47\x12\x1a\x6f\x38\x3a\x86\xa9\x17\x8f\x32\xd8\xb1\x75\x77\x66\x4f\xcb\x4d\xa1\x8f\x38\xba\x25\x64\xc5\xc5\x9e\x3c\x2d\xe2\x32\x31\x0c\x27\x4b\x4e\x2b\x5b\x77\x2d\x7b\x48\x6e\x5f\x75\x5b\xe8\xbf\x07\x31\x0b\x62\x23\x1d\x8d\xc2\xd1\x8c\x2e\x94\xe5\xcc\xb3\xf9\xce\x02\x06\xc7\x2e\xc1\xf1\x02\x18\x1b\x0c\x03\x87\xa3\x39\x8c\x17\xb0\x56\x12\x38\x4c\xa7\x18\xd3\x4d\xa8\x93\xe7\x7f\x50\xf3\x78\x7a\x7a\xb2\x63\x3b\xbf\x76\x80\xb7\x23\x42\xdf\x3e\xec\xb7\x9f\xcd\xbb\xc9\x7c\xac\x1c\x46\x10\xcf\x3d\x6d\xd9\x0b\xa5\xf7\x83\xf5\x52\xe9\xfb\xfe\x7f\xa1\xf4\xb6\xeb\xff\x93\x6c\xee\xd7\xe7\x43\x3e\xfe\x39\xce\x87\xdf\x01\x00\x00\xff\xff\xbd\x2e\xf0\x59\x62\x06\x00\x00") + +func piecesBrSvgBytes() ([]byte, error) { + return bindataRead( + _piecesBrSvg, + "pieces/bR.svg", + ) +} + +func piecesBrSvg() (*asset, error) { + bytes, err := piecesBrSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/bR.svg", size: 1634, mode: os.FileMode(420), modTime: time.Unix(1445797357, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesWbSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x84\x54\xdd\x72\x9b\x3c\x10\xbd\xcf\x53\xec\x90\x5b\x47\xe8\x07\x89\x9f\xd8\x99\xf9\xe6\xbb\xb5\x1f\x82\x06\xc5\xd0\x12\xf0\x80\x6c\xd7\x7d\xfa\x6a\xa5\x85\xb8\x9d\xb4\xc5\x63\xb4\x3e\x3a\x7b\xce\x59\x0d\x78\x3b\x5f\x8e\xf0\xfd\xbd\x1f\xe6\x5d\xd2\x3a\x77\xaa\xd2\xf4\x7a\xbd\xb2\xab\x62\xe3\x74\x4c\x25\xe7\x3c\xf5\x8c\x04\x2e\x76\x9a\xbb\x71\xd8\x25\x82\x89\x04\xae\x5d\xe3\xda\x5d\x92\xe9\x04\x5a\xdb\x1d\x5b\x17\xea\x97\x07\x80\xed\x11\x66\x77\xeb\xed\x2e\x19\x4f\xf5\x6b\xe7\x6e\x95\x78\x86\xb7\xae\xef\xab\x61\x1c\x6c\x2c\x9f\xa6\x73\x6f\x2b\x7b\xb1\xc3\xd8\x34\x04\xdd\xb1\x67\x37\x8d\xdf\x6c\xf5\xc8\xc3\xb5\xfc\x7e\x0a\x9e\x95\x60\x7a\x45\xfa\x6e\xb0\xaf\xf5\xa9\x9a\xc6\xf3\xd0\xfc\x82\x7e\x1d\xbb\xe1\x37\xf8\xbd\x73\x76\xea\x3b\xbf\x54\xd9\x0a\x36\xf5\xdc\xd6\xd3\x54\xdf\x28\x1d\xc1\x1f\x61\xc2\x4c\xf7\x53\x85\x51\x1e\xdf\xc2\xf5\xc7\xa8\x4b\xb0\x2f\x67\xe7\x16\x09\x2f\x72\xaa\x5d\x4b\x35\x40\xb3\x4b\x0e\x50\x6e\x94\x81\xff\x41\x48\xa6\x7c\xa9\x19\x57\x20\x4a\x26\x84\x87\x59\xa6\x40\x4a\xa6\x37\x2a\xf3\x0c\xa9\x59\x51\x12\xaa\x24\x33\x82\xd8\xca\x44\x85\xb8\xaa\x9c\x19\x8d\x2c\x9d\x01\x0a\x16\xb8\x53\x30\x25\x7d\xc9\xca\x1c\xf7\x95\x0e\x75\x19\x3a\x0a\xa6\x91\x11\xf5\x72\xa6\xd5\xe2\xe3\x19\x86\xdc\xf3\xc0\xa1\x54\x01\xa7\xb4\x81\x5f\x2e\x22\xde\x39\x33\x24\x6d\x98\xc9\x73\xb2\x34\x31\x05\x1a\x67\x98\x8c\x9b\x38\x74\xb8\xfd\x48\x20\xfd\xcb\xe9\x08\x6f\x2f\xd1\x3c\x0f\xc7\xe0\x7d\xe4\x5a\x29\x1e\xf7\x14\x47\x84\x13\xc2\xe9\x8e\xf8\x06\xc9\xb1\x43\x9a\x75\xf5\x3b\x6a\x23\x83\x82\xf2\x88\x08\xaa\x38\xa8\xe0\x71\x50\xb1\xa0\x42\x46\x9e\x58\x3b\x97\xca\xe7\x0a\xda\x98\x0f\xbd\xe2\x8a\x4d\x31\x49\xcc\xfd\x8f\xe1\xa4\x86\x02\xfe\x03\x89\xfe\xfe\xeb\xfb\xfd\x07\x24\xdf\x7c\x86\x22\xf7\x43\x6f\x9b\x1e\xa9\xb8\x93\x8d\x27\x46\x09\xf7\xeb\xbc\x07\x4a\xb7\xa7\x93\x39\xd0\xb4\xda\xab\xef\x63\x2d\x31\xf3\x01\xad\x45\x81\x98\xdf\x2d\x12\x52\xbd\x7f\xea\xef\x5f\x91\x4f\x1f\xf9\xf0\xd6\x85\xf7\xec\x99\xb2\x86\xa4\x5b\xfc\x07\x79\x79\xf8\x19\x00\x00\xff\xff\xd8\xa0\xa7\x83\x6a\x04\x00\x00") + +func piecesWbSvgBytes() ([]byte, error) { + return bindataRead( + _piecesWbSvg, + "pieces/wB.svg", + ) +} + +func piecesWbSvg() (*asset, error) { + bytes, err := piecesWbSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/wB.svg", size: 1130, mode: os.FileMode(420), modTime: time.Unix(1445797361, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesWkSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb4\x54\x4d\x73\x9b\x30\x10\xbd\xfb\x57\xec\x28\x57\x22\xf4\x01\xc6\x60\xe3\x4b\xae\xee\x8f\xa0\x41\x01\xb5\x04\x3c\x20\x9b\xba\xbf\xbe\xd2\x22\x70\x32\x6d\xea\x7a\xa6\xe1\x60\x3d\x69\x57\xbb\xef\xbd\xd5\x78\x37\x9c\x2b\xf8\xf1\xda\xb4\x43\x4e\x6a\x63\x8e\x59\x18\x8e\xe3\x48\x47\x49\xbb\xbe\x0a\x05\x63\x2c\xb4\x19\x04\xce\xaa\x1f\x74\xd7\xe6\x84\x53\x4e\x60\xd4\xa5\xa9\x73\x12\xc5\x04\x6a\xa5\xab\xda\x20\xde\xaf\x00\x76\x15\x0c\xe6\xd2\xa8\x9c\xbc\xe8\xa6\xc9\xda\xae\x55\x5b\x70\xf0\xb1\x3b\x16\xcf\xda\x5c\x32\xee\xf7\xfd\xa9\x51\x99\x3a\xab\xb6\x2b\xcb\xad\xbd\xd4\x77\xdf\x55\xf6\xc0\xf0\x9b\xf7\x8f\xd8\x28\xe3\x34\x5e\x4e\x1a\xdd\xaa\xe7\xe2\x98\xf5\xdd\xa9\x2d\xb7\x6f\x0e\xbf\x75\xba\x7d\x7f\xfa\xaa\x8d\xea\x1b\x6d\x97\x2c\x5a\xee\x97\xc5\x50\x17\x7d\x5f\x5c\x3c\x37\x7f\x7c\x65\x87\x32\xac\x90\x63\x61\x6a\x44\x00\x65\x4e\xbe\x80\x10\x34\x0e\x38\xa7\x6b\x09\x87\x69\xb3\x26\x3e\xfe\xbb\xe2\x0f\xe4\x2c\x3c\x91\xda\x96\x40\xf8\x51\x33\x16\x6c\x5c\x9b\x38\xd8\x7c\x62\x13\x27\x42\xc4\xf0\xb4\x20\x91\x04\x3c\xa1\x76\x8d\x9d\xd6\x88\x62\x6c\xc1\x22\x72\x48\x78\x27\x84\x8b\xb1\x09\xf1\x74\xce\xb9\xa2\x27\xe0\x1b\x5f\x6d\xae\x3e\xad\x7f\x12\xf4\xf0\x82\xdf\x5f\x35\xb9\xa9\x7f\x3d\x19\x73\xbf\x50\x3b\xb5\x38\x90\x89\xa3\x94\x04\x11\xa3\x28\x14\x57\x29\xa6\xc8\xc1\x23\x66\x73\x66\x14\x71\xa4\xeb\xb2\x36\x4e\x54\x8a\xa2\x24\x9a\x20\xdd\x68\xf8\xda\x4b\x92\x36\xe2\xdf\x84\x48\x16\x24\x27\x13\x52\x97\x97\x4e\x97\xd6\x6f\xea\xcc\xc5\x91\x9c\x48\xdf\xa1\xc3\x42\xf9\x27\xdc\x63\xd7\x2d\x0b\xd8\x64\x81\x25\x29\xf0\xd7\x4b\xfd\xe7\x27\x76\xab\xbe\x97\x9c\xd8\x9a\x93\xc9\xf2\x6a\xb2\x8d\xfd\xb7\x3e\x7e\x94\x32\xc2\x1e\xd1\x3c\xc6\x7b\xeb\xef\xc2\x6a\xbf\xda\xb9\x3f\xb8\xfd\xea\x57\x00\x00\x00\xff\xff\xdb\x19\xf5\xd8\x09\x05\x00\x00") + +func piecesWkSvgBytes() ([]byte, error) { + return bindataRead( + _piecesWkSvg, + "pieces/wK.svg", + ) +} + +func piecesWkSvg() (*asset, error) { + bytes, err := piecesWkSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/wK.svg", size: 1289, mode: os.FileMode(420), modTime: time.Unix(1445797369, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesWnSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xac\x54\x61\x6f\x9b\x4c\x0c\xfe\xde\x5f\x61\xd1\x2f\xef\x2b\x5d\xcc\xf9\x8e\x03\x8e\x86\x48\x53\xbe\x76\x3f\x02\x15\x12\xd8\x08\x44\x40\x93\x66\xbf\x7e\xbe\x0b\xa1\xeb\x96\x49\xd3\x34\xa4\xfa\xf1\xd9\x67\xfb\xf1\x73\x55\xd6\xe3\x69\x0f\x6f\x87\xb6\x1b\xf3\xa0\x9e\xa6\x63\x16\x86\xe7\xf3\x19\xcf\x1a\xfb\x61\x1f\x2a\x29\x65\xc8\x37\x02\x38\x55\xc3\xd8\xf4\x5d\x1e\x10\x52\x00\xe7\xa6\x9c\xea\x3c\x88\x4c\x00\x75\xd5\xec\xeb\xc9\xfb\x9b\x07\x80\xf5\x1e\xc6\xe9\xd2\x56\x79\xd0\x1f\x8b\x97\x66\xba\x64\xf4\x04\xbb\xa6\x6d\xb3\xae\xef\xaa\xab\xbb\xfa\x29\xb5\x1a\x5e\xdb\x2a\xab\x4e\x55\xd7\x97\xe5\x13\xd7\x0f\xfd\xd7\x2a\x7b\x94\xfe\xbb\x9d\x57\x7e\x66\x46\x68\x96\x48\xdb\x74\xd5\x4b\x71\xcc\x86\xfe\xb5\x2b\x9f\x7e\x08\x7e\xe9\x9b\xee\x63\xf4\xd0\x4c\xd5\xd0\x36\x0c\x59\xb4\xd4\x97\xc5\x58\x17\xc3\x50\x5c\x66\x6e\x73\xf8\x9d\x9d\xdf\x88\x77\x3a\x16\x53\xed\x3d\x80\x32\x0f\x3e\x83\x52\x82\x24\x6c\x41\x2b\x34\x82\x08\x74\xea\x30\x65\x14\xda\xc2\x33\x90\x71\xb8\xf5\x28\x41\xb1\xe5\x8b\xa0\x34\xdf\x09\xe6\x3e\xb3\x48\x5e\x99\xc7\x9d\xff\x7e\xd9\x3c\x80\xf0\x77\x04\x22\x37\x6e\xcb\x88\x3c\x53\x49\xb4\x04\x94\x62\x64\x84\x32\xa8\x13\xa0\x58\xa8\xc4\x11\xd0\x42\x59\xb6\x48\xcc\x8c\x50\x47\x40\xc4\x0e\x67\x2c\x5a\xc3\x31\x89\x32\x06\x52\x18\x11\x17\xa0\x8d\x5d\x5e\xb9\xce\x24\x1d\x12\xbf\xb6\xe5\x16\xa8\xb4\x8b\x68\xb7\xb4\x75\x60\xd0\xda\xc4\x75\xe2\x41\x31\x07\x19\xb8\x37\xcb\x72\xb3\xdb\xdb\x49\x63\x6a\x05\x4f\x60\x82\x4c\x5a\xb2\x0e\x8e\x16\xaa\x44\x58\x34\x6e\xb8\x66\xed\x58\xbf\xab\x93\x5c\xf3\x11\xbb\xb1\x8b\xc5\x4e\x58\xb9\xe0\xb3\xdb\xd2\x5c\xc5\xbf\x79\xc4\xec\x52\xee\x20\x65\x0a\x8a\x84\x5b\x9b\xdf\x27\x99\x1f\xc9\xdb\x7f\x23\xba\x75\x6f\x68\xd8\x7c\x02\xb7\x86\xff\x03\x5e\x0b\x1c\x91\xbb\x89\xa5\xe2\xdb\x5d\x06\x1f\xff\xc1\xff\x80\x01\xb1\x22\xef\x73\x68\x99\xe3\xa5\xbd\x9f\x98\x2b\x16\x02\xd3\x50\x74\xe3\xae\x1f\x0e\x79\x70\x28\xa6\xa1\x79\xfb\x4f\x62\x1a\xc7\x82\xeb\xc4\xca\x99\xeb\xd1\x62\x6c\xb5\x58\x19\xa4\x44\xff\xff\x17\xe4\xd7\xe1\x7e\xf3\xb0\x76\x3f\x1d\x9b\x87\xef\x01\x00\x00\xff\xff\xe4\xc9\xe6\xad\x63\x04\x00\x00") + +func piecesWnSvgBytes() ([]byte, error) { + return bindataRead( + _piecesWnSvg, + "pieces/wN.svg", + ) +} + +func piecesWnSvg() (*asset, error) { + bytes, err := piecesWnSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/wN.svg", size: 1123, mode: os.FileMode(420), modTime: time.Unix(1445797366, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesWpSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x5c\x92\xcd\x72\x9b\x30\x10\xc7\xef\x7e\x8a\x1d\xe5\x8a\x05\xfa\x02\xa4\x18\x5f\x72\x6d\x1f\x82\x89\x15\xa3\x96\x20\x8f\x50\x42\x9d\xa7\xef\x4a\x60\x3a\x8d\x0e\xd6\x6f\xff\xfb\xa1\xdd\x35\xa7\xf9\xf3\x0a\x7f\xde\xc7\x69\xee\xc8\x10\xe3\xcd\x94\xe5\xb2\x2c\x74\x11\xd4\x87\x6b\xc9\xab\xaa\x2a\x31\x82\xc0\xa7\x0d\xb3\xf3\x53\x47\x18\x65\x04\x16\x77\x89\x43\x47\xa4\x22\x30\x58\x77\x1d\x62\xe6\xf3\x01\xe0\x74\xeb\xe3\x80\x37\xc0\xa5\x23\x3f\x81\xf3\x42\xc3\x0b\x30\x4d\x1b\x8d\xc4\xda\x82\x55\x88\x19\x44\x72\xa4\x9b\xb6\x49\xa0\x5c\x17\x4c\xd2\x86\x25\x6e\x50\x57\x54\xb4\x29\xa4\xa6\xad\x28\xf0\x57\x01\x4a\xaa\x40\xaf\xd2\x2b\x72\x96\xfc\x99\x04\xad\x44\x0a\x95\xb2\xe0\x92\xb6\x12\x58\x43\x35\x2b\x78\x9d\x74\x0c\x92\xd9\x6a\x68\x85\xa9\x15\x26\x08\x46\x55\xbb\xa1\xc6\xd2\x3f\x40\x88\x07\xbf\x6c\x9c\x43\xb8\xc6\x9c\x2d\x33\x55\xd3\x7b\x4d\xd4\x54\xbd\xbd\xc6\xdb\xbd\x89\x15\x53\x67\x99\xd6\x76\x31\x96\x35\xeb\x10\x5c\x51\xdc\xca\x63\x3a\xb4\x1a\xb6\xcd\xcd\xeb\x6d\x19\x19\x92\xb3\xde\xd6\x85\x8f\x70\x86\xfb\xcb\xeb\xfc\x02\x92\x17\x3c\xc7\xfb\x68\x3b\xe2\x6f\xfd\xab\x8b\x77\xc3\x9e\xe1\xcd\x8d\xa3\x79\x7a\xcb\x67\xb5\x8e\xdf\xbc\xc7\xf0\x31\x5a\x33\xf9\xe9\xcb\x06\xff\x8c\x25\x82\xff\x6d\xcd\x53\x95\xcf\xc3\x3e\xe6\xbf\xd7\xe0\xf8\xbb\x32\xba\xc9\xbe\xf6\x37\x13\xfc\xc7\x74\xf9\x4f\xfd\xe5\xdd\x64\xde\x5d\xb4\x61\x97\xb3\x35\x3a\xbc\x8c\xdc\xc5\x4b\x3f\x0f\x7d\x08\xfd\x3d\xbd\x6e\x77\xf9\x5f\x7f\x04\xca\xf3\xe1\x94\xbe\xb6\xf3\xe1\x6f\x00\x00\x00\xff\xff\x88\xb7\x8e\xde\x96\x02\x00\x00") + +func piecesWpSvgBytes() ([]byte, error) { + return bindataRead( + _piecesWpSvg, + "pieces/wP.svg", + ) +} + +func piecesWpSvg() (*asset, error) { + bytes, err := piecesWpSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/wP.svg", size: 662, mode: os.FileMode(420), modTime: time.Unix(1445797373, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesWqSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xbc\x54\xcd\x8e\x9b\x30\x10\xbe\xe7\x29\x46\xde\x4b\x2b\x99\x1f\xdb\x40\x80\x84\x48\x55\xae\xed\x43\xd0\xc5\x01\x5a\x02\x91\x71\x42\xb3\x4f\xdf\xb1\xf9\x49\x9a\xc3\xee\xa5\x1b\x2c\xe5\xfb\x3c\x63\xcf\x7c\x33\x8e\xbd\xed\x2f\x25\xfc\x39\x36\x6d\x9f\x91\x4a\xeb\x53\xea\x79\xc3\x30\xb8\x83\x70\x3b\x55\x7a\xdc\xf7\x7d\x0f\x57\x10\xb8\x48\xd5\xd7\x5d\x9b\x11\xe6\x32\x02\x43\x5d\xe8\x2a\x23\x41\x48\xa0\x92\x75\x59\x69\xcb\x77\x2b\x80\x6d\x09\xbd\xbe\x36\x32\x23\xdd\x29\x7f\xad\xf5\x35\x65\x1b\x38\xd4\x4d\x93\xbe\x1c\xec\x37\xce\x9c\x07\xaf\xa3\xce\x8d\x4c\xe5\x45\xb6\x5d\x51\x6c\x30\x84\xea\x7e\xcb\xf4\xc5\xb7\xdf\x3c\x77\x6c\xda\x94\xb9\xe1\x62\x69\xea\x56\xbe\xe6\xa7\x54\x75\xe7\xb6\xd8\xdc\x19\x7f\x75\x75\xfb\xaf\xf5\x58\x6b\xa9\x9a\x1a\x21\x0d\x96\xfd\x45\xde\x57\xb9\x52\xf9\x35\x6d\xbb\x56\x2e\xe6\x9b\x3a\x5b\x14\x96\x75\xca\x75\x65\x19\x40\x91\x91\x1f\x90\x00\x13\xf0\x0d\x38\x0e\x1f\x18\x0e\x08\xe9\xa3\xc5\xae\x79\x23\xd3\x36\xad\xf2\xb6\x3f\x74\xea\x98\x11\x4b\x9b\x5c\xcb\x2f\x0e\xa3\x0e\xfb\x4a\xc0\xfb\xdc\x34\x2c\x74\x43\xea\xe0\xcf\xe7\xa7\x12\xfc\x29\x15\xad\xa9\x13\x3c\xa3\x1c\x1e\x60\xa2\xf7\xd2\x50\x1e\xc1\x1e\xd8\x1a\x1b\xcc\x51\x11\x08\x7f\xc2\xc8\x78\xbe\x83\x88\x29\x0b\x0c\x32\xca\xc3\x11\x19\x43\xe4\xe1\xbc\x03\x39\x47\x9e\x58\xca\x92\x9b\x99\x05\x94\xf9\x33\xb3\x9b\xd7\x63\x2c\x9b\xf4\x0d\x66\xe1\xd3\x85\x7b\xb8\x12\x3f\xcf\x5a\x6f\x3e\x14\x8e\x10\x83\xc9\x62\x11\xaf\x16\x15\xbe\xa9\xc7\x28\x12\x38\x9d\x19\x22\x15\x02\xe7\xfb\x71\xb5\x30\x12\x47\x16\x2d\x68\xc2\x89\xb5\x71\x30\x2a\xe2\x3b\x9c\x1a\x24\x4c\x8d\x7c\x61\x22\x98\xbd\x33\x13\xa6\x29\x36\x82\xb1\x44\x93\x27\x02\x93\x6d\x4c\x29\xc4\x2c\x43\x4c\xc2\xc4\x22\xd5\x38\x46\xf9\x76\x3d\x16\x64\x0e\x21\x9e\x8e\x62\x3f\x66\xb6\xbd\xbd\x1d\xd7\xff\x68\xe5\x5d\xdb\x30\x6a\x62\xff\x02\xc9\xac\xe6\x21\xb2\x7d\x0a\xed\x5b\xf3\x4e\xbc\x5b\xab\x63\x6a\xca\x43\xe5\x23\x4e\xd5\x7f\x14\x73\xeb\x95\xbb\xd5\xd6\x3c\xdc\xbb\xd5\xdf\x00\x00\x00\xff\xff\xe0\xcf\x6d\x6c\xe1\x05\x00\x00") + +func piecesWqSvgBytes() ([]byte, error) { + return bindataRead( + _piecesWqSvg, + "pieces/wQ.svg", + ) +} + +func piecesWqSvg() (*asset, error) { + bytes, err := piecesWqSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/wQ.svg", size: 1505, mode: os.FileMode(420), modTime: time.Unix(1445797377, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _piecesWrSvg = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xa4\x93\xdd\x72\xb2\x30\x10\x86\xcf\xbd\x8a\x9d\x78\xaa\x40\xf8\xd1\x21\x80\x57\xf0\x7d\x17\x41\x25\x42\x5a\x4c\x98\x10\xa5\xf6\xea\x9b\x84\x3f\x6b\xad\xed\x8c\x9c\x3c\x9b\xd7\xec\xee\x9b\x35\x49\xdb\x73\x09\xef\xc7\x9a\xb7\x19\xaa\x94\x6a\x88\xeb\x76\x5d\xe7\x74\x81\x23\x64\xe9\xfa\x9e\xe7\xb9\x7a\x07\x82\x33\x95\x2d\x13\x3c\x43\xd8\xc1\x08\x3a\x56\xa8\x2a\x43\x61\x84\xa0\xa2\xac\xac\x94\x8d\x77\x0b\x80\xb4\x84\x56\x5d\x6a\x9a\x21\xd1\xe4\x7b\xa6\x2e\x04\x27\x70\x60\x75\x4d\x96\x07\xfb\xf5\xab\xf5\xcd\xaf\x6b\x79\xaa\x29\xa1\x67\xca\x45\x51\x24\xba\x84\x14\x6f\x94\x2c\x3d\xfb\x8d\xeb\xb5\x6d\x4b\xb0\x13\x4d\x4a\xcd\x38\xdd\xe7\x0d\x91\xe2\xc4\x8b\xe4\x4a\x7c\x15\x8c\x7f\x55\x8f\x4c\x51\x59\x33\x0d\x12\x4e\xf9\x45\xde\x56\xb9\x94\xf9\x85\x70\xc1\xe9\x24\xcf\xee\xec\xa1\xf4\xb1\x9a\x5c\x55\x36\x02\x28\x32\xf4\x1f\xe2\x55\x10\xc3\x3f\x08\x36\x33\x37\x9a\xf1\x84\x18\x3e\x00\x0d\x19\xc3\x44\x6e\x3c\xbf\x9c\x94\x4a\x10\xb8\x3f\x74\xc0\x7e\x5f\xcb\xd0\x37\x2d\x82\x99\xa3\xbe\x79\xba\x09\x5e\xe1\xd0\x14\xc3\x2b\x73\x0c\x1c\x8d\xc0\x58\xd3\xf7\x26\x1a\xd9\x8f\x46\x58\x35\xf0\x26\xda\x11\x84\x23\x70\xf8\x94\x27\x5b\xc1\x54\xd2\xde\xb6\xc6\x4d\x38\xd0\x78\x7d\x90\x36\x6c\xd7\xf4\x63\x27\xea\x13\xe7\x08\x6f\xff\x60\x0a\x6e\x6f\x90\xbd\x34\x8f\xbc\x4e\xcd\x02\xdf\x89\xfa\x3f\x08\xcf\x51\x6f\xe0\xf7\xf1\xdf\x1b\x9a\x7d\x34\xd7\xb7\xf2\xdb\x73\xb8\xef\x32\x75\xcb\xdd\x22\x35\xaf\x76\xb7\xf8\x0c\x00\x00\xff\xff\xed\x96\xaa\xe2\xde\x03\x00\x00") + +func piecesWrSvgBytes() ([]byte, error) { + return bindataRead( + _piecesWrSvg, + "pieces/wR.svg", + ) +} + +func piecesWrSvg() (*asset, error) { + bytes, err := piecesWrSvgBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "pieces/wR.svg", size: 990, mode: os.FileMode(420), modTime: time.Unix(1445797380, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "pieces/.DS_Store": piecesDs_store, + "pieces/bB.svg": piecesBbSvg, + "pieces/bK.svg": piecesBkSvg, + "pieces/bN.svg": piecesBnSvg, + "pieces/bP.svg": piecesBpSvg, + "pieces/bQ.svg": piecesBqSvg, + "pieces/bR.svg": piecesBrSvg, + "pieces/wB.svg": piecesWbSvg, + "pieces/wK.svg": piecesWkSvg, + "pieces/wN.svg": piecesWnSvg, + "pieces/wP.svg": piecesWpSvg, + "pieces/wQ.svg": piecesWqSvg, + "pieces/wR.svg": piecesWrSvg, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} + +var _bintree = &bintree{nil, map[string]*bintree{ + "pieces": &bintree{nil, map[string]*bintree{ + ".DS_Store": &bintree{piecesDs_store, map[string]*bintree{}}, + "bB.svg": &bintree{piecesBbSvg, map[string]*bintree{}}, + "bK.svg": &bintree{piecesBkSvg, map[string]*bintree{}}, + "bN.svg": &bintree{piecesBnSvg, map[string]*bintree{}}, + "bP.svg": &bintree{piecesBpSvg, map[string]*bintree{}}, + "bQ.svg": &bintree{piecesBqSvg, map[string]*bintree{}}, + "bR.svg": &bintree{piecesBrSvg, map[string]*bintree{}}, + "wB.svg": &bintree{piecesWbSvg, map[string]*bintree{}}, + "wK.svg": &bintree{piecesWkSvg, map[string]*bintree{}}, + "wN.svg": &bintree{piecesWnSvg, map[string]*bintree{}}, + "wP.svg": &bintree{piecesWpSvg, map[string]*bintree{}}, + "wQ.svg": &bintree{piecesWqSvg, map[string]*bintree{}}, + "wR.svg": &bintree{piecesWrSvg, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} diff --git a/image/internal/pieces/bB.svg b/image/internal/pieces/bB.svg new file mode 100755 index 0000000..f41f35a --- /dev/null +++ b/image/internal/pieces/bB.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/image/internal/pieces/bK.svg b/image/internal/pieces/bK.svg new file mode 100755 index 0000000..a7d85c0 --- /dev/null +++ b/image/internal/pieces/bK.svg @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/image/internal/pieces/bN.svg b/image/internal/pieces/bN.svg new file mode 100755 index 0000000..03d20f5 --- /dev/null +++ b/image/internal/pieces/bN.svg @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/image/internal/pieces/bP.svg b/image/internal/pieces/bP.svg new file mode 100755 index 0000000..4fe10d3 --- /dev/null +++ b/image/internal/pieces/bP.svg @@ -0,0 +1,5 @@ + + + diff --git a/image/internal/pieces/bQ.svg b/image/internal/pieces/bQ.svg new file mode 100755 index 0000000..2b29f3c --- /dev/null +++ b/image/internal/pieces/bQ.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + diff --git a/image/internal/pieces/bR.svg b/image/internal/pieces/bR.svg new file mode 100755 index 0000000..19cb4e6 --- /dev/null +++ b/image/internal/pieces/bR.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + diff --git a/image/internal/pieces/wB.svg b/image/internal/pieces/wB.svg new file mode 100755 index 0000000..60b82e6 --- /dev/null +++ b/image/internal/pieces/wB.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/image/internal/pieces/wK.svg b/image/internal/pieces/wK.svg new file mode 100755 index 0000000..dae6ec6 --- /dev/null +++ b/image/internal/pieces/wK.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + diff --git a/image/internal/pieces/wN.svg b/image/internal/pieces/wN.svg new file mode 100755 index 0000000..6ae4210 --- /dev/null +++ b/image/internal/pieces/wN.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/image/internal/pieces/wP.svg b/image/internal/pieces/wP.svg new file mode 100755 index 0000000..7d0b25e --- /dev/null +++ b/image/internal/pieces/wP.svg @@ -0,0 +1,5 @@ + + + diff --git a/image/internal/pieces/wQ.svg b/image/internal/pieces/wQ.svg new file mode 100755 index 0000000..3b13d3a --- /dev/null +++ b/image/internal/pieces/wQ.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + diff --git a/image/internal/pieces/wR.svg b/image/internal/pieces/wR.svg new file mode 100755 index 0000000..688d547 --- /dev/null +++ b/image/internal/pieces/wR.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/image/test.svg b/image/test.svg new file mode 100644 index 0000000..85eaaae --- /dev/null +++ b/image/test.svg @@ -0,0 +1,534 @@ + + + + + + + + + + + + + + + + +1 +a + + + + + + + + + +b + + + + + + + + + + + +c + + + + + + + + + + + + + + +d + + + + + + + + + + + + +e + + + + + + + + + + + +f + + + + + + + + + +g + + + + + + + + + + + + +h + + + + +2 + + + + + + + + + + + + + + + + + + + + + + + + + + + +3 + + + + + + + + +4 + + + + + + + + + + + + +5 + + + + + + + + +6 + + + + + + + + + + + +7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/move.go b/move.go new file mode 100644 index 0000000..453d104 --- /dev/null +++ b/move.go @@ -0,0 +1,72 @@ +package chess + +// A MoveTag represents a notable consequence of a move. +type MoveTag uint16 + +const ( + // KingSideCastle indicates that the move is a king side castle. + KingSideCastle MoveTag = 1 << iota + // QueenSideCastle indicates that the move is a queen side castle. + QueenSideCastle + // Capture indicates that the move captures a piece. + Capture + // EnPassant indicates that the move captures via en passant. + EnPassant + // Check indicates that the move puts the opposing player in check. + Check + // inCheck indicates that the move puts the moving player in check and + // is therefore invalid. + inCheck +) + +// A Move is the movement of a piece from one square to another. +type Move struct { + s1 Square + s2 Square + promo PieceType + tags MoveTag +} + +// String returns a string useful for debugging. String doesn't return +// algebraic notation. +func (m *Move) String() string { + return m.s1.String() + m.s2.String() + m.promo.String() +} + +// S1 returns the origin square of the move. +func (m *Move) S1() Square { + return m.s1 +} + +// S2 returns the destination square of the move. +func (m *Move) S2() Square { + return m.s2 +} + +// Promo returns promotion piece type of the move. +func (m *Move) Promo() PieceType { + return m.promo +} + +// HasTag returns true if the move contains the MoveTag given. +func (m *Move) HasTag(tag MoveTag) bool { + return (tag & m.tags) > 0 +} + +func (m *Move) addTag(tag MoveTag) { + m.tags = m.tags | tag +} + +type moveSlice []*Move + +func (a moveSlice) find(m *Move) *Move { + if m == nil { + return nil + } + for _, move := range a { + if move.String() == m.String() { + return move + } + } + return nil +} diff --git a/move_test.go b/move_test.go new file mode 100644 index 0000000..ceccb99 --- /dev/null +++ b/move_test.go @@ -0,0 +1,302 @@ +package chess + +import ( + "log" + "testing" +) + +type moveTest struct { + pos *Position + m *Move + postPos *Position +} + +var ( + validMoves = []moveTest{ + // pawn moves + {m: &Move{s1: E2, s2: E4}, pos: unsafeFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")}, + {m: &Move{s1: A2, s2: A3}, pos: unsafeFEN("8/8/8/8/8/8/P7/8 w - - 0 1")}, + {m: &Move{s1: A7, s2: A6}, pos: unsafeFEN("8/p7/8/8/8/8/8/8 b - - 0 1")}, + {m: &Move{s1: A7, s2: A5}, pos: unsafeFEN("8/p7/8/8/8/8/8/8 b - - 0 1")}, + {m: &Move{s1: C4, s2: B5}, pos: unsafeFEN("8/8/8/1p1p4/2P5/8/8/8 w - - 0 1")}, + {m: &Move{s1: C4, s2: D5}, pos: unsafeFEN("8/8/8/1p1p4/2P5/8/8/8 w - - 0 1")}, + {m: &Move{s1: C4, s2: C5}, pos: unsafeFEN("8/8/8/1p1p4/2P5/8/8/8 w - - 0 1")}, + {m: &Move{s1: C5, s2: B4}, pos: unsafeFEN("8/8/8/2p5/1P1P4/8/8/8 b - - 0 1")}, + {m: &Move{s1: C5, s2: D4}, pos: unsafeFEN("8/8/8/2p5/1P1P4/8/8/8 b - - 0 1")}, + {m: &Move{s1: C5, s2: C4}, pos: unsafeFEN("8/8/8/2p5/1P1P4/8/8/8 b - - 0 1")}, + {m: &Move{s1: A4, s2: B3}, pos: unsafeFEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 0 23")}, + {m: &Move{s1: A2, s2: A1, promo: Queen}, pos: unsafeFEN("8/8/8/8/8/8/p7/8 b - - 0 1")}, + {m: &Move{s1: E7, s2: E6}, pos: unsafeFEN("r2qkbnr/pppnpppp/8/3p4/6b1/1P3NP1/PBPPPP1P/RN1QKB1R b KQkq - 2 4")}, + // knight moves + {m: &Move{s1: E4, s2: F6}, pos: unsafeFEN("8/8/8/3pp3/4N3/8/5B2/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: D6}, pos: unsafeFEN("8/8/8/3pp3/4N3/8/5B2/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: G5}, pos: unsafeFEN("8/8/8/3pp3/4N3/8/5B2/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: G3}, pos: unsafeFEN("8/8/8/3pp3/4N3/8/5B2/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: D2}, pos: unsafeFEN("8/8/8/3pp3/4N3/8/5B2/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: C3}, pos: unsafeFEN("8/8/8/3pp3/4N3/8/5B2/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: C5}, pos: unsafeFEN("8/8/8/3pp3/4N3/8/5B2/8 w - - 0 1")}, + {m: &Move{s1: B8, s2: D7}, pos: unsafeFEN("rn1qkb1r/pp3ppp/2p1pn2/3p4/2PP4/2NQPN2/PP3PPP/R1B1K2R b KQkq - 0 7")}, + {m: &Move{s1: F6, s2: E4}, pos: unsafeFEN("r1b1k2r/ppp2ppp/2p2n2/4N3/4P3/2P5/PPP2PPP/R1BK3R b kq - 0 8")}, + // bishop moves + {m: &Move{s1: E4, s2: H7}, pos: unsafeFEN("8/8/8/3pp3/4B3/5N2/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: D5}, pos: unsafeFEN("8/8/8/3pp3/4B3/5N2/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: B1}, pos: unsafeFEN("8/8/8/3pp3/4B3/5N2/8/8 w - - 0 1")}, + // rook moves + {m: &Move{s1: B2, s2: B4}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1R6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: B7}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1R6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: A2}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1R6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: H2}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1R6/1B6 w - - 0 1")}, + {m: &Move{s1: E1, s2: E8}, pos: unsafeFEN("r3r1k1/p4p1p/3p4/1p4p1/2pP4/2P2P2/PP3P1P/R3RK2 w - g6 0 22")}, + // queen moves + {m: &Move{s1: B2, s2: E5}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1Q6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: A1}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1Q6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: A2}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1Q6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: H2}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1Q6/1B6 w - - 0 1")}, + {m: &Move{s1: D8, s2: D1}, pos: unsafeFEN("r1bqk2r/ppp2ppp/2p2n2/4N3/4P3/2P5/PPP2PPP/R1BQK2R b KQkq - 0 7")}, + // king moves + {m: &Move{s1: E4, s2: E5}, pos: unsafeFEN("5r2/8/8/8/4K3/8/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: E3}, pos: unsafeFEN("5r2/8/8/8/4K3/8/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: D3}, pos: unsafeFEN("5r2/8/8/8/4K3/8/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: D4}, pos: unsafeFEN("5r2/8/8/8/4K3/8/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: D5}, pos: unsafeFEN("5r2/8/8/8/4K3/8/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: E5}, pos: unsafeFEN("5r2/8/8/8/4K3/8/8/8 w - - 0 1")}, + // castleing + {m: &Move{s1: E1, s2: G1}, pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1")}, + {m: &Move{s1: E1, s2: C1}, pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1")}, + {m: &Move{s1: E8, s2: G8}, pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1")}, + {m: &Move{s1: E8, s2: C8}, pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1")}, + // king moving in front of enemy pawn http://en.lichess.org/4HXJOtpN#75 + {m: &Move{s1: F8, s2: G7}, pos: unsafeFEN("3rrk2/8/2p3P1/1p2nP1p/pP2p3/P1B1NbPB/2P2K2/5R2 b - - 1 38")}, + } + + invalidMoves = []moveTest{ + // out of turn moves + {m: &Move{s1: E7, s2: E5}, pos: unsafeFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")}, + {m: &Move{s1: E2, s2: E4}, pos: unsafeFEN("rnbqkbnr/1ppppppp/p7/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1")}, + // pawn moves + {m: &Move{s1: E2, s2: D3}, pos: unsafeFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")}, + {m: &Move{s1: E2, s2: F3}, pos: unsafeFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")}, + {m: &Move{s1: E2, s2: E5}, pos: unsafeFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")}, + {m: &Move{s1: A2, s2: A1}, pos: unsafeFEN("8/8/8/8/8/8/p7/8 b - - 0 1")}, + {m: &Move{s1: E6, s2: E5}, pos: unsafeFEN(`2b1r3/2k2p1B/p2np3/4B3/8/5N2/PP1K1PPP/3R4 b - - 2 1`)}, + {m: &Move{s1: H7, s2: H5}, pos: unsafeFEN(`2bqkbnr/rpppp2p/2n2p2/p5pB/5P2/4P3/PPPP2PP/RNBQK1NR b KQk - 4 6`)}, + // knight moves + {m: &Move{s1: E4, s2: F2}, pos: unsafeFEN("8/8/8/3pp3/4N3/8/5B2/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: F3}, pos: unsafeFEN("8/8/8/3pp3/4N3/8/5B2/8 w - - 0 1")}, + // bishop moves + {m: &Move{s1: E4, s2: C6}, pos: unsafeFEN("8/8/8/3pp3/4B3/5N2/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: E5}, pos: unsafeFEN("8/8/8/3pp3/4B3/5N2/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: E4}, pos: unsafeFEN("8/8/8/3pp3/4B3/5N2/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: F3}, pos: unsafeFEN("8/8/8/3pp3/4B3/5N2/8/8 w - - 0 1")}, + // rook moves + {m: &Move{s1: B2, s2: B1}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1R6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: C3}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1R6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: B8}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1R6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: G7}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1R6/1B6 w - - 0 1")}, + // queen moves + {m: &Move{s1: B2, s2: B1}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1Q6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: C4}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1Q6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: B8}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1Q6/1B6 w - - 0 1")}, + {m: &Move{s1: B2, s2: G7}, pos: unsafeFEN("8/1p5b/4N3/4p3/8/8/1Q6/1B6 w - - 0 1")}, + // king moves + {m: &Move{s1: E4, s2: F3}, pos: unsafeFEN("5r2/8/8/8/4K3/8/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: F4}, pos: unsafeFEN("5r2/8/8/8/4K3/8/8/8 w - - 0 1")}, + {m: &Move{s1: E4, s2: F5}, pos: unsafeFEN("5r2/8/8/8/4K3/8/8/8 w - - 0 1")}, + // castleing + {m: &Move{s1: E1, s2: B1}, pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1")}, + {m: &Move{s1: E8, s2: B8}, pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R3K2R b KQkq - 0 1")}, + {m: &Move{s1: E1, s2: C1}, pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R2QK2R w KQkq - 0 1")}, + {m: &Move{s1: E1, s2: C1}, pos: unsafeFEN("2r1k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1")}, + {m: &Move{s1: E1, s2: C1}, pos: unsafeFEN("3rk2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1")}, + {m: &Move{s1: E1, s2: G1}, pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R3K2R w Qkq - 0 1")}, + {m: &Move{s1: E1, s2: C1}, pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R3K2R w Kkq - 0 1")}, + // invalid promotion for non-pawn move + {m: &Move{s1: B8, s2: D7, promo: Pawn}, pos: unsafeFEN("rn1qkb1r/pp3ppp/2p1pn2/3p4/2PP4/2NQPN2/PP3PPP/R1B1K2R b KQkq - 0 7")}, + // en passant on doubled pawn file http://en.lichess.org/TnRtrHxf#24 + {m: &Move{s1: E3, s2: F6}, pos: unsafeFEN("r1b2rk1/pp2b1pp/1qn1p3/3pPp2/1P1P4/P2BPN2/6PP/RN1Q1RK1 w - f6 0 13")}, + // can't move piece out of pin (even if checking enemy king) http://en.lichess.org/JCRBhXH7#62 + {m: &Move{s1: E1, s2: E7}, pos: unsafeFEN("4R3/1r1k2pp/p1p5/1pP5/8/8/1PP3PP/2K1Rr2 w - - 5 32")}, + // invalid one up pawn capture + {m: &Move{s1: E6, s2: E5}, pos: unsafeFEN(`2b1r3/2k2p1B/p2np3/4B3/8/5N2/PP1K1PPP/3R4 b - - 2 1`)}, + // invalid two up pawn capture + {m: &Move{s1: H7, s2: H5}, pos: unsafeFEN(`2bqkbnr/rpppp2p/2n2p2/p5pB/5P2/4P3/PPPP2PP/RNBQK1NR b KQk - 4 6`)}, + // invalid pawn move d5e4 + {m: &Move{s1: D5, s2: E4}, pos: unsafeFEN(`rnbqkbnr/pp2pppp/8/2pp4/3P4/4PN2/PPP2PPP/RNBQKB1R b KQkq - 0 3`)}, + } + + positionUpdates = []moveTest{ + { + m: &Move{s1: E2, s2: E4}, + pos: unsafeFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"), + postPos: unsafeFEN("rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"), + }, + { + m: &Move{s1: E1, s2: G1, tags: KingSideCastle}, + pos: unsafeFEN("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1"), + postPos: unsafeFEN("r3k2r/8/8/8/8/8/8/R4RK1 b kq - 0 1"), + }, + { + m: &Move{s1: A4, s2: B3, tags: EnPassant}, + pos: unsafeFEN("2r3k1/1q1nbppp/r3p3/3pP3/pPpP4/P1Q2N2/2RN1PPP/2R4K b - b3 0 23"), + postPos: unsafeFEN("2r3k1/1q1nbppp/r3p3/3pP3/11pP4/PpQ2N2/2RN1PPP/2R4K w - - 0 24"), + }, + { + m: &Move{s1: E1, s2: G1, tags: KingSideCastle}, + pos: unsafeFEN("r2qk2r/pp1n1ppp/2pbpn2/3p4/2PP4/1PNQPN2/P4PPP/R1B1K2R w KQkq - 1 9"), + postPos: unsafeFEN("r2qk2r/pp1n1ppp/2pbpn2/3p4/2PP4/1PNQPN2/P4PPP/R1B2RK1 b kq - 0 9"), + }, + } +) + +func unsafeFEN(s string) *Position { + pos, err := decodeFEN(s) + if err != nil { + log.Fatal(err) + } + return pos +} + +func TestValidMoves(t *testing.T) { + for _, mt := range validMoves { + if !moveIsValid(mt.pos, mt.m, false) { + log.Println(mt.pos.String()) + log.Println(mt.pos.board.Draw()) + log.Println(mt.pos.ValidMoves()) + log.Println("In Check:", squaresAreAttacked(mt.pos, mt.pos.board.whiteKingSq)) + // log.Println("In Check:", mt.pos.inCheck()) + mt.pos.turn = mt.pos.turn.Other() + t.Fatalf("expected move %s to be valid", mt.m) + } + } +} + +func TestInvalidMoves(t *testing.T) { + for _, mt := range invalidMoves { + if moveIsValid(mt.pos, mt.m, false) { + log.Println(mt.pos.String()) + log.Println(mt.pos.board.Draw()) + t.Fatalf("expected move %s to be invalid", mt.m) + } + } +} + +func TestPositionUpdates(t *testing.T) { + for _, mt := range positionUpdates { + if !moveIsValid(mt.pos, mt.m, true) { + log.Println(mt.pos.String()) + log.Println(mt.pos.board.Draw()) + log.Println(mt.pos.ValidMoves()) + t.Fatalf("expected move %s %v to be valid", mt.m, mt.m.tags) + } + + postPos := mt.pos.Update(mt.m) + if postPos.String() != mt.postPos.String() { + t.Fatalf("starting from board \n%s%s\n after move %s\n expected board to be %s\n%s\n but was %s\n%s\n", + mt.pos.String(), + mt.pos.board.Draw(), + mt.m.String(), + mt.postPos.String(), + mt.postPos.board.Draw(), + postPos.String(), + postPos.board.Draw(), + ) + } + } +} + +type perfTest struct { + pos *Position + nodesPerDepth []int +} + +/* https://www.chessprogramming.org/Perft_Results */ +var perfResults = []perfTest{ + {pos: unsafeFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"), nodesPerDepth: []int{ + 20, 400, 8902, 197281, + // 4865609, 119060324, 3195901860, 84998978956, 2439530234167, 69352859712417 + }}, + {pos: unsafeFEN("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1"), nodesPerDepth: []int{ + 48, 2039, 97862, + // 4085603, 193690690 + }}, + {pos: unsafeFEN("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1"), nodesPerDepth: []int{ + 14, 191, 2812, 43238, 674624, + // 11030083, 178633661 + }}, + {pos: unsafeFEN("r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1"), nodesPerDepth: []int{ + 6, 264, 9467, 422333, + // 15833292, 706045033 + }}, + {pos: unsafeFEN("r2q1rk1/pP1p2pp/Q4n2/bbp1p3/Np6/1B3NBn/pPPP1PPP/R3K2R b KQ - 0 1"), nodesPerDepth: []int{ + 6, 264, 9467, 422333, + // 15833292, 706045033 + }}, + {pos: unsafeFEN("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8"), nodesPerDepth: []int{ + 44, 1486, 62379, + // 2103487, 89941194 + }}, + {pos: unsafeFEN("r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10"), nodesPerDepth: []int{ + 46, 2079, 89890, + // 3894594, 164075551, 6923051137, 287188994746, 11923589843526, 490154852788714 + }}, +} + +func TestPerfResults(t *testing.T) { + for _, perf := range perfResults { + countMoves(t, perf.pos, []*Position{perf.pos}, perf.nodesPerDepth, len(perf.nodesPerDepth)) + } +} + +func countMoves(t *testing.T, originalPosition *Position, positions []*Position, nodesPerDepth []int, maxDepth int) { + if len(nodesPerDepth) == 0 { + return + } + depth := maxDepth - len(nodesPerDepth) + 1 + expNodes := nodesPerDepth[0] + newPositions := make([]*Position, 0) + for _, pos := range positions { + for _, move := range pos.ValidMoves() { + newPos := pos.Update(move) + newPositions = append(newPositions, newPos) + } + } + gotNodes := len(newPositions) + if expNodes != gotNodes { + t.Errorf("Depth: %d Expected: %d Got: %d", depth, expNodes, gotNodes) + t.Log("##############################") + t.Log("# Original position info") + t.Log("###") + t.Log(originalPosition.String()) + t.Log(originalPosition.board.Draw()) + t.Log("##############################") + t.Log("# Details in JSONL (http://jsonlines.org)") + t.Log("###") + for _, pos := range positions { + t.Logf(`{"position": "%s", "moves": %d}`, pos.String(), len(pos.ValidMoves())) + } + } + countMoves(t, originalPosition, newPositions, nodesPerDepth[1:], maxDepth) +} + +func BenchmarkValidMoves(b *testing.B) { + pos := unsafeFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") + b.ResetTimer() + for n := 0; n < b.N; n++ { + pos.ValidMoves() + pos.validMoves = nil + } +} + +func moveIsValid(pos *Position, m *Move, useTags bool) bool { + for _, move := range pos.ValidMoves() { + if move.s1 == m.s1 && move.s2 == m.s2 && move.promo == m.promo { + if useTags { + if m.tags != move.tags { + return false + } + } + return true + } + } + return false +} diff --git a/notation.go b/notation.go new file mode 100644 index 0000000..d42650f --- /dev/null +++ b/notation.go @@ -0,0 +1,224 @@ +package chess + +import ( + "fmt" + "strings" +) + +// Encoder is the interface implemented by objects that can +// encode a move into a string given the position. It is not +// the encoders responsibility to validate the move. +type Encoder interface { + Encode(pos *Position, m *Move) string +} + +// Decoder is the interface implemented by objects that can +// decode a string into a move given the position. It is not +// the decoders responsibility to validate the move. An error +// is returned if the string could not be decoded. +type Decoder interface { + Decode(pos *Position, s string) (*Move, error) +} + +// Notation is the interface implemented by objects that can +// encode and decode moves. +type Notation interface { + Encoder + Decoder +} + +// LongAlgebraicNotation is a more computer friendly alternative to algebraic +// notation. This notation uses the same format as the UCI (Universal Chess +// Interface). Examples: e2e4, e7e5, e1g1 (white short castling), e7e8q (for promotion) +type LongAlgebraicNotation struct{} + +// String implements the fmt.Stringer interface and returns +// the notation's name. +func (_ LongAlgebraicNotation) String() string { + return "Long Algebraic Notation" +} + +// Encode implements the Encoder interface. +func (_ LongAlgebraicNotation) Encode(pos *Position, m *Move) string { + return m.S1().String() + m.S2().String() + m.Promo().String() +} + +// Decode implements the Decoder interface. +func (_ LongAlgebraicNotation) Decode(pos *Position, s string) (*Move, error) { + l := len(s) + err := fmt.Errorf(`chess: failed to decode long algebraic notation text "%s" for position %s`, s, pos.String()) + if l < 4 || l > 5 { + return nil, err + } + s1, ok := strToSquareMap[s[0:2]] + if !ok { + return nil, err + } + s2, ok := strToSquareMap[s[2:4]] + if !ok { + return nil, err + } + promo := NoPieceType + if l == 5 { + promo = pieceTypeFromChar(s[4:5]) + if promo == NoPieceType { + return nil, err + } + } + m := &Move{s1: s1, s2: s2, promo: promo} + p := pos.Board().Piece(s1) + if p.Type() == King { + if (s1 == E1 && s2 == G1) || (s1 == E8 && s2 == G8) { + m.addTag(KingSideCastle) + } else if (s1 == E1 && s2 == C1) || (s1 == E8 && s2 == C8) { + m.addTag(QueenSideCastle) + } + } else if p.Type() == Pawn && s2 == pos.enPassantSquare { + m.addTag(EnPassant) + m.addTag(Capture) + } + c1 := p.Color() + c2 := pos.Board().Piece(s2).Color() + if c2 != NoColor && c1 != c2 { + m.addTag(Capture) + } + return m, nil +} + +// AlgebraicNotation (or Standard Algebraic Notation) is the +// official chess notation used by FIDE. Examples: e2, e5, +// O-O (short castling), e8=Q (promotion) +type AlgebraicNotation struct{} + +// String implements the fmt.Stringer interface and returns +// the notation's name. +func (_ AlgebraicNotation) String() string { + return "Algebraic Notation" +} + +// Encode implements the Encoder interface. +func (_ AlgebraicNotation) Encode(pos *Position, m *Move) string { + checkChar := getCheckChar(pos, m) + if m.HasTag(KingSideCastle) { + return "O-O" + checkChar + } else if m.HasTag(QueenSideCastle) { + return "O-O-O" + checkChar + } + p := pos.Board().Piece(m.S1()) + pChar := charFromPieceType(p.Type()) + s1Str := formS1(pos, m) + capChar := "" + if m.HasTag(Capture) || m.HasTag(EnPassant) { + capChar = "x" + if p.Type() == Pawn && s1Str == "" { + capChar = m.s1.File().String() + "x" + } + } + promoText := charForPromo(m.promo) + return pChar + s1Str + capChar + m.s2.String() + promoText + checkChar +} + +// Decode implements the Decoder interface. +func (_ AlgebraicNotation) Decode(pos *Position, s string) (*Move, error) { + s = removeSubstrings(s, "?", "!", "+", "#", "e.p.") + for _, m := range pos.ValidMoves() { + str := AlgebraicNotation{}.Encode(pos, m) + str = removeSubstrings(str, "?", "!", "+", "#", "e.p.") + if str == s { + return m, nil + } + } + return nil, fmt.Errorf("chess: could not decode algebraic notation %s for position %s", s, pos.String()) +} + +func getCheckChar(pos *Position, move *Move) string { + if !move.HasTag(Check) { + return "" + } + nextPos := pos.Update(move) + if nextPos.Status() == Checkmate { + return "#" + } + return "+" +} + +func formS1(pos *Position, m *Move) string { + p := pos.board.Piece(m.s1) + if p.Type() == Pawn { + return "" + } + + var req, fileReq, rankReq bool + moves := pos.ValidMoves() + + for _, mv := range moves { + if mv.s1 != m.s1 && mv.s2 == m.s2 && p == pos.board.Piece(mv.s1) { + req = true + + if mv.s1.File() == m.s1.File() { + rankReq = true; + } + + if mv.s1.Rank() == m.s1.Rank() { + fileReq = true + } + } + } + + var s1 = "" + + if fileReq || !rankReq && req { + s1 = m.s1.File().String() + } + + if rankReq { + s1 += m.s1.Rank().String() + } + + return s1 +} + +func charForPromo(p PieceType) string { + c := charFromPieceType(p) + if c != "" { + c = "=" + c + } + return c +} + +func charFromPieceType(p PieceType) string { + switch p { + case King: + return "K" + case Queen: + return "Q" + case Rook: + return "R" + case Bishop: + return "B" + case Knight: + return "N" + } + return "" +} + +func pieceTypeFromChar(c string) PieceType { + switch c { + case "q": + return Queen + case "r": + return Rook + case "b": + return Bishop + case "n": + return Knight + } + return NoPieceType +} + +func removeSubstrings(s string, subs ...string) string { + for _, sub := range subs { + s = strings.Replace(s, sub, "", -1) + } + return s +} diff --git a/notation_test.go b/notation_test.go new file mode 100644 index 0000000..e65d2e2 --- /dev/null +++ b/notation_test.go @@ -0,0 +1,106 @@ +package chess + +import ( + "encoding/json" + "os" + "strings" + "testing" +) + +type validNotationTest struct { + Pos1 *Position + Pos2 *Position + AlgText string + LongAlgText string + Description string +} + +func TestValidDecoding(t *testing.T) { + f, err := os.Open("assets/valid_notation_tests.json") + if err != nil { + t.Fatal(err) + return + } + + validTests := []validNotationTest{} + if err := json.NewDecoder(f).Decode(&validTests); err != nil { + t.Fatal(err) + return + } + + for _, test := range validTests { + for i, n := range []Notation{AlgebraicNotation{}, LongAlgebraicNotation{}} { + moveText := test.AlgText + if i == 1 { + moveText = test.LongAlgText + } + m, err := n.Decode(test.Pos1, moveText) + if err != nil { + movesStrList := []string{} + for _, m := range test.Pos1.ValidMoves() { + s := n.Encode(test.Pos1, m) + movesStrList = append(movesStrList, s) + } + t.Fatalf("starting from board \n%s\n expected move to be valid error - %s %s\n", test.Pos1.board.Draw(), err, strings.Join(movesStrList, ",")) + } + postPos := test.Pos1.Update(m) + if test.Pos2.String() != postPos.String() { + t.Fatalf("starting from board \n%s\n after move %s\n expected board to be %s\n%s\n but was %s\n%s\n", + test.Pos1.board.Draw(), m.String(), test.Pos2.String(), + test.Pos2.board.Draw(), postPos.String(), postPos.board.Draw()) + } + } + + } +} + +type notationDecodeTest struct { + N Notation + Pos *Position + Text string + PostPos *Position +} + +var ( + invalidDecodeTests = []notationDecodeTest{ + { + // opening for white + N: AlgebraicNotation{}, + Pos: unsafeFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"), + Text: "e5", + }, + { + // http://en.lichess.org/W91M4jms#14 + N: AlgebraicNotation{}, + Pos: unsafeFEN("rn1qkb1r/pp3ppp/2p1pn2/3p4/2PP4/2NQPN2/PP3PPP/R1B1K2R b KQkq - 0 7"), + Text: "Nd7", + }, + { + // http://en.lichess.org/W91M4jms#17 + N: AlgebraicNotation{}, + Pos: unsafeFEN("r2qk2r/pp1n1ppp/2pbpn2/3p4/2PP4/1PNQPN2/P4PPP/R1B1K2R w KQkq - 1 9"), + Text: "O-O-O-O", + PostPos: unsafeFEN("r2qk2r/pp1n1ppp/2pbpn2/3p4/2PP4/1PNQPN2/P4PPP/R1B2RK1 b kq - 0 9"), + }, + { + // http://en.lichess.org/W91M4jms#23 + N: AlgebraicNotation{}, + Pos: unsafeFEN("3r1rk1/pp1nqppp/2pbpn2/3p4/2PP4/1PNQPN2/PB3PPP/3RR1K1 b - - 5 12"), + Text: "dx4", + }, + { + // should not assume pawn for unknown piece type "n" + N: AlgebraicNotation{}, + Pos: unsafeFEN("rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq e6 0 2"), + Text: "nf3", + }, + } +) + +func TestInvalidDecoding(t *testing.T) { + for _, test := range invalidDecodeTests { + if _, err := test.N.Decode(test.Pos, test.Text); err == nil { + t.Fatalf("starting from board\n%s\n expected move notation %s to be invalid", test.Pos.board.Draw(), test.Text) + } + } +} diff --git a/pgn.go b/pgn.go new file mode 100644 index 0000000..52d70cc --- /dev/null +++ b/pgn.go @@ -0,0 +1,167 @@ +package chess + +import ( + "bufio" + "fmt" + "io" + "log" + "regexp" + "strings" +) + +// GamesFromPGN returns all PGN decoding games from the +// reader. It is designed to be used decoding multiple PGNs +// in the same file. An error is returned if there is an +// issue parsing the PGNs. +func GamesFromPGN(r io.Reader) ([]*Game, error) { + games := []*Game{} + current := "" + count := 0 + totalCount := 0 + br := bufio.NewReader(r) + for { + line, err := br.ReadString('\n') + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + if strings.TrimSpace(line) == "" { + count++ + } else { + current += line + } + if count == 2 { + game, err := decodePGN(current) + if err != nil { + return nil, err + } + games = append(games, game) + count = 0 + current = "" + totalCount++ + log.Println("Processed game", totalCount) + } + } + return games, nil +} + +func decodePGN(pgn string) (*Game, error) { + tagPairs := getTagPairs(pgn) + moveStrs, outcome := moveList(pgn) + gameFuncs := []func(*Game){} + for _, tp := range tagPairs { + if strings.ToLower(tp.Key) == "fen" { + fenFunc, err := FEN(tp.Value) + if err != nil { + return nil, fmt.Errorf("chess: pgn decode error %s on tag %s", err.Error(), tp.Key) + } + gameFuncs = append(gameFuncs, fenFunc) + break + } + } + gameFuncs = append(gameFuncs, TagPairs(tagPairs)) + g := NewGame(gameFuncs...) + g.IgnoreAutomaticDraws = true + var notation Notation = AlgebraicNotation{} + if len(moveStrs) > 0 { + _, err := LongAlgebraicNotation{}.Decode(g.Position(), moveStrs[0]) + if err == nil { + notation = LongAlgebraicNotation{} + } + } + for _, alg := range moveStrs { + m, err := notation.Decode(g.Position(), alg) + if err != nil { + return nil, fmt.Errorf("chess: pgn decode error %s on move %d", err.Error(), g.Position().moveCount) + } + if err := g.Move(m); err != nil { + return nil, fmt.Errorf("chess: pgn invalid move error %s on move %d", err.Error(), g.Position().moveCount) + } + } + g.Outcome = outcome + return g, nil +} + +func encodePGN(g *Game) string { + s := "" + for _, tag := range g.TagPairs { + s += fmt.Sprintf("[%s \"%s\"]\n", tag.Key, tag.Value) + } + s += "\n" + for i, move := range g.Moves { + pos := g.Positions[i] + txt := g.Notation.Encode(pos, move) + if i%2 == 0 { + s += fmt.Sprintf("%d.%s", (i/2)+1, txt) + } else { + s += fmt.Sprintf(" %s ", txt) + } + } + s += " " + string(g.Outcome) + return s +} + +var ( + tagPairRegex = regexp.MustCompile(`\[(.*)\s\"(.*)\"\]`) +) + +func getTagPairs(pgn string) []*TagPair { + tagPairs := []*TagPair{} + matches := tagPairRegex.FindAllString(pgn, -1) + for _, m := range matches { + results := tagPairRegex.FindStringSubmatch(m) + if len(results) == 3 { + pair := &TagPair{ + Key: results[1], + Value: results[2], + } + tagPairs = append(tagPairs, pair) + } + } + return tagPairs +} + +var ( + moveNumRegex = regexp.MustCompile(`(?:\d+\.+)?(.*)`) +) + +func moveList(pgn string) ([]string, Outcome) { + // remove comments + text := removeSection("{", "}", pgn) + // remove variations + text = removeSection(`\(`, `\)`, text) + // remove tag pairs + text = removeSection(`\[`, `\]`, text) + // remove line breaks + text = strings.Replace(text, "\n", " ", -1) + + list := strings.Split(text, " ") + filtered := []string{} + var outcome Outcome + for _, move := range list { + move = strings.TrimSpace(move) + switch move { + case string(NoOutcome), string(WhiteWon), string(BlackWon), string(Draw): + outcome = Outcome(move) + case "": + default: + results := moveNumRegex.FindStringSubmatch(move) + if len(results) == 2 && results[1] != "" { + filtered = append(filtered, results[1]) + } + } + } + return filtered, outcome +} + +func removeSection(leftChar, rightChar, s string) string { + r := regexp.MustCompile(leftChar + ".*?" + rightChar) + for { + i := r.FindStringIndex(s) + if i == nil { + return s + } + s = s[0:i[0]] + s[i[1]:len(s)] + } +} diff --git a/pgn_test.go b/pgn_test.go new file mode 100644 index 0000000..5ef010e --- /dev/null +++ b/pgn_test.go @@ -0,0 +1,114 @@ +package chess + +import ( + "strings" + "testing" +) + +type pgnTest struct { + PostPos *Position + PGN string +} + +var ( + validPGNs = []pgnTest{ + { + PostPos: unsafeFEN("4r3/6P1/2p2P1k/1p6/pP2p1R1/P1B5/2P2K2/3r4 b - - 0 45"), + PGN: `[Event "?"] + [Site "?"] + [Date "1997.05.03"] + [Round "1"] + [White "Kasparov"] + [Black "Deep-Blue"] + [Result "1-0"] + [WhiteElo "2795"] + + 1. Nf3 d5 2. g3 Bg4 3. b3 Nd7 4. Bb2 e6 5. Bg2 Ngf6 6. O-O c6 + 7. d3 Bd6 8. Nbd2 O-O 9. h3 Bh5 10. e3 h6 11. Qe1 Qa5 12. a3 + Bc7 13. Nh4 g5 14. Nhf3 e5 15. e4 Rfe8 16. Nh2 Qb6 17. Qc1 a5 + 18. Re1 Bd6 19. Ndf1 dxe4 20. dxe4 Bc5 21. Ne3 Rad8 22. Nhf1 g4 + 23. hxg4 Nxg4 24. f3 Nxe3 25. Nxe3 Be7 26. Kh1 Bg5 27. Re2 a4 + 28. b4 f5 29. exf5 e4 30. f4 Bxe2 31. fxg5 Ne5 32. g6 Bf3 33. Bc3 + Qb5 34. Qf1 Qxf1+ 35. Rxf1 h5 36. Kg1 Kf8 37. Bh3 b5 38. Kf2 Kg7 + 39. g4 Kh6 40. Rg1 hxg4 41. Bxg4 Bxg4 42. Nxg4+ Nxg4+ 43. Rxg4 + Rd5 44. f6 Rd1 45. g7 1-0`, + }, + { + PostPos: unsafeFEN("4r3/6P1/2p2P1k/1p6/pP2p1R1/P1B5/2P2K2/3r4 b - - 0 45"), + PGN: `[Event "?"] +[Site "http://lichess.org/4HXJOtpN"] +[Date "1997.05.03"] +[White "Kasparov (2795)"] +[Black "Deep-Blue"] +[Result "1-0"] +[WhiteElo "?"] +[BlackElo "?"] +[PlyCount "89"] +[Variant "Standard"] +[TimeControl "-"] +[ECO "A07"] +[Opening "King's Indian Attack, General"] +[Termination "Normal"] +[Annotator "lichess.org"] + +1. Nf3 d5 2. g3 { King's Indian Attack, General } Bg4 3. b3 Nd7 4. Bb2 e6 5. Bg2 Ngf6 6. O-O c6 7. d3 Bd6 8. Nbd2 O-O 9. h3 Bh5 10. e3 h6 11. Qe1 Qa5 12. a3 Bc7 13. Nh4 g5 14. Nhf3 e5 15. e4 Rfe8 16. Nh2 Qb6 17. Qc1 a5 18. Re1 Bd6 19. Ndf1 dxe4 20. dxe4 Bc5 21. Ne3 Rad8 22. Nhf1 g4 23. hxg4 Nxg4 24. f3 Nxe3 25. Nxe3 Be7 26. Kh1 Bg5 27. Re2 a4 28. b4 f5 29. exf5 e4 30. f4 Bxe2 31. fxg5 Ne5 32. g6 Bf3 33. Bc3 Qb5 34. Qf1 Qxf1+ 35. Rxf1 h5 36. Kg1 Kf8 37. Bh3 b5 38. Kf2 Kg7?! { (0.70 → 1.52) Inaccuracy. The best move was Rd6. } (38... Rd6 39. Re1 Re7 40. Rg1 Re8 41. g4 h4 42. g5 Kg8 43. Rf1 Kf8 44. Re1 Kg8 45. Rb1 Rdd8 46. Rf1 Kf8 47. Rg1 Kg8 48. Rb1) 39. g4 Kh6?! { (1.42 → 2.07) Inaccuracy. The best move was h4. } (39... h4 40. g5 Kf8 41. Bg2 Ng4+ 42. Nxg4 Bxg4 43. Ke3 h3 44. Rf4 hxg2 45. Rxg4 Rd1 46. Rxg2 Rf1 47. Rf2 Rg1 48. Bf6 Rh1 49. Kd4) 40. Rg1 hxg4 41. Bxg4 Bxg4 42. Nxg4+ Nxg4+ 43. Rxg4 Rd5 44. f6 Rd1?? { (1.60 → 8.36) Blunder. The best move was Rf5+. } (44... Rf5+ 45. Ke2 Rg8 46. g7 e3 47. Bd4 Kh7 48. Kxe3 Re8+ 49. Re4 Rxe4+ 50. Kxe4 Rf1 51. Ke5 Kg8 52. Ke6 Rf4 53. c3 Rf1 54. Ke7) 45. g7 { Black resigns } 1-0`, + }, + { + PostPos: unsafeFEN("2r2rk1/pp1bBpp1/2np4/2pp2p1/1bP5/1P4P1/P1QPPPBP/3R1RK1 b - - 0 3"), + PGN: `[Event "?"] + [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`, + }, + } +) + +func TestValidPGNs(t *testing.T) { + for _, test := range validPGNs { + game, err := decodePGN(test.PGN) + if err != nil { + t.Fatalf("recieved unexpected pgn error %s", err.Error()) + } + if game.Position().String() != test.PostPos.String() { + t.Fatalf("expected board to be \n%s\nFEN:%s\n but got \n%s\n\nFEN:%s\n", + test.PostPos.board.Draw(), test.PostPos.String(), + game.Position().board.Draw(), game.Position().String()) + } + } +} + +func BenchmarkPGN(b *testing.B) { + pgn := `[Event "?"] + [Site "?"] + [Date "1997.05.03"] + [Round "1"] + [White "Kasparov"] + [Black "Deep-Blue"] + [Result "1-0"] + [WhiteElo "2795"] + + 1. Nf3 d5 2. g3 Bg4 3. b3 Nd7 4. Bb2 e6 5. Bg2 Ngf6 6. O-O c6 + 7. d3 Bd6 8. Nbd2 O-O 9. h3 Bh5 10. e3 h6 11. Qe1 Qa5 12. a3 + Bc7 13. Nh4 g5 14. Nhf3 e5 15. e4 Rfe8 16. Nh2 Qb6 17. Qc1 a5 + 18. Re1 Bd6 19. Ndf1 dxe4 20. dxe4 Bc5 21. Ne3 Rad8 22. Nhf1 g4 + 23. hxg4 Nxg4 24. f3 Nxe3 25. Nxe3 Be7 26. Kh1 Bg5 27. Re2 a4 + 28. b4 f5 29. exf5 e4 30. f4 Bxe2 31. fxg5 Ne5 32. g6 Bf3 33. Bc3 + Qb5 34. Qf1 Qxf1+ 35. Rxf1 h5 36. Kg1 Kf8 37. Bh3 b5 38. Kf2 Kg7 + 39. g4 Kh6 40. Rg1 hxg4 41. Bxg4 Bxg4 42. Nxg4+ Nxg4+ 43. Rxg4 + Rd5 44. f6 Rd1 45. g7 1-0` + + for n := 0; n < b.N; n++ { + opt, _ := PGN(strings.NewReader(pgn)) + NewGame(opt) + } +} diff --git a/piece.go b/piece.go new file mode 100644 index 0000000..50875c5 --- /dev/null +++ b/piece.go @@ -0,0 +1,192 @@ +package chess + +// Color represents the color of a chess piece. +type Color int8 + +const ( + // NoColor represents no color + NoColor Color = iota + // White represents the color white + White + // Black represents the color black + Black +) + +// Other returns the opposie color of the receiver. +func (c Color) Other() Color { + switch c { + case White: + return Black + case Black: + return White + } + return NoColor +} + +// String implements the fmt.Stringer interface and returns +// the color's FEN compatible notation. +func (c Color) String() string { + switch c { + case White: + return "w" + case Black: + return "b" + } + return "-" +} + +// Name returns a display friendly name. +func (c Color) Name() string { + switch c { + case White: + return "White" + case Black: + return "Black" + } + return "No Color" +} + +// PieceType is the type of a piece. +type PieceType int8 + +const ( + // NoPieceType represents a lack of piece type + NoPieceType PieceType = iota + // King represents a king + King + // Queen represents a queen + Queen + // Rook represents a rook + Rook + // Bishop represents a bishop + Bishop + // Knight represents a knight + Knight + // Pawn represents a pawn + Pawn +) + +// PieceTypes returns a slice of all piece types. +func PieceTypes() [6]PieceType { + return [6]PieceType{King, Queen, Rook, Bishop, Knight, Pawn} +} + +func (p PieceType) String() string { + switch p { + case King: + return "k" + case Queen: + return "q" + case Rook: + return "r" + case Bishop: + return "b" + case Knight: + return "n" + } + return "" +} + +func (p PieceType) promotableTo() bool { + switch p { + case Queen, Rook, Bishop, Knight: + return true + } + return false +} + +// Piece is a piece type with a color. +type Piece int8 + +const ( + // NoPiece represents no piece + NoPiece Piece = iota + // WhiteKing is a white king + WhiteKing + // WhiteQueen is a white queen + WhiteQueen + // WhiteRook is a white rook + WhiteRook + // WhiteBishop is a white bishop + WhiteBishop + // WhiteKnight is a white knight + WhiteKnight + // WhitePawn is a white pawn + WhitePawn + // BlackKing is a black king + BlackKing + // BlackQueen is a black queen + BlackQueen + // BlackRook is a black rook + BlackRook + // BlackBishop is a black bishop + BlackBishop + // BlackKnight is a black knight + BlackKnight + // BlackPawn is a black pawn + BlackPawn +) + +var ( + allPieces = []Piece{ + WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight, WhitePawn, + BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn, + } +) + +func getPiece(t PieceType, c Color) Piece { + for _, p := range allPieces { + if p.Color() == c && p.Type() == t { + return p + } + } + return NoPiece +} + +// Type returns the type of the piece. +func (p Piece) Type() PieceType { + switch p { + case WhiteKing, BlackKing: + return King + case WhiteQueen, BlackQueen: + return Queen + case WhiteRook, BlackRook: + return Rook + case WhiteBishop, BlackBishop: + return Bishop + case WhiteKnight, BlackKnight: + return Knight + case WhitePawn, BlackPawn: + return Pawn + } + return NoPieceType +} + +// Color returns the color of the piece. +func (p Piece) Color() Color { + switch p { + case WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight, WhitePawn: + return White + case BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn: + return Black + } + return NoColor +} + +// String implements the fmt.Stringer interface +func (p Piece) String() string { + return pieceUnicodes[int(p)] +} + +var ( + pieceUnicodes = []string{" ", "♔", "♕", "♖", "♗", "♘", "♙", "♚", "♛", "♜", "♝", "♞", "♟"} +) + +func (p Piece) getFENChar() string { + for key, piece := range fenPieceMap { + if piece == p { + return key + } + } + return "" +} diff --git a/position.go b/position.go new file mode 100644 index 0000000..dc6bcfb --- /dev/null +++ b/position.go @@ -0,0 +1,221 @@ +package chess + +import ( + "crypto/md5" + "fmt" + "strconv" + "strings" +) + +// Side represents a side of the board. +type Side int + +const ( + // KingSide is the right side of the board from white's perspective. + KingSide Side = iota + 1 + // QueenSide is the left side of the board from white's perspective. + QueenSide +) + +// CastleRights holds the state of both sides castling abilities. +type CastleRights string + +// CanCastle returns true if the given color and side combination +// can castle, otherwise returns false. +func (cr CastleRights) CanCastle(c Color, side Side) bool { + char := "k" + if side == QueenSide { + char = "q" + } + if c == White { + char = strings.ToUpper(char) + } + return strings.Contains(string(cr), char) +} + +// String implements the fmt.Stringer interface and returns +// a FEN compatible string. Ex. KQq +func (cr CastleRights) String() string { + return string(cr) +} + +// Position represents the state of the game without reguard +// to its outcome. Position is translatable to FEN notation. +type Position struct { + board *Board + turn Color + castleRights CastleRights + enPassantSquare Square + halfMoveClock int + moveCount int + inCheck bool + validMoves []*Move +} + +// Update returns a new position resulting from the given move. +// The move itself isn't validated, if validation is needed use +// Game's Move method. This method is more performant for bots that +// rely on the ValidMoves because it skips redundant validation. +func (pos *Position) Update(m *Move) *Position { + moveCount := pos.moveCount + if pos.turn == Black { + moveCount++ + } + cr := pos.CastleRights() + ncr := pos.updateCastleRights(m) + p := pos.board.Piece(m.s1) + halfMove := pos.halfMoveClock + if p.Type() == Pawn || m.HasTag(Capture) || cr != ncr { + halfMove = 0 + } else { + halfMove++ + } + b := pos.board.copy() + b.update(m) + return &Position{ + board: b, + turn: pos.turn.Other(), + castleRights: ncr, + enPassantSquare: pos.updateEnPassantSquare(m), + halfMoveClock: halfMove, + moveCount: moveCount, + inCheck: m.HasTag(Check), + } +} + +// ValidMoves returns a list of valid moves for the position. +func (pos *Position) ValidMoves() []*Move { + if pos.validMoves != nil { + return append([]*Move(nil), pos.validMoves...) + } + pos.validMoves = engine{}.CalcMoves(pos, false) + return append([]*Move(nil), pos.validMoves...) +} + +// Status returns the position's status as one of the outcome methods. +// Possible returns values include Checkmate, Stalemate, and NoMethod. +func (pos *Position) Status() Method { + return engine{}.Status(pos) +} + +// Board returns the position's board. +func (pos *Position) Board() *Board { + return pos.board +} + +// Turn returns the color to move next. +func (pos *Position) Turn() Color { + return pos.turn +} + +// CastleRights returns the castling rights of the position. +func (pos *Position) CastleRights() CastleRights { + return pos.castleRights +} + +// String implements the fmt.Stringer interface and returns a +// string with the FEN format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 +func (pos *Position) String() string { + b := pos.board.String() + t := pos.turn.String() + c := pos.castleRights.String() + sq := "-" + if pos.enPassantSquare != NoSquare { + sq = pos.enPassantSquare.String() + } + return fmt.Sprintf("%s %s %s %s %d %d", b, t, c, sq, pos.halfMoveClock, pos.moveCount) +} + +// Hash returns a unique hash of the position +func (pos *Position) Hash() [16]byte { + sq := "-" + if pos.enPassantSquare != NoSquare { + sq = pos.enPassantSquare.String() + } + s := pos.turn.String() + ":" + pos.castleRights.String() + ":" + sq + for _, p := range allPieces { + bb := pos.board.bbForPiece(p) + s += ":" + strconv.FormatUint(uint64(bb), 16) + } + return md5.Sum([]byte(s)) +} + +// MarshalText implements the encoding.TextMarshaler interface and +// encodes the position's FEN. +func (pos *Position) MarshalText() (text []byte, err error) { + return []byte(pos.String()), nil +} + +// UnmarshalText implements the encoding.TextUnarshaler interface and +// assumes the data is in the FEN format. +func (pos *Position) UnmarshalText(text []byte) error { + cp, err := decodeFEN(string(text)) + if err != nil { + return err + } + pos.board = cp.board + pos.turn = cp.turn + pos.castleRights = cp.castleRights + pos.enPassantSquare = cp.enPassantSquare + pos.halfMoveClock = cp.halfMoveClock + pos.moveCount = cp.moveCount + pos.inCheck = isInCheck(cp) + return nil +} + +func (pos *Position) copy() *Position { + return &Position{ + board: pos.board.copy(), + turn: pos.turn, + castleRights: pos.castleRights, + enPassantSquare: pos.enPassantSquare, + halfMoveClock: pos.halfMoveClock, + moveCount: pos.moveCount, + inCheck: pos.inCheck, + } +} + +func (pos *Position) updateCastleRights(m *Move) CastleRights { + cr := string(pos.castleRights) + p := pos.board.Piece(m.s1) + if p == WhiteKing || m.s1 == H1 || m.s2 == H1 { + cr = strings.Replace(cr, "K", "", -1) + } + if p == WhiteKing || m.s1 == A1 || m.s2 == A1 { + cr = strings.Replace(cr, "Q", "", -1) + } + if p == BlackKing || m.s1 == H8 || m.s2 == H8 { + cr = strings.Replace(cr, "k", "", -1) + } + if p == BlackKing || m.s1 == A8 || m.s2 == A8 { + cr = strings.Replace(cr, "q", "", -1) + } + if cr == "" { + cr = "-" + } + return CastleRights(cr) +} + +func (pos *Position) updateEnPassantSquare(m *Move) Square { + p := pos.board.Piece(m.s1) + if p.Type() != Pawn { + return NoSquare + } + if pos.turn == White && + (bbForSquare(m.s1)&bbRank2) != 0 && + (bbForSquare(m.s2)&bbRank4) != 0 { + return Square(m.s2 - 8) + } else if pos.turn == Black && + (bbForSquare(m.s1)&bbRank7) != 0 && + (bbForSquare(m.s2)&bbRank5) != 0 { + return Square(m.s2 + 8) + } + return NoSquare +} + +func (pos *Position) samePosition(pos2 *Position) bool { + return pos.board.String() == pos2.board.String() && + pos.turn == pos2.turn && + pos.castleRights.String() == pos2.castleRights.String() && + pos.enPassantSquare == pos2.enPassantSquare +} diff --git a/square.go b/square.go new file mode 100644 index 0000000..d4fc31f --- /dev/null +++ b/square.go @@ -0,0 +1,156 @@ +package chess + +const ( + numOfSquaresInBoard = 64 + numOfSquaresInRow = 8 +) + +// A Square is one of the 64 rank and file combinations that make up a chess board. +type Square int8 + +// File returns the square's file. +func (sq Square) File() File { + return File(int(sq) % numOfSquaresInRow) +} + +// Rank returns the square's rank. +func (sq Square) Rank() Rank { + return Rank(int(sq) / numOfSquaresInRow) +} + +func (sq Square) String() string { + return sq.File().String() + sq.Rank().String() +} + +func (sq Square) color() Color { + if ((sq / 8) % 2) == (sq % 2) { + return Black + } + return White +} + +func getSquare(f File, r Rank) Square { + return Square((int(r) * 8) + int(f)) +} + +const ( + NoSquare Square = iota - 1 + A1 + B1 + C1 + D1 + E1 + F1 + G1 + H1 + A2 + B2 + C2 + D2 + E2 + F2 + G2 + H2 + A3 + B3 + C3 + D3 + E3 + F3 + G3 + H3 + A4 + B4 + C4 + D4 + E4 + F4 + G4 + H4 + A5 + B5 + C5 + D5 + E5 + F5 + G5 + H5 + A6 + B6 + C6 + D6 + E6 + F6 + G6 + H6 + A7 + B7 + C7 + D7 + E7 + F7 + G7 + H7 + A8 + B8 + C8 + D8 + E8 + F8 + G8 + H8 +) + +const ( + fileChars = "abcdefgh" + rankChars = "12345678" +) + +// A Rank is the rank of a square. +type Rank int8 + +const ( + Rank1 Rank = iota + Rank2 + Rank3 + Rank4 + Rank5 + Rank6 + Rank7 + Rank8 +) + +func (r Rank) String() string { + return rankChars[r : r+1] +} + +// A File is the file of a square. +type File int8 + +const ( + FileA File = iota + FileB + FileC + FileD + FileE + FileF + FileG + FileH +) + +func (f File) String() string { + return fileChars[f : f+1] +} + +var ( + strToSquareMap = map[string]Square{ + "a1": A1, "a2": A2, "a3": A3, "a4": A4, "a5": A5, "a6": A6, "a7": A7, "a8": A8, + "b1": B1, "b2": B2, "b3": B3, "b4": B4, "b5": B5, "b6": B6, "b7": B7, "b8": B8, + "c1": C1, "c2": C2, "c3": C3, "c4": C4, "c5": C5, "c6": C6, "c7": C7, "c8": C8, + "d1": D1, "d2": D2, "d3": D3, "d4": D4, "d5": D5, "d6": D6, "d7": D7, "d8": D8, + "e1": E1, "e2": E2, "e3": E3, "e4": E4, "e5": E5, "e6": E6, "e7": E7, "e8": E8, + "f1": F1, "f2": F2, "f3": F3, "f4": F4, "f5": F5, "f6": F6, "f7": F7, "f8": F8, + "g1": G1, "g2": G2, "g3": G3, "g4": G4, "g5": G5, "g6": G6, "g7": G7, "g8": G8, + "h1": H1, "h2": H2, "h3": H3, "h4": H4, "h5": H5, "h6": H6, "h7": H7, "h8": H8, + } +) diff --git a/stringer.go b/stringer.go new file mode 100644 index 0000000..83e754f --- /dev/null +++ b/stringer.go @@ -0,0 +1,16 @@ +// generated by stringer -type=Method -output=stringer.go; DO NOT EDIT + +package chess + +import "fmt" + +const _Method_name = "NoMethodCheckmateResignationDrawOfferStalemateThreefoldRepetitionFivefoldRepetitionFiftyMoveRuleSeventyFiveMoveRuleInsufficientMaterial" + +var _Method_index = [...]uint8{0, 8, 17, 28, 37, 46, 65, 83, 96, 115, 135} + +func (i Method) String() string { + if i >= Method(len(_Method_index)-1) { + return fmt.Sprintf("Method(%d)", i) + } + return _Method_name[_Method_index[i]:_Method_index[i+1]] +} diff --git a/vendor/github.com/ajstarks/svgo/LICENSE b/vendor/github.com/ajstarks/svgo/LICENSE new file mode 100644 index 0000000..306663c --- /dev/null +++ b/vendor/github.com/ajstarks/svgo/LICENSE @@ -0,0 +1,220 @@ +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree to +be bound by the terms and conditions of this Creative Commons Attribution +4.0 International Public License ("Public License"). To the extent this +Public License may be interpreted as a contract, You are granted the +Licensed Rights in consideration of Your acceptance of these terms and +conditions, and the Licensor grants You such rights in consideration +of benefits the Licensor receives from making the Licensed Material +available under these terms and conditions. + +Section 1 – Definitions. + +Adapted Material means material subject to Copyright and Similar Rights +that is derived from or based upon the Licensed Material and in which +the Licensed Material is translated, altered, arranged, transformed, or +otherwise modified in a manner requiring permission under the Copyright +and Similar Rights held by the Licensor. For purposes of this Public +License, where the Licensed Material is a musical work, performance, +or sound recording, Adapted Material is always produced where the +Licensed Material is synched in timed relation with a moving image. +Adapter's License means the license You apply to Your Copyright and +Similar Rights in Your contributions to Adapted Material in accordance +with the terms and conditions of this Public License. Copyright and +Similar Rights means copyright and/or similar rights closely related to +copyright including, without limitation, performance, broadcast, sound +recording, and Sui Generis Database Rights, without regard to how the +rights are labeled or categorized. For purposes of this Public License, +the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar +Rights. Effective Technological Measures means those measures that, +in the absence of proper authority, may not be circumvented under laws +fulfilling obligations under Article 11 of the WIPO Copyright Treaty +adopted on December 20, 1996, and/or similar international agreements. +Exceptions and Limitations means fair use, fair dealing, and/or any other +exception or limitation to Copyright and Similar Rights that applies to +Your use of the Licensed Material. Licensed Material means the artistic +or literary work, database, or other material to which the Licensor +applied this Public License. Licensed Rights means the rights granted +to You subject to the terms and conditions of this Public License, which +are limited to all Copyright and Similar Rights that apply to Your use +of the Licensed Material and that the Licensor has authority to license. +Licensor means the individual(s) or entity(ies) granting rights under +this Public License. Share means to provide material to the public by +any means or process that requires permission under the Licensed Rights, +such as reproduction, public display, public performance, distribution, +dissemination, communication, or importation, and to make material +available to the public including in ways that members of the public +may access the material from a place and at a time individually chosen +by them. Sui Generis Database Rights means rights other than copyright +resulting from Directive 96/9/EC of the European Parliament and of the +Council of 11 March 1996 on the legal protection of databases, as amended +and/or succeeded, as well as other essentially equivalent rights anywhere +in the world. You means the individual or entity exercising the Licensed +Rights under this Public License. Your has a corresponding meaning. +Section 2 – Scope. + +License grant. Subject to the terms and conditions of this Public +License, the Licensor hereby grants You a worldwide, royalty-free, +non-sublicensable, non-exclusive, irrevocable license to exercise the +Licensed Rights in the Licensed Material to: reproduce and Share the +Licensed Material, in whole or in part; and produce, reproduce, and +Share Adapted Material. Exceptions and Limitations. For the avoidance +of doubt, where Exceptions and Limitations apply to Your use, this +Public License does not apply, and You do not need to comply with +its terms and conditions. Term. The term of this Public License is +specified in Section 6(a). Media and formats; technical modifications +allowed. The Licensor authorizes You to exercise the Licensed Rights in +all media and formats whether now known or hereafter created, and to make +technical modifications necessary to do so. The Licensor waives and/or +agrees not to assert any right or authority to forbid You from making +technical modifications necessary to exercise the Licensed Rights, +including technical modifications necessary to circumvent Effective +Technological Measures. For purposes of this Public License, simply making +modifications authorized by this Section 2(a)(4) never produces Adapted +Material. Downstream recipients. Offer from the Licensor – Licensed +Material. Every recipient of the Licensed Material automatically receives +an offer from the Licensor to exercise the Licensed Rights under the terms +and conditions of this Public License. No downstream restrictions. You +may not offer or impose any additional or different terms or conditions +on, or apply any Effective Technological Measures to, the Licensed +Material if doing so restricts exercise of the Licensed Rights by any +recipient of the Licensed Material. No endorsement. Nothing in this +Public License constitutes or may be construed as permission to assert +or imply that You are, or that Your use of the Licensed Material is, +connected with, or sponsored, endorsed, or granted official status by, +the Licensor or others designated to receive attribution as provided in +Section 3(a)(1)(A)(i). Other rights. + +Moral rights, such as the right of integrity, are not licensed under +this Public License, nor are publicity, privacy, and/or other similar +personality rights; however, to the extent possible, the Licensor waives +and/or agrees not to assert any such rights held by the Licensor to the +limited extent necessary to allow You to exercise the Licensed Rights, but +not otherwise. Patent and trademark rights are not licensed under this +Public License. To the extent possible, the Licensor waives any right +to collect royalties from You for the exercise of the Licensed Rights, +whether directly or through a collecting society under any voluntary or +waivable statutory or compulsory licensing scheme. In all other cases +the Licensor expressly reserves any right to collect such royalties. +Section 3 – License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + +Attribution. + +If You Share the Licensed Material (including in modified form), You must: + +retain the following if it is supplied by the Licensor with the Licensed +Material: identification of the creator(s) of the Licensed Material and +any others designated to receive attribution, in any reasonable manner +requested by the Licensor (including by pseudonym if designated); a +copyright notice; a notice that refers to this Public License; a notice +that refers to the disclaimer of warranties; a URI or hyperlink to the +Licensed Material to the extent reasonably practicable; indicate if You +modified the Licensed Material and retain an indication of any previous +modifications; and indicate the Licensed Material is licensed under this +Public License, and include the text of, or the URI or hyperlink to, +this Public License. You may satisfy the conditions in Section 3(a)(1) +in any reasonable manner based on the medium, means, and context in which +You Share the Licensed Material. For example, it may be reasonable to +satisfy the conditions by providing a URI or hyperlink to a resource +that includes the required information. If requested by the Licensor, +You must remove any of the information required by Section 3(a)(1)(A) +to the extent reasonably practicable. If You Share Adapted Material You +produce, the Adapter's License You apply must not prevent recipients of +the Adapted Material from complying with this Public License. Section 4 +– Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that apply +to Your use of the Licensed Material: + +for the avoidance of doubt, Section 2(a)(1) grants You the right to +extract, reuse, reproduce, and Share all or a substantial portion of the +contents of the database; if You include all or a substantial portion of +the database contents in a database in which You have Sui Generis Database +Rights, then the database in which You have Sui Generis Database Rights +(but not its individual contents) is Adapted Material; and You must comply +with the conditions in Section 3(a) if You Share all or a substantial +portion of the contents of the database. For the avoidance of doubt, +this Section 4 supplements and does not replace Your obligations under +this Public License where the Licensed Rights include other Copyright and +Similar Rights. Section 5 – Disclaimer of Warranties and Limitation +of Liability. + +Unless otherwise separately undertaken by the Licensor, to the +extent possible, the Licensor offers the Licensed Material as-is and +as-available, and makes no representations or warranties of any kind +concerning the Licensed Material, whether express, implied, statutory, +or other. This includes, without limitation, warranties of title, +merchantability, fitness for a particular purpose, non-infringement, +absence of latent or other defects, accuracy, or the presence or absence +of errors, whether or not known or discoverable. Where disclaimers of +warranties are not allowed in full or in part, this disclaimer may not +apply to You. To the extent possible, in no event will the Licensor +be liable to You on any legal theory (including, without limitation, +negligence) or otherwise for any direct, special, indirect, incidental, +consequential, punitive, exemplary, or other losses, costs, expenses, +or damages arising out of this Public License or use of the Licensed +Material, even if the Licensor has been advised of the possibility of +such losses, costs, expenses, or damages. Where a limitation of liability +is not allowed in full or in part, this limitation may not apply to You. +The disclaimer of warranties and limitation of liability provided above +shall be interpreted in a manner that, to the extent possible, most +closely approximates an absolute disclaimer and waiver of all liability. +Section 6 – Term and Termination. + +This Public License applies for the term of the Copyright and Similar +Rights licensed here. However, if You fail to comply with this +Public License, then Your rights under this Public License terminate +automatically. Where Your right to use the Licensed Material has +terminated under Section 6(a), it reinstates: + +automatically as of the date the violation is cured, provided it is +cured within 30 days of Your discovery of the violation; or upon express +reinstatement by the Licensor. For the avoidance of doubt, this Section +6(b) does not affect any right the Licensor may have to seek remedies +for Your violations of this Public License. For the avoidance of doubt, +the Licensor may also offer the Licensed Material under separate terms +or conditions or stop distributing the Licensed Material at any time; +however, doing so will not terminate this Public License. Sections 1, +5, 6, 7, and 8 survive termination of this Public License. Section 7 +– Other Terms and Conditions. + +The Licensor shall not be bound by any additional or different terms or +conditions communicated by You unless expressly agreed. Any arrangements, +understandings, or agreements regarding the Licensed Material not stated +herein are separate from and independent of the terms and conditions of +this Public License. Section 8 – Interpretation. + +For the avoidance of doubt, this Public License does not, and shall not be +interpreted to, reduce, limit, restrict, or impose conditions on any use +of the Licensed Material that could lawfully be made without permission +under this Public License. To the extent possible, if any provision of +this Public License is deemed unenforceable, it shall be automatically +reformed to the minimum extent necessary to make it enforceable. If +the provision cannot be reformed, it shall be severed from this Public +License without affecting the enforceability of the remaining terms +and conditions. No term or condition of this Public License will be +waived and no failure to comply consented to unless expressly agreed +to by the Licensor. Nothing in this Public License constitutes or may +be interpreted as a limitation upon, or waiver of, any privileges and +immunities that apply to the Licensor or You, including from the legal +processes of any jurisdiction or authority. Creative Commons is not +a party to its public licenses. Notwithstanding, Creative Commons may +elect to apply one of its public licenses to material it publishes and +in those instances will be considered the “Licensor.” The text of +the Creative Commons public licenses is dedicated to the public domain +under the CC0 Public Domain Dedication. Except for the limited purpose of +indicating that material is shared under a Creative Commons public license +or as otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark “Creative Commons” or any other trademark or +logo of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, understandings, +or agreements concerning use of licensed material. For the avoidance of +doubt, this paragraph does not form part of the public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/vendor/github.com/ajstarks/svgo/LICENSE-link.txt b/vendor/github.com/ajstarks/svgo/LICENSE-link.txt new file mode 100644 index 0000000..e7810f1 --- /dev/null +++ b/vendor/github.com/ajstarks/svgo/LICENSE-link.txt @@ -0,0 +1,3 @@ +The contents of this repository are Licensed under +the Creative Commons Attribution 4.0 International license as described in +https://creativecommons.org/licenses/by/4.0/legalcode diff --git a/vendor/github.com/ajstarks/svgo/README.markdown b/vendor/github.com/ajstarks/svgo/README.markdown new file mode 100644 index 0000000..cc3388a --- /dev/null +++ b/vendor/github.com/ajstarks/svgo/README.markdown @@ -0,0 +1,727 @@ +# SVGo: A Go library for SVG generation # + +The library generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (). +Output goes to the specified io.Writer. + +## Supported SVG elements and functions ## + +### Shapes, lines, text + + circle, ellipse, polygon, polyline, rect (including roundrects), line, text + +### Paths + + general, arc, cubic and quadratic bezier paths, + +### Image and Gradients + + image, linearGradient, radialGradient, + +### Transforms ### + + translate, rotate, scale, skewX, skewY + + ### Animation ### + animate, animateMotion, animateTranslate, animateRotate, animateScale, animateSkewX, animateSkewY + +### Filter Effects + + filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting, + feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight, + feSpecularLighting, feSpotLight,feTile, feTurbulence + + +### Metadata elements ### + + desc, defs, g (style, transform, id), marker, mask, pattern, title, (a)ddress, link, script, use + +## Building and Usage ## + +See svgdef.[svg|png|pdf] for a graphical view of the function calls + + +Usage: (assuming GOPATH is set) + + go get github.com/ajstarks/svgo + go install github.com/ajstarks/svgo/... + + +You can use godoc to browse the documentation from the command line: + + $ go doc github.com/ajstarks/svgo + + +a minimal program, to generate SVG to standard output. + + package main + + import ( + "github.com/ajstarks/svgo" + "os" + ) + + func main() { + width := 500 + height := 500 + canvas := svg.New(os.Stdout) + canvas.Start(width, height) + canvas.Circle(width/2, height/2, 100) + canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white") + canvas.End() + } + +Drawing in a web server: (http://localhost:2003/circle) + + package main + + import ( + "log" + "github.com/ajstarks/svgo" + "net/http" + ) + + func main() { + http.Handle("/circle", http.HandlerFunc(circle)) + err := http.ListenAndServe(":2003", nil) + if err != nil { + log.Fatal("ListenAndServe:", err) + } + } + + func circle(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "image/svg+xml") + s := svg.New(w) + s.Start(500, 500) + s.Circle(250, 250, 125, "fill:none;stroke:black") + s.End() + } + +You may view the SVG output with a browser that supports SVG (tested on Chrome, Opera, Firefox and Safari), or any other SVG user-agent such as Batik Squiggle. + +### Graphics Sketching with SVGo and svgplay ### + +Combined with the svgplay command, SVGo can be used to "sketch" with code in a browser. + +To use svgplay and SVGo, first go to a directory with your code, and run: + + $ svgplay + 2014/06/25 22:05:28 ☠ ☠ ☠ Warning: this server allows a client connecting to 127.0.0.1:1999 to execute code on this computer ☠ ☠ ☠ + +Next open your browser to the svgplay server you just started. +svgplay only listens on localhost, and uses port 1999 (guess which year SVG was first introduced) by default + + http://localhost:1999/ + +Enter your code in the textarea, and when you are ready to run press Shift--Enter. The code will be compiled, with the results +on the right. To update, change the code and repeat. Note that compilation errors are shown in red under the code. In order for svgplay/SVGo to work, make sure that the io.Writer specified with the New function is os.Stdout. + + +If you want to sketch with an existing file, enter its URL: + + http://localhost:1999/foo.go + +![SVGplay](https://farm4.staticflickr.com/3859/14322978157_31c0114850.jpg) + + +### SVGo Papers and presentations ### + +* SVGo paper from SVGOpen 2011 + +* Programming Pictures with SVGo + +* SVGo Workshop + + +### Tutorial Video ### + +A video describing how to use the package can be seen on YouTube at + +## Package contents ## + +* svg.go: Library +* newsvg: Coding template command +* svgdef: Creates a SVG representation of the API +* animate: Animation demo +* am: Animate motion demo +* amt: Animate transformation demo +* android: The Android logo +* bubtrail: Bubble trails +* bulletgraph: Bullet Graphs (via Stephen Few) +* colortab: Display SVG named colors with RGB values +* compx: Component diagrams +* flower: Random "flowers" +* fontcompare: Compare two fonts +* f50: Get 50 photos from Flickr based on a query +* fe: Filter effects +* funnel: Funnel from transparent circles +* gradient: Linear and radial gradients +* html5logo: HTML5 logo with draggable elements +* imfade: Show image fading +* lewitt: Version of Sol Lewitt's Wall Drawing 91 +* ltr: Layer Tennis Remixes +* marker: Test markers +* paths: Demonstrate SVG paths +* pattern: Test patterns +* planets: Show the scale of the Solar system +* pmap: Proportion maps +* randcomp: Compare random number generators +* richter: Gerhard Richter's 256 colors +* rl: Random lines (port of a Processing demo) +* skewabc: Skew ABC +* stockproduct: Visualize product and stock prices +* svgopher: SVGo Mascot +* svgplay: SVGo sketching server +* svgplot: Plot data +* svgrid: Compose SVG files in a grid +* tsg: Twitter Search Grid +* tumblrgrid: Tumblr picture grid +* turbulence: Turbulence filter effect +* vismem: Visualize data from files +* webfonts: "Hello, World" with Google Web Fonts +* websvg: Generate SVG as a web server + + +## Functions and types ## + +Many functions use x, y to specify an object's location, and w, h to specify the object's width and height. +Where applicable, a final optional argument specifies the style to be applied to the object. +The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a +series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: ) + +The SVG type: + + type SVG struct { + Writer io.Writer + } + +Most operations are methods on this type, specifying the destination io.Writer. + +The Offcolor type: + + type Offcolor struct { + Offset uint8 + Color string + Opacity float64 + } + +is used to specify the offset, color, and opacity of stop colors in linear and radial gradients + +The Filterspec type: + + type Filterspec struct { + In string + In2 string + Result string + } + +is used to specify inputs and results for filter effects + + +### Structure, Scripting, Metadata, Transformation and Links ### + + New(w io.Writer) *SVG + Constructor, Specify the output destination. + + Start(w int, h int, attributes ...string) + begin the SVG document with the width w and height h. Optionally add additional elements + (such as additional namespaces or scripting events) + + + Startview(w, h, minx, miny, vw, vh int) + begin the SVG document with the width w, height h, with a viewBox at minx, miny, vw, vh. + + + Startunit(w int, h int, unit string, ns ...string) + begin the SVG document, with width and height in the specified units. Optionally add additional elements + (such as additional namespaces or scripting events) + + + + Startpercent(w int, h int, ns ...string) + begin the SVG document, with width and height in percent. Optionally add additional elements + (such as additional namespaces or scripting events) + + + + StartviewUnit(w, h int, unit string, minx, miny, vw, vh int) + begin the SVG document with the width w, height h, in the specified unit, with a viewBox at minx, miny, vw, vh. + + + End() + end the SVG document + + Script(scriptype string, data ...string) + Script defines a script with a specified type, (for example "application/javascript"). + if the first variadic argument is a link, use only the link reference. + Otherwise, treat variadic arguments as the text of the script (marked up as CDATA). + if no data is specified, simply close the script element. + + + Style(scriptype string, data ...string) + Style defines a script with a specified type, (for example "text/css"). + if the first variadic argument is a link, use only the link reference. + Otherwise, treat variadic arguments as the text of the script (marked up as CDATA). + if no data is specified, simply close the style element. + + + Group(s ...string) + begin a group, with arbitrary attributes + + + Gstyle(s string) + begin a group, with the specified style. + + + Gid(s string) + begin a group, with the specified id. + + Gtransform(s string) + begin a group, with the specified transform, end with Gend(). + + + Translate(x, y int) + begins coordinate translation to (x,y), end with Gend(). + + + Scale(n float64) + scales the coordinate system by n, end with Gend(). + + + ScaleXY(x, y float64) + scales the coordinate system by x, y. End with Gend(). + + + SkewX(a float64) + SkewX skews the x coordinate system by angle a, end with Gend(). + + + SkewY(a float64) + SkewY skews the y coordinate system by angle a, end with Gend(). + + + SkewXY(ax, ay float64) + SkewXY skews x and y coordinate systems by ax, ay respectively, end with Gend(). + + + Rotate(r float64) + rotates the coordinate system by r degrees, end with Gend(). + + + TranslateRotate(x, y int, r float64) + translates the coordinate system to (x,y), then rotates to r degrees, end with Gend(). + + RotateTranslate(x, y int, r float64) + rotates the coordinate system r degrees, then translates to (x,y), end with Gend(). + + Gend() + end the group (must be paired with Gstyle, Gtransform, Gid). + + ClipPath(s ...string) + Begin a ClipPath + + + ClipEnd() + End a ClipPath + + + Def() + begin a definition block. + + + DefEnd() + end a definition block. + + Marker(id string, x, y, w, h int, s ...string) + define a marker + + + + MarkerEnd() + end a marker + + + Mask(id string, x int, y int, w int, h int, s ...string) + creates a mask with a specified id, dimension, and optional style. + + + MaskEnd() + ends the Mask element. + + + Pattern(id string, x, y, width, height int, putype string, s ...string) + define a Pattern with the specified dimensions, the putype can be either "user" or "obj", which sets the patternUnits + attribute to be either userSpaceOnUse or objectBoundingBox. + + + Desc(s string) + specify the text of the description. + + + Title(s string) + specify the text of the title. + + + Link(href string, title string) + begin a link named "href", with the specified title. + + + LinkEnd() + end the link. + + Use(x int, y int, link string, s ...string) + place the object referenced at link at the location x, y. + + +### Shapes ### + + Circle(x int, y int, r int, s ...string) + draw a circle, centered at x,y with radius r. + + + ![Circle](http://farm5.static.flickr.com/4144/5187953823_01a1741489_m.jpg) + + Ellipse(x int, y int, w int, h int, s ...string) + draw an ellipse, centered at x,y with radii w, and h. + + + ![Ellipse](http://farm2.static.flickr.com/1271/5187953773_a9d1fc406c_m.jpg) + + Polygon(x []int, y []int, s ...string) + draw a series of line segments using an array of x, y coordinates. + + + ![Polygon](http://farm2.static.flickr.com/1006/5187953873_337dc26597_m.jpg) + + Rect(x int, y int, w int, h int, s ...string) + draw a rectangle with upper left-hand corner at x,y, with width w, and height h. + + + ![Rect](http://farm2.static.flickr.com/1233/5188556032_86c90e354b_m.jpg) + + CenterRect(x int, y int, w int, h int, s ...string) + draw a rectangle with its center at x,y, with width w, and height h. + + Roundrect(x int, y int, w int, h int, rx int, ry int, s ...string) + draw a rounded rectangle with upper the left-hand corner at x,y, + with width w, and height h. The radii for the rounded portion + is specified by rx (width), and ry (height). + + ![Roundrect](http://farm2.static.flickr.com/1275/5188556120_e2a9998fee_m.jpg) + + Square(x int, y int, s int, style ...string) + draw a square with upper left corner at x,y with sides of length s. + + ![Square](http://farm5.static.flickr.com/4110/5187953659_54dcce242e_m.jpg) + +### Paths ### + + Path(p string, s ...style) + draw the arbitrary path as specified in p, according to the style specified in s. + + + Arc(sx int, sy int, ax int, ay int, r int, large bool, sweep bool, ex int, ey int, s ...string) + draw an elliptical arc beginning coordinate at sx,sy, ending coordinate at ex, ey + width and height of the arc are specified by ax, ay, the x axis rotation is r + + if sweep is true, then the arc will be drawn in a "positive-angle" direction (clockwise), + if false, the arc is drawn counterclockwise. + + if large is true, the arc sweep angle is greater than or equal to 180 degrees, + otherwise the arc sweep is less than 180 degrees. + + + ![Arc](http://farm2.static.flickr.com/1300/5188556148_df1a176074_m.jpg) + + + + Bezier(sx int, sy int, cx int, cy int, px int, py int, ex int, ey int, s ...string) + draw a cubic bezier curve, beginning at sx,sy, ending at ex,ey + with control points at cx,cy and px,py. + + + ![Bezier](http://farm2.static.flickr.com/1233/5188556246_a03e67d013.jpg) + + + + Qbezier(sx int, sy int, cx int, cy int, ex int, ey int, tx int, ty int, s ...string) + draw a quadratic bezier curve, beginning at sx, sy, ending at tx,ty + with control points are at cx,cy, ex,ey. + + + ![Qbezier](http://farm2.static.flickr.com/1018/5187953917_9a43cf64fb.jpg) + + + Qbez(sx int, sy int, cx int, cy int, ex int, ey int, s...string) + draws a quadratic bezier curver, with optional style beginning at sx,sy, ending at ex, sy + with the control point at cx, cy. + + + ![Qbez](http://farm6.static.flickr.com/5176/5569879349_5f726aab5e.jpg) + +### Lines ### + + Line(x1 int, y1 int, x2 int, y2 int, s ...string) + draw a line segment between x1,y1 and x2,y2. + + + ![Line](http://farm5.static.flickr.com/4154/5188556080_0be19da0bc.jpg) + + + Polyline(x []int, y []int, s ...string) + draw a polygon using coordinates specified in x,y arrays. + + + ![Polyline](http://farm2.static.flickr.com/1266/5188556384_a863273a69.jpg) + +### Image and Text ### + + Image(x int, y int, w int, h int, link string, s ...string) + place at x,y (upper left hand corner), the image with width w, and height h, referenced at link. + + + ![Image](http://farm5.static.flickr.com/4058/5188556346_e5ce3dcbc2_m.jpg) + + Text(x int, y int, t string, s ...string) + Place the specified text, t at x,y according to the style specified in s. + + + Textlines(x, y int, s []string, size, spacing int, fill, align string) + Places lines of text in s, starting at x,y, at the specified size, fill, and alignment, and spacing. + + Textpath(t string, pathid string, s ...string) + places optionally styled text along a previously defined path. + + ![Image](http://farm4.static.flickr.com/3149/5694580737_4b291df768_m.jpg) + +### Color ### + + RGB(r int, g int, b int) string + creates a style string for the fill color designated + by the (r)ed, g(reen), (b)lue components. + + + RGBA(r int, g int, b int, a float64) string + as above, but includes the color's opacity as a value + between 0.0 (fully transparent) and 1.0 (opaque). + +### Gradients ### + + LinearGradient(id string, x1, y1, x2, y2 uint8, sc []Offcolor) + constructs a linear color gradient identified by id, + along the vector defined by (x1,y1), and (x2,y2). + The stop color sequence defined in sc. Coordinates are expressed as percentages. + + ![LinearGradient](http://farm5.static.flickr.com/4153/5187954033_3972f63fa9.jpg) + + RadialGradient(id string, cx, cy, r, fx, fy uint8, sc []Offcolor) + constructs a radial color gradient identified by id, + centered at (cx,cy), with a radius of r. + (fx, fy) define the location of the focal point of the light source. + The stop color sequence defined in sc. + Coordinates are expressed as percentages. + + + ![RadialGradient](http://farm2.static.flickr.com/1302/5187954065_7ddba7b819.jpg) + +### Animation ### + + Animate(link, attr string, from, to int, duration float64, repeat int, s ...string) +Animate animates the item referenced by the link, using the specified attribute +The animation starts at coordinate from, terminates at to, and repeats as specified. +Addtional attributes may be added as needed. + + + AnimateMotion(link, path string, duration float64, repeat int, s ...string) +AnimateMotion animates the referenced object ```link``` along the specified ```path``` + + + + AnimateTranslate(link string, fx, fy, tx, ty int, duration float64, repeat int, s ...string) +AnimateTranslate animates the translation transformation (link refers to the object to animate, fx, fy are from coordinates, tx, ty are the to coordinates) + + + AnimateRotate(link string, fs, fc, fe, ts, tc, te int, duration float64, repeat int, s ...string) +AnimateRotate animates the rotation transformation (link refers to the object to animate, f[s,c,e] are the from start, center, and end angles, t[s,c,e] are the +start, center, and end angles) + + + + AnimateScale(link string, from, to, duration float64, repeat int, s ...string) +AnimateScale animates the scale transformation (link refers to the object to animate, from and to specify the scaling factor) + + + + AnimateSkewX(link string, from, to, duration float64, repeat int, s ...string) +AnimateSkewX animates the skewX transformation ((link refers to the object to animate, from and to specify the skew angle) + + + + AnimateSkewY(link string, from, to, duration float64, repeat int, s ...string) +AnimateSkewY animates the skewY transformation (link refers to the object to animate, and from and to specify the skew angle) + + + +### Filter Effects ### + + Filter(id string, s ...string) + Filter begins a filter set +Standard reference: + + Fend() +Fend ends a filter set +Standard reference: + + FeBlend(fs Filterspec, mode string, s ...string) +FeBlend specifies a Blend filter primitive +Standard reference: + + FeColorMatrix(fs Filterspec, values [20]float64, s ...string) +FeColorMatrix specifies a color matrix filter primitive, with matrix values +Standard reference: + + FeColorMatrixHue(fs Filterspec, value float64, s ...string) +FeColorMatrix specifies a color matrix filter primitive, with hue values +Standard reference: + + FeColorMatrixSaturate(fs Filterspec, value float64, s ...string) +FeColorMatrix specifies a color matrix filter primitive, with saturation values +Standard reference: + + FeColorMatrixLuminence(fs Filterspec, s ...string) +FeColorMatrix specifies a color matrix filter primitive, with luminence values +Standard reference: + + FeComponentTransfer() +FeComponentTransfer begins a feComponent filter Element> +Standard reference: + + FeCompEnd() +FeCompEnd ends a feComponent filter Element> + + FeComposite(fs Filterspec, operator string, k1, k2, k3, k4 int, s ...string) +FeComposite specifies a feComposite filter primitive +Standard reference: + + FeConvolveMatrix(fs Filterspec, matrix [9]int, s ...string) +FeConvolveMatrix specifies a feConvolveMatrix filter primitive +Standard reference: + + + FeDiffuseLighting(fs Filterspec, scale, constant float64, s ...string) +FeDiffuseLighting specifies a diffuse lighting filter primitive, +a container for light source Element>s, end with DiffuseEnd() + + FeDiffEnd() +FeDiffuseEnd ends a diffuse lighting filter primitive container +Standard reference: + + + FeDisplacementMap(fs Filterspec, scale float64, xchannel, ychannel string, s ...string) +FeDisplacementMap specifies a feDisplacementMap filter primitive +Standard reference: + + FeDistantLight(fs Filterspec, azimuth, elevation float64, s ...string) +FeDistantLight specifies a feDistantLight filter primitive +Standard reference: + + FeFlood(fs Filterspec, color string, opacity float64, s ...string) +FeFlood specifies a flood filter primitive +Standard reference: + + FeFuncLinear(channel string, slope, intercept float64) +FeFuncLinear is the linear form of feFunc +Standard reference: + + FeFuncGamma(channel, amplitude, exponent, offset float64) +FeFuncGamma is the gamma curve form of feFunc +Standard reference: + + FeFuncTable(channel string, tv []float64) +FeFuncGamma is the form of feFunc using a table of values +Standard reference: + + FeFuncDiscrete(channel string, tv []float64) +FeFuncGamma is the form of feFunc using discrete values +Standard reference: + + FeGaussianBlur(fs Filterspec, stdx, stdy float64, s ...string) +FeGaussianBlur specifies a Gaussian Blur filter primitive +Standard reference: + + FeImage(href string, result string, s ...string) +FeImage specifies a feImage filter primitive +Standard reference: + + FeMerge(nodes []string, s ...string) +FeMerge specifies a feMerge filter primitive, containing feMerge Element>s +Standard reference: + + FeMorphology(fs Filterspec, operator string, xradius, yradius float64, s ...string) +FeMorphologyLight specifies a feMorphologyLight filter primitive +Standard reference: + + FeOffset(fs Filterspec, dx, dy int, s ...string) +FeOffset specifies the feOffset filter primitive +Standard reference: + + FePointLight(x, y, z float64, s ...string) +FePointLight specifies a fePpointLight filter primitive +Standard reference: + + FeSpecularLighting(fs Filterspec, scale, constant float64, exponent int, color string, s ...string) +FeSpecularLighting specifies a specular lighting filter primitive, +a container for light source elements, end with SpecularEnd() + + + FeSpecEnd() +FeSpecularEnd ends a specular lighting filter primitive container +Standard reference: + + + FeSpotLight(fs Filterspec, x, y, z, px, py, pz float64, s ...string) +FeSpotLight specifies a feSpotLight filter primitive +Standard reference: + + FeTile(fs Filterspec, in string, s ...string) +FeTile specifies the tile utility filter primitive +Standard reference: + + + FeTurbulence(fs Filterspec, ftype string, bfx, bfy float64, octaves int, seed int64, stitch bool, s ...string) +FeTurbulence specifies a turbulence filter primitive +Standard reference: + +### Filter convenience functions (modeled on CSS filter effects) ### + + Blur(p float64) +Blur function by standard deviation + + Brightness(p float64) +Brightness function (0-100) + + Grayscale() +Apply a grayscale filter to the image + + HueRotate(a float64) +Rotate Hues (0-360 degrees) + + Invert() +Invert the image's colors + + Saturate(p float64) +Percent saturation, 0 is grayscale + + Sepia() +Apply sepia tone + + +### Utility ### + + Grid(x int, y int, w int, h int, n int, s ...string) + draws a grid of straight lines starting at x,y, with a width w, and height h, and a size of n. + + ![Grid](http://farm5.static.flickr.com/4133/5190957924_7a31d0db34.jpg) + +### Credits ### + +Thanks to Jonathan Wright for the io.Writer update. diff --git a/vendor/github.com/ajstarks/svgo/doc.go b/vendor/github.com/ajstarks/svgo/doc.go new file mode 100644 index 0000000..c24fc2d --- /dev/null +++ b/vendor/github.com/ajstarks/svgo/doc.go @@ -0,0 +1,126 @@ +/* +Package svg generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (). +Output goes to the specified io.Writer. + +Supported SVG elements and functions + +Shapes, lines, text + + circle, ellipse, polygon, polyline, rect (including roundrects), line, text + +Paths + + general, arc, cubic and quadratic bezier paths, + +Image and Gradients + + image, linearGradient, radialGradient, + +Transforms + + translate, rotate, scale, skewX, skewY + +Filter Effects + + filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting, + feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight, + feSpecularLighting, feSpotLight,feTile, feTurbulence + + +Metadata elements + + desc, defs, g (style, transform, id), mask, marker, pattern, title, (a)ddress, link, script, style, use + +Usage: (assuming GOPATH is set) + + go get github.com/ajstarks/svgo + go install github.com/ajstarks/svgo/... + + +You can use godoc to browse the documentation from the command line: + + $ godoc github.com/ajstarks/svgo + + +a minimal program, to generate SVG to standard output. + + package main + + import ( + "github.com/ajstarks/svgo" + "os" + ) + + func main() { + width := 500 + height := 500 + canvas := svg.New(os.Stdout) + canvas.Start(width, height) + canvas.Circle(width/2, height/2, 100) + canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white") + canvas.End() + } + +Drawing in a web server: (http://localhost:2003/circle) + + package main + + import ( + "log" + "github.com/ajstarks/svgo" + "net/http" + ) + + func main() { + http.Handle("/circle", http.HandlerFunc(circle)) + err := http.ListenAndServe(":2003", nil) + if err != nil { + log.Fatal("ListenAndServe:", err) + } + } + + func circle(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "image/svg+xml") + s := svg.New(w) + s.Start(500, 500) + s.Circle(250, 250, 125, "fill:none;stroke:black") + s.End() + } + +Functions and types + +Many functions use x, y to specify an object's location, and w, h to specify the object's width and height. +Where applicable, a final optional argument specifies the style to be applied to the object. +The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a +series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: ) + +The SVG type: + + type SVG struct { + Writer io.Writer + } + +Most operations are methods on this type, specifying the destination io.Writer. + +The Offcolor type: + + type Offcolor struct { + Offset uint8 + Color string + Opacity float64 + } + +is used to specify the offset, color, and opacity of stop colors in linear and radial gradients + +The Filterspec type: + + type Filterspec struct { + In string + In2 string + Result string + } + +is used to specify inputs and results for filter effects + +*/ +package svg diff --git a/vendor/github.com/ajstarks/svgo/gophercolor128x128.png b/vendor/github.com/ajstarks/svgo/gophercolor128x128.png new file mode 100644 index 0000000..aa11cf2 Binary files /dev/null and b/vendor/github.com/ajstarks/svgo/gophercolor128x128.png differ diff --git a/vendor/github.com/ajstarks/svgo/newsvg b/vendor/github.com/ajstarks/svgo/newsvg new file mode 100644 index 0000000..46c7099 --- /dev/null +++ b/vendor/github.com/ajstarks/svgo/newsvg @@ -0,0 +1,39 @@ +#!/bin/sh + +if test $# -lt 1 +then + echo "specify a file" + exit 2 +fi + +if test ! -f $1 +then +cat < $1 +package main + +import ( + "github.com/ajstarks/svgo" + "os" +) + +var ( + width = 500 + height = 500 + canvas = svg.New(os.Stdout) +) + +func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } + + +func main() { + canvas.Start(width, height) + background(255) + + // your code here + + canvas.Grid(0, 0, width, height, 10, "stroke:black;opacity:0.1") + canvas.End() +} +! +fi +$EDITOR $1 diff --git a/vendor/github.com/ajstarks/svgo/svg.go b/vendor/github.com/ajstarks/svgo/svg.go new file mode 100644 index 0000000..31c70c7 --- /dev/null +++ b/vendor/github.com/ajstarks/svgo/svg.go @@ -0,0 +1,1055 @@ +// Package svg provides an API for generating Scalable Vector Graphics (SVG) +package svg + +// package main +// +// import ( +// "github.com/ajstarks/svgo" +// "os" +// ) +// +// var ( +// width = 500 +// height = 500 +// canvas = svg.New(os.Stdout) +// ) +// +// func main() { +// canvas.Start(width, height) +// canvas.Circle(width/2, height/2, 100) +// canvas.Text(width/2, height/2, "Hello, SVG", +// "text-anchor:middle;font-size:30px;fill:white") +// canvas.End() +// } +// + +import ( + "fmt" + "io" + + "encoding/xml" + "strings" +) + +// SVG defines the location of the generated SVG +type SVG struct { + Writer io.Writer +} + +// Offcolor defines the offset and color for gradients +type Offcolor struct { + Offset uint8 + Color string + Opacity float64 +} + +// Filterspec defines the specification of SVG filters +type Filterspec struct { + In, In2, Result string +} + +const ( + svgtop = ` + +` + vbfmt = `viewBox="%d %d %d %d"` + + emptyclose = "/>\n" +) + +// New is the SVG constructor, specifying the io.Writer where the generated SVG is written. +func New(w io.Writer) *SVG { return &SVG{w} } + +func (svg *SVG) print(a ...interface{}) (n int, errno error) { + return fmt.Fprint(svg.Writer, a...) +} + +func (svg *SVG) println(a ...interface{}) (n int, errno error) { + return fmt.Fprintln(svg.Writer, a...) +} + +func (svg *SVG) printf(format string, a ...interface{}) (n int, errno error) { + return fmt.Fprintf(svg.Writer, format, a...) +} + +func (svg *SVG) genattr(ns []string) { + for _, v := range ns { + svg.printf("\n %s", v) + } + svg.println(svgns) +} + +// Structure, Metadata, Scripting, Style, Transformation, and Links + +// Start begins the SVG document with the width w and height h. +// Other attributes may be optionally added, for example viewbox or additional namespaces +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#SVGElement +func (svg *SVG) Start(w int, h int, ns ...string) { + svg.printf(svginitfmt, svgtop, w, "", h, "") + svg.genattr(ns) +} + +// Startunit begins the SVG document, with width and height in the specified units +// Other attributes may be optionally added, for example viewbox or additional namespaces +func (svg *SVG) Startunit(w int, h int, unit string, ns ...string) { + svg.printf(svginitfmt, svgtop, w, unit, h, unit) + svg.genattr(ns) +} + +// Startpercent begins the SVG document, with width and height as percentages +// Other attributes may be optionally added, for example viewbox or additional namespaces +func (svg *SVG) Startpercent(w int, h int, ns ...string) { + svg.printf(svginitfmt, svgtop, w, "%", h, "%") + svg.genattr(ns) +} + +// Startview begins the SVG document, with the specified width, height, and viewbox +// Other attributes may be optionally added, for example viewbox or additional namespaces +func (svg *SVG) Startview(w, h, minx, miny, vw, vh int) { + svg.Start(w, h, fmt.Sprintf(vbfmt, minx, miny, vw, vh)) +} + +// StartviewUnit begins the SVG document with the specified width, height, and unit +func (svg *SVG) StartviewUnit(w, h int, unit string, minx, miny, vw, vh int) { + svg.Startunit(w, h, unit, fmt.Sprintf(vbfmt, minx, miny, vw, vh)) +} + +// Startraw begins the SVG document, passing arbitrary attributes +func (svg *SVG) Startraw(ns ...string) { + svg.printf(svgtop) + svg.genattr(ns) +} + +// End the SVG document +func (svg *SVG) End() { svg.println("") } + +// linkembed defines an element with a specified type, +// (for example "application/javascript", or "text/css"). +// if the first variadic argument is a link, use only the link reference. +// Otherwise, treat those arguments as the text of the script (marked up as CDATA). +// if no data is specified, just close the element +func (svg *SVG) linkembed(tag string, scriptype string, data ...string) { + svg.printf(`<%s type="%s"`, tag, scriptype) + switch { + case len(data) == 1 && islink(data[0]): + svg.printf(" %s/>\n", href(data[0])) + + case len(data) > 0: + svg.printf(">\n\n") + for _, v := range data { + svg.println(v) + } + svg.printf("\n\n", tag) + + default: + svg.println(`/>`) + } +} + +// Script defines a script with a specified type, (for example "application/javascript"). +func (svg *SVG) Script(scriptype string, data ...string) { + svg.linkembed("script", scriptype, data...) +} + +// Style defines the specified style (for example "text/css") +func (svg *SVG) Style(scriptype string, data ...string) { + svg.linkembed("style", scriptype, data...) +} + +// Gstyle begins a group, with the specified style. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#GElement +func (svg *SVG) Gstyle(s string) { svg.println(group("style", s)) } + +// Gtransform begins a group, with the specified transform +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) Gtransform(s string) { svg.println(group("transform", s)) } + +// Translate begins coordinate translation, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) Translate(x, y int) { svg.Gtransform(translate(x, y)) } + +// Scale scales the coordinate system by n, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) Scale(n float64) { svg.Gtransform(scale(n)) } + +// ScaleXY scales the coordinate system by dx and dy, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) ScaleXY(dx, dy float64) { svg.Gtransform(scaleXY(dx, dy)) } + +// SkewX skews the x coordinate system by angle a, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) SkewX(a float64) { svg.Gtransform(skewX(a)) } + +// SkewY skews the y coordinate system by angle a, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) SkewY(a float64) { svg.Gtransform(skewY(a)) } + +// SkewXY skews x and y coordinates by ax, ay respectively, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) SkewXY(ax, ay float64) { svg.Gtransform(skewX(ax) + " " + skewY(ay)) } + +// Rotate rotates the coordinate system by r degrees, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) Rotate(r float64) { svg.Gtransform(rotate(r)) } + +// TranslateRotate translates the coordinate system to (x,y), then rotates to r degrees, end with Gend() +func (svg *SVG) TranslateRotate(x, y int, r float64) { + svg.Gtransform(translate(x, y) + " " + rotate(r)) +} + +// RotateTranslate rotates the coordinate system r degrees, then translates to (x,y), end with Gend() +func (svg *SVG) RotateTranslate(x, y int, r float64) { + svg.Gtransform(rotate(r) + " " + translate(x, y)) +} + +// Group begins a group with arbitrary attributes +func (svg *SVG) Group(s ...string) { svg.printf("`)) } + +// Gid begins a group, with the specified id +func (svg *SVG) Gid(s string) { + svg.print(``) +} + +// Gend ends a group (must be paired with Gsttyle, Gtransform, Gid). +func (svg *SVG) Gend() { svg.println(``) } + +// ClipPath defines a clip path +func (svg *SVG) ClipPath(s ...string) { svg.printf(``)) } + +// ClipEnd ends a ClipPath +func (svg *SVG) ClipEnd() { + svg.println(``) +} + +// Def begins a defintion block. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#DefsElement +func (svg *SVG) Def() { svg.println(``) } + +// DefEnd ends a defintion block. +func (svg *SVG) DefEnd() { svg.println(``) } + +// Marker defines a marker +// Standard reference: http://www.w3.org/TR/SVG11/painting.html#MarkerElement +func (svg *SVG) Marker(id string, x, y, width, height int, s ...string) { + svg.printf(`\n")) +} + +// MarkerEnd ends a marker +func (svg *SVG) MarkerEnd() { svg.println(``) } + +// Pattern defines a pattern with the specified dimensions. +// The putype can be either "user" or "obj", which sets the patternUnits +// attribute to be either userSpaceOnUse or objectBoundingBox +// Standard reference: http://www.w3.org/TR/SVG11/pservers.html#Patterns +func (svg *SVG) Pattern(id string, x, y, width, height int, putype string, s ...string) { + puattr := "userSpaceOnUse" + if putype != "user" { + puattr = "objectBoundingBox" + } + svg.printf(`\n")) +} + +// PatternEnd ends a marker +func (svg *SVG) PatternEnd() { svg.println(``) } + +// Desc specified the text of the description tag. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#DescElement +func (svg *SVG) Desc(s string) { svg.tt("desc", s) } + +// Title specified the text of the title tag. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#TitleElement +func (svg *SVG) Title(s string) { svg.tt("title", s) } + +// Link begins a link named "name", with the specified title. +// Standard Reference: http://www.w3.org/TR/SVG11/linking.html#Links +func (svg *SVG) Link(href string, title string) { + svg.printf("") +} + +// LinkEnd ends a link. +func (svg *SVG) LinkEnd() { svg.println(``) } + +// Use places the object referenced at link at the location x, y, with optional style. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#UseElement +func (svg *SVG) Use(x int, y int, link string, s ...string) { + svg.printf(``)) +} + +// MaskEnd ends a Mask. +func (svg *SVG) MaskEnd() { svg.println(``) } + +// Shapes + +// Circle centered at x,y, with radius r, with optional style. +// Standard Reference: http://www.w3.org/TR/SVG11/shapes.html#CircleElement +func (svg *SVG) Circle(x int, y int, r int, s ...string) { + svg.printf(`")) + xml.Escape(svg.Writer, []byte(t)) + svg.println(``) +} + +// Textpath places text optionally styled text along a previously defined path +// Standard Reference: http://www.w3.org/TR/SVG11/text.html#TextPathElement +func (svg *SVG) Textpath(t string, pathid string, s ...string) { + svg.printf("", endstyle(s, ">"), pathid) + xml.Escape(svg.Writer, []byte(t)) + svg.println(``) +} + +// Textlines places a series of lines of text starting at x,y, at the specified size, fill, and alignment. +// Each line is spaced according to the spacing argument +func (svg *SVG) Textlines(x, y int, s []string, size, spacing int, fill, align string) { + svg.Gstyle(fmt.Sprintf("font-size:%dpx;fill:%s;text-anchor:%s", size, fill, align)) + for _, t := range s { + svg.Text(x, y, t) + y += spacing + } + svg.Gend() +} + +// Colors + +// RGB specifies a fill color in terms of a (r)ed, (g)reen, (b)lue triple. +// Standard reference: http://www.w3.org/TR/css3-color/ +func (svg *SVG) RGB(r int, g int, b int) string { + return fmt.Sprintf(`fill:rgb(%d,%d,%d)`, r, g, b) +} + +// RGBA specifies a fill color in terms of a (r)ed, (g)reen, (b)lue triple and opacity. +func (svg *SVG) RGBA(r int, g int, b int, a float64) string { + return fmt.Sprintf(`fill-opacity:%.2f; %s`, a, svg.RGB(r, g, b)) +} + +// Gradients + +// LinearGradient constructs a linear color gradient identified by id, +// along the vector defined by (x1,y1), and (x2,y2). +// The stop color sequence defined in sc. Coordinates are expressed as percentages. +func (svg *SVG) LinearGradient(id string, x1, y1, x2, y2 uint8, sc []Offcolor) { + svg.printf("\n", + id, pct(x1), pct(y1), pct(x2), pct(y2)) + svg.stopcolor(sc) + svg.println("") +} + +// RadialGradient constructs a radial color gradient identified by id, +// centered at (cx,cy), with a radius of r. +// (fx, fy) define the location of the focal point of the light source. +// The stop color sequence defined in sc. +// Coordinates are expressed as percentages. +func (svg *SVG) RadialGradient(id string, cx, cy, r, fx, fy uint8, sc []Offcolor) { + svg.printf("\n", + id, pct(cx), pct(cy), pct(r), pct(fx), pct(fy)) + svg.stopcolor(sc) + svg.println("") +} + +// stopcolor is a utility function used by the gradient functions +// to define a sequence of offsets (expressed as percentages) and colors +func (svg *SVG) stopcolor(oc []Offcolor) { + for _, v := range oc { + svg.printf("\n", + pct(v.Offset), v.Color, v.Opacity) + } +} + +// Filter Effects: +// Most functions have common attributes (in, in2, result) defined in type Filterspec +// used as a common first argument. + +// Filter begins a filter set +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#FilterElement +func (svg *SVG) Filter(id string, s ...string) { + svg.printf(`\n")) +} + +// Fend ends a filter set +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#FilterElement +func (svg *SVG) Fend() { + svg.println(``) +} + +// FeBlend specifies a Blend filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feBlendElement +func (svg *SVG) FeBlend(fs Filterspec, mode string, s ...string) { + switch mode { + case "normal", "multiply", "screen", "darken", "lighten": + break + default: + mode = "normal" + } + svg.printf(` 360 { + value = 0 + } + svg.printf(` 1 { + value = 1 + } + svg.printf(``) +} + +// FeCompEnd ends a feComponent filter element +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement +func (svg *SVG) FeCompEnd() { + svg.println(``) +} + +// FeComposite specifies a feComposite filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feCompositeElement +func (svg *SVG) FeComposite(fs Filterspec, operator string, k1, k2, k3, k4 int, s ...string) { + switch operator { + case "over", "in", "out", "atop", "xor", "arithmetic": + break + default: + operator = "over" + } + svg.printf(``)) +} + +// FeDiffEnd ends a diffuse lighting filter primitive container +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feDiffuseLightingElement +func (svg *SVG) FeDiffEnd() { + svg.println(``) +} + +// FeDisplacementMap specifies a feDisplacementMap filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feDisplacementMapElement +func (svg *SVG) FeDisplacementMap(fs Filterspec, scale float64, xchannel, ychannel string, s ...string) { + svg.printf(``) + for _, n := range nodes { + svg.printf("\n", n) + } + svg.println(``) +} + +// FeMorphology specifies a feMorphologyLight filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feMorphologyElement +func (svg *SVG) FeMorphology(fs Filterspec, operator string, xradius, yradius float64, s ...string) { + switch operator { + case "erode", "dilate": + break + default: + operator = "erode" + } + svg.printf(`\n")) +} + +// FeSpecEnd ends a specular lighting filter primitive container +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feSpecularLightingElement +func (svg *SVG) FeSpecEnd() { + svg.println(``) +} + +// FeSpotLight specifies a feSpotLight filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feSpotLightElement +func (svg *SVG) FeSpotLight(fs Filterspec, x, y, z, px, py, pz float64, s ...string) { + svg.printf(` 1 { + bfx = 0 + } + if bfy < 0 || bfy > 1 { + bfy = 0 + } + switch ftype[0:1] { + case "f", "F": + ftype = "fractalNoise" + case "t", "T": + ftype = "turbulence" + default: + ftype = "turbulence" + } + + var ss string + if stitch { + ss = "stitch" + } else { + ss = "noStitch" + } + svg.printf(` +`, href(link), duration, repeatString(repeat), endstyle(s, ">"), href(path)) +} + +// AnimateTransform animates in the context of SVG transformations +func (svg *SVG) AnimateTransform(link, ttype, from, to string, duration float64, repeat int, s ...string) { + svg.printf(` 0 { + svg.Gstyle(s[0]) + } + for ix := x; ix <= x+w; ix += n { + svg.Line(ix, y, ix, y+h) + } + + for iy := y; iy <= y+h; iy += n { + svg.Line(x, iy, x+w, iy) + } + if len(s) > 0 { + svg.Gend() + } + +} + +// Support functions + +// coordpair returns a coordinate pair as a string +func coordpair(x, y int) string { + return fmt.Sprintf("%d %d", x, y) +} + +// sce makes start, center, end coordinates string for animate transformations +func sce(start, center, end int) string { + return fmt.Sprintf("%d %d %d", start, center, end) +} + +// repeatString computes the repeat string for animation methods +// repeat <= 0 --> "indefinite", otherwise the integer string +func repeatString(n int) string { + if n > 0 { + return fmt.Sprintf("%d", n) + } + return "indefinite" +} + +// style returns a style name,attribute string +func style(s string) string { + if len(s) > 0 { + return fmt.Sprintf(`style="%s"`, s) + } + return s +} + +// pp returns a series of polygon points +func (svg *SVG) pp(x []int, y []int, tag string) { + svg.print(tag) + if len(x) != len(y) { + svg.print(" ") + return + } + lx := len(x) - 1 + for i := 0; i < lx; i++ { + svg.print(coord(x[i], y[i]) + " ") + } + svg.print(coord(x[lx], y[lx])) +} + +// endstyle modifies an SVG object, with either a series of name="value" pairs, +// or a single string containing a style +func endstyle(s []string, endtag string) string { + if len(s) > 0 { + nv := "" + for i := 0; i < len(s); i++ { + if strings.Index(s[i], "=") > 0 { + nv += (s[i]) + " " + } else { + nv += style(s[i]) + " " + } + } + return nv + endtag + } + return endtag + +} + +// tt creates a xml element, tag containing s +func (svg *SVG) tt(tag string, s string) { + svg.print("<" + tag + ">") + xml.Escape(svg.Writer, []byte(s)) + svg.println("") +} + +// poly compiles the polygon element +func (svg *SVG) poly(x []int, y []int, tag string, s ...string) { + svg.pp(x, y, "<"+tag+" points=\"") + svg.print(`" ` + endstyle(s, "/>\n")) +} + +// onezero returns "0" or "1" +func onezero(flag bool) string { + if flag { + return "1" + } + return "0" +} + +// pct returns a percetage, capped at 100 +func pct(n uint8) uint8 { + if n > 100 { + return 100 + } + return n +} + +// islink determines if a string is a script reference +func islink(link string) bool { + return strings.HasPrefix(link, "http://") || strings.HasPrefix(link, "#") || + strings.HasPrefix(link, "../") || strings.HasPrefix(link, "./") +} + +// group returns a group element +func group(tag string, value string) string { return fmt.Sprintf(``, tag, value) } + +// scale return the scale string for the transform +func scale(n float64) string { return fmt.Sprintf(`scale(%g)`, n) } + +// scaleXY return the scale string for the transform +func scaleXY(dx, dy float64) string { return fmt.Sprintf(`scale(%g,%g)`, dx, dy) } + +// skewx returns the skewX string for the transform +func skewX(angle float64) string { return fmt.Sprintf(`skewX(%g)`, angle) } + +// skewx returns the skewX string for the transform +func skewY(angle float64) string { return fmt.Sprintf(`skewY(%g)`, angle) } + +// rotate returns the rotate string for the transform +func rotate(r float64) string { return fmt.Sprintf(`rotate(%g)`, r) } + +// translate returns the translate string for the transform +func translate(x, y int) string { return fmt.Sprintf(`translate(%d,%d)`, x, y) } + +// coord returns a coordinate string +func coord(x int, y int) string { return fmt.Sprintf(`%d,%d`, x, y) } + +// ptag returns the beginning of the path element +func ptag(x int, y int) string { return fmt.Sprintf(` 0 { + attrs += fmt.Sprintf(`in="%s" `, s.In) + } + if len(s.In2) > 0 { + attrs += fmt.Sprintf(`in2="%s" `, s.In2) + } + if len(s.Result) > 0 { + attrs += fmt.Sprintf(`result="%s" `, s.Result) + } + return attrs +} + +// tablevalues outputs a series of values as a XML attribute +func (svg *SVG) tablevalues(s string, t []float64) { + svg.printf(` %s="`, s) + for i := 0; i < len(t)-1; i++ { + svg.printf("%g ", t[i]) + } + svg.printf(`%g"%s`, t[len(t)-1], emptyclose) +} + +// imgchannel validates the image channel indicator +func imgchannel(c string) string { + switch c { + case "R", "G", "B", "A": + return c + case "r", "g", "b", "a": + return strings.ToUpper(c) + case "red", "green", "blue", "alpha": + return strings.ToUpper(c[0:1]) + case "Red", "Green", "Blue", "Alpha": + return c[0:1] + } + return "R" +} diff --git a/vendor/github.com/ajstarks/svgo/svgdef.pdf b/vendor/github.com/ajstarks/svgo/svgdef.pdf new file mode 100644 index 0000000..66664bd Binary files /dev/null and b/vendor/github.com/ajstarks/svgo/svgdef.pdf differ diff --git a/vendor/github.com/ajstarks/svgo/svgdef.png b/vendor/github.com/ajstarks/svgo/svgdef.png new file mode 100644 index 0000000..8fae61a Binary files /dev/null and b/vendor/github.com/ajstarks/svgo/svgdef.png differ diff --git a/vendor/github.com/ajstarks/svgo/svgdef.svg b/vendor/github.com/ajstarks/svgo/svgdef.svg new file mode 100644 index 0000000..6318580 --- /dev/null +++ b/vendor/github.com/ajstarks/svgo/svgdef.svg @@ -0,0 +1,395 @@ + + + +Object Definitions + + + + + + + + + + + + + + + + +x, y + +w +Square(x, y, w int, style ...string) + + + +x, y + +h +w +Rect(x, y, w, h int, style ...string) + + + +x, y + +h +w +CenterRect(x, y, w, h int, style ...string) + + + +x, y + +h +w + + +ry +rx +Roundrect(x, y, w, h, rx, ry int, style ...string) + + + +x, y + +x, y + +x, y + +x, y + +x, y + +x, y + +Polygon(x, y []int, style ...string) + + + + +x, y + + +r +Circle(x, y, r int, style ...string) + + + + + +x, y + + + +rx +ry +Ellipse(x, y, rx, ry int, style ...string) + + + + +x1, y1 + +x2, y2 + +Line(x1, y1, x2, y2 int, style ...string) + + + +x, y + +x, y + +x, y + +x, y + +Polyline(x, y []int, style ...string) + + + +sx, sy + +ex, ey + +Arc(sx, sy, ax, ay, r int, lflag, sflag bool, ex, ey int, style ...string) + + + + + +x, y +Path(s string, style ...string) + + + +sx, sy + +cx, cy + +ex, ey + +Qbez(sx, sy, cx, cy, ex, ey int, style ...string) + + + +sx, sy + +cx, cy + +px, py + +ex, ey + +Bezier(sx, sy, cx, cy, px, py, ex, ey int, style ...string) + + + +x, y + +h +w + +Image(x, y, w, h, int path string, style ...string) + + + + +x1%, y1% + +x2%, y2% +LinearGradient(s string, x1, y1, x2, y2 uint8, oc []Offcolor) + + + + +cx%, cy% + +fx%, fy% +RadialGradient(s string, cx, cy, r, fx, fy uint8, oc []Offcolor) + + + +0, 0 + +x, y +Translate(x, y int) + + + + + + + +x, y +h +w +n + + + + + + + + + + + + + + + + +Grid(x, y, w, h, n int, style ...string) + + + +x, y +hello, this is SVG +Text(x, y int, s string, style ...string) + + + +0, 0 + + + + +Scale(n float64) + + + +0, 0 + + + + +ScaleXY(x, y float64) + + + +0, 0 + + + + +SkewX(a float64) + + + +0, 0 + + + + +SkewY(a float64) + + + +0, 0 + + + + +SkewXY(x, y float64) + + + +0, 0 +Rotate(r float64) + + +r + + + + + +It's "fine" & "dandy" to draw text along a path +Textpath(s, pathid string, style ...string) + + + +New(w io Writer) +Start(w, h int, options ...string)/End() +Startview(w, h, minx, miny, vw, vh int) +Group(s ...string)/Gend() +Gstyle(s string)/Gend() +Gtransform(s string)/Gend() +Gid(id string)/Gend() +ClipPath(s ...string)/ClipEnd() +Def()/DefEnd() +Marker()/MarkerEnd() +Pattern()/PatternEnd() +Desc(s string) +Title(s string) +Script(type, data ...string) +Mask(id string, x,y,w,h int, style ...string)/MaskEnd() +Link(href string, title string)/LinkEnd() +Use(x int, y int, link string, style ...string) + + +specify destination +begin/end the document +begin/end the document with viewport +begin/end group with attributes +begin/end group style +begin/end group transform +begin/end group id +begin/end clip path +begin/end a defintion block +begin/end markers +begin/end pattern +set the description element +set the title element +define a script +begin/end mask element +begin/end link to href, with a title +use defined objects + +Textlines(x, y int, s []string, size, spacing int, fill, align string) + + + + + + + +r +g +b +-> + +RGB(r, g, b int) + + + + + + + + +alpha +r +g +b +-> + +RGBA(r, g, b int, opacity float64) + + +SVG Go Library Description + + + +SVG Go Library +github.com/ajstarks/svgo + +Object Usage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..1f2fe8f --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,3 @@ +# github.com/ajstarks/svgo v0.0.0-20200725142600-7a3c8b57fecb +## explicit +github.com/ajstarks/svgo