kunt

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

commit f49c9d48e342615f5c327c319b5319c4e12aa457
parent 9766e397b006b38ba94438c7d309e2c7d8a8e27e
Author: sin <sin@2f30.org>
Date:   Thu May  2 16:47:18 +0100

separate games.go and hangman.go

Diffstat:
src/games/games.go | 209++++---------------------------------------------------------------------------
src/games/hangman.go | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 204 insertions(+), 199 deletions(-)
diff --git a/src/games/games.go b/src/games/games.go @@ -1,150 +1,19 @@ package games import ( - "bytes" - "fmt" "irc" - "strings" - "time" ) -// just a function with a gamestate pointer as input -type hgstateFn func(*hangmanState) - -//this is a base gamestate for irc games +// 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} -} - -//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.Command == "SETNICK" { - cs.botname = msg.Args[0] - } - if msg.Command == "SETCHAN" { - cs.channame = msg.Args[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.Privmsg(cs.channame, "Hangman is waiting for goal. When ready use hangman <letter|possibleword>") - for msg := range *cs.gchan { - msg.Args[1] = strings.TrimSpace(msg.Args[1]) - if msg.Args[0] == cs.botname { - if strings.HasPrefix(msg.Args[1], "setgoal ") { - cs.goal = msg.Args[1][8:] - who := msg.Nick - cs.ictx.Privmsg(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.Nick - cs.ictx.Privmsg(who, "Now we are playing hangman. Use setgoal <WORD>") - } - } else { - if strings.HasPrefix(msg.Args[1], "hangman ") { - cs.ictx.Privmsg(msg.Args[0], "Still waiting for goal...") - } - } - } - *cs.statchan <- hangmanMainLoop + 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 } -//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.Privmsg(cs.channame, cs.String()) - for msg := range *cs.gchan { - msg.Args[1] = strings.TrimSpace(msg.Args[1]) - if msg.Args[0] != cs.botname { - if cs.lives < 1 { - cs.ictx.Privmsg(msg.Args[0], "You are hanged! Goal was ->"+cs.goal) - *cs.statchan <- hangmanEnd - } - if strings.HasPrefix(msg.Args[1], "hangman ") { - in := msg.Args[1][8:] - if len(in) > 1 { - if in == cs.goal { - cs.ictx.Privmsg(msg.Args[0], "You won!") - *cs.statchan <- hangmanEnd - } else { - cs.ictx.Privmsg(msg.Args[0], "You lost a life punk...") - cs.lives -= 1 - cs.ictx.Privmsg(msg.Args[0], cs.String()) - continue - } - } else { - if strings.Index(cs.tried, string(in[0])) != -1 { - cs.ictx.Privmsg(msg.Args[0], "I heard you the first time") - continue - } - hm := cs.checkLetter([]rune(in)[0]) - cs.ictx.Privmsg(msg.Args[0], fmt.Sprintf("You got %d letters", hm)) - if hm == 0 { - cs.lives -= 1 - } - cs.ictx.Privmsg(msg.Args[0], cs.String()) - if cs.foundnow == cs.goal { - cs.ictx.Privmsg(msg.Args[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. +// 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) @@ -154,37 +23,7 @@ type gameEng struct { games map[string]func(*irc.IrcContext, *chan irc.IrcMessage) } -//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) - stchan := make(chan hgstateFn, 1) - hc := newHangmanState(a, gc, &stchan) - stchan <- hangmanInit - for { - select { - case <-timeout: - a.Privmsg(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 - } else { - break - } - } - break - } - a.DelIntercept(gc) - close(stchan) -} - -//Returns all available games as a string array. +// Returns all available games as a string array func (g *gameEng) List() []string { r := []string{} for k, _ := range g.games { @@ -193,43 +32,15 @@ func (g *gameEng) List() []string { return r } -//Initializes games strings with their corresponding goroutines +// Initializes games strings with their corresponding goroutines func NewGameEng() *gameEng { return &gameEng{games: map[string]func(*irc.IrcContext, *chan irc.IrcMessage){"hangman": cmdHangman}} } -//Fire up a goroutine if the game is in the map +// 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) } } - -//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 - for _, v := range g.tried { - if c == v { - return -1 - } - } - buf.WriteString(g.tried + " " + string(c)) - g.tried = buf.String() - hm := 0 - for i, v := range all { - if v == c { - if now[i] == v { - return -1 - } /* again */ - now[i] = v - hm++ - } - } - g.foundnow = string(now) - return hm -} diff --git a/src/games/hangman.go b/src/games/hangman.go @@ -0,0 +1,194 @@ +package games + +import ( + "bytes" + "fmt" + "irc" + "strings" + "time" +) + +// Just a function with a gamestate pointer as input +type hgstateFn func(*hangmanState) + +// 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} +} + +// 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.Command == "SETNICK" { + cs.botname = msg.Args[0] + } + if msg.Command == "SETCHAN" { + cs.channame = msg.Args[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.Privmsg(cs.channame, "Hangman is waiting for goal. When ready use hangman <letter|possibleword>") + for msg := range *cs.gchan { + msg.Args[1] = strings.TrimSpace(msg.Args[1]) + if msg.Args[0] == cs.botname { + if strings.HasPrefix(msg.Args[1], "setgoal ") { + cs.goal = msg.Args[1][8:] + who := msg.Nick + cs.ictx.Privmsg(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.Nick + cs.ictx.Privmsg(who, "Now we are playing hangman. Use setgoal <WORD>") + } + } else { + if strings.HasPrefix(msg.Args[1], "hangman ") { + cs.ictx.Privmsg(msg.Args[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.Privmsg(cs.channame, cs.String()) + for msg := range *cs.gchan { + msg.Args[1] = strings.TrimSpace(msg.Args[1]) + if msg.Args[0] != cs.botname { + if cs.lives < 1 { + cs.ictx.Privmsg(msg.Args[0], "You are hanged! Goal was ->"+cs.goal) + *cs.statchan <- hangmanEnd + } + if strings.HasPrefix(msg.Args[1], "hangman ") { + in := msg.Args[1][8:] + if len(in) > 1 { + if in == cs.goal { + cs.ictx.Privmsg(msg.Args[0], "You won!") + *cs.statchan <- hangmanEnd + } else { + cs.ictx.Privmsg(msg.Args[0], "You lost a life punk...") + cs.lives -= 1 + cs.ictx.Privmsg(msg.Args[0], cs.String()) + continue + } + } else { + if strings.Index(cs.tried, string(in[0])) != -1 { + cs.ictx.Privmsg(msg.Args[0], "I heard you the first time") + continue + } + hm := cs.checkLetter([]rune(in)[0]) + cs.ictx.Privmsg(msg.Args[0], fmt.Sprintf("You got %d letters", hm)) + if hm == 0 { + cs.lives -= 1 + } + cs.ictx.Privmsg(msg.Args[0], cs.String()) + if cs.foundnow == cs.goal { + cs.ictx.Privmsg(msg.Args[0], "You won!") + *cs.statchan <- hangmanEnd + } + } + } + } + } +} + +// Fourth hangman state. Final state mostly for cleanup code. +func hangmanEnd(cs *hangmanState) { + *cs.statchan <- nil +} + +// 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) + stchan := make(chan hgstateFn, 1) + hc := newHangmanState(a, gc, &stchan) + stchan <- hangmanInit + for { + select { + case <-timeout: + a.Privmsg(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 + } else { + break + } + } + break + } + a.DelIntercept(gc) + close(stchan) +} + +// 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 + for _, v := range g.tried { + if c == v { + return -1 + } + } + buf.WriteString(g.tried + " " + string(c)) + g.tried = buf.String() + hm := 0 + for i, v := range all { + if v == c { + if now[i] == v { + return -1 + } /* again */ + now[i] = v + hm++ + } + } + g.foundnow = string(now) + return hm +}