[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