# \[SOLUTION\] LSRC Name Picker (#129)

**URL:** <https://www.ruby-forum.com/t/solution-lsrc-name-picker-129/108108>\
**Category:** Ruby\
**Created:** [June 29, 2007, 2:08am UTC](https://www.ruby-forum.com/t/solution-lsrc-name-picker-129/108108 "2007-06-29T02:08:07Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![jameskelly](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/3ab097/32.png) [@jameskelly](https://www.ruby-forum.com/u/jameskelly)\
**Post date:** [June 29, 2007, 2:08am UTC](https://www.ruby-forum.com/t/solution-lsrc-name-picker-129/108108/1 "2007-06-29T02:08:07Z")

</div>

Here is my solution to this week’s Ruby quiz. It is my first program to  
use Ruby/Tk, and, after using it, will probably be my last.

The  
picker’s main procedure as follows: Upon startup, the program creates a  
pseudo terminal command line displaying a short “conversation” between  
the program and the user, with a picture of a star above it the whole  
time (ASCII-art, as I was loathe to include, other non-plain-text files  
in the solution). It then quickly scrolls through a listbox with a  
terminal-ish feel, saying that it is “scanning” for one worthy of  
receiving the prize.It then switches to a view with the scrambled name  
of the recipient at the bottom, and suspensefully has the characters  
fly up towards the top, placing themselves in the correct order. If  
known, the picker then displays the recipient’s hometown and  
organization below the name.

To  
implement the persistence - well, what could be sexier than having the  
program modify its own source code? If it detects the “-a” command-line  
argument, rather than running the picker, the program will instead  
append source code to itself that adds the attendee’s name to an array  
(with -o and -t options for organization and hometown, respectively).  
Similarly, after being run, the program modifies itself to add the  
winner to the $previous\_winners array. Originally, the add\_code method  
implemented a “Schlemiel the Painter” algorithm, as all appended code  
had to come before Tk.mainloop; by using at\_exit, I have managed to  
avoid that so that the program can just do a plain 'ol append.

To seed the program with test data, I just grabbed the list of last  
names from [http://www.census.gov/genealogy/names/dist.all.last](http://www.census.gov/genealogy/names/dist.all.last), saved it  
as “names.dat” and used the following script (LSRCPicker.rb was the name  
of my program’s file):

File.open(“names.dat”) do |f|  
names = f.readlines.map{|l|l.split[0].capitalize}  
300.times do  
name = names[rand(names.length)] + " " + name =  
names[rand(names.length)]  
org = nil  
if rand(2)==1  
org = names[rand(names.length)] + " and " +  
names[rand(names.length)] + " Consulting, LLC"  
end  
home = names[rand(names.length)] + "ville, " + %w{MA CA NY MI FL  
MO AZ TX AR IL}.sort\_by{rand}.first  
if  
org  
`ruby LSRCPicker.rb -a #{name} -o #{org} -t #{home}`  
else  
`ruby LSRCPicker.rb -a #{name} -t #{home}`  
end  
end  
end

Here is my actual solution:

####Door prize picekr for LSRC. The program creates persistance by  
modifying its  
####own source code.  
####Command line usage: Without arguments, it runs the name picker  
####With the -a argument, combined with the optional -o and -t  
arguments,  
####it adds an attendee’s name, organization, and hometown respectively  
to the program  
require ‘tk’  
require ‘enumerator’

$attendees = []  
$organization = {}  
$hometown = {}

$previous\_winners = [nil]

###Adds code\_str to this source file right before Tk::mainloop is  
invoked  
def add\_code(code\_str)

File.open($0, “a”) do |f|  
f.puts code\_str  
end  
end

class Array  
def first\_satisfies\_i  
each\_with\_index {|el, i| return i if yield el}  
nil  
end

def map\_with\_index  
mapped = []  
each\_with\_index {|el, i| mapped[i] = yield(el,i)}  
mapped  
end  
end

def get\_arg(name)  
arg\_proc = proc {|arg| arg =~ /^-/}  
if (i = ARGV.index name)  
ARGV[(i+1)…((j=ARGV[(i+1)…-1].first\_satisfies\_i(&arg\_proc)) ? i+j  
: -1)  
].join(’ ')  
end  
end

###Code to check and process people being added from the command line  
unless ARGV==[]  
name, org, town = [‘-a’,‘-o’,‘-t’].map{|n| get\_arg n}  
add\_code \<\<-EOC

```
$attendees <<

```

#{name.inspect}  
$organization[#{name.inspect}] = #{org.inspect}  
$hometown[#{name.inspect}] = #{town.inspect}  
EOC  
exit  
end

class TkVariable  
###Makes updating values as easy as it should be  
def []=(\*args)  
v = self.value  
v[\*args[0…-1]] = args.last  
self.value = v  
end  
end

###Simulates a user typing text into a console  
###The only way I could get it to wait for the typing to finish before  
###continuing was to have it yield when done  
def type(tkvar, text, sleep\_t=0.05)  
Thread.new(tkvar, text) do |tkvar, text|  
until text.empty?  
sleep sleep\_t  
tkvar.value, text[0,1] = tkvar.value+text[0,1], “”  
end  
yield  
end  
end

def char\_fly(tkvars,  
char\_pos, dest\_pos)  
incr =(dest\_pos.to\_f-char\_pos)/(tkvars.length-1)  
c = tkvars.last.value[char\_pos, 1]  
return if " "==c  
Thread.new(tkvars, char\_pos, incr, c) do |tkvars, char\_in, incr, c|  
tkvars.reverse.each\_cons(2) do |tkvar\_prev, tkvar|  
tkvar\_prev[char\_in.round, 1] = ’ ’  
char\_in += incr  
tkvar[char\_in.round, 1] = c  
sleep 0.1  
end  
end  
end

root = TkRoot.new {  
title ‘Lone Star Ruby Conf Door Prize Picker’  
background ‘#000000’}  
TkMessage.new(root){  
background ‘#000000’  
borderwidth 0  
justify ‘center’  
font ‘courier’  
foreground ‘#C0C0C0’  
text \<\<EOD

.+  
+h:  
-shh` `shhhs  
`........./hhhhy---:--::::. `:shhhhyyyyyssyhhhhhhhys/` `:shhhhyo+oyhhhhhy+.  
`/syyyhhhhyyy: `ohhhhhyssoy:

/yhhhdhhhysyh:  
.shhyo- `:oyhhh`  
oho- -+ys  
-:` -.  
EOD  
}.grid

content\_frame = TkFrame.new(root){  
background ‘#000000’  
grid{rowspan 60; colspan ‘100’; sticky “ew”}}

##Holds a pseudo-console  
console\_frame = TkFrame.new(content\_frame) {  
background ‘#000000’  
width 100  
grid{rowspan 100; colspan ‘100’; sticky “ew”}}  
console\_var = TkVariable.new " "\*100  
console = TkLabel.new(console\_frame){  
background ‘#000000’  
foreground ‘#C0C0C0’  
justify  
‘left’  
font TkFont.new(‘Courier’){size 40}  
grid{rowspan 100; colspan ‘100’; sticky “ew”}  
height 7  
}.textvariable(console\_var)

##Holds the list that scrolls all the attendees names  
list\_frame = TkFrame.new(content\_frame) {  
background ‘#000000’  
grid{rowspan 100; colspan ‘100’; sticky “ew”}}  
list = TkListbox.new(list\_frame){  
background ‘#000000’  
foreground ‘#C0C0C0’  
borderwidth 0  
selectforeground ‘#000000’  
selectbackground ‘#C0C0C0’  
highlightthickness 0  
width 75  
font TkFont.new(‘Courier’){size 40}  
listvariable [’ ']\*10  
height 10  
}

#Displays the word “scanning” when the list of attendees scrolled by  
#There is significant flicker involved with this method, as everything  
is drawn as soon  
#as I programmatically make the change.  
#I was unable to remove the flicker (perhaps by suspending drawng  
routines, but  
#could not find the class responsible in the docs). I had signicant  
trouble getting updating  
#the value of #the listvariable to work; replacing the listvariable  
worked but was very slow.  
#This approach is the best I came up with.  
scanning\_display = TkListbox.new(list\_frame){  
foreground ‘#000000’  
background ‘#000000’  
highlightthickness 0  
borderwidth 0  
width 25  
font TkFont.new(‘Courier’){size 40}  
listvariable [’ ']\*10  
height 10}

flying\_text\_frame = TkFrame.new(content\_frame) {  
background ‘#000000’  
width 100}

flying\_textboxes = ([nil]\*10).map {  
[(v=TkVariable.new(" "\*100)),  
TkEntry.new(flying\_text\_frame){  
background ‘#000000’  
foreground ‘#C0C0C0’  
borderwidth 0

font TkFont.new(‘Courier’){size 40}  
width 100  
grid{rowspan 100; colspan ‘100’; sticky “ew”}  
}.textvariable(v)]}.map{|arr|arr[0]}

TkGrid.grid(list, scanning\_display)

##The main procedure of the program  
run\_picker = proc do  
if $ran  
return  
else  
$ran = true  
end  
$attendees = $attendees.sort\_by {|n| n.split.reverse.join(’ ')}  
type(console\_var,“\n”) do  
sleep 2  
console\_var.value += “Why do you wake me, mortal?\n\>”  
sleep 2; type(console\_var, “I seek your wisdom and guidance.\n”) do  
sleep 2; console\_var.value += “What perplexes you?\n\>”; sleep 2  
type(console\_var, "Tell me the one most worthy of  
"+  
“receiving this prize.\n”) do  
sleep 2  
console\_var.value += "Very well; "  
sleep 0.5  
console\_var.value += “the time is right for that decision.”  
lvar = TkVariable.new $attendees  
sleep 1  
list.listvariable lvar  
scanning\_display.listvariable(TkVariable.new([“scanning”]))  
scanning\_display.itemconfigure(0, “background”=\> “#C0C0C0”)  
$attendees[0…-10].each\_with\_index do |el, i|

list.yview(i)  
list.selection\_set(i)  
sleep 0.01  
end  
list\_frame.ungrid  
sleep 0.2  
console\_var.value += “\nI have found your worthy candidate.” +  
" Watch and let the mystery reveal itself."  
sleep 3  
console\_frame.ungrid  
chosen = nil  
chosen = $attendees[rand($attendees.length)  
] while $previous\_winners.include? chosen  
scrambled =  
chosen.split(//).map\_with\_index{|el, i|  
[rand,el,i]}.sort.map{|arr|arr[1…2]}  
scrambled\_str = scrambled.map{|el| el[0]}.join  
flying\_textboxes.last.value = " "\*(50-scrambled\_str.length/2)+  
scrambled\_str  
flying\_text\_frame.grid  
sleep 1  
until flying\_textboxes.first.value.include? chosen  
ci=rand(scrambled\_str.length)  
char\_fly(flying\_textboxes, (50-scrambled\_str.length/2)+ci,

(50-scrambled\_str.length/2)+scrambled[ci][1])  
sleep 0.1  
end  
t = $hometown[chosen]  
o = $organization[chosen]  
flying\_textboxes[1][50-t.length/2,t.length] = t if t  
flying\_textboxes[2][50-o.length/2,o.length] = o if o  
add\_code \<\<-EOC

```
      $previous_winners << #{chosen.inspect}
    EOC
  end
end

```

end  
end

root.bind(‘FocusIn’,  
&run\_picker)  
at\_exit {Tk.mainloop}
