I run an irc bot, written in ruby, running the cinch irc framework. The bot replies with interesting facts and cycles through these facts so you won't get bored of them. I have set a cool down, so they can't be shown for 6 hours. Instead of showing the facts it first showed, it now shows randomly selected ones, which could be the ones that have been shown earlier.
line = IO.readlines("facts.txt")
factnumber = rand(line.length)
if fact_not_next_6_hours[factnumber] == true
factnumber = rand(line.length)
m.reply "fact #{factnumber}: #{line[factnumber]}"
fact_not_next_6_hours[factnumber] = true
fact_not_next_6_hours[factnumber]
is the variable for the 6 hour cool down; if it's set to true, cool down is active. I need to do:
factnumber = rand(line.length)
until it gets one that dosen't have the 6 hour cool down set to true, and then do
m.reply "fact #{factnumber}: #{line[factnumber]}"
fact_not_next_6_hours[factnumber] = true
My first idea was to do multiple if
s, but it didn't work and I'm sure there is a better way.
You can do:
factnumber = rand(line.length)
while fact_not_next_6_hours[factnumber] == true
factnumber = rand(line.length)
end
m.reply "fact #{factnumber}: #{line[factnumber]}"
fact_not_next_6_hours[factnumber] = true
Or:
nil while fact_not_next_6_hours[factnumber = rand(line.length)] == true
m.reply "fact #{factnumber}: #{line[factnumber]}"
fact_not_next_6_hours[factnumber] = true
You should really rename line
to lines
, as it is in fact an array of lines. I've done so in my answer.
This is essentially a "do while" loop:
begin
factnumber = rand(lines.length)
end while fact_not_next_6_hours[factnumber]
But depending on how many facts you have and how many you expect to be "used", filtering out the ones you can't use first may make more sense:
fact = (0...lines.length).zip(lines).reject do |k, v|
fact_not_next_6_hours[k]
end.sample
m.reply "fact #{fact[0]}: #{fact[1]}"
The first bit of that ((0...lines.length).zip(lines)
) is just associating each of the lines with a number (e.g. [[0, "fact"], [1, "afact"], ...]
). I recommend running each part of the method chain individually so you can fully understand what's happening.
First of all, if you just set a boolean flag, how will you know when to "unfreeze" it? I'd keep the "timestamp" of last accessed time in the object. Also, instead of using primitive types all around, I'd do it in a bit more object oriented fashion.
Here's my solution:
class Fact
attr_reader :text
def initialize(text)
@text = text
@last_accessed = Time.new(0) # a long time ago, not in cooldown
end
def in_cooldown?
Time.now - @last_accessed < 60*60*6
end
def cooldown!
@last_accessed = Time.now
end
end
class Facts
attr_reader :all
def initialize(file_name)
@all = IO.readlines("facts.txt").map{|line| Fact.new(line)}
end
end
class FactRandomizer
def initialize(facts)
@facts = facts
end
def get
fact = not_in_cooldown.sample || all.sample # all can be in cooldown
fact.cooldown!
fact.text
end
private
def not_in_cooldown
@facts.select{|fact| !fact.in_cooldown?}
end
end
Usage:
facts = Facts.new("whatever").all
randomizer = FactRandomizer.new(facts)
randomizer.get
EDIT:
I refactored the code, so that it does not use class methods anymore. Note, how much easier it would be to test this code now and how easy it is to interchange parts of it (like for example replacing the part that reads the facts from file or what does it mean for a fact to be in cooldown).