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 [email protected]_alive
@player.give_xp @zombie.xp if [email protected]_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
rescue Interrupt
. See this SO question for more info. \$\endgroup\$