diff --git a/JonsGame/controller.rb b/JonsGame/controller.rb new file mode 100644 index 0000000..2cf70be --- /dev/null +++ b/JonsGame/controller.rb @@ -0,0 +1,34 @@ +require_relative 'view' +require_relative 'retirement_account_scenario' + +class GameController + include GameView + + def run! + sample_values = {name: "Sample Scenario", beginning_age: 23} + scenario1 = RetirementAccountScenario.new sample_values + scenario2 = RetirementAccountScenario.new sample_values + + Print::run_spinner + Print::title_screen + + loop do + Print::menu + case Print::fetch_user_input + when "V" + Print::print_retirement_comparison(scenario1, scenario2) + when "S1" + scenario1 = RetirementAccountScenario.new Print::serialize_scenario + when "S2" + scenario2 = RetirementAccountScenario.new Print::serialize_scenario + when "Q" + puts "We're done" + exit + else + Print::error_message + end + end + end +end + +GameController.new.run! diff --git a/JonsGame/retirement_account_scenario.rb b/JonsGame/retirement_account_scenario.rb new file mode 100644 index 0000000..484af5d --- /dev/null +++ b/JonsGame/retirement_account_scenario.rb @@ -0,0 +1,63 @@ +require 'faker' +require 'pry' + +class RetirementAccountScenario + attr_reader :name, :beginning_age, :retirement_age, :death_age + attr_reader :ira_type, :annual_contribution + attr_reader :accumulation_market_return_rate, :accumulation_tax_rate + attr_reader :retirement_tax_rate + + VALID_IRA_TYPES = [ :roth, :regular] + + def initialize args + @name = args.fetch(:name, "UNNAMED") + @beginning_age = args.fetch(:beginning_age, 25).to_f + @retirement_age = args.fetch(:retirement_age, 65).to_f + @death_age = args.fetch(:death_age, 100).to_f + @annual_contribution = args.fetch(:annual_contribution, 500).to_f + @accumulation_market_return_rate = args.fetch(:accumulation_market_return_rate, 0.01).to_f + @accumulation_tax_rate = args.fetch(:accumulation_tax_rate, 0).to_f + @retirement_tax_rate = args.fetch(:retirement_tax_rate, 0).to_f + @ira_type = args.fetch(:ira_type, :regular) + + raise ArguementError "Retirement must be after beginning age" unless retirement_age > beginning_age + raise ArguementError "Death must be after retirement" unless death_age > retirement_age + raise ArguementError "Must earn interest" unless accumulation_market_return_rate > 0 + raise ArguementError "Must contribute" unless annual_contribution > 0 + raise ArguementError unless VALID_IRA_TYPES.include?(ira_type) + end + + def retirement_income + yearly_withdraw = (balance_at_retirement / years_in_retirement).round + if ira_type == :roth + yearly_withdraw + else #Must take out taxes + (yearly_withdraw * (1.0 - retirement_tax_rate)).round + end + end + + private + + def balance_at_retirement + # http://www.moneychimp.com/articles/finworks/fmbasinv.htm + z = 1 + accumulation_market_return_rate + if ira_type == :roth + c = annual_contribution * (1.0 - accumulation_tax_rate) + else + c = annual_contribution + end + y = years_in_accumulation + + c * ( ( z**(y + 1) - z ) / ( z - 1 )) + end + + def years_in_retirement + death_age - retirement_age + end + + def years_in_accumulation + retirement_age - beginning_age + end + +end + diff --git a/JonsGame/spec/retirement_account_scenario_test.rb b/JonsGame/spec/retirement_account_scenario_test.rb new file mode 100644 index 0000000..ff31dae --- /dev/null +++ b/JonsGame/spec/retirement_account_scenario_test.rb @@ -0,0 +1,86 @@ +require_relative '../retirement_account_scenario' + +describe RetirementAccountScenario do + describe "#retirement_income" do + context "when given valid parameters" do + subject(:valid_scenario) { + setup_values = {} + setup_values[:name] = "Valid Account" + setup_values[:beginning_age] = 35 + setup_values[:retirement_age] = 65 + setup_values[:death_age] = 95 + setup_values[:annual_contribution] = 5000 + setup_values[:accumulation_market_return_rate] = 0.08 + setup_values[:ira_type] = :roth + RetirementAccountScenario.new setup_values + } + + it "correctly calculates income" do + expect(valid_scenario.retirement_income).to eq(20391) + end + end + + context "when roth is given accumulation tax rate" do + subject() { + setup_values = {} + setup_values[:name] = "Valid with Tax Rate" + setup_values[:beginning_age] = 35 + setup_values[:retirement_age] = 65 + setup_values[:death_age] = 95 + setup_values[:annual_contribution] = 5000 + setup_values[:accumulation_market_return_rate] = 0.08 + setup_values[:accumulation_tax_rate] = 0.28 + setup_values[:ira_type] = :roth + RetirementAccountScenario.new setup_values + } + it "reduces contributions by tax rate" do + expect(subject.retirement_income).to eq(14682) + end + end + + context "when roth is given retirement tax rate" do + subject() { + setup_values = {} + setup_values[:name] = "Valid with Tax Rate" + setup_values[:beginning_age] = 35 + setup_values[:retirement_age] = 65 + setup_values[:death_age] = 95 + setup_values[:annual_contribution] = 5000 + setup_values[:accumulation_market_return_rate] = 0.08 + setup_values[:accumulation_tax_rate] = 0.28 + setup_values[:retirement_tax_rate] = 0.20 + setup_values[:ira_type] = :roth + RetirementAccountScenario.new setup_values + } + it "does not reduce retirement income" do + expect(subject.retirement_income).to eq(14682) + end + end + + context "when a regular retirement account" do + subject() { + setup_values = {} + setup_values[:name] = "Regular retirement account" + setup_values[:beginning_age] = 35 + setup_values[:retirement_age] = 65 + setup_values[:death_age] = 95 + setup_values[:annual_contribution] = 5000 + setup_values[:accumulation_market_return_rate] = 0.08 + setup_values[:accumulation_tax_rate] = 0.28 + setup_values[:retirement_tax_rate] = 0.20 + setup_values[:ira_type] = :regular + RetirementAccountScenario.new setup_values + } + it "gets taxed in retirement" do + expect(subject.retirement_income).to eq (16313) + end + + end + end + + describe "#initialize" do + context "when a required parameter is missing" do + it { expect { RetirementAccountScenario.new nothing: "yes" }.to_not raise_error() } + end + end +end diff --git a/JonsGame/view.rb b/JonsGame/view.rb new file mode 100644 index 0000000..63cd43a --- /dev/null +++ b/JonsGame/view.rb @@ -0,0 +1,129 @@ +module GameView + + module Print + + class << self + def run_spinner + print "Loading (please wait) " + #5.times { print "."; sleep 1; } + 5.times { print "."; } + print "\n" + end + + def error_message + puts "That's not a command key. Try again!" + end + + def title_screen +title = < scenario2.retirement_income + return "Result: #{scenario2.name} generates $#{difference} more income in retirement!" if scenario2.retirement_income > scenario1.retirement_income + end + + def serialize_scenario + {}.tap do |args| + args[:name] = fetch_user_input("\nEnter the name:") + args[:beginning_age] = fetch_user_input("\nEnter beginning age:") + args[:retirement_age] = fetch_user_input("\nEnter retirement age:") + args[:death_age] = fetch_user_input("\nEnter age of death:") + args[:annual_contribution] = fetch_user_input("\nEnter annual contribution:") + args[:accumulation_market_return_rate] = fetch_user_input("\nEnter accumulation market return rate:") + args[:accumulation_tax_rate] = fetch_user_input("\nEnter accumulation tax rate:") + args[:retirement_tax_rate] = fetch_user_input("\nEnter retirement tax rate:") + args[:ira_type] = fetch_user_input("\nEnter retirement type (roth, regular):").to_sym + end + end + + def fetch_user_input(question=nil) + puts question if question + print "> " + gets.chomp + end + end + end +end