]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/games/hangman.rb
hangman plugin
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / games / hangman.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Hangman Plugin
5 #
6 # Author:: Raine Virta <rane@kapsi.fi>
7 # Copyright:: (C) 2009 Raine Virta
8 # License:: GPL v2
9 #
10 # Hangman game for rbot
11
12 module RandomWord
13   SITE = "http://coyotecult.com/tools/randomwordgenerator.php"
14   
15   def self.get(count=1)
16     res = Net::HTTP.post_form(URI.parse(SITE), {'numwords' => count})
17     words = res.body.scan(%r{<a.*?\?w=(.*?)\n}).flatten
18     
19     count == 1 ? words.first : words
20   end
21 end
22
23 class Hangman
24   attr_reader :misses, :guesses, :word, :letters
25   
26   STAGES = [' (x_x) ', ' (;_;) ', ' (>_<) ', ' (-_-) ', ' (o_~) ', ' (^_^) ', '\(^o^)/']
27   HEALTH = STAGES.size-1
28   LETTER = /[^\W0-9_]/u
29
30   def initialize(word, channel=nil)
31     @word    = word.downcase
32     @guesses = []
33     @misses  = []
34     @health  = HEALTH
35     @solved  = false
36   end
37
38   def letters
39     # array of the letters in the word
40     @word.split(//).reject { |c| c !~ LETTER  }
41   end
42
43   def face
44     STAGES[@health]
45   end
46
47   def to_s
48     @word.split(//).map { |c|
49       @guesses.include?(c) || c !~ LETTER  ? c : "_"
50     }.join
51   end
52
53   def guess(str)
54     str.downcase!
55
56     # full word guess
57     if str !~ /^#{LETTER}$/
58       word == str ? @solved = true : punish
59     else # single letter guess
60       return false if @guesses.include?(str) # letter has been guessed before
61
62       unless letters.include?(str)
63         @misses << str
64         punish
65       end
66       
67       @guesses << str
68     end
69   end
70
71   def over?
72     won? || lost?
73   end
74
75   def won?
76     (letters-@guesses).empty? || @solved
77   end
78
79   def lost?
80     @health.zero?
81   end
82   
83   def punish
84     @health -= 1
85   end
86 end
87
88 class HangmanPlugin < Plugin
89   def initialize
90     super
91     @games = {}
92   end
93
94   def help(plugin, topic="")
95     case topic
96     when ""
97       #plugin.map "hangman [play] [on :channel] [with word :word] [with [:adj] length [:relation :size]]",
98       return "hangman game plugin - topics: play, stop"
99     when "play"
100       return "hangman play [on <channel>] [with word <word>] | hangman play with [max|min] length [<|>|==] [<length>] => start a hangman game -- word will be randomed in case it's omitted"
101     when "stop"
102       return "hangman stop => quits the current game"
103     end
104   end
105
106   def start(m, params)
107     word = unless params[:word]
108       words = RandomWord::get(100)
109       
110       if adj = params[:adj]
111         words = words.sort_by { |e| e.size }
112         
113         if adj == "max"
114           words.last
115         else
116           words.first
117         end
118       elsif params[:relation] && params[:size]
119         words = words.select { |w| w.size.send(params[:relation], params[:size].to_i) }
120         
121         unless words.empty?
122           words.first
123         else
124           m.reply "suitable word not found in the set"
125           nil
126         end
127       else
128         words.first
129       end
130     else
131       params[:word]
132     end
133     
134     return unless word
135
136     if (params[:channel] || m.public?)
137       target = if m.public?
138         m.channel.to_s
139       else
140         params[:channel]
141       end
142       
143       # is the bot on the channel?
144       unless @bot.server.channels.names.include?(target.to_s)
145         m.reply "i'm not on that channel"
146         return
147       end
148       
149       if @games.has_key?(target)
150         m.reply "there's already a hangman game in progress on that channel"
151         return
152       end
153       
154       @bot.say target, "#{m.source} has started a hangman -- join the fun!"
155     else
156       target = m.source.to_s
157     end
158     
159     @games[target] = Hangman.new(word)
160
161     @bot.say target, game_status(@games[target])
162   end
163   
164   def stop(m, params)
165     source = if m.public?
166       m.channel
167     else
168       m.source
169     end
170     
171     if @games.has_key?(source.to_s) 
172       @bot.say source, "oh well, the answer would've been #{Bold}#{@games[source.to_s].word}#{Bold}"
173       @games.delete(source.to_s)
174     end
175   end
176
177   def message(m)
178     source = if m.public?
179       m.channel.to_s
180     else
181       m.source.to_s
182     end
183
184     if (game = @games[source])
185       if m.message =~ /^[^\W0-9_]$/u || m.message =~ prepare_guess_regex(game)
186         return unless game.guess(m.message)
187         
188         m.reply game_status(game)
189       end
190
191       if game.over?
192         if game.won?
193           m.reply "game over - you win!"
194         elsif game.lost?
195           m.reply "game over - you lose!"
196         end
197
198         @games.delete(source)
199       end
200     end
201   end
202   
203   def prepare_guess_regex(game)
204     Regexp.new("^#{game.word.split(//).map { |c|
205       game.guesses.include?(c) || c !~ Hangman::LETTER ? c : '[^\W0-9_]'
206     }.join("")}$")
207   end
208
209   def game_status(game)
210     "%{word} %{face} %{misses}" % {
211       :word   => game.over? ? "#{Bold}#{game.word}#{Bold}" : game.to_s,
212       :face   => game.face,
213       :misses => game.misses.map { |e| e.upcase }.join(" ")
214     }
215   end
216 end
217
218 plugin = HangmanPlugin.new
219 plugin.map "hangman [play] [on :channel] [with word :word] [with [:adj] length [:relation :size]]",
220   :action => 'start',
221   :requirements => { :adj => /min|max/, :relation => /<|<=|>=|>|==/, :size => /\d+/ }
222   
223 plugin.map "hangman stop", :action => 'stop'