├── .gitignore ├── .travis.yml ├── Dockerfile ├── Godeps ├── Godeps.json └── Readme ├── LICENSE ├── README.md ├── cmd └── slackchess │ └── main.go ├── internal ├── chessutil │ ├── chessutil.go │ └── chessutil_test.go ├── imageutil │ ├── imageutil.go │ ├── imageutil_test.go │ └── test.png ├── slack │ ├── response.go │ ├── slash.go │ └── slash_test.go └── stockfish │ ├── stockfish-7-64-linux │ ├── stockfish-7-64-mac │ ├── stockfish.go │ ├── stockfish.sh │ ├── stockfish_mac.sh │ └── stockfish_test.go ├── screen_shots ├── screen_shot_1.png ├── screen_shot_2.png └── screen_shot_3.png └── vendor └── github.com ├── ajstarks └── svgo │ ├── LICENSE │ ├── README.markdown │ ├── doc.go │ ├── gophercolor128x128.png │ ├── newsvg │ ├── svg.go │ ├── svgdef.pdf │ ├── svgdef.png │ └── svgdef.svg ├── gorilla └── schema │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── cache.go │ ├── converter.go │ ├── decoder.go │ └── doc.go └── loganjspears ├── chess ├── .gitignore ├── LICENSE ├── README.md ├── alg.go ├── bitboard.go ├── board.go ├── doc.go ├── fen.go ├── game.go ├── move.go ├── pgn.go ├── piece.go ├── position.go ├── square.go ├── stringer.go └── valid_moves.go └── chessimg ├── .gitignore ├── LICENSE ├── README.md ├── chessimg.go ├── example.png ├── example.svg └── internal └── bindata.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | *.pgn 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go_import_path: github.com/loganjspears/slackchess 4 | 5 | go: 6 | - 1.6 7 | 8 | sudo: true 9 | dist: trusty 10 | 11 | before_install: 12 | - sudo apt-get -y -qq update 13 | - sudo apt-get install -y librsvg2-bin 14 | 15 | script: go test ./internal/... -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | # required for rsvg-convert dependency 4 | RUN apt-get -y update && apt-get install -y librsvg2-bin && apt-get install -y curl 5 | 6 | ENV GOLANG_VERSION 1.6 7 | ENV GOLANG_DOWNLOAD_URL https://golang.org/dl/go$GOLANG_VERSION.linux-amd64.tar.gz 8 | ENV GOLANG_DOWNLOAD_SHA256 5470eac05d273c74ff8bac7bef5bad0b5abbd1c4052efbdbc8db45332e836b0b 9 | 10 | RUN curl -fsSL "$GOLANG_DOWNLOAD_URL" -o golang.tar.gz \ 11 | && echo "$GOLANG_DOWNLOAD_SHA256 golang.tar.gz" | sha256sum -c - \ 12 | && tar -C /usr/local -xzf golang.tar.gz \ 13 | && rm golang.tar.gz 14 | 15 | ENV GOPATH /go 16 | ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH 17 | 18 | RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH" 19 | WORKDIR $GOPATH 20 | 21 | # Copy the local package files to the container's workspace. 22 | ADD . /go/src/github.com/loganjspears/slackchess 23 | 24 | # Build the slackchess command inside the container. 25 | # (You may fetch or manage dependencies here, 26 | # either manually or with a tool like "godep".) 27 | RUN go install github.com/loganjspears/slackchess/cmd/slackchess 28 | 29 | # Run the command by default when the container starts. 30 | ENTRYPOINT /go/bin/slackchess -token=$TOKEN -url=$URL 31 | 32 | # Document that the service listens on port 5000. 33 | EXPOSE 5000 -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/loganjspears/slackchess", 3 | "GoVersion": "go1.6", 4 | "GodepVersion": "v60", 5 | "Deps": [ 6 | { 7 | "ImportPath": "github.com/ajstarks/svgo", 8 | "Comment": "go.weekly.2012-01-27-117-g99831a8", 9 | "Rev": "99831a8cf95786bcee3956cbf3f181c91bc37c24" 10 | }, 11 | { 12 | "ImportPath": "github.com/gorilla/schema", 13 | "Rev": "14c555599c2a4f493c1e13fd1ea6fdf721739028" 14 | }, 15 | { 16 | "ImportPath": "github.com/loganjspears/chess", 17 | "Rev": "26b8630b475e2f5e0bf08caeedc0bb49e106db52" 18 | }, 19 | { 20 | "ImportPath": "github.com/loganjspears/chessimg", 21 | "Rev": "63d5893490bc03dacbd4389f0006f6711ea92b3f" 22 | }, 23 | { 24 | "ImportPath": "github.com/loganjspears/chessimg/internal", 25 | "Rev": "63d5893490bc03dacbd4389f0006f6711ea92b3f" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Logan Spears 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # slackchess 2 | [![Build Status](https://travis-ci.org/loganjspears/slackchess.svg?branch=master)](https://travis-ci.org/loganjspears/slackchess) 3 | 4 | How can you play chess on any device? 5 | 6 | Slack! 7 | 8 | This project gives you a turn key solution for tranforming slack into a chess client. With slackchess you can: 9 | - challenge another slack user 10 | - play against @slackbot powered by [Stockfish](https://stockfishchess.org) 11 | - offer draws and resign 12 | - export your game as a PGN 13 | 14 | ## Screenshot 15 | 16 | 17 | ## Installation Guide 18 | 19 | To start the Slack Integration Guide you will need the IP or URL of your server. If you don't already have a place to host slackchess, you can follow the Digital Ocean Setup Guide first. After the Slack Integration is setup, you should follow the Server Setup Guide. The Server Setup Guide will guide you through pulling down the [docker image](https://hub.docker.com/r/loganjspears/slackchess/) and running it as a container. 20 | 21 | ### Digital Ocean Setup Guide 22 | 23 | 1. Signup for Digital Ocean if you don't have an account (https://m.do.co/c/f4609bed935c referal link to get $10 free) 24 | 2. Click Create Droplet 25 | 3. Select One-click Apps > Docker 26 | 4. Choose smallest size 27 | 5. Make sure to add your SSH keys (if you need help click "New SSH Key > How to use SSH keys") 28 | 6. Select 1 droplet 29 | 7. Click Create 30 | 31 | ### Slack Integration Guide 32 | 33 | 1. Login to Slack and go to https://slack.com/apps 34 | 2. Go to Configure > Custom Integrations > Slash Commands > Add Configuration 35 | 3. For "Choose a Command" type "/chess" and press "Add Slash Command Integration" 36 | 4. Set "URL" to http://45.55.141.331/command where "45.55.141.331" is your IP 37 | 5. Make sure "Method" is POST 38 | 6. Copy and paste the generated "Token" somewhere, you will need it later 39 | 7. For "Customize Name" you can enter anything (ex. "ChessBot") 40 | 8. For "Customize Icon" I used this image: https://upload.wikimedia.org/wikipedia/commons/thumb/f/f0/Chess_kdt45.svg/45px-Chess_kdt45.svg.png 41 | 9. Click "Save Integration" 42 | 43 | ![slack integration](/screen_shots/screen_shot_3.png) 44 | 45 | ### Server Setup Guide 46 | 47 | If you added your SSH keys to your local machine (you should have in the Digital Ocean Setup Guide), you can SSH into your machine. Replace "45.55.141.331" with your IP. 48 | ```bash 49 | ssh root@45.55.141.331 50 | ``` 51 | 52 | Pull down the docker image from Docker Hub. 53 | ```bash 54 | docker pull loganjspears/slackchess 55 | ``` 56 | 57 | Create and run a docker container with your information. Replace "R546Sk2RoZiAXZltssJ4WfEO" with your Slack token. Replace "45.55.141.331" with your IP. 58 | ```bash 59 | docker run -d -p 80:5000 -e TOKEN=R546Sk2RoZiAXZltssJ4WfEO -e URL=http://45.55.141.331 loganjspears/slackchess 60 | ``` 61 | 62 | If everything worked the ps command should show the container running. 63 | ```bash 64 | docker ps 65 | ``` 66 | 67 | Thats it! 68 | 69 | ## Commands 70 | 71 | Play against user: 72 | ``` 73 | /chess play @user 74 | ``` 75 | 76 | Play against bot: 77 | ``` 78 | /chess play @slackbot 79 | ``` 80 | 81 | You can view all commands by using the help command. 82 | ``` 83 | /chess help 84 | ``` 85 | 86 | ![slackchess](/screen_shots/screen_shot_2.png) 87 | 88 | -------------------------------------------------------------------------------- /cmd/slackchess/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "strings" 10 | 11 | "github.com/gorilla/schema" 12 | "github.com/loganjspears/chess" 13 | "github.com/loganjspears/slackchess/internal/imageutil" 14 | "github.com/loganjspears/slackchess/internal/slack" 15 | ) 16 | 17 | var token string 18 | var url string 19 | 20 | func init() { 21 | flag.StringVar(&token, "token", "", "slack token") 22 | flag.StringVar(&url, "url", "", "root url for of the server") 23 | } 24 | 25 | func main() { 26 | flag.Parse() 27 | if token == "" { 28 | log.Fatal("must set token flag") 29 | } 30 | if url == "" { 31 | log.Fatal("must set url flag") 32 | } 33 | slack.SetBaseURL(url) 34 | slack.SetStockfishPath("/go/src/github.com/loganjspears/slackchess/internal/stockfish") 35 | http.HandleFunc("/", logHandler(upHandler)) 36 | http.HandleFunc("/command", logHandler(commandHandler)) 37 | http.HandleFunc("/board/", logHandler(boardImgHandler)) 38 | log.Fatal(http.ListenAndServe(":5000", nil)) 39 | } 40 | 41 | func logHandler(handler http.HandlerFunc) http.HandlerFunc { 42 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 43 | log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL) 44 | handler.ServeHTTP(w, r) 45 | }) 46 | } 47 | 48 | func upHandler(w http.ResponseWriter, r *http.Request) { 49 | fmt.Fprintf(w, "up") 50 | } 51 | 52 | func commandHandler(w http.ResponseWriter, r *http.Request) { 53 | if r.Method != "POST" { 54 | http.Error(w, "", http.StatusNotFound) 55 | return 56 | } 57 | if err := r.ParseForm(); err != nil { 58 | http.Error(w, err.Error(), http.StatusInternalServerError) 59 | return 60 | } 61 | log.Printf("slack slash command form %+v", r.Form) 62 | 63 | cmd := &slack.SlashCmd{} 64 | if err := schema.NewDecoder().Decode(cmd, r.Form); err != nil { 65 | http.Error(w, err.Error(), http.StatusInternalServerError) 66 | return 67 | } 68 | if cmd.Token != token { 69 | log.Println(cmd.Token, token) 70 | http.Error(w, "invalid token", http.StatusBadRequest) 71 | return 72 | } 73 | resp := cmd.Response() 74 | log.Printf("sending response %+v", resp) 75 | w.Header().Set("Content-Type", "application/json") 76 | if err := json.NewEncoder(w).Encode(resp); err != nil { 77 | http.Error(w, err.Error(), http.StatusInternalServerError) 78 | return 79 | } 80 | } 81 | 82 | func boardImgHandler(w http.ResponseWriter, r *http.Request) { 83 | if r.Method != "GET" { 84 | http.Error(w, "", http.StatusNotFound) 85 | return 86 | } 87 | log.Println("board img handler - ", r.URL.Path) 88 | path := strings.TrimPrefix(r.URL.Path, "/board/") 89 | path = strings.TrimSuffix(path, ".png") 90 | path = path + " w KQkq - 0 1" 91 | 92 | fen, err := chess.FEN(path) 93 | if err != nil { 94 | http.Error(w, "could not parse fen "+err.Error(), http.StatusNotFound) 95 | return 96 | } 97 | v := r.URL.Query() 98 | sqs := squaresFromString(v.Get("markSquares")) 99 | g := chess.NewGame(fen) 100 | log.Println("creating image for fen ", path) 101 | w.Header().Set("Content-Type", "image/png") 102 | w.Header().Set("Cache-Control", "max-age=31536000") 103 | if err := imageutil.WritePNG(w, g.Position(), sqs...); err != nil { 104 | http.Error(w, "could not parse fen "+err.Error(), http.StatusInternalServerError) 105 | return 106 | } 107 | } 108 | 109 | // s must be in the format: a1,b2,c3 110 | func squaresFromString(s string) []chess.Square { 111 | sqStrs := strings.Split(s, ",") 112 | sqs := []chess.Square{} 113 | for _, s := range sqStrs { 114 | if s == "" { 115 | continue 116 | } 117 | for i := 0; i < 64; i++ { 118 | sq := chess.Square(i) 119 | if sq.String() == s { 120 | sqs = append(sqs, sq) 121 | break 122 | } 123 | } 124 | } 125 | return sqs 126 | } 127 | -------------------------------------------------------------------------------- /internal/chessutil/chessutil.go: -------------------------------------------------------------------------------- 1 | package chessutil 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | 8 | "github.com/loganjspears/chess" 9 | ) 10 | 11 | // NewGame returns a game at the starting position with added tag pairs. 12 | func NewGame(whitePlayer, blackPlayer string) *chess.Game { 13 | tagPairs := []*chess.TagPair{ 14 | {Key: "Event", Value: "Casual game"}, 15 | {Key: "Date", Value: time.Now().Format("2006.01.02")}, // TODO figure out time zone 16 | {Key: "White", Value: whitePlayer}, 17 | {Key: "Black", Value: blackPlayer}, 18 | {Key: "TimeControl", Value: "-"}, 19 | {Key: "Annotator", Value: "slackchess"}, 20 | } 21 | return chess.NewGame(chess.TagPairs(tagPairs)) 22 | } 23 | 24 | func BotForColor(g *chess.Game, c chess.Color) (isBot bool, skillLvl int) { 25 | player := PlayerForColor(g, c) 26 | parts := strings.Split(player, ":") 27 | is := player == "slackbot" || (len(parts) > 0 && parts[0] == "slackbot") 28 | if is && len(parts) == 1 { 29 | return true, 10 30 | } else if is && len(parts) == 2 { 31 | i, err := strconv.Atoi(parts[1]) 32 | if err == nil && (i >= 0 && i <= 20) { 33 | return true, i 34 | } 35 | } 36 | return false, 0 37 | } 38 | 39 | // AddDrawOffer adds the "Draw Offer" tag pair showing a pending draw offer. 40 | func AddDrawOffer(g *chess.Game, c chess.Color) *chess.Game { 41 | return addTag(g, "Draw Offer", c.String()) 42 | } 43 | 44 | // RemoveDrawOffer removes the "Draw Offer" tag pair showing a pending draw offer. 45 | func RemoveDrawOffer(g *chess.Game) *chess.Game { 46 | return removeTag(g, "Draw Offer") 47 | } 48 | 49 | // DrawOfferColor reads the "Draw Offer" tag pair and returns the color 50 | // that made the offer. If no offer has been made, chess.NoColor will 51 | // be returned. 52 | func DrawOfferColor(g *chess.Game) chess.Color { 53 | v := tagPairValueForKey(g, "Draw Offer") 54 | switch v { 55 | case chess.White.String(): 56 | return chess.White 57 | case chess.Black.String(): 58 | return chess.Black 59 | } 60 | return chess.NoColor 61 | } 62 | 63 | // PlayerToMove returns the name of the current player to move from the 64 | // "White" or "Black" tag pair. 65 | func PlayerToMove(g *chess.Game) string { 66 | if g.Position().Turn() == chess.White { 67 | return tagPairValueForKey(g, "White") 68 | } 69 | return tagPairValueForKey(g, "Black") 70 | } 71 | 72 | // ColorOfPlayer returns the chess.Color of the given player. If the 73 | // player isn't in the "White" or "Black" tag pair then chess.NoColor 74 | // is returned. 75 | func ColorOfPlayer(g *chess.Game, player string) chess.Color { 76 | if tagPairValueForKey(g, "White") == player { 77 | return chess.White 78 | } else if tagPairValueForKey(g, "Black") == player { 79 | return chess.Black 80 | } 81 | return chess.NoColor 82 | } 83 | 84 | // PlayerForColor returns the player for the give chess.Color. 85 | func PlayerForColor(g *chess.Game, c chess.Color) string { 86 | switch c { 87 | case chess.White: 88 | return tagPairValueForKey(g, "White") 89 | case chess.Black: 90 | return tagPairValueForKey(g, "Black") 91 | } 92 | return "" 93 | } 94 | 95 | func addTag(g *chess.Game, key, value string) *chess.Game { 96 | tagPairs := g.TagPairs() 97 | tagPairs = append(tagPairs, &chess.TagPair{Key: key, Value: value}) 98 | pgnStr := g.String() 99 | pgn, _ := chess.PGN(strings.NewReader(pgnStr)) 100 | return chess.NewGame(pgn, chess.TagPairs(tagPairs)) 101 | } 102 | 103 | func removeTag(g *chess.Game, key string) *chess.Game { 104 | tagPairs := g.TagPairs() 105 | cp := []*chess.TagPair{} 106 | for _, tagPair := range tagPairs { 107 | if tagPair.Key != key { 108 | cp = append(cp, tagPair) 109 | } 110 | } 111 | pgnStr := g.String() 112 | pgn, _ := chess.PGN(strings.NewReader(pgnStr)) 113 | return chess.NewGame(pgn, chess.TagPairs(cp)) 114 | } 115 | 116 | func tagPairValueForKey(g *chess.Game, key string) string { 117 | for _, tagPair := range g.TagPairs() { 118 | if tagPair.Key == key { 119 | return tagPair.Value 120 | } 121 | } 122 | return "" 123 | } 124 | -------------------------------------------------------------------------------- /internal/chessutil/chessutil_test.go: -------------------------------------------------------------------------------- 1 | package chessutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/loganjspears/chess" 7 | ) 8 | 9 | func TestNewGame(t *testing.T) { 10 | g := NewGame("p1", "p2") 11 | w := tagPairValueForKey(g, "White") 12 | if w != "p1" { 13 | t.Fatalf("expected tag pair value %s but got %s", "p1", w) 14 | } 15 | 16 | b := tagPairValueForKey(g, "Black") 17 | if b != "p2" { 18 | t.Fatalf("expected tag pair value %s but got %s", "p2", b) 19 | } 20 | } 21 | 22 | func TestDrawOffer(t *testing.T) { 23 | g := NewGame("p1", "p2") 24 | g = AddDrawOffer(g, chess.White) 25 | actual := DrawOfferColor(g) 26 | if actual != chess.White { 27 | t.Fatalf("expected draw offer %s but got %s", chess.White, actual) 28 | } 29 | g = RemoveDrawOffer(g) 30 | actual = DrawOfferColor(g) 31 | if actual != chess.NoColor { 32 | t.Fatalf("expected draw offer %s but got %s", chess.NoColor, actual) 33 | } 34 | } 35 | 36 | func TestPlayerToMove(t *testing.T) { 37 | g := NewGame("p1", "p2") 38 | actual := PlayerToMove(g) 39 | if actual != "p1" { 40 | t.Fatalf("expected player to move %s but got %s", "p1", actual) 41 | } 42 | if err := g.MoveAlg("e4"); err != nil { 43 | t.Fatal(err) 44 | } 45 | actual = PlayerToMove(g) 46 | if actual != "p2" { 47 | t.Fatalf("expected player to move %s but got %s", "p2", actual) 48 | } 49 | } 50 | 51 | func TestColorOfPlayer(t *testing.T) { 52 | g := NewGame("p1", "p2") 53 | actual := ColorOfPlayer(g, "p1") 54 | if actual != chess.White { 55 | t.Fatalf("expected player to be %s but got %s", chess.White, actual) 56 | } 57 | actual = ColorOfPlayer(g, "p2") 58 | if actual != chess.Black { 59 | t.Fatalf("expected player to be %s but got %s", chess.Black, actual) 60 | } 61 | actual = ColorOfPlayer(g, "p3") 62 | if actual != chess.NoColor { 63 | t.Fatalf("expected player to be %s but got %s", chess.NoColor, actual) 64 | } 65 | } 66 | 67 | func TestPlayerForColor(t *testing.T) { 68 | g := NewGame("p1", "p2") 69 | actual := PlayerForColor(g, chess.White) 70 | if actual != "p1" { 71 | t.Fatalf("expected player to be %s but got %s", "p1", actual) 72 | } 73 | actual = PlayerForColor(g, chess.Black) 74 | if actual != "p2" { 75 | t.Fatalf("expected player to be %s but got %s", "p2", actual) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /internal/imageutil/imageutil.go: -------------------------------------------------------------------------------- 1 | package imageutil 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "image/color" 7 | "io" 8 | "os" 9 | "os/exec" 10 | "time" 11 | 12 | "github.com/loganjspears/chess" 13 | "github.com/loganjspears/chessimg" 14 | ) 15 | 16 | // WritePNG writes a 500x500 pixel PNG of the position to the writer. 17 | // Any markedSquares will be highlighted in the resulting image. This 18 | // function is dependent on rsvg-convert which is installed with 19 | // apt-get install librsvg2-bin on debian systems. An error will 20 | // be return if there is a problem reading from or writing to the 21 | // filesystem (filesystem access is required to use rsvg-convert). 22 | func WritePNG(w io.Writer, p *chess.Position, markedSquares ...chess.Square) error { 23 | // create temp svg file to be used by rsvg-convert 24 | fileName := fmt.Sprint(time.Now().UnixNano()) 25 | tempSVG, err := os.Create(fileName + ".svg") 26 | if err != nil { 27 | return errors.New("could not create svg file " + err.Error()) 28 | } 29 | defer tempSVG.Close() 30 | defer os.Remove(fileName + ".svg") 31 | mark := chessimg.MarkSquares(color.RGBA{R: 255, G: 255, B: 0, A: 1}, markedSquares...) 32 | if err := chessimg.New(tempSVG, mark).EncodeSVG(p.String()); err != nil { 33 | return errors.New("could not write to svg file " + err.Error()) 34 | } 35 | 36 | // create temp png file using rsvg-convert 37 | // rsvg-convert -h 32 icon.svg > icon-32.png 38 | if err := exec.Command("rsvg-convert", "-h", "500", fileName+".svg", "-o", fileName+".png").Run(); err != nil { 39 | return errors.New("could not use rsvg-convert " + err.Error()) 40 | } 41 | tempPGN, err := os.Open(fileName + ".png") 42 | if err != nil { 43 | return errors.New("could not open png file " + err.Error()) 44 | } 45 | defer tempPGN.Close() 46 | defer os.Remove(fileName + ".png") 47 | 48 | if _, err := io.Copy(w, tempPGN); err != nil { 49 | return errors.New("could not copy png to writer " + err.Error()) 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /internal/imageutil/imageutil_test.go: -------------------------------------------------------------------------------- 1 | package imageutil 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "fmt" 7 | "runtime" 8 | "testing" 9 | 10 | "github.com/loganjspears/chess" 11 | ) 12 | 13 | func TestImage(t *testing.T) { 14 | buf := bytes.NewBuffer([]byte{}) 15 | g := chess.NewGame() 16 | if err := g.MoveAlg("e4"); err != nil { 17 | t.Fatal(err) 18 | } 19 | p := g.Position() 20 | if err := WritePNG(buf, p, chess.E2, chess.E4); err != nil { 21 | t.Fatal(err) 22 | } 23 | actual := fmt.Sprintf("%x", md5.Sum(buf.Bytes())) 24 | expected := getExpectedMD5Hash() 25 | if expected != actual { 26 | t.Fatalf("expected %s md5 hash but got %s", expected, actual) 27 | } 28 | } 29 | 30 | func getExpectedMD5Hash() string { 31 | if runtime.GOOS == "darwin" { 32 | return "6f711cc83010cc0694943171ea3c8518" 33 | } 34 | return "954a16709a5ee00d5f34bec0d01a3fcc" 35 | } 36 | -------------------------------------------------------------------------------- /internal/imageutil/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/internal/imageutil/test.png -------------------------------------------------------------------------------- /internal/slack/response.go: -------------------------------------------------------------------------------- 1 | package slack 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/loganjspears/chess" 7 | "github.com/loganjspears/slackchess/internal/chessutil" 8 | ) 9 | 10 | // Response is a slash command response. More information can 11 | // be found here: https://api.slack.com/slash-commands#responding_to_a_command 12 | type Response struct { 13 | ResponseType string `json:"response_type,omitempty"` 14 | Text string `json:"text,omitempty"` 15 | Attachments []*Attachment `json:"attachments,omitempty"` 16 | } 17 | 18 | // Attachment is a slack attachment. Formatting and other information can be 19 | // found here: https://api.slack.com/docs/attachments 20 | type Attachment struct { 21 | Fallback string `json:"fallback,omitempty"` 22 | Color string `json:"color,omitempty"` 23 | Pretext string `json:"pretext,omitempty"` 24 | AuthorName string `json:"author_name,omitempty"` 25 | AuthorLink string `json:"author_link,omitempty"` 26 | AuthorIcon string `json:"author_icon,omitempty"` 27 | Title string `json:"title,omitempty"` 28 | TitleLink string `json:"title_link,omitempty"` 29 | Text string `json:"text,omitempty"` 30 | Fields []struct { 31 | Title string `json:"title,omitempty"` 32 | Value string `json:"value,omitempty"` 33 | Short bool `json:"short,omitempty"` 34 | } `json:"fields,omitempty"` 35 | ImageURL string `json:"image_url,omitempty"` 36 | ThumbURL string `json:"thumb_url,omitempty"` 37 | } 38 | 39 | // boardResponse returns a response for the current state of the board. 40 | func boardResponse(g *chess.Game) *Response { 41 | whitePlayer := chessutil.PlayerForColor(g, chess.White) 42 | blackPlayer := chessutil.PlayerForColor(g, chess.Black) 43 | moveNum := (len(g.Moves()) / 2) + 1 44 | color := "#f2f2f2" 45 | if g.Position().Turn() == chess.Black { 46 | color = "#000000" 47 | } 48 | resp := &Response{ 49 | ResponseType: responseTypeInChannel, 50 | Attachments: []*Attachment{{ 51 | ImageURL: imageURL(g), 52 | Color: color, 53 | }}, 54 | } 55 | outcome := g.Outcome() 56 | switch outcome { 57 | case chess.NoOutcome: 58 | title := "White to move" 59 | if g.Position().Turn() == chess.Black { 60 | title = "Black to move" 61 | } 62 | resp.Attachments[0].Text = fmt.Sprintf("@%s vs @%s on move #%d", whitePlayer, blackPlayer, moveNum) 63 | resp.Attachments[0].Title = title 64 | case chess.WhiteWon: 65 | resp.Attachments[0].Title = "White won" 66 | resp.Attachments[0].Text = fmt.Sprintf("🏆 @%s defeated @%s by %s on move #%d", whitePlayer, blackPlayer, g.Method().String(), moveNum) 67 | resp.Attachments = append(resp.Attachments, &Attachment{ 68 | Text: g.String(), 69 | }) 70 | case chess.BlackWon: 71 | resp.Attachments[0].Title = "Black won" 72 | resp.Attachments[0].Text = fmt.Sprintf("🏆 @%s defeated @%s by %s on move #%d", blackPlayer, whitePlayer, g.Method().String(), moveNum) 73 | resp.Attachments = append(resp.Attachments, &Attachment{ 74 | Text: g.String(), 75 | }) 76 | case chess.Draw: 77 | resp.Attachments[0].Title = "Draw" 78 | resp.Attachments[0].Text = fmt.Sprintf("Draw by %s on move #%d", g.Method().String(), moveNum) 79 | resp.Attachments = append(resp.Attachments, &Attachment{ 80 | Text: g.String(), 81 | }) 82 | } 83 | resp.Attachments[0].Fallback = resp.Attachments[0].Title 84 | return resp 85 | } 86 | 87 | // imageURL returns a string in the format: 88 | // http://104.196.27.70/board/rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR.png?markSquares=e2,e4 89 | func imageURL(g *chess.Game) string { 90 | queryStr := "" 91 | moves := g.Moves() 92 | if len(moves) > 0 { 93 | m := moves[len(moves)-1] 94 | queryStr = fmt.Sprintf("?markSquares=%s,%s", m.S1().String(), m.S2().String()) 95 | } 96 | return fmt.Sprintf("%s/board/%s.png%s", baseURL, g.Position().Board().String(), queryStr) 97 | } 98 | 99 | var ( 100 | helpResponse = &Response{ 101 | ResponseType: responseTypeEphemeral, 102 | Text: helpText, 103 | } 104 | unknownResponse = &Response{ 105 | ResponseType: responseTypeEphemeral, 106 | Text: unknownText, 107 | } 108 | noGameResponse = &Response{ 109 | ResponseType: responseTypeEphemeral, 110 | Text: noGameText, 111 | } 112 | outOfTurnResponse = &Response{ 113 | ResponseType: responseTypeEphemeral, 114 | Text: outOfTurnText, 115 | } 116 | notInGameResponse = &Response{ 117 | ResponseType: responseTypeEphemeral, 118 | Text: notInGameText, 119 | } 120 | invalidMoveResponse = &Response{ 121 | ResponseType: responseTypeEphemeral, 122 | Text: invalidMoveText, 123 | } 124 | noDrawOfferResponse = &Response{ 125 | ResponseType: responseTypeEphemeral, 126 | Text: noDrawToAccept, 127 | } 128 | ) 129 | 130 | func errorResponse(err error) *Response { 131 | text := "The server encountered an error - " + err.Error() 132 | return &Response{ 133 | ResponseType: responseTypeEphemeral, 134 | Text: text, 135 | } 136 | } 137 | 138 | func pgnResponse(g *chess.Game) *Response { 139 | return &Response{ 140 | ResponseType: responseTypeEphemeral, 141 | Text: g.String(), 142 | } 143 | } 144 | 145 | const ( 146 | responseTypeEphemeral = "ephemeral" 147 | responseTypeInChannel = "in_channel" 148 | 149 | unknownText = "The command given couldn't be processed. Use /chess help to view available commands." 150 | noGameText = "This command requires an ongoing game in the channel. Use /chess help to view available commands." 151 | outOfTurnText = "Its not your turn!" 152 | notInGameText = "Your not in the current game! Use /chess help to view available commands." 153 | invalidMoveText = "Invalid move! Please use Algebraic Notation: https://en.wikipedia.org/wiki/Algebraic_notation_(chess)" 154 | noDrawToAccept = "There is no draw offer to accept!" 155 | 156 | helpText = "The chess slash command adds chess playing capabilities to slack. Here is the list of commands:\n*/chess help* - this help screen\n*/chess play* - '/chess play @magnus' will start a game against the other player in this channel. There can only be one game per channel and starting a new game will end any in progress. '/chess play slackbot' and '/chess play slackbot:10' will play against the stockfish engine with an optional skill level. The skill level starts at 0 and goes up to 20. '/chess play @magnus black' will start a game as black. '/chess play @magnus random' will start a game as random color.\n*/chess board* - will show the board of the current game\n*/chess pgn* - will show the PGN of the current game\n*/chess move* - 'chess move e4' will move the player using the given Algebraic Notation\n*/chess resign* - resigns the current game\n*/chess draw offer* - offers a draw to other player\n*/chess draw accept* - accepts the draw offer\n*/chess draw reject* - rejects the draw offer (also moving will reject a draw offer)" 157 | ) 158 | -------------------------------------------------------------------------------- /internal/slack/slash.go: -------------------------------------------------------------------------------- 1 | package slack 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "math/rand" 7 | "os" 8 | "strings" 9 | "time" 10 | 11 | "github.com/loganjspears/chess" 12 | "github.com/loganjspears/slackchess/internal/chessutil" 13 | "github.com/loganjspears/slackchess/internal/stockfish" 14 | ) 15 | 16 | var ( 17 | baseURL = "" 18 | stockfishPath = "" 19 | ) 20 | 21 | // SetBaseURL sets the baseURL used in the board image URL embedded in slack attachments. 22 | func SetBaseURL(url string) { 23 | baseURL = url 24 | } 25 | 26 | // SetStockfishPath sets the path to the stockfish folder 27 | func SetStockfishPath(path string) { 28 | stockfishPath = path 29 | } 30 | 31 | // SlashCmd represents a slack "Slash Command". You can read more about 32 | // slash commands here: https://api.slack.com/slash-commands 33 | type SlashCmd struct { 34 | Token string `schema:"token"` 35 | TeamID string `schema:"team_id"` 36 | TeamDomain string `schema:"team_domain"` 37 | ChannelID string `schema:"channel_id"` 38 | ChannelName string `schema:"channel_name"` 39 | UserID string `schema:"user_id"` 40 | UserName string `schema:"user_name"` 41 | Command string `schema:"command"` 42 | Text string `schema:"text"` 43 | ResponseURL string `schema:"response_url"` 44 | } 45 | 46 | // Response returns the response to the given command. 47 | func (s *SlashCmd) Response() *Response { 48 | cmd := userEntryFromText(s.Text) 49 | switch cmd.Type { 50 | case cmdUnknown: 51 | return unknownResponse 52 | case cmdHelp: 53 | return helpResponse 54 | case cmdPlay: 55 | var g *chess.Game 56 | switch cmd.Args[1] { 57 | case "", "white": 58 | g = chessutil.NewGame(s.UserName, cmd.Args[0]) 59 | case "black": 60 | g = chessutil.NewGame(cmd.Args[0], s.UserName) 61 | case "random": 62 | rand.Seed(time.Now().UnixNano()) 63 | r := rand.Intn(2) 64 | if r == 0 { 65 | g = chessutil.NewGame(s.UserName, cmd.Args[0]) 66 | } else { 67 | g = chessutil.NewGame(cmd.Args[0], s.UserName) 68 | } 69 | } 70 | if err := checkForStockfishMove(g); err != nil { 71 | return errorResponse(err) 72 | } 73 | if err := s.SaveGame(g); err != nil { 74 | return errorResponse(err) 75 | } 76 | return boardResponse(g) 77 | } 78 | g, _ := s.Game() 79 | if g == nil { 80 | return noGameResponse 81 | } 82 | c := chessutil.ColorOfPlayer(g, s.UserName) 83 | if c == chess.NoColor { 84 | return notInGameResponse 85 | } 86 | switch cmd.Type { 87 | case cmdMove: 88 | player := chessutil.PlayerToMove(g) 89 | if player != s.UserName { 90 | return outOfTurnResponse 91 | } 92 | if err := g.MoveAlg(cmd.Args[0]); err != nil { 93 | return invalidMoveResponse 94 | } 95 | g = chessutil.RemoveDrawOffer(g) 96 | if err := checkForStockfishMove(g); err != nil { 97 | return errorResponse(err) 98 | } 99 | if err := s.SaveGame(g); err != nil { 100 | return errorResponse(err) 101 | } 102 | return boardResponse(g) 103 | case cmdBoard: 104 | return boardResponse(g) 105 | case cmdPGN: 106 | return pgnResponse(g) 107 | case cmdResign: 108 | g.Resign(c) 109 | if err := s.SaveGame(g); err != nil { 110 | return errorResponse(err) 111 | } 112 | return boardResponse(g) 113 | case cmdDrawOffer: 114 | g = chessutil.AddDrawOffer(g, c) 115 | if err := s.SaveGame(g); err != nil { 116 | return errorResponse(err) 117 | } 118 | return &Response{ 119 | ResponseType: responseTypeInChannel, 120 | Text: s.UserName + " offers a draw.", 121 | } 122 | case cmdDrawAccept: 123 | drawColor := chessutil.DrawOfferColor(g) 124 | if drawColor != c.Other() { 125 | return noDrawOfferResponse 126 | } 127 | g = chessutil.RemoveDrawOffer(g) 128 | if err := g.Draw(chess.DrawOffer); err != nil { 129 | return errorResponse(err) 130 | } 131 | if err := s.SaveGame(g); err != nil { 132 | return errorResponse(err) 133 | } 134 | return boardResponse(g) 135 | case cmdDrawReject: 136 | g = chessutil.RemoveDrawOffer(g) 137 | if err := s.SaveGame(g); err != nil { 138 | return errorResponse(err) 139 | } 140 | return &Response{ 141 | ResponseType: responseTypeInChannel, 142 | Text: s.UserName + " rejects a draw.", 143 | } 144 | } 145 | return unknownResponse 146 | } 147 | 148 | // Game returns the game associated with the slash command. 149 | // Games are stored on the file system and an error is returned 150 | // if there is an IO error or if the game doesn't exist. 151 | func (s *SlashCmd) Game() (*chess.Game, error) { 152 | chess.NewGame() 153 | // open pgn file if it exists 154 | f, err := os.Open(s.GameFileName()) 155 | if err != nil { 156 | return nil, err 157 | } 158 | defer f.Close() 159 | 160 | pgn, err := chess.PGN(f) 161 | if err != nil { 162 | return nil, err 163 | } 164 | return chess.NewGame(pgn), nil 165 | } 166 | 167 | // SaveGame saves the game to disk. An error is returned if there is an IO error. 168 | func (s *SlashCmd) SaveGame(game *chess.Game) error { 169 | pgn := []byte(game.String()) 170 | return ioutil.WriteFile(s.GameFileName(), pgn, 0666) 171 | } 172 | 173 | // GameFileName returns the file name of where the game would be stored (even if there 174 | // is no game actually saved). 175 | func (s *SlashCmd) GameFileName() string { 176 | return fmt.Sprintf("%s_%s.pgn", s.TeamID, s.ChannelID) 177 | } 178 | 179 | // commandType represents a command supported by the slash command 180 | type commandType int 181 | 182 | const ( 183 | cmdUnknown commandType = iota 184 | cmdHelp 185 | cmdPlay 186 | cmdBoard 187 | cmdPGN 188 | cmdMove 189 | cmdResign 190 | cmdDrawOffer 191 | cmdDrawAccept 192 | cmdDrawReject 193 | ) 194 | 195 | // userEntry is a structure result of SlashCmd's text field 196 | type userEntry struct { 197 | Type commandType 198 | Args []string 199 | } 200 | 201 | // userEntryFromText takes a text argument and returns a formatted 202 | // userEntry. If the text can't be parsed then returned userEntry's 203 | // type will be cmdUnknown. 204 | func userEntryFromText(text string) userEntry { 205 | parts := strings.Split(text, " ") 206 | if len(parts) == 1 { 207 | switch parts[0] { 208 | case "help": 209 | return userEntry{Type: cmdHelp, Args: []string{}} 210 | case "board": 211 | return userEntry{Type: cmdBoard, Args: []string{}} 212 | case "pgn": 213 | return userEntry{Type: cmdPGN, Args: []string{}} 214 | case "resign": 215 | return userEntry{Type: cmdResign, Args: []string{}} 216 | } 217 | } else if len(parts) == 2 && parts[0] == "draw" { 218 | switch parts[1] { 219 | case "offer": 220 | return userEntry{Type: cmdDrawOffer, Args: []string{}} 221 | case "accept": 222 | return userEntry{Type: cmdDrawAccept, Args: []string{}} 223 | case "reject": 224 | return userEntry{Type: cmdDrawReject, Args: []string{}} 225 | } 226 | } else if len(parts) == 2 && parts[0] == "move" { 227 | return userEntry{Type: cmdMove, Args: []string{parts[1]}} 228 | } else if (len(parts) == 2 || len(parts) == 3) && parts[0] == "play" { 229 | username := strings.Replace(parts[1], "@", "", -1) 230 | colorTxt := "" 231 | if len(parts) == 3 { 232 | colorTxt = parts[2] 233 | } 234 | return userEntry{Type: cmdPlay, Args: []string{username, colorTxt}} 235 | } 236 | return userEntry{Type: cmdUnknown, Args: []string{}} 237 | } 238 | 239 | func checkForStockfishMove(g *chess.Game) error { 240 | // bot can't move if game is over 241 | if g.Outcome() != chess.NoOutcome { 242 | return nil 243 | } 244 | c := g.Position().Turn() 245 | isBot, skillLvl := chessutil.BotForColor(g, c) 246 | if !isBot { 247 | return nil 248 | } 249 | move, err := stockfish.Move(g, skillLvl, stockfishPath) 250 | if err != nil { 251 | return err 252 | } 253 | return g.Move(move) 254 | } 255 | -------------------------------------------------------------------------------- /internal/slack/slash_test.go: -------------------------------------------------------------------------------- 1 | package slack 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "testing" 7 | 8 | "github.com/loganjspears/chess" 9 | "github.com/loganjspears/slackchess/internal/chessutil" 10 | ) 11 | 12 | type SlashTest struct { 13 | SlashCommands []*SlashCmd 14 | Responses []*Response 15 | } 16 | 17 | var ( 18 | validSlashTests = []SlashTest{ 19 | // help 20 | { 21 | SlashCommands: []*SlashCmd{newSlashCmd("test", "logan", "help")}, 22 | Responses: []*Response{helpResponse}, 23 | }, 24 | // invalid command 25 | { 26 | SlashCommands: []*SlashCmd{newSlashCmd("test", "logan", "unknown")}, 27 | Responses: []*Response{unknownResponse}, 28 | }, 29 | // board 30 | { 31 | SlashCommands: []*SlashCmd{ 32 | newSlashCmd("test", "logan", "play magnus"), 33 | newSlashCmd("test", "logan", "board"), 34 | }, 35 | Responses: []*Response{ 36 | boardResponse(testGame("logan", "magnus")), 37 | boardResponse(testGame("logan", "magnus")), 38 | }, 39 | }, 40 | { 41 | SlashCommands: []*SlashCmd{ 42 | newSlashCmd("board2", "logan", "board"), 43 | }, 44 | Responses: []*Response{ 45 | noGameResponse, 46 | }, 47 | }, 48 | // pgn 49 | { 50 | SlashCommands: []*SlashCmd{ 51 | newSlashCmd("test", "logan", "play magnus"), 52 | newSlashCmd("test", "logan", "pgn"), 53 | }, 54 | Responses: []*Response{ 55 | boardResponse(testGame("logan", "magnus")), 56 | pgnResponse(testGame("logan", "magnus")), 57 | }, 58 | }, 59 | { 60 | SlashCommands: []*SlashCmd{ 61 | newSlashCmd("board2", "logan", "board"), 62 | }, 63 | Responses: []*Response{ 64 | noGameResponse, 65 | }, 66 | }, 67 | // play and move 68 | { 69 | SlashCommands: []*SlashCmd{ 70 | newSlashCmd("test", "logan", "play magnus"), 71 | newSlashCmd("test", "logan", "move e4"), 72 | newSlashCmd("test", "magnus", "move e5"), 73 | }, 74 | Responses: []*Response{ 75 | boardResponse(testGame("logan", "magnus")), 76 | boardResponse(testGame("logan", "magnus", "e4")), 77 | boardResponse(testGame("logan", "magnus", "e4", "e5")), 78 | }, 79 | }, 80 | // draw 81 | { 82 | SlashCommands: []*SlashCmd{ 83 | newSlashCmd("test", "logan", "play magnus"), 84 | newSlashCmd("test", "logan", "draw offer"), 85 | newSlashCmd("test", "magnus", "draw accept"), 86 | }, 87 | Responses: []*Response{ 88 | boardResponse(testGame("logan", "magnus")), 89 | &Response{ 90 | ResponseType: responseTypeInChannel, 91 | Text: "logan offers a draw.", 92 | }, 93 | boardResponse(testGameConfig("logan", "magnus", func(g *chess.Game) { 94 | g.Draw(chess.DrawOffer) 95 | })), 96 | }, 97 | }, 98 | // resign 99 | { 100 | SlashCommands: []*SlashCmd{ 101 | newSlashCmd("test", "logan", "play magnus"), 102 | newSlashCmd("test", "logan", "move e4"), 103 | newSlashCmd("test", "magnus", "resign"), 104 | }, 105 | Responses: []*Response{ 106 | boardResponse(testGame("logan", "magnus")), 107 | boardResponse(testGame("logan", "magnus", "e4")), 108 | boardResponse(testGameConfig("logan", "magnus", func(g *chess.Game) { 109 | g.MoveAlg("e4") 110 | g.Resign(chess.Black) 111 | })), 112 | }, 113 | }, 114 | } 115 | ) 116 | 117 | func testGameConfig(whitePlayer, blackPlayer string, f func(g *chess.Game)) *chess.Game { 118 | g := chessutil.NewGame(whitePlayer, blackPlayer) 119 | f(g) 120 | return g 121 | } 122 | 123 | func testGame(whitePlayer, blackPlayer string, moves ...string) *chess.Game { 124 | g := chessutil.NewGame(whitePlayer, blackPlayer) 125 | for _, m := range moves { 126 | g.MoveAlg(m) 127 | } 128 | return g 129 | } 130 | 131 | func newSlashCmd(ch, userName, text string) *SlashCmd { 132 | return &SlashCmd{ 133 | TeamID: "test", 134 | ChannelID: ch, 135 | UserName: userName, 136 | Text: text, 137 | } 138 | } 139 | 140 | func TestValidSlashTests(t *testing.T) { 141 | for _, test := range validSlashTests { 142 | fileNames := map[string]bool{} 143 | for i, s := range test.SlashCommands { 144 | expected := test.Responses[i] 145 | actual := s.Response() 146 | expectedB, _ := json.Marshal(expected) 147 | actualB, _ := json.Marshal(actual) 148 | if string(expectedB) != string(actualB) { 149 | t.Fatalf("expected %s but got %s", string(expectedB), string(actualB)) 150 | } 151 | fileNames[s.GameFileName()] = true 152 | } 153 | for fileName := range fileNames { 154 | os.Remove(fileName) 155 | } 156 | } 157 | } 158 | 159 | func TestPlayStockfish(t *testing.T) { 160 | SetStockfishPath("../stockfish") 161 | cmd := newSlashCmd("test", "logan", "play slackbot black") 162 | resp := cmd.Response() 163 | g, err := cmd.Game() 164 | if err != nil { 165 | t.Fatal(err) 166 | } 167 | if len(g.Moves()) != 1 { 168 | b, _ := json.Marshal(resp) 169 | t.Fatal(string(b)) 170 | } 171 | } 172 | 173 | func TestPlayStockfishWithSkillLevel(t *testing.T) { 174 | SetStockfishPath("../stockfish") 175 | newSlashCmd("test", "logan", "play slackbot:5").Response() 176 | cmd := newSlashCmd("test", "logan", "move e4") 177 | resp := cmd.Response() 178 | g, err := cmd.Game() 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | if len(g.Moves()) != 2 { 183 | b, _ := json.Marshal(resp) 184 | t.Fatal(string(b)) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /internal/stockfish/stockfish-7-64-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/internal/stockfish/stockfish-7-64-linux -------------------------------------------------------------------------------- /internal/stockfish/stockfish-7-64-mac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/internal/stockfish/stockfish-7-64-mac -------------------------------------------------------------------------------- /internal/stockfish/stockfish.go: -------------------------------------------------------------------------------- 1 | package stockfish 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "runtime" 10 | "strings" 11 | 12 | "github.com/loganjspears/chess" 13 | ) 14 | 15 | // Move returns a move from the Stockfish chess engine. skillLvl is the skill 16 | // level of the engine and must be [0,20]. execPath should be the path to stockfish 17 | // directory. An error is returned if there an issue communicating with the stockfish executable. 18 | func Move(g *chess.Game, skillLvl int, execPath string) (*chess.Move, error) { 19 | if skillLvl < 0 || skillLvl > 20 { 20 | return nil, errors.New("stockfish: skill level must be between 0 and 20") 21 | } 22 | buf := bytes.NewBuffer([]byte{}) 23 | cmd := exec.Command(execPath+"/stockfish.sh", fmt.Sprint(skillLvl), g.FEN(), executable(execPath)) 24 | cmd.Stdout = buf 25 | cmd.Stderr = os.Stderr 26 | if err := cmd.Run(); err != nil { 27 | return nil, errors.New("stockfish: error occured when running stockfish command " + err.Error()) 28 | } 29 | 30 | output := buf.String() 31 | moveTxt := parseOutput(output) 32 | if moveTxt == "" { 33 | return nil, errors.New("stockfish: couldn't parse stockfish output - " + output) 34 | } 35 | 36 | move := getMoveFromText(g, moveTxt) 37 | if move == nil { 38 | return nil, errors.New("stockfish: couldn't parse stockfish move - " + moveTxt) 39 | } 40 | return move, nil 41 | } 42 | 43 | func getMoveFromText(g *chess.Game, moveTxt string) *chess.Move { 44 | moveTxt = strings.Replace(moveTxt, "x", "", -1) 45 | isValidLength := (len(moveTxt) == 4 || len(moveTxt) == 5) 46 | if !isValidLength { 47 | return nil 48 | } 49 | s1Txt := moveTxt[0:2] 50 | s2Txt := moveTxt[2:4] 51 | promoTxt := "" 52 | if len(moveTxt) == 5 { 53 | promoTxt = moveTxt[4:5] 54 | } 55 | for _, m := range g.ValidMoves() { 56 | if m.S1().String() == s1Txt && 57 | m.S2().String() == s2Txt && 58 | promoTxt == m.Promo().String() { 59 | return m 60 | } 61 | } 62 | return nil 63 | } 64 | 65 | // searching for format: "bestmove e2e4 ponder e7e6" 66 | func parseOutput(output string) string { 67 | output = strings.Replace(output, "\n", " ", -1) 68 | words := strings.Split(output, " ") 69 | next := false 70 | for _, word := range words { 71 | if next { 72 | return word 73 | } 74 | if word == "bestmove" { 75 | next = true 76 | } 77 | } 78 | return "" 79 | } 80 | 81 | func executable(path string) string { 82 | if runtime.GOOS == "darwin" { 83 | return path + "/stockfish-7-64-mac" 84 | } 85 | return path + "/stockfish-7-64-linux" 86 | } 87 | -------------------------------------------------------------------------------- /internal/stockfish/stockfish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ( 3 | echo "setoption name Skill Level $1" ; 4 | echo "position fen $2" ; 5 | echo "go movetime 950" ; 6 | sleep 1 7 | ) | $3 -------------------------------------------------------------------------------- /internal/stockfish/stockfish_mac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ( 3 | echo "setoption name Skill Level $1" ; 4 | echo "position fen $2" ; 5 | echo "go movetime 1000" ; 6 | sleep 1 7 | ) | ./stockfish-7-64-mac -------------------------------------------------------------------------------- /internal/stockfish/stockfish_test.go: -------------------------------------------------------------------------------- 1 | package stockfish 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/loganjspears/chess" 7 | ) 8 | 9 | func TestStockfish(t *testing.T) { 10 | if _, err := Move(chess.NewGame(), 5, "."); err != nil { 11 | t.Fatal(err) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /screen_shots/screen_shot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/screen_shots/screen_shot_1.png -------------------------------------------------------------------------------- /screen_shots/screen_shot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/screen_shots/screen_shot_2.png -------------------------------------------------------------------------------- /screen_shots/screen_shot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/screen_shots/screen_shot_3.png -------------------------------------------------------------------------------- /vendor/github.com/ajstarks/svgo/LICENSE: -------------------------------------------------------------------------------- 1 | The contents of this repository are Licensed under 2 | the Creative Commons Attribution 3.0 license as described in 3 | http://creativecommons.org/licenses/by/3.0/us/ 4 | -------------------------------------------------------------------------------- /vendor/github.com/ajstarks/svgo/README.markdown: -------------------------------------------------------------------------------- 1 | #SVGo: A Go library for SVG generation# 2 | 3 | The library generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (). 4 | Output goes to the specified io.Writer. 5 | 6 | ## Supported SVG elements and functions ## 7 | 8 | ### Shapes, lines, text 9 | 10 | circle, ellipse, polygon, polyline, rect (including roundrects), line, text 11 | 12 | ### Paths 13 | 14 | general, arc, cubic and quadratic bezier paths, 15 | 16 | ### Image and Gradients 17 | 18 | image, linearGradient, radialGradient, 19 | 20 | ### Transforms ### 21 | 22 | translate, rotate, scale, skewX, skewY 23 | 24 | ### Filter Effects 25 | 26 | filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting, 27 | feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight, 28 | feSpecularLighting, feSpotLight,feTile, feTurbulence 29 | 30 | 31 | ### Metadata elements ### 32 | 33 | desc, defs, g (style, transform, id), marker, mask, pattern, title, (a)ddress, link, script, use 34 | 35 | ## Building and Usage ## 36 | 37 | See svgdef.[svg|png|pdf] for a graphical view of the function calls 38 | 39 | 40 | Usage: (assuming GOPATH is set) 41 | 42 | go get github.com/ajstarks/svgo 43 | go install github.com/ajstarks/svgo/... 44 | 45 | 46 | You can use godoc to browse the documentation from the command line: 47 | 48 | $ godoc github.com/ajstarks/svgo 49 | 50 | 51 | a minimal program, to generate SVG to standard output. 52 | 53 | package main 54 | 55 | import ( 56 | "github.com/ajstarks/svgo" 57 | "os" 58 | ) 59 | 60 | func main() { 61 | width := 500 62 | height := 500 63 | canvas := svg.New(os.Stdout) 64 | canvas.Start(width, height) 65 | canvas.Circle(width/2, height/2, 100) 66 | canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white") 67 | canvas.End() 68 | } 69 | 70 | Drawing in a web server: (http://localhost:2003/circle) 71 | 72 | package main 73 | 74 | import ( 75 | "log" 76 | "github.com/ajstarks/svgo" 77 | "net/http" 78 | ) 79 | 80 | func main() { 81 | http.Handle("/circle", http.HandlerFunc(circle)) 82 | err := http.ListenAndServe(":2003", nil) 83 | if err != nil { 84 | log.Fatal("ListenAndServe:", err) 85 | } 86 | } 87 | 88 | func circle(w http.ResponseWriter, req *http.Request) { 89 | w.Header().Set("Content-Type", "image/svg+xml") 90 | s := svg.New(w) 91 | s.Start(500, 500) 92 | s.Circle(250, 250, 125, "fill:none;stroke:black") 93 | s.End() 94 | } 95 | 96 | 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. 97 | 98 | ### Graphics Sketching with SVGo and svgplay ### 99 | 100 | Combined with the svgplay command, SVGo can be used to "sketch" with code in a browser. 101 | 102 | To use svgplay and SVGo, first go to a directory with your code, and run: 103 | 104 | $ svgplay 105 | 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 ☠ ☠ ☠ 106 | 107 | Next open your browser to the svgplay server you just started. 108 | svgplay only listens on localhost, and uses port 1999 (guess which year SVG was first introduced) by default 109 | 110 | http://localhost:1999/ 111 | 112 | Enter your code in the textarea, and when you are ready to run press Shift--Enter. The code will be compiled, with the results 113 | 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. 114 | 115 | 116 | If you want to sketch with an existing file, enter its URL: 117 | 118 | http://localhost:1999/foo.go 119 | 120 | ![SVGplay](https://farm4.staticflickr.com/3859/14322978157_31c0114850.jpg) 121 | 122 | 123 | ### SVGo Papers and presentations ### 124 | 125 | * SVGo paper from SVGOpen 2011 126 | 127 | * Programming Pictures with SVGo 128 | 129 | * SVGo Workshop 130 | 131 | 132 | ### Tutorial Video ### 133 | 134 | A video describing how to use the package can be seen on YouTube at 135 | 136 | ## Package contents ## 137 | 138 | * svg.go: Library 139 | * newsvg: Coding template command 140 | * svgdef: Creates a SVG representation of the API 141 | * android: The Android logo 142 | * bubtrail: Bubble trails 143 | * bulletgraph: Bullet Graphs (via Stephen Few) 144 | * colortab: Display SVG named colors with RGB values 145 | * compx: Component diagrams 146 | * flower: Random "flowers" 147 | * fontcompare: Compare two fonts 148 | * f50: Get 50 photos from Flickr based on a query 149 | * fe: Filter effects 150 | * funnel: Funnel from transparent circles 151 | * gradient: Linear and radial gradients 152 | * html5logo: HTML5 logo with draggable elements 153 | * imfade: Show image fading 154 | * lewitt: Version of Sol Lewitt's Wall Drawing 91 155 | * ltr: Layer Tennis Remixes 156 | * marker: Test markers 157 | * paths: Demonstrate SVG paths 158 | * pattern: Test patterns 159 | * planets: Show the scale of the Solar system 160 | * pmap: Proportion maps 161 | * randcomp: Compare random number generators 162 | * richter: Gerhard Richter's 256 colors 163 | * rl: Random lines (port of a Processing demo) 164 | * skewabc: Skew ABC 165 | * stockproduct: Visualize product and stock prices 166 | * svgopher: SVGo Mascot 167 | * svgplay: SVGo sketching server 168 | * svgplot: Plot data 169 | * svgrid: Compose SVG files in a grid 170 | * tsg: Twitter Search Grid 171 | * tumblrgrid: Tumblr picture grid 172 | * turbulence: Turbulence filter effect 173 | * vismem: Visualize data from files 174 | * webfonts: "Hello, World" with Google Web Fonts 175 | * websvg: Generate SVG as a web server 176 | 177 | 178 | ## Functions and types ## 179 | 180 | Many functions use x, y to specify an object's location, and w, h to specify the object's width and height. 181 | Where applicable, a final optional argument specifies the style to be applied to the object. 182 | The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a 183 | series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: ) 184 | 185 | The Offcolor type: 186 | 187 | type Offcolor struct { 188 | Offset uint8 189 | Color string 190 | Opacity float 191 | } 192 | 193 | is used to specify the offset, color, and opacity of stop colors in linear and radial gradients 194 | 195 | The Filterspec type: 196 | 197 | type Filterspec struct { 198 | In string 199 | In2 string 200 | Result string 201 | } 202 | 203 | is used to specify inputs and results for filter effects 204 | 205 | 206 | ### Structure, Scripting, Metadata, Transformation and Links ### 207 | 208 | New(w io.Writer) *SVG 209 | Constructor, Specify the output destination. 210 | 211 | Start(w int, h int, attributes ...string) 212 | begin the SVG document with the width w and height h. Optionally add additional elememts 213 | (such as additional namespaces or scripting events) 214 | 215 | 216 | Startview(w, h, minx, miny, vw, vh int) 217 | begin the SVG document with the width w, height h, with a viewBox at minx, miny, vw, vh. 218 | 219 | 220 | Startunit(w int, h int, unit string, ns ...string) 221 | begin the SVG document, with width and height in the specified units. Optionally add additional elememts 222 | (such as additional namespaces or scripting events) 223 | 224 | 225 | 226 | Startpercent(w int, h int, ns ...string) 227 | begin the SVG document, with width and height in percent. Optionally add additional elememts 228 | (such as additional namespaces or scripting events) 229 | 230 | 231 | 232 | StartviewUnit(w, h int, unit string, minx, miny, vw, vh int) 233 | begin the SVG document with the width w, height h, in the specified unit, with a viewBox at minx, miny, vw, vh. 234 | 235 | 236 | End() 237 | end the SVG document 238 | 239 | Script(scriptype string, data ...string) 240 | Script defines a script with a specified type, (for example "application/javascript"). 241 | if the first variadic argument is a link, use only the link reference. 242 | Otherwise, treat variadic arguments as the text of the script (marked up as CDATA). 243 | if no data is specified, simply close the script element. 244 | 245 | 246 | Group(s ...string) 247 | begin a group, with arbitrary attributes 248 | 249 | 250 | Gstyle(s string) 251 | begin a group, with the specified style. 252 | 253 | 254 | Gid(s string) 255 | begin a group, with the specified id. 256 | 257 | Gtransform(s string) 258 | begin a group, with the specified transform, end with Gend(). 259 | 260 | 261 | Translate(x, y int) 262 | begins coordinate translation to (x,y), end with Gend(). 263 | 264 | 265 | Scale(n float64) 266 | scales the coordinate system by n, end with Gend(). 267 | 268 | 269 | ScaleXY(x, y float64) 270 | scales the coordinate system by x, y. End with Gend(). 271 | 272 | 273 | SkewX(a float64) 274 | SkewX skews the x coordinate system by angle a, end with Gend(). 275 | 276 | 277 | SkewY(a float64) 278 | SkewY skews the y coordinate system by angle a, end with Gend(). 279 | 280 | 281 | SkewXY(ax, ay float64) 282 | SkewXY skews x and y coordinate systems by ax, ay respectively, end with Gend(). 283 | 284 | 285 | Rotate(r float64) 286 | rotates the coordinate system by r degrees, end with Gend(). 287 | 288 | 289 | TranslateRotate(x, y int, r float64) 290 | translates the coordinate system to (x,y), then rotates to r degrees, end with Gend(). 291 | 292 | RotateTranslate(x, y int, r float64) 293 | rotates the coordinate system r degrees, then translates to (x,y), end with Gend(). 294 | 295 | Gend() 296 | end the group (must be paired with Gstyle, Gtransform, Gid). 297 | 298 | ClipPath(s ...string) 299 | Begin a ClipPath 300 | 301 | 302 | ClipEnd() 303 | End a ClipPath 304 | 305 | 306 | Def() 307 | begin a definition block. 308 | 309 | 310 | DefEnd() 311 | end a definition block. 312 | 313 | Marker(id string, x, y, w, h int, s ...string) 314 | define a marker 315 | 316 | 317 | 318 | MarkerEnd() 319 | end a marker 320 | 321 | 322 | Mask(id string, x int, y int, w int, h int, s ...string) 323 | creates a mask with a specified id, dimension, and optional style. 324 | 325 | 326 | MaskEnd() 327 | ends the Mask element. 328 | 329 | 330 | Pattern(id string, x, y, width, height int, putype string, s ...string) 331 | define a Pattern with the specified dimensions, the putype can be either "user" or "obj", which sets the patternUnits 332 | attribute to be either userSpaceOnUse or objectBoundingBox. 333 | 334 | 335 | Desc(s string) 336 | specify the text of the description. 337 | 338 | 339 | Title(s string) 340 | specify the text of the title. 341 | 342 | 343 | Link(href string, title string) 344 | begin a link named "href", with the specified title. 345 | 346 | 347 | LinkEnd() 348 | end the link. 349 | 350 | Use(x int, y int, link string, s ...string) 351 | place the object referenced at link at the location x, y. 352 | 353 | 354 | ### Shapes ### 355 | 356 | Circle(x int, y int, r int, s ...string) 357 | draw a circle, centered at x,y with radius r. 358 | 359 | 360 | ![Circle](http://farm5.static.flickr.com/4144/5187953823_01a1741489_m.jpg) 361 | 362 | Ellipse(x int, y int, w int, h int, s ...string) 363 | draw an ellipse, centered at x,y with radii w, and h. 364 | 365 | 366 | ![Ellipse](http://farm2.static.flickr.com/1271/5187953773_a9d1fc406c_m.jpg) 367 | 368 | Polygon(x []int, y []int, s ...string) 369 | draw a series of line segments using an array of x, y coordinates. 370 | 371 | 372 | ![Polygon](http://farm2.static.flickr.com/1006/5187953873_337dc26597_m.jpg) 373 | 374 | Rect(x int, y int, w int, h int, s ...string) 375 | draw a rectangle with upper left-hand corner at x,y, with width w, and height h. 376 | 377 | 378 | ![Rect](http://farm2.static.flickr.com/1233/5188556032_86c90e354b_m.jpg) 379 | 380 | CenterRect(x int, y int, w int, h int, s ...string) 381 | draw a rectangle with its center at x,y, with width w, and height h. 382 | 383 | Roundrect(x int, y int, w int, h int, rx int, ry int, s ...string) 384 | draw a rounded rectangle with upper the left-hand corner at x,y, 385 | with width w, and height h. The radii for the rounded portion 386 | is specified by rx (width), and ry (height). 387 | 388 | ![Roundrect](http://farm2.static.flickr.com/1275/5188556120_e2a9998fee_m.jpg) 389 | 390 | Square(x int, y int, s int, style ...string) 391 | draw a square with upper left corner at x,y with sides of length s. 392 | 393 | ![Square](http://farm5.static.flickr.com/4110/5187953659_54dcce242e_m.jpg) 394 | 395 | ### Paths ### 396 | 397 | Path(p string, s ...style) 398 | draw the arbitrary path as specified in p, according to the style specified in s. 399 | 400 | 401 | Arc(sx int, sy int, ax int, ay int, r int, large bool, sweep bool, ex int, ey int, s ...string) 402 | draw an elliptical arc beginning coordinate at sx,sy, ending coordinate at ex, ey 403 | width and height of the arc are specified by ax, ay, the x axis rotation is r 404 | 405 | if sweep is true, then the arc will be drawn in a "positive-angle" direction (clockwise), 406 | if false, the arc is drawn counterclockwise. 407 | 408 | if large is true, the arc sweep angle is greater than or equal to 180 degrees, 409 | otherwise the arc sweep is less than 180 degrees. 410 | 411 | 412 | ![Arc](http://farm2.static.flickr.com/1300/5188556148_df1a176074_m.jpg) 413 | 414 | 415 | 416 | Bezier(sx int, sy int, cx int, cy int, px int, py int, ex int, ey int, s ...string) 417 | draw a cubic bezier curve, beginning at sx,sy, ending at ex,ey 418 | with control points at cx,cy and px,py. 419 | 420 | 421 | ![Bezier](http://farm2.static.flickr.com/1233/5188556246_a03e67d013.jpg) 422 | 423 | 424 | 425 | Qbezier(sx int, sy int, cx int, cy int, ex int, ey int, tx int, ty int, s ...string) 426 | draw a quadratic bezier curve, beginning at sx, sy, ending at tx,ty 427 | with control points are at cx,cy, ex,ey. 428 | 429 | 430 | ![Qbezier](http://farm2.static.flickr.com/1018/5187953917_9a43cf64fb.jpg) 431 | 432 | 433 | Qbez(sx int, sy int, cx int, cy int, ex int, ey int, s...string) 434 | draws a quadratic bezier curver, with optional style beginning at sx,sy, ending at ex, sy 435 | with the control point at cx, cy. 436 | 437 | 438 | ![Qbez](http://farm6.static.flickr.com/5176/5569879349_5f726aab5e.jpg) 439 | 440 | ### Lines ### 441 | 442 | Line(x1 int, y1 int, x2 int, y2 int, s ...string) 443 | draw a line segment between x1,y1 and x2,y2. 444 | 445 | 446 | ![Line](http://farm5.static.flickr.com/4154/5188556080_0be19da0bc.jpg) 447 | 448 | 449 | Polyline(x []int, y []int, s ...string) 450 | draw a polygon using coordinates specified in x,y arrays. 451 | 452 | 453 | ![Polyline](http://farm2.static.flickr.com/1266/5188556384_a863273a69.jpg) 454 | 455 | ### Image and Text ### 456 | 457 | Image(x int, y int, w int, h int, link string, s ...string) 458 | place at x,y (upper left hand corner), the image with width w, and height h, referenced at link. 459 | 460 | 461 | ![Image](http://farm5.static.flickr.com/4058/5188556346_e5ce3dcbc2_m.jpg) 462 | 463 | Text(x int, y int, t string, s ...string) 464 | Place the specified text, t at x,y according to the style specified in s. 465 | 466 | 467 | Textlines(x, y int, s []string, size, spacing int, fill, align string) 468 | Places lines of text in s, starting at x,y, at the specified size, fill, and alignment, and spacing. 469 | 470 | Textpath(t string, pathid string, s ...string) 471 | places optionally styled text along a previously defined path. 472 | 473 | ![Image](http://farm4.static.flickr.com/3149/5694580737_4b291df768_m.jpg) 474 | 475 | ### Color ### 476 | 477 | RGB(r int, g int, b int) string 478 | creates a style string for the fill color designated 479 | by the (r)ed, g(reen), (b)lue components. 480 | 481 | 482 | RGBA(r int, g int, b int, a float64) string 483 | as above, but includes the color's opacity as a value 484 | between 0.0 (fully transparent) and 1.0 (opaque). 485 | 486 | ### Gradients ### 487 | 488 | LinearGradient(id string, x1, y1, x2, y2 uint8, sc []Offcolor) 489 | constructs a linear color gradient identified by id, 490 | along the vector defined by (x1,y1), and (x2,y2). 491 | The stop color sequence defined in sc. Coordinates are expressed as percentages. 492 | 493 | ![LinearGradient](http://farm5.static.flickr.com/4153/5187954033_3972f63fa9.jpg) 494 | 495 | RadialGradient(id string, cx, cy, r, fx, fy uint8, sc []Offcolor) 496 | constructs a radial color gradient identified by id, 497 | centered at (cx,cy), with a radius of r. 498 | (fx, fy) define the location of the focal point of the light source. 499 | The stop color sequence defined in sc. 500 | Coordinates are expressed as percentages. 501 | 502 | 503 | ![RadialGradient](http://farm2.static.flickr.com/1302/5187954065_7ddba7b819.jpg) 504 | 505 | ### Filter Effects ### 506 | 507 | Filter(id string, s ...string) 508 | Filter begins a filter set 509 | Standard reference: 510 | 511 | Fend() 512 | Fend ends a filter set 513 | Standard reference: 514 | 515 | FeBlend(fs Filterspec, mode string, s ...string) 516 | FeBlend specifies a Blend filter primitive 517 | Standard reference: 518 | 519 | FeColorMatrix(fs Filterspec, values [20]float64, s ...string) 520 | FeColorMatrix specifies a color matrix filter primitive, with matrix values 521 | Standard reference: 522 | 523 | FeColorMatrixHue(fs Filterspec, value float64, s ...string) 524 | FeColorMatrix specifies a color matrix filter primitive, with hue values 525 | Standard reference: 526 | 527 | FeColorMatrixSaturate(fs Filterspec, value float64, s ...string) 528 | FeColorMatrix specifies a color matrix filter primitive, with saturation values 529 | Standard reference: 530 | 531 | FeColorMatrixLuminence(fs Filterspec, s ...string) 532 | FeColorMatrix specifies a color matrix filter primitive, with luminence values 533 | Standard reference: 534 | 535 | FeComponentTransfer() 536 | FeComponentTransfer begins a feComponent filter Element> 537 | Standard reference: 538 | 539 | FeCompEnd() 540 | FeCompEnd ends a feComponent filter Element> 541 | 542 | FeComposite(fs Filterspec, operator string, k1, k2, k3, k4 int, s ...string) 543 | FeComposite specifies a feComposite filter primitive 544 | Standard reference: 545 | 546 | FeConvolveMatrix(fs Filterspec, matrix [9]int, s ...string) 547 | FeConvolveMatrix specifies a feConvolveMatrix filter primitive 548 | Standard referencd: 549 | 550 | 551 | FeDiffuseLighting(fs Filterspec, scale, constant float64, s ...string) 552 | FeDiffuseLighting specifies a diffuse lighting filter primitive, 553 | a container for light source Element>s, end with DiffuseEnd() 554 | 555 | FeDiffEnd() 556 | FeDiffuseEnd ends a diffuse lighting filter primitive container 557 | Standard reference: 558 | 559 | 560 | FeDisplacementMap(fs Filterspec, scale float64, xchannel, ychannel string, s ...string) 561 | FeDisplacementMap specifies a feDisplacementMap filter primitive 562 | Standard reference: 563 | 564 | FeDistantLight(fs Filterspec, azimuth, elevation float64, s ...string) 565 | FeDistantLight specifies a feDistantLight filter primitive 566 | Standard reference: 567 | 568 | FeFlood(fs Filterspec, color string, opacity float64, s ...string) 569 | FeFlood specifies a flood filter primitive 570 | Standard reference: 571 | 572 | FeFuncLinear(channel string, slope, intercept float64) 573 | FeFuncLinear is the linear form of feFunc 574 | Standard reference: 575 | 576 | FeFuncGamma(channel, amplitude, exponent, offset float64) 577 | FeFuncGamma is the gamma curve form of feFunc 578 | Standard reference: 579 | 580 | FeFuncTable(channel string, tv []float64) 581 | FeFuncGamma is the form of feFunc using a table of values 582 | Standard reference: 583 | 584 | FeFuncDiscrete(channel string, tv []float64) 585 | FeFuncGamma is the form of feFunc using discrete values 586 | Standard reference: 587 | 588 | FeGaussianBlur(fs Filterspec, stdx, stdy float64, s ...string) 589 | FeGaussianBlur specifies a Gaussian Blur filter primitive 590 | Standard reference: 591 | 592 | FeImage(href string, result string, s ...string) 593 | FeImage specifies a feImage filter primitive 594 | Standard reference: 595 | 596 | FeMerge(nodes []string, s ...string) 597 | FeMerge specifies a feMerge filter primitive, containing feMerge Element>s 598 | Standard reference: 599 | 600 | FeMorphology(fs Filterspec, operator string, xradius, yradius float64, s ...string) 601 | FeMorphologyLight specifies a feMorphologyLight filter primitive 602 | Standard reference: 603 | 604 | FeOffset(fs Filterspec, dx, dy int, s ...string) 605 | FeOffset specifies the feOffset filter primitive 606 | Standard reference: 607 | 608 | FePointLight(x, y, z float64, s ...string) 609 | FePointLight specifies a fePpointLight filter primitive 610 | Standard reference: 611 | 612 | FeSpecularLighting(fs Filterspec, scale, constant float64, exponent int, color string, s ...string) 613 | FeSpecularLighting specifies a specular lighting filter primitive, 614 | a container for light source elements, end with SpecularEnd() 615 | 616 | 617 | FeSpecEnd() 618 | FeSpecularEnd ends a specular lighting filter primitive container 619 | Standard reference: 620 | 621 | 622 | FeSpotLight(fs Filterspec, x, y, z, px, py, pz float64, s ...string) 623 | FeSpotLight specifies a feSpotLight filter primitive 624 | Standard reference: 625 | 626 | FeTile(fs Filterspec, in string, s ...string) 627 | FeTile specifies the tile utility filter primitive 628 | Standard reference: 629 | 630 | 631 | FeTurbulence(fs Filterspec, ftype string, bfx, bfy float64, octaves int, seed int64, stitch bool, s ...string) 632 | FeTurbulence specifies a turbulence filter primitive 633 | Standard reference: 634 | 635 | ### Filter convenience functions (modeled on CSS filter effects) ### 636 | 637 | Blur(p float64) 638 | Blur function by standard deviation 639 | 640 | Brightness(p float64) 641 | Brightness function (0-100) 642 | 643 | Grayscale() 644 | Apply a grayscale filter to the image 645 | 646 | HueRotate(a float64) 647 | Rotate Hues (0-360 degrees) 648 | 649 | Invert() 650 | Invert the image's colors 651 | 652 | Saturate(p float64) 653 | Percent saturation, 0 is grayscale 654 | 655 | Sepia() 656 | Apply sepia tone 657 | 658 | 659 | ### Utility ### 660 | 661 | Grid(x int, y int, w int, h int, n int, s ...string) 662 | draws a grid of straight lines starting at x,y, with a width w, and height h, and a size of n. 663 | 664 | ![Grid](http://farm5.static.flickr.com/4133/5190957924_7a31d0db34.jpg) 665 | 666 | ### Credits ### 667 | 668 | Thanks to Jonathan Wright for the io.Writer update. 669 | -------------------------------------------------------------------------------- /vendor/github.com/ajstarks/svgo/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package svg generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (). 3 | Output goes to the specified io.Writer. 4 | 5 | Supported SVG elements and functions 6 | 7 | Shapes, lines, text 8 | 9 | circle, ellipse, polygon, polyline, rect (including roundrects), line, text 10 | 11 | Paths 12 | 13 | general, arc, cubic and quadratic bezier paths, 14 | 15 | Image and Gradients 16 | 17 | image, linearGradient, radialGradient, 18 | 19 | Transforms 20 | 21 | translate, rotate, scale, skewX, skewY 22 | 23 | Filter Effects 24 | 25 | filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting, 26 | feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight, 27 | feSpecularLighting, feSpotLight,feTile, feTurbulence 28 | 29 | 30 | Metadata elements 31 | 32 | desc, defs, g (style, transform, id), mask, marker, pattern, title, (a)ddress, link, script, use 33 | 34 | Usage: (assuming GOPATH is set) 35 | 36 | go get github.com/ajstarks/svgo 37 | go install github.com/ajstarks/svgo/... 38 | 39 | 40 | You can use godoc to browse the documentation from the command line: 41 | 42 | $ godoc github.com/ajstarks/svgo 43 | 44 | 45 | a minimal program, to generate SVG to standard output. 46 | 47 | package main 48 | 49 | import ( 50 | "github.com/ajstarks/svgo" 51 | "os" 52 | ) 53 | 54 | func main() { 55 | width := 500 56 | height := 500 57 | canvas := svg.New(os.Stdout) 58 | canvas.Start(width, height) 59 | canvas.Circle(width/2, height/2, 100) 60 | canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white") 61 | canvas.End() 62 | } 63 | 64 | Drawing in a web server: (http://localhost:2003/circle) 65 | 66 | package main 67 | 68 | import ( 69 | "log" 70 | "github.com/ajstarks/svgo" 71 | "net/http" 72 | ) 73 | 74 | func main() { 75 | http.Handle("/circle", http.HandlerFunc(circle)) 76 | err := http.ListenAndServe(":2003", nil) 77 | if err != nil { 78 | log.Fatal("ListenAndServe:", err) 79 | } 80 | } 81 | 82 | func circle(w http.ResponseWriter, req *http.Request) { 83 | w.Header().Set("Content-Type", "image/svg+xml") 84 | s := svg.New(w) 85 | s.Start(500, 500) 86 | s.Circle(250, 250, 125, "fill:none;stroke:black") 87 | s.End() 88 | } 89 | 90 | Functions and types 91 | 92 | Many functions use x, y to specify an object's location, and w, h to specify the object's width and height. 93 | Where applicable, a final optional argument specifies the style to be applied to the object. 94 | The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a 95 | series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: ) 96 | 97 | The Offcolor type: 98 | 99 | type Offcolor struct { 100 | Offset uint8 101 | Color string 102 | Opacity float 103 | } 104 | 105 | is used to specify the offset, color, and opacity of stop colors in linear and radial gradients 106 | 107 | The Filterspec type: 108 | 109 | type Filterspec struct { 110 | In string 111 | In2 string 112 | Result string 113 | } 114 | 115 | is used to specify inputs and results for filter effects 116 | 117 | */ 118 | package svg 119 | -------------------------------------------------------------------------------- /vendor/github.com/ajstarks/svgo/gophercolor128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/vendor/github.com/ajstarks/svgo/gophercolor128x128.png -------------------------------------------------------------------------------- /vendor/github.com/ajstarks/svgo/newsvg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if test $# -lt 1 4 | then 5 | echo "specify a file" 6 | exit 2 7 | fi 8 | 9 | if test ! -f $1 10 | then 11 | cat < $1 12 | package main 13 | 14 | import ( 15 | "github.com/ajstarks/svgo" 16 | "os" 17 | ) 18 | 19 | var ( 20 | width = 500 21 | height = 500 22 | canvas = svg.New(os.Stdout) 23 | ) 24 | 25 | func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } 26 | 27 | 28 | func main() { 29 | canvas.Start(width, height) 30 | background(255) 31 | 32 | // your code here 33 | 34 | canvas.Grid(0, 0, width, height, 10, "stroke:black;opacity:0.1") 35 | canvas.End() 36 | } 37 | ! 38 | fi 39 | $EDITOR $1 40 | -------------------------------------------------------------------------------- /vendor/github.com/ajstarks/svgo/svgdef.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/vendor/github.com/ajstarks/svgo/svgdef.pdf -------------------------------------------------------------------------------- /vendor/github.com/ajstarks/svgo/svgdef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/vendor/github.com/ajstarks/svgo/svgdef.png -------------------------------------------------------------------------------- /vendor/github.com/ajstarks/svgo/svgdef.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | Object Definitions 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | x, y 24 | 25 | w 26 | Square(x, y, w int, style ...string) 27 | 28 | 29 | 30 | x, y 31 | 32 | h 33 | w 34 | Rect(x, y, w, h int, style ...string) 35 | 36 | 37 | 38 | x, y 39 | 40 | h 41 | w 42 | CenterRect(x, y, w, h int, style ...string) 43 | 44 | 45 | 46 | x, y 47 | 48 | h 49 | w 50 | 51 | 52 | ry 53 | rx 54 | Roundrect(x, y, w, h, rx, ry int, style ...string) 55 | 56 | 57 | 58 | x, y 59 | 60 | x, y 61 | 62 | x, y 63 | 64 | x, y 65 | 66 | x, y 67 | 68 | x, y 69 | 70 | Polygon(x, y []int, style ...string) 71 | 72 | 73 | 74 | 75 | x, y 76 | 77 | 78 | r 79 | Circle(x, y, r int, style ...string) 80 | 81 | 82 | 83 | 84 | 85 | x, y 86 | 87 | 88 | 89 | rx 90 | ry 91 | Ellipse(x, y, rx, ry int, style ...string) 92 | 93 | 94 | 95 | 96 | x1, y1 97 | 98 | x2, y2 99 | 100 | Line(x1, y1, x2, y2 int, style ...string) 101 | 102 | 103 | 104 | x, y 105 | 106 | x, y 107 | 108 | x, y 109 | 110 | x, y 111 | 112 | Polyline(x, y []int, style ...string) 113 | 114 | 115 | 116 | sx, sy 117 | 118 | ex, ey 119 | 120 | Arc(sx, sy, ax, ay, r int, lflag, sflag bool, ex, ey int, style ...string) 121 | 122 | 123 | 124 | 125 | 126 | x, y 127 | Path(s string, style ...string) 128 | 129 | 130 | 131 | sx, sy 132 | 133 | cx, cy 134 | 135 | ex, ey 136 | 137 | Qbez(sx, sy, cx, cy, ex, ey int, style ...string) 138 | 139 | 140 | 141 | sx, sy 142 | 143 | cx, cy 144 | 145 | px, py 146 | 147 | ex, ey 148 | 149 | Bezier(sx, sy, cx, cy, px, py, ex, ey int, style ...string) 150 | 151 | 152 | 153 | x, y 154 | 155 | h 156 | w 157 | 158 | Image(x, y, w, h, int path string, style ...string) 159 | 160 | 161 | 162 | 163 | x1%, y1% 164 | 165 | x2%, y2% 166 | LinearGradient(s string, x1, y1, x2, y2 uint8, oc []Offcolor) 167 | 168 | 169 | 170 | 171 | cx%, cy% 172 | 173 | fx%, fy% 174 | RadialGradient(s string, cx, cy, r, fx, fy uint8, oc []Offcolor) 175 | 176 | 177 | 178 | 0, 0 179 | 180 | x, y 181 | Translate(x, y int) 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | x, y 190 | h 191 | w 192 | n 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | Grid(x, y, w, h, n int, style ...string) 210 | 211 | 212 | 213 | x, y 214 | hello, this is SVG 215 | Text(x, y int, s string, style ...string) 216 | 217 | 218 | 219 | 0, 0 220 | 221 | 222 | 223 | 224 | Scale(n float64) 225 | 226 | 227 | 228 | 0, 0 229 | 230 | 231 | 232 | 233 | ScaleXY(x, y float64) 234 | 235 | 236 | 237 | 0, 0 238 | 239 | 240 | 241 | 242 | SkewX(a float64) 243 | 244 | 245 | 246 | 0, 0 247 | 248 | 249 | 250 | 251 | SkewY(a float64) 252 | 253 | 254 | 255 | 0, 0 256 | 257 | 258 | 259 | 260 | SkewXY(x, y float64) 261 | 262 | 263 | 264 | 0, 0 265 | Rotate(r float64) 266 | 267 | 268 | r 269 | 270 | 271 | 272 | 273 | 274 | It's "fine" & "dandy" to draw text along a path 275 | Textpath(s, pathid string, style ...string) 276 | 277 | 278 | 279 | New(w io Writer) 280 | Start(w, h int, options ...string)/End() 281 | Startview(w, h, minx, miny, vw, vh int) 282 | Group(s ...string)/Gend() 283 | Gstyle(s string)/Gend() 284 | Gtransform(s string)/Gend() 285 | Gid(id string)/Gend() 286 | ClipPath(s ...string)/ClipEnd() 287 | Def()/DefEnd() 288 | Marker()/MarkerEnd() 289 | Pattern()/PatternEnd() 290 | Desc(s string) 291 | Title(s string) 292 | Script(type, data ...string) 293 | Mask(id string, x,y,w,h int, style ...string)/MaskEnd() 294 | Link(href string, title string)/LinkEnd() 295 | Use(x int, y int, link string, style ...string) 296 | 297 | 298 | specify destination 299 | begin/end the document 300 | begin/end the document with viewport 301 | begin/end group with attributes 302 | begin/end group style 303 | begin/end group transform 304 | begin/end group id 305 | begin/end clip path 306 | begin/end a defintion block 307 | begin/end markers 308 | begin/end pattern 309 | set the description element 310 | set the title element 311 | define a script 312 | begin/end mask element 313 | begin/end link to href, with a title 314 | use defined objects 315 | 316 | Textlines(x, y int, s []string, size, spacing int, fill, align string) 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | r 325 | g 326 | b 327 | -> 328 | 329 | RGB(r, g, b int) 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | alpha 339 | r 340 | g 341 | b 342 | -> 343 | 344 | RGBA(r, g, b int, opacity float64) 345 | 346 | 347 | SVG Go Library Description 348 | 349 | 350 | 351 | SVG Go Library 352 | github.com/ajstarks/svgo 353 | 354 | Object Usage 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/schema/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | go: 5 | - 1.3 6 | - 1.4 7 | - 1.5 8 | - tip 9 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/schema/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/schema/README.md: -------------------------------------------------------------------------------- 1 | schema 2 | ====== 3 | [![GoDoc](https://godoc.org/github.com/gorilla/schema?status.svg)](https://godoc.org/github.com/gorilla/schema) [![Build Status](https://travis-ci.org/gorilla/schema.png?branch=master)](https://travis-ci.org/gorilla/schema) 4 | 5 | Package gorilla/schema fills a struct with form values. 6 | 7 | ## Example 8 | 9 | Here's a quick example: we parse POST form values and then decode them into a struct: 10 | 11 | ```go 12 | // Set a Decoder instance as a package global, because it caches 13 | // meta-data about structs, and an instance can be shared safely. 14 | var decoder = schema.NewDecoder() 15 | 16 | type Person struct { 17 | Name string 18 | Phone string 19 | } 20 | 21 | func MyHandler(w http.ResponseWriter, r *http.Request) { 22 | err := r.ParseForm() 23 | 24 | if err != nil { 25 | // Handle error 26 | } 27 | 28 | person := &Person{} 29 | decoder := schema.NewDecoder() 30 | 31 | // r.PostForm is a map of our POST form values 32 | err := decoder.Decode(person, r.PostForm) 33 | 34 | if err != nil { 35 | // Handle error 36 | } 37 | 38 | // Do something with person.Name or person.Phone 39 | } 40 | ``` 41 | 42 | To define custom names for fields, use a struct tag "schema". To not populate certain fields, use a dash for the name and it will be ignored: 43 | 44 | ```go 45 | type Person struct { 46 | Name string `schema:"name"` // custom name 47 | Phone string `schema:"phone"` // custom name 48 | Admin bool `schema:"-"` // this field is never set 49 | } 50 | ``` 51 | 52 | The supported field types in the destination struct are: 53 | 54 | * bool 55 | * float variants (float32, float64) 56 | * int variants (int, int8, int16, int32, int64) 57 | * string 58 | * uint variants (uint, uint8, uint16, uint32, uint64) 59 | * struct 60 | * a pointer to one of the above types 61 | * a slice or a pointer to a slice of one of the above types 62 | 63 | Unsupported types are simply ignored, however custom types can be registered to be converted. 64 | 65 | More examples are available on the Gorilla website: http://www.gorillatoolkit.org/pkg/schema 66 | 67 | ## License 68 | 69 | BSD licensed. See the LICENSE file for details. 70 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/schema/cache.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package schema 6 | 7 | import ( 8 | "errors" 9 | "reflect" 10 | "strconv" 11 | "strings" 12 | "sync" 13 | ) 14 | 15 | var invalidPath = errors.New("schema: invalid path") 16 | 17 | // newCache returns a new cache. 18 | func newCache() *cache { 19 | c := cache{ 20 | m: make(map[reflect.Type]*structInfo), 21 | conv: make(map[reflect.Kind]Converter), 22 | regconv: make(map[reflect.Type]Converter), 23 | tag: "schema", 24 | } 25 | for k, v := range converters { 26 | c.conv[k] = v 27 | } 28 | return &c 29 | } 30 | 31 | // cache caches meta-data about a struct. 32 | type cache struct { 33 | l sync.RWMutex 34 | m map[reflect.Type]*structInfo 35 | conv map[reflect.Kind]Converter 36 | regconv map[reflect.Type]Converter 37 | tag string 38 | } 39 | 40 | // parsePath parses a path in dotted notation verifying that it is a valid 41 | // path to a struct field. 42 | // 43 | // It returns "path parts" which contain indices to fields to be used by 44 | // reflect.Value.FieldByString(). Multiple parts are required for slices of 45 | // structs. 46 | func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) { 47 | var struc *structInfo 48 | var field *fieldInfo 49 | var index64 int64 50 | var err error 51 | parts := make([]pathPart, 0) 52 | path := make([]string, 0) 53 | keys := strings.Split(p, ".") 54 | for i := 0; i < len(keys); i++ { 55 | if t.Kind() != reflect.Struct { 56 | return nil, invalidPath 57 | } 58 | if struc = c.get(t); struc == nil { 59 | return nil, invalidPath 60 | } 61 | if field = struc.get(keys[i]); field == nil { 62 | return nil, invalidPath 63 | } 64 | // Valid field. Append index. 65 | path = append(path, field.name) 66 | if field.ss { 67 | // Parse a special case: slices of structs. 68 | // i+1 must be the slice index. 69 | // 70 | // Now that struct can implements TextUnmarshaler interface, 71 | // we don't need to force the struct's fields to appear in the path. 72 | // So checking i+2 is not necessary anymore. 73 | i++ 74 | if i+1 > len(keys) { 75 | return nil, invalidPath 76 | } 77 | if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil { 78 | return nil, invalidPath 79 | } 80 | parts = append(parts, pathPart{ 81 | path: path, 82 | field: field, 83 | index: int(index64), 84 | }) 85 | path = make([]string, 0) 86 | 87 | // Get the next struct type, dropping ptrs. 88 | if field.typ.Kind() == reflect.Ptr { 89 | t = field.typ.Elem() 90 | } else { 91 | t = field.typ 92 | } 93 | if t.Kind() == reflect.Slice { 94 | t = t.Elem() 95 | if t.Kind() == reflect.Ptr { 96 | t = t.Elem() 97 | } 98 | } 99 | } else if field.typ.Kind() == reflect.Ptr { 100 | t = field.typ.Elem() 101 | } else { 102 | t = field.typ 103 | } 104 | } 105 | // Add the remaining. 106 | parts = append(parts, pathPart{ 107 | path: path, 108 | field: field, 109 | index: -1, 110 | }) 111 | return parts, nil 112 | } 113 | 114 | // get returns a cached structInfo, creating it if necessary. 115 | func (c *cache) get(t reflect.Type) *structInfo { 116 | c.l.RLock() 117 | info := c.m[t] 118 | c.l.RUnlock() 119 | if info == nil { 120 | info = c.create(t, nil) 121 | c.l.Lock() 122 | c.m[t] = info 123 | c.l.Unlock() 124 | } 125 | return info 126 | } 127 | 128 | // create creates a structInfo with meta-data about a struct. 129 | func (c *cache) create(t reflect.Type, info *structInfo) *structInfo { 130 | if info == nil { 131 | info = &structInfo{fields: []*fieldInfo{}} 132 | } 133 | for i := 0; i < t.NumField(); i++ { 134 | field := t.Field(i) 135 | if field.Anonymous { 136 | ft := field.Type 137 | if ft.Kind() == reflect.Ptr { 138 | ft = ft.Elem() 139 | } 140 | if ft.Kind() == reflect.Struct { 141 | c.create(ft, info) 142 | } 143 | } 144 | c.createField(field, info) 145 | } 146 | return info 147 | } 148 | 149 | // createField creates a fieldInfo for the given field. 150 | func (c *cache) createField(field reflect.StructField, info *structInfo) { 151 | alias := fieldAlias(field, c.tag) 152 | if alias == "-" { 153 | // Ignore this field. 154 | return 155 | } 156 | // Check if the type is supported and don't cache it if not. 157 | // First let's get the basic type. 158 | isSlice, isStruct := false, false 159 | ft := field.Type 160 | if ft.Kind() == reflect.Ptr { 161 | ft = ft.Elem() 162 | } 163 | if isSlice = ft.Kind() == reflect.Slice; isSlice { 164 | ft = ft.Elem() 165 | if ft.Kind() == reflect.Ptr { 166 | ft = ft.Elem() 167 | } 168 | } 169 | if ft.Kind() == reflect.Array { 170 | ft = ft.Elem() 171 | if ft.Kind() == reflect.Ptr { 172 | ft = ft.Elem() 173 | } 174 | } 175 | if isStruct = ft.Kind() == reflect.Struct; !isStruct { 176 | if conv := c.conv[ft.Kind()]; conv == nil { 177 | // Type is not supported. 178 | return 179 | } 180 | } 181 | 182 | info.fields = append(info.fields, &fieldInfo{ 183 | typ: field.Type, 184 | name: field.Name, 185 | ss: isSlice && isStruct, 186 | alias: alias, 187 | }) 188 | } 189 | 190 | // converter returns the converter for a type. 191 | func (c *cache) converter(t reflect.Type) Converter { 192 | conv := c.regconv[t] 193 | if conv == nil { 194 | conv = c.conv[t.Kind()] 195 | } 196 | return conv 197 | } 198 | 199 | // ---------------------------------------------------------------------------- 200 | 201 | type structInfo struct { 202 | fields []*fieldInfo 203 | } 204 | 205 | func (i *structInfo) get(alias string) *fieldInfo { 206 | for _, field := range i.fields { 207 | if strings.EqualFold(field.alias, alias) { 208 | return field 209 | } 210 | } 211 | return nil 212 | } 213 | 214 | type fieldInfo struct { 215 | typ reflect.Type 216 | name string // field name in the struct. 217 | ss bool // true if this is a slice of structs. 218 | alias string 219 | } 220 | 221 | type pathPart struct { 222 | field *fieldInfo 223 | path []string // path to the field: walks structs using field names. 224 | index int // struct index in slices of structs. 225 | } 226 | 227 | // ---------------------------------------------------------------------------- 228 | 229 | // fieldAlias parses a field tag to get a field alias. 230 | func fieldAlias(field reflect.StructField, tagName string) string { 231 | var alias string 232 | if tag := field.Tag.Get(tagName); tag != "" { 233 | // For now tags only support the name but let's follow the 234 | // comma convention from encoding/json and others. 235 | if idx := strings.Index(tag, ","); idx == -1 { 236 | alias = tag 237 | } else { 238 | alias = tag[:idx] 239 | } 240 | } 241 | if alias == "" { 242 | alias = field.Name 243 | } 244 | return alias 245 | } 246 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/schema/converter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package schema 6 | 7 | import ( 8 | "reflect" 9 | "strconv" 10 | ) 11 | 12 | type Converter func(string) reflect.Value 13 | 14 | var ( 15 | invalidValue = reflect.Value{} 16 | boolType = reflect.Bool 17 | float32Type = reflect.Float32 18 | float64Type = reflect.Float64 19 | intType = reflect.Int 20 | int8Type = reflect.Int8 21 | int16Type = reflect.Int16 22 | int32Type = reflect.Int32 23 | int64Type = reflect.Int64 24 | stringType = reflect.String 25 | uintType = reflect.Uint 26 | uint8Type = reflect.Uint8 27 | uint16Type = reflect.Uint16 28 | uint32Type = reflect.Uint32 29 | uint64Type = reflect.Uint64 30 | ) 31 | 32 | // Default converters for basic types. 33 | var converters = map[reflect.Kind]Converter{ 34 | boolType: convertBool, 35 | float32Type: convertFloat32, 36 | float64Type: convertFloat64, 37 | intType: convertInt, 38 | int8Type: convertInt8, 39 | int16Type: convertInt16, 40 | int32Type: convertInt32, 41 | int64Type: convertInt64, 42 | stringType: convertString, 43 | uintType: convertUint, 44 | uint8Type: convertUint8, 45 | uint16Type: convertUint16, 46 | uint32Type: convertUint32, 47 | uint64Type: convertUint64, 48 | } 49 | 50 | func convertBool(value string) reflect.Value { 51 | if value == "on" { 52 | return reflect.ValueOf(true) 53 | } else if v, err := strconv.ParseBool(value); err == nil { 54 | return reflect.ValueOf(v) 55 | } 56 | return invalidValue 57 | } 58 | 59 | func convertFloat32(value string) reflect.Value { 60 | if v, err := strconv.ParseFloat(value, 32); err == nil { 61 | return reflect.ValueOf(float32(v)) 62 | } 63 | return invalidValue 64 | } 65 | 66 | func convertFloat64(value string) reflect.Value { 67 | if v, err := strconv.ParseFloat(value, 64); err == nil { 68 | return reflect.ValueOf(v) 69 | } 70 | return invalidValue 71 | } 72 | 73 | func convertInt(value string) reflect.Value { 74 | if v, err := strconv.ParseInt(value, 10, 0); err == nil { 75 | return reflect.ValueOf(int(v)) 76 | } 77 | return invalidValue 78 | } 79 | 80 | func convertInt8(value string) reflect.Value { 81 | if v, err := strconv.ParseInt(value, 10, 8); err == nil { 82 | return reflect.ValueOf(int8(v)) 83 | } 84 | return invalidValue 85 | } 86 | 87 | func convertInt16(value string) reflect.Value { 88 | if v, err := strconv.ParseInt(value, 10, 16); err == nil { 89 | return reflect.ValueOf(int16(v)) 90 | } 91 | return invalidValue 92 | } 93 | 94 | func convertInt32(value string) reflect.Value { 95 | if v, err := strconv.ParseInt(value, 10, 32); err == nil { 96 | return reflect.ValueOf(int32(v)) 97 | } 98 | return invalidValue 99 | } 100 | 101 | func convertInt64(value string) reflect.Value { 102 | if v, err := strconv.ParseInt(value, 10, 64); err == nil { 103 | return reflect.ValueOf(v) 104 | } 105 | return invalidValue 106 | } 107 | 108 | func convertString(value string) reflect.Value { 109 | return reflect.ValueOf(value) 110 | } 111 | 112 | func convertUint(value string) reflect.Value { 113 | if v, err := strconv.ParseUint(value, 10, 0); err == nil { 114 | return reflect.ValueOf(uint(v)) 115 | } 116 | return invalidValue 117 | } 118 | 119 | func convertUint8(value string) reflect.Value { 120 | if v, err := strconv.ParseUint(value, 10, 8); err == nil { 121 | return reflect.ValueOf(uint8(v)) 122 | } 123 | return invalidValue 124 | } 125 | 126 | func convertUint16(value string) reflect.Value { 127 | if v, err := strconv.ParseUint(value, 10, 16); err == nil { 128 | return reflect.ValueOf(uint16(v)) 129 | } 130 | return invalidValue 131 | } 132 | 133 | func convertUint32(value string) reflect.Value { 134 | if v, err := strconv.ParseUint(value, 10, 32); err == nil { 135 | return reflect.ValueOf(uint32(v)) 136 | } 137 | return invalidValue 138 | } 139 | 140 | func convertUint64(value string) reflect.Value { 141 | if v, err := strconv.ParseUint(value, 10, 64); err == nil { 142 | return reflect.ValueOf(v) 143 | } 144 | return invalidValue 145 | } 146 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/schema/decoder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package schema 6 | 7 | import ( 8 | "encoding" 9 | "errors" 10 | "fmt" 11 | "reflect" 12 | "strings" 13 | ) 14 | 15 | // NewDecoder returns a new Decoder. 16 | func NewDecoder() *Decoder { 17 | return &Decoder{cache: newCache()} 18 | } 19 | 20 | // Decoder decodes values from a map[string][]string to a struct. 21 | type Decoder struct { 22 | cache *cache 23 | zeroEmpty bool 24 | ignoreUnknownKeys bool 25 | } 26 | 27 | // SetAliasTag changes the tag used to locate custom field aliases. 28 | // The default tag is "schema". 29 | func (d *Decoder) SetAliasTag(tag string) { 30 | d.cache.tag = tag 31 | } 32 | 33 | // ZeroEmpty controls the behaviour when the decoder encounters empty values 34 | // in a map. 35 | // If z is true and a key in the map has the empty string as a value 36 | // then the corresponding struct field is set to the zero value. 37 | // If z is false then empty strings are ignored. 38 | // 39 | // The default value is false, that is empty values do not change 40 | // the value of the struct field. 41 | func (d *Decoder) ZeroEmpty(z bool) { 42 | d.zeroEmpty = z 43 | } 44 | 45 | // IgnoreUnknownKeys controls the behaviour when the decoder encounters unknown 46 | // keys in the map. 47 | // If i is true and an unknown field is encountered, it is ignored. This is 48 | // similar to how unknown keys are handled by encoding/json. 49 | // If i is false then Decode will return an error. Note that any valid keys 50 | // will still be decoded in to the target struct. 51 | // 52 | // To preserve backwards compatibility, the default value is false. 53 | func (d *Decoder) IgnoreUnknownKeys(i bool) { 54 | d.ignoreUnknownKeys = i 55 | } 56 | 57 | // RegisterConverter registers a converter function for a custom type. 58 | func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) { 59 | d.cache.regconv[reflect.TypeOf(value)] = converterFunc 60 | } 61 | 62 | // Decode decodes a map[string][]string to a struct. 63 | // 64 | // The first parameter must be a pointer to a struct. 65 | // 66 | // The second parameter is a map, typically url.Values from an HTTP request. 67 | // Keys are "paths" in dotted notation to the struct fields and nested structs. 68 | // 69 | // See the package documentation for a full explanation of the mechanics. 70 | func (d *Decoder) Decode(dst interface{}, src map[string][]string) error { 71 | v := reflect.ValueOf(dst) 72 | if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { 73 | return errors.New("schema: interface must be a pointer to struct") 74 | } 75 | v = v.Elem() 76 | t := v.Type() 77 | errors := MultiError{} 78 | for path, values := range src { 79 | if parts, err := d.cache.parsePath(path, t); err == nil { 80 | if err = d.decode(v, path, parts, values); err != nil { 81 | errors[path] = err 82 | } 83 | } else if !d.ignoreUnknownKeys { 84 | errors[path] = fmt.Errorf("schema: invalid path %q", path) 85 | } 86 | } 87 | if len(errors) > 0 { 88 | return errors 89 | } 90 | return nil 91 | } 92 | 93 | // decode fills a struct field using a parsed path. 94 | func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values []string) error { 95 | // Get the field walking the struct fields by index. 96 | for _, name := range parts[0].path { 97 | if v.Type().Kind() == reflect.Ptr { 98 | if v.IsNil() { 99 | v.Set(reflect.New(v.Type().Elem())) 100 | } 101 | v = v.Elem() 102 | } 103 | v = v.FieldByName(name) 104 | } 105 | 106 | // Don't even bother for unexported fields. 107 | if !v.CanSet() { 108 | return nil 109 | } 110 | 111 | // Dereference if needed. 112 | t := v.Type() 113 | if t.Kind() == reflect.Ptr { 114 | t = t.Elem() 115 | if v.IsNil() { 116 | v.Set(reflect.New(t)) 117 | } 118 | v = v.Elem() 119 | } 120 | 121 | // Slice of structs. Let's go recursive. 122 | if len(parts) > 1 { 123 | idx := parts[0].index 124 | if v.IsNil() || v.Len() < idx+1 { 125 | value := reflect.MakeSlice(t, idx+1, idx+1) 126 | if v.Len() < idx+1 { 127 | // Resize it. 128 | reflect.Copy(value, v) 129 | } 130 | v.Set(value) 131 | } 132 | return d.decode(v.Index(idx), path, parts[1:], values) 133 | } 134 | 135 | // Get the converter early in case there is one for a slice type. 136 | conv := d.cache.converter(t) 137 | if conv == nil && t.Kind() == reflect.Slice { 138 | var items []reflect.Value 139 | elemT := t.Elem() 140 | isPtrElem := elemT.Kind() == reflect.Ptr 141 | if isPtrElem { 142 | elemT = elemT.Elem() 143 | } 144 | 145 | // Try to get a converter for the element type. 146 | conv := d.cache.converter(elemT) 147 | if conv == nil { 148 | // As we are not dealing with slice of structs here, we don't need to check if the type 149 | // implements TextUnmarshaler interface 150 | return fmt.Errorf("schema: converter not found for %v", elemT) 151 | } 152 | 153 | for key, value := range values { 154 | if value == "" { 155 | if d.zeroEmpty { 156 | items = append(items, reflect.Zero(elemT)) 157 | } 158 | } else if item := conv(value); item.IsValid() { 159 | if isPtrElem { 160 | ptr := reflect.New(elemT) 161 | ptr.Elem().Set(item) 162 | item = ptr 163 | } 164 | if item.Type() != elemT && !isPtrElem { 165 | item = item.Convert(elemT) 166 | } 167 | items = append(items, item) 168 | } else { 169 | if strings.Contains(value, ",") { 170 | values := strings.Split(value, ",") 171 | for _, value := range values { 172 | if value == "" { 173 | if d.zeroEmpty { 174 | items = append(items, reflect.Zero(elemT)) 175 | } 176 | } else if item := conv(value); item.IsValid() { 177 | if isPtrElem { 178 | ptr := reflect.New(elemT) 179 | ptr.Elem().Set(item) 180 | item = ptr 181 | } 182 | if item.Type() != elemT && !isPtrElem { 183 | item = item.Convert(elemT) 184 | } 185 | items = append(items, item) 186 | } else { 187 | return ConversionError{ 188 | Key: path, 189 | Type: elemT, 190 | Index: key, 191 | } 192 | } 193 | } 194 | } else { 195 | return ConversionError{ 196 | Key: path, 197 | Type: elemT, 198 | Index: key, 199 | } 200 | } 201 | } 202 | } 203 | value := reflect.Append(reflect.MakeSlice(t, 0, 0), items...) 204 | v.Set(value) 205 | } else { 206 | val := "" 207 | // Use the last value provided if any values were provided 208 | if len(values) > 0 { 209 | val = values[len(values)-1] 210 | } 211 | 212 | if val == "" { 213 | if d.zeroEmpty { 214 | v.Set(reflect.Zero(t)) 215 | } 216 | } else if conv != nil { 217 | if value := conv(val); value.IsValid() { 218 | v.Set(value.Convert(t)) 219 | } else { 220 | return ConversionError{ 221 | Key: path, 222 | Type: t, 223 | Index: -1, 224 | } 225 | } 226 | } else { 227 | // When there's no registered conversion for the custom type, we will check if the type 228 | // implements the TextUnmarshaler interface. As the UnmarshalText function should be applied 229 | // to the pointer of the type, we convert the value to pointer. 230 | if v.CanAddr() { 231 | v = v.Addr() 232 | } 233 | 234 | if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { 235 | if err := u.UnmarshalText([]byte(val)); err != nil { 236 | return ConversionError{ 237 | Key: path, 238 | Type: t, 239 | Index: -1, 240 | Err: err, 241 | } 242 | } 243 | 244 | } else { 245 | return fmt.Errorf("schema: converter not found for %v", t) 246 | } 247 | } 248 | } 249 | return nil 250 | } 251 | 252 | // Errors --------------------------------------------------------------------- 253 | 254 | // ConversionError stores information about a failed conversion. 255 | type ConversionError struct { 256 | Key string // key from the source map. 257 | Type reflect.Type // expected type of elem 258 | Index int // index for multi-value fields; -1 for single-value fields. 259 | Err error // low-level error (when it exists) 260 | } 261 | 262 | func (e ConversionError) Error() string { 263 | var output string 264 | 265 | if e.Index < 0 { 266 | output = fmt.Sprintf("schema: error converting value for %q", e.Key) 267 | } else { 268 | output = fmt.Sprintf("schema: error converting value for index %d of %q", 269 | e.Index, e.Key) 270 | } 271 | 272 | if e.Err != nil { 273 | output = fmt.Sprintf("%s. Details: %s", output, e.Err) 274 | } 275 | 276 | return output 277 | } 278 | 279 | // MultiError stores multiple decoding errors. 280 | // 281 | // Borrowed from the App Engine SDK. 282 | type MultiError map[string]error 283 | 284 | func (e MultiError) Error() string { 285 | s := "" 286 | for _, err := range e { 287 | s = err.Error() 288 | break 289 | } 290 | switch len(e) { 291 | case 0: 292 | return "(0 errors)" 293 | case 1: 294 | return s 295 | case 2: 296 | return s + " (and 1 other error)" 297 | } 298 | return fmt.Sprintf("%s (and %d other errors)", s, len(e)-1) 299 | } 300 | -------------------------------------------------------------------------------- /vendor/github.com/gorilla/schema/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Gorilla Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package gorilla/schema fills a struct with form values. 7 | 8 | The basic usage is really simple. Given this struct: 9 | 10 | type Person struct { 11 | Name string 12 | Phone string 13 | } 14 | 15 | ...we can fill it passing a map to the Load() function: 16 | 17 | values := map[string][]string{ 18 | "Name": {"John"}, 19 | "Phone": {"999-999-999"}, 20 | } 21 | person := new(Person) 22 | decoder := schema.NewDecoder() 23 | decoder.Decode(person, values) 24 | 25 | This is just a simple example and it doesn't make a lot of sense to create 26 | the map manually. Typically it will come from a http.Request object and 27 | will be of type url.Values: http.Request.Form or http.Request.MultipartForm: 28 | 29 | func MyHandler(w http.ResponseWriter, r *http.Request) { 30 | err := r.ParseForm() 31 | 32 | if err != nil { 33 | // Handle error 34 | } 35 | 36 | decoder := schema.NewDecoder() 37 | // r.PostForm is a map of our POST form values 38 | err := decoder.Decode(person, r.PostForm) 39 | 40 | if err != nil { 41 | // Handle error 42 | } 43 | 44 | // Do something with person.Name or person.Phone 45 | } 46 | 47 | Note: it is a good idea to set a Decoder instance as a package global, 48 | because it caches meta-data about structs, and a instance can be shared safely: 49 | 50 | var decoder = schema.NewDecoder() 51 | 52 | To define custom names for fields, use a struct tag "schema". To not populate 53 | certain fields, use a dash for the name and it will be ignored: 54 | 55 | type Person struct { 56 | Name string `schema:"name"` // custom name 57 | Phone string `schema:"phone"` // custom name 58 | Admin bool `schema:"-"` // this field is never set 59 | } 60 | 61 | The supported field types in the destination struct are: 62 | 63 | * bool 64 | * float variants (float32, float64) 65 | * int variants (int, int8, int16, int32, int64) 66 | * string 67 | * uint variants (uint, uint8, uint16, uint32, uint64) 68 | * struct 69 | * a pointer to one of the above types 70 | * a slice or a pointer to a slice of one of the above types 71 | 72 | Non-supported types are simply ignored, however custom types can be registered 73 | to be converted. 74 | 75 | To fill nested structs, keys must use a dotted notation as the "path" for the 76 | field. So for example, to fill the struct Person below: 77 | 78 | type Phone struct { 79 | Label string 80 | Number string 81 | } 82 | 83 | type Person struct { 84 | Name string 85 | Phone Phone 86 | } 87 | 88 | ...the source map must have the keys "Name", "Phone.Label" and "Phone.Number". 89 | This means that an HTML form to fill a Person struct must look like this: 90 | 91 |
92 | 93 | 94 | 95 |
96 | 97 | Single values are filled using the first value for a key from the source map. 98 | Slices are filled using all values for a key from the source map. So to fill 99 | a Person with multiple Phone values, like: 100 | 101 | type Person struct { 102 | Name string 103 | Phones []Phone 104 | } 105 | 106 | ...an HTML form that accepts three Phone values would look like this: 107 | 108 |
109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
117 | 118 | Notice that only for slices of structs the slice index is required. 119 | This is needed for disambiguation: if the nested struct also had a slice 120 | field, we could not translate multiple values to it if we did not use an 121 | index for the parent struct. 122 | 123 | There's also the possibility to create a custom type that implements the 124 | TextUnmarshaler interface, and in this case there's no need to registry 125 | a converter, like: 126 | 127 | type Person struct { 128 | Emails []Email 129 | } 130 | 131 | type Email struct { 132 | *mail.Address 133 | } 134 | 135 | func (e *Email) UnmarshalText(text []byte) (err error) { 136 | e.Address, err = mail.ParseAddress(string(text)) 137 | return 138 | } 139 | 140 | ...an HTML form that accepts three Email values would look like this: 141 | 142 |
143 | 144 | 145 | 146 |
147 | */ 148 | package schema 149 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Logan Spears 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/README.md: -------------------------------------------------------------------------------- 1 | # chess 2 | [![GoDoc](https://godoc.org/github.com/loganjspears/chess?status.svg)](https://godoc.org/github.com/loganjspears/chess) 3 | [![Build Status](https://drone.io/github.com/loganjspears/chess/status.png)](https://drone.io/github.com/loganjspears/chess/latest) 4 | [![Coverage Status](https://coveralls.io/repos/loganjspears/chess/badge.svg?branch=master&service=github)](https://coveralls.io/github/loganjspears/chess?branch=master) 5 | [![Go Report Card](http://goreportcard.com/badge/loganjspears/chess)](http://goreportcard.com/report/loganjspears/chess) 6 | 7 | package chess is a go library designed to accomplish the following: 8 | - chess game / turn management 9 | - move validation 10 | - PGN encoding / decoding 11 | - FEN encoding / decoding 12 | 13 | ## Usage 14 | 15 | Using Moves 16 | ```go 17 | game := chess.NewGame() 18 | moves := game.ValidMoves() 19 | game.Move(moves[0]) 20 | ``` 21 | 22 | Using Algebraic Notation 23 | ```go 24 | game := chess.NewGame() 25 | game.MoveAlg("e4") 26 | ``` 27 | 28 | Using PGN 29 | ```go 30 | pgn, _ := chess.PGN(pgnReader) 31 | game := chess.NewGame(pgn) 32 | ``` 33 | 34 | Using FEN 35 | ```go 36 | fen, _ := chess.FEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") 37 | game := chess.NewGame(fen) 38 | ``` 39 | 40 | Random Game 41 | ```go 42 | package main 43 | 44 | import ( 45 | "fmt" 46 | 47 | "github.com/loganjspears/chess" 48 | ) 49 | 50 | func main() { 51 | game := chess.NewGame() 52 | // generate moves until game is over 53 | for game.Outcome() == chess.NoOutcome { 54 | // select a random move 55 | moves := game.ValidMoves() 56 | move := moves[rand.Intn(len(moves))] 57 | game.Move(move) 58 | } 59 | // print outcome and game PGN 60 | fmt.Println(game.Position().Board().Draw()) 61 | fmt.Printf("Game completed. %s by %s.\n", game.Outcome(), game.Method()) 62 | fmt.Println(game.String()) 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/alg.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func encodeMove(pos *Position, move *Move) string { 9 | checkChar := getCheckChar(pos, move) 10 | if move.HasTag(KingSideCastle) { 11 | return "O-O" + checkChar 12 | } else if move.HasTag(QueenSideCastle) { 13 | return "O-O-O" + checkChar 14 | } 15 | p := pos.Board().piece(move.S1()) 16 | pChar := charFromPieceType(p.Type()) 17 | s1Str := formS1(pos, move) 18 | capChar := "" 19 | if move.HasTag(Capture) || move.HasTag(EnPassant) { 20 | capChar = "x" 21 | if p.Type() == Pawn && s1Str == "" { 22 | capChar = move.s1.File().String() + "x" 23 | } 24 | } 25 | epText := "" 26 | if move.HasTag(EnPassant) { 27 | epText = "e.p." 28 | } 29 | promoText := charForPromo(move.promo) 30 | return fmt.Sprint(pChar, s1Str, capChar, move.s2, epText, promoText, checkChar) 31 | } 32 | 33 | func decodeMove(pos *Position, s string) (*Move, error) { 34 | s = strings.Replace(s, "?", "", -1) 35 | s = strings.Replace(s, "!", "", -1) 36 | s = strings.Replace(s, "+", "", -1) 37 | s = strings.Replace(s, "#", "", -1) 38 | s = strings.Replace(s, "e.p.", "", -1) 39 | moves := pos.ValidMoves() 40 | for _, move := range moves { 41 | str := encodeMove(pos, move) 42 | str = strings.Replace(str, "+", "", -1) 43 | str = strings.Replace(str, "#", "", -1) 44 | str = strings.Replace(str, "e.p.", "", -1) 45 | if str == s { 46 | return move, nil 47 | } 48 | } 49 | return nil, fmt.Errorf("chess: could not decode algebraic notation %s for position %s %s", s, pos.String(), moves) 50 | } 51 | 52 | func getCheckChar(pos *Position, move *Move) string { 53 | if !move.HasTag(Check) { 54 | return "" 55 | } 56 | nextPos := pos.Update(move) 57 | if nextPos.Status() == Checkmate { 58 | return "#" 59 | } 60 | return "+" 61 | } 62 | 63 | func formS1(pos *Position, m *Move) string { 64 | moves := pos.ValidMoves() 65 | // find moves for piece type 66 | pMoves := []*Move{} 67 | files := map[File]int{} 68 | ranks := map[Rank]int{} 69 | p := pos.board.piece(m.s1) 70 | if p.Type() == Pawn { 71 | return "" 72 | } 73 | for _, mv := range moves { 74 | if mv.s2 == m.s2 && p == pos.board.piece(mv.s1) { 75 | pMoves = append(pMoves, mv) 76 | files[mv.s1.File()] = files[mv.s1.File()] + 1 77 | ranks[mv.s1.Rank()] = ranks[mv.s1.Rank()] + 1 78 | } 79 | } 80 | if len(pMoves) == 1 { 81 | return "" 82 | } else if len(files) == len(pMoves) { 83 | return m.s1.File().String() 84 | } else if len(ranks) == len(pMoves) { 85 | return m.s1.Rank().String() 86 | } 87 | return m.s1.String() 88 | } 89 | 90 | func charForPromo(p PieceType) string { 91 | c := charFromPieceType(p) 92 | if c != "" { 93 | c = "=" + c 94 | } 95 | return c 96 | } 97 | 98 | func charFromPieceType(p PieceType) string { 99 | switch p { 100 | case King: 101 | return "K" 102 | case Queen: 103 | return "Q" 104 | case Rook: 105 | return "R" 106 | case Bishop: 107 | return "B" 108 | case Knight: 109 | return "N" 110 | } 111 | return "" 112 | } 113 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/bitboard.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // bitboard is a board representation encoded in an unsigned 64-bit integer. The 9 | // 64 board positions begin with A1 as the most significant bit and H8 as the least. 10 | type bitboard uint64 11 | 12 | func newBitboard(m map[Square]bool) bitboard { 13 | s := "" 14 | for sq := 0; sq < numOfSquaresInBoard; sq++ { 15 | if m[Square(sq)] { 16 | s += "1" 17 | } else { 18 | s += "0" 19 | } 20 | } 21 | bb, err := strconv.ParseUint(s, 2, 64) 22 | if err != nil { 23 | panic(err) 24 | } 25 | return bitboard(bb) 26 | } 27 | 28 | func (b bitboard) Mapping() map[Square]bool { 29 | s := b.String() 30 | m := map[Square]bool{} 31 | for sq := 0; sq < numOfSquaresInBoard; sq++ { 32 | if s[sq:sq+1] == "1" { 33 | m[Square(sq)] = true 34 | } 35 | } 36 | return m 37 | } 38 | 39 | // Occupied returns true if the square's bitboard position is 1. 40 | func (b bitboard) Occupied(sq Square) bool { 41 | return (uint64(b) >> uint64(63-sq) & 1) == 1 42 | } 43 | 44 | // String returns a 64 character string of 1s and 0s starting with the most significant bit. 45 | func (b bitboard) String() string { 46 | s := strconv.FormatUint(uint64(b), 2) 47 | return strings.Repeat("0", numOfSquaresInBoard-len(s)) + s 48 | } 49 | 50 | // Reverse returns a bitboard where the bit order is reversed. 51 | func (b bitboard) Reverse() bitboard { 52 | var u uint64 53 | for sq := 0; sq < 64; sq++ { 54 | u = (u << 1) + (uint64(b) & 1) 55 | b = b >> 1 56 | } 57 | return bitboard(u) 58 | } 59 | 60 | // Draw returns visual representation of the bitboard useful for debugging. 61 | func (b bitboard) Draw() string { 62 | s := "\n A B C D E F G H\n" 63 | for r := 7; r >= 0; r-- { 64 | s += Rank(r).String() 65 | for f := 0; f < numOfSquaresInRow; f++ { 66 | sq := getSquare(File(f), Rank(r)) 67 | if b.Occupied(sq) { 68 | s += "1" 69 | } else { 70 | s += "0" 71 | } 72 | s += " " 73 | } 74 | s += "\n" 75 | } 76 | return s 77 | } 78 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/board.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // A Board represents a chess board and its relationship between squares and pieces. 9 | type Board struct { 10 | bbWhiteKing bitboard 11 | bbWhiteQueen bitboard 12 | bbWhiteRook bitboard 13 | bbWhiteBishop bitboard 14 | bbWhiteKnight bitboard 15 | bbWhitePawn bitboard 16 | bbBlackKing bitboard 17 | bbBlackQueen bitboard 18 | bbBlackRook bitboard 19 | bbBlackBishop bitboard 20 | bbBlackKnight bitboard 21 | bbBlackPawn bitboard 22 | whiteSqs bitboard 23 | blackSqs bitboard 24 | emptySqs bitboard 25 | whiteKingSq Square 26 | blackKingSq Square 27 | } 28 | 29 | // SquareMap returns a mapping of squares to pieces. A square is only added to the map if it is occupied. 30 | func (b *Board) SquareMap() map[Square]Piece { 31 | m := map[Square]Piece{} 32 | for sq := 0; sq < numOfSquaresInBoard; sq++ { 33 | p := b.piece(Square(sq)) 34 | if p != NoPiece { 35 | m[Square(sq)] = p 36 | } 37 | } 38 | return m 39 | } 40 | 41 | // Draw returns visual representation of the board useful for debugging. 42 | func (b *Board) Draw() string { 43 | s := "\n A B C D E F G H\n" 44 | for r := 7; r >= 0; r-- { 45 | s += Rank(r).String() 46 | for f := 0; f < numOfSquaresInRow; f++ { 47 | p := b.piece(getSquare(File(f), Rank(r))) 48 | if p == NoPiece { 49 | s += "-" 50 | } else { 51 | s += p.String() 52 | } 53 | s += " " 54 | } 55 | s += "\n" 56 | } 57 | return s 58 | } 59 | 60 | // String implements the fmt.Stringer interface and returns 61 | // a string in the FEN board format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR 62 | func (b *Board) String() string { 63 | fen := "" 64 | for r := 7; r >= 0; r-- { 65 | for f := 0; f < numOfSquaresInRow; f++ { 66 | sq := getSquare(File(f), Rank(r)) 67 | p := b.piece(sq) 68 | if p != NoPiece { 69 | fen += p.getFENChar() 70 | } else { 71 | fen += "1" 72 | } 73 | } 74 | if r != 0 { 75 | fen += "/" 76 | } 77 | } 78 | for i := 8; i > 1; i-- { 79 | repeatStr := strings.Repeat("1", i) 80 | countStr := strconv.Itoa(i) 81 | fen = strings.Replace(fen, repeatStr, countStr, -1) 82 | } 83 | return fen 84 | } 85 | 86 | func newBoard(m map[Square]Piece) *Board { 87 | b := &Board{} 88 | for _, p1 := range allPieces { 89 | bm := map[Square]bool{} 90 | for sq, p2 := range m { 91 | if p1 == p2 { 92 | bm[sq] = true 93 | } 94 | } 95 | bb := newBitboard(bm) 96 | b.setBBForPiece(p1, bb) 97 | } 98 | b.calcConvienceBBs() 99 | return b 100 | } 101 | 102 | func (b *Board) update(m *Move) { 103 | p1 := b.piece(m.s1) 104 | s1BB := bbSquares[m.s1] 105 | s2BB := bbSquares[m.s2] 106 | // move s1 piece to s2 107 | for _, p := range allPieces { 108 | bb := b.bbForPiece(p) 109 | // remove what was at s2 110 | b.setBBForPiece(p, bb & ^s2BB) 111 | // move what was at s1 to s2 112 | if bb.Occupied(m.s1) { 113 | bb = b.bbForPiece(p) 114 | b.setBBForPiece(p, (bb & ^s1BB)|s2BB) 115 | } 116 | } 117 | // check promotion 118 | if m.promo != NoPieceType { 119 | newPiece := getPiece(m.promo, p1.Color()) 120 | // remove pawn 121 | bbPawn := b.bbForPiece(p1) 122 | b.setBBForPiece(p1, bbPawn & ^s2BB) 123 | // add promo piece 124 | bbPromo := b.bbForPiece(newPiece) 125 | b.setBBForPiece(newPiece, bbPromo|s2BB) 126 | } 127 | // remove captured en passant piece 128 | if m.HasTag(EnPassant) { 129 | if p1.Color() == White { 130 | b.bbBlackPawn = ^(bbSquares[m.s2] << 8) & b.bbBlackPawn 131 | } else { 132 | b.bbWhitePawn = ^(bbSquares[m.s2] >> 8) & b.bbWhitePawn 133 | } 134 | } 135 | // move rook for castle 136 | if p1.Color() == White && m.HasTag(KingSideCastle) { 137 | b.bbWhiteRook = (b.bbWhiteRook & ^bbSquares[H1]) | bbSquares[F1] 138 | } else if p1.Color() == White && m.HasTag(QueenSideCastle) { 139 | b.bbWhiteRook = (b.bbWhiteRook & ^bbSquares[A1]) | bbSquares[D1] 140 | } else if p1.Color() == Black && m.HasTag(KingSideCastle) { 141 | b.bbBlackRook = (b.bbBlackRook & ^bbSquares[H8]) | bbSquares[F8] 142 | } else if p1.Color() == Black && m.HasTag(QueenSideCastle) { 143 | b.bbBlackRook = (b.bbBlackRook & ^bbSquares[A8]) | bbSquares[D8] 144 | } 145 | b.calcConvienceBBs() 146 | } 147 | 148 | func (b *Board) calcConvienceBBs() { 149 | whiteSqs := b.bbWhiteKing | b.bbWhiteQueen | b.bbWhiteRook | b.bbWhiteBishop | b.bbWhiteKnight | b.bbWhitePawn 150 | blackSqs := b.bbBlackKing | b.bbBlackQueen | b.bbBlackRook | b.bbBlackBishop | b.bbBlackKnight | b.bbBlackPawn 151 | emptySqs := ^(whiteSqs | blackSqs) 152 | b.whiteSqs = whiteSqs 153 | b.blackSqs = blackSqs 154 | b.emptySqs = emptySqs 155 | b.whiteKingSq = NoSquare 156 | b.blackKingSq = NoSquare 157 | 158 | for sq := 0; sq < numOfSquaresInBoard; sq++ { 159 | sqr := Square(sq) 160 | if b.bbWhiteKing.Occupied(sqr) { 161 | b.whiteKingSq = sqr 162 | } else if b.bbBlackKing.Occupied(sqr) { 163 | b.blackKingSq = sqr 164 | } 165 | } 166 | } 167 | 168 | func (b *Board) copy() *Board { 169 | return &Board{ 170 | whiteSqs: b.whiteSqs, 171 | blackSqs: b.blackSqs, 172 | emptySqs: b.emptySqs, 173 | whiteKingSq: b.whiteKingSq, 174 | blackKingSq: b.blackKingSq, 175 | bbWhiteKing: b.bbWhiteKing, 176 | bbWhiteQueen: b.bbWhiteQueen, 177 | bbWhiteRook: b.bbWhiteRook, 178 | bbWhiteBishop: b.bbWhiteBishop, 179 | bbWhiteKnight: b.bbWhiteKnight, 180 | bbWhitePawn: b.bbWhitePawn, 181 | bbBlackKing: b.bbBlackKing, 182 | bbBlackQueen: b.bbBlackQueen, 183 | bbBlackRook: b.bbBlackRook, 184 | bbBlackBishop: b.bbBlackBishop, 185 | bbBlackKnight: b.bbBlackKnight, 186 | bbBlackPawn: b.bbBlackPawn, 187 | } 188 | } 189 | 190 | func (b *Board) isOccupied(sq Square) bool { 191 | return !b.emptySqs.Occupied(sq) 192 | } 193 | 194 | func (b *Board) piece(sq Square) Piece { 195 | for _, p := range allPieces { 196 | bb := b.bbForPiece(p) 197 | if bb.Occupied(sq) { 198 | return p 199 | } 200 | } 201 | return NoPiece 202 | } 203 | 204 | func (b *Board) hasSufficientMaterial() bool { 205 | // queen, rook, or pawn exist 206 | if (b.bbWhiteQueen | b.bbWhiteRook | b.bbWhitePawn | 207 | b.bbBlackQueen | b.bbBlackRook | b.bbBlackPawn) > 0 { 208 | return true 209 | } 210 | // if king is missing then it is a test 211 | if b.bbWhiteKing == 0 || b.bbBlackKing == 0 { 212 | return true 213 | } 214 | count := map[PieceType]int{} 215 | pieceMap := b.SquareMap() 216 | for _, p := range pieceMap { 217 | count[p.Type()]++ 218 | } 219 | // king versus king 220 | if count[Bishop] == 0 && count[Knight] == 0 { 221 | return false 222 | } 223 | // king and bishop versus king 224 | if count[Bishop] == 1 && count[Knight] == 0 { 225 | return false 226 | } 227 | // king and knight versus king 228 | if count[Bishop] == 0 && count[Knight] == 1 { 229 | return false 230 | } 231 | // king and bishop(s) versus king and bishop(s) with the bishops on the same colour. 232 | if count[Knight] == 0 { 233 | whiteCount := 0 234 | blackCount := 0 235 | for sq, p := range pieceMap { 236 | if p.Type() == Bishop { 237 | switch sq.color() { 238 | case White: 239 | whiteCount++ 240 | case Black: 241 | blackCount++ 242 | } 243 | } 244 | } 245 | if whiteCount == 0 || blackCount == 0 { 246 | return false 247 | } 248 | } 249 | return true 250 | } 251 | 252 | func (b *Board) bbForPiece(p Piece) bitboard { 253 | switch p { 254 | case WhiteKing: 255 | return b.bbWhiteKing 256 | case WhiteQueen: 257 | return b.bbWhiteQueen 258 | case WhiteRook: 259 | return b.bbWhiteRook 260 | case WhiteBishop: 261 | return b.bbWhiteBishop 262 | case WhiteKnight: 263 | return b.bbWhiteKnight 264 | case WhitePawn: 265 | return b.bbWhitePawn 266 | case BlackKing: 267 | return b.bbBlackKing 268 | case BlackQueen: 269 | return b.bbBlackQueen 270 | case BlackRook: 271 | return b.bbBlackRook 272 | case BlackBishop: 273 | return b.bbBlackBishop 274 | case BlackKnight: 275 | return b.bbBlackKnight 276 | case BlackPawn: 277 | return b.bbBlackPawn 278 | } 279 | return bitboard(0) 280 | } 281 | 282 | func (b *Board) setBBForPiece(p Piece, bb bitboard) { 283 | switch p { 284 | case WhiteKing: 285 | b.bbWhiteKing = bb 286 | case WhiteQueen: 287 | b.bbWhiteQueen = bb 288 | case WhiteRook: 289 | b.bbWhiteRook = bb 290 | case WhiteBishop: 291 | b.bbWhiteBishop = bb 292 | case WhiteKnight: 293 | b.bbWhiteKnight = bb 294 | case WhitePawn: 295 | b.bbWhitePawn = bb 296 | case BlackKing: 297 | b.bbBlackKing = bb 298 | case BlackQueen: 299 | b.bbBlackQueen = bb 300 | case BlackRook: 301 | b.bbBlackRook = bb 302 | case BlackBishop: 303 | b.bbBlackBishop = bb 304 | case BlackKnight: 305 | b.bbBlackKnight = bb 306 | case BlackPawn: 307 | b.bbBlackPawn = bb 308 | default: 309 | panic("HERE") 310 | } 311 | } 312 | 313 | var ( 314 | // file bitboards 315 | bbFileA bitboard 316 | bbFileB bitboard 317 | bbFileC bitboard 318 | bbFileD bitboard 319 | bbFileE bitboard 320 | bbFileF bitboard 321 | bbFileG bitboard 322 | bbFileH bitboard 323 | 324 | // rank bitboards 325 | bbRank1 bitboard 326 | bbRank2 bitboard 327 | bbRank3 bitboard 328 | bbRank4 bitboard 329 | bbRank5 bitboard 330 | bbRank6 bitboard 331 | bbRank7 bitboard 332 | bbRank8 bitboard 333 | 334 | bbFiles [8]bitboard 335 | bbRanks [8]bitboard 336 | bbSquares [64]bitboard 337 | bbDiagonal [64]bitboard 338 | bbAntiDiagonal [64]bitboard 339 | ) 340 | 341 | func init() { 342 | bbFileA = bbForFile(FileA) 343 | bbFileB = bbForFile(FileB) 344 | bbFileC = bbForFile(FileC) 345 | bbFileD = bbForFile(FileD) 346 | bbFileE = bbForFile(FileE) 347 | bbFileF = bbForFile(FileF) 348 | bbFileG = bbForFile(FileG) 349 | bbFileH = bbForFile(FileH) 350 | 351 | bbRank1 = bbForRank(Rank1) 352 | bbRank2 = bbForRank(Rank2) 353 | bbRank3 = bbForRank(Rank3) 354 | bbRank4 = bbForRank(Rank4) 355 | bbRank5 = bbForRank(Rank5) 356 | bbRank6 = bbForRank(Rank6) 357 | bbRank7 = bbForRank(Rank7) 358 | bbRank8 = bbForRank(Rank8) 359 | 360 | bbFiles = [8]bitboard{bbFileA, bbFileB, bbFileC, bbFileD, bbFileE, bbFileF, bbFileG, bbFileH} 361 | bbRanks = [8]bitboard{bbRank1, bbRank2, bbRank3, bbRank4, bbRank5, bbRank6, bbRank7, bbRank8} 362 | for sq := 0; sq < numOfSquaresInBoard; sq++ { 363 | sqr := Square(sq) 364 | bbSquares[sq] = bbRanks[sqr.Rank()] & bbFiles[sqr.File()] 365 | } 366 | 367 | // init diagonal and anti-diagonal bitboards 368 | bbDiagonal = [64]bitboard{} 369 | bbAntiDiagonal = [64]bitboard{} 370 | for sq := 0; sq < numOfSquaresInBoard; sq++ { 371 | sqr := Square(sq) 372 | bbDiagonal[sqr] = bbForDiagonal(sqr) 373 | bbAntiDiagonal[sqr] = bbForAntiDiagonal(sqr) 374 | } 375 | } 376 | 377 | func bbForFile(f File) bitboard { 378 | m := map[Square]bool{} 379 | var sq Square 380 | for ; sq < numOfSquaresInBoard; sq++ { 381 | if sq.File() == f { 382 | m[sq] = true 383 | } 384 | } 385 | return newBitboard(m) 386 | } 387 | 388 | func bbForRank(r Rank) bitboard { 389 | m := map[Square]bool{} 390 | var sq Square 391 | for ; sq < numOfSquaresInBoard; sq++ { 392 | if sq.Rank() == r { 393 | m[sq] = true 394 | } 395 | } 396 | return newBitboard(m) 397 | } 398 | 399 | func bbForDiagonal(sq Square) bitboard { 400 | v := int(sq.File()) - int(sq.Rank()) 401 | m := map[Square]bool{} 402 | for sq := 0; sq < numOfSquaresInBoard; sq++ { 403 | sqr := Square(sq) 404 | if int(sqr.File())-int(sqr.Rank()) == v { 405 | m[sqr] = true 406 | } 407 | } 408 | return newBitboard(m) 409 | } 410 | 411 | func bbForAntiDiagonal(sq Square) bitboard { 412 | v := int(sq.Rank()) + int(sq.File()) 413 | m := map[Square]bool{} 414 | for sq := 0; sq < numOfSquaresInBoard; sq++ { 415 | sqr := Square(sq) 416 | if int(sqr.Rank())+int(sqr.File()) == v { 417 | m[sqr] = true 418 | } 419 | } 420 | return newBitboard(m) 421 | } 422 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package chess is a go library designed to accomplish the following: 3 | - chess game / turn management 4 | - move validation 5 | - PGN encoding / decoding 6 | - FEN encoding / decoding 7 | 8 | Using Moves 9 | game := chess.NewGame() 10 | moves := game.ValidMoves() 11 | game.Move(moves[0]) 12 | 13 | Using Algebraic Notation 14 | game := chess.NewGame() 15 | game.MoveAlg("e4") 16 | 17 | Using PGN 18 | pgn, _ := chess.PGN(pgnReader) 19 | game := chess.NewGame(pgn) 20 | 21 | Using FEN 22 | fen, _ := chess.FEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") 23 | game := chess.NewGame(fen) 24 | 25 | Random Game 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | 31 | "github.com/loganjspears/chess" 32 | ) 33 | 34 | func main() { 35 | game := chess.NewGame() 36 | // generate moves until game is over 37 | for game.Outcome() == chess.NoOutcome { 38 | // select a random move 39 | moves := game.ValidMoves() 40 | move := moves[rand.Intn(len(moves))] 41 | game.Move(move) 42 | } 43 | // print outcome and game PGN 44 | fmt.Println(game.Position().Board().Draw()) 45 | fmt.Printf("Game completed. %s by %s.\n", game.Outcome(), game.Method()) 46 | fmt.Println(game.String()) 47 | } 48 | */ 49 | package chess 50 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/fen.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Decodes FEN notation into a GameState. An error is returned 10 | // if there is a parsing error. FEN notation format: 11 | // rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 12 | func decodeFEN(fen string) (*Position, error) { 13 | fen = strings.TrimSpace(fen) 14 | parts := strings.Split(fen, " ") 15 | if len(parts) != 6 { 16 | return nil, fmt.Errorf("chess: fen invalid notiation %s must have 6 sections", fen) 17 | } 18 | b, err := fenBoard(parts[0]) 19 | if err != nil { 20 | return nil, err 21 | } 22 | turn, ok := fenTurnMap[parts[1]] 23 | if !ok { 24 | return nil, fmt.Errorf("chess: fen invalid turn %s", parts[1]) 25 | } 26 | rights, err := formCastleRights(parts[2]) 27 | if err != nil { 28 | return nil, err 29 | } 30 | sq, err := formEnPassant(parts[3]) 31 | if err != nil { 32 | return nil, err 33 | } 34 | halfMoveClock, err := strconv.Atoi(parts[4]) 35 | if err != nil || halfMoveClock < 0 { 36 | return nil, fmt.Errorf("chess: fen invalid half move clock %s", parts[4]) 37 | } 38 | moveCount, err := strconv.Atoi(parts[5]) 39 | if err != nil || moveCount < 1 { 40 | return nil, fmt.Errorf("chess: fen invalid move count %s", parts[5]) 41 | } 42 | return &Position{ 43 | board: b, 44 | turn: turn, 45 | castleRights: rights, 46 | enPassantSquare: sq, 47 | halfMoveClock: halfMoveClock, 48 | moveCount: moveCount, 49 | }, nil 50 | } 51 | 52 | // generates board from fen format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR 53 | func fenBoard(boardStr string) (*Board, error) { 54 | rankStrs := strings.Split(boardStr, "/") 55 | if len(rankStrs) != 8 { 56 | return nil, fmt.Errorf("chess: fen invalid board %s", boardStr) 57 | } 58 | m := map[Square]Piece{} 59 | for i, rankStr := range rankStrs { 60 | rank := Rank(7 - i) 61 | fileMap, err := fenFormRank(rankStr) 62 | if err != nil { 63 | return nil, err 64 | } 65 | for file, piece := range fileMap { 66 | m[getSquare(file, rank)] = piece 67 | } 68 | } 69 | return newBoard(m), nil 70 | } 71 | 72 | func fenFormRank(rankStr string) (map[File]Piece, error) { 73 | count := 0 74 | m := map[File]Piece{} 75 | err := fmt.Errorf("chess: fen invalid rank %s", rankStr) 76 | for _, r := range rankStr { 77 | c := fmt.Sprintf("%c", r) 78 | piece := fenPieceMap[c] 79 | if piece == NoPiece { 80 | skip, err := strconv.Atoi(c) 81 | if err != nil { 82 | return nil, err 83 | } 84 | count += skip 85 | continue 86 | } 87 | m[File(count)] = piece 88 | count++ 89 | } 90 | if count != 8 { 91 | return nil, err 92 | } 93 | return m, nil 94 | } 95 | 96 | func formCastleRights(castleStr string) (CastleRights, error) { 97 | // check for duplicates aka. KKkq right now is valid 98 | for _, s := range []string{"K", "Q", "k", "q", "-"} { 99 | if strings.Count(castleStr, s) > 1 { 100 | return "-", fmt.Errorf("chess: fen invalid castle rights %s", castleStr) 101 | } 102 | } 103 | for _, r := range castleStr { 104 | c := fmt.Sprintf("%c", r) 105 | switch c { 106 | case "K", "Q", "k", "q", "-": 107 | default: 108 | return "-", fmt.Errorf("chess: fen invalid castle rights %s", castleStr) 109 | } 110 | } 111 | return CastleRights(castleStr), nil 112 | } 113 | 114 | func formEnPassant(enPassant string) (Square, error) { 115 | if enPassant == "-" { 116 | return NoSquare, nil 117 | } 118 | sq := strToSquareMap[enPassant] 119 | if sq == NoSquare || !(sq.Rank() == Rank3 || sq.Rank() == Rank6) { 120 | return NoSquare, fmt.Errorf("chess: fen invalid En Passant square %s", enPassant) 121 | } 122 | return sq, nil 123 | } 124 | 125 | var ( 126 | fenSkipMap = map[int]string{ 127 | 1: "1", 128 | 2: "2", 129 | 3: "3", 130 | 4: "4", 131 | 5: "5", 132 | 6: "6", 133 | 7: "7", 134 | 8: "8", 135 | } 136 | fenPieceMap = map[string]Piece{ 137 | "K": WhiteKing, 138 | "Q": WhiteQueen, 139 | "R": WhiteRook, 140 | "B": WhiteBishop, 141 | "N": WhiteKnight, 142 | "P": WhitePawn, 143 | "k": BlackKing, 144 | "q": BlackQueen, 145 | "r": BlackRook, 146 | "b": BlackBishop, 147 | "n": BlackKnight, 148 | "p": BlackPawn, 149 | } 150 | 151 | fenTurnMap = map[string]Color{ 152 | "w": White, 153 | "b": Black, 154 | } 155 | ) 156 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/game.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | ) 9 | 10 | const ( 11 | startFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" 12 | ) 13 | 14 | // A Outcome is the result of a game. 15 | type Outcome string 16 | 17 | const ( 18 | // NoOutcome indicates that a game is in progress or ended without a result. 19 | NoOutcome Outcome = "*" 20 | // WhiteWon indicates that white won the game. 21 | WhiteWon Outcome = "1-0" 22 | // BlackWon indicates that black won the game. 23 | BlackWon Outcome = "0-1" 24 | // Draw indicates that game was a draw. 25 | Draw Outcome = "1/2-1/2" 26 | ) 27 | 28 | // String implements the fmt.Stringer interface 29 | func (o Outcome) String() string { 30 | return string(o) 31 | } 32 | 33 | // A Method is the method that generated the outcome. 34 | type Method uint8 35 | 36 | const ( 37 | // NoMethod indicates that an outcome hasn't occurred or that the method can't be determined. 38 | NoMethod Method = iota 39 | // Checkmate indicates that the game was won checkmate. 40 | Checkmate 41 | // Resignation indicates that the game was won by resignation. 42 | Resignation 43 | // DrawOffer indicates that the game was drawn by a draw offer. 44 | DrawOffer 45 | // Stalemate indicates that the game was drawn by stalemate. 46 | Stalemate 47 | // ThreefoldRepetition indicates that the game was drawn when the game 48 | // state was repeated three times and a player requested a draw. 49 | ThreefoldRepetition 50 | // FivefoldRepetition indicates that the game was automatically drawn 51 | // by the game state being repeated five times. 52 | FivefoldRepetition 53 | // FiftyMoveRule indicates that the game was drawn by the half 54 | // move clock being fifty or greater when a player requested a draw. 55 | FiftyMoveRule 56 | // SeventyFiveMoveRule indicates that the game was automatically drawn 57 | // when the half move clock was seventy five or greater. 58 | SeventyFiveMoveRule 59 | // InsufficientMaterial indicates that the game was automatically drawn 60 | // because there was insufficient material for checkmate. 61 | InsufficientMaterial 62 | ) 63 | 64 | // TagPair represents metadata in a key value pairing used in PGN notation. 65 | type TagPair struct { 66 | Key string 67 | Value string 68 | } 69 | 70 | // A Game represents a single chess game. 71 | type Game struct { 72 | tagPairs []*TagPair 73 | moves []*Move 74 | positions []*Position 75 | pos *Position 76 | outcome Outcome 77 | method Method 78 | ignoreAutomaticDraws bool 79 | } 80 | 81 | // PGN takes a reader and returns a function that updates 82 | // the game to reflect the PGN data. The returned function 83 | // is designed to be used in the NewGame constructor. An 84 | // error is returned if there is a problem parsing the PGN 85 | // data. 86 | func PGN(r io.Reader) (func(*Game), error) { 87 | b, err := ioutil.ReadAll(r) 88 | if err != nil { 89 | return nil, err 90 | } 91 | game, err := decodePGN(string(b)) 92 | if err != nil { 93 | return nil, err 94 | } 95 | return func(g *Game) { 96 | g.copy(game) 97 | }, nil 98 | } 99 | 100 | // FEN takes a string and returns a function that updates 101 | // the game to reflect the FEN data. Since FEN doesn't include 102 | // prior moves, the move list will be empty. The returned 103 | // function is designed to be used in the NewGame constructor. 104 | // An error is returned if there is a problem parsing the FEN data. 105 | func FEN(fenStr string) (func(*Game), error) { 106 | pos, err := decodeFEN(fenStr) 107 | if err != nil { 108 | return nil, err 109 | } 110 | return func(g *Game) { 111 | g.pos = pos 112 | g.positions = []*Position{pos} 113 | g.updatePosition() 114 | }, nil 115 | } 116 | 117 | // TagPairs returns a function that sets the tag pairs 118 | // to the given value. The returned function is designed 119 | // to be used in the NewGame constructor. 120 | func TagPairs(tagPairs []*TagPair) func(*Game) { 121 | return func(g *Game) { 122 | g.tagPairs = append([]*TagPair(nil), tagPairs...) 123 | } 124 | } 125 | 126 | // NewGame defaults to returning a game in the standard 127 | // opening position. Options can be given to configure 128 | // the game's initial state. 129 | func NewGame(options ...func(*Game)) *Game { 130 | pos, _ := decodeFEN(startFEN) 131 | game := &Game{ 132 | moves: []*Move{}, 133 | pos: pos, 134 | positions: []*Position{pos}, 135 | outcome: NoOutcome, 136 | method: NoMethod, 137 | } 138 | for _, f := range options { 139 | f(game) 140 | } 141 | return game 142 | } 143 | 144 | // Move updates the game with the given move. An error is returned 145 | // if the move is invalid or the game has already been completed. 146 | func (g *Game) Move(m *Move) error { 147 | if !moveSlice(g.ValidMoves()).contains(m) { 148 | return fmt.Errorf("chess: invalid move %s", m) 149 | } 150 | g.moves = append(g.moves, m) 151 | g.pos = g.pos.Update(m) 152 | g.positions = append(g.positions, g.pos) 153 | g.updatePosition() 154 | return nil 155 | } 156 | 157 | // MoveAlg decodes the given string in algebraic notation 158 | // and calls the Move function. An error is returned if 159 | // the move can't be decoded, the move is invalid, or the 160 | // game has already been completed. 161 | func (g *Game) MoveAlg(alg string) error { 162 | m, err := decodeMove(g.pos, alg) 163 | if err != nil { 164 | return err 165 | } 166 | return g.Move(m) 167 | } 168 | 169 | // ValidMoves returns a list of valid moves in the 170 | // current position. 171 | func (g *Game) ValidMoves() []*Move { 172 | return g.pos.ValidMoves() 173 | } 174 | 175 | // Positions returns the position history of the game. 176 | func (g *Game) Positions() []*Position { 177 | return append([]*Position(nil), g.positions...) 178 | } 179 | 180 | // Moves returns the move history of the game. 181 | func (g *Game) Moves() []*Move { 182 | return append([]*Move(nil), g.moves...) 183 | } 184 | 185 | // TagPairs returns the game's tag pairs. 186 | func (g *Game) TagPairs() []*TagPair { 187 | return append([]*TagPair(nil), g.tagPairs...) 188 | } 189 | 190 | // Position returns the game's current position. 191 | func (g *Game) Position() *Position { 192 | return g.pos 193 | } 194 | 195 | // Outcome returns the game outcome. 196 | func (g *Game) Outcome() Outcome { 197 | return g.outcome 198 | } 199 | 200 | // Method returns the method in which the outcome occurred. 201 | func (g *Game) Method() Method { 202 | return g.method 203 | } 204 | 205 | // FEN returns the FEN notation of the current position. 206 | func (g *Game) FEN() string { 207 | return g.pos.String() 208 | } 209 | 210 | // String implements the fmt.Stringer interface and returns 211 | // the game's PGN. 212 | func (g *Game) String() string { 213 | return encodePGN(g) 214 | } 215 | 216 | // MarshalText implements the encoding.TextMarshaler interface and 217 | // encodes the game's PGN. 218 | func (g *Game) MarshalText() (text []byte, err error) { 219 | return []byte(encodePGN(g)), nil 220 | } 221 | 222 | // UnmarshalText implements the encoding.TextUnarshaler interface and 223 | // assumes the data is in the PGN format. 224 | func (g *Game) UnmarshalText(text []byte) error { 225 | game, err := decodePGN(string(text)) 226 | if err != nil { 227 | return err 228 | } 229 | g.copy(game) 230 | return nil 231 | } 232 | 233 | // Draw attempts to draw the game by the given method. If the 234 | // method is valid, then the game is updated to a draw by that 235 | // method. If the method isn't valid then an error is returned. 236 | func (g *Game) Draw(method Method) error { 237 | switch method { 238 | case ThreefoldRepetition: 239 | if g.numOfRepitions() < 3 { 240 | return errors.New("chess: draw by ThreefoldRepetition requires at least three repetitions of the current board state") 241 | } 242 | case FiftyMoveRule: 243 | if g.pos.halfMoveClock < 50 { 244 | return fmt.Errorf("chess: draw by FiftyMoveRule requires the half move clock to be at 50 or greater but is %d", g.pos.halfMoveClock) 245 | } 246 | case DrawOffer: 247 | default: 248 | return fmt.Errorf("chess: unsupported draw method %s", method.String()) 249 | } 250 | g.outcome = Draw 251 | g.method = method 252 | return nil 253 | } 254 | 255 | // Resign resigns the game for the given color. If the game has 256 | // already been completed then the game is not updated. 257 | func (g *Game) Resign(color Color) { 258 | if g.outcome != NoOutcome || color == NoColor { 259 | return 260 | } 261 | if color == White { 262 | g.outcome = BlackWon 263 | } else { 264 | g.outcome = WhiteWon 265 | } 266 | g.method = Resignation 267 | } 268 | 269 | // EligibleDraws returns valid inputs for the Draw() method. 270 | func (g *Game) EligibleDraws() []Method { 271 | draws := []Method{DrawOffer} 272 | if g.numOfRepitions() >= 3 { 273 | draws = append(draws, ThreefoldRepetition) 274 | } 275 | if g.pos.halfMoveClock < 50 { 276 | draws = append(draws, FiftyMoveRule) 277 | } 278 | return draws 279 | } 280 | 281 | func (g *Game) updatePosition() { 282 | method := g.pos.Status() 283 | if method == Stalemate { 284 | g.method = Stalemate 285 | g.outcome = Draw 286 | } else if method == Checkmate { 287 | g.method = Checkmate 288 | g.outcome = WhiteWon 289 | if g.pos.Turn() == White { 290 | g.outcome = BlackWon 291 | } 292 | } 293 | if g.outcome != NoOutcome { 294 | return 295 | } 296 | 297 | // five fold rep creates automatic draw 298 | if !g.ignoreAutomaticDraws && g.numOfRepitions() >= 5 { 299 | g.outcome = Draw 300 | g.method = FivefoldRepetition 301 | } 302 | 303 | // 75 move rule creates automatic draw 304 | if !g.ignoreAutomaticDraws && g.pos.halfMoveClock >= 75 && g.method != Checkmate { 305 | g.outcome = Draw 306 | g.method = SeventyFiveMoveRule 307 | } 308 | 309 | // insufficient material creates automatic draw 310 | if !g.ignoreAutomaticDraws && !g.pos.board.hasSufficientMaterial() { 311 | g.outcome = Draw 312 | g.method = InsufficientMaterial 313 | } 314 | } 315 | 316 | func (g *Game) copy(game *Game) { 317 | g.tagPairs = game.TagPairs() 318 | g.moves = game.Moves() 319 | g.positions = game.Positions() 320 | g.pos = game.pos 321 | g.outcome = game.outcome 322 | g.method = game.method 323 | } 324 | 325 | func (g *Game) numOfRepitions() int { 326 | count := 0 327 | for _, pos := range g.Positions() { 328 | if g.pos.samePosition(pos) { 329 | count++ 330 | } 331 | } 332 | return count 333 | } 334 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/move.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | // A MoveTag represents a notable consequence of a move. 4 | type MoveTag int8 5 | 6 | const ( 7 | // KingSideCastle indicates that the move is a king side castle. 8 | KingSideCastle MoveTag = iota + 1 9 | // QueenSideCastle indicates that the move is a queen side castle. 10 | QueenSideCastle 11 | // Capture indicates that the move captures a piece. 12 | Capture 13 | // EnPassant indicates that the move captures via en passant. 14 | EnPassant 15 | // Check indicates that the move puts the opposing player in check. 16 | Check 17 | // inCheck indicates that the move puts the moving player in check and 18 | // is therefore invalid. 19 | inCheck 20 | ) 21 | 22 | // A Move is the movement of a piece from one square to another. 23 | type Move struct { 24 | s1 Square 25 | s2 Square 26 | promo PieceType 27 | tags map[MoveTag]bool 28 | } 29 | 30 | // String returns a string useful for debugging. String doesn't return 31 | // algebraic notation. 32 | func (m *Move) String() string { 33 | return m.s1.String() + m.s2.String() + m.promo.String() 34 | } 35 | 36 | // S1 returns the origin square of the move. 37 | func (m *Move) S1() Square { 38 | return m.s1 39 | } 40 | 41 | // S2 returns the destination square of the move. 42 | func (m *Move) S2() Square { 43 | return m.s2 44 | } 45 | 46 | // Promo returns promotion piece type of the move. 47 | func (m *Move) Promo() PieceType { 48 | return m.promo 49 | } 50 | 51 | // HasTag returns true if the move contains the MoveTag given. 52 | func (m *Move) HasTag(tag MoveTag) bool { 53 | return m.tags[tag] 54 | } 55 | 56 | func (m *Move) addTag(tag MoveTag) { 57 | if m.tags == nil { 58 | m.tags = map[MoveTag]bool{} 59 | } 60 | m.tags[tag] = true 61 | } 62 | 63 | type moveSlice []*Move 64 | 65 | func (a moveSlice) contains(m *Move) bool { 66 | for _, move := range a { 67 | if move.String() == m.String() { 68 | return true 69 | } 70 | } 71 | return false 72 | } 73 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/pgn.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "log" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | // GamesFromPGN returns all PGN decoding games from the 13 | // reader. It is designed to be used decoding multiple PGNs 14 | // in the same file. An error is returned if there is an 15 | // issue parsing the PGNs. 16 | func GamesFromPGN(r io.Reader) ([]*Game, error) { 17 | games := []*Game{} 18 | current := "" 19 | count := 0 20 | totalCount := 0 21 | br := bufio.NewReader(r) 22 | for { 23 | line, err := br.ReadString('\n') 24 | if err == io.EOF { 25 | break 26 | } else if err != nil { 27 | return nil, err 28 | } 29 | if strings.TrimSpace(line) == "" { 30 | count++ 31 | } else { 32 | current += line 33 | } 34 | if count == 2 { 35 | game, err := decodePGN(current) 36 | if err != nil { 37 | return nil, err 38 | } 39 | games = append(games, game) 40 | count = 0 41 | current = "" 42 | totalCount++ 43 | log.Println("Processed game", totalCount) 44 | } 45 | } 46 | return games, nil 47 | } 48 | 49 | func decodePGN(pgn string) (*Game, error) { 50 | tagPairs := getTagPairs(pgn) 51 | moveStrs, outcome := moveList(pgn) 52 | g := NewGame(TagPairs(tagPairs)) 53 | g.ignoreAutomaticDraws = true 54 | for _, alg := range moveStrs { 55 | if err := g.MoveAlg(alg); err != nil { 56 | return nil, fmt.Errorf("%s on move %d - Tag Pairs: %s", err.Error(), g.Position().moveCount, g.TagPairs()) 57 | } 58 | } 59 | g.outcome = outcome 60 | return g, nil 61 | } 62 | 63 | func encodePGN(g *Game) string { 64 | s := "" 65 | for _, tag := range g.tagPairs { 66 | s += fmt.Sprintf("[%s \"%s\"]\n", tag.Key, tag.Value) 67 | } 68 | s += "\n" 69 | for i, move := range g.moves { 70 | pos := g.positions[i] 71 | alg := encodeMove(pos, move) 72 | if i%2 == 0 { 73 | s += fmt.Sprintf("%d.%s", (i/2)+1, alg) 74 | } else { 75 | s += fmt.Sprintf(" %s ", alg) 76 | } 77 | } 78 | s += " " + string(g.outcome) 79 | return s 80 | } 81 | 82 | var ( 83 | tagPairRegex = regexp.MustCompile(`\[(.*)\s\"(.*)\"\]`) 84 | ) 85 | 86 | func getTagPairs(pgn string) []*TagPair { 87 | tagPairs := []*TagPair{} 88 | matches := tagPairRegex.FindAllString(pgn, -1) 89 | for _, m := range matches { 90 | results := tagPairRegex.FindStringSubmatch(m) 91 | if len(results) == 3 { 92 | pair := &TagPair{ 93 | Key: results[1], 94 | Value: results[2], 95 | } 96 | tagPairs = append(tagPairs, pair) 97 | } 98 | } 99 | return tagPairs 100 | } 101 | 102 | var ( 103 | moveNumRegex = regexp.MustCompile(`(?:\d+\.+)?(.*)`) 104 | ) 105 | 106 | func moveList(pgn string) ([]string, Outcome) { 107 | // remove comments 108 | text := removeSection("{", "}", pgn) 109 | // remove variations 110 | text = removeSection(`\(`, `\)`, text) 111 | // remove tag pairs 112 | text = removeSection(`\[`, `\]`, text) 113 | // remove line breaks 114 | text = strings.Replace(text, "\n", " ", -1) 115 | 116 | list := strings.Split(text, " ") 117 | filtered := []string{} 118 | var outcome Outcome 119 | for _, move := range list { 120 | move = strings.TrimSpace(move) 121 | switch move { 122 | case string(NoOutcome), string(WhiteWon), string(BlackWon), string(Draw): 123 | outcome = Outcome(move) 124 | case "": 125 | default: 126 | results := moveNumRegex.FindStringSubmatch(move) 127 | if len(results) == 2 && results[1] != "" { 128 | filtered = append(filtered, results[1]) 129 | } 130 | } 131 | } 132 | return filtered, outcome 133 | } 134 | 135 | func removeSection(leftChar, rightChar, s string) string { 136 | r := regexp.MustCompile(leftChar + ".*?" + rightChar) 137 | for { 138 | i := r.FindStringIndex(s) 139 | if i == nil { 140 | return s 141 | } 142 | s = s[0:i[0]] + s[i[1]:len(s)] 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/piece.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | // Color represents the color of a chess piece. 4 | type Color int8 5 | 6 | const ( 7 | // NoColor represents no color 8 | NoColor Color = iota 9 | // White represents the color white 10 | White 11 | // Black represents the color black 12 | Black 13 | ) 14 | 15 | // Other returns the opposie color of the receiver. 16 | func (c Color) Other() Color { 17 | switch c { 18 | case White: 19 | return Black 20 | case Black: 21 | return White 22 | } 23 | return NoColor 24 | } 25 | 26 | // String implements the fmt.Stringer interface and returns 27 | // the color's FEN compatible notation. 28 | func (c Color) String() string { 29 | switch c { 30 | case White: 31 | return "w" 32 | case Black: 33 | return "b" 34 | } 35 | return "-" 36 | } 37 | 38 | // PieceType is the type of a piece. 39 | type PieceType int8 40 | 41 | const ( 42 | // NoPieceType represents a lack of piece type 43 | NoPieceType PieceType = iota 44 | // King represents a king 45 | King 46 | // Queen represents a queen 47 | Queen 48 | // Rook represents a rook 49 | Rook 50 | // Bishop represents a bishop 51 | Bishop 52 | // Knight represents a knight 53 | Knight 54 | // Pawn represents a pawn 55 | Pawn 56 | ) 57 | 58 | // PieceTypes returns a slice of all piece types. 59 | func PieceTypes() [6]PieceType { 60 | return [6]PieceType{King, Queen, Rook, Bishop, Knight, Pawn} 61 | } 62 | 63 | func (p PieceType) String() string { 64 | switch p { 65 | case King: 66 | return "k" 67 | case Queen: 68 | return "q" 69 | case Rook: 70 | return "r" 71 | case Bishop: 72 | return "b" 73 | case Knight: 74 | return "n" 75 | } 76 | return "" 77 | } 78 | func (p PieceType) promotableTo() bool { 79 | switch p { 80 | case Queen, Rook, Bishop, Knight: 81 | return true 82 | } 83 | return false 84 | } 85 | 86 | // Piece is a piece type with a color. 87 | type Piece int8 88 | 89 | const ( 90 | // NoPiece represents no piece 91 | NoPiece Piece = iota 92 | // WhiteKing is a white king 93 | WhiteKing 94 | // WhiteQueen is a white queen 95 | WhiteQueen 96 | // WhiteRook is a white rook 97 | WhiteRook 98 | // WhiteBishop is a white bishop 99 | WhiteBishop 100 | // WhiteKnight is a white knight 101 | WhiteKnight 102 | // WhitePawn is a white pawn 103 | WhitePawn 104 | // BlackKing is a black king 105 | BlackKing 106 | // BlackQueen is a black queen 107 | BlackQueen 108 | // BlackRook is a black rook 109 | BlackRook 110 | // BlackBishop is a black bishop 111 | BlackBishop 112 | // BlackKnight is a black knight 113 | BlackKnight 114 | // BlackPawn is a black pawn 115 | BlackPawn 116 | ) 117 | 118 | var ( 119 | allPieces = []Piece{ 120 | WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight, WhitePawn, 121 | BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn, 122 | } 123 | ) 124 | 125 | func getPiece(t PieceType, c Color) Piece { 126 | for _, p := range allPieces { 127 | if p.Color() == c && p.Type() == t { 128 | return p 129 | } 130 | } 131 | return NoPiece 132 | } 133 | 134 | // Type returns the type of the piece. 135 | func (p Piece) Type() PieceType { 136 | switch p { 137 | case WhiteKing, BlackKing: 138 | return King 139 | case WhiteQueen, BlackQueen: 140 | return Queen 141 | case WhiteRook, BlackRook: 142 | return Rook 143 | case WhiteBishop, BlackBishop: 144 | return Bishop 145 | case WhiteKnight, BlackKnight: 146 | return Knight 147 | case WhitePawn, BlackPawn: 148 | return Pawn 149 | } 150 | return NoPieceType 151 | } 152 | 153 | // Color returns the color of the piece. 154 | func (p Piece) Color() Color { 155 | switch p { 156 | case WhiteKing, WhiteQueen, WhiteRook, WhiteBishop, WhiteKnight, WhitePawn: 157 | return White 158 | case BlackKing, BlackQueen, BlackRook, BlackBishop, BlackKnight, BlackPawn: 159 | return Black 160 | } 161 | return NoColor 162 | } 163 | 164 | // String implements the fmt.Stringer interface 165 | func (p Piece) String() string { 166 | return pieceUnicodes[int(p)] 167 | } 168 | 169 | var ( 170 | pieceUnicodes = []string{" ", "♔", "♕", "♖", "♗", "♘", "♙", "♚", "♛", "♜", "♝", "♞", "♟"} 171 | ) 172 | 173 | func (p Piece) getFENChar() string { 174 | for key, piece := range fenPieceMap { 175 | if piece == p { 176 | return key 177 | } 178 | } 179 | return "" 180 | } 181 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/position.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // Side represents a side of the board. 9 | type Side int 10 | 11 | const ( 12 | // KingSide is the right side of the board from white's perspective. 13 | KingSide Side = iota + 1 14 | // QueenSide is the left side of the board from white's perspective. 15 | QueenSide 16 | ) 17 | 18 | // CastleRights holds the state of both sides castling abilities. 19 | type CastleRights string 20 | 21 | // CanCastle returns true if the given color and side combination 22 | // can castle, otherwise returns false. 23 | func (cr CastleRights) CanCastle(c Color, side Side) bool { 24 | char := "k" 25 | if side == QueenSide { 26 | char = "q" 27 | } 28 | if c == White { 29 | char = strings.ToUpper(char) 30 | } 31 | return strings.Contains(string(cr), char) 32 | } 33 | 34 | // String implements the fmt.Stringer interface and returns 35 | // a FEN compatible string. Ex. KQq 36 | func (cr CastleRights) String() string { 37 | return string(cr) 38 | } 39 | 40 | // Position represents the state of the game without reguard 41 | // to its outcome. Position is translatable to FEN notation. 42 | type Position struct { 43 | board *Board 44 | turn Color 45 | castleRights CastleRights 46 | enPassantSquare Square 47 | halfMoveClock int 48 | moveCount int 49 | validMoves []*Move 50 | } 51 | 52 | // Update returns a new position resulting from the given move. 53 | // The move itself isn't validated, if validation is needed use 54 | // Game's Move method. This method is more performant for bots that 55 | // rely on the ValidMoves because it skips redundant validation. 56 | func (pos *Position) Update(m *Move) *Position { 57 | moveCount := pos.moveCount 58 | if pos.turn == Black { 59 | moveCount++ 60 | } 61 | cr := pos.CastleRights() 62 | ncr := pos.updateCastleRights(m) 63 | p := pos.board.piece(m.s1) 64 | halfMove := pos.halfMoveClock 65 | if p.Type() == Pawn || m.HasTag(Capture) || cr != ncr { 66 | halfMove = 0 67 | } else { 68 | halfMove++ 69 | } 70 | b := pos.board.copy() 71 | b.update(m) 72 | return &Position{ 73 | board: b, 74 | turn: pos.turn.Other(), 75 | castleRights: ncr, 76 | enPassantSquare: pos.updateEnPassantSquare(m), 77 | halfMoveClock: halfMove, 78 | moveCount: moveCount, 79 | } 80 | } 81 | 82 | // ValidMoves returns a list of valid moves for the position. 83 | func (pos *Position) ValidMoves() []*Move { 84 | if pos.validMoves != nil { 85 | return append([]*Move(nil), pos.validMoves...) 86 | } 87 | s2BB := ^pos.board.whiteSqs 88 | if pos.Turn() == Black { 89 | s2BB = ^pos.board.blackSqs 90 | } 91 | pos.validMoves = pos.getValidMoves(s2BB, getAll, false) 92 | return append([]*Move(nil), pos.validMoves...) 93 | } 94 | 95 | // Status returns the position's status as one of the outcome methods. 96 | // Possible returns values include Checkmate, Stalemate, and NoMethod. 97 | func (pos *Position) Status() Method { 98 | inCheck := pos.inCheck() 99 | hasMove := len(pos.ValidMoves()) > 0 100 | if !inCheck && !hasMove { 101 | return Stalemate 102 | } else if inCheck && !hasMove { 103 | return Checkmate 104 | } 105 | return NoMethod 106 | } 107 | 108 | // Board returns the position's board. 109 | func (pos *Position) Board() *Board { 110 | return pos.board 111 | } 112 | 113 | // Turn returns the color to move next. 114 | func (pos *Position) Turn() Color { 115 | return pos.turn 116 | } 117 | 118 | // CastleRights returns the castling rights of the position. 119 | func (pos *Position) CastleRights() CastleRights { 120 | return pos.castleRights 121 | } 122 | 123 | // String implements the fmt.Stringer interface and returns a 124 | // string with the FEN format: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 125 | func (pos *Position) String() string { 126 | b := pos.board.String() 127 | t := pos.turn.String() 128 | c := pos.castleRights.String() 129 | sq := "-" 130 | if pos.enPassantSquare != NoSquare { 131 | sq = pos.enPassantSquare.String() 132 | } 133 | return fmt.Sprintf("%s %s %s %s %d %d", b, t, c, sq, pos.halfMoveClock, pos.moveCount) 134 | } 135 | 136 | // MarshalText implements the encoding.TextMarshaler interface and 137 | // encodes the position's FEN. 138 | func (pos *Position) MarshalText() (text []byte, err error) { 139 | return []byte(pos.String()), nil 140 | } 141 | 142 | // UnmarshalText implements the encoding.TextUnarshaler interface and 143 | // assumes the data is in the FEN format. 144 | func (pos *Position) UnmarshalText(text []byte) error { 145 | cp, err := decodeFEN(string(text)) 146 | if err != nil { 147 | return err 148 | } 149 | pos.board = cp.board 150 | pos.turn = cp.turn 151 | pos.castleRights = cp.castleRights 152 | pos.enPassantSquare = cp.enPassantSquare 153 | pos.halfMoveClock = cp.halfMoveClock 154 | pos.moveCount = cp.moveCount 155 | return nil 156 | } 157 | 158 | func (pos *Position) copy() *Position { 159 | return &Position{ 160 | board: pos.board.copy(), 161 | turn: pos.turn, 162 | castleRights: pos.castleRights, 163 | enPassantSquare: pos.enPassantSquare, 164 | halfMoveClock: pos.halfMoveClock, 165 | moveCount: pos.moveCount, 166 | } 167 | } 168 | 169 | // TODO isn't working correctly, use in status method 170 | func (pos *Position) hasValidMove() bool { 171 | s2BB := ^pos.board.whiteSqs 172 | if pos.Turn() == Black { 173 | s2BB = ^pos.board.blackSqs 174 | } 175 | moves := pos.getValidMoves(s2BB, getFirst, false) 176 | return len(moves) > 0 177 | } 178 | 179 | func (pos *Position) updateCastleRights(m *Move) CastleRights { 180 | cr := string(pos.castleRights) 181 | p := pos.board.piece(m.s1) 182 | if p == WhiteKing || m.s1 == H1 { 183 | cr = strings.Replace(cr, "K", "", -1) 184 | } 185 | if p == WhiteKing || m.s1 == A1 { 186 | cr = strings.Replace(cr, "Q", "", -1) 187 | } 188 | if p == BlackKing || m.s1 == H8 { 189 | cr = strings.Replace(cr, "k", "", -1) 190 | } 191 | if p == BlackKing || m.s1 == A8 { 192 | cr = strings.Replace(cr, "q", "", -1) 193 | } 194 | if cr == "" { 195 | cr = "-" 196 | } 197 | return CastleRights(cr) 198 | } 199 | 200 | func (pos *Position) updateEnPassantSquare(m *Move) Square { 201 | p := pos.board.piece(m.s1) 202 | if p.Type() != Pawn { 203 | return NoSquare 204 | } 205 | if pos.turn == White && 206 | (bbSquares[m.s1]&bbRank2) != 0 && 207 | (bbSquares[m.s2]&bbRank4) != 0 { 208 | return Square(m.s2 - 8) 209 | } else if pos.turn == Black && 210 | (bbSquares[m.s1]&bbRank7) != 0 && 211 | (bbSquares[m.s2]&bbRank5) != 0 { 212 | return Square(m.s2 + 8) 213 | } 214 | return NoSquare 215 | } 216 | 217 | func (pos *Position) samePosition(pos2 *Position) bool { 218 | return pos.board.String() == pos2.board.String() && 219 | pos.turn == pos2.turn && 220 | pos.castleRights.String() == pos2.castleRights.String() && 221 | pos.enPassantSquare == pos2.enPassantSquare 222 | } 223 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/square.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | const ( 4 | numOfSquaresInBoard = 64 5 | numOfSquaresInRow = 8 6 | ) 7 | 8 | // A Square is one of the 64 rank and file combinations that make up a chess board. 9 | type Square int8 10 | 11 | // File returns the square's file. 12 | func (sq Square) File() File { 13 | return File(int(sq) % numOfSquaresInRow) 14 | } 15 | 16 | // Rank returns the square's rank. 17 | func (sq Square) Rank() Rank { 18 | return Rank(int(sq) / numOfSquaresInRow) 19 | } 20 | 21 | func (sq Square) String() string { 22 | return sq.File().String() + sq.Rank().String() 23 | } 24 | 25 | func (sq Square) color() Color { 26 | if ((sq / 8) % 2) == (sq % 2) { 27 | return Black 28 | } 29 | return White 30 | } 31 | 32 | func getSquare(f File, r Rank) Square { 33 | return Square((int(r) * 8) + int(f)) 34 | } 35 | 36 | const ( 37 | NoSquare Square = iota - 1 38 | A1 39 | B1 40 | C1 41 | D1 42 | E1 43 | F1 44 | G1 45 | H1 46 | A2 47 | B2 48 | C2 49 | D2 50 | E2 51 | F2 52 | G2 53 | H2 54 | A3 55 | B3 56 | C3 57 | D3 58 | E3 59 | F3 60 | G3 61 | H3 62 | A4 63 | B4 64 | C4 65 | D4 66 | E4 67 | F4 68 | G4 69 | H4 70 | A5 71 | B5 72 | C5 73 | D5 74 | E5 75 | F5 76 | G5 77 | H5 78 | A6 79 | B6 80 | C6 81 | D6 82 | E6 83 | F6 84 | G6 85 | H6 86 | A7 87 | B7 88 | C7 89 | D7 90 | E7 91 | F7 92 | G7 93 | H7 94 | A8 95 | B8 96 | C8 97 | D8 98 | E8 99 | F8 100 | G8 101 | H8 102 | ) 103 | 104 | const ( 105 | fileChars = "abcdefgh" 106 | rankChars = "12345678" 107 | ) 108 | 109 | // A Rank is the rank of a square. 110 | type Rank int8 111 | 112 | const ( 113 | Rank1 Rank = iota 114 | Rank2 115 | Rank3 116 | Rank4 117 | Rank5 118 | Rank6 119 | Rank7 120 | Rank8 121 | ) 122 | 123 | func (r Rank) String() string { 124 | return rankChars[r : r+1] 125 | } 126 | 127 | // A File is the file of a square. 128 | type File int8 129 | 130 | const ( 131 | FileA File = iota 132 | FileB 133 | FileC 134 | FileD 135 | FileE 136 | FileF 137 | FileG 138 | FileH 139 | ) 140 | 141 | func (f File) String() string { 142 | return fileChars[f : f+1] 143 | } 144 | 145 | var ( 146 | strToSquareMap = map[string]Square{ 147 | "a1": A1, "a2": A2, "a3": A3, "a4": A4, "a5": A5, "a6": A6, "a7": A7, "a8": A8, 148 | "b1": B1, "b2": B2, "b3": B3, "b4": B4, "b5": B5, "b6": B6, "b7": B7, "b8": B8, 149 | "c1": C1, "c2": C2, "c3": C3, "c4": C4, "c5": C5, "c6": C6, "c7": C7, "c8": C8, 150 | "d1": D1, "d2": D2, "d3": D3, "d4": D4, "d5": D5, "d6": D6, "d7": D7, "d8": D8, 151 | "e1": E1, "e2": E2, "e3": E3, "e4": E4, "e5": E5, "e6": E6, "e7": E7, "e8": E8, 152 | "f1": F1, "f2": F2, "f3": F3, "f4": F4, "f5": F5, "f6": F6, "f7": F7, "f8": F8, 153 | "g1": G1, "g2": G2, "g3": G3, "g4": G4, "g5": G5, "g6": G6, "g7": G7, "g8": G8, 154 | "h1": H1, "h2": H2, "h3": H3, "h4": H4, "h5": H5, "h6": H6, "h7": H7, "h8": H8, 155 | } 156 | ) 157 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/stringer.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type=Method -output=stringer.go; DO NOT EDIT 2 | 3 | package chess 4 | 5 | import "fmt" 6 | 7 | const _Method_name = "NoMethodCheckmateResignationDrawOfferStalemateThreefoldRepetitionFivefoldRepetitionFiftyMoveRuleSeventyFiveMoveRuleInsufficientMaterial" 8 | 9 | var _Method_index = [...]uint8{0, 8, 17, 28, 37, 46, 65, 83, 96, 115, 135} 10 | 11 | func (i Method) String() string { 12 | if i >= Method(len(_Method_index)-1) { 13 | return fmt.Sprintf("Method(%d)", i) 14 | } 15 | return _Method_name[_Method_index[i]:_Method_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chess/valid_moves.go: -------------------------------------------------------------------------------- 1 | package chess 2 | 3 | type moveFunc func(*Position, bitboard, searchType) []*Move 4 | 5 | var ( 6 | moveFuncs []moveFunc 7 | ) 8 | 9 | func init() { 10 | moveFuncs = []moveFunc{ 11 | kingMoves, pawnMoves, knightMoves, rookMoves, bishopMoves, queenMoves, 12 | } 13 | } 14 | 15 | func (pos *Position) getValidMoves(s2BB bitboard, st searchType, allowChecks bool) []*Move { 16 | moves := []*Move{} 17 | for _, f := range moveFuncs { 18 | result := f(pos, s2BB, st) 19 | for _, m := range result { 20 | if addMove(pos, m, allowChecks) { 21 | moves = append(moves, m) 22 | if st == getFirst { 23 | return moves 24 | } 25 | } 26 | } 27 | } 28 | // have to separate castle moves because of callback loop 29 | if !allowChecks { 30 | result := castleMoves(pos, s2BB, st) 31 | for _, m := range result { 32 | if addMove(pos, m, allowChecks) { 33 | moves = append(moves, m) 34 | if st == getFirst { 35 | return moves 36 | } 37 | } 38 | } 39 | } 40 | return moves 41 | } 42 | 43 | func addMove(pos *Position, m *Move, allowChecks bool) bool { 44 | if !allowChecks { 45 | pos.addTags(m) 46 | if m.HasTag(inCheck) { 47 | return false 48 | } 49 | } 50 | return true 51 | } 52 | 53 | type validMoveBB struct { 54 | bb bitboard 55 | shift int 56 | msb uint8 57 | lsb uint8 58 | } 59 | 60 | func (pos *Position) addTags(m *Move) { 61 | p := pos.board.piece(m.s1) 62 | if pos.board.isOccupied(m.s2) { 63 | m.addTag(Capture) 64 | } else if m.s2 == pos.enPassantSquare && p.Type() == Pawn { 65 | m.addTag(EnPassant) 66 | } 67 | // determine if in check after move (makes move invalid) 68 | cp := pos.copy() 69 | cp.board.update(m) 70 | if cp.inCheck() { 71 | m.addTag(inCheck) 72 | } 73 | // determine if opponent in check after move 74 | cp.turn = cp.turn.Other() 75 | if cp.inCheck() { 76 | m.addTag(Check) 77 | } 78 | } 79 | 80 | func (pos *Position) inCheck() bool { 81 | kingSq := pos.board.whiteKingSq 82 | if pos.Turn() == Black { 83 | kingSq = pos.board.blackKingSq 84 | } 85 | // king should only be missing in tests / examples 86 | if kingSq == NoSquare { 87 | return false 88 | } 89 | // if no piece is on a attacking square, there can't be a check 90 | kingBB := bbSquares[kingSq] 91 | s2BB := pos.board.blackSqs 92 | if pos.Turn() == Black { 93 | s2BB = pos.board.whiteSqs 94 | } 95 | occ := ^pos.board.emptySqs 96 | // check queen attack vector 97 | bb := (diaAttack(occ, kingSq) | hvAttack(occ, kingSq)) & s2BB 98 | if bb != 0 { 99 | return pos.squaresAreAttacked(kingSq) 100 | } 101 | // check knight attack vector 102 | bb = ((kingBB & ^(bbRank7 | bbRank8) & ^bbFileH) >> 17) & s2BB 103 | bb = (((kingBB & ^(bbRank7 | bbRank8) & ^bbFileA) >> 15) & s2BB) | bb 104 | bb = ((kingBB & ^bbRank8 & ^(bbFileG | bbFileH) >> 10) & s2BB) | bb 105 | bb = ((kingBB & ^bbRank1 & ^(bbFileG | bbFileH) << 6) & s2BB) | bb 106 | bb = (((kingBB & ^(bbRank1 | bbRank2) & ^bbFileH) << 15) & s2BB) | bb 107 | bb = (((kingBB & ^(bbRank1 | bbRank2) & ^bbFileA) << 17) & s2BB) | bb 108 | bb = ((kingBB & ^bbRank8 & ^(bbFileA | bbFileB) >> 6) & s2BB) | bb 109 | bb = ((kingBB & ^bbRank1 & ^(bbFileA | bbFileB) << 10) & s2BB) | bb 110 | if bb != 0 { 111 | return pos.squaresAreAttacked(kingSq) 112 | } 113 | return pos.squaresAreAttacked(kingSq) 114 | } 115 | 116 | func (pos *Position) squaresAreAttacked(sqs ...Square) bool { 117 | // make s2bb for attacked squares 118 | var s2BB bitboard 119 | for _, sq := range sqs { 120 | s2BB = s2BB | bbSquares[sq] 121 | } 122 | // toggle turn to get other colors moves 123 | cp := pos.copy() 124 | cp.turn = cp.turn.Other() 125 | cp.enPassantSquare = NoSquare 126 | moves := cp.getValidMoves(s2BB, getFirst, true) 127 | return len(moves) > 0 128 | } 129 | 130 | func castleMoves(pos *Position, s2BB bitboard, st searchType) []*Move { 131 | moves := []*Move{} 132 | kingSide := pos.castleRights.CanCastle(pos.Turn(), KingSide) 133 | queenSide := pos.castleRights.CanCastle(pos.Turn(), QueenSide) 134 | // white king side 135 | if pos.turn == White && kingSide && 136 | (^pos.board.emptySqs&(bbSquares[F1]|bbSquares[G1])) == 0 && 137 | !pos.squaresAreAttacked(F1, G1) && 138 | !pos.inCheck() { 139 | m := &Move{s1: E1, s2: G1} 140 | m.addTag(KingSideCastle) 141 | moves = append(moves, m) 142 | } 143 | // white queen side 144 | if pos.turn == White && queenSide && 145 | (^pos.board.emptySqs&(bbSquares[B1]|bbSquares[C1]|bbSquares[D1])) == 0 && 146 | !pos.squaresAreAttacked(C1, D1) && 147 | !pos.inCheck() { 148 | m := &Move{s1: E1, s2: C1} 149 | m.addTag(QueenSideCastle) 150 | moves = append(moves, m) 151 | } 152 | // black king side 153 | if pos.turn == Black && kingSide && 154 | (^pos.board.emptySqs&(bbSquares[F8]|bbSquares[G8])) == 0 && 155 | !pos.squaresAreAttacked(F8, G8) && 156 | !pos.inCheck() { 157 | m := &Move{s1: E8, s2: G8} 158 | m.addTag(KingSideCastle) 159 | moves = append(moves, m) 160 | } 161 | // black queen side 162 | if pos.turn == Black && queenSide && 163 | (^pos.board.emptySqs&(bbSquares[B8]|bbSquares[C8]|bbSquares[D8])) == 0 && 164 | !pos.squaresAreAttacked(C8, D8) && 165 | !pos.inCheck() { 166 | m := &Move{s1: E8, s2: C8} 167 | m.addTag(QueenSideCastle) 168 | moves = append(moves, m) 169 | } 170 | return moves 171 | } 172 | 173 | func pawnMoves(pos *Position, s2BB bitboard, st searchType) []*Move { 174 | var bbs []*validMoveBB 175 | var bbEnPassant bitboard 176 | if pos.enPassantSquare != NoSquare { 177 | bbEnPassant = bbSquares[pos.enPassantSquare] 178 | } 179 | p := NoPiece 180 | if pos.Turn() == White { 181 | p = WhitePawn 182 | bbWhite := pos.board.bbWhitePawn 183 | if bbWhite == 0 { 184 | return []*Move{} 185 | } 186 | bbWhiteCapRight := ((bbWhite & ^bbFileH & ^bbRank8) >> 9) & ((pos.board.blackSqs & s2BB) | bbEnPassant) 187 | bbWhiteCapLeft := ((bbWhite & ^bbFileA & ^bbRank8) >> 7) & ((pos.board.blackSqs & s2BB) | bbEnPassant) 188 | bbWhiteUpOne := ((bbWhite & ^bbRank8) >> 8) & (pos.board.emptySqs & s2BB) 189 | bbWhiteUpTwo := ((bbWhiteUpOne & bbRank3) >> 8) & (pos.board.emptySqs & s2BB) 190 | bbs = []*validMoveBB{ 191 | {bb: bbWhiteCapRight, shift: 9, lsb: 17, msb: 63}, 192 | {bb: bbWhiteCapLeft, shift: 7, lsb: 16, msb: 62}, 193 | {bb: bbWhiteUpOne, shift: 8, lsb: 16, msb: 63}, 194 | {bb: bbWhiteUpTwo, shift: 16, lsb: 24, msb: 31}, 195 | } 196 | } else { 197 | p = BlackPawn 198 | bbBlack := pos.board.bbBlackPawn 199 | if bbBlack == 0 { 200 | return []*Move{} 201 | } 202 | bbBlackCapRight := ((bbBlack & ^bbFileH & ^bbRank1) << 7) & ((pos.board.whiteSqs & s2BB) | bbEnPassant) 203 | bbBlackCapLeft := ((bbBlack & ^bbFileA & ^bbRank1) << 9) & ((pos.board.whiteSqs & s2BB) | bbEnPassant) 204 | bbBlackUpOne := ((bbBlack & ^bbRank1) << 8) & (pos.board.emptySqs & s2BB) 205 | bbBlackUpTwo := ((bbBlackUpOne & bbRank6) << 8) & (pos.board.emptySqs & s2BB) 206 | bbs = []*validMoveBB{ 207 | {bb: bbBlackCapRight, shift: -7, lsb: 1, msb: 47}, 208 | {bb: bbBlackCapLeft, shift: -9, lsb: 0, msb: 46}, 209 | {bb: bbBlackUpOne, shift: -8, lsb: 0, msb: 47}, 210 | {bb: bbBlackUpTwo, shift: -16, lsb: 32, msb: 39}, 211 | } 212 | } 213 | return steppingMoves(p, bbs, st) 214 | } 215 | 216 | func knightMoves(pos *Position, s2BB bitboard, st searchType) []*Move { 217 | bb := pos.board.bbWhiteKnight 218 | p := WhiteKnight 219 | if pos.Turn() == Black { 220 | bb = pos.board.bbBlackKnight 221 | p = BlackKnight 222 | } 223 | if bb == 0 { 224 | return []*Move{} 225 | } 226 | bbUpRight := ((bb & ^(bbRank7 | bbRank8) & ^bbFileH) >> 17) & s2BB 227 | bbUpLeft := ((bb & ^(bbRank7 | bbRank8) & ^bbFileA) >> 15) & s2BB 228 | bbRightUp := (bb & ^bbRank8 & ^(bbFileG | bbFileH) >> 10) & s2BB 229 | bbRightDown := (bb & ^bbRank1 & ^(bbFileG | bbFileH) << 6) & s2BB 230 | bbDownRight := ((bb & ^(bbRank1 | bbRank2) & ^bbFileH) << 15) & s2BB 231 | bbDownLeft := ((bb & ^(bbRank1 | bbRank2) & ^bbFileA) << 17) & s2BB 232 | bbLeftUp := (bb & ^bbRank8 & ^(bbFileA | bbFileB) >> 6) & s2BB 233 | bbLeftDown := (bb & ^bbRank1 & ^(bbFileA | bbFileB) << 10) & s2BB 234 | bbs := []*validMoveBB{ 235 | {bb: bbUpRight, shift: 17, lsb: 17, msb: 63}, 236 | {bb: bbUpLeft, shift: 15, lsb: 15, msb: 63}, 237 | {bb: bbRightUp, shift: 10, lsb: 10, msb: 63}, 238 | {bb: bbRightDown, shift: -6, lsb: 0, msb: 57}, 239 | {bb: bbDownRight, shift: -15, lsb: 0, msb: 48}, 240 | {bb: bbDownLeft, shift: -17, lsb: 0, msb: 46}, 241 | {bb: bbLeftUp, shift: 6, lsb: 6, msb: 63}, 242 | {bb: bbLeftDown, shift: -10, lsb: 0, msb: 53}, 243 | } 244 | return steppingMoves(p, bbs, st) 245 | } 246 | 247 | func kingMoves(pos *Position, s2BB bitboard, st searchType) []*Move { 248 | bb := pos.board.bbWhiteKing 249 | p := WhiteKing 250 | if pos.Turn() == Black { 251 | bb = pos.board.bbBlackKing 252 | p = BlackKing 253 | } 254 | if bb == 0 { 255 | return []*Move{} 256 | } 257 | bbUp := ((bb & ^bbRank8) >> 8) & s2BB 258 | bbUpRight := ((bb & ^bbRank8 & ^bbFileH) >> 9) & s2BB 259 | bbRight := ((bb & ^bbFileH) >> 1) & s2BB 260 | bbDownRight := ((bb & ^bbRank1 & ^bbFileH) << 7) & s2BB 261 | bbDown := ((bb & ^bbRank1) << 8) & s2BB 262 | bbDownLeft := ((bb & ^bbRank1 & ^bbFileA) << 9) & s2BB 263 | bbLeft := ((bb & ^bbFileA) << 1) & s2BB 264 | bbLeftUp := ((bb & ^bbRank8 & ^bbFileA) >> 7) & s2BB 265 | bbs := []*validMoveBB{ 266 | {bb: bbUp, shift: 8, lsb: 8, msb: 63}, 267 | {bb: bbUpRight, shift: 9, lsb: 9, msb: 63}, 268 | {bb: bbRight, shift: 1, lsb: 1, msb: 63}, 269 | {bb: bbDownRight, shift: -7, lsb: 0, msb: 56}, 270 | {bb: bbDown, shift: -8, lsb: 0, msb: 55}, 271 | {bb: bbDownLeft, shift: -9, lsb: 0, msb: 54}, 272 | {bb: bbLeft, shift: -1, lsb: 0, msb: 62}, 273 | {bb: bbLeftUp, shift: 7, lsb: 7, msb: 63}, 274 | } 275 | // todo use predetermined king square 276 | return steppingMoves(p, bbs, st) 277 | } 278 | 279 | type slidingBB struct { 280 | s1 Square 281 | bb bitboard 282 | } 283 | 284 | var ( 285 | rookFunc = func(pos *Position, sq Square, validBB bitboard) *slidingBB { 286 | bb := hvAttack(^pos.board.emptySqs, Square(sq)) & validBB 287 | return &slidingBB{bb: bb, s1: Square(sq)} 288 | } 289 | 290 | bishopFunc = func(pos *Position, sq Square, validBB bitboard) *slidingBB { 291 | bb := diaAttack(^pos.board.emptySqs, Square(sq)) & validBB 292 | return &slidingBB{bb: bb, s1: Square(sq)} 293 | } 294 | 295 | queenFunc = func(pos *Position, sq Square, validBB bitboard) *slidingBB { 296 | occ := ^pos.board.emptySqs 297 | bb := (diaAttack(occ, Square(sq)) | hvAttack(occ, Square(sq))) & validBB 298 | return &slidingBB{bb: bb, s1: Square(sq)} 299 | } 300 | ) 301 | 302 | func queenMoves(pos *Position, s2BB bitboard, st searchType) []*Move { 303 | return pos.slidingMoves(Queen, s2BB, st, queenFunc) 304 | } 305 | 306 | func rookMoves(pos *Position, s2BB bitboard, st searchType) []*Move { 307 | return pos.slidingMoves(Rook, s2BB, st, rookFunc) 308 | } 309 | 310 | func bishopMoves(pos *Position, s2BB bitboard, st searchType) []*Move { 311 | return pos.slidingMoves(Bishop, s2BB, st, bishopFunc) 312 | } 313 | 314 | func (pos *Position) slidingMoves(pt PieceType, s2BB bitboard, st searchType, f func(*Position, Square, bitboard) *slidingBB) []*Move { 315 | moves := []*Move{} 316 | p := getPiece(pt, pos.Turn()) 317 | bb := pos.board.bbForPiece(p) 318 | if bb == 0 { 319 | return moves 320 | } 321 | bbs := []*slidingBB{} 322 | for sq := 0; sq < 64; sq++ { 323 | if bb.Occupied(Square(sq)) { 324 | bbs = append(bbs, f(pos, Square(sq), s2BB)) 325 | } 326 | } 327 | for _, sBB := range bbs { 328 | for sq := 0; sq < 64; sq++ { 329 | if sBB.bb.Occupied(Square(sq)) { 330 | moves = append(moves, &Move{s1: sBB.s1, s2: Square(sq)}) 331 | if st == getFirst { 332 | return moves 333 | } 334 | } 335 | } 336 | } 337 | return moves 338 | } 339 | 340 | func diaAttack(occupied bitboard, sq Square) bitboard { 341 | pos := bbSquares[sq] 342 | dMask := bbDiagonal[sq] 343 | adMask := bbAntiDiagonal[sq] 344 | return linearAttack(occupied, pos, dMask) | linearAttack(occupied, pos, adMask) 345 | } 346 | 347 | func hvAttack(occupied bitboard, sq Square) bitboard { 348 | pos := bbSquares[sq] 349 | rankMask := bbRanks[Square(sq).Rank()] 350 | fileMask := bbFiles[Square(sq).File()] 351 | return linearAttack(occupied, pos, rankMask) | linearAttack(occupied, pos, fileMask) 352 | } 353 | 354 | func linearAttack(occupied, pos, mask bitboard) bitboard { 355 | oInMask := occupied & mask 356 | return ((oInMask - 2*pos) ^ (oInMask.Reverse() - 2*pos.Reverse()).Reverse()) & mask 357 | } 358 | 359 | type searchType uint8 360 | 361 | const ( 362 | getAll searchType = iota 363 | getFirst 364 | ) 365 | 366 | func steppingMoves(p Piece, bbs []*validMoveBB, st searchType) []*Move { 367 | moves := []*Move{} 368 | for _, validBB := range bbs { 369 | if validBB.bb == 0 { 370 | continue 371 | } 372 | // TODO reduce number of squares by range of LSB to MSB per bitboard 373 | for sq := validBB.lsb; sq <= validBB.msb; sq++ { 374 | if validBB.bb.Occupied(Square(sq)) { 375 | s1 := Square(int(sq) - validBB.shift) 376 | s2 := Square(sq) 377 | if (p == WhitePawn && s2.Rank() == Rank8) || (p == BlackPawn && s2.Rank() == Rank1) { 378 | qm := &Move{s1: s1, s2: s2, promo: Queen} 379 | rm := &Move{s1: s1, s2: s2, promo: Rook} 380 | bm := &Move{s1: s1, s2: s2, promo: Bishop} 381 | nm := &Move{s1: s1, s2: s2, promo: Knight} 382 | moves = append(moves, qm, rm, bm, nm) 383 | if st == getFirst { 384 | return moves 385 | } 386 | } else { 387 | moves = append(moves, &Move{s1: s1, s2: s2}) 388 | if st == getFirst { 389 | return moves 390 | } 391 | } 392 | } 393 | } 394 | } 395 | return moves 396 | } 397 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chessimg/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chessimg/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Logan Spears 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chessimg/README.md: -------------------------------------------------------------------------------- 1 | # chessimg 2 | [![GoDoc](https://godoc.org/github.com/loganjspears/chessimg?status.svg)](https://godoc.org/github.com/loganjspears/chessimg) 3 | [![Build Status](https://drone.io/github.com/loganjspears/chessimg/status.png)](https://drone.io/github.com/loganjspears/chessimg/latest) 4 | [![Coverage Status](https://coveralls.io/repos/github/loganjspears/chessimg/badge.svg?branch=master)](https://coveralls.io/github/loganjspears/chessimg?branch=master) 5 | [![Go Report Card](http://goreportcard.com/badge/loganjspears/chessimg)](http://goreportcard.com/report/loganjspears/chessimg) 6 | 7 | ### Code Example 8 | 9 | ```go 10 | // create file 11 | f, err := os.Create("example.svg") 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | // write image of position and marked squares to file 16 | fenStr := "rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1" 17 | mark := chessimg.MarkSquares(color.RGBA{255, 255, 0, 1}, chess.D2, chess.D4) 18 | if err := chessimg.New(f, mark).EncodeSVG(fenStr); err != nil { 19 | log.Fatal(err) 20 | } 21 | ``` 22 | 23 | ### Resulting Image 24 | 25 | ![rnbqkbnr/pppppppp/8/8/3P4/8/PPP1PPPP/RNBQKBNR b KQkq - 0 1](/example.png) 26 | 27 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chessimg/chessimg.go: -------------------------------------------------------------------------------- 1 | // Package chessimg is a go library designed to create images from board positions 2 | package chessimg 3 | 4 | import ( 5 | "fmt" 6 | "image/color" 7 | "io" 8 | "strings" 9 | 10 | "github.com/ajstarks/svgo" 11 | "github.com/loganjspears/chess" 12 | "github.com/loganjspears/chessimg/internal" 13 | ) 14 | 15 | // A Encoder encodes chess boards into images. 16 | type Encoder struct { 17 | w io.Writer 18 | light color.Color 19 | dark color.Color 20 | marks map[chess.Square]color.Color 21 | } 22 | 23 | // SquareColors is designed to be used as an optional argument 24 | // to the New function. It changes the default light and 25 | // dark square colors to the colors given. 26 | func SquareColors(light, dark color.Color) func(*Encoder) { 27 | return func(e *Encoder) { 28 | e.light = light 29 | e.dark = dark 30 | } 31 | } 32 | 33 | // MarkSquares is designed to be used as an optional argument 34 | // to the New function. It marks the given squares with the 35 | // color. A possible useage includes marking squares of the 36 | // previous move. 37 | func MarkSquares(c color.Color, sqs ...chess.Square) func(*Encoder) { 38 | return func(e *Encoder) { 39 | for _, sq := range sqs { 40 | e.marks[sq] = c 41 | } 42 | } 43 | } 44 | 45 | // New returns an encoder that writes to the given writer. 46 | // New also takes options which can customize the image 47 | // output. 48 | func New(w io.Writer, options ...func(*Encoder)) *Encoder { 49 | e := &Encoder{ 50 | w: w, 51 | light: color.RGBA{235, 209, 166, 1}, 52 | dark: color.RGBA{165, 117, 81, 1}, 53 | marks: map[chess.Square]color.Color{}, 54 | } 55 | for _, op := range options { 56 | op(e) 57 | } 58 | return e 59 | } 60 | 61 | const ( 62 | sqWidth = 45 63 | sqHeight = 45 64 | boardWidth = 8 * sqWidth 65 | boardHeight = 8 * sqHeight 66 | ) 67 | 68 | var ( 69 | orderOfRanks = []chess.Rank{chess.Rank8, chess.Rank7, chess.Rank6, chess.Rank5, chess.Rank4, chess.Rank3, chess.Rank2, chess.Rank1} 70 | orderOfFiles = []chess.File{chess.FileA, chess.FileB, chess.FileC, chess.FileD, chess.FileE, chess.FileF, chess.FileG, chess.FileH} 71 | ) 72 | 73 | // EncodeSVG writes the board SVG representation into 74 | // the Encoder's writer. An error is returned if there 75 | // is there is an error writing data. 76 | func (e *Encoder) EncodeSVG(fenStr string) error { 77 | fen, err := chess.FEN(fenStr) 78 | if err != nil { 79 | return err 80 | } 81 | g := chess.NewGame(fen) 82 | boardMap := g.Position().Board().SquareMap() 83 | canvas := svg.New(e.w) 84 | canvas.Start(boardWidth, boardHeight) 85 | canvas.Rect(0, 0, boardWidth, boardHeight) 86 | 87 | for i := 0; i < 64; i++ { 88 | sq := chess.Square(i) 89 | x, y := xyForSquare(sq) 90 | // draw square 91 | c := e.colorForSquare(sq) 92 | canvas.Rect(x, y, sqWidth, sqHeight, "fill: "+colorToHex(c)) 93 | markColor, ok := e.marks[sq] 94 | if ok { 95 | canvas.Rect(x, y, sqWidth, sqHeight, "fill-opacity:0.2;fill: "+colorToHex(markColor)) 96 | } 97 | // draw piece 98 | p := boardMap[sq] 99 | if p != chess.NoPiece { 100 | xml := pieceXML(x, y, p) 101 | if _, err := io.WriteString(canvas.Writer, xml); err != nil { 102 | return err 103 | } 104 | } 105 | // draw rank text on file A 106 | txtColor := e.colorForText(sq) 107 | if sq.File() == chess.FileA { 108 | style := "font-size:11px;fill: " + colorToHex(txtColor) 109 | canvas.Text(x+(sqWidth*1/20), y+(sqHeight*5/20), sq.Rank().String(), style) 110 | } 111 | // draw file text on rank 1 112 | if sq.Rank() == chess.Rank1 { 113 | style := "text-anchor:end;font-size:11px;fill: " + colorToHex(txtColor) 114 | canvas.Text(x+(sqWidth*19/20), y+sqHeight-(sqHeight*1/15), sq.File().String(), style) 115 | } 116 | } 117 | canvas.End() 118 | return nil 119 | } 120 | 121 | func (e *Encoder) colorForSquare(sq chess.Square) color.Color { 122 | sqSum := int(sq.File()) + int(sq.Rank()) 123 | if sqSum%2 == 0 { 124 | return e.dark 125 | } 126 | return e.light 127 | } 128 | 129 | func (e *Encoder) colorForText(sq chess.Square) color.Color { 130 | sqSum := int(sq.File()) + int(sq.Rank()) 131 | if sqSum%2 == 0 { 132 | return e.light 133 | } 134 | return e.dark 135 | } 136 | 137 | func xyForSquare(sq chess.Square) (x, y int) { 138 | fileIndex := int(sq.File()) 139 | rankIndex := 7 - int(sq.Rank()) 140 | return fileIndex * sqWidth, rankIndex * sqHeight 141 | } 142 | 143 | func colorToHex(c color.Color) string { 144 | r, g, b, _ := c.RGBA() 145 | 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)) 146 | } 147 | 148 | func pieceXML(x, y int, p chess.Piece) string { 149 | fileName := fmt.Sprintf("pieces/%s%s.svg", p.Color().String(), pieceTypeMap[p.Type()]) 150 | svgStr := string(internal.MustAsset(fileName)) 151 | old := `` 152 | new := fmt.Sprintf(``, (-1 * x), (-1 * y)) 153 | return strings.Replace(svgStr, old, new, 1) 154 | } 155 | 156 | var ( 157 | pieceTypeMap = map[chess.PieceType]string{ 158 | chess.King: "K", 159 | chess.Queen: "Q", 160 | chess.Rook: "R", 161 | chess.Bishop: "B", 162 | chess.Knight: "N", 163 | chess.Pawn: "P", 164 | } 165 | ) 166 | -------------------------------------------------------------------------------- /vendor/github.com/loganjspears/chessimg/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notnil/slackchess/6141ce6aeee55359164bdfea5c8f866a6e1e705f/vendor/github.com/loganjspears/chessimg/example.png --------------------------------------------------------------------------------