[QUIZ] LSRC Name Picker (#129)

M

Morton Goldberg

* Each time your application is run, it should select and display
a single name from the list. Your application should remember
previously selected names and not choose them a second time.

There are two ways to interpret 'run':

1. The app has a Pick button and a new winner is selected each time
it is clicked.

2. The app automatically runs each time it is started up and it picks
one winner. To pick another, the app is relaunched.

Which behavior is wanted? I ask because remembering previous winners
between launches makes the quiz somewhat more difficult. Extra points
for the 1st behavior combined with the winner list persistence of the
2nd?

Regards, Morton
 
J

James Edward Gray II

There are two ways to interpret 'run':

1. The app has a Pick button and a new winner is selected each time
it is clicked.

2. The app automatically runs each time it is started up and it
picks one winner. To pick another, the app is relaunched.

Which behavior is wanted?

I meant each time your application is started (#2). I can see where
#1 might be attractive for something like a web application, but I
have a hard time imagining that the program will run non-stop for two
days.

James Edward Gray II
 
R

Robert Dober

1. The app has a Pick button and a new winner is selected each time
it is clicked.

2. The app automatically runs each time it is started up and it picks
one winner. To pick another, the app is relaunched.

Which behavior is wanted? I ask because remembering previous winners
between launches makes the quiz somewhat more difficult. Extra points
for the 1st behavior combined with the winner list persistence of the
2nd?

Regards, Morton
Honestly I am rude Morton, because I am jealous ;). I have read your
recent quiz submissions, they are lightyears above my level, what are
you asking here?
Just solve the problem ;)

Seriously now:
I read the Quiz in a way that winner persistence is explicitly asked
for and I really like this part.

Cheers
Robert
 
M

M. Edward (Ed) Borasky

James said:
I meant each time your application is started (#2). I can see where #1
might be attractive for something like a web application, but I have a
hard time imagining that the program will run non-stop for two days.

James Edward Gray II

Yeah ... and typically from the two Ruby conferences I've been to, it
may not even run on the same laptop twice in a row. That's one more
organizational detail. :)
 
M

Morton Goldberg

Seriously now:
I read the Quiz in a way that winner persistence is explicitly asked
for and I really like this part.

I just thought that they might award all the prizes in one session
with multiple drawings, not one at a time with fairly long intervals
between drawings. But, as James has replied, your interpretation is
the correct one.

My extra points idea was to allow an arbitrary number of drawings to
be made at one time then sometime later allow more to be mode.

Regards, Morton
 
G

Gregory Seidman

Sorry the quiz was late this week.
[...]
It's my fault this week's quiz was two days late, so it will run for
an extra week to give people the time they need.

Sorry for the inconveniences.

So, er, when does the spoiler moratorium period end this time? Next Sunday?
James Edward Gray II
--Greg
 
J

James Edward Gray II

Sorry the quiz was late this week.
[...]
It's my fault this week's quiz was two days late, so it will run for
an extra week to give people the time they need.

Sorry for the inconveniences.

So, er, when does the spoiler moratorium period end this time? Next
Sunday?

48 hours from the time on the quiz message, just like always.

James Edward Gray II
 
R

Robert Dober

My extra points idea was to allow an arbitrary number of drawings to
be made at one time then sometime later allow more to be mode.
Psst, already implemented -- well with certain Plugins :)

Robert
 
C

Carl Porth

I assume the 48 hour deadline is over. I made a fun little camping
app, however its 300k zipped up (has a couple images). I assume it
would be poor form to post it to the list, but I currently don't have
a publicly facing server where I can put it. What shall I do?

Carl Porth
 
J

Jesse Merriman

--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: Multipart/Mixed;
boundary="Boundary-00=_YHPgGLx5wXMeysx"

--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

My solution uses SQLite to store a DB of names, and RMagick to display chosen
names. Consider the display part a first draft - I think I'll redo it, maybe
in a completely different way - and submit again later, though the backend
shouldn't change.

There are 4 files that roughly follow MVC:

names_db.rb: Wrapper around a SQLite::Database (M)
pick.rb: Main script (C)
pickerface.rb: Interface stuff (V)
name_image.rb: RMagick stuff (V)

