[Members] voting update #1

Nolan Eakins sneakin at semanticgap.com
Sat Sep 9 01:59:19 CDT 2006


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Peter Saint-Andre wrote:
> 3. Develop a general-purpose voting engine that really works, perhaps in
> Python using Twisted Words. :-) As I've said many times, the memberbot
> code is spaghetti code written by a glorified tech writer. It would be
> good to replace it with generic voting code written by a real developer.

I have some Ruby code that I made while messing with continuations. It
eliminates the spaghetti at the cost of losing the ability to restart
the bot and place everyone back at their same spot. In essence the code
that gets written is akin to what every programmer has written during
their larval years.

I went ahead and attached it if anyone is enterprising enough. It
contains a few different ways to do the same thing: a couple of state
machines and the continuation implementation.

- - Nolan

- --
SemanticGap: To act as one (TM) -- http://www.semanticgap.com/
Instant awareness & messaging * Online presence design
Cross platform and agile development
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFFAmZFhuPszQVSPEARAlU3AKCiWdTxQlsNEE4YxqYFiYVZ5rrn3wCgi80o
YGZhGyGIfbvbWDH9yZWX6RM=
=LjC7
-----END PGP SIGNATURE-----
-------------- next part --------------
### This program implements a simple number summer. It
### asks an inquiring entity it's name, how many numbers
### it would like summed, the numbers to be summed, and
### then sends the sum. Then everything starts again with
### the question of how many numbers.
###

require "getoptlong"

require 'rubygems'
require_gem 'xmpp4r'
#require 'xmpp4r'
include Jabber

#Jabber::DEBUG = true

### This is the state machine implementation.
class MethConvo
  attr :session
  attr :jid
  attr :msg
  attr :name
  attr :num_nums
  attr :numbers
  attr :state

  def initialize(session, jid)
    @session, @jid = session, jid
    restart
  end

  def restart
    @numbers = Array.new
    @state = method(:start)
  end

  def sendMsg(body)
    m = Message::new(@jid, body)
    m.type = :chat
    @session.send(m)
  end

  def prompt(p, next_state)
    sendMsg(p)
    @state = next_state
  end

  def start(msg)
    prompt("What's your name\?", method(:greet))
  end

  def greet(msg)
    @name = msg.body
    sendMsg("Nice to meet you #{@name}\!") if @state == method(:greet)
    askNumNums(msg)
  end

  def askNumNums(msg, zero=false)
    sendMsg("Come on, we can't sum up zero numbers\!") if zero
    prompt("How many numbers do you want to sum\?", method(:askFirstNum))
    @sum = @count = 0
    @numbers = Array.new
  end

  def convertNum(text)
    n = text.to_i
    if n == 0
      body = text.lstrip
      if body[0] != ?0
        return nil
      end
    end

    return n
  end

  def askFirstNum(msg)
    @num_nums = convertNum(msg.body)
    if @num_nums == nil
      prompt("Please try entering a number.", method(:askNumNums))
    elsif @num_nums == 0
      askNumNums(nil, true)
    else
      prompt("What's the first number\?", method(:askNum))
    end
  end

  def askNum(msg)
    n = convertNum(msg.body)
    if n == nil
      prompt("Please try entering a number.", method(:askNum))
    else
      @numbers.push(msg.body.to_i)
      if @count < (@num_nums - 1)
        @count += 1
        prompt("There's #{@num_nums - @count} left to enter. What's the next number\?", method(:askNum))
      else
        sum = 0
        @numbers.each do |i|
          sum += i
        end
        sendMsg("Whew! Your total is #{sum}.")
        askNumNums(msg)
      end
    end
  end

  def handle(msg)
    @state.call msg
  end
end

### This is another state machine implementation.
class SMConvo
  attr :session
  attr :jid
  attr :msg
  attr :name
  attr :num_nums
  attr :numbers
  attr :state

  def initialize(session, jid)
    @session, @jid = session, jid
    @numbers = Array.new
    restart
  end

  def restart
    @state = :start
  end

  def sendMsg(body)
    m = Message::new(@jid, body)
    m.type = :chat
    @session.send(m)
  end

  def prompt(p, next_state)
    sendMsg(p)
    @state = next_state
  end

  def askNumNums()
      prompt("How many numbers do you want to sum\?", :askFirstNum)
      @sum = @count = 0
  end

  def convertNum(text)
    n = text.to_i
    if n == 0
      body = text.lstrip
      if body[0] != ?0
        return nil
      end
    end

    return n
  end

  def handle(msg)
    case @state
      when :start
      prompt("What's your name\?", :greet)

      when (:greet or :askNumNums)
      @name = msg.body
      sendMsg("Nice to meet you #{@name}\!") if @state == :greet
      askNumNums

      when :askFirstNum
      @num_nums = convertNum(msg.body)
      if @num_nums == nil
        prompt("Please try entering a number.", :askFirstNum)
        askNumNums
      elsif @num_nums == 0
        prompt("Come on, we can't sum up zero numbers\!", :askFirstNum)
        askNumNums
      else
        prompt("What's the first number\?", :askNum)
      end

      when :askNum
      n = convertNum(msg.body)
      if n == nil
        prompt("Please try entering a number.", :askNum)
      else
        @numbers.push(n)
        if @count < (@num_nums - 1)
          @count += 1
          prompt("There's #{@num_nums - @count} left to enter. What's the next number\?", :askNum)
        else
          sum = 0
          @numbers.each do |i|
            sum += i
          end
          sendMsg("Whew! Your total is #{sum}.")
          askNumNums
        end
      end
    end
  end
