4 # :title: Hangman/Wheel Of Fortune
6 # Author:: Giuseppe "Oblomov" Bilotta <giuseppe.bilotta@gmail.com>
7 # Copyright:: (C) 2007 Giuseppe Bilotta
10 # Wheel-of-Fortune Question/Answer
12 attr_accessor :cat, :clue, :answer, :hint
13 def initialize(cat, clue, ans=nil)
15 @clue = clue # clue phrase
21 ret << "(" + cat + ") " unless cat.empty?
32 @answer = ans.dup.downcase
33 @split = @answer.scan(/./u)
34 @hint = @split.inject([]) { |list, ch|
45 ret = self.catclue << "\n"
46 ret << _("Letters called so far: ") << @used.join(" ") << "\n" unless @used.empty?
50 def check(ans_or_letter)
51 d = ans_or_letter.downcase
62 @split.each_with_index { |c, i|
80 # Wheel-of-Fortune game
82 attr_reader :manager, :single, :max, :pending
83 def initialize(manager, single, max)
95 !@curr_idx || (@last_replied == @curr_idx)
102 def mark_winner(user)
103 @last_replied = @curr_idx
106 @scores[k][:nick] = user.nick
107 @scores[k][:score] += @single
109 @scores[k] = { :nick => user.nick, :score => @single }
111 if @scores[k][:score] >= @max
120 @scores.each { |k, val|
121 table << ["%s (%s)" % [val[:nick], k], val[:score]]
123 table.sort! { |a, b| b.last <=> a.last }
127 return nil unless @curr_idx
132 # don't advance if there are no further QAs
133 return nil if @curr_idx == @qas.length - 1
144 return nil unless cur
145 return cur.check(whatever)
148 def start_add_qa(cat, clue)
149 return [nil, @pending] if @pending
150 @pending = WoFQA.new(cat.dup, clue.dup)
151 return [true, @pending]
154 def finish_add_qa(ans)
155 return nil unless @pending
156 @pending.answer = ans.dup
163 class WheelOfFortune < Plugin
166 # TODO load/save running games?
171 chan = p[:chan] || m.channel
173 m.reply _("you must specify a channel")
176 ch = p[:chan].irc_downcase(m.server.casemap).intern
179 m.reply _("there's already a Wheel-of-Fortune game on %{chan}, managed by %{who}") % {
181 :who => @games[ch].manager
185 @games[ch] = game = WoFGame.new(m.botuser, p[:single], p[:max])
186 @bot.say chan, _("%{who} just created a new Wheel-of-Fortune game to %{max} points (%{single} per question)") % {
187 :who => game.manager,
189 :single => game.single
191 @bot.say m.source, _("ok, the game has been created. now add clues and answers with \"wof %{chan} [category: <category>,] clue: <clue>, answer: <ans>\". if the clue and answer don't fit in one line, add the answer separately with \"wof %{chan} answer <answer>\"") % {
197 ch = p[:chan].irc_downcase(m.server.casemap).intern
199 m.reply _("there's no Wheel-of-Fortune game running on %{chan}") % {
209 m.reply _("sorry, the answer cannot contain the '*' character")
214 worked, qa = game.start_add_qa(cat, clue)
216 str = ans.empty? ? _("ok, new clue added for %{chan}: %{catclue}") : nil
218 str = _("there's already a pending clue for %{chan}: %{catclue}")
220 m.reply _(str) % { :chan => p[:chan], :catclue => qa.catclue } if str
221 return unless worked or !ans.empty?
224 qa = game.finish_add_qa(ans)
226 str = _("ok, new QA added for %{chan}: %{catclue} => %{ans}")
228 str = _("there's no pending clue for %{chan}!")
230 m.reply _(str) % { :chan => p[:chan], :catclue => qa ? qa.catclue : nil, :ans => qa ? qa.answer : nil}
231 announce(m, p.merge({ :next => true }) ) if game.waiting?
233 m.reply _("something went wrong, I can't seem to understand what you're trying to set up")
237 def announce(m, p={})
238 chan = p[:chan] || m.channel
239 ch = chan.irc_downcase(m.server.casemap).intern
241 m.reply _("there's no Wheel-of-Fortune game running on %{chan}") % { :chan => p[:chan] }
245 qa = p[:next] ? game.next : game.current
247 m.reply _("there are no Wheel-of-Fortune questions for %{chan}, I'm waiting for %{who} to add them") % {
254 @bot.say chan, qa.announcement
257 def score_table(chan, game, opts={})
258 limit = opts[:limit] || -1
259 table = game.score_table[0..limit]
260 nick_wd = table.map { |a| a.first.length }.max
261 score_wd = table.first.to_s.length
263 @bot.say chan, "%*s : %*u" % [nick_wd, t.first, score_wd, t.last]
268 return unless m.kind_of?(PrivMessage) and not m.address?
269 ch = m.channel.irc_downcase(m.server.casemap).intern
270 return unless game = @games[ch]
271 return if game.waiting?
272 check = game.check(m.message)
273 debug "check: #{check.inspect}"
277 warning "game #{game}, qa #{game.current} checked nil against #{m.message}"
280 # m.reply "STUPID! YOU SO STUPID!"
284 when Numeric, :missing
285 # TODO may alter score depening on how many letters were guessed
286 # TODO what happens when the last hint reveals the whole answer?
289 want_more = game.mark_winner(m.source)
290 m.reply _("%{who} got it! The answer was: %{ans}") % {
291 :who => m.sourcenick,
292 :ans => game.current.answer
294 if want_more == :done
297 m.reply _("%{who} wins the game after %{count} rounds!") % {
298 :who => table.first.first,
301 score_table(m.channel, game)
304 table = game.score_table
305 nick_wd = table.map { |a| a.first.length }.max
306 score_wd = table.first.to_s.length
307 m.reply _("Score after %{count} rounds") % { :count => game.round }
308 score_table(m.channel, game)
309 announce(m, :next => true)
313 warning "game #{game}, qa #{game.current} checked #{check} against #{m.message}"
318 ch = m.channel.irc_downcase(m.server.casemap).intern
320 m.reply _("there's no Wheel-of-Fortune game running on %{chan}") % {
329 game = @games.delete(ch)
331 @bot.say chan, _("Wheel-of-Fortune game cancelled after %{count} rounds. Partial score:")
332 score_table(chan, game)
336 @games.each_key { |k| do_cancel(k) }
341 plugin = WheelOfFortune.new
343 plugin.map "wof", :action => 'announce', :private => false
344 plugin.map "wof cancel", :action => 'cancel', :private => false
345 plugin.map "wof [:chan] play for :single [points] to :max [points]", :action => 'setup_game'
346 plugin.map "wof :chan [category: *cat,] clue: *clue[, answer: *ans]", :action => 'setup_qa', :public => false
347 plugin.map "wof :chan answer: *ans", :action => 'setup_qa', :public => false