The DB stores just one thing: names. When a name is chosen, its simply deleted.
I thought of marking the names as either picked or unpicked, or storing chosen
names in another table, but I'm not sure if that would be used, and in any
event it would be easy enough to add if needed.

When pick.rb is run without a command option, it either creates a DB and starts
adding names if the DB doesn't exist, or picks and displays a name if it does.
See the top of pick.rb, or run pick.rb --help for a full usage message.

For all text-mode-only operations, names can be Unicode, but RMagick doesn't
seem to like that, so they can't be displayed nicely. Magick::ImageList.animate
is used for displaying, so it won't work for any name on Windows.

I bracket chosen names in stars taken from:

http://lonestarrubyconf.com/stylesheets/lsrc_2007/images/LSRC_big_title.jpg

I'm not sure this list will accept messages containing graphics, so you'll
have to fetch this image and place it in the same directory as my code:

http://www.jessemerriman.com/images/ruby_quiz/129_lsrc_name_picker/star_small.png


--
Jesse Merriman
(e-mail address removed)
http://www.jessemerriman.com/

--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: application/x-ruby;
name="names_db.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="names_db.rb"

require 'sqlite'

# NamesDB wraps up a SQLite::Database.
class NamesDB
class AlreadyAdded < StandardError; end
class NonexistantDB < StandardError; end

NamesTable = 'names'

# Initialize a new NamesDB.
# - filename: The filename of the DB.
# - create: If true, and the DB doesn't exist, it'll be created. If false,
# and the DB doesn't exist, raise a NonexistantDB exception.
# - optimize: Enable optimizations.
def initialize filename, create = false, optimize = true
raise NonexistantDB if not create and not File.exists? filename

@filename = filename
@db = SQLite::Database.new filename
create_names_table if @db.table_info(NamesTable).empty?
enable_optimizations if optimize
prepare_statements
self
end

# Add a name to the DB.
def add_name name
begin
@statements[:add_name].execute! NamesTable, name
rescue SQLite::Exceptions::SQLException
raise AlreadyAdded.new, "#{name} is already in the DB!"
end
end

# Delete a name from the DB.
def delete_name name
@statements[:delete_name].execute! NamesTable, name
end

# Yield each name in the DB.
def each_name
@statements[:each_name].execute!(NamesTable) { |res| yield res[0] }
end

# Clear the DB of all names.
def clear
@statements[:clear].execute! NamesTable
end

# Destroy the DB.
def destroy
@db.close
File.delete @filename
end

# Return a randomly-chosen name, or nil if there are non left.
def pick
res = @statements[:pick].execute! NamesTable
res.empty? ? nil : res[0][0]
end

private

def create_names_table
@db.execute <<-SQL, NamesTable
CREATE TABLE ? (
name TEXT PRIMARY KEY
);
SQL
end

def enable_optimizations
@db.execute 'PRAGMA default_synchronous = OFF;'
end

def prepare_statements
@statements = {}

@statements[:add_name] = @db.prepare 'INSERT INTO ? (name) VALUES (?);'
@statements[:delete_name] = @db.prepare 'DELETE FROM ? WHERE name = ?;'
@statements[:each_name] = @db.prepare 'SELECT name FROM ?;'
@statements[:clear] = @db.prepare 'DELETE FROM ?; VACUUM;'
@statements[:pick] = @db.prepare <<-SQL
SELECT name FROM ? ORDER BY random() LIMIT 1;
SQL
end
end

--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: application/x-ruby;
name="pickerface.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="pickerface.rb"

require 'names_db'
require 'name_image'

# Pickerface handles the interface for pick.rb.
class Pickerface
def initialize filename
@db_file = filename
self
end

# Read names from standard input and add the to the DB.
def add_names
puts 'Enter names to add, one per line. Blank or ^D to stop.'
db = NamesDB.new @db_file, true
while name = $stdin.gets and name != "\n"
begin
db.add_name name.chomp
rescue NamesDB::AlreadyAdded => ex
$stderr.puts ex
end
end
end

# List all names to standard output.
def list_names
existing_db do |db|
puts str = "Contents of #{@db_file}:"
puts '-' * str.length
db.each_name { |name| puts name }
end
end

