You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
13 KiB
510 lines
13 KiB
package main |
|
|
|
import ( |
|
"fmt" |
|
"log" |
|
"strconv" |
|
"strings" |
|
|
|
"git.fromouter.space/mcg/draft/mlp" |
|
|
|
"git.fromouter.space/mcg/draft" |
|
|
|
"git.fromouter.space/hamcha/tg" |
|
lobby "git.fromouter.space/mcg/cardgage/lobby/proto" |
|
room "git.fromouter.space/mcg/cardgage/room/api" |
|
"git.fromouter.space/mcg/draftbot" |
|
) |
|
|
|
type draftUser struct { |
|
ID int64 |
|
Session string |
|
} |
|
|
|
const ( |
|
positionEven = "Evenly spaced" |
|
positionRandom = "At random" |
|
) |
|
|
|
const ( |
|
stepPlayerCount = iota |
|
stepSpacing = iota |
|
stepDraftType = iota |
|
stepDraftInfo = iota |
|
//stepPackCount = iota |
|
//stepExtraPackCount = iota // Only for I8PCube |
|
stepDone = iota |
|
) |
|
|
|
type draftSession struct { |
|
Step int |
|
Options draftbot.SessionOptions |
|
|
|
// Session open message |
|
SessionOpenMessage int64 |
|
|
|
// Pick message |
|
CurrentPack int |
|
PickDone []string |
|
PickWaiting []string |
|
PickMessage int64 |
|
PickNum int |
|
} |
|
|
|
type tgInterface struct { |
|
client *tg.Telegram |
|
bot *draftbot.DraftBot |
|
usermap map[string]*draftUser |
|
sessions map[int64]*draftSession |
|
} |
|
|
|
// Draft types |
|
const ( |
|
DTSetDraft = "Set draft" |
|
DTBlockDraft = "Block draft (BROKEN!)" |
|
DTCubeDraft = "Cube draft" |
|
DTI8PCube = "Seeded cube" |
|
) |
|
|
|
const ( |
|
blockPremiere = "Premiere" |
|
blockOdyssey = "Odyssey" |
|
blockDefenders = "Defenders" |
|
) |
|
|
|
func (tgi *tgInterface) Send(msg room.BotMessage) { |
|
// Get room ID |
|
chatid, err := strconv.ParseInt(msg.RoomID, 10, 64) |
|
if err != nil { |
|
log.Printf("[WARN] Trying to send message to weird place: \"%s\". Dropped.\n", msg.RoomID) |
|
return |
|
} |
|
|
|
// For now, skip non-messages |
|
if msg.Type != room.MsgMessage { |
|
//TODO |
|
return |
|
} |
|
|
|
// Are we sending a message to a player in particular? |
|
if msg.Message.To != "" { |
|
chatid = tgi.usermap[msg.Message.To].ID |
|
} |
|
|
|
switch msg.Message.Type { |
|
case "draft-newpack": |
|
tgi.sessions[chatid].CurrentPack++ |
|
tgi.sessions[chatid].PickNum = 0 |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: chatid, |
|
Text: msg.Message.Message, |
|
}) |
|
tgi.sessions[chatid].PickMessage = 0 |
|
case "draft-newpick": |
|
tgi.sessions[chatid].PickNum++ |
|
tgi.sessions[chatid].PickDone = []string{} |
|
tgi.sessions[chatid].PickWaiting = playerNames(tgi.bot.Sessions[msg.RoomID].Players) |
|
if tgi.sessions[chatid].PickMessage != 0 { |
|
tgi.client.EditText(tg.ClientEditTextData{ |
|
ChatID: chatid, |
|
MessageID: tgi.sessions[chatid].PickMessage, |
|
Text: txtCurrentPick(tgi.sessions[chatid]), |
|
}) |
|
} else { |
|
msg, _ := tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: chatid, |
|
Text: txtCurrentPick(tgi.sessions[chatid]), |
|
}) |
|
tgi.sessions[chatid].PickMessage = msg.MessageID |
|
} |
|
case "card-picked": |
|
picked := msg.Message.Data.(struct { |
|
Player string |
|
}) |
|
player := "@" + picked.Player |
|
tgi.sessions[chatid].PickDone = append(tgi.sessions[chatid].PickDone, player) |
|
tgi.sessions[chatid].PickWaiting = removeItem(tgi.sessions[chatid].PickWaiting, player) |
|
tgi.client.EditText(tg.ClientEditTextData{ |
|
ChatID: chatid, |
|
MessageID: tgi.sessions[chatid].PickMessage, |
|
Text: txtCurrentPick(tgi.sessions[chatid]), |
|
}) |
|
case "draft-availablepicks": |
|
cards := msg.Message.Data.(draft.Pack) |
|
cardphotos := []tg.APIInputMediaPhoto{} |
|
buttons := []tg.APIKeyboardButton{} |
|
for _, card := range cards { |
|
cardphotos = append(cardphotos, tg.APIInputMediaPhoto{ |
|
Type: "photo", |
|
Media: fmt.Sprintf("http://ponyhead.com/img/cards/%s.jpg", card.ID), |
|
}) |
|
buttons = append(buttons, tg.APIKeyboardButton{ |
|
Text: card.ID, |
|
}) |
|
} |
|
// If sending more than 10 cards, split in two messages |
|
if len(cardphotos) > 10 { |
|
half := len(cardphotos) / 2 |
|
tgi.client.SendAlbum(tg.ClientAlbumData{ |
|
ChatID: chatid, |
|
Silent: true, |
|
Media: cardphotos[:half], |
|
}) |
|
tgi.client.SendAlbum(tg.ClientAlbumData{ |
|
ChatID: chatid, |
|
Silent: true, |
|
Media: cardphotos[half:], |
|
}) |
|
} else { |
|
tgi.client.SendAlbum(tg.ClientAlbumData{ |
|
ChatID: chatid, |
|
Silent: true, |
|
Media: cardphotos, |
|
}) |
|
} |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: chatid, |
|
Text: "Choose your pick:", |
|
ReplyMarkup: &tg.APIReplyKeyboardMarkup{ |
|
Keyboard: rebalance(buttons), |
|
OneTime: true, |
|
Resize: true, |
|
}, |
|
}) |
|
case "session-open": |
|
opt := msg.Message.Data.(draftbot.SessionOptions) |
|
tgi.sessions[chatid].Options = opt |
|
msg, _ := tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: chatid, |
|
Text: txtDraftSessionCreated(opt, |
|
tgi.bot.Sessions[msg.RoomID].Players), |
|
}) |
|
tgi.sessions[chatid].SessionOpenMessage = msg.MessageID |
|
case "player-joined-session": |
|
tgi.client.EditText(tg.ClientEditTextData{ |
|
ChatID: chatid, |
|
MessageID: tgi.sessions[chatid].SessionOpenMessage, |
|
Text: txtDraftSessionCreated(tgi.sessions[chatid].Options, |
|
tgi.bot.Sessions[msg.RoomID].Players), |
|
}) |
|
case "draft-order": |
|
players := msg.Message.Data.([]string) |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: chatid, |
|
Text: txtDraftSessionOrder(players), |
|
}) |
|
case "draft-picks": |
|
picks := msg.Message.Data.([]string) |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: chatid, |
|
Text: txtYourPicks(picks), |
|
}) |
|
default: |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: chatid, |
|
Text: fmt.Sprintf("[%s] %s", msg.Message.Type, msg.Message.Message), |
|
}) |
|
} |
|
} |
|
|
|
func (tgi *tgInterface) OnMessage(msg tg.APIMessage) { |
|
switch msg.Chat.Type { |
|
case tg.ChatTypeGroup, tg.ChatTypeSupergroup: |
|
if msg.Text != nil { |
|
if isCommand(msg, "newdraft") { |
|
tgi.NewDraft(msg) |
|
return |
|
} |
|
if isCommand(msg, "join") { |
|
tgi.JoinDraft(msg.Chat, msg.User) |
|
return |
|
} |
|
if isCommand(msg, "startdraft") { |
|
tgi.StartDraft(msg.Chat, msg.User) |
|
return |
|
} |
|
if session, ok := tgi.sessions[msg.Chat.ChatID]; ok && session.Step != stepDone { |
|
tgi.nextCreateDraftStep(msg) |
|
return |
|
} |
|
} |
|
case tg.ChatTypePrivate: |
|
if user, ok := tgi.usermap[msg.User.Username]; ok && user.Session != "" { |
|
if msg.Text != nil && (*msg.Text)[0] != '/' { |
|
tgi.PickCard(msg.User, *msg.Text) |
|
} |
|
} |
|
} |
|
} |
|
|
|
func (tgi *tgInterface) Bind(bot *draftbot.DraftBot) { |
|
tgi.bot = bot |
|
} |
|
|
|
func (tgi *tgInterface) message(user tg.APIUser, session string, typ string, data interface{}) { |
|
tgi.usermap[user.Username] = &draftUser{ |
|
ID: user.UserID, |
|
Session: session, |
|
} |
|
tgi.bot.OnMessage(room.ServerMessage{ |
|
RoomID: session, |
|
Type: room.MsgMessage, |
|
Message: &room.Message{ |
|
From: user.Username, |
|
To: botname, |
|
Type: typ, |
|
Data: data, |
|
}, |
|
}) |
|
} |
|
|
|
func (tgi *tgInterface) NewDraft(msg tg.APIMessage) { |
|
chat := msg.Chat |
|
author := msg.User |
|
chatid := strconv.FormatInt(chat.ChatID, 10) |
|
tgi.bot.OnMessage(room.ServerMessage{ |
|
RoomID: chatid, |
|
Type: room.MsgEvent, |
|
Event: &room.Event{ |
|
Type: room.EvtNewRoom, |
|
Room: &lobby.Room{ |
|
Id: chatid, |
|
Name: *chat.Title, |
|
Creator: author.Username, |
|
}, |
|
}, |
|
}) |
|
|
|
// Open interactive dialog |
|
tgi.sessions[chat.ChatID] = &draftSession{ |
|
Step: stepPlayerCount, |
|
Options: draftbot.SessionOptions{ |
|
Options: draftbot.DraftOptions{ |
|
// For now let's make these non-modifiable |
|
PackCount: 4, |
|
PackSize: 12, |
|
MainCount: 4, |
|
ProblemCount: 1, |
|
}, |
|
}, |
|
} |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "How many players will be partecipating in the draft?", |
|
ReplyID: &msg.MessageID, |
|
}) |
|
} |
|
|
|
func (tgi *tgInterface) nextCreateDraftStep(msg tg.APIMessage) { |
|
session, ok := tgi.sessions[msg.Chat.ChatID] |
|
if !ok { |
|
return |
|
} |
|
|
|
switch session.Step { |
|
case stepPlayerCount: |
|
n, err := strconv.Atoi(*msg.Text) |
|
if err != nil { |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "That's not a valid number!", |
|
ReplyID: &msg.MessageID, |
|
}) |
|
return |
|
} |
|
if n < 1 || n > 8 { |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "Please choose a reasonable number. (1-8)", |
|
ReplyID: &msg.MessageID, |
|
}) |
|
return |
|
} |
|
session.Options.Players = n |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "How should player be positioned in the pod (assuming bots)?", |
|
ReplyID: &msg.MessageID, |
|
ReplyMarkup: tg.APIReplyKeyboardMarkup{ |
|
Selective: true, |
|
OneTime: true, |
|
Keyboard: rebalance([]tg.APIKeyboardButton{ |
|
{Text: positionEven}, |
|
{Text: positionRandom}, |
|
}), |
|
}, |
|
}) |
|
session.Step = stepSpacing |
|
case stepSpacing: |
|
switch *msg.Text { |
|
case positionEven: |
|
session.Options.Options.Positioning = draftbot.PosEven |
|
case positionRandom: |
|
session.Options.Options.Positioning = draftbot.PosRandom |
|
default: |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "Sorry, valid choices are: " + strings.Join([]string{positionEven, positionRandom}, ", "), |
|
ReplyID: &msg.MessageID, |
|
ReplyMarkup: tg.APIReplyKeyboardMarkup{ |
|
Selective: true, |
|
OneTime: true, |
|
Keyboard: rebalance([]tg.APIKeyboardButton{ |
|
{Text: positionEven}, |
|
{Text: positionRandom}, |
|
}), |
|
}, |
|
}) |
|
return |
|
} |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "What type of draft do you want?", |
|
ReplyID: &msg.MessageID, |
|
ReplyMarkup: tg.APIReplyKeyboardMarkup{ |
|
Selective: true, |
|
OneTime: true, |
|
Keyboard: rebalance([]tg.APIKeyboardButton{ |
|
{Text: DTSetDraft}, |
|
{Text: DTBlockDraft}, |
|
{Text: DTCubeDraft}, |
|
{Text: DTI8PCube}, |
|
}), |
|
}, |
|
}) |
|
session.Step = stepDraftType |
|
case stepDraftType: |
|
switch *msg.Text { |
|
case DTSetDraft: |
|
session.Options.Options.Type = draftbot.DraftSet |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "What set do you want to draft?", |
|
ReplyID: &msg.MessageID, |
|
ReplyMarkup: tg.APIReplyKeyboardMarkup{ |
|
Selective: true, |
|
OneTime: true, |
|
Keyboard: rebalance([]tg.APIKeyboardButton{ |
|
{Text: string(mlp.SetPremiere)}, |
|
{Text: string(mlp.SetCanterlotNights)}, |
|
{Text: string(mlp.SetCrystalGames)}, |
|
{Text: string(mlp.SetAbsoluteDiscord)}, |
|
{Text: string(mlp.SetEquestrialOdysseys)}, |
|
{Text: string(mlp.SetHighMagic)}, |
|
{Text: string(mlp.SetMarksInTime)}, |
|
{Text: string(mlp.SetDefendersOfEquestria)}, |
|
{Text: string(mlp.SetSeaquestriaBeyond)}, |
|
{Text: string(mlp.SetFriendsForever)}, |
|
}), |
|
}, |
|
}) |
|
session.Step = stepDraftInfo |
|
case DTBlockDraft: |
|
session.Options.Options.Type = draftbot.DraftBlock |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "What block do you want to draft?", |
|
ReplyID: &msg.MessageID, |
|
ReplyMarkup: tg.APIReplyKeyboardMarkup{ |
|
Selective: true, |
|
OneTime: true, |
|
Keyboard: rebalance([]tg.APIKeyboardButton{ |
|
{Text: blockPremiere}, |
|
{Text: blockOdyssey}, |
|
{Text: blockDefenders}, |
|
}), |
|
}, |
|
}) |
|
session.Step = stepDraftInfo |
|
case DTCubeDraft: |
|
session.Options.Options.Type = draftbot.DraftCube |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "Write the URL of a file containing a line-separated list of cards in the cube", |
|
ReplyID: &msg.MessageID, |
|
}) |
|
session.Step = stepDraftInfo |
|
case DTI8PCube: |
|
session.Options.Options.Type = draftbot.DraftI8PCube |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "Write the URL of a I8P-JSON cube", |
|
ReplyID: &msg.MessageID, |
|
}) |
|
session.Step = stepDraftInfo |
|
default: |
|
tgi.client.SendTextMessage(tg.ClientTextMessageData{ |
|
ChatID: msg.Chat.ChatID, |
|
Text: "Sorry, valid choices are: " + strings.Join([]string{DTSetDraft, DTBlockDraft, DTCubeDraft, DTI8PCube}, " "), |
|
ReplyID: &msg.MessageID, |
|
}) |
|
return |
|
} |
|
case stepDraftInfo: |
|
switch session.Options.Options.Type { |
|
case draftbot.DraftSet: |
|
session.Options.Options.Set = mlp.SetID(*msg.Text) |
|
case draftbot.DraftBlock: |
|
session.Options.Options.Block = mlp.BlockID(*msg.Text) |
|
case draftbot.DraftCube, draftbot.DraftI8PCube: |
|
// Check for a valid URL |
|
session.Options.Options.CubeURL = *msg.Text |
|
} |
|
chatid := strconv.FormatInt(msg.Chat.ChatID, 10) |
|
tgi.message(msg.User, chatid, "create", session.Options) |
|
session.Step = stepDone |
|
} |
|
} |
|
|
|
func (tgi *tgInterface) OpenDraft(chat *tg.APIChat, author tg.APIUser, options draftbot.SessionOptions) { |
|
chatid := strconv.FormatInt(chat.ChatID, 10) |
|
tgi.message(author, chatid, "create", options) |
|
} |
|
|
|
func (tgi *tgInterface) JoinDraft(chat *tg.APIChat, usr tg.APIUser) { |
|
chatid := strconv.FormatInt(chat.ChatID, 10) |
|
tgi.message(usr, chatid, "join", nil) |
|
} |
|
|
|
func (tgi *tgInterface) StartDraft(chat *tg.APIChat, usr tg.APIUser) { |
|
chatid := strconv.FormatInt(chat.ChatID, 10) |
|
tgi.message(usr, chatid, "start", nil) |
|
} |
|
|
|
func (tgi *tgInterface) PickCard(usr tg.APIUser, cardid string) { |
|
tgi.message(usr, tgi.usermap[usr.Username].Session, "pick", cardid) |
|
} |
|
|
|
func rebalance(buttons []tg.APIKeyboardButton) [][]tg.APIKeyboardButton { |
|
out := [][]tg.APIKeyboardButton{} |
|
currentRow := []tg.APIKeyboardButton{} |
|
var perRow int |
|
switch { |
|
case len(buttons) < 5: |
|
perRow = 2 |
|
case len(buttons) < 10: |
|
perRow = 3 |
|
default: |
|
perRow = 4 |
|
} |
|
for i := range buttons { |
|
currentRow = append(currentRow, buttons[i]) |
|
if i%perRow == (perRow - 1) { |
|
out = append(out, currentRow) |
|
currentRow = []tg.APIKeyboardButton{} |
|
} |
|
} |
|
if len(currentRow) > 0 { |
|
out = append(out, currentRow) |
|
} |
|
return out |
|
} |
|
|
|
func removeItem(lst []string, item string) []string { |
|
for i := range lst { |
|
if lst[i] == item { |
|
return append(lst[:i], lst[i+1:]...) |
|
} |
|
} |
|
return lst |
|
}
|
|
|