6
\$\begingroup\$

So about a year and a half ago, me and my friend started on a game called Zombie Smack Down. It's a text based game written in Ruby. But we were both really new to Ruby when most of it was written. I realize lots of this code is probably really bad, but I'm kinda stuck in my ways.

I'd love to know which parts of it can be refactored, and changed to follow Ruby best practices, in the game and in the tests The code is also on Github here, but that'll probably get updated and no longer be the same as the code on this site. (and if you find any camel case methods, or variables please let me know, but I tried to remove all that, but I may have missed some. I know it's bad practice in Ruby, I was younger when I made much of this.) If you install if from the one-liner on github, you don't have to enter your correct password, but if you do it'll add a really outdate manual page. Sorry, the installer and updater don't work on Windows and probably not Linux either. I don't have a way to test that.

And I know a lot of this can be updated for Ruby 2 syntax, but I need it to be compatible with the computers at my school, which have 1.8.7 installed, and jailbroken iPods, using Ruby 1.8.6 which is why there a lot of parenthesis around things like puts, because it gives warnings. For now, the tests only really need to work in Ruby 2.1.1, I'm not even sure how well it works in 1.8.7/1.8.6

One last thing, the signal trapping is not working, I tried typing Ctrl-c and it didn't quit. I'd love help with that, but I'm asking this question for a "Code Review". It's just a side note.

requie/player.yml, scores.yml, and tests.rb are on Github only because it said I had too many charecters in my question.

zsd:

#!/usr/bin/env ruby

$rpath = "#{File.expand_path '~'}/.zsd"

require "optparse"
$options = []

OptionParser.new { |opts|

    opts.on("-i", "--info", "Shows info on current game") {
        $options << "i"
    }

    opts.on("-h", "--help", "Shows help page") {
        $options << "h"
    }

    opts.on("-s", "--scores", "Shows top 5 high scores") {
        $options << "s"
    }

    opts.on("-u", "--update", "Updates zsd") {
        $options << "u"
    }

    opts.on("-t", "--testing", "For dev use only") {
        $rpath = "."
    }

}.parse!

require "#{$rpath}/require/other"

def main

    cli = Cli.new

    unless $options.empty?
        $options.each { |o|
            case o
            when "i"
                cli.player.info
                exit
            when "h"
                cli.tutorial
                exit
            when "s"
                cli.scores
                exit
            when "u"
                system "~/.zsd/update.sh"
                exit
            end
        }
    end

    %w[ TERM INT HUP ].each { |signal|
        trap(signal) {
            exit_game $player
        }
    }

    start cli
end

def start cli

    p_info("Type help for help.")

    loop {

        cli.spawn_zombie

        while cli.zombie.is_alive
            input = prompt(">").split
            command = input[0]

            if cli.commands.include? command
                cli.send(command, *input[1..-1])
            else
                p_warn("Unknown Command.") unless command.nil?
            end

        end # zombie has died if loop ends

    } # end game loop

end

if __FILE__ == $0
    main
end

require/colors.rb:

class String
    def black;          "\033[30m#{self}\033[0m" end
    def red;            "\033[31m#{self}\033[0m" end
    def green;          "\033[32m#{self}\033[0m" end
    def brown;          "\033[33m#{self}\033[0m" end
    def blue;           "\033[34m#{self}\033[0m" end
    def magenta;        "\033[35m#{self}\033[0m" end
    def cyan;           "\033[36m#{self}\033[0m" end
    def gray;           "\033[37m#{self}\033[0m" end
    def bg_black;       "\033[40m#{self}\033[0m" end
    def bg_red;         "\033[41m#{self}\033[0m" end
    def bg_green;       "\033[42m#{self}\033[0m" end
    def bg_brown;       "\033[43m#{self}\033[0m" end
    def bg_blue;        "\033[44m#{self}\033[0m" end
    def bg_magenta;     "\033[45m#{self}\033[0m" end
    def bg_cyan;        "\033[46m#{self}\033[0m" end
    def bg_gray;        "\033[47m#{self}\033[0m" end
    def bold;           "\033[1m#{self}\033[22m" end
    def reverse_color;  "\033[7m#{self}\033[27m" end
end

def p_info s
    puts s.magenta
end

def p_error s
    puts s.red
end

def p_pain s
    puts s.red
end

def p_level_up s
    puts s.cyan
end

def p_warn s
    puts s.brown # its yellow
end

require/other.rb:

require "readline"
require "yaml"
require "#{$rpath}/require/zombie"
require "#{$rpath}/require/colors"
require "#{$rpath}/require/combo"
require "#{$rpath}/require/player"

class Cli

    attr_accessor :player, :zombie, :commands

    def initialize 
        @player = Player.new
        @commands = %w[ kick punch combo combolist taunt info scores quit help heal easter ]
        @combos = { "kick punch" => KickPunch.new,
                   "trip stomp" => TripStomp.new,
                   "punch punch kick" => PunchPunchKick.new,
                   "Knee Punch Face Slap" => KneePunchFaceSlap.new,
                   "heal fury" => HealFury.new(@player),
                   "elbow fist knee fist knee body slam" => ElbowFistKneeFistKneeBodySlam.new,
                   "kick kick kick kick kick kick kick kick kick kick kick kick kick kick kick" => KickKickKickKickKickKickKickKickKickKickKickKickKickKickKick.new,
                   "combo of possible death" => ComboOfPossibleDeath.new,
                   "combo of death" => ComboOfDeath.new,
                   "coolest combo ever" => CoolestComboEver.new,
                   "chase punch of fire" => ChasePunchOfFire.new,
                   "addison kick of cold hard music" => AddisonKickOfColdHardMusic.new,
                   "not a combo" => NotACombo.new,
                   "pain with a side of blood" => PainWithASideOfBlood.new,
                   "the combo" => TheCombo.new,
                   "the 2nd combo" => The2ndCombo.new,
                   "ultimate destruction kick punch" => UltimateDestructionKickPunch.new,
                   "the 3rd combo" => The3rdCombo.new,
                   "pretty good combo" => PrettyGoodCombo.new,
                   "chuck norris stomp of mayhem" => ChuckNorrisStompOfMayhem.new
        }
    end

    def spawn_zombie
        zombies = [ Zombie,
                BigZombie,
                DaddyZombie,
                GunZombie,
                NinjaZombie,
                IdiotZombie,
                BlindZombie,
                StrongZombie,
                BasicallyDeadZombie,
                SuperZombie,
                BossZombie,
                UltimateZombie ]

        @zombie = zombies[@player.save[:wave] - 1].new
    end

    def attack damage
        @zombie.take_damage damage
        z_damage = @zombie.attack
        @player.take_damage z_damage if @zombie.is_alive
        p_pain("#{@player.phrases.rand_choice} #{@zombie.name}! -#{damage}")
        p_pain("#{@zombie.name} #{@zombie.phrases.rand_choice}! -#{z_damage}")
        @zombie.check_dead
        @player.check_dead
        @player.add_kill if !@zombie.is_alive
        @player.give_xp @zombie.xp if !@zombie.is_alive
    end

    def do_combo
        # keep combos in order of xp cost
        # and keep keys lowercase

        if @combos.has_key? c = prompt("Which combo? ")
            used_combo = @combos[c]
            if @player.save[:xp] >= used_combo.price
                damage = used_combo.use
                @player.give_xp -used_combo.price
                return true, damage
            else
                p_warn("You don't have enough xp loser.")
                return false
            end
        else 
            p_warn("That is not a combo.")
            return false
        end

    end

    # CLI METHODS BELOW

    def kick *args
        attack @player.kick
    end

    def punch *args
        attack @player.punch
    end

    def combo *args
        success, damage = do_combo
        attack damage if success
    end

    def combolist *args
        amount = @player.save[:rank]
        p_info "Unlocked Combos:" 
        combos = @combos.sort_by { |k, v| v.price }
        amount.times { |i|
            p_info "#{combos[i][1].name}: -#{combos[i][1].price} xp"
        }
    end

    def scores *args
        scores = YAML.load_file("#{$rpath}/scores.yml")
        p_info "High Scores:"
        scores.each { |s|
            p_info "#{s[1]}: #{s[0]}"
        }
    end

    def quit *args
        p_warn "Wanna save yo game? yes or no"
        answer = prompt
        while !(["yes", "y", "no", "n"].include? answer)
            p_warn "I didn't catch that. Yes or No?"
            answer = prompt
        end
        save_game = (answer == "yes" ? true : false)
        @player.save_game if save_game
        exit
    end

    def help *args
        p_info "Available commands:"
        puts(@commands.join " ")    
    end

    def taunt *args
        if @player.save[:taunts_available] > 0
            @player.taunt
        else
            p_warn "You have no more taunts."
        end
    end

    def heal *args
        amount = args[0].to_i
        if amount > 0
            @player.heal amount
        else
            p_warn "Please specify a number greater than 0. Example: heal 5"
        end
    end

    def info *args
        @player.info
        puts
        @zombie.info
    end

    def easter *args
        if args[0] == "egg"
            unless @player.save[:egg_used]
                xp = (-50..75).to_a.rand_choice
                @player.give_xp xp
                p_level_up "#{(xp >= 0 ? "+" : "-") + xp.abs.to_s} xp"
                @player.save[:egg_used] = true
            else
                p_warn "You used your easter egg this game you cheater :/"
            end
        else
            p_warn "Unknown Command."
        end
    end

end

class Array
    def rand_choice
        if RUBY_VERSION.to_f > 1.8
            self.sample
        else
            self[rand(self.length)]
        end
    end
end

def exit_game player
  Thread.new {
    player.save_game
    puts("^C")
    p_level_up "Game saved."
    exit
  }
end

def prompt _prompt="", newline=false
    _prompt += "\n" if newline
    inText = Readline.readline(_prompt, true).squeeze(" ").strip.downcase
    inText
end

require/combo.rb:

class Combo

    attr_accessor :name, :price

    def initialize player=nil
        @player = player
        self.setInfo
    end

    def use
        self.extra
        @damage.to_a.rand_choice
    end

    def extra

    end

end

# try to keep combos in order of xp cost
# also add new combos to combos variable in the start function in the zsd file
class KickPunch < Combo
    def setInfo
        @name = "Kick Punch"
        @price = 2
        @damage = (3..9)
    end
end

class TripStomp < Combo
    def setInfo
        @name = "Trip Stomp"
        @price = 3
        @damage = (4..10)
    end
end

class PunchPunchKick < Combo
    def setInfo
        @name = "Punch Punch Kick"
        @price = 4
        @damage = (4..12)
    end
end

class KneePunchFaceSlap < Combo
    def setInfo
        @name = "Knee Punch Face Slap"
        @price = 4
        @damage = (2..12)
    end
end

class HealFury < Combo
    def setInfo
        @name = "Heal Fury"
        @price = 5
        @damage = (5..10)
    end

    def extra
        health = -((3..7).to_a.rand_choice)
        @player.take_damage health
    end
end

class ElbowFistKneeFistKneeBodySlam < Combo
    def setInfo
        @name = "Elbow Fist Knee Fist Knee Body Slam"
        @price = 6
        @damage = (3..18)
    end
end

class KickKickKickKickKickKickKickKickKickKickKickKickKickKickKick < Combo
    def setInfo
        @name = "Kick Kick Kick Kick Kick Kick Kick Kick Kick Kick Kick Kick Kick Kick Kick"
        @price = 7
        @damage = (9..17)
    end
end

class ComboOfPossibleDeath < Combo
    def setInfo
        @name = "Combo of Possible Death"
        @price = 9
        @damage = (5..25)
    end
end

class ComboOfDeath < Combo
    def setInfo
        @name = "Combo of Death"
        @price = 12
        @damage = (14..30)
    end
end

class CoolestComboEver < Combo
    def setInfo
        @name = "Coolest Combo Ever"
        @price = 15
        @damage = (10..25)
    end
end

class ChasePunchOfFire < Combo
    def setInfo
        @name = "Chase Punch of Fire"
        @price = 20
        @damage = (20..40)
    end
end

class AddisonKickOfColdHardMusic < Combo
    def setInfo
        @name = "Addison Kick of Cold Hard Music"
        @price = 20
        @damage = (20..40)
    end
end

class NotACombo < Combo
    def setInfo
        @name = "Not A Combo"
        @price = 20
        @damage = (25..45)
    end
end

class PainWithASideOfBlood < Combo
    def setInfo
        @name = "Pain With a Side of Blood"
        @price = 25
        @damage = (35..50)
    end
end

class TheCombo < Combo
    def setInfo
        @name = "The Combo"
        @price = 25
        @damage = (1..100)
    end
end

class The2ndCombo < Combo
    def setInfo
        @name = "The 2nd Combo"
        @price = 30
        @damage = (20..100)
    end
end

class UltimateDestructionKickPunch < Combo
    def setInfo
        @name = "Ultimate Destruction Kick Punch"
        @price = 30
        @damage = (40..75)
    end
end

class The3rdCombo < Combo
    def setInfo
        @name = "The 3rd Combo"
        @price = 35
        @damage = (50..85)
    end
end

class PrettyGoodCombo < Combo
    def setInfo
        @name = "Pretty Good Combo"
        @price = 50
        @damage = (45..111)
    end
end

class ChuckNorrisStompOfMayhem < Combo
    def setInfo
        @name = "Chuck Norris Stomp of Mayhem"
        @price = 1000
        @damage = (1..2_000_000)
    end
end

require/player.rb:

class Player

    # try to put methods in alphebetical order, except init

    attr_accessor :save, :phrases

    def initialize
        @save = YAML.load_file("#{$rpath}/require/player.yml")
        @save_original = { :health => 25,
                           :xp => 15,
                           :rank => 1,
                           :wave => 1,
                           :zombies_killed => 0,
                           :total_kills => 0,
                           :kick_upgrade => 0,
                           :punch_upgrade => 0,
                           :taunts_available => 3,
                           :egg_used => false,
                           :new_game => true }
        if @save[:new_game]
            self.give_xp((@save[:rank] - 1) * 2)
            @save[:new_game] = false
        end

        @phrases = ["You smacked down the", "You hit the", "Whose your daddy", "You just powned the"]

    end

    def add_kill
        @save[:total_kills] += 1
        @save[:zombies_killed] += 1
        self.next_wave if @save[:zombies_killed] % 3 == 0
        self.rank_up if @save[:total_kills] % 15 == 0
    end

    def check_dead
        self.die if @save[:health] <= 0
    end

    def die
        self.reset
        p_warn "You died!!!"
        p_warn "You killed #{@save[:zombies_killed]} zombies."
        self.save_score
        exit
    end

    def give_xp amount
        @save[:xp] += amount
    end

    def heal amount
        if @save[:xp] >= amount
            @save[:health] += amount
            self.give_xp -amount
            p_level_up "+#{amount} health!"
        else
            p_warn "You do not have enough xp!"
        end
    end

    def info
        p_info "Health: #{@save[:health]}"
        p_info "XP: #{@save[:xp]}"
        p_info "Rank: #{@save[:rank]}"
        p_info "Wave: #{@save[:wave]}"
        p_info "Zombies Killed: #{@save[:zombies_killed]}"
        p_info "Total Kills: #{@save[:total_kills]}"
        p_info "Kick Upgrade: #{@save[:kick_upgrade]}"
        p_info "Punch Upgrade: #{@save[:punch_upgrade]}"
        p_info "Taunts Available: #{@save[:taunts_available]}"
    end

    def kick
        (3..7).to_a.rand_choice + @save[:kick_upgrade]
    end

    def next_wave
        @save[:wave] += 1
        xp = @save[:wave] + 2
        self.give_xp xp
        p_level_up "Wave #{@save[:wave]}, +#{xp} xp"
    end

    def punch
        (4..6).to_a.rand_choice + @save[:punch_upgrade]
    end

    def rank_up
        @save[:rank] += 1
        p_level_up "Rank Up! You are now rank #{@save[:rank]}. You unlocked a new combo."
        self.upgrade
    end

    def reset
        @save_original[:rank] = @save[:rank]
        @save_original[:total_kills] = @save[:total_kills]
        @save_original[:kick_upgrade] = @save[:kick_upgrade]
        @save_original[:punch_upgrade] = @save[:punch_upgrade]
        File.open("#{$rpath}/require/player.yml", 'w') { |out|
           YAML.dump(@save_original, out)
        }
    end

    def save_game
        File.open("#{$rpath}/require/player.yml", 'w') { |out|
           YAML.dump(@save, out)
        }
    end

    def save_score
        score = @save[:zombies_killed]
        scores = YAML.load_file("#{$rpath}/scores.yml")
        if score > scores.last[0]
            p_level_up "High Score! What is your name?"
            name = prompt
            scores = scores.push([score, name]).sort_by { |i| -i[0]}
            scores.pop
            File.open("#{$rpath}/scores.yml", "w") { |out| 
                YAML.dump(scores, out)
            }
        end
    end

    def take_damage damage
        @save[:health] -= damage
    end

    def taunt
        taunt = ["HEY ZOMBIE! UR FACE!", "DIRT BAG", "UR MOM", "POOP FACE", "GET OWNED BUDDY BOY", ":p", "EAT MY FIST", "be nice", "You stink", "YO MAMA"].rand_choice
        if @save[:xp] >= 2
            xp = (-12..12).to_a.rand_choice
            self.give_xp xp
            p_pain "#{taunt} #{(xp >= 0 ? "+" : "-")}#{xp.abs} xp"
            @save[:taunts_available] -= 1
        else
            p_warn "You are missing the necessary xp to taunt (2)"
        end
    end

    def upgrade
        max_level = 6
        if @save[:kick_upgrade] >= max_level && @save[:punch_upgrade] >= max_level
            return
        else
            p_level_up "What do you want to upgrade? (kick or punch)"
            skill = prompt
            while !(["kick", "punch"].include? skill)
                p_warn "Please answer with kick or punch. What would you like to upgrade?"
                skill = prompt
            end
            max_level_message = "#{skill} is at the max level (6)"
            plus_1 = p_level_up "#{skill} +1"
            case skill
            when "kick"
                if @save[:kick_upgrade] < max_level
                    @save[:kick_upgrade] += 1
                    p_level_up plus_1
                else 
                    p_warn max_level_message
                    upgrade
                end
            when "punch"
                if @save[:punch_upgrade] < max_level
                    @save[:punch_upgrade] += 1
                    p_level_up plus_1
                else 
                    p_warn max_level_message
                    upgrade
                end
            end
        end

    end

end

require/zombie.rb:

class Zombie 

    attr_accessor :is_alive, :name, :power, :xp, :phrases, :health

    def initialize
        @is_alive = true
        self.setInfo
    end

    def setInfo
        @name = "Zombie"
        @power = (4..6)
        @health = 10
        @xp = 2
        @phrases = ["hit ur face", "punched the heck out of you", "beat the heck out of you", "bruised ur face"]
    end

    def info
        _power = @power.to_a
        p_info "#{@name} health: #{@health}"
        p_info "Attack Strength: #{_power[0]} to #{_power[-1]}"
    end

    def attack
        @power.to_a.rand_choice
    end

    def take_damage amount
        @health -= amount
    end

    def check_dead
        self.die if @health <= 0
    end

    def die
        p_pain "KO! You killed the #{@name}"
        @is_alive = false
    end

end

# start extra zombies

class BigZombie < Zombie
    def setInfo
        @name = "Big Zombie"
        @power = (6..8)
        @health = 15
        @xp = 4
        @phrases = ["hit ur face", "punched the heck out of you", "beat the heck out of you", "bruised ur face"]
    end
end

class DaddyZombie < Zombie
    def setInfo
        @name = "Daddy Zombie"
        @power = (4..10)
        @health = 20
        @xp = 9
        @phrases = ["IS your daddy", "punched the heck out of you", "beat the heck out of you", "ain't your mom", "told you to go to bed"]
    end
end

class GunZombie < Zombie
    def setInfo
        @name = "Gun Zombie"
        @power = (3..15)
        @health = 20
        @xp = 15        
        @phrases = ["shot yo face", "shot the heck out of you", "beat the heck out of you", "made you eat bullets", "showed you his ak-47"]
    end
end

class NinjaZombie < Zombie
    def setInfo
        @name = "Ninja Zombie"
        @power = (7..20)
        @health = 20
        @xp = 18
        @phrases = ["was to ninja for you", "threw a ninja star at your face", "is a blur", "sent you flying", "has a black belt"]
    end
end

class IdiotZombie < Zombie
    def setInfo
        @name = "Idiot Zombie"
        @power = (7..20)
        @health = 2
        @xp = 5
        @phrases = ["is an idiot but still pwn-ed u", "fell down from stupidness but somehow landed on you", "beat ur face's face", "is somehow beating you"]
    end
end

class BlindZombie < Zombie
    def setInfo
        @name = "Blind Zombie"
        @power = (1..25)
        @health = 24
        @xp = 20
        @phrases = ["tried to hit you", "cant see ur face", "cant touch this", "cant see you but hurt you anyway"]
    end
end

class StrongZombie < Zombie
    def setInfo
        @name = "Strong Zombie"
        @power = (15..21)
        @health = 30
        @xp = 30
        @phrases = ["destroyed you", "may have murdered you", "is strong", "is VERY strong", "works out"]
    end
end

class BasicallyDeadZombie < Zombie
    def setInfo
        @name = "Basically Dead Zombie"
        @power = (50..75)
        @health = (2..5).to_a.rand_choice
        @xp = (2..5).to_a.rand_choice
        @phrases = ["totally pwn-ed you!", "hurt you pretty bad", "obliterated you", "probably killed you", "is not dead"]
    end
end

class SuperZombie < Zombie
    def setInfo
        @name = "Super Zombie"
        @power = (35..56)
        @health = 65
        @xp = 38
        @phrases = ["is up up and away!", "just chucked kryptonite at ur face", "has super strength", "is the ultimate super villain", "just mad you cry"]
    end
end

class BossZombie < Zombie
    def setInfo
        @name = "Boss Zombie"
        @power = (60..90)
        @health = (95..105).to_a.rand_choice
        @xp = (65..80).to_a.rand_choice
        @phrases = ["sent you to work!", "is not giving you a raise", "is a boss!", "just fired you", "just demoted you"]
    end
end

class UltimateZombie < Zombie
    def setInfo
        @name = "Ultimate Zombie"
        @power = (75..115)
        @health = 100
        @xp = 115
        @phrases = ["is to ultimate", "is really scary", "just gave you a nice punch to the face", "is more ultimate than you", "makes you look... un-ultimate"]
    end
end
\$\endgroup\$
  • \$\begingroup\$ To trap the CTRL-C interrupt signal, you need to rescue Interrupt. See this SO question for more info. \$\endgroup\$ – syb0rg Apr 12 '14 at 2:32
  • \$\begingroup\$ It used to work using signal trapping though. I don't see any changes in that part of the code so I don't know why it doesn't anymore. But I'll try that, thanks! \$\endgroup\$ – addison Apr 12 '14 at 2:39
5
\$\begingroup\$

I would like to start by saying that I've never worked with ruby 1.8.7, so I apologize in advance if some of my observations are not relevant for that version...

Require relative
You use $rpath where you assume that your app is installed somewhere, which is a very big assumption. Don't do that.
I know, require_relative is not supported on 1.8.7, but it appears to have a workaround. This way you can install your app anywhere, and it will still work.

Global methods
You use global methods to drive your printing. Global methods are 'magical', in the sense that a reader cannot be sure where they came from.
A better solution might be to put these methods in a module, maybe call it ColoredLogger, and include it in classes you want to use it in.

Patching classes
You are patching the String class. Patching a class should be done very rarely, as it may affect unaware third parties.
Your use of patching seems hardly warranted, as you add for an internal single use in the same place you define it.
Simply define the color table anywhere else:

Colors = {
black: { prefix: 30, suffix: 0 },
red: { prefix: 31, suffix: 0 },
green: { prefix: 32, suffix: 0 },
brown: { prefix: 33, suffix: 0 },
blue: { prefix: 34, suffix: 0 },
magenta: { prefix: 35, suffix: 0 },
cyan: { prefix: 36, suffix: 0 },
gray: { prefix: 37, suffix: 0 },
bg_black: { prefix: 40, suffix: 0 },
bg_red: { prefix: 41, suffix: 0 },
bg_green: { prefix: 42, suffix: 0 },
bg_brown: { prefix: 43, suffix: 0 },
bg_blue: { prefix: 44, suffix: 0 },
bg_magenta: { prefix: 45, suffix: 0 },
bg_cyan: { prefix: 46, suffix: 0 },
bg_gray: { prefix: 47, suffix: 0 },
bold: { prefix: 1, suffix: 22 },
reverse_color: { prefix: 7, suffix: 27 }
}

def colorize(color, string)
  "\033[#{Colors[color][:prefix]}m#{string}\033[#{Colors[color][:suffix]}m
end

or even simply do it hard coded in your methods.

Your Array patch seems much more relevant than the String patch.

Variables vs. constants
Your CLI class's initializer populates combos and commands with hard coded data which never changes. They should be declared as constants:

class CLI
  COMMANDS = %w[ kick punch combo combolist taunt info scores quit help heal easter ]
  COMBOS = { "kick punch" => KickPunch.new,
    # ..
  }

What are classes for?
Your Combo classes and Zombie classes are not really classes, but configuration - they do not add any functionality, and therefore should not be classes.
You can use JSON configuration files, where you can keep the names and vitals of each flavor you need, and Combo and Zombie instances may simply refer to a flavor:

{ "KickPunch": { "name": "Kick Punch",
    "price": 2,
    "min_damage": 3,
    "max_damage": 9},
  "TripStomp": { ... },
  ....
}

State once-removed
You save your player's state in a @save hash inside your instance. This is not very object oriented, and doesn't seem to be warranted - facilitating a quick save is not a good enough reason. When you save, simply collect the relevant data.
It is also unclear what happens if the saved file does not exist when the game starts.

What does reset do?
Give clear names to your methods and members. What does @save_original hold? Why does reset change it? Why is it called when a player dies?
You have two different methods called attack which seem to do completely different stuff - one is a getter, while the other manages the battle. You might want to reconsider their names.

Model View Controller
Your design is not very MVC - the model prompts the user, and gives the user feedback, etc. Make an effort to separate the game logic from user interactions, it would make adapting your game to different UIs a lot easier, or should I say - possible.

When to use *args?
You use *args quite liberally in your Cli class. *args are used when you want to have a dynamic number of arguments. It is not a solution for a catch-all API. You want to tell the user of your API that he uses your API with too many arguments, rather than swallow it.

Cli.combo "some", ["garbage", "that"], :should_not, Be, here

I don't think that it is in the scope of CR to hunt down camelCase methods in your code, so I'm not going to do it (setInfo)

\$\endgroup\$
  • \$\begingroup\$ Thanks! Ya sorry I'm wasn't asking to find camel case methods, I just thought if anyone happened to notice one then I'd love to know. \$\endgroup\$ – addison Apr 12 '14 at 13:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.