Ruby using Elapsed Time to increase Obj stats - ruby

I'm still new to Ruby and I'm having some issues working with time. Essentially the goal of this project is to make a virtual pet. I have class Pet set up at that when initialized randomly generates stats for the pets hunger, energy, fun, affection, and then will determine the pets mood.
In my class Game, the user is displayed the pets current stats and then asked for input to interact with the pet. The user input takes the string (e.g. 'Feed') and then will decrease #pet.hunger by 1.
What I want to do is create some way of storing the time elapsed since the user last interacted with a stat. If the stat has not been interacted with within 2 minutes it will be increased or decreased accordingly and the time will restart. Here is the module I attempted to use
module FeedTime
def feed_time
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
#user_input == 'Feed'
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
time_elapsed = end_time - start_time
#pet.hunger += 1 unless time_elapsed < 120
end
end
This is how it was used within the Game class
require_relative 'time'
class StartGame
include Reaction
include FeedTime
attr_reader :dragon
def initialize
#dragon = Dragon.new
end
def game
feed_time
loop do
welcome
user_input
break if #user_input == 'Quit'
end
end
I've tried a few variations of this code with no luck. All of my code runs without error but Hunger will never increase.

Related

Turning a single file into MVC without Rails

I need to take the single file code below and separate it into a Model, View, Controller (MVC) ruby program that can run by the ruby command in the command line without using Rails (for instructions on how to run this program from irb, check out the README.md on my RubyBank Github Repo).
require_relative 'view'
class BankAccount
attr_accessor :name, :balance
def initialize(name, balance=0)
#name = name
#balance = balance
end
def show_balance(pin_access)
if pin_access == pin || pin_access == bank_manager
puts "\nYour current balance is: $#{#balance}"
else
puts pin_error_message
end
end
def withdraw(pin_access, amount)
if pin_access == pin
#balance -= amount
puts "'\nYou just withdrew $#{amount} from your account. \n\nYour remaining balance is: $#{#balance}\n\n"
else
puts pin_error_message
end
if #balance < 0
#balance += amount
return overdraft_protection
end
end
def deposit(pin_access, amount)
if pin_access == pin
#balance += amount
puts "\nYou just deposited $#{amount} into your account. \n\nYour remaining balance is: $#{#balance}"
else
puts pin_error_message
end
end
private
def pin
#pin = 1234
end
def bank_manager
#bank_manager = 4321
end
def pin_error_message
puts "Invalid PIN number. Try again."
end
def overdraft_protection
puts "\nYou have overdrafted your account. We cannot complete your withdrawl. Please deposit money before trying again. \n\nYour corrected balance is $#{#balance}"
end
end
I am looking for a good place to start or a general approach towards taking on such a task.
A simple approach would be to create three classes:
BankAccount minus text output is your Model.
All the text I/O goes into your View. Prompt the user for an action or registration. Get the model (for displaying data) from your controller or use the model directly.
Your Controller is responsible for a) reacting to user input, b) modifying the model and c) for holding state not directly related to the BankAccount (this point is discussable) like being logged in or what actions are possible from your current state. Your Controller receives all actions with user supplied data from your view.
Clean separation between View and Controller may be a bit hard in a console application. Also, there are about a million possible ways to implement this in a MVC style. Most important point: no UI-Code (puts/gets) in your model.

Basic Ruby questions

