Implement Jeopardy cashout #18

Merged
pratyush merged 3 commits from jeopardy into master 2024-10-02 20:07:10 +02:00
4 changed files with 115 additions and 0 deletions

View File

@ -6,6 +6,12 @@ watbot:
name: watest name: watest
nick: watest # nick is name by default nick: watest # nick is name by default
user: watest # user is nick by default user: watest # user is nick by default
bots: # optional, no default
games: # mapping of bot names to games
katyusha:
- jeopardy # currently jeopardy is the only integrated game
hosts: # hostmasks considered as valid bots
- bot.example.com
admins: # optional, no default admins: # optional, no default
hosts: hosts:
- admin.example.com - admin.example.com

View File

@ -24,6 +24,10 @@ type watConfig struct {
Pass string `yaml:"pass"` Pass string `yaml:"pass"`
User string `yaml:"user"` User string `yaml:"user"`
Name string `yaml:"name"` Name string `yaml:"name"`
Bots struct {
Hosts []string `yaml:"hosts"`
Games wat.BotGameConfig `yaml:"games"`
} `yaml:"bots"`
Admins struct { Admins struct {
Hosts []string `yaml:"hosts"` Hosts []string `yaml:"hosts"`
} `yaml:"admins"` } `yaml:"admins"`
@ -99,6 +103,8 @@ func main() {
PermittedChannels: config.Channels.Permitted, PermittedChannels: config.Channels.Permitted,
IgnoredHosts: config.Ignores.Hosts, IgnoredHosts: config.Ignores.Hosts,
AdminHosts: config.Admins.Hosts, AdminHosts: config.Admins.Hosts,
BotHosts: config.Bots.Hosts,
BotGames: config.Bots.Games,
} }
tcpConf := &tls.Config{ tcpConf := &tls.Config{
InsecureSkipVerify: !config.Server.TlsVerify, InsecureSkipVerify: !config.Server.TlsVerify,

View File

@ -13,11 +13,14 @@ type WatBot struct {
conn *tls.Conn conn *tls.Conn
c *WatConfig c *WatConfig
game *WatGame game *WatGame
integration *WatIntegration
Db *WatDb Db *WatDb
Nick string Nick string
} }
type WatConfig struct { type WatConfig struct {
BotHosts []string
BotGames BotGameConfig
AdminHosts []string AdminHosts []string
IgnoredHosts []string IgnoredHosts []string
AutoJoinChannels []string AutoJoinChannels []string
@ -28,6 +31,7 @@ func NewWatBot(config *irc.ClientConfig, watConfig *WatConfig, serverConn *tls.C
wat := WatBot{conn: serverConn, Nick: config.Nick, c: watConfig} wat := WatBot{conn: serverConn, Nick: config.Nick, c: watConfig}
wat.Db = NewWatDb() wat.Db = NewWatDb()
wat.game = NewWatGame(&wat, wat.Db) wat.game = NewWatGame(&wat, wat.Db)
wat.integration = NewWatIntegration(&wat, wat.Db, &WatIntegrationConfig{BotHosts: watConfig.BotHosts, BotGames: watConfig.BotGames})
config.Handler = irc.HandlerFunc(wat.HandleIrcMsg) config.Handler = irc.HandlerFunc(wat.HandleIrcMsg)
wat.client = irc.NewClient(wat.conn, *config) wat.client = irc.NewClient(wat.conn, *config)
return &wat return &wat
@ -123,6 +127,11 @@ func (w *WatBot) Msg(m *irc.Message) {
args = args[1:] args = args[1:]
} }
// integration with games in other bots
if w.integration.HandleIntegration(m, args) {
return
}
// check if command char (or something weird) is present // check if command char (or something weird) is present
if args[0] != "wat" && args[0][0] != '#' { if args[0] != "wat" && args[0][0] != '#' {
return return

94
wat/integration.go Normal file
View File

@ -0,0 +1,94 @@
package wat
import (
"fmt"
"strconv"
"strings"
"github.com/go-irc/irc"
)
type BotGameConfig map[string][]string
type WatIntegrationConfig struct {
BotHosts []string
BotGames BotGameConfig
}
type WatIntegration struct {
bot *WatBot
db *WatDb
c *WatIntegrationConfig
}
func NewWatIntegration(bot *WatBot, db *WatDb, c *WatIntegrationConfig) *WatIntegration {
return &WatIntegration{bot, db, c}
}
func (w *WatIntegration) Bot(m *irc.Message) (bool, []string) {
isBot := w.bot.Allowed(m.Prefix.Host, w.c.BotHosts)
var games []string
if isBot {
for b, g := range w.c.BotGames {
if b == m.Prefix.Name {
games = g
break
}
}
}
return isBot, games
}
func (w *WatIntegration) HandleIntegration(m *irc.Message, msgargs []string) bool {
isBot, games := w.Bot(m)
Georg marked this conversation as resolved Outdated

empty line not needed here, right?

empty line not needed here, right?
Outdated
Review

Good point, the other functions do not have it either.

Good point, the other functions do not have it either.
if isBot {
// handles a message "Top finishers: (nick1: 1300) (nick2: 1200)" from an authorized Jeopardy game bot
if msgargs[0] == "Top" && msgargs[1] == "finishers:" && w.bot.Allowed("jeopardy", games) {
w.Jeopardy(m, msgargs)
return true
}
}
// not an authorized bot or no integration matched the given message
return false
Georg marked this conversation as resolved Outdated

I think here neither.

I think here neither.
Outdated
Review

I would prefer to have it here for better structure, but I get Bot() does not separate the return either, similar to the functions in the other file.

I would prefer to have it here for better structure, but I get `Bot()` does not separate the `return` either, similar to the functions in the other file.
}
func (w *WatIntegration) Jeopardy(m *irc.Message, msgargs []string) {
// hey, I avoided regex!
// 1. Starts parsing an array of message arguments containing "Top finishers: (nick1: 1000) (nick2: 2000)", where
// the "($nick: $value)" pairs can contain arbitrary nicknames + integer values and can repeat one to any amount of times
// 2. Join the array on spaces to a string, but skip the first two elements to remove "Top" and "finishers:"
// 3. Replace ") (" in the string with ";" - the semicolon is chosen as a temporary delimiter because it does not conflict with any other characters in the message
// 4. Replace ": " in the string with ":"
// 5. Replace "(" in the string with "" (relevant for the first nick/value pair)
// 6. Replace ")" in the string with "" (relevant for the last nick/value pair)
// 7. Now, we have a string like "nick1:1000;nick2:2000" - split it back into an array on ";"
// 8. The result is an array like "[nick1:1000, nick2:2000]"
finisherPrizes := strings.Split(strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Join(msgargs[2:], " "), ") (", ";", -1), ": ", ":", -1), "(", "", 1), ")", "", 1), ";")
fmt.Printf("Processing Jeopardy: %s\n", finisherPrizes)
// iterate over the "$nick:$value" string elements
for _, pair := range finisherPrizes {
// turn the string element into an array, where the first entry is the nickname, and the second the value
nameCoinPair := strings.Split(pair, ":")
coins, err := strconv.ParseUint(nameCoinPair[1], 10, 64)
if err != nil {
fmt.Printf("Invalid coins, cannot process pair for cashout: %s.\n", nameCoinPair)
continue
}
name := nameCoinPair[0]
// Jeopardy prizes are quite a lot of $$$, make it a bit more sane
coins = coins / 40
// name = we assume the Jeopardy player name to match a Watbot player name
// host = we could use some WHO logic to find the host, but assuming nickname lookup to be sufficient here
// create = based on the above, maybe rather not create Watbot players based on only a nick?
// but it expects someone to have played with Watbot before to be eligible for Jeopardy cashout ..
player := w.db.User(name, "", false)
if player.Nick == "" {
fmt.Printf("Player %s does not exist in Watbot, skipping cashout.\n", name)
continue
} else {
w.bot.reply(m, fmt.Sprintf("smartass %s, gave u %d :)", player.Nick, coins))
player.Coins += coins
w.db.Update(player)
}
}
}