# Clear the DB of all names.
def clear
existing_db do |db|
db.clear
puts "#{@db_file} has been cleared of all names."
end
end

# Randomly choose a name, write it to standard output, and delete it from the
# DB.
def pick_simple
pick { |name| puts name }
end

# Randomly choose a name, display it in a fancy way, and delete it from the
# DB.
def pick_fancy
pick { |name| NameImage.fancy(name).animate }
end

# Like pick_fancy, but save the image to the given filename instead of
# displaying it.
def save_fancy filename
pick { |name| NameImage.fancy(name).write(filename) }
end

# Destroy the DB.
def destroy
existing_db do |db|
db.destroy
puts "#{@db_file} has been destroyed."
end
end

private

# Yield a NamesDB if @db_file exists, otherwise print an error.
def existing_db
begin
yield NamesDB.new(@db_file)
rescue NamesDB::NonexistantDB
$stderr.puts "Error: #{@db_file} does not exist!"
end
end

# Yield a randomly-picked name, or a message that there are none left.
def pick
existing_db do |db|
if name = db.pick
db.delete_name name
yield name
else
yield 'No one left!'
end
end
end
end

--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: application/x-ruby;
name="pick.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="pick.rb"

#!/usr/bin/env ruby
# = Ruby Quiz 129: LSRC Name Picker
# Author: Jesse Merriman
#
# == Usage
#
# pick.rb [OPTION] [DB-FILE]
#
# -h, --help:
# Print out usage message.
#
# -a, --add-names:
# Take a list of names on standard input and add them to the DB. Creates the
# DB if it does not exist.
#
# -l, --list-names:
# List out all names in the DB to standard output, if it exists.
#
# -c, --clear-names:
# Clear the DB of all names, if it exists.
#
# -p, --pick-simple
# Randomly choose an unchosen name from the DB, if it exists.
#
# -f, --pick-fancy
# Like --pick, but display the chosen name in a fancy way.
#
# -s, --save-fancy FILENAME
# Like --pick-fancy, but save the graphic to FILENAME instead of displaying.
# Very SLOW.
#
# -d, --destroy-db:
# Destroy the DB, if it exists.
#
# If no option is given, the default is --add-names if no DB exists, or
# --pick-fancy if it does.
#
# DB-FILE defaults to 'names.db'.

require 'pickerface'
require 'getoptlong'
require 'rdoc/usage'

if __FILE__ == $0
Opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
[ '--add-names', '-a', GetoptLong::NO_ARGUMENT ],
[ '--list-names', '-l', GetoptLong::NO_ARGUMENT ],
[ '--clear-names', '-c', GetoptLong::NO_ARGUMENT ],
[ '--pick-simple', '-p', GetoptLong::NO_ARGUMENT ],
[ '--pick-fancy', '-f', GetoptLong::NO_ARGUMENT ],
[ '--save-fancy', '-s', GetoptLong::REQUIRED_ARGUMENT ],
[ '--destroy-db', '-d', GetoptLong::NO_ARGUMENT ] )

# Handle arguments.
op, args = nil, []
Opts.each do |opt, arg|
case opt
when '--help'; RDoc::usage
when '--add-names'; op = :add_names
when '--list-names'; op = :list_names
when '--clear-names'; op = :clear
when '--pick-simple'; op = :pick_simple
when '--pick-fancy'; op = :pick_fancy
when '--save-fancy'; op = :save_fancy; args << arg
when '--destroy-db'; op = :destroy
end
end

# Setup default arguments.
ARGV.empty? ? db_file = 'names.db' : db_file = ARGV.first
if op.nil?
File.exists?(db_file) ? op = :pick_fancy : op = :add_names
end

# Run.
Pickerface.new(db_file).send(op, *args)
end

--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: application/x-ruby;
name="name_image.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="name_image.rb"

require 'RMagick'

# A few methods for creating images of names with RMagick.
module NameImage
StarFile = './star_small.png'
Colors = { :bg => 'black', :border_in => 'darkblue', :border_out => 'black',
:text_stroke => 'darkblue', :text_fill => 'gold' }

