Skip to content

Instantly share code, notes, and snippets.

@psylone
Created July 12, 2014 05:58
Show Gist options
  • Save psylone/4084aa26c2be1c0a2312 to your computer and use it in GitHub Desktop.
Save psylone/4084aa26c2be1c0a2312 to your computer and use it in GitHub Desktop.
Indicator class for control player parameters (experience, energy, attack etc.)
class Indicator < ActiveRecord::Base
include CorruptionResponder
include CorruptionIncrement
include CorruptionDecrement
include PartnerShip
include GiveUnit
PARAM_NAMES = %w{attack defence health energy clashes}
attr_accessible :experience, :skill_points, :level, :current_health, :max_health, :current_energy, :max_energy, :current_attack, :max_attack
belongs_to :player, inverse_of: :indicator
validates :player, presence: true
before_save :update_level, if: :experience_changed?
before_save :reset_timers
def update_level
return unless time_to_update_level?
inc :level, value: 1
inc :skill_points, value: 3 if level > 3
Thread.new{ perform_offer } if level == 5
revive
rise_unit if respond_to? :rise_unit
return update_level if time_to_update_level?
Activity.publish!(player: player, action: :update_level)
end
def reset_timers
%w{health energy attack}.each do |value|
player.reset_timer value if changes["current_#{value}"].try(:first) == self["max_#{value}"]
end
end
# Распределение очков умения
#
# @param [Hash] params
def distribute_skill_points(params)
return Failure.new message: I18n.tkey('indicators.distribute_skill_points.failure.cannot_distribute_during_study') if level <= 3
return Failure.new message: I18n.tkey('indicators.distribute_skill_points.failure.have_no_skill_points') unless have_skill_points?
return Failure.new message: I18n.tkey('indicators.distribute_skill_points.failure.cannot_distribute_more') if overmuch?(params)
return Failure.new message: I18n.tkey('indicators.distribute_skill_points.failure.cannot_distribute_more') if params[:clashes].to_i[0] == 1
params.each do |key, value|
value = value.to_i.abs
case key.to_sym
when :attack
player.modifier.inc(:attack, value: 1 * value)
when :defence
player.modifier.inc(:defence, value: 1 * value)
when :health
%w(max_health current_health).each do |attr|
player.indicator.inc(attr, value: 10 * value)
end
when :energy
%w(max_energy current_energy).each do |attr|
player.indicator.inc(attr, value: 10 * value)
end
when :clashes
%w(max_attack current_attack).each do |attr|
player.indicator.inc(attr, value: value / 2)
end
end
player.indicator.dec(:skill_points, value: value) if PARAM_NAMES.include? key.to_s
end
player.save!
Success.new(message: I18n.tkey('indicators.distribute_skill_points.success'))
end
# Количество опыта, необходимое для перехода на следующий уровень.
#
# @return [Integer]
def experience_for_next_level
Level.new(level).next.exp
end
def experience_for_prev_level
Level.new(level).pred.exp
end
def time_to_update_level?
experience >= experience_for_next_level
end
# Возвращает true, если произошла попытка потратить больше очков, чем имеется.
#
# @param [Hash] params очки умения
def overmuch? params
params.values_at(*PARAM_NAMES).map{ |i| i.to_i.abs }.sum > skill_points
end
# Возвращает true, если у игрока есть очки умения.
# TODO replace with skill_points?
def have_skill_points?
skill_points > 0
end
def revive *values
default = [:energy, :health, :attack]
return nil unless (values - default).blank?
values = default if values.blank?
values.each{ |stat| self["current_#{stat}"] = self["max_#{stat}"] }
end
def revive! *values
revive(*values) and save!
end
def perk_slots
case level
when 1...10 then 0
when 10...30 then 1
when 30...50 then 2
when 50...70 then 3
when 70...90 then 4
else 5
end
end
end
# encoding: utf-8
require 'spec_helper'
describe Indicator do
def boost_exp amount = 10
@indicator.experience += amount
end
def set_level value
@indicator.level = value
end
def set_sp amount
@indicator.skill_points = amount
end
def decrease *stats
options = stats.extract_options!
options.reverse_merge! amount: 10
stats.each do |stat|
stat = stat.to_sym
attack_amount = stat == :attack ? 1 : nil
@indicator.decrement "current_#{stat}", attack_amount || options[:amount]
@indicator.save! if options[:save]
end
end
before :all do
FactoryGirl.create :special_item
end
describe "has valid factories for" do
it "new indicator" do
FactoryGirl.create(:indicator).should be_valid
end
end
describe "has validations" do
context "for player" do
it "should present" do
FactoryGirl.build(:indicator, player: nil).should_not be_valid
end
end
end
describe "has callbacks" do
describe "before save" do
before :each do
@indicator = FactoryGirl.create(:indicator)
end
let(:run_callbacks){ @indicator.run_callbacks(:save) }
context "#update_level" do
it "skip if experience wasn't change" do
@indicator.should_not_receive(:update_level)
run_callbacks
end
it "invoke if experience changed" do
boost_exp
@indicator.should_receive(:update_level)
run_callbacks
end
it "don't increase level if experience is not enough" do
boost_exp
expect{ run_callbacks }.to_not change{ @indicator.level }
end
it "increase level by 1 if experience is enough" do
boost_exp 11
expect{ run_callbacks }.to change{ @indicator.level }.by(1)
end
it "increase level several times if experience is enough" do
boost_exp 45
expect{ run_callbacks }.to change{ @indicator.level }.by(2)
end
it "increase skill points by 3 after the 3rd level" do
boost_exp 46
expect{ run_callbacks }.to change{ @indicator.skill_points }.by(3)
end
it "send activity" do
boost_exp 45
expect{ run_callbacks }.to change{ Cache::List[:activities].length }.by(1)
end
it "restore stats" do
boost_exp 11
@indicator.should_receive(:revive).with(no_args)
run_callbacks
end
it "apply partnership offer" do
boost_exp 75
@indicator.should_receive(:perform_offer)
run_callbacks
end
if Indicator.included_modules.include? GiveUnit
before :all do
FactoryGirl.create(:special_item).update_attributes level: 21
end
it "rise special unit up to 21st level" do
boost_exp 3014 # 21st level
run_callbacks
@indicator.player.items.where(title: "Коррупционный Дед Мороз")[0].level.should eq(21)
end
it "rise special unit up to 21st level if there's no one" do
@indicator.player.properties.destroy_all
boost_exp 3014
run_callbacks
@indicator.player.items.where(title: "Коррупционный Дед Мороз")[0].level.should eq(21)
end
end
end
context "#reset_timers" do
def should_reset_timer stat
@indicator.decrement "current_#{stat}", 10
@indicator.player.should_receive(:reset_timer).with(stat)
run_callbacks
end
it "reset health timer if health lost" do
should_reset_timer "health"
end
it "reset energy timer if energy lost" do
should_reset_timer "energy"
end
it "reset attack timer if attack lost" do
should_reset_timer "attack"
end
end
end
end
describe "has instance methods" do
def should_restore *stats
stats.each{ |stat| @indicator["current_#{stat}"].should eq(@indicator["max_#{stat}"]) }
end
before :each do
@indicator = FactoryGirl.create :indicator
end
context "#experience_for_next_level" do
it "up to 2 level" do
@indicator.experience_for_next_level.should eq(11)
end
it "up to 4 level" do
set_level 3
@indicator.experience_for_next_level.should eq(46)
end
it "up to 13 level" do
set_level 12
@indicator.experience_for_next_level.should eq(804)
end
end
context "#experience_for_prev_level" do
it "return 0 for 1 level" do
@indicator.experience_for_prev_level.should eq(0)
end
it "down to 1 level" do
set_level 2
@indicator.experience_for_prev_level.should eq(11)
end
it "down to 3 level" do
set_level 4
@indicator.experience_for_prev_level.should eq(46)
end
it "down to 12 level" do
set_level 13
@indicator.experience_for_prev_level.should eq(804)
end
end
context "#time_to_update_level?" do
it "return true if experience corresponds new level" do
boost_exp(11)
@indicator.send(:time_to_update_level?).should be_true
end
end
context "#overmuch?" do
let!(:sp){ set_sp 19 }
it "return true if distribute skill points exceed availables" do
distribute_sp = { "attack" => "2", "defence" => "3", "health" => "1", "energy" => "10", "clashes" => "4" }
@indicator.send(:overmuch?, distribute_sp).should be_true
end
it "return false if distribute skill points less than availables" do
distribute_sp = { "attack" => "2", "health" => "1", "energy" => "10", "clashes" => "4" }
@indicator.send(:overmuch?, distribute_sp).should be_false
end
end
context "#have_skill_points?" do
it "return true if skill points are available" do
set_sp 3
@indicator.send(:have_skill_points?).should be_true
end
it "return false if there aren't skill points" do
@indicator.send(:have_skill_points?).should be_false
end
end
context "#revive" do
before :each do
decrease :energy, :health, :attack
end
it "return nil for unknown stat" do
@indicator.revive(:superforce).should be_nil
end
it "don't change stat for unknown one" do
@indicator.revive(:superforce)
@indicator.current_energy.should eq(190)
end
it "restore energy with :energy parameter" do
@indicator.revive :energy
should_restore(:energy)
end
it "restore health with :health parameter" do
@indicator.revive :health
should_restore(:health)
end
it "restore attack with :attack parameter" do
@indicator.revive :attack
should_restore(:attack)
end
it "restore all stats without parameter" do
@indicator.revive
should_restore(:energy, :health, :attack)
end
it "restore several stats at once" do
@indicator.revive :energy, :attack
should_restore(:energy, :attack)
end
it "restore only necessary stat" do
@indicator.revive :attack
@indicator.current_energy.should eq(190)
end
end
context "#revive!" do
def revive_and_reload *stats
@indicator.revive! *stats
@indicator.reload
end
before :each do
decrease :energy, :health, :attack
@indicator.save
end
it "invoke #revive with correct arguments" do
@indicator.should_receive(:revive).with(:energy, :attack)
@indicator.revive! :energy, :attack
end
it "restore one stat and save" do
revive_and_reload :energy
should_restore :energy
end
it "restore several stats at once and save" do
revive_and_reload :energy, :attack
should_restore :energy, :attack
end
it "restore all stats without parameters and save" do
revive_and_reload
should_restore :energy, :health, :attack
end
it "don't change stats for unknown stat" do
revive_and_reload :superforce
@indicator.current_energy.should eq(190)
end
it "return nil for unknown stat" do
@indicator.revive!(:superforce).should be_nil
end
end
context "#perk_slots" do
it "return 0 for 1 up to 9 level" do
set_level 9
@indicator.perk_slots.should eq(0)
end
it "return 1 for 10 up to 29 level" do
set_level 29
@indicator.perk_slots.should eq(1)
end
it "return 2 for 30 up to 49 level" do
set_level 49
@indicator.perk_slots.should eq(2)
end
it "return 3 for 50 up to 69 level" do
set_level 69
@indicator.perk_slots.should eq(3)
end
it "return 4 for 70 up to 89 level" do
set_level 89
@indicator.perk_slots.should eq(4)
end
it "return 5 for 90 level and more" do
set_level 90
@indicator.perk_slots.should eq(5)
end
end
end
describe "provides #distribute_skill_points" do
def distribute params = {}
sp_params.update params
@indicator.distribute_skill_points(sp_params)
end
before :each do
@indicator = FactoryGirl.create :indicator, level: 4, skill_points: 22
end
let(:sp_params){ { "attack" => "2", "defence" => "3", "health" => "1", "energy" => "10", "clashes" => "4" }.with_indifferent_access }
context "which fails when" do
it "player level less than 4" do
set_level 3
distribute.message.should eq(I18n.t "indicators.distribute_skill_points.failure.cannot_distribute_during_study")
end
it "havn't skill points" do
set_sp 0
distribute.message.should eq(I18n.t "indicators.distribute_skill_points.failure.have_no_skill_points")
end
it "not enough skill points" do
set_sp 19
distribute.message.should eq(I18n.t "indicators.distribute_skill_points.failure.cannot_distribute_more")
end
it "clashes is odd (prevents skill point gap)" do
distribute("clashes" => 5).message.should eq(I18n.t "indicators.distribute_skill_points.failure.cannot_distribute_more")
end
end
context "with" do
it "handle negative values" do
expect{ distribute "energy" => -2 }.to change{ @indicator.max_energy }.by(20)
end
it "increase attack by 1 per skill point" do
expect{ distribute "attack" => 3 }.to change{ @indicator.player.modifier.attack }.by(3)
end
it "increase defence by 1 per skill point" do
expect{ distribute "defence" => 3 }.to change{ @indicator.player.modifier.defence }.by(3)
end
it "increase max health by 10 per skill point" do
expect{ distribute "health" => 3 }.to change{ @indicator.max_health }.by(30)
end
it "increase current health by 10 per skill point" do
decrease :health, amount: 20
distribute "health" => 3
@indicator.current_health.should eq(110)
end
it "increase max energy by 10 per skill point" do
expect{ distribute "energy" => 3 }.to change{ @indicator.max_energy }.by(30)
end
it "restore current energy by 10 per skill point" do
decrease :energy, amount: 20
distribute "energy" => 3
@indicator.current_energy.should eq(210)
end
it "increase max attack by 1 per 2 skill points" do
expect{ distribute "attack" => 4 }.to change{ @indicator.max_attack }.by(2)
end
it "restore current attack by 1 per 2 skill points" do
decrease :attack
distribute "attack" => 4
@indicator.current_attack.should eq(3)
end
it "decrease skill points" do
expect{ distribute }.to change{ @indicator.skill_points }.from(22).to(2)
end
it "saving the results" do
distribute "attack" => 4
@indicator.reload.max_attack.should eq(4)
end
it "successful response" do
distribute.message.should eq(I18n.t "indicators.distribute_skill_points.success")
end
end
end
end
class Level
attr_reader :level, :exp
def initialize level = 1
@level = level
@exp = start_exp
end
def next
Level.new @level + 1
end
def pred
Level.new @level - 1
end
def next!
@level += 1
@exp = start_exp
end
def pred!
@level -= 1
@exp = start_exp
end
private
def next_exp
(((((@level + 2) / 1.2) ** 3) / 2.1) + (@level * 4)).to_i
end
def start_exp
@level == 1 ? 0 : (((((@level + 1) / 1.2) ** 3) / 2.1) + ((@level - 1) * 4)).to_i
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment