]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - data/rbot/plugins/games/azgame.rb
azgame plugin: better handling of late checks
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / games / azgame.rb
index a6979830439741ca861fe41e80c49ce0aea7d386..fb81b6d74948f1109944d09d2065258bb87d8296 100644 (file)
@@ -40,20 +40,29 @@ class AzGame
   def check(word)\r
     w = word.downcase\r
     debug "checking #{w} for #{@word} in #{@range}"\r
+    # Since we're called threaded, bail out early if a winner\r
+    # was assigned already\r
+    return [:ignore, nil] if @winner\r
     return [:bingo, nil] if w == @word\r
     return [:out, @range] if w < @range.first or w > @range.last\r
     return [:ignore, @range] if w == @range.first or w == @range.last\r
+    # This is potentially slow (for languages that check online)\r
     return [:noexist, @range] unless @plugin.send("is_#{@lang}?", w)\r
     debug "we like it"\r
-    if w < @word\r
+    # Check again if there was a winner in the mean time,\r
+    # and bail out if there was\r
+    return [:ignore, nil] if @winner\r
+    if w < @word and w > @range.first\r
       @range.first.replace(w)\r
-    else\r
+      return [:in, @range]\r
+    elsif w > @word and w < @range.last\r
       @range.last.replace(w)\r
+      return [:in, @range]\r
     end\r
-    return [:in, @range]\r
+    return [:out, @range]\r
   end\r
 \r
-# TODO scoring: base score is t = ceil(100*exp(-(n-1)^2/50))+p for n attempts\r
+# TODO scoring: base score is t = ceil(100*exp(-((n-1)^2)/(50^2)))+p for n attempts\r
 #               done by p players; players that didn't win but contributed\r
 #               with a attempts will get t*a/n points\r
 \r
@@ -62,16 +71,16 @@ class AzGame
   def score\r
     n = @total_tries\r
     p = @tries.keys.length\r
-    t = (100*exp(-(n-1)**2/50**2)).ceil + p\r
+    t = (100*exp(-((n-1)**2)/(50.0**2))).ceil + p\r
     debug "Total score: #{t}"\r
     ret = Hash.new\r
     @tries.each { |k, a|\r
-      ret[k] = [t*a/n, "%d %s" % [a, a > 1 ? "tries" : "try"]]\r
+      ret[k] = [t*a/n, n_("%{count} try", "%{count} tries", a) % {:count => a}]\r
     }\r
     if @winner\r
       debug "replacing winner score of %d with %d" % [ret[@winner].first, t]\r
       tries = ret[@winner].last\r
-      ret[@winner] = [t, "winner, #{tries}"]\r
+      ret[@winner] = [t, _("winner, %{tries}") % {:tries => tries}]\r
     end\r
     return ret.sort_by { |h| h.last.first }.reverse\r
   end\r
@@ -92,7 +101,7 @@ class AzGamePlugin < Plugin
     else\r
       @wordcache = Hash.new\r
     end\r