# Create a fancy animated ImageList.
def NameImage.fancy name
img = add_ripple(plain(name))
img.border! 4, 4, Colors[:border_in]
img.border! 4, 4, Colors[:border_out]
img = add_stars img
shake img
end

# Create a rather plain Image.
def NameImage.plain name
img = Magick::Image.new(1, 1) { self.background_color = Colors[:bg] }

gc = Magick::Draw.new
metrics = gc.get_multiline_type_metrics img, name
img.resize! metrics.width * 4, metrics.height * 4

gc.annotate(img, 0, 0, 0, 0, name) do |gc|
gc.font_family = 'Verdana'
gc.font_weight = Magick::LighterWeight
gc.pointsize = 40
gc.gravity = Magick::SouthEastGravity
gc.stroke = Colors[:text_stroke]
gc.fill = Colors[:text_fill]
end

img
end

# Create a new image consisting of img and a rippling reflection under it.
def NameImage.add_ripple img
img_list = Magick::ImageList.new
xform = Magick::AffineMatrix.new 1.0, 0.0, Math::pI/4.0, 1.0, 0.0, 0.0
img_list << img
img_list << img.wet_floor(0.5, 0.7).affine_transform(xform).
rotate(90).wave(2, 10).rotate(-90)
img_list.append true
end

# Create a new image consisting of img with stars on either side.
def NameImage.add_stars img
begin
star = Magick::ImageList.new(StarFile).first
img_list = Magick::ImageList.new
img_list << star.copy
img_list << img
img_list << star
img_list.append false
rescue Magick::ImageMagickError
$stderr.puts "Couldn't open #{StarFile}. Did you download it?"
img
end
end

# Create an animated ImageList of img shaking.
def NameImage.shake img
animation = Magick::ImageList.new

20.times { animation << img.copy }
0.3.step(0.6, 0.3) do |deg|
animation << img.rotate(deg)
animation << img.rotate(-deg)
end
animation << img.radial_blur(6)
animation.delay = 5
animation.iterations = 10000

animation
end
end

--Boundary-00=_YHPgGLx5wXMeysx--
--Boundary-00=_YHPgGLx5wXMeysx--
 
B

Bob Showalter

The LSRC organizers would like us to build name picking applications they can
choose from. The functionality is very basic:

My design goals were:

* What can I do in an hour?
* How can I avoid graphics design, which I suck at?
* How can I minimize (to zero, if possible) gems, add-ons, etc?

My solution is a dead simple WEBrick servlet thingy that poaches the
stylesheet and images from the conference website. It uses a YAML
input file of names and attributes, and an ERB template to display the
result. Every time you reload the page you get a new name.

It keeps track of the names that have been served (saved to a file, so
it survives a restart of the app). I wasn't sure what to do when it
ran out of names, so I'm currently just clearing the save file and
restarting the serving of names (if there are more door prizes than
attendees?).

Here's the app: http://pastie.caboo.se/73618 (save as name_picker.rb)

Here's a sample data file: http://pastie.caboo.se/73619 (save as names.yml)

Run the app: ruby name_picker.rb names.yml

Point your browser to http://localhost:4242/
 
J

James Edward Gray II

I assume the 48 hour deadline is over. I made a fun little camping
app, however its 300k zipped up (has a couple images). I assume it
would be poor form to post it to the list, but I currently don't have
a publicly facing server where I can put it. What shall I do?

You may email it to me off-list and I'll host it for you.

James Edward Gray II
 
F

Florian Aßmann

Hi James,

I refactored my code a little, so the e-mail I sent directly to the ml
should be the richt (better) one. :D

Thanks for hosting the code :)

Regards
Florian
 
F

Florian Aßmann

Florian said:
Hi,
=20
my minimalistic approach uses the main.rb as data storage with the
__END__ keyword. So I don't need any 3rd party libs doing the
persistence stuff for me. I also tried to build a GUI but shattered a
whole day on wxRuby.
=20
Features:
* persitence + test suite
* multiple picks
=20
* eats everything, not just names, just needs TSV with header (see
example.tsv)
=20
* no sql, no extra files
* reads Tab-Separated-Values from stdin or file
=20
* ...
=20
What so ever, have a try... :D
=20
Cheers
Florian
=20

