From 2b1d67f3cd7790f241a4dad2849fb3b9706c32d6 Mon Sep 17 00:00:00 2001 From: Gregory Rudolph Date: Fri, 30 Oct 2020 11:17:33 -0400 Subject: [PATCH] Initial commit --- .gitignore | 4 ++ README.md | 3 + chess.go | 140 +++++++++++++++++++++++++++++++++++++++++++++ config.go | 30 ++++++++++ config.json.sample | 11 ++++ go.mod | 12 ++++ go.sum | 29 ++++++++++ main.go | 69 ++++++++++++++++++++++ types.go | 23 ++++++++ 9 files changed, 321 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 chess.go create mode 100644 config.go create mode 100644 config.json.sample create mode 100644 go.mod create mode 100644 go.sum create mode 100755 main.go create mode 100644 types.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6cb8a56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +games/* +.bashrc +chessbot.log +chessbot \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dabf0d9 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Required for `go build` + export CGO_CFLAGS_ALLOW='-l.*' + export CGO_LDFLAGS_ALLOW='-I.*' diff --git a/chess.go b/chess.go new file mode 100644 index 0000000..db4def1 --- /dev/null +++ b/chess.go @@ -0,0 +1,140 @@ +package main + +import ( + "fmt" + "image/color" + "os" + "strings" + "time" + + "github.com/notnil/chess" + "github.com/notnil/chess/image" + "samhofi.us/x/keybase/v2/types/chat1" +) + +func chessCommand(m chat1.MsgSummary) { + defer log.PanicSafe() + defer saveConfig() + convID := string(m.ConvID) + parts := strings.Split(m.Content.Text.Body, " ") + if g, ok := config.Games[convID]; ok { + if len(parts) != 2 { + return + } + if parts[1] == "show" { + go showBoard(m, &g) + return + } + + submitMove(m, &g) + return + } + if len(parts) < 3 { + return + } + coin := r.Intn(100) + log.LogInfo(fmt.Sprintf("Coin toss resulted in %+v\n", coin)) + var game Game + if coin%2 == 0 { + if len(parts) == 4 && parts[3] == "heads" { + game.White = m.Sender.Username + game.Black = strings.Replace(parts[2], "@", "", -1) + + } else { + game.Black = m.Sender.Username + game.White = strings.Replace(parts[2], "@", "", -1) + } + } else { + if len(parts) == 4 && parts[3] == "heads" { + game.Black = m.Sender.Username + game.White = strings.Replace(parts[2], "@", "", -1) + } else { + game.White = m.Sender.Username + game.Black = strings.Replace(parts[2], "@", "", -1) + } + } + log.LogDebug("There are currently %+v games.\n", len(config.Games)) + g := chess.NewGame() + game.Game = g + game.ConvID = convID + game.Move = true + game.StartTime = time.Now() + config.Games[convID] = game + k.SendMessageByConvID(m.ConvID, fmt.Sprintf("I have created a new game for this conversation @%+v is playing as White, @%+v is playing as Black.", game.White, game.Black)) + k.SendMessageByConvID(m.ConvID, fmt.Sprintf("Possible commands are `@chessbot show` to show the board, or `@chessbot forfeit`. Otherwise I will be expecting a move in Algebraic Notation.")) + +} + +func showBoard(m chat1.MsgSummary, g *Game) { + defer log.PanicSafe() + // create file + f, err := os.Create(fmt.Sprintf("/home/chessbot/chessbot/games/%+v.svg", m.ConvID)) + if err != nil { + log.LogError(fmt.Sprintf("Error in ShowBoard os.Create: %+v\n", err)) + } + + // create board position + fenStr := g.Game.Position().String() + pos := &chess.Position{} + if err := pos.UnmarshalText([]byte(fenStr)); err != nil { + log.LogError(fmt.Sprintf("Error in ShowBoard pos.UnmasrhalText: %+v\n", err)) + } + if len(g.Game.Moves()) != 0 { + // write board SVG to file + yellow := color.RGBA{255, 255, 0, 1} + lastMove := g.Game.Moves()[len(g.Game.Moves())-1] + + mark := image.MarkSquares(yellow, lastMove.S1(), lastMove.S2()) + if err := image.SVG(f, pos.Board(), mark); err != nil { + log.LogError("Error in ShowBoard image.SVG Mark: %+v\n", err) + } + } else { + if err := image.SVG(f, pos.Board()); err != nil { + log.LogError("Error in ShowBoard image.SVG: %+v\n", err) + } + + } + f.Close() + if svgToPNG(fmt.Sprintf("/home/chessbot/chessbot/games/%+v", m.ConvID)) { + k.UploadToConversation(m.ConvID, "", fmt.Sprintf("/home/chessbot/chessbot/games/%+v.png", m.ConvID)) + } else { + k.SendMessageByConvID(m.ConvID, fmt.Sprintf("```%+v```", g.Game.Position().Board().Draw())) + } +} + +func submitMove(m chat1.MsgSummary, g *Game) { + defer log.PanicSafe() + parts := strings.Split(m.Content.Text.Body, " ") + if g.Move { + if m.Sender.Username != g.White { + k.SendMessageByConvID(m.ConvID, "Wait your turn.") + return + } + } else { + if m.Sender.Username != g.Black { + k.SendMessageByConvID(m.ConvID, "Wait your turn.") + return + } + } + if parts[1] == "forfeit" || parts[1] == "surrender" { + if g.Move { + g.Game.Resign(chess.White) + } else { + g.Game.Resign(chess.Black) + } + } else { + err := g.Game.MoveStr(parts[1]) + if err != nil { + k.SendMessageByConvID(m.ConvID, "There was an error with your move.") + log.LogDebug("%+v\n", err) + return + } + } + g.Move = !g.Move + + if g.Game.Outcome() != chess.NoOutcome { + k.SendMessageByConvID(m.ConvID, fmt.Sprintf("Game completed in %+v. %s by %s.\n", time.Since(g.StartTime), g.Game.Outcome(), g.Game.Method())) + delete(config.Games, g.ConvID) + } + go showBoard(m, g) +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..6b03ba4 --- /dev/null +++ b/config.go @@ -0,0 +1,30 @@ +package main + +import ( + "encoding/json" + "io/ioutil" +) + +func loadConfig() { + var c Config + confFile, _ := ioutil.ReadFile(configFile) + err := json.Unmarshal([]byte(confFile), &c) + if err != nil { + log.LogErrorType(err) + return + } + config = c + log.LogInfo("Setup completed using config file.") +} + +func saveConfig() { + defer log.PanicSafe() + file, err := json.Marshal(config) + if err != nil { + log.LogErrorType(err) + } + err = ioutil.WriteFile(configFile, file, 0600) + if err != nil { + log.LogErrorType(err) + } +} diff --git a/config.json.sample b/config.json.sample new file mode 100644 index 0000000..395e78f --- /dev/null +++ b/config.json.sample @@ -0,0 +1,11 @@ +{ + "Games": {}, + "LogOpts": { + "OutFile": "", + "KBTeam": "", + "KBChann": "", + "Level": 5, + "ProgName": "chessbot", + "UseStdout": true + } +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4c7cdfb --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module git.nightmare.haus/rudi/chessbot + +go 1.15 + +require ( + github.com/bwmarrin/discordgo v0.22.0 + github.com/llgcode/draw2d v0.0.0-20200930101115-bfaf5d914d1e + github.com/notnil/chess v1.2.0 + github.com/rogpeppe/misc v0.0.0-20200516092017-43a33a8f4c44 + github.com/rudi9719/loggy v0.0.0-20201030144506-35d9b032ab93 + samhofi.us/x/keybase/v2 v2.0.6 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa4c363 --- /dev/null +++ b/go.sum @@ -0,0 +1,29 @@ +github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA= +github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIxDdFM= +github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= +github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= +github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/llgcode/draw2d v0.0.0-20200930101115-bfaf5d914d1e h1:YRRazju3DMGuZTSWEj0nE2SCRcK3DW/qdHQ4UQx7sgs= +github.com/llgcode/draw2d v0.0.0-20200930101115-bfaf5d914d1e/go.mod h1:mVa0dA29Db2S4LVqDYLlsePDzRJLDfdhVZiI15uY0FA= +github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY= +github.com/notnil/chess v1.2.0 h1:5We6movgg1yzqBa68xsi0gWBM9XMIRfiaNc0Kcq6guk= +github.com/notnil/chess v1.2.0/go.mod h1:cRuJUIBFq9Xki05TWHJxHYkC+fFpq45IWwk94DdlCrA= +github.com/rogpeppe/misc v0.0.0-20200516092017-43a33a8f4c44 h1:osBnWuJgXxjdv/zUO9sm32B1Peew1cPGGyonHrqXd5M= +github.com/rogpeppe/misc v0.0.0-20200516092017-43a33a8f4c44/go.mod h1:hT7K6J2LNwUaaq7rtwF5VMB9kQ9yf7HgYFNVOOf41OI= +github.com/rudi9719/loggy v0.0.0-20201030144506-35d9b032ab93 h1:GjCI3CXdGt2PysVpObVzmKqMgKf3RlsKLBnaSOqPpUk= +github.com/rudi9719/loggy v0.0.0-20201030144506-35d9b032ab93/go.mod h1:s1ANCN8bF6HwwTpJLR458MFVGua9oqKKDbph/2jptL4= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= +golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +samhofi.us/x/keybase v0.0.0-20200129212102-e05e93be9f3f h1:MHSEiuiRFrFi7BTw46lC22PMk3Fit8IvVRM4xANTt20= +samhofi.us/x/keybase v0.0.0-20200129212102-e05e93be9f3f/go.mod h1:fcva80IUFyWcHtV4bBSzgKg07K6Rvuvi3GtGCLNGkyE= +samhofi.us/x/keybase v0.0.0-20200409184719-3e5afcc7f988 h1:ZQDHB2AP5ouuMnANibyBJQBbxwi+tCMQHa4ttp15ac0= +samhofi.us/x/keybase/v2 v2.0.6 h1:gLluTcyjbwckQxSarF1ig2klL4Li7O/THdxsgo1dUvw= +samhofi.us/x/keybase/v2 v2.0.6/go.mod h1:lJivwhzMSV+WUg+XUbatszStjjFVcuLGl+xcQpqQ5GQ= diff --git a/main.go b/main.go new file mode 100755 index 0000000..f6a3e48 --- /dev/null +++ b/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + "image" + "math/rand" + "os" + "strings" + "time" + + "github.com/llgcode/draw2d/draw2dimg" + "github.com/rogpeppe/misc/svg" + "github.com/rudi9719/loggy" + "samhofi.us/x/keybase/v2" + "samhofi.us/x/keybase/v2/types/chat1" +) + +var ( + k = keybase.NewKeybase() + + configFile = "config.json" + config Config + log = loggy.NewLogger(config.LogOpts) + r = rand.New(rand.NewSource(time.Now().UnixNano())) +) + +func printChat(m chat1.MsgSummary) { + defer log.PanicSafe() + if m.Sender.Username == k.Username { + return + } + if strings.HasPrefix(m.Content.Text.Body, "@chessbot") { + chessCommand(m) + return + } + log.LogInfo("%s: %s\n", m.Sender.Username, m.Content.Text.Body) +} +func main() { + loadConfig() + log = loggy.NewLogger(config.LogOpts) + defer log.PanicSafe() + chat := printChat + err := log.LogErrorType + handlers := keybase.Handlers{ + ChatHandler: &chat, + ErrorHandler: &err, + } + log.LogCritical("Starting Chessbot") + k.Run(handlers, &keybase.RunOptions{}) +} +func svgToPNG(path string) bool { + defer log.PanicSafe() + file, err := os.Open(fmt.Sprintf("%+v.svg", path)) + if err != nil { + return false + } + defer file.Close() + size := image.Point{512, 512} + dest, err := svg.Render(file, size) + if err != nil { + return false + } + err = draw2dimg.SaveToPngFile(fmt.Sprintf("%+v.png", path), dest) + if err != nil { + return false + } + return true + +} diff --git a/types.go b/types.go new file mode 100644 index 0000000..56b6a46 --- /dev/null +++ b/types.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/notnil/chess" + "github.com/rudi9719/loggy" + "time" +) + +// Game is just a struct of type Game +type Game struct { + Game *chess.Game + StartTime time.Time + ConvID string + White string + Black string + Move bool +} + +// Config struct for restarting game +type Config struct { + Games map[string]Game + LogOpts loggy.LogOpts +} \ No newline at end of file