259 lines
5.9 KiB
Go
259 lines
5.9 KiB
Go
package main
|
|
|
|
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
|
|
)
|
|
|
|
type draftOptions struct {
|
|
Type string `json:"type"`
|
|
Positioning string `json:"positioning"`
|
|
|
|
// Block draft properties
|
|
Block string `json:"block,omitempty"`
|
|
|
|
// Set draft properties
|
|
Set string `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(mlp.BlockID(do.Block))
|
|
case draftSet:
|
|
set, err := mlp.LoadSetHTTP(mlp.SetID(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),
|
|
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",
|
|
Message: fmt.Sprintf("Opening pack %d (of %d)", currentPack, totalPacks),
|
|
}
|
|
// Pick loop, this `for` handles exactly one round of packs
|
|
for {
|
|
// Make bots pick their cards
|
|
for _, bot := range s.Bots {
|
|
bot.PickNext()
|
|
}
|
|
// Tell every players their new cards
|
|
for _, player := range s.Players {
|
|
s.messages <- room.Message{
|
|
Channel: "draft",
|
|
Type: "draft-newpick",
|
|
Data: player.CurrentPack,
|
|
Message: fmt.Sprintf("You got these cards: %s", player.CurrentPack),
|
|
}
|
|
}
|
|
select {
|
|
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.Pod.ReadyNextPack:
|
|
// Break of pick loop, get next packs
|
|
break
|
|
case <-s.exit:
|
|
// Room closed, exit early
|
|
close(s.messages)
|
|
close(s.exit)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|