-    debug "\n\n\nA-Z wordcache: #{@wordcache.inspect}\n\n\n"\r
+    debug "A-Z wordcache: #{@wordcache.pretty_inspect}"\r
 \r
     @rules = {\r
       :italian => {\r
@@ -147,44 +156,51 @@ class AzGamePlugin < Plugin
   end\r
 \r
   def word_check(m, k, word)\r
-    isit = @games[k].check(word)\r
-    case isit.first\r
-    when :bingo\r
-      m.reply "#{Bold}BINGO!#{Bold}: the word was #{Underline}#{word}#{Underline}. Congrats, #{Bold}#{m.sourcenick}#{Bold}!"\r
-      @games[k].total_tries += 1\r
-      @games[k].tries[m.source] += 1\r
-      @games[k].winner = m.source\r
-      ar = @games[k].score.inject([]) { |res, kv|\r
-        res.push("%s: %d (%s)" % kv.flatten)\r
-      }\r
-      m.reply "The game was won after #{@games[k].total_tries} tries. Scores for this game:    #{ar.join('; ')}"\r
-      @games.delete(k)\r
-    when :out\r
-      m.reply "#{word} is not in the range #{Bold}#{isit.last}#{Bold}" if m.address?\r
-    when :noexist\r
-      m.reply "#{word} doesn't exist or is not acceptable for the game"\r
-      @games[k].total_failed += 1\r
-      @games[k].failed[m.source] += 1\r
-    when :in\r
-      m.reply "close, but no cigar. New range: #{Bold}#{isit.last}#{Bold}"\r
-      @games[k].total_tries += 1\r
-      @games[k].tries[m.source] += 1\r
-    when :ignore\r
-      m.reply "#{word} is already one of the range extrema: #{isit.last}" if m.address?\r
-    else\r
-      m.reply "hm, something went wrong while verifying #{word}"\r
-    end\r
+    # Not really safe ... what happens\r
+    Thread.new {\r
+      isit = @games[k].check(word)\r
+      case isit.first\r
+      when :bingo\r
+        m.reply _("%{bold}BINGO!%{bold} the word was %{underline}%{word}%{underline}. Congrats, %{bold}%{player}%{bold}!") % {:bold => Bold, :underline => Underline, :word => word, :player => m.sourcenick}\r
+        @games[k].total_tries += 1\r
+        @games[k].tries[m.source] += 1\r
+        @games[k].winner = m.source\r
+        ar = @games[k].score.inject([]) { |res, kv|\r
+          res.push("%s: %d (%s)" % kv.flatten)\r
+        }\r
+        m.reply _("The game was won after %{tries} tries. Scores for this game:    %{scores}") % {:tries => @games[k].total_tries, :scores => ar.join('; ')}\r
+        @games.delete(k)\r
+      when :out\r
+        m.reply _("%{word} is not in the range %{bold}%{range}%{bold}") % {:word => word, :bold => Bold, :range => isit.last} if m.address?\r
+      when :noexist\r
+        # bail out early if the game was won in the mean time\r
+        return if !@games[k] or @games[k].winner\r
+        m.reply _("%{word} doesn't exist or is not acceptable for the game") % {:word => word}\r
+        @games[k].total_failed += 1\r
+        @games[k].failed[m.source] += 1\r
+      when :in\r
+        # bail out early if the game was won in the mean time\r
+        return if !@games[k] or @games[k].winner\r
+        m.reply _("close, but no cigar. New range: %{bold}%{range}%{bold}") % {:bold => Bold, :range => isit.last}\r
+        @games[k].total_tries += 1\r
+        @games[k].tries[m.source] += 1\r
+      when :ignore\r
+        m.reply _("%{word} is already one of the range extrema: %{range}") % {:word => word, :range => isit.last} if m.address?\r
+      else\r
+        m.reply _("hm, something went wrong while verifying %{word}")\r
+      end\r
+    }\r
   end\r
 \r
   def manual_word_check(m, params)\r
     k = m.channel.downcase.to_s\r
     word = params[:word].downcase\r
     if not @games.key?(k)\r
-      m.reply "no A-Z game running here, can't check if #{word} is valid, can I?"\r
+      m.reply _("no A-Z game running here, can't check if %{word} is valid, can I?")\r
       return\r
     end\r
     if word !~ /^\S+$/\r
-      m.reply "I only accept single words composed by letters only, sorry"\r
+      m.reply _("I only accept single words composed by letters only, sorry")\r
       return\r
     end\r
     word_check(m, k, word)\r
@@ -194,14 +210,14 @@ class AzGamePlugin < Plugin
     return if m.channel.nil? # Shouldn't happen, but you never know\r
     k = m.channel.downcase.to_s # to_sym?\r
     if @games.key?(k)\r
-      m.reply "the word in #{Bold}#{@games[k].range}#{Bold} was:   #{Bold}#{@games[k].word}"\r
+      m.reply _("the word in %{bold}%{range}%{bold} was:   %{bold}%{word}%{bold}") % {:bold => Bold, :range => @games[k].range, :word => @games[k].word}\r
       ar = @games[k].score.inject([]) { |res, kv|\r
         res.push("%s: %d (%s)" % kv.flatten)\r
       }\r
-      m.reply "The game was cancelled after #{@games[k].total_tries} tries. Scores for this game would have been:    #{ar.join('; ')}"\r
+      m.reply _("The game was cancelled after %{tries} tries. Scores for this game would have been:    %{scores}") % {:tries => @games[k].total_tries, :scores => ar.join('; ')}\r
       @games.delete(k)\r
     else\r
-      m.reply "no A-Z game running in this channel ..."\r
+      m.reply _("no A-Z game running in this channel ...")\r
     end\r
   end\r
 \r
@@ -211,43 +227,40 @@ class AzGamePlugin < Plugin
     unless @games.key?(k)\r
       lang = (params[:lang] || @bot.config['core.language']).to_sym\r
       method = 'random_pick_'+lang.to_s\r
-      m.reply "let me think ..."\r
+      m.reply _("let me think ...")\r
       if @rules.has_key?(lang) and self.respond_to?(method)\r
         word = self.send(method)\r
         if word.empty?\r
-          m.reply "couldn't think of anything ..."\r
+          m.reply _("couldn't think of anything ...")\r
           return\r
         end\r
       else\r
-        m.reply "I can't play A-Z in #{lang}, sorry"\r
+        m.reply _("I can't play A-Z in %{lang}, sorry") % {:lang => lang}\r
         return\r
       end\r
-      m.reply "got it!"\r
+      m.reply _("got it!")\r
       @games[k] = AzGame.new(self, lang, @rules[lang], word)\r
     end\r
     tr = @games[k].total_tries\r
-    case tr\r
-    when 0\r
-      tr_msg = ""\r
-    when 1\r
-      tr_msg = " (after 1 try"\r
+    # this message building code is rewritten to make translation easier\r
+    if tr == 0\r
+      tr_msg = ''\r
     else\r
-      tr_msg = " (after #{tr} tries"\r
-    end\r
-\r
-    unless tr_msg.empty?\r
       f_tr = @games[k].total_failed\r
-      case f_tr\r
-      when 0\r
-        tr_msg << ")"\r
-      when 1\r
-        tr_msg << " and 1 invalid try)"\r
+      if f_tr > 0\r
+        tr_msg = _(" (after %{total_tries} and %{invalid_tries})") %\r
+           { :total_tries => n_("%{count} try", "%{count} tries", tr) %\r
+                             {:count => tr},\r
+             :invalid_tries => n_("%{count} invalid try", "%{count} invalid tries", tr) %\r
+                               {:count => f_tr} }\r
       else\r
-        tr_msg << " and #{f_tr} invalid tries)"\r
+        tr_msg = _(" (after %{total_tries})") %\r
+                 { :total_tries => n_("%{count} try", "%{count} tries", tr) %\r
+                             {:count => tr}}\r
       end\r
     end\r
 \r
-    m.reply "A-Z: #{Bold}#{@games[k].range}#{Bold}" + tr_msg\r
+    m.reply _("A-Z: %{bold}%{range}%{bold}") % {:bold => Bold, :range => @games[k].range} + tr_msg\r
     return\r
   end\r
 \r
@@ -258,10 +271,10 @@ class AzGamePlugin < Plugin
     cmd = params[:cmd].to_sym rescue :count\r
     case cmd\r
     when :count\r
-      m.reply "I have #{wc.size > 0 ? wc.size : 'no'} #{lang} words in my cache"\r
+      m.reply n_("I have %{count} %{lang} word in my cache", "I have %{count} %{lang} words in my cache", wc.size) % {:count => wc.size, :lang => lang}\r
     when :show, :list\r
       if pars.empty?\r
-        m.reply "provide a regexp to match"\r
+        m.reply _("provide a regexp to match")\r
         return\r
       end\r
       begin\r
@@ -273,45 +286,48 @@ class AzGamePlugin < Plugin
         matches = []\r
       end\r
       if matches.size == 0\r
-        m.reply "no #{lang} word I know match #{pars[0]}"\r
+        m.reply _("no %{lang} word I know match %{pattern}") % {:lang => lang, :pattern => pars[0]}\r
       elsif matches.size > 25\r
-        m.reply "more than 25 #{lang} words I know match #{pars[0]}, try a stricter matching"\r
+        m.reply _("more than 25 %{lang} words I know match %{pattern}, try a stricter matching") % {:lang => lang, :pattern => pars[0]}\r
       else\r
         m.reply "#{matches.join(', ')}"\r
       end\r
     when :info\r
       if pars.empty?\r
-        m.reply "provide a word"\r
+        m.reply _("provide a word")\r
         return\r
       end\r
       word = pars[0].downcase.to_sym\r
       if not wc.key?(word)\r
-        m.reply "I don't know any #{lang} word #{word}"\r
+        m.reply _("I don't know any %{lang} word %{word}") % {:lang => lang, :word => word}\r
         return\r
       end\r
-      tr = "#{word} learned from #{wc[word][:who]}"\r
-      (tr << " on #{wc[word][:when]}") if wc[word].key?(:when)\r
+      if wc[word].key?(:when)\r
+        tr = _("%{word} learned from %{user} on %{date}") % {:word => word, :user => wc[word][:who], :date => wc[word][:when]}\r
+      else\r
+        tr = _("%{word} learned from %{user}") % {:word => word, :user => wc[word][:who]} \r
+      end\r
       m.reply tr\r
-    when :delete\r
+    when :delete \r
       if pars.empty?\r
-        m.reply "provide a word"\r
+        m.reply _("provide a word")\r
         return\r
       end\r
       word = pars[0].downcase.to_sym\r
       if not wc.key?(word)\r
-        m.reply "I don't know any #{lang} word #{word}"\r
+        m.reply _("I don't know any %{lang} word %{word}") % {:lang => lang, :word => word}\r
         return\r
       end\r
       wc.delete(word)\r
       @bot.okay m.replyto\r
     when :add\r
       if pars.empty?\r
-        m.reply "provide a word"\r
+        m.reply _("provide a word")\r
         return\r
       end\r
       word = pars[0].downcase.to_sym\r
       if wc.key?(word)\r
-        m.reply "I already know the #{lang} word #{word}"\r
+        m.reply _("I already know the %{lang} word %{word}")\r
         return\r
       end\r
       wc[word] = { :who => m.sourcenick, :when => Time.now }\r
@@ -351,7 +367,7 @@ class AzGamePlugin < Plugin
     wc = @wordcache[:italian]\r
     return true if wc.key?(word.to_sym)\r
     rules = @rules[:italian]\r
-    p = @bot.httputil.get_cached(rules[:wapurl] % word)\r
+    p = @bot.httputil.get(rules[:wapurl] % word, :open_timeout => 60, :read_timeout => 60)\r
     if not p\r
       error "could not connect!"\r
       return false\r
@@ -404,11 +420,11 @@ class AzGamePlugin < Plugin
         l = ('a'..'z').to_a[rand(26)]\r
         debug "getting random word from dictionary, starting with letter #{l}"\r
         first = rules[:url] % "lettera_#{l}_0_50"\r
-        p = @bot.httputil.get_cached(first)\r
+        p = @bot.httputil.get(first)\r
         max_page = p.match(/ \/ (\d+)<\/label>/)[1].to_i\r
         pp = rand(max_page)+1\r
         debug "getting random word from dictionary, starting with letter #{l}, page #{pp}"\r
-        p = @bot.httputil.get_cached(first+"&pagina=#{pp}") if pp > 1\r
+        p = @bot.httputil.get(first+"&pagina=#{pp}") if pp > 1\r
         lemmi = Array.new\r
         good = rules[:good]\r
         bad =  rules[:bad]\r
@@ -446,7 +462,7 @@ class AzGamePlugin < Plugin
     wc = @wordcache[:english]\r
     return true if wc.key?(word.to_sym)\r
     rules = @rules[:english]\r
-    p = @bot.httputil.get_cached(rules[:url] % URI.escape(word))\r
+    p = @bot.httputil.get(rules[:url] % CGI.escape(word))\r
     if not p\r
       error "could not connect!"\r
       return false\r
@@ -497,7 +513,7 @@ class AzGamePlugin < Plugin
         ll = ('a'..'z').to_a[rand(26)]\r
         random = [l,ll].join('*') + '*'\r
         debug "getting random word from dictionary, matching #{random}"\r
-        p = @bot.httputil.get_cached(rules[:url] % URI.escape(random))\r
+        p = @bot.httputil.get(rules[:url] % CGI.escape(random))\r
         debug p\r
         lemmi = Array.new\r
         good = rules[:good]\r
@@ -527,17 +543,17 @@ class AzGamePlugin < Plugin
   def help(plugin, topic="")\r
     case topic\r
     when 'manage'\r
-      return "az [lang] word [count|list|add|delete] => manage the az wordlist for language lang (defaults to current bot language)"\r
+      return _("az [lang] word [count|list|add|delete] => manage the az wordlist for language lang (defaults to current bot language)")\r
     when 'cancel'\r
-      return "az cancel => abort current game"\r
+      return _("az cancel => abort current game")\r
     when 'check'\r
-      return 'az check <word> => checks <word> against current game'\r
+      return _('az check <word> => checks <word> against current game')\r
     when 'rules'\r
-      return "try to guess the word the bot is thinking of; if you guess wrong, the bot will use the new word to restrict the range of allowed words: eventually, the range will be so small around the correct word that you can't miss it"\r
+      return _("try to guess the word the bot is thinking of; if you guess wrong, the bot will use the new word to restrict the range of allowed words: eventually, the range will be so small around the correct word that you can't miss it")\r
     when 'play'\r
-      return "az => start a game if none is running, show the current word range otherwise; you can say 'az <language>' if you want to play in a language different from the current bot default"\r
+      return _("az => start a game if none is running, show the current word range otherwise; you can say 'az <language>' if you want to play in a language different from the current bot default")\r
     end\r
-    return "az topics: play, rules, cancel, manage, check"\r
+    return _("az topics: play, rules, cancel, manage, check")\r
   end\r
 \r
 end\r