commit f04aafc8024da51b07eea6f15d4c697740ebf500
parent ae82f1d3c307db1cc3bd9bb0321224d46d53ab54
Author: sin <sin@2f30.org>
Date: Sun, 21 Apr 2013 13:44:27 +0100
Apply changes by dsp
Diffstat:
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],