I found a flaw in lucky_filter.rb when processing empty values, just
replace at lucky_filter.rb#20:
- @attendees[ key ] =3D row.map { |value| value.strip }
+ @attendees[ key ] =3D row.map { |value| value.to_s.strip }

God bless duck typing :D

Sincerely
Florian
 
F

Florian Aßmann

Hi Jesse,

definitely a very nice idea to doing the r magic stuff :D

Regards
Florian
 
R

Rubén Medellín

The three rules of Ruby Quiz:

Here is my sollution, using Tk. It is not very fancy, as uses very
basic GUI, but is what I had time for. I will probably make it fancy,
I hope I can get it on time. If someone wants to add some eye'candy,
go on (as long as it credits the original). After all, it's for a good
cause for Ruby ;D.

______________________

#! /usr/bin/ruby

# Ruby Quiz 129 - Name Picker
# Author: Ruben Medellin <[email protected]>

require 'csv'

def read_names_csv( filename )
table = CSV::parse(File.read(filename)) rescue table =
CSV::parse( filename )
titles = table.shift
return titles, table
end

def get_name( data_row )
# Override if the format is different.
data_row[0..1].join(' ')
end

class TkRoulette

require 'tk'

attr_accessor :prizes
attr_accessor :contestants

COLORS = [:blue, :red, :green, :eek:range, :yellow, :pink]

def initialize( contestants = [], prizes = nil )
@contestants = contestants
@prizes = prizes
@winners = []
initialize_gui
Tk.mainloop
end

def initialize_gui
@root = TkRoot.new {
title "Ruby Quiz 129 - Name Picker"
}
@name = TkLabel.new {
text 'Press PLAY to start'
relief :groove
width 100
height 10
font :size => 60, :weight => :bold
pack :side => :top, :expand => true, :fill => :x
}
@play = TkButton.new {
text 'PLAY!'
width 100
height 3
font :size => 30, :weight => :bold
pack :side => :bottom, :fill => :x
}.command method:)play).to_proc
end

def play( category = nil )
if @contestants.empty?
show_message( "No more contestants" )
return
end
@tick_time = 200
pick_winner
end

def pick_winner
if @tick_time <= 0
winner = @contestants.delete_at(rand(@contestants.size))
@winners << winner
show_name( "Winner is: " + get_name(winner) + "!!" )
return
end
Tk.after(200 - @tick_time, method:)pick_winner).to_proc )
@tick_time -= 5
show_name( get_name( @contestants[rand(@contestants.size)]) )
end

def show_name( name )
@name.fg COLORS[rand(COLORS.size)]
@name.text name
end

def show_message( message )
@name.fg :black
@name.text message
end

end

titles, data = read_names_csv( <<-CSV )
"First name","Last name","Organization","Mail"
"Fred","Flinstone","Slate Rock and Gravel
Company","(e-mail address removed)"
"Homer","Simpson","Sprinfield's Nuclear Power Plant","(e-mail address removed)"
"George","Jetson","Spacely's Space
Sprockets","(e-mail address removed)"
"Elmer","Food","Acme","(e-mail address removed)"
CSV
#read_names_csv('attendants.csv')

if __FILE__ == $0
roulette = TkRoulette.new(data)
end

____________________________________
 
M

Morton Goldberg

I tried to submit my solution but the mailing list rejected it as too
big (216 KB). What do I do now? Anybody got a suggestion for a work-
around?

Regards, Morton

P.S. The reason it's so big is because it has a GUI that uses several
of GIF files to do a little animation.
 
J

James Edward Gray II

I tried to submit my solution but the mailing list rejected it as
too big (216 KB). What do I do now? Anybody got a suggestion for a
work-around?

You may send it to me off-list. I will host it from the Ruby Quiz site.

James Edward Gray II
 
M

Morton Goldberg

You may send it to me off-list. I will host it from the Ruby Quiz
site.

Thanks, James. I'll do that. Do you think it might be a good idea to
post just the code (without the support files) on the mailing list?
In addition to sending it to you, that is.

Regards, Morton
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top