hangman.go (6093B)
1 // Copyright 2013 TLH and dsp. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package games 6 7 import ( 8 "bytes" 9 "fmt" 10 "irc" 11 "strings" 12 "time" 13 ) 14 15 // Just a function with a gamestate pointer as input 16 type hgstateFn func(*hangmanState) 17 18 // Specific state for hangman game 19 type hangmanState struct { 20 gameState 21 lives uint // Lives remaining 22 tried string // Tried characters 23 goal string // Goal word 24 foundnow string // Found now chars (_ if not found) 25 botname string // Name of irc bot for privmsg 26 channame string // Name of invoking chan for privmsg 27 statchan *chan hgstateFn // Channel of next game state 28 } 29 30 // Creates a new hangman state with default values 31 func newHangmanState(ic *irc.Context, gc *chan irc.Message, sc *chan hgstateFn) *hangmanState { 32 return &hangmanState{gameState{ic, gc, "hangman", "dsp"}, 33 5, "", "", "", "unnamed", "", sc} 34 } 35 36 // For printf style functions, a short status of the game state 37 func (s *hangmanState) String() string { 38 return fmt.Sprintf(">>[%s]<< lives: %d - tried: [%s]", s.foundnow, s.lives, s.tried) 39 } 40 41 // First hangman state. Awaits on the game chan for 2 special irc messages to set the nickname 42 // of the bot and the channel on which to send msgs. Then it emits the next state (hangmanWaitGoal) 43 // to the state channel. 44 func hangmanInit(cs *hangmanState) { 45 for msg := range *cs.gchan { 46 if msg.Command == "SETNICK" { 47 cs.botname = msg.Args[0] 48 } 49 if msg.Command == "SETCHAN" { 50 cs.channame = msg.Args[0] 51 } 52 if cs.botname != "" && cs.channame != "" { 53 break 54 } 55 } 56 *cs.statchan <- hangmanWaitGoal 57 } 58 59 // Second hangman state. It waits on a private query for the goal and then emits the next state (hangmanMainLoop) 60 // to the state channel. 61 func hangmanWaitGoal(cs *hangmanState) { 62 cs.ictx.Privmsg(cs.channame, "Hangman is waiting for goal. When ready use hangman <letter|possibleword>.") 63 for msg := range *cs.gchan { 64 if len(msg.Args) < 2 { 65 continue 66 } 67 msg.Args[1] = strings.TrimSpace(msg.Args[1]) 68 if msg.Args[0] == cs.botname { 69 if strings.HasPrefix(msg.Args[1], "setgoal ") { 70 cs.goal = msg.Args[1][8:] 71 who := msg.Nick 72 cs.ictx.Privmsg(who, "Goal is now set to: "+cs.goal) 73 runes := make([]rune, len(cs.goal)) 74 for i := 0; i < len(runes); i++ { 75 runes[i] = '_' 76 } 77 cs.foundnow = string(runes) 78 break 79 } else { 80 who := msg.Nick 81 cs.ictx.Privmsg(who, "Now we are playing hangman. Use setgoal <WORD>") 82 } 83 } else { 84 if strings.HasPrefix(msg.Args[1], "hangman ") { 85 cs.ictx.Privmsg(msg.Args[0], "Still waiting for goal...") 86 } 87 } 88 } 89 *cs.statchan <- hangmanMainLoop 90 } 91 92 // Third hangman state. It waits for hangman prefixed messages on the game channel. If a word is provided 93 // and it doesn't match the goal it reduces lives and continues. If it matches the game is won. 94 // If a character is provided, it checks on the tried characters and if it is in it continues without losing. 95 // If it is not in the tried characters , checks for it with checkLetter(this updates the tried characters) 96 // and reprints the game state. At each loop if foundNow matches the goal the game is won. 97 // Then it emits the final state (hangmanEnd) to the state channel. 98 func hangmanMainLoop(cs *hangmanState) { 99 cs.ictx.Privmsg(cs.channame, cs.String()) 100 for msg := range *cs.gchan { 101 if len(msg.Args) < 2 { //this was a bug when kunt was getting NICK xxxx Args[1] was nil 102 continue 103 } 104 msg.Args[1] = strings.TrimSpace(msg.Args[1]) 105 if msg.Args[0] != cs.botname { 106 if cs.lives < 1 { 107 cs.ictx.Privmsg(msg.Args[0], "You are hanged! Goal was ->"+cs.goal) 108 *cs.statchan <- hangmanEnd 109 } 110 if strings.HasPrefix(msg.Args[1], "hangman") { 111 in := msg.Args[1][8:] 112 if len(in) > 1 { 113 if in == cs.goal { 114 cs.ictx.Privmsg(msg.Args[0], "You won!") 115 *cs.statchan <- hangmanEnd 116 } else { 117 cs.ictx.Privmsg(msg.Args[0], "You lost a life punk...") 118 cs.lives-- 119 cs.ictx.Privmsg(msg.Args[0], cs.String()) 120 continue 121 } 122 } else { 123 if strings.Index(cs.tried, string(in[0])) != -1 { 124 cs.ictx.Privmsg(msg.Args[0], "I heard you the first time") 125 continue 126 } 127 hm := cs.checkLetter([]rune(in)[0]) 128 cs.ictx.Privmsg(msg.Args[0], fmt.Sprintf("You got %d letters", hm)) 129 if hm == 0 { 130 cs.lives-- 131 } 132 cs.ictx.Privmsg(msg.Args[0], cs.String()) 133 if cs.foundnow == cs.goal { 134 cs.ictx.Privmsg(msg.Args[0], "You won!") 135 *cs.statchan <- hangmanEnd 136 } 137 } 138 } 139 } 140 } 141 } 142 143 // Fourth hangman state. Final state mostly for cleanup code. 144 func hangmanEnd(cs *hangmanState) { 145 *cs.statchan <- nil 146 } 147 148 // Main hangman dispatch, It sets a timeout and then runs as a new goroutine each 149 // consecutive state it receives on the statechannel. Before ending it removes 150 // the irc Intercept channel for this game. 151 func cmdHangman(a *irc.Context, gc *chan irc.Message) { 152 timeout := time.After(4 * time.Minute) 153 a.AddIntercept(gc) 154 stchan := make(chan hgstateFn, 1) 155 hc := newHangmanState(a, gc, &stchan) 156 stchan <- hangmanInit 157 for { 158 select { 159 case <-timeout: 160 if hc.goal == "" { 161 a.Privmsg(hc.channame, "Time is up! No goal was given") 162 } else { 163 a.Privmsg(hc.channame, "Time is up! Goal was -> "+hc.goal) 164 } 165 a.DelIntercept(gc) 166 close(stchan) 167 return 168 case st := <-stchan: 169 if st != nil { 170 go st(hc) 171 continue 172 } else { 173 break 174 } 175 } 176 break 177 } 178 a.DelIntercept(gc) 179 close(stchan) 180 } 181 182 // Checks letter against the goal. 183 // Returns the number of new letters discovered. 184 // Returns -1 if repeated check is detected. 185 func (g *hangmanState) checkLetter(c rune) int { 186 now := []rune(g.foundnow) 187 all := []rune(g.goal) 188 var buf bytes.Buffer 189 for _, v := range g.tried { 190 if c == v { 191 return -1 192 } 193 } 194 buf.WriteString(g.tried + " " + string(c)) 195 g.tried = buf.String() 196 hm := 0 197 for i, v := range all { 198 if v == c { 199 if now[i] == v { 200 return -1 201 } /* again */ 202 now[i] = v 203 hm++ 204 } 205 } 206 g.foundnow = string(now) 207 return hm 208 }