kunt

golang IRC bot
git clone git://git.2f30.org/kunt
Log | Files | Refs | LICENSE

commit f04aafc8024da51b07eea6f15d4c697740ebf500
parent ae82f1d3c307db1cc3bd9bb0321224d46d53ab54
Author: sin <sin@2f30.org>
Date:   Sun, 21 Apr 2013 13:44:27 +0100

Apply changes by dsp

Diffstat:
Msrc/games/games.go | 307++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/kunt/kunt.go | 10++++++++--
2 files changed, 170 insertions(+), 147 deletions(-)

diff --git a/src/games/games.go b/src/games/games.go @@ -5,25 +5,146 @@ import ( "fmt" "irc" "strings" + "time" ) -type game struct { - On bool - name string - creator string - goal string - goalmaster string +// just a function with a gamestate pointer as input +type hgstateFn func(*hangmanState) + +//this is a base gamestate for irc games +type gameState struct { + ictx *irc.IrcContext // the context to send commands with + gchan *chan irc.IrcMessage // a chan that intercepts game irc messages + name string //name of the game + creator string //name of game creator +} + +//specific state for hangman game +type hangmanState struct { + gameState + lives uint //lives remaining + tried string //tried characters + goal string //goal word + foundnow string //found now chars (_ if not found) + botname string //name of irc bot for privmsg + channame string //name of invoking chan for privmsg + statchan *chan hgstateFn //channel of next game state +} + +//creates a new hangman state with default values +func newHangmanState(ic *irc.IrcContext, gc *chan irc.IrcMessage, sc *chan hgstateFn) *hangmanState { + return &hangmanState{gameState{ic, gc, "hangman", "dsp"}, + 5, "", "", "", "unnamed", "", sc} } -type Gamer interface { - IsOn() bool - SetOn(bool) - ParseGameInput(a string) string - GameStatus() string - SetGameGoal(a string, who string) string - GetGoal() string +//for printf style functions, a short status of the game state +func (s *hangmanState) String() string { + return fmt.Sprintf(">>[%s]<< lives: %d tried :[%s]", s.foundnow, s.lives, s.tried) } +//First hangman state. Awaits on the game chan for 2 special irc messages to set the nickname +//of the bot and the channel on which to send msgs. Then it emits the next state (hangmanWaitGoal) +//to the state channel. +func hangmanInit(cs *hangmanState) { + for msg := range *cs.gchan { + if msg.Cmd == "SETNICK" { + cs.botname = msg.Params[0] + } + if msg.Cmd == "SETCHAN" { + cs.channame = msg.Params[0] + } + if cs.botname != "" && cs.channame != "" { + break + } + } + *cs.statchan <- hangmanWaitGoal +} + +//Second hangman state. It waits on a private query for the goal and then emits the next state (hangmanMainLoop) +//to the state channel +func hangmanWaitGoal(cs *hangmanState) { + cs.ictx.SendPrivmsg(cs.channame, "hangman is waiting for goal.When ready use hangman <letter|possibleword>") + for msg := range *cs.gchan { + msg.Params[1] = strings.TrimSpace(msg.Params[1]) + if msg.Params[0] == cs.botname { + if strings.HasPrefix(msg.Params[1], ":setgoal ") { + cs.goal = msg.Params[1][9:] + who := msg.Prefix[:strings.Index(msg.Prefix, "!")] + cs.ictx.SendPrivmsg(who, "goal is now set to :"+cs.goal) + runes := make([]rune, len(cs.goal)) + for i := 0; i < len(runes); i++ { + runes[i] = '_' + } + cs.foundnow = string(runes) + break + } else { + who := msg.Prefix[:strings.Index(msg.Prefix, "!")] + cs.ictx.SendPrivmsg(who, "Now we are playing hangman. use setgoal <WORD>") + } + } else { + if strings.HasPrefix(msg.Params[1], ":hangman ") { + cs.ictx.SendPrivmsg(msg.Params[0], "still waiting for goal") + } + } + } + *cs.statchan <- hangmanMainLoop +} + +//Third hangman state. It waits for hangman prefixed messages on the game channel. If a word is provided +//and it doesn't match the goal it reduces lives and continues. If it matches the game is won. +//If a character is provided, it checks on the tried characters and if it is in it continues without losing. +//If it is not in the tried characters , checks for it with checkLetter(this updates the tried characters) +//and reprints the game state. At each loop if foundNow matches the goal the game is won. +//Then it emits the final state (hangmanEnd) to the state channel +func hangmanMainLoop(cs *hangmanState) { + cs.ictx.SendPrivmsg(cs.channame, cs.String()) + for msg := range *cs.gchan { + msg.Params[1] = strings.TrimSpace(msg.Params[1]) + if msg.Params[0] != cs.botname { + if cs.lives < 1 { + cs.ictx.SendPrivmsg(msg.Params[0], "You are hanged! goal was ->"+cs.goal) + *cs.statchan <- hangmanEnd + } + if strings.HasPrefix(msg.Params[1], ":hangman ") { + in := msg.Params[1][9:] + if len(in) > 1 { + if in == cs.goal { + cs.ictx.SendPrivmsg(msg.Params[0], "You won!") + *cs.statchan <- hangmanEnd + } else { + cs.ictx.SendPrivmsg(msg.Params[0], "You lost a life punk...") + cs.lives -= 1 + cs.ictx.SendPrivmsg(msg.Params[0], cs.String()) + continue + } + } else { + if strings.Index(cs.tried, string(in[0])) != -1 { + cs.ictx.SendPrivmsg(msg.Params[0], "I heard you the first time") + continue + } + hm := cs.checkLetter([]rune(in)[0]) + cs.ictx.SendPrivmsg(msg.Params[0], fmt.Sprintf("You got %d letters", hm)) + if hm == 0 { + cs.lives -= 1 + } + cs.ictx.SendPrivmsg(msg.Params[0], cs.String()) + if cs.foundnow == cs.goal { + cs.ictx.SendPrivmsg(msg.Params[0], "You won!") + *cs.statchan <- hangmanEnd + } + } + } + } + } +} + +//Fourth hangman state. Final state mostly for cleanup code. +func hangmanEnd(cs *hangmanState) { + *cs.statchan <- nil +} + +//A GameEnger will List available games and invoke a new one if it is on the map. +//New games are invoked as new goroutines that communicate via channels. type GameEnger interface { List() []string New(string, *irc.IrcContext, *chan irc.IrcMessage) @@ -33,47 +154,37 @@ type gameEng struct { games map[string]func(*irc.IrcContext, *chan irc.IrcMessage) } -type hangmanGame struct { - game - lives uint - foundnow string - tried string -} - -func cmdTest(a *irc.IrcContext, gc *chan irc.IrcMessage) { - a.SendPrivmsg("#2f30-devel", "test workd") +//Main hangman dispatch, It sets a timeout and then runs as a new goroutine each +//consecutive state it receives on the statechannel. Before ending it removes +//the irc Intercept channel for this game. +func cmdHangman(a *irc.IrcContext, gc *chan irc.IrcMessage) { + timeout := time.After(4 * time.Minute) a.AddIntercept(gc) - name := "unset" + stchan := make(chan hgstateFn, 1) + hc := newHangmanState(a, gc, &stchan) + stchan <- hangmanInit for { select { - case msg := <-*gc: - if msg.Cmd == "SETNICK" { - name = msg.Params[0] + case <-timeout: + a.SendPrivmsg(hc.channame, "Time is up! goal was -> "+hc.goal) + a.DelIntercept(gc) + close(stchan) + return + case st := <-stchan: + if st != nil { + go st(hc) continue - } - - if msg.Params[0] == name { - who := msg.Prefix[:strings.Index(msg.Prefix, "!")] - a.SendPrivmsg(who, "hello there "+who) } else { - a.SendPrivmsg(msg.Params[0], "caught string") + break } } + break } - /* - for { - select { - case msg := <- a.incomingMsg : - a.SendPrivmsg(msg.Params[0],"intercepted by game reemmiting") - a.incomingMsg<-msg - } - }*/ -} - -func cmdTest1(a *irc.IrcContext, gc *chan irc.IrcMessage) { - a.SendPrivmsg("#2f30-devel", "OLEEEEEEEEE") + a.DelIntercept(gc) + close(stchan) } +//Returns all available games as a string array. func (g *gameEng) List() []string { r := []string{} for k, _ := range g.games { @@ -82,107 +193,23 @@ func (g *gameEng) List() []string { return r } +//Initializes games strings with their corresponding goroutines func NewGameEng() *gameEng { - return &gameEng{games: map[string]func(*irc.IrcContext, *chan irc.IrcMessage){"test": cmdTest, "test1": cmdTest1}} + return &gameEng{games: map[string]func(*irc.IrcContext, *chan irc.IrcMessage){"hangman": cmdHangman}} } +//Fire up a goroutine if the game is in the map func (g *gameEng) New(a string, ic *irc.IrcContext, gc *chan irc.IrcMessage) { if f, ok := g.games[a]; ok { go f(ic, gc) } } -func (g *hangmanGame) ParseGameInput(a string) string { - q := strings.Fields(a) - if q[0] == "hangman:" { - if g.goal == "" { - return "Goal is not yet set" - } - if len(q) != 2 { - return "Syntax is hangman: <letter> or <goal>" - } - if len(q[1]) == 1 { - hm := g.checkLetter([]rune(q[1])[0]) - if hm == -1 { - return "I heard you the first time" - } - if hm == 0 { - if g.lives >= 1 { - g.lives-- - return fmt.Sprintf("You lost a life punk.Remaining:%d [%s tries]", g.lives, g.tried) - } else { - g.SetOn(false) - return fmt.Sprintf("You are hanged mate. It was %s", g.goal) - } - } else { - winstr := "" - if g.foundnow == g.goal { - winstr = " You win!" - g.SetOn(false) - } - return fmt.Sprintf("Great! You said '%s' and found %d new letters: >> %s << [%d lives]%s", - q[1], hm, g.foundnow, g.lives, winstr) - } - } else { - if q[1] == g.goal { - g.SetOn(false) - return "You win!" - } else { - if g.lives >= 1 { - g.lives-- - return fmt.Sprintf("You lost a life punk.Remaining:%d", g.lives) - } else { - g.SetOn(false) - return fmt.Sprintf("You are hanged mate. It was %s", g.goal) - } - } - } - } - return "" -} - -func (g *hangmanGame) GetGoal() string { - return g.goal -} - -func (g *hangmanGame) GameStatus() string { - return fmt.Sprintf("Game: %s by %s, >> %s << [%d lives] [%s tries]", - g.name, g.creator, g.foundnow, g.lives, g.tried) -} - -func (g *hangmanGame) IsOn() bool { - return g.On -} - -func (g *hangmanGame) SetOn(a bool) { - g.On = a -} - -func (g *hangmanGame) SetGameGoal(a string, who string) string { - q := strings.Fields(a) - if len(q) != 2 || q[0] != "setgoal" { - return "Now we are playing hangman, i need : setgoal stringToFind" - } - if g.goal != "" { - return fmt.Sprintf("%s already has set the goal", g.goalmaster) - } - g.goalmaster = who - g.goal = q[1] - runes := make([]rune, len(g.goal)) - for i := 0; i < len(runes); i++ { - runes[i] = '_' - } - g.foundnow = string(runes) - g.SetOn(true) - return "goal is now set" -} - -/* checks letter against the goal - returns the number of new letters discovered - returns -1 if repeated check is detected */ - -func (g *hangmanGame) checkLetter(c rune) int { +//Checks letter against the goal. +//returns the number of new letters discovered. +//returns -1 if repeated check is detected. +func (g *hangmanState) checkLetter(c rune) int { now := []rune(g.foundnow) all := []rune(g.goal) var buf bytes.Buffer @@ -206,13 +233,3 @@ func (g *hangmanGame) checkLetter(c rune) int { g.foundnow = string(now) return hm } - -func NewHangmanGame() *hangmanGame { - r := new(hangmanGame) - r.On = true - r.name = "HangMan" - r.creator = "lostd|dsp" - r.lives = 5 - r.tried = "" - return r -} diff --git a/src/kunt/kunt.go b/src/kunt/kunt.go @@ -80,6 +80,7 @@ func cmdHelp(msg irc.IrcMessage) { "!uptime -> Show uptime", "!wisdom -> Invoke fortune(6)", "!radio -> Print now playing song", + "!game -> List or Play games", "!src -> Fetch kunt's source code", "!TODO -> Print the TODO list", "!version -> Show version", @@ -116,6 +117,7 @@ func cmdAddQuote(msg irc.IrcMessage) { } s = s[11:] s = strings.TrimSpace(s) + s += "\n" buf := []byte(s) quote, err := quoteDb.Put(-1, buf) if err != nil { @@ -236,7 +238,9 @@ func cmdGame(msg irc.IrcMessage) { } gchan := make(chan irc.IrcMessage) geng.New(s, kunt.ircCtx, &gchan) - gchan <- irc.IrcMessage{Prefix: "", Cmd: "SETNICK", Params: []string{"kunt"}} + time.Sleep(512 * time.Millisecond) + gchan <- irc.IrcMessage{Prefix: "", Cmd: "SETNICK", Params: []string{botname}} //Let the game engine know. + gchan <- irc.IrcMessage{Prefix: "", Cmd: "SETCHAN", Params: []string{msg.Params[0]}} } var sslon = flag.Bool("s", false, "SSL support") @@ -245,9 +249,11 @@ var quoteDb *fsdb.Fsdb var urlDb *fsdb.Fsdb var kunt kuntCtx var geng games.GameEnger +var botname string func main() { kunt.stime = time.Now() + botname = "kunt" geng = games.NewGameEng() log.SetPrefix("kunt: ") @@ -288,7 +294,7 @@ func main() { cfg := irc.IrcConfig{ irc.Network: "grnet", - irc.Nick: "kunt", + irc.Nick: botname, irc.User: "z0mg", irc.Serv: hostport[0], irc.Port: hostport[1],