end

class Conversation
  attr :session
  attr :jid
  attr :msg
  attr :cont
  
  def initialize(session, jid)
    @session, @jid = session, jid
    restart
  end

  # Allows a question to be asked in the conversation.
  def prompt(msg)
    # Store our position in this method for the next time
    # a new message comes our way.
    callcc do |cc|
      @cont = cc

      # Send the question
      sendMsg msg

      # Call our starting position in handle() so we
      # can process other incoming messages.
      @start_cc.call
    end

    # Got a new message, and this is where we pick back up.
    return @msg.body
  end

  def getNumberInternal(msg)
    # send the prompt
    n = prompt(msg)
    # ensure the user entered a valid number
    if n.to_i == 0
      body = @msg.body.lstrip
      if body[0] != ?0
        return nil
      end
    end

    return n.to_i
  end

  def getNumber(msg)
    p = msg
    while (n = getNumberInternal(p)) == nil
      p = "Please try entering a number again."
    end

    return n
  end

  def sendMsg(body)
    m = Message::new(@jid, body)
    m.type = :chat
    @session.send(m)
  end

  # Resets everything so it's like the very first handled
  # message.
  def restart()
    @cont = method(:theHandler)
  end

  # Overide this method to add your own conversation
  # logic.
  def theHandler()
    # nothing
  end

  # This gets called by the message handler when
  # a new message comes in. It return false if this
  # message is not meant for this conversation and
  # true if it is.
  def handle(msg)
    # store a copy of the msg
    @msg = msg
     
    # Pick theHandler back up where we left off,
    # but we store our position so the handler
    # can receive a response.
    callcc do |cc|
      @start_cc = cc
      @cont.call
    end

    # We handled this message.
    return true
  end
end

### This is the actual continuation summer.
class SumContConvo < Conversation
  attr :name
  attr :num_nums
  attr :numbers

  def initialize(session, jid)
    super session, jid

    @numbers = Array.new
  end

  # This the actual logic of the conversation.
  def theHandler()
    @name = prompt('What\'s your name?')
    sendMsg("Nice to meet you #{@name}!")

    while true
      @num_nums = getNumber("How many numbers do you want to sum?")
      #sendMsg("Number = #{@num_nums}")

      if @num_nums > 0
        @numbers = Array.new
        @numbers.push(getNumber("What's the first number\?"))
        (@numbers.length... at num_nums).each do |i|
          @numbers.push(getNumber("There's #{@num_nums - i} left to enter. What's the next number\?"))
        end

        sum = 0
        @numbers.each do |i|
          sum += i
        end
        sendMsg("Whew! Your total is #{sum}.")
      else
        sendMsg("Come on, we can't sum up zero numbers!")
      end
    end

    # if we weren't looping we would want to call
    # restart() so strange things don't happen.
  end
end

class Summer
  attr :admin
  attr :convos
  attr :has_shutdown
  attr :session

  def initialize(admin)
    @admin = admin
    @convos = Hash.new
    @has_shutdown = false
  end

  def login(jid, password)
    @session = Client::new(JID.new(jid)) # , false) # uncomment for non-threaded
    @session.connect
    @session.auth(password)
    @session.send(Presence::new)

    @thread = Thread.current

    # Create our message listener
    mlid = @session.add_message_callback do |message|
      from = message.from.strip.to_s
      if (message.body=="shutdown" and from =~ /^#{@admin}/)
        shutdown
      else
        print "Convo = #{@convos[from]}\n"
        if not @convos[from]
          print "New convo for #{from}"
          @convos[from] = SumContConvo.new(session, from) #SumContConvo
        end

        @convos[from].handle(message)
      end
    end

    #pres_hand = @session.add_roster_listener do |event, item|
    #	puts "#{item.item.jid} is #{item.status}: #{event}"
    # end
  end

  def shutdown()
    @has_shutdown = true
    @convos.each do |convo|
      # say good bye
      m = Message.new(convo[0], "Goodbye #{convo[1].name}")
      m.type = :chat
      @session.send(m)
      
      # save each convo
      puts "Convo: #{convo[0]}\t #{convo[1].name}"
      puts "\tNumber of numbers: #{convo[1].num_nums}"
      puts "\tNumbers: #{convo[1].numbers.join(', ')}"
    end

    @thread.wakeup
  end

  def process
    @session.process
  end

  def close
    @session.close
  end
end

def prompt(p)
  print p
  ret = gets()
  ret[0, ret.length - 1]
end

def assignIfNotNull(value, default)
  if value
    value
  else
    default
  end
end

# Taken from a Jabber4R example.
begin
  admin = "sneakin at semanticgap.com"
  jid = admin + "/Ruby"
  pass = nil

  print "#{ARGV[1]}\n"

  pass = ARGV[0] if ARGV[0]
  if ARGV[1]
    j = JID.new(ARGV[1])
    admin = j.strip.to_s
    jid = ARGV[1]
  end
  admin = ARGV[2] if ARGV[2]

  summer = Summer.new(admin)
  summer.login(jid, pass)

  #while not summer.has_shutdown
    #summer.process
    #sleep 1
  #end

  Thread.stop
  summer.close
  print "Done...\n"
rescue Exception=>error
  puts error
ensure
  summer
end
-------------- next part --------------
A non-text attachment was scrubbed...
Name: sneakin.vcf
Type: text/x-vcard
Size: 207 bytes
Desc: not available
Url : http://mail.jabber.org/pipermail/members/attachments/20060909/25f5e998/sneakin-0001.vcf


More information about the Members mailing list