Move bot to its own repo
This commit is contained in:
commit
6bce4375da
11 changed files with 1680 additions and 0 deletions
37
bot/bot.go
Normal file
37
bot/bot.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
"git.fromouter.space/mcg/draft"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bot implements a bot for filling spots in draft pods
|
||||||
|
type Bot struct {
|
||||||
|
player *draft.Player
|
||||||
|
|
||||||
|
// Currently unused, the bot just picks at random.
|
||||||
|
// In the future, these fields will be used for having the bot try to assemble
|
||||||
|
// a sort-of playable deck. The actual deck doesn't matter, just the fact
|
||||||
|
// that their picks make some sort of sense.
|
||||||
|
color1 string
|
||||||
|
color2 string
|
||||||
|
entryCount int
|
||||||
|
friendCount int
|
||||||
|
otherCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeBot returns a bot for a given pod spot
|
||||||
|
func MakeBot(player *draft.Player) *Bot {
|
||||||
|
bot := &Bot{
|
||||||
|
player: player,
|
||||||
|
}
|
||||||
|
return bot
|
||||||
|
}
|
||||||
|
|
||||||
|
// PickNext makes the bot pick a card from his pack
|
||||||
|
func (b *Bot) PickNext() {
|
||||||
|
// For now, just pick a card at random
|
||||||
|
cardid := rand.Intn(len(b.player.CurrentPack))
|
||||||
|
b.player.Pick(b.player.CurrentPack[cardid])
|
||||||
|
}
|
66
bot/bot_test.go
Normal file
66
bot/bot_test.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package bot_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.fromouter.space/mcg/mlp-server-tools/draftbot/bot"
|
||||||
|
|
||||||
|
"git.fromouter.space/mcg/draft"
|
||||||
|
)
|
||||||
|
|
||||||
|
const PACKSIZE = 5
|
||||||
|
|
||||||
|
// Test set that can be used by tests that don't need special features (like alternates)
|
||||||
|
var testSet = &draft.GenericSet{
|
||||||
|
Cards: []draft.Card{{ID: "a"}, {ID: "b"}, {ID: "c"}},
|
||||||
|
PackSize: PACKSIZE,
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPick(t *testing.T) {
|
||||||
|
const PacksPerPlayer = 3
|
||||||
|
const PlayersPerPod = 5
|
||||||
|
|
||||||
|
// Get provider for test set
|
||||||
|
testProvider := draft.PacksFromSet(PacksPerPlayer, testSet)
|
||||||
|
|
||||||
|
// Create pod
|
||||||
|
pod := draft.MakePod(PlayersPerPod, testProvider)
|
||||||
|
|
||||||
|
// Create a bot for each player in the pod
|
||||||
|
var bots []*bot.Bot
|
||||||
|
for _, player := range pod.Players {
|
||||||
|
bots = append(bots, bot.MakeBot(player))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate a round of drafting
|
||||||
|
// Repeat until all packs are gone
|
||||||
|
// Channels are tested when they should trigger
|
||||||
|
for packnum := 0; packnum < PacksPerPlayer; packnum++ {
|
||||||
|
|
||||||
|
// Open new packs!
|
||||||
|
err := pod.OpenPacks()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Got an error while opening packs #%d: %s", packnum, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for picknum := 0; picknum < PACKSIZE; picknum++ {
|
||||||
|
for _, bot := range bots {
|
||||||
|
bot.PickNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure either ReadyNextPick or ReadyNextPack triggers
|
||||||
|
select {
|
||||||
|
case <-pod.ReadyNextPick:
|
||||||
|
// Pass packs around
|
||||||
|
err := pod.NextPacks()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Got an error while passing packs: %s", err.Error())
|
||||||
|
}
|
||||||
|
case <-pod.ReadyNextPack:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
t.Fatal("Either ReadyNextPick/ReadyNextPack should trigger but neither has")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
cmd/cgdraftbot/main.go
Normal file
97
cmd/cgdraftbot/main.go
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package main // import "git.fromouter.space/mcg/mlp-server-tools/draftbot"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.fromouter.space/Artificiale/moa/sd"
|
||||||
|
botapi "git.fromouter.space/mcg/cardgage/client/bot"
|
||||||
|
"git.fromouter.space/mcg/draft/mlp"
|
||||||
|
bot "git.fromouter.space/mcg/mlp-server-tools/draftbot"
|
||||||
|
"github.com/go-kit/kit/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger log.Logger
|
||||||
|
var logAll bool
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
consulAddr := flag.String("consul.addr", "consul:8500", "Consul address")
|
||||||
|
botName := flag.String("bot.name", "draftbot", "Bot name")
|
||||||
|
gameFilter := flag.String("filter.game", "mlpccg-mcg", "What game to filter for (separated by comma)")
|
||||||
|
tagFilter := flag.String("filter.tag", "draft", "What tags to filter for (separated by comma)")
|
||||||
|
flag.BoolVar(&logAll, "debug.log", false, "Log a lot of stuff")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
logger = log.NewLogfmtLogger(os.Stderr)
|
||||||
|
logger = log.With(logger, "ts", log.DefaultTimestampUTC)
|
||||||
|
logger = log.With(logger, "caller", log.DefaultCaller)
|
||||||
|
|
||||||
|
// Register with consul
|
||||||
|
registrar := sd.Register(*consulAddr, sd.Options{
|
||||||
|
Name: "draftbot",
|
||||||
|
Tags: []string{
|
||||||
|
"bot", // I am a room bot
|
||||||
|
},
|
||||||
|
}, logger)
|
||||||
|
defer registrar.Deregister()
|
||||||
|
|
||||||
|
// Seed RNG
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
|
// Initialize consul client for service discovery
|
||||||
|
sd.InitClient(*consulAddr)
|
||||||
|
|
||||||
|
errs := make(chan error)
|
||||||
|
go func() {
|
||||||
|
c := make(chan os.Signal)
|
||||||
|
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
errs <- fmt.Errorf("%s", <-c)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
games := strings.Split(*gameFilter, ",")
|
||||||
|
tags := strings.Split(*tagFilter, ",")
|
||||||
|
errs <- runBot(*botName, games, tags)
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger.Log("exit", <-errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runBot(name string, games, tags []string) error {
|
||||||
|
// Search for roomsvc
|
||||||
|
roomsvc, err := sd.GetOne("roomsvc")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all sets into memory
|
||||||
|
err = mlp.LoadAllSets()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("ws://%s:%d/bot", roomsvc.Service.Address, roomsvc.Service.Port)
|
||||||
|
|
||||||
|
wsbot, err := botapi.NewBot(addr, botapi.Params{
|
||||||
|
Name: name,
|
||||||
|
MsgBufferSize: 10,
|
||||||
|
Logger: logger,
|
||||||
|
GameIDs: games,
|
||||||
|
Tags: tags,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
draftbot := bot.NewDraftBot(wsbot, name)
|
||||||
|
wsbot.Listen(draftbot.OnMessage)
|
||||||
|
|
||||||
|
return errors.New("eof")
|
||||||
|
}
|
81
cube.go
Normal file
81
cube.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package draftbot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.fromouter.space/mcg/draft"
|
||||||
|
"git.fromouter.space/mcg/draft/mlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadCube(cubeURL string) ([]draft.Card, error) {
|
||||||
|
// Fetch document
|
||||||
|
resp, err := http.Get(cubeURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each line is a card ID
|
||||||
|
cardids := strings.Split(string(respBytes), "\n")
|
||||||
|
cards := make([]draft.Card, 0)
|
||||||
|
// Convert to draft.Card
|
||||||
|
for _, cardid := range cardids {
|
||||||
|
cardid := strings.TrimSpace(cardid)
|
||||||
|
if len(cardid) < 1 {
|
||||||
|
// Skip empty lines
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cards = append(cards, draft.Card{ID: cardid})
|
||||||
|
}
|
||||||
|
return cards, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// I8PCubeConfig is an external JSON doc with the I8PCube pack seeding schema and card list
|
||||||
|
type I8PCubeConfig struct {
|
||||||
|
Schema mlp.I8PSchema
|
||||||
|
ProblemPackSize int
|
||||||
|
Cards map[mlp.I8PType][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToCube creates a I8PCube from the config
|
||||||
|
func (cfg *I8PCubeConfig) ToCube() (*mlp.I8PCube, error) {
|
||||||
|
// Load cards from given list
|
||||||
|
var err error
|
||||||
|
pool := make(mlp.I8PPool)
|
||||||
|
for typ, cardids := range cfg.Cards {
|
||||||
|
pool[typ], err = mlp.LoadCardList(cardids, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make cube and return it
|
||||||
|
return mlp.MakeI8PCube(pool, cfg.Schema, cfg.ProblemPackSize), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadI8PCube(cubeURL string) (*mlp.I8PCube, error) {
|
||||||
|
// Fetch document
|
||||||
|
resp, err := http.Get(cubeURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize response to JSON object
|
||||||
|
var cubeconf I8PCubeConfig
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&cubeconf)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create cube from config
|
||||||
|
return cubeconf.ToCube()
|
||||||
|
}
|
152
draftbot.go
Normal file
152
draftbot.go
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
package draftbot
|
||||||
|
|
||||||
|
import (
|
||||||
|
room "git.fromouter.space/mcg/cardgage/room/api"
|
||||||
|
"github.com/go-kit/kit/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DraftBot is the functional part of draftbot
|
||||||
|
type DraftBot struct {
|
||||||
|
API BotInterface
|
||||||
|
Logger log.Logger
|
||||||
|
Name string
|
||||||
|
Rooms map[string]roomInfo
|
||||||
|
Sessions map[string]*session
|
||||||
|
}
|
||||||
|
|
||||||
|
type roomInfo struct {
|
||||||
|
Name string
|
||||||
|
Owner string
|
||||||
|
}
|
||||||
|
|
||||||
|
// BotInterface is the interface needed by draftbot for working in cardgage
|
||||||
|
// This exists so that draftbot can be attached to a mocked API for testing
|
||||||
|
type BotInterface interface {
|
||||||
|
Send(room.BotMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDraftBot creates a new draft bot instance with the given name
|
||||||
|
// and communication interface
|
||||||
|
func NewDraftBot(botAPI BotInterface, name string) *DraftBot {
|
||||||
|
return &DraftBot{
|
||||||
|
API: botAPI,
|
||||||
|
Name: name,
|
||||||
|
Rooms: make(map[string]roomInfo),
|
||||||
|
Sessions: make(map[string]*session),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnMessage is the function to be called when messages are received
|
||||||
|
func (d *DraftBot) OnMessage(msg room.ServerMessage) {
|
||||||
|
switch msg.Type {
|
||||||
|
case room.MsgMessage:
|
||||||
|
if d.Logger != nil {
|
||||||
|
d.Logger.Log("event", "message",
|
||||||
|
"roomid", msg.RoomID,
|
||||||
|
"from", msg.Message.From,
|
||||||
|
"to", msg.Message.To,
|
||||||
|
"content", msg.Message.Message)
|
||||||
|
}
|
||||||
|
// Only consider messages that speak directly to me
|
||||||
|
if msg.Message.To == d.Name {
|
||||||
|
d.handleMessage(msg.RoomID, *msg.Message)
|
||||||
|
}
|
||||||
|
case room.MsgEvent:
|
||||||
|
if d.Logger != nil {
|
||||||
|
d.Logger.Log("event", "event",
|
||||||
|
"roomid", msg.RoomID,
|
||||||
|
"content", msg.Event.Message)
|
||||||
|
}
|
||||||
|
d.handleEvent(msg.RoomID, *msg.Event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DraftBot) sendMessage(roomid string, msg room.Message) {
|
||||||
|
d.API.Send(room.BotMessage{
|
||||||
|
RoomID: roomid,
|
||||||
|
Type: room.MsgMessage,
|
||||||
|
Message: &msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DraftBot) handleMessage(roomid string, msg room.Message) {
|
||||||
|
// Get session in room
|
||||||
|
session, ok := d.Sessions[roomid]
|
||||||
|
if !ok {
|
||||||
|
// Room does not have a currently running session, ignore unless it's the owner asking for specific stuff
|
||||||
|
d.commands(commandMap{
|
||||||
|
// Owner wants to create a session
|
||||||
|
"create": d.cmdfnOnlyOwner(d.cmdCreateSession),
|
||||||
|
})(roomid, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if player is in the draft
|
||||||
|
_, ok = session.Players[msg.From]
|
||||||
|
if !ok {
|
||||||
|
// Player not in draft, are they asking to join?
|
||||||
|
d.commands(commandMap{
|
||||||
|
// Player wants to join the session
|
||||||
|
"join": d.cmdJoinSession,
|
||||||
|
// Owner wants to start the session (but not partecipate)
|
||||||
|
"start": d.cmdfnOnlyOwner(d.cmdStartSession),
|
||||||
|
})(roomid, msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Players in the draft session
|
||||||
|
d.commands(commandMap{
|
||||||
|
// Player has picked a card
|
||||||
|
"pick": d.cmdPickCard,
|
||||||
|
// Owner wants to start the session
|
||||||
|
"start": d.cmdfnOnlyOwner(d.cmdStartSession),
|
||||||
|
})(roomid, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DraftBot) handleEvent(roomid string, evt room.Event) {
|
||||||
|
switch evt.Type {
|
||||||
|
|
||||||
|
// Got added to a new room
|
||||||
|
case room.EvtNewRoom:
|
||||||
|
// Add room to the list
|
||||||
|
d.Rooms[evt.Room.Id] = roomInfo{
|
||||||
|
Name: evt.Room.Name,
|
||||||
|
Owner: evt.Room.Creator,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Someone left
|
||||||
|
case room.EvtLeft:
|
||||||
|
// Check if room has a running session
|
||||||
|
sess, ok := d.Sessions[roomid]
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Check if player is in that session
|
||||||
|
player, ok := sess.Players[evt.PlayerName]
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Replace player with bot
|
||||||
|
//TODO
|
||||||
|
_ = player
|
||||||
|
|
||||||
|
// A room got closed
|
||||||
|
case room.EvtRoomClosed:
|
||||||
|
// Check if there's a session there
|
||||||
|
session, ok := d.Sessions[roomid]
|
||||||
|
if ok {
|
||||||
|
// Close session
|
||||||
|
session.exit <- true
|
||||||
|
// Remove session from list
|
||||||
|
delete(d.Sessions, roomid)
|
||||||
|
}
|
||||||
|
// Remove room from list
|
||||||
|
delete(d.Rooms, roomid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DraftBot) handleSessionMessages(roomid string, s *session) {
|
||||||
|
for msg := range s.messages {
|
||||||
|
d.sendMessage(roomid, msg)
|
||||||
|
}
|
||||||
|
}
|
190
draftbot.messages.go
Normal file
190
draftbot.messages.go
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
package draftbot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
room "git.fromouter.space/mcg/cardgage/room/api"
|
||||||
|
"git.fromouter.space/mcg/draft"
|
||||||
|
)
|
||||||
|
|
||||||
|
type commandHandler func(roomid string, msg room.Message)
|
||||||
|
type commandMap map[string]commandHandler
|
||||||
|
|
||||||
|
func (d *DraftBot) commands(commands commandMap) commandHandler {
|
||||||
|
return func(roomid string, msg room.Message) {
|
||||||
|
action, ok := commands[msg.Type]
|
||||||
|
if !ok {
|
||||||
|
cmdlist := []string{}
|
||||||
|
for cmd := range commands {
|
||||||
|
cmdlist = append(cmdlist, cmd)
|
||||||
|
}
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
To: msg.From,
|
||||||
|
Type: "command-unavailable",
|
||||||
|
Message: fmt.Sprintf("Available commands (at this state) are: %s", cmdlist),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
action(roomid, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DraftBot) cmdJoinSession(roomid string, msg room.Message) {
|
||||||
|
// Get session
|
||||||
|
session, _ := d.Sessions[roomid]
|
||||||
|
|
||||||
|
// Players can only join if session didn't start yet
|
||||||
|
if session.started {
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
To: msg.From,
|
||||||
|
Type: "session-already-started",
|
||||||
|
Message: "You can't join a running session",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are still open slots
|
||||||
|
if len(session.Players)+1 > len(session.Pod.Players) {
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
To: msg.From,
|
||||||
|
Type: "session-full",
|
||||||
|
Message: "There aren't any spots left",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add player to the list
|
||||||
|
session.Players[msg.From] = nil
|
||||||
|
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
Channel: "draft",
|
||||||
|
Type: "player-joined-session",
|
||||||
|
Data: msg.From,
|
||||||
|
Message: fmt.Sprintf("%s joined the draft session (%d players, %d missing)", msg.From, len(session.Players), len(session.Pod.Players)-len(session.Players)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DraftBot) cmdPickCard(roomid string, msg room.Message) {
|
||||||
|
// Get session
|
||||||
|
session, _ := d.Sessions[roomid]
|
||||||
|
|
||||||
|
// Get player
|
||||||
|
player, _ := session.Players[msg.From]
|
||||||
|
|
||||||
|
// Get picked card
|
||||||
|
picked := msg.Data.(string)
|
||||||
|
|
||||||
|
// Try to pick on player struct
|
||||||
|
err := player.Pick(draft.Card{ID: picked})
|
||||||
|
if err != nil {
|
||||||
|
if err == draft.ErrNotInPack {
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
To: msg.From,
|
||||||
|
Type: "invalid-pick",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Technically not needed, as Pick can only throw ErrNotInPack right now
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
Channel: "draft",
|
||||||
|
Type: "card-picked",
|
||||||
|
Data: struct {
|
||||||
|
Player string
|
||||||
|
}{
|
||||||
|
msg.From,
|
||||||
|
},
|
||||||
|
Message: fmt.Sprintf("%s picked a card from his pack", msg.From),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DraftBot) cmdCreateSession(roomid string, msg room.Message) {
|
||||||
|
// Get session options from data
|
||||||
|
var opt SessionOptions
|
||||||
|
err := mapstructure.Decode(msg.Data, &opt)
|
||||||
|
if err != nil {
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
To: msg.From,
|
||||||
|
Type: "invalid-data",
|
||||||
|
Message: "Error parsing session options: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sess, err := newSession(opt.Players, opt.Options)
|
||||||
|
if err != nil {
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
To: msg.From,
|
||||||
|
Type: "session-create-error",
|
||||||
|
Data: err.Error(),
|
||||||
|
Message: "Error creating session: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// All ok, assign session
|
||||||
|
d.Sessions[roomid] = sess
|
||||||
|
|
||||||
|
// Start handling messages for the session
|
||||||
|
go d.handleSessionMessages(roomid, sess)
|
||||||
|
|
||||||
|
// Tell everyone about the new session
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
Channel: "draft",
|
||||||
|
Type: "session-open",
|
||||||
|
Data: opt,
|
||||||
|
Message: fmt.Sprintf("Created a new draft session for %d players, type: %s", opt.Players, opt.Options.Type),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DraftBot) cmdStartSession(roomid string, msg room.Message) {
|
||||||
|
// Get session
|
||||||
|
session, _ := d.Sessions[roomid]
|
||||||
|
|
||||||
|
// Try starting the session
|
||||||
|
err := session.Start()
|
||||||
|
if err != nil {
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
To: msg.From,
|
||||||
|
Type: "session-start-error",
|
||||||
|
Data: err.Error(),
|
||||||
|
Message: "Could not start session: " + err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell everyone about the new session
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
Channel: "draft",
|
||||||
|
Type: "session-start",
|
||||||
|
Message: "Session started, get drafting!",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DraftBot) cmdfnOnlyOwner(wrapped commandHandler) commandHandler {
|
||||||
|
return func(roomid string, msg room.Message) {
|
||||||
|
// Get room the message was sent from
|
||||||
|
roomData, ok := d.Rooms[roomid]
|
||||||
|
if !ok {
|
||||||
|
// Message from a room we're not in?
|
||||||
|
// Ignore it for now
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the message is coming from the room owner
|
||||||
|
if msg.From != roomData.Owner {
|
||||||
|
d.sendMessage(roomid, room.Message{
|
||||||
|
To: msg.From,
|
||||||
|
Type: "must-be-owner",
|
||||||
|
Message: "Sorry, only the room's owner can tell me to do that",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check done, call wrapped function
|
||||||
|
wrapped(roomid, msg)
|
||||||
|
}
|
||||||
|
}
|
433
draftbot_test.go
Normal file
433
draftbot_test.go
Normal file
|
@ -0,0 +1,433 @@
|
||||||
|
package draftbot_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.fromouter.space/mcg/draft"
|
||||||
|
"git.fromouter.space/mcg/draft/mlp"
|
||||||
|
mlptestdata "git.fromouter.space/mcg/draft/mlp/testdata"
|
||||||
|
|
||||||
|
draftbot "git.fromouter.space/mcg/mlp-server-tools/draftbot"
|
||||||
|
testdata "git.fromouter.space/mcg/mlp-server-tools/draftbot/testdata"
|
||||||
|
|
||||||
|
"git.fromouter.space/mcg/cardgage/client/bot"
|
||||||
|
lobby "git.fromouter.space/mcg/cardgage/lobby/proto"
|
||||||
|
room "git.fromouter.space/mcg/cardgage/room/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TestBotName = "test-bot"
|
||||||
|
const TestRoomName = "test-room"
|
||||||
|
|
||||||
|
type MockServer struct {
|
||||||
|
in chan room.ServerMessage
|
||||||
|
out chan room.BotMessage
|
||||||
|
t *testing.T
|
||||||
|
timeout time.Duration
|
||||||
|
roomName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeMockServer(t *testing.T, timeout int) *MockServer {
|
||||||
|
srv := &MockServer{
|
||||||
|
in: make(chan room.ServerMessage, 99),
|
||||||
|
out: make(chan room.BotMessage, 99),
|
||||||
|
t: t,
|
||||||
|
timeout: time.Duration(timeout) * time.Second,
|
||||||
|
roomName: TestRoomName,
|
||||||
|
}
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockServer) Send(msg room.BotMessage) {
|
||||||
|
m.out <- msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockServer) Bind(fn bot.MessageHandler) {
|
||||||
|
for msg := range m.in {
|
||||||
|
fn(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDraftSession(t *testing.T) {
|
||||||
|
mock := makeMockServer(t, 5)
|
||||||
|
drafter := draftbot.NewDraftBot(mock, TestBotName)
|
||||||
|
go mock.Bind(drafter.OnMessage)
|
||||||
|
|
||||||
|
// Create a new room
|
||||||
|
mock.in <- room.ServerMessage{
|
||||||
|
RoomID: TestRoomName,
|
||||||
|
Type: room.MsgEvent,
|
||||||
|
Event: &room.Event{
|
||||||
|
Type: room.EvtNewRoom,
|
||||||
|
Room: &lobby.Room{
|
||||||
|
Id: TestRoomName,
|
||||||
|
Name: "Test draft room",
|
||||||
|
Creator: "test-owner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new session with a fake message from owner
|
||||||
|
mock.message("test-owner", "create", draftbot.SessionOptions{
|
||||||
|
Players: 8, // Two players, six bots
|
||||||
|
Options: draftbot.DraftOptions{
|
||||||
|
Type: draftbot.DraftSet,
|
||||||
|
Positioning: draftbot.PosEven,
|
||||||
|
Set: mlp.SetAbsoluteDiscord,
|
||||||
|
PackCount: 4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
mock.expect("session-open")
|
||||||
|
|
||||||
|
// Join session as owner
|
||||||
|
mock.message("test-owner", "join", nil)
|
||||||
|
mock.expect("player-joined-session")
|
||||||
|
|
||||||
|
// .. and as second player
|
||||||
|
mock.message("test-guest", "join", nil)
|
||||||
|
mock.expect("player-joined-session")
|
||||||
|
|
||||||
|
// Try to start the session
|
||||||
|
mock.message("test-owner", "start", nil)
|
||||||
|
|
||||||
|
// These two can happen in any order, so they get their own special check
|
||||||
|
mock.multiexpect("session-start", "draft-order")
|
||||||
|
|
||||||
|
// Pick card for each player
|
||||||
|
for packi := 0; packi < 4; packi++ {
|
||||||
|
mock.expect("draft-newpack")
|
||||||
|
for cardi := 0; cardi < 12; cardi++ {
|
||||||
|
mock.expect("draft-newpick")
|
||||||
|
// Get packs
|
||||||
|
msg1 := mock.expect("draft-availablepicks")
|
||||||
|
pack1 := msg1.Data.(draft.Pack)
|
||||||
|
msg2 := mock.expect("draft-availablepicks")
|
||||||
|
pack2 := msg2.Data.(draft.Pack)
|
||||||
|
// Pick first card in each pack
|
||||||
|
mock.message(msg1.To, "pick", pack1[0].ID)
|
||||||
|
mock.message(msg2.To, "pick", pack2[0].ID)
|
||||||
|
// Intercept picked events
|
||||||
|
mock.multiexpect("card-picked", "card-picked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mock.expect("draft-finish")
|
||||||
|
|
||||||
|
// Close the room
|
||||||
|
mock.in <- room.ServerMessage{
|
||||||
|
RoomID: TestRoomName,
|
||||||
|
Type: room.MsgEvent,
|
||||||
|
Event: &room.Event{
|
||||||
|
Type: room.EvtRoomClosed,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDraftSessionButEverythingGoesWrong(t *testing.T) {
|
||||||
|
mock := makeMockServer(t, 5)
|
||||||
|
drafter := draftbot.NewDraftBot(mock, TestBotName)
|
||||||
|
go mock.Bind(drafter.OnMessage)
|
||||||
|
|
||||||
|
// Create a new room
|
||||||
|
mock.in <- room.ServerMessage{
|
||||||
|
Type: room.MsgEvent,
|
||||||
|
Event: &room.Event{
|
||||||
|
Type: room.EvtNewRoom,
|
||||||
|
Room: &lobby.Room{
|
||||||
|
Id: TestRoomName,
|
||||||
|
Name: "Test draft room",
|
||||||
|
Creator: "test-owner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try creating a new session as NOT the owner
|
||||||
|
mock.message("test-guest", "create", draftbot.SessionOptions{
|
||||||
|
Players: 8,
|
||||||
|
Options: draftbot.DraftOptions{
|
||||||
|
Type: draftbot.DraftSet,
|
||||||
|
Positioning: draftbot.PosEven,
|
||||||
|
Set: mlp.SetAbsoluteDiscord,
|
||||||
|
PackCount: 4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
mock.expect("must-be-owner")
|
||||||
|
|
||||||
|
// Try creating a session with an invalid type
|
||||||
|
mock.message("test-owner", "create", draftbot.SessionOptions{
|
||||||
|
Players: 8,
|
||||||
|
Options: draftbot.DraftOptions{
|
||||||
|
Type: "lolwhat",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
mock.expect("session-create-error")
|
||||||
|
|
||||||
|
// Try creating a session with invalid data
|
||||||
|
mock.message("test-owner", "create", 42)
|
||||||
|
mock.expect("invalid-data")
|
||||||
|
|
||||||
|
// Try starting a session that doesn't exist
|
||||||
|
mock.message("test-owner", "start", nil)
|
||||||
|
mock.expect("command-unavailable")
|
||||||
|
|
||||||
|
// Try creating the session twice
|
||||||
|
mock.message("test-owner", "create", draftbot.SessionOptions{
|
||||||
|
Players: 2,
|
||||||
|
Options: draftbot.DraftOptions{
|
||||||
|
Type: draftbot.DraftSet,
|
||||||
|
Positioning: draftbot.PosEven,
|
||||||
|
Set: mlp.SetAbsoluteDiscord,
|
||||||
|
PackCount: 4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
mock.expect("session-open")
|
||||||
|
|
||||||
|
mock.message("test-owner", "create", draftbot.SessionOptions{
|
||||||
|
Players: 2,
|
||||||
|
Options: draftbot.DraftOptions{
|
||||||
|
Type: draftbot.DraftSet,
|
||||||
|
Positioning: draftbot.PosEven,
|
||||||
|
Set: mlp.SetAbsoluteDiscord,
|
||||||
|
PackCount: 4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
mock.expect("command-unavailable")
|
||||||
|
|
||||||
|
// Try to start session when no-one has joined
|
||||||
|
mock.message("test-owner", "start", nil)
|
||||||
|
mock.expect("session-start-error")
|
||||||
|
|
||||||
|
// Try to make too many players join
|
||||||
|
mock.message("a", "join", nil)
|
||||||
|
mock.expect("player-joined-session")
|
||||||
|
mock.message("b", "join", nil)
|
||||||
|
mock.expect("player-joined-session")
|
||||||
|
mock.message("c", "join", nil)
|
||||||
|
mock.expect("session-full")
|
||||||
|
|
||||||
|
// Try to make someone join a session that already started
|
||||||
|
mock.message("test-owner", "start", nil)
|
||||||
|
mock.multiexpect("session-start", "draft-order", "draft-newpack", "draft-newpick", "draft-availablepicks", "draft-availablepicks")
|
||||||
|
|
||||||
|
mock.message("c", "join", nil)
|
||||||
|
mock.expect("session-already-started")
|
||||||
|
|
||||||
|
//TODO More picking, etc shenanigans
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDraftTypes(t *testing.T) {
|
||||||
|
// Load all sets into memory
|
||||||
|
err := mlp.LoadAllSets()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not load MLP sets needed for some drafts: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
mock := makeMockServer(t, 5)
|
||||||
|
drafter := draftbot.NewDraftBot(mock, TestBotName)
|
||||||
|
go mock.Bind(drafter.OnMessage)
|
||||||
|
|
||||||
|
makeSession := func(roomName string, options draftbot.DraftOptions) {
|
||||||
|
// Create a new room
|
||||||
|
mock.in <- room.ServerMessage{
|
||||||
|
RoomID: roomName,
|
||||||
|
Type: room.MsgEvent,
|
||||||
|
Event: &room.Event{
|
||||||
|
Type: room.EvtNewRoom,
|
||||||
|
Room: &lobby.Room{
|
||||||
|
Id: roomName,
|
||||||
|
Name: "Test draft room",
|
||||||
|
Creator: "test-owner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mock.roomName = roomName
|
||||||
|
|
||||||
|
// Create new session
|
||||||
|
mock.message("test-owner", "create", draftbot.SessionOptions{
|
||||||
|
Players: 8, // Two players, six bots
|
||||||
|
Options: options,
|
||||||
|
})
|
||||||
|
|
||||||
|
mock.expect("session-open")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make Set draft session
|
||||||
|
makeSession("set", draftbot.DraftOptions{
|
||||||
|
Type: draftbot.DraftSet,
|
||||||
|
Positioning: draftbot.PosEven,
|
||||||
|
Set: mlp.SetAbsoluteDiscord,
|
||||||
|
PackCount: 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Make Block draft session
|
||||||
|
makeSession("block", draftbot.DraftOptions{
|
||||||
|
Type: draftbot.DraftBlock,
|
||||||
|
Positioning: draftbot.PosRandom,
|
||||||
|
Block: mlp.BlockDefenders,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Make a plain Cube draft session
|
||||||
|
makeSession("cube", draftbot.DraftOptions{
|
||||||
|
Type: draftbot.DraftCube,
|
||||||
|
Positioning: draftbot.PosEven,
|
||||||
|
CubeURL: mlp.HTTPSource + "cube",
|
||||||
|
PackSize: 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Make a I8PCube draft session
|
||||||
|
makeSession("i8pcube", draftbot.DraftOptions{
|
||||||
|
Type: draftbot.DraftI8PCube,
|
||||||
|
Positioning: draftbot.PosEven,
|
||||||
|
CubeURL: mlp.HTTPSource + "i8pcube",
|
||||||
|
MainCount: 1,
|
||||||
|
ProblemCount: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDraftPositioning(t *testing.T) {
|
||||||
|
mock := makeMockServer(t, 5)
|
||||||
|
drafter := draftbot.NewDraftBot(mock, TestBotName)
|
||||||
|
go mock.Bind(drafter.OnMessage)
|
||||||
|
|
||||||
|
makeSession := func(roomName string, pos string) {
|
||||||
|
// Create a new room
|
||||||
|
mock.in <- room.ServerMessage{
|
||||||
|
RoomID: roomName,
|
||||||
|
Type: room.MsgEvent,
|
||||||
|
Event: &room.Event{
|
||||||
|
Type: room.EvtNewRoom,
|
||||||
|
Room: &lobby.Room{
|
||||||
|
Id: roomName,
|
||||||
|
Name: "Test draft room",
|
||||||
|
Creator: "test-owner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mock.roomName = roomName
|
||||||
|
|
||||||
|
// Create new session
|
||||||
|
mock.message("test-owner", "create", draftbot.SessionOptions{
|
||||||
|
Players: 8, // Two players, six bots
|
||||||
|
Options: draftbot.DraftOptions{
|
||||||
|
Type: draftbot.DraftSet,
|
||||||
|
Positioning: pos,
|
||||||
|
Set: mlp.SetAbsoluteDiscord,
|
||||||
|
PackCount: 4,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
mock.expect("session-open")
|
||||||
|
|
||||||
|
// Make two random players join
|
||||||
|
mock.message("a", "join", nil)
|
||||||
|
mock.expect("player-joined-session")
|
||||||
|
mock.message("b", "join", nil)
|
||||||
|
mock.expect("player-joined-session")
|
||||||
|
|
||||||
|
// Start the session
|
||||||
|
mock.message("test-owner", "start", nil)
|
||||||
|
mock.multiexpect("session-start", "draft-order", "draft-newpack", "draft-newpick", "draft-availablepicks", "draft-availablepicks")
|
||||||
|
}
|
||||||
|
|
||||||
|
makeSession("even", draftbot.PosEven)
|
||||||
|
makeSession("random", draftbot.PosRandom)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockServer) expect(typ string) *room.Message {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-m.out:
|
||||||
|
// Skip all actions
|
||||||
|
if msg.Type != room.MsgMessage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check expected type
|
||||||
|
if msg.Message.Type != typ {
|
||||||
|
// Oh noes
|
||||||
|
m.t.Fatalf("Expected message \"%s\" but got \"%s\" (%v)", typ, msg.Message.Type, msg.Message.Data)
|
||||||
|
}
|
||||||
|
return msg.Message
|
||||||
|
case <-time.After(m.timeout):
|
||||||
|
m.t.Fatalf("Expected message \"%s\" but found nothing (timeout)!", typ)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockServer) multiexpect(types ...string) {
|
||||||
|
for {
|
||||||
|
if len(types) < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case msg := <-m.out:
|
||||||
|
|
||||||
|
// Skip all actions
|
||||||
|
if msg.Type != room.MsgMessage {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.t.Logf("-> [%s] %s", msg.Message.Type, msg.Message.Message)
|
||||||
|
|
||||||
|
// Check expected type
|
||||||
|
found := false
|
||||||
|
for i, typ := range types {
|
||||||
|
if typ == msg.Message.Type {
|
||||||
|
found = true
|
||||||
|
types[i] = types[len(types)-1]
|
||||||
|
types = types[:len(types)-1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
// Oh noes
|
||||||
|
m.t.Fatalf("Expected one of %s but got \"%s\"", types, msg.Message.Type)
|
||||||
|
}
|
||||||
|
case <-time.After(m.timeout):
|
||||||
|
m.t.Fatalf("Expected one of %s but found nothing (timeout)!", types)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockServer) message(from string, typ string, data interface{}) {
|
||||||
|
m.t.Logf("<- <%s> %s (%v)", from, typ, data)
|
||||||
|
m.in <- room.ServerMessage{
|
||||||
|
RoomID: m.roomName,
|
||||||
|
Type: room.MsgMessage,
|
||||||
|
Message: &room.Message{
|
||||||
|
From: from,
|
||||||
|
To: TestBotName,
|
||||||
|
Type: typ,
|
||||||
|
Data: data,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
switch req.URL.Path {
|
||||||
|
case "/nopenope.json":
|
||||||
|
// 404
|
||||||
|
http.Error(res, "Not found", http.StatusNotFound)
|
||||||
|
case "/broken.json":
|
||||||
|
// Broken response
|
||||||
|
fmt.Fprintf(res, "{{{{")
|
||||||
|
case "/pr.json":
|
||||||
|
fmt.Fprintf(res, mlptestdata.SetPremiere)
|
||||||
|
case "/cube":
|
||||||
|
fmt.Fprintf(res, testdata.TestGenericCubeFile)
|
||||||
|
case "/i8pcube":
|
||||||
|
fmt.Fprintf(res, testdata.TestI8PCubeFile)
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(res, mlptestdata.SetFriendForever)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
mlp.HTTPSource = testServer.URL + "/"
|
||||||
|
defer testServer.Close()
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
11
go.mod
Normal file
11
go.mod
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
module git.fromouter.space/mcg/mlp-server-tools/draftbot
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.fromouter.space/Artificiale/moa v0.0.1-p2
|
||||||
|
git.fromouter.space/mcg/cardgage v0.0.4
|
||||||
|
git.fromouter.space/mcg/draft v0.0.7
|
||||||
|
github.com/go-kit/kit v0.8.0
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2
|
||||||
|
)
|
202
go.sum
Normal file
202
go.sum
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
git.fromouter.space/Artificiale/moa v0.0.1-p2 h1:KhoRQeYCFIpHZEucrXz142O5zfSsyExyhuPSazCrt6I=
|
||||||
|
git.fromouter.space/Artificiale/moa v0.0.1-p2/go.mod h1:dHYul6vVMwDCzre18AFs6NmI22yeI7AE0iQC1jFEQi0=
|
||||||
|
git.fromouter.space/mcg/cardgage v0.0.4 h1:LHMUeNMh0QiMkM3TgsLe9l5sDmanQrej6UiWSVTb67c=
|
||||||
|
git.fromouter.space/mcg/cardgage v0.0.4/go.mod h1:vCmJ9HRdRGSWg2YQW9oNG7geYACdgWYmzL+zZdrsYhQ=
|
||||||
|
git.fromouter.space/mcg/draft v0.0.7 h1:kTcvGSs8MjrGjajKrUi0jj5uuCN6BF7x4OCvoUxQkGg=
|
||||||
|
git.fromouter.space/mcg/draft v0.0.7/go.mod h1:QQmDm9FgAZL3b2/pIDd4Eo608SxMiCQQe5vIybe/CDY=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM=
|
||||||
|
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
|
||||||
|
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
|
github.com/cenkalti/backoff v2.0.0+incompatible h1:5IIPUHhlnUZbcHQsQou5k1Tn58nJkeJL9U+ig5CHJbY=
|
||||||
|
github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
|
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||||
|
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
|
||||||
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
|
||||||
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
|
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
|
||||||
|
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I=
|
||||||
|
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
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/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||||
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||||
|
github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA=
|
||||||
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
|
github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY=
|
||||||
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||||
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc=
|
||||||
|
github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||||
|
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
|
||||||
|
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
|
||||||
|
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||||
|
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||||
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
|
github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs=
|
||||||
|
github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
|
github.com/hashicorp/serf v0.8.3 h1:MWYcmct5EtKz0efYooPcL0yNkem+7kWxqXDi/UIh+8k=
|
||||||
|
github.com/hashicorp/serf v0.8.3/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/miekg/dns v1.1.13 h1:x7DQtkU0cedzeS8TD36tT/w1Hm4rDtfCaYYAHE7TTBI=
|
||||||
|
github.com/miekg/dns v1.1.13/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA=
|
||||||
|
github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc=
|
||||||
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
|
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||||
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg=
|
||||||
|
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
|
||||||
|
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
|
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
|
||||||
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
|
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||||
|
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||||
|
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
|
||||||
|
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190531132440-69e3a3a65b5b h1:cuzzKXORsbIjh3IeOgOj0x2QA08ntIxmwi1mkRC3qoc=
|
||||||
|
golang.org/x/sys v0.0.0-20190531132440-69e3a3a65b5b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
|
||||||
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg=
|
||||||
|
gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/rethinkdb/rethinkdb-go.v5 v5.0.1 h1:cBTUuUFTllRYtInK6FtBRa+tOBlZqpeDTnZF54xjGbg=
|
||||||
|
gopkg.in/rethinkdb/rethinkdb-go.v5 v5.0.1/go.mod h1:x+1XKi70FH0kHCpvPQ78hGBCCxoNdE7sP+kEFdKgN6A=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
281
session.go
Normal file
281
session.go
Normal file
|
@ -0,0 +1,281 @@
|
||||||
|
package draftbot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
|
room "git.fromouter.space/mcg/cardgage/room/api"
|
||||||
|
|
||||||
|
"git.fromouter.space/mcg/draft"
|
||||||
|
"git.fromouter.space/mcg/draft/mlp"
|
||||||
|
"git.fromouter.space/mcg/mlp-server-tools/draftbot/bot"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Session-related errors
|
||||||
|
var (
|
||||||
|
ErrTooManyPlayers = errors.New("too many players")
|
||||||
|
ErrNotEnoughPlayers = errors.New("not enough players")
|
||||||
|
)
|
||||||
|
|
||||||
|
type session struct {
|
||||||
|
Options DraftOptions
|
||||||
|
Players map[string]*draft.Player
|
||||||
|
Bots []*bot.Bot
|
||||||
|
Pod *draft.Pod
|
||||||
|
|
||||||
|
// State management variables
|
||||||
|
started bool // Has the draft started already?
|
||||||
|
|
||||||
|
// Channels for communication while the session is running
|
||||||
|
messages chan room.Message
|
||||||
|
exit chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types of drafts
|
||||||
|
const (
|
||||||
|
DraftBlock = "block"
|
||||||
|
DraftSet = "set"
|
||||||
|
DraftCube = "cube"
|
||||||
|
DraftI8PCube = "i8pcube"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ways in which players can be positioned along the draft pod
|
||||||
|
const (
|
||||||
|
PosRandom = "random" // Place players randomly
|
||||||
|
PosEven = "even" // Place players spaced as evenly as possible
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionOptions is the data contained in a create session request
|
||||||
|
type SessionOptions struct {
|
||||||
|
Players int `json:"players"`
|
||||||
|
Options DraftOptions `json:"options"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DraftOptions are the options needed for a draft session
|
||||||
|
type DraftOptions struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Positioning string `json:"positioning"`
|
||||||
|
|
||||||
|
// Block draft properties
|
||||||
|
Block mlp.BlockID `json:"block,omitempty"`
|
||||||
|
|
||||||
|
// Set draft properties
|
||||||
|
Set mlp.SetID `json:"set,omitempty"`
|
||||||
|
|
||||||
|
// Cube draft properties
|
||||||
|
CubeURL string `json:"cube_url,omitempty"`
|
||||||
|
PackSize int `json:"pack_size,omitempty"`
|
||||||
|
|
||||||
|
// I8PCube properties
|
||||||
|
MainCount int `json:"main_count,omitempty"`
|
||||||
|
ProblemCount int `json:"problem_count,omitempty"`
|
||||||
|
|
||||||
|
// Shared
|
||||||
|
PackCount int `json:"pack_count,omitempty"` // Set and Cube
|
||||||
|
}
|
||||||
|
|
||||||
|
func (do DraftOptions) getProvider() (draft.PackProvider, error) {
|
||||||
|
switch do.Type {
|
||||||
|
case DraftBlock:
|
||||||
|
return mlp.BlockPacks(do.Block)
|
||||||
|
case DraftSet:
|
||||||
|
set, err := mlp.LoadSetHTTP(do.Set)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return draft.PacksFromSet(do.PackCount, set), nil
|
||||||
|
case DraftCube:
|
||||||
|
cards, err := loadCube(do.CubeURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cube := &draft.GenericCube{
|
||||||
|
Cards: cards,
|
||||||
|
PackSize: do.PackSize,
|
||||||
|
}
|
||||||
|
return draft.PacksFromSet(do.PackCount, cube), nil
|
||||||
|
case DraftI8PCube:
|
||||||
|
cube, err := loadI8PCube(do.CubeURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return cube.PackProvider(do.MainCount, do.ProblemCount), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("unknown draft type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSession(playerCount int, opt DraftOptions) (*session, error) {
|
||||||
|
// Get pack provider for given options
|
||||||
|
provider, err := opt.getProvider()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &session{
|
||||||
|
Options: opt,
|
||||||
|
Pod: draft.MakePod(playerCount, provider),
|
||||||
|
Players: make(map[string]*draft.Player),
|
||||||
|
messages: make(chan room.Message),
|
||||||
|
exit: make(chan bool),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) Start() error {
|
||||||
|
// Figure out how many players there are vs spots to be filled
|
||||||
|
spots := len(s.Pod.Players)
|
||||||
|
players := len(s.Players)
|
||||||
|
if players > spots {
|
||||||
|
return ErrTooManyPlayers
|
||||||
|
}
|
||||||
|
|
||||||
|
if players < 1 {
|
||||||
|
return ErrNotEnoughPlayers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign players to their spot on the drafting pod
|
||||||
|
playerSpot := make(map[int]string)
|
||||||
|
|
||||||
|
switch s.Options.Positioning {
|
||||||
|
case PosRandom:
|
||||||
|
// Assign a random number to each player
|
||||||
|
for pname := range s.Players {
|
||||||
|
var pos int
|
||||||
|
for {
|
||||||
|
pos = rand.Intn(spots)
|
||||||
|
// Make sure chosen number wasn't already picked
|
||||||
|
if _, ok := playerSpot[pos]; !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
playerSpot[pos] = pname
|
||||||
|
}
|
||||||
|
case PosEven:
|
||||||
|
// Space players evenly
|
||||||
|
playerRatio := float64(spots) / float64(players)
|
||||||
|
i := 0
|
||||||
|
for name := range s.Players {
|
||||||
|
pos := int(math.Floor(playerRatio * float64(i)))
|
||||||
|
playerSpot[pos] = name
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare order to be broadcasted after all spots have been assigned
|
||||||
|
order := make([]string, len(s.Pod.Players))
|
||||||
|
|
||||||
|
// Assign player instances and make bots where needed
|
||||||
|
for i := range s.Pod.Players {
|
||||||
|
if name, ok := playerSpot[i]; ok {
|
||||||
|
s.Players[name] = s.Pod.Players[i]
|
||||||
|
order[i] = "player:" + name
|
||||||
|
} else {
|
||||||
|
s.Bots = append(s.Bots, bot.MakeBot(s.Pod.Players[i]))
|
||||||
|
order[i] = "bot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify players of the order
|
||||||
|
s.messages <- room.Message{
|
||||||
|
Channel: "draft",
|
||||||
|
Type: "draft-order",
|
||||||
|
Data: order,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start handling packs
|
||||||
|
s.started = true
|
||||||
|
go s.handlePicks()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) handlePicks() {
|
||||||
|
// Pack loop, this `for` handles an entire draft session
|
||||||
|
totalPacks := len(s.Pod.Players[0].Packs)
|
||||||
|
currentPack := 0
|
||||||
|
for {
|
||||||
|
err := s.Pod.OpenPacks()
|
||||||
|
if err != nil {
|
||||||
|
if err == draft.ErrNoPacksLeft {
|
||||||
|
// Notify players that the draft is over
|
||||||
|
s.messages <- room.Message{
|
||||||
|
Channel: "draft",
|
||||||
|
Type: "draft-finish",
|
||||||
|
Message: "No more packs, the draft is over!",
|
||||||
|
}
|
||||||
|
// Send each player their deck
|
||||||
|
for pname, pdata := range s.Players {
|
||||||
|
s.messages <- room.Message{
|
||||||
|
To: pname,
|
||||||
|
Type: "draft-picks",
|
||||||
|
Data: draft.Pack(pdata.Picks).IDs(),
|
||||||
|
Message: fmt.Sprintf("Your picks are: %s", pdata.Picks),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Something is wrong!
|
||||||
|
//TODO
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentPack++
|
||||||
|
s.messages <- room.Message{
|
||||||
|
Channel: "draft",
|
||||||
|
Type: "draft-newpack",
|
||||||
|
Data: []int{currentPack, totalPacks},
|
||||||
|
Message: fmt.Sprintf("Opening pack %d (of %d)", currentPack, totalPacks),
|
||||||
|
}
|
||||||
|
// Pick loop, this `for` handles exactly one round of packs
|
||||||
|
for {
|
||||||
|
s.messages <- room.Message{
|
||||||
|
To: "draft",
|
||||||
|
Type: "draft-newpick",
|
||||||
|
Message: fmt.Sprintf("New pick for pack #%d", currentPack),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make bots pick their cards
|
||||||
|
for _, bot := range s.Bots {
|
||||||
|
bot.PickNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tell every players their new cards
|
||||||
|
for pname, player := range s.Players {
|
||||||
|
s.messages <- room.Message{
|
||||||
|
To: pname,
|
||||||
|
Type: "draft-availablepicks",
|
||||||
|
Data: player.CurrentPack,
|
||||||
|
Message: fmt.Sprintf("You got these cards: %s", player.CurrentPack),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPack := false
|
||||||
|
select {
|
||||||
|
case <-s.Pod.ReadyNextPack:
|
||||||
|
// Break of pick loop, get next packs
|
||||||
|
nextPack = true
|
||||||
|
case <-s.Pod.ReadyNextPick:
|
||||||
|
// Pass packs around
|
||||||
|
err := s.Pod.NextPacks()
|
||||||
|
if err != nil {
|
||||||
|
if err == draft.ErrNoPendingPack {
|
||||||
|
// No more picks to do for this round of packs, go to next
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
// Something is wrong!
|
||||||
|
//TODO
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-s.exit:
|
||||||
|
// Room closed, exit early
|
||||||
|
close(s.messages)
|
||||||
|
close(s.exit)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextPack {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
testdata/cube.go
vendored
Normal file
130
testdata/cube.go
vendored
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
// TestGenericCubeFile is an example file for a generic cube draft
|
||||||
|
const TestGenericCubeFile = `pr1
|
||||||
|
pr2
|
||||||
|
pr3
|
||||||
|
pr4
|
||||||
|
pr5
|
||||||
|
pr6
|
||||||
|
pr7
|
||||||
|
pr8
|
||||||
|
pr9
|
||||||
|
pr10
|
||||||
|
pr11
|
||||||
|
pr12
|
||||||
|
pr13
|
||||||
|
pr14
|
||||||
|
pr15
|
||||||
|
pr16
|
||||||
|
pr17
|
||||||
|
pr18
|
||||||
|
pr19
|
||||||
|
pr20
|
||||||
|
pr21
|
||||||
|
pr22
|
||||||
|
pr23
|
||||||
|
pr24
|
||||||
|
pr25
|
||||||
|
pr26
|
||||||
|
pr27
|
||||||
|
pr28
|
||||||
|
pr29
|
||||||
|
pr30
|
||||||
|
`
|
||||||
|
|
||||||
|
// TestI8PCubeFile is an example file for a I8Pages' style cube draft
|
||||||
|
const TestI8PCubeFile = `{
|
||||||
|
"Schema": [
|
||||||
|
{ "Amount": 1, "Type": "blue" },
|
||||||
|
{ "Amount": 1, "Type": "orange" },
|
||||||
|
{ "Amount": 1, "Type": "pink" },
|
||||||
|
{ "Amount": 1, "Type": "purple" },
|
||||||
|
{ "Amount": 1, "Type": "white" },
|
||||||
|
{ "Amount": 1, "Type": "yellow" },
|
||||||
|
{ "Amount": 1, "Type": "none" },
|
||||||
|
{ "Amount": 2, "Type": "multi" },
|
||||||
|
{ "Amount": 2, "Type": "entry" },
|
||||||
|
{ "Amount": 1, "Type": "all" }
|
||||||
|
],
|
||||||
|
"Cards": {
|
||||||
|
"blue": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
],
|
||||||
|
"orange": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
],
|
||||||
|
"pink": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
],
|
||||||
|
"purple": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
],
|
||||||
|
"white": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
],
|
||||||
|
"yellow": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
],
|
||||||
|
"none": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
],
|
||||||
|
"multi": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
],
|
||||||
|
"entry": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
],
|
||||||
|
"problem": [
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54",
|
||||||
|
"pr54"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`
|
Loading…
Reference in a new issue