├── .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 | [](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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
447 |
448 |
449 | Polyline(x []int, y []int, s ...string)
450 | draw a polygon using coordinates specified in x,y arrays.
451 |
452 |
453 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
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 | [](https://godoc.org/github.com/gorilla/schema) [](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 |
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 |
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 |
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 | [](https://godoc.org/github.com/loganjspears/chess)
3 | [](https://drone.io/github.com/loganjspears/chess/latest)
4 | [](https://coveralls.io/github/loganjspears/chess?branch=master)
5 | [](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 | [](https://godoc.org/github.com/loganjspears/chessimg)
3 | [](https://drone.io/github.com/loganjspears/chessimg/latest)
4 | [](https://coveralls.io/github/loganjspears/chessimg?branch=master)
5 | [](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 | 
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 := `