]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blobdiff - data/rbot/plugins/games/shiritori.rb
remove whitespace
[user/henk/code/ruby/rbot.git] / data / rbot / plugins / games / shiritori.rb
index 0e53ad2e5ce12b2cf0bfc2ca78a01e85660041f4..92f4dda4c7068e0d02c0de33066b67d50edc5119 100644 (file)
@@ -1,7 +1,7 @@
 #-- vim:sw=2:et
 #kate: indent-width 2
 #++
-# 
+#
 # :title: Shiritori Plugin for RBot
 #
 # Author:: Yaohan Chen <yaohan.chen@gmail.com>
 # playing several games, each per channel. A game can be turn-based, where only new
 # players can interrupt a turn to join, or a free mode where anyone can speak at any
 # time.
-# 
+#
+# In Japanese mode, if present, the plugin can use normalize-japanese
+# <http://neruchan.mine.nu:60880/normalize-japanese.rb> to allow
+# katakana words be used like hiragana.
+#
 # TODO
 # * a system to describe settings, so they can be displayed, changed and saved
 # * adjust settings during game
@@ -31,7 +35,7 @@ class Dictionary
   def has_word?(s)
     raise NotImplementedError
   end
-  
+
   # whether any word starts with prefix, excluding words in excludes. This can be
   # possible with non-enumerable dictionaries since some dictionary engines provide
   # prefix searching.
@@ -45,14 +49,14 @@ class WordlistDictionary < Dictionary
   def initialize(words)
     super()
     @words = words
-    debug "Created dictionary with #{@words.length} words"
+    debug "Created dictionary with #{@words.length} words"
   end
-  
+
     # whether string s is a word
   def has_word?(s)
     @words.include? s
   end
-  
+
   # whether any word starts with prefix, excluding words in excludes
   def any_word_starting?(prefix, excludes)
     # (@words - except).any? {|w| w =~ /\A#{prefix}.+/}
@@ -65,7 +69,7 @@ end
 # whether it's possible to continue a word
 class Shiritori
   attr_reader :used_words
-  
+
   # dictionary:: a Dictionary object
   # overlap_lengths:: a Range for allowed lengths to overlap when continuing words
   # check_continuable:: whether all words are checked whether they're continuable,
@@ -78,7 +82,7 @@ class Shiritori
     @allow_reuse = allow_reuse
     @used_words = []
   end
-  
+
   # Prefix of s with length n
   def head_of(s, n)
     # TODO ruby2 unicode
@@ -98,7 +102,7 @@ class Shiritori
   def range_under(r, n)
     r.begin .. [r.end, n-1].min
   end
-  
+
   # TODO allow the ruleset to customize this
   def continues?(w2, w1)
     # this uses the definition w1[-n,n] == w2[0,n] && n < [w1.length, w2.length].min
@@ -106,7 +110,7 @@ class Shiritori
     range_under(@overlap_lengths, [len(w1), len(w2)].min).any? {|n|
       tail_of(w1, n)== head_of(w2, n)}
   end
-  
+
   # Checks whether *any* unused word in the dictionary completes the word
   # This has the limitation that it can't detect when a word is continuable, but the
   # only continuers aren't continuable
@@ -114,13 +118,13 @@ class Shiritori
     range_under(@overlap_lengths, len(s)).any? {|n|
       @dictionary.any_word_starting?(tail_of(s, n), @used_words) }
   end
-  
-  # Given a string, give a verdict based on current shiritori state and dictionary 
+
+  # Given a string, give a verdict based on current shiritori state and dictionary
   def process(s)
     # TODO optionally allow used words
     # TODO ruby2 unicode
     if len(s) < @overlap_lengths.min || !@dictionary.has_word?(s)
-      debug "#{s} is too short or not in dictionary"
+      debug "#{s} is too short or not in dictionary"
       :ignore
     elsif @used_words.empty?
       if !@check_continuable || continuable_from?(s)
@@ -163,23 +167,27 @@ class ShiritoriGame
     @timer_handle = nil
     @say = say
     @when_die = when_die
-    
+
     # TODO allow other forms of dictionaries
     dictionary = WordlistDictionary.new(@ruleset[:words])
     @game = Shiritori.new(dictionary, @ruleset[:overlap_lengths],
                                       @ruleset[:check_continuable],
                                       @ruleset[:allow_reuse])
   end
-  
+
+  def say(s)
+     @say.call(s)
+  end
+
   # Whether the players must take turns
   # * when there is only one player, turns are not enforced
   # * when time_limit > 0, new players can join at any time, but existing players must
   #   take turns, each of which expires after time_limit
   # * when time_imit is 0, anyone can speak in the game at any time