My understanding is that checking and savings are saved under account_a and account_b. Can someone explain what's happening in the last two lines?
class Account
attr_accessor :balance
def initialize(balance)
#balance = balance
end
end
class Transaction
def initialize(account_a,account_b)
#account_a = account_a
#account_b = account_b
end
private
def debit(account, amount)
account.balance -= amount
end
def credit(account,amount)
account.balance += amount
end
public
def transfer(amount)
debit(#account_a, amount)
credit(#account_b, amount)
end
end
savings = Account.new (100)
checking = Account.new (200)
trans = Transaction.new(checking, savings)
trans.transfer(50)
How do I print the value of trans?
In the second to last line, you are initializing a new Transaction from your checking account to your savings account. Then in the last line, you are transferring 50.
You cannot print the value of trans because there is no value associated with it. You can print out the balance of your initialized accounts though, by calling savings.balance and checking.balance
When you create trans using Transaction.new then references to checking and savings are stored inside the transaction object as #account_a and #account_b
The transfer method calls debit with #account_a and credit with #account_b so decreasing the balance of a by the amount and increasing the balance of b by the same amount, in effect transferring from checking to savings.

Tracking score and rounds played using Sinatra

I have spent the last two weeks building a Sinatra app, and I have hit my first major roadblock. I have spent the last 3 days trying to find a solution, but need to ask for help.
I am building a small game using Sinatra, and I am having trouble understanding a concept.
The game tracks rounds and scores.
Here is some of my code:
#Player Class
class Player
attr_accessor :name, :guess, :round_score, :total_score
def initialize
#guess = 0
#round_score = 0
#total_score = 0
end
end
#Tracks Round
class RoundCount
attr_accessor :round
def initialize
#round = 0
end
def count
#round += 1
end
end
helpers do
#New game function. Makes set number of player classes in ##player array.
def new_game(players)
##i = RoundCount.new
##player = []
players.times do |x|
##player[x] = Player.new
##player[x].name = "Player#{x}"
end
end
#Calculates score
def player_score()
params.each do |x, y|
##player.each do |z|
if z.name == x
z.guess = y.to_i
z.round_score = (y.to_i - ##movie[##i.round].ratings['critics_score'].to_i).abs
z.total_score += (y.to_i - ##movie[##i.round].ratings['critics_score'].to_i).abs
end
end
end
end
get '/' do
slim :home
end
get '/players' do
slim :players
end
post '/numplayers' do
new_game(params[:numplayers].to_i)
slim :names
end
post '/names' do
slim :titles
end
Currently the program gives me the error "warning: class variable access from topelevel" and is affected by other users who play at the same time on seperate computers.
I need help understanding how to track players scores and the round of the game in each view, in a way that isn't affected by other players running a different game.
Any suggestions on how to avoid using class variables for round and score, while retaining a way to access them in each route and view?
Use Sessions or Cookies
Generally, keeping server-side state through class variables will make your life harder. There's also usually more than one way to do anything, but for your use case I'd recommend that you store your state in a session variable or a cookie.
You could then store your state in any serializable object, such as a Hash, Array, Struct, or OpenSruct. This will allow for concurrent users, and do away with the need for the class variables you're currently using.

Learnstreet Ruby Lesson 11.5 Transfer state between objects

I've been stuck on this Learnstreet lesson for a day now. The exercise prompts:
Can you now implement a method called transfer! which takes two parameters, amount and other_account. The method should withdraw the specified amount from the current object and deposit it into other_account object.
The code in the editor goes as follows:
class BankAccount
attr_accessor :name, :balance, :address
def initialize(name, balance, address)
#name = name
#balance = balance
#address = address
end
def withdraw!(amount)
if #balance - amount > 0
#balance = #balance - amount
end
#balance
end
def deposit!(amount)
#balance += amount
end
# your code here
end
alices_account = BankAccount.new("Alice Cooper", 2500, "456 University Avenue")
bobs_account = BankAccount.new("Bob Ventura", 2100, "3500 Fox Street")
I know that you need to set up a method with def transfer!(amount, other_account). However I do not know what to put in the bottom after alices_account and bobs_account.
You'd call transfer! on one of the objects, passing in the other, e.g.,
bobs_account.transfer!(500, alices_account)
You're just calling a method on an instance, like "foo".size or [1, 2, 3].each etc. The only difference is that you've created the method you're calling.
I know that you need to set up a method with def transfer!(amount, other_account).
So basically you have to create BankAccount#transfer! that withdraw some money from the object that calls it and deposit the sum into the "other" BankAccount object.
The solution is pretty trivial since you have the BankAccount#withdraw! and BankAccount#deposit! already set up:
def transfer!(amount, other_account)
withdraw! amount
other_account.deposit! amount
end
However I do not know what to put in the bottom after alices_account and bobs_account.
The exercise doesn't require you to do anything with the latter. If you were supposed to do something you would need to know the amount of "money" to transfer from alices_account to bobs_account an viceversa and then go with:
# assuming x to be the amount to transfer
alices_account.transfer! x, bobs_account
or:
# assuming x to be the amount to transfer
bobs_account.transfer! x, alices_account
Ok now. I've spent an hour to complete all the 10 course before that one and this is what I discovered. At some point you get to write the last two lines of your code.
Then a weird thing happens. The code generated by the exercise contains a . To near the end which is obviously a syntax error. By removing that line and adding the method I provided above you get to pass the test.

How to fake Time.now?

What's the best way to set Time.now for the purpose of testing time-sensitive methods in a unit test?
I really like the Timecop library. You can do time warps in block form (just like time-warp):
Timecop.travel(6.days.ago) do
#model = TimeSensitiveMode.new
end
assert #model.times_up!
(Yes, you can nest block-form time travel.)
You can also do declarative time travel:
class MyTest < Test::Unit::TestCase
def setup
Timecop.travel(...)
end
def teardown
Timecop.return
end
end
I have some cucumber helpers for Timecop here. They let you do things like:
Given it is currently January 24, 2008
And I go to the new post page
And I fill in "title" with "An old post"
And I fill in "body" with "..."
And I press "Submit"
And we jump in our Delorean and return to the present
When I go to the home page
I should not see "An old post"
Personally I prefer to make the clock injectable, like so:
def hello(clock=Time)
puts "the time is now: #{clock.now}"
end
Or:
class MyClass
attr_writer :clock
def initialize
#clock = Time
end
def hello
puts "the time is now: #{#clock.now}"
end
end
However, many prefer to use a mocking/stubbing library. In RSpec/flexmock you can use:
Time.stub!(:now).and_return(Time.mktime(1970,1,1))
Or in Mocha:
Time.stubs(:now).returns(Time.mktime(1970,1,1))
I'm using RSpec and I did this: Time.stub!(:now).and_return(2.days.ago) before I call Time.now. In that way I'm able to control the time I used for that particular test case
Using Rspec 3.2, the only simple way I found to fake Time.now return value is :
now = Time.parse("1969-07-20 20:17:40")
allow(Time).to receive(:now) { now }
Now Time.now will always return the date of Apollo 11 landing on the moon.
Source: https://www.relishapp.com/rspec/rspec-mocks/docs
Do the time-warp
time-warp is a library that does what you want. It gives you a method that takes a time and a block and anything that happens in the block uses the faked time.
pretend_now_is(2000,"jan",1,0) do
Time.now
end
Don't forget that Time is merely a constant that refers to a class object. If you're willing to cause a warning, you can always do
real_time_class = Time
Time = FakeTimeClass
# run test
Time = real_time_class
If you have ActiveSupport included, you could use:
travel_to Time.zone.parse('2010-07-05 08:00')
http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html
Also see this question where I put this comment as well.
Depending upon what you are comparing Time.now to, sometimes you can change your fixtures to accomplish the same goal or test the same feature. For example, I had a situation where I needed one thing to happen if some date was in the future and another to happen if it was in the past. What I was able to do was include in my fixtures some embedded ruby (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
Then in your tests then you choose which one to use to test the different features or actions based upon the time relative to Time.now.
Had the same issue, I had to fake time for a spec for a specific day and time just did that:
Time.stub!(:now).and_return(Time.mktime(2014,10,22,5,35,28))
this will give you:
2014-10-22 05:35:28 -0700
This kind of works and allows for nesting:
class Time
class << self
attr_accessor :stack, :depth
end
def self.warp(time)
Time.stack ||= []
Time.depth ||= -1
Time.depth += 1
Time.stack.push time
if Time.depth == 0
class << self
alias_method :real_now, :now
alias_method :real_new, :new
define_method :now do
stack[depth]
end
define_method :new do
now
end
end
end
yield
Time.depth -= 1
Time.stack.pop
class << self
if Time.depth < 0
alias_method :new, :real_new
alias_method :now, :real_now
remove_method :real_new
remove_method :real_now
end
end
end
end
It could be slightly improved by undefing the stack and depth accessors at the end
Usage:
time1 = 2.days.ago
time2 = 5.months.ago
Time.warp(time1) do
Time.real_now.should_not == Time.now
Time.now.should == time1
Time.warp(time2) do
Time.now.should == time2
end
Time.now.should == time1
end
Time.now.should_not == time1
Time.now.should_not be_nil
Depending upon what you are comparing Time.now to, sometimes you can change your fixtures to accomplish the same goal or test the same feature. For example, I had a situation where I needed one thing to happen if some date was in the future and another to happen if it was in the past. What I was able to do was include in my fixtures some embedded ruby (erb):
future:
comparing_date: <%= Time.now + 10.years %>
...
past:
comparing_date: <%= Time.now - 10.years %>
...
Then in your tests then you choose which one to use to test the different features or actions based upon the time relative to Time.now.
i just have this in my test file:
def time_right_now
current_time = Time.parse("07/09/10 14:20")
current_time = convert_time_to_utc(current_date)
return current_time
end
and in my Time_helper.rb file i have a
def time_right_now
current_time= Time.new
return current_time
end
so when testing the time_right_now is overwritten to use what ever time you want it to be.
I allways extract Time.now into a separate method that I turn into attr_accessor in the mock.
The recently-released Test::Redef makes this and other fakery easy, even without restructuring the code in a dependency-injection style (especially helpful if you're using other peoples' code.)
fake_time = Time.at(12345) # ~3:30pm UTC Jan 1 1970
Test::Redef.rd 'Time.now' => proc { fake_time } do
assert_equal 12345, Time.now.to_i
end
However, be careful of other ways to obtain time that this will not fake out (Date.new, a compiled extension that makes its own system call, interfaces to things like external database servers which know current timestamps, etc.) It sounds like the Timecop library above might overcome these limitations.
Other great uses include testing things like "what happens when I'm trying to use this friendly http client but it decides to raise this an exception instead of returning me a string?" without actually setting up the network conditions which lead to that exception (which may be tricky). It also lets you check the arguments to redef'd functions.
My own solution https://github.com/igorkasyanchuk/rails_time_travel - a gem with UI, so you don't need to hardcode any datetime in the code. Just change it from the UI.
It might be also very useful for you QA's team, or testing app on the staging.

Resources