-  def take_turns? 
+  def take_turns?
     @players.length > 1 && @ruleset[:time_limit] > 0
   end
-  
+
   # the player who has the current turn
   def current_player
     @players.first
@@ -192,16 +200,20 @@ class ShiritoriGame
   def previous_word
     @game.used_words[-2]
   end
-  
+
   # announce the current word, and player if take_turns?
   def announce
-    if take_turns?
-      @say.call "#{current_player}, it's your turn. #{previous_word} -> #{current_word}"
+    say(if take_turns?
+      _("%{current_player}, it's your turn. %{previous_word} -> %{current_word}") %
+       { :current_player => current_player, :previous_word => previous_word,
+         :current_word => current_word }
     elsif @players.empty?
-      @say.call "No one has given the first word yet. Say the first word to start."
+      _("No one has given the first word yet. Say the first word to start.")
     else
-      @say.call "Poor #{current_player} is playing alone! Anyone care to join? #{previous_word} -> #{current_word}"
-    end
+      _("Poor %{current_player} is playing alone! Anyone care to join? %{previous_word} -> %{current_word}") %
+      { :current_player => current_player, :previous_word => previous_word,
+        :current_word => current_word }
+    end)
   end
   # create/reschedule timer
   def restart_timer
@@ -225,22 +237,25 @@ class ShiritoriGame
     end
     announce
   end
-  
+
   # handle when turn time limit goes out
   def time_out
     if @ruleset[:lose_when_timeout]
-      @say.call "#{current_player} took too long and is out of the game. Try again next game!"
-      if @players.length == 2 
+      say _("%{player} took too long and is out of the game. Try again next game!") %
+      { :player => current_player }
+      if @players.length == 2
         # 2 players before, and one should remain now
         # since the game is ending, save the trouble of removing and booting the player
-        @say.call "#{@players[1]} is the last remaining player and the winner! Congratulations!"
+        say _("%{player} is the last remaining player and the winner! Congratulations!") %
+          {:player => @players.last}
         die
       else
         @booted_players << @players.shift
         announce
       end
     else
-      @say.call "#{current_player} took too long and skipped the turn."
+      say _("%{player} took too long and skipped the turn.") %
+          {:player => current_player}
       next_player
     end
   end
@@ -254,12 +269,12 @@ class ShiritoriGame
   def handle_message(m)
     message = m.message
     speaker = m.sourcenick.to_s
-    
+
     return unless @ruleset[:listen] =~ message
 
     # in take_turns mode, only new players are allowed to interrupt a turn
     return if @booted_players.include? speaker ||
-              (take_turns? && 
+              (take_turns? &&
                speaker != current_player &&
                (@players.length > 1 && @players.include?(speaker)))
 
@@ -267,31 +282,35 @@ class ShiritoriGame
     case @game.process @ruleset[:normalize].call(message)
     when :start
       @players << speaker
-      m.reply "#{speaker} has given the first word: #{current_word}"
+      m.reply _("%{player} has given the first word: %{word}") %
+              {:player => speaker, :word => current_word}
     when :next
       if !@players.include?(speaker)
         # A new player
         @players.unshift speaker
-        m.reply "Welcome to shiritori, #{speaker}."
+        m.reply _("Welcome to shiritori, %{player}.") %
+                {:player => speaker}
       end
       next_player
     when :used
-      m.reply "The word #{message} has been used. Retry from #{current_word}"
+      m.reply _("The word %{used_word} has been used. Retry from %{word}") %
+              {:used_word => message, :word => current_word}
     when :end
       # TODO respect shiritori.end_when_uncontinuable setting
       if @ruleset[:end_when_uncontinuable]
-        m.reply "It's impossible to continue the chain from #{message}. The game has ended. Thanks a lot, #{speaker}! :("
+        m.reply _("It's impossible to continue the chain from %{word}. The game has ended. Thanks a lot, %{player}! :(") %
+                {:word => message, :player => speaker}
         die
       else
-        m.reply "It's impossible to continue the chain from #{message}. Retry from #{current_word}"
+        m.reply _("It's impossible to continue the chain from %{bad_word}. Retry from %{word}") % {:bad_word => message, :word => current_word}
       end
     when :start_end
       # when the first word is uncontinuable, the game doesn't stop, as presumably
       # someone wanted to play
-      m.reply "It's impossible to continue the chain from #{message}. Start with another word."
+      m.reply _("It's impossible to continue the chain from %{word}. Start with another word.") % {:word => message}
     end
   end
-  
+
   # end the game
   def die
     # redefine restart_timer to no-op
@@ -309,13 +328,14 @@ end
 # shiritori plugin for rbot
 class ShiritoriPlugin < Plugin
   def help(plugin, topic="")
-    "A game in which each player must continue the previous player's word, by using its last one or few characters/letters of the word to start a new word. 'shiritori <ruleset>' => Play shiritori with a set of rules. Available rulesets: #{@rulesets.keys.join ', '}. 'shiritori stop' => Stop the current shiritori game."
+    _("A game in which each player must continue the previous player's word, by using its last one or few characters/letters of the word to start a new word. 'shiritori <ruleset>' => Play shiritori with a set of rules. Available rulesets: %{rulesets}. 'shiritori stop' => Stop the current shiritori game.") %
+      {:rulesets => @rulesets.keys.join(', ')}
   end
-  
+
   def initialize()
     super
     @games = {}
-    
+
     # TODO make rulesets more easily customizable
     # TODO initialize default ruleset from config
     # Default values of rulesets
@@ -351,7 +371,13 @@ class ShiritoriPlugin < Plugin
         :listen => /\A\S+\Z/u,
         :overlap_lengths => 1..4,
         :desc => 'Use Japanese words in hiragana; 1-4 kana at the beginning of the next word must overlap with those at the end of the previous word.',
-        # Optionally use a module to normalize Japanese words, enabling input in multiple writing systems
+        :normalize =>
+          begin
+            require 'normalize-japanese'
+            lambda {|w| w.to_hiragana}
+          rescue LoadError
+            lambda {|w| w}
+          end
       }
     }
   end
@@ -364,7 +390,7 @@ class ShiritoriPlugin < Plugin
       if ruleset.has_key?(:wordlist_file)
         begin
           ruleset[:words] =
-            File.new("#{@bot.botclass}/shiritori/#{ruleset[:wordlist_file]}").grep(
+            File.new(datafile ruleset[:wordlist_file]).grep(
               ruleset[:listen]) {|l| ruleset[:normalize].call l.chomp}
         rescue
           raise "unable to load word list"
@@ -375,11 +401,11 @@ class ShiritoriPlugin < Plugin
     end
     return ruleset
   end
-  
+
   # start shiritori in a channel
   def cmd_shiritori(m, params)
     if @games.has_key?( m.channel )
-      m.reply "Already playing shiritori here"
+      m.reply _("Already playing shiritori here")
       @games[m.channel].announce
     else
       ruleset = params[:ruleset].downcase
@@ -390,16 +416,17 @@ class ShiritoriPlugin < Plugin
             @bot.timer,
             lambda {|msg| m.reply msg},
             lambda {remove_game m.channel} )
-          m.reply "Shiritori has started. Please say the first word"
+          m.reply _("Shiritori has started. Please say the first word")
         rescue => e
-          m.reply "couldn't start #{ruleset} shiritori: #{e}"
+          m.reply _("couldn't start %{ruleset} shiritori: %{error}") %
+                  {:ruleset => ruleset, :error => e}
         end
       else
-        m.reply "There is no defined ruleset named #{ruleset}"
+        m.reply _("There is no ruleset named %{ruleset}") % {:ruleset => ruleset}
       end
     end
   end
-  
+
   # change rules for current game
   def cmd_set(m, params)
     require 'enumerator'
@@ -407,36 +434,36 @@ class ShiritoriPlugin < Plugin
     params[:rules].each_slice(2) {|opt, value| new_rules[opt] = value}
     raise NotImplementedError
   end
-  
+
   # stop the current game
   def cmd_stop(m, params)
     if @games.has_key? m.channel
       # TODO display statistics
       @games[m.channel].die
-      m.reply "Shiritori has stopped. Hope you had fun!"
+      m.reply _("Shiritori has stopped. Hope you had fun!")
     else
       # TODO display statistics
-      m.reply "No game to stop here, because no game is being played."
+      m.reply _("No game to stop here, because no game is being played.")
     end
   end
-  
+
   # remove the game, so channel messages are no longer processed, and timer removed
   def remove_game(channel)
     @games.delete channel
   end
-  
+
   # all messages from a channel is sent to its shiritori game if any
-  def listen(m)
-    return unless m.kind_of?(PrivMessage)
+  def message(m)
     return unless @games.has_key?(m.channel)
     # send the message to the game in the channel to handle it
     @games[m.channel].handle_message m
   end
-  
+
   # remove all games
   def cleanup
     @games.each_key {|g| g.die}
     @games.clear
+    super
   end
 end