*RUBY* Extreme beginner stuck [closed] - ruby

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I can't figure out the guidance for this text-based RPG. I want the player's input to choose between one of four classes, then save that class and assign stats to it. For now, it only works if I choose "Warrior". What am I doing wrong?
stats = Hash.new
stats["Strength"] = 10
stats["Dexterity"] = 10
stats["Charisma"] = 10
stats["Stamina"] = 10
puts "Hello, brave adventurer. What is your name?"
player_name = gets.chomp.capitalize
puts "Well, #{player_name}, you are certainly brave! Choose your profession. (Choose from Warrior, Wizard, Archer, or Thief)."
player_class = gets.chomp.downcase
while player_class != ("warrior" || "thief" || "archer" || "wizard")
puts "I do not recognize #{player_class} as a valid class. Please choose between Warrior, Wizard, Archer, or Thief."
player_class = gets.chomp.downcase
end
if player_class == "warrior"
puts "Yay a warrior!"
stats["Strength"] = 20
elsif player_class == "thief"
puts "yay a thief!"
stats["Dexterity"] = 20
elsif player_class == "archer"
puts "yay an archer!"
elsif player_class == "wizard"
puts "Sweet a wizard!"
end

It's very simple.
1.9.3p194 :001 > ("warrior" || "thief" || "archer" || "wizard")
=> "warrior"
The logical OR of several strings evaluates to the first one.
You could replace that line by something like:
while player_class != "warrior" and player_class != "thief" and player_class != "archer" and player_class != "wizard"

Try setting up the classes as an array...
player_classes = ["warrior", "thief", "archer", "wizard"]
And then when you want to check if the player has entered a valid class...
while ! player_classes.include? player_class
instead.
You can use an even nicer idiom for single words...
%w(warrior thief archer wizard)
Generates
["warrior", "thief", "archer", "wizard"]
Moving forward
You could take this approach a step forward by putting the player classes into a hash.
For example:
player_classes = {
'warrior' => {:message => "Yay a warrior!", :stats => {:strength => 20} },
'thief' => {:message => "Ooh a thief!", :stats => {:dexterity => 20} },
'archer' => {:message => "Cool! an archer" },
'wizard' => {:message => "Sweet! a wizard" }
}
You can then do things like this:
while ! player_classes.key? player_class
Once you've got a match you can then pull the values out of the hash, like this:
selected_class = player_classes[player_class]
stats.merge selected_class[:stats] if selected_class[:stats]
If there's no stats in the hash for that player class nothing will happen, if there is, it'll be merged in.
e.g. to test this...
selected_class = player_classes['warrior']
stats.merge selected_class[:stats] if selected_class[:stats]
# stats is now {:strength=>20, :dexterity=>10, :charisma=>10, :stamina=>10}
selected_class = player_classes['wizard']
stats.merge selected_class[:stats] if selected_class[:stats]
# stats is now {:strength=>10, :dexterity=>10, :charisma=>10, :stamina=>10}
We can then show the message with:
puts player_classes[player_class][:message]
This would reduce your logic down to capturing the player class input and then processing the hash.
Revisting your original code
Using a hash to act as a simple data-model.
You'd end up with code like this:
#!/usr/bin/env ruby
stats = { :strength => 10, :dexterity => 10, :charisma => 10, :stamina => 10 }
player_classes = {
'warrior' => {:message => "Yay a warrior!", :stats => {:strength => 20} },
'thief' => {:message => "Ooh a thief!", :stats => {:dexterity => 20} },
'archer' => {:message => "Cool! an archer" },
'wizard' => {:message => "Sweet! a wizard" }
}
puts "Welcome brave adventurer, what is your name?"
player_name = gets.chomp.capitalize
puts "Well, #{player_name}, you are certainly brave! Choose your profession. (Choose from Warrior, Wizard, Archer, or Thief)."
player_class = gets.chomp.downcase
while ! player_classes.key? player_class
puts "I do not recognize #{player_class} as a valid class. Please choose between Warrior, Wizard, Archer, or Thief."
player_class = gets.chomp.downcase
end
selected_class = player_classes[player_class]
stats.merge selected_class[:stats] if selected_class[:stats]
puts selected_class[:message]
You should also find this more readable, however, as you extend your game, you'll find that you can't easily work with code like this. You should next learn about using functions to break up your code into different routines. There are also more things you can do with arrays, hashes and collections.
Also, as soon as possible, you should start learning about programming Ruby in an Object Oriented style, which is how it should be used, ideally.
Tutorials Point is a pretty decent site for learning more about Ruby

For what you did wrong, see Diego's answer.
This is a typical case where you should use the case statement. You can cut out the routine to get the player class like this:
public
def get_player_class
case player_class = gets.chomp.downcase
when "warrior" then puts "Yay a warrior!"; stats["Strength"] = 20
when "thief" then puts "yay a thief!"; stats["Dexterity"] = 20
when "archer" then puts "yay an archer!"; true
when "wizard" then puts "Sweet a wizard!"; true
else puts "I do not recognize #{player_class} as a valid class. "\
"Please choose between Warrior, Wizard, Archer, or Thief."; false
end
end
stats = {"Strength" => 10, "Dexterity" => 10, "Charisma" => 10, "Stamina" => 10,}
puts "Hello, brave adventurer. What is your name?"
player_name = gets.chomp.capitalize
puts "Well, #{player_name}, you are certainly brave! "\
"Choose your profession. (Choose from Warrior, Wizard, Archer, or Thief)."
nil until get_player_class

Related

How to read/write a hash from a file in ruby? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I want to store data of countries in a file and read that data. I tried to write it in filename.yml file but I am not able to get each hash separately to perform specific operation. Have a look what I tried
require 'yaml'
def addData
puts 'Enter country name :'
name = gets.chop.to_s
puts 'Enter population in billion :'
population = gets.chop.to_s
puts 'Enter GDP in lakh crore USD:'
gdp = gets.chop.to_s
puts 'Enter army strength:'
army_strength = gets.chop.to_s
puts 'Enter state of country:'
state = gets.chop.to_s
country = Hash.new
country = { "name" => name , "population" => population , "gdp" => gdp , "army_strength" => army_strength , "state" => state }
arr_country.push(country)
# Writing to the file
File.open('filename.yml','a') { |f| YAML.dump(country, f) }
end
def readData
File.open('filename.yml') {
|f|.each {
arr = YAML.load(f)
if arr
puts arr
puts arr.class
end
}
}
end
while true
puts '1. Write','2. Read','3.Exit'
opt = gets.chop.to_i
if (opt>2)
return
end
case opt
when 1
addData
when 2
readData
end
end
But it's not working, what would be the best way ?
Apart from the minor mistakes, that are mentioned above, you might have most success using the load_stream function from YAML, rather than load
def readData
File.open('filename.yml') do |f|
arr = YAML.load_stream(f)
if arr
puts arr
puts arr.class
end
end
end
While this is not how I would do it, it will allow you to append to the file, from any source, as long as it remains compatible with YAML.
My preference would be to use a database.
the resulting full code, on my system (run through rubocop) is:
require 'yaml'
def add_data
puts 'Enter country name :'
name = gets.chop.to_s
puts 'Enter population in billion :'
population = gets.chop.to_s
puts 'Enter GDP in lakh crore USD:'
gdp = gets.chop.to_s
puts 'Enter army strength:'
army_strength = gets.chop.to_s
puts 'Enter state of country:'
state = gets.chop.to_s
country = {}
country = { 'name' => name, 'population' => population, 'gdp' => gdp, 'army_strength' => army_strength, 'state' => state }
# Writing to the file
File.open('filename.yml', 'a') { |f| YAML.dump(country, f) }
end
def read_data
arr = []
File.open('filename.yml') do |f|
arr = YAML.load_stream(f)
end
return unless arr
puts arr
puts arr.class
end
loop do
puts '1. Write', '2. Read', '3.Exit'
opt = gets.chop.to_i
break if opt > 2
case opt
when 1
add_data
when 2
read_data
end
end
The f.each does not make sense. f is the file object, and each processes each line and tries to interpret each line as a separate YAML expression. However, your whole file consists of a single YAML expression.
You could do something like:
country = YAML.load(File.read('filename.yaml'))

how to use .include? in Ruby with a hash statement

How do I get the .include? to work? When the user chooses a character, I want the console to print the puts ok statement and then go to the if statement.
name = {"1" => "Mario",
"2" => "Luigi",
"3" => "Kirby",
}
puts "Peach's apocalypse, will you survive?"
def character (prompt, options)
puts = "who will you be?"
options = name[1] || name[2] || name[3]
character = gets.chomp.downcase
until character.include? name
end
puts "ok #{name} all three of you run out of peach's castle which has been overrun"
if character = name[1] || name[2] || name[3]
puts ("zombies are in the castle grounds, there are weapons over the bridge")
puts "What do you do, charge through or sneak?"
x = gets.chomp.downcase
if x == "sneak"
puts "oh you died"
if x == "charge through"
puts "the zombies tumbled over the bridge's edge, you made it safe and sound"
else
puts "you did nothing and were eaten alive by Princess Peach"
end
end
end
end
It looks like you're calling include? on a string. This will only return true if you pass it a substring of itself. For example:
"Mario".include?("Mar") #=> true
You want to call include? on the array of keys in the name hash. You could do:
name.values.include?(character)
or more concisely
name.has_value?(character)
Here's some documentation on the include? method of the Array class and the include? method of the string class, as well as the has_value? method of the Hash class.
There's considerably more that needs modifying for this program to run as you're expecting it to though. Here's one working implementation:
puts "Peach's apocalypse, will you survive?"
names = {
"1" => "Mario",
"2" => "Luigi",
"3" => "Kirby"
}
def choose_character(character = "", options)
puts = "who will you be?"
options.each do |num, name|
puts "#{num}: #{name}"
end
until options.has_key? character or options.has_value? character
character = gets.chomp.capitalize
end
return options[character] || character
end
name = choose_character(names)
puts "ok #{name} all three of you run out of peach's castle which has been overrun"
puts "zombies are in the castle grounds, there are weapons over the bridge"
puts "What do you do, charge through or sneak?"
case gets.chomp.downcase
when "sneak"
puts "oh you died"
when "charge through"
puts "the zombies tumbled over the bridge's edge, you made it safe and sound"
else
puts "you did nothing and were eaten alive by Princess Peach"
end
The answer above is great and features awesome refactoring, but I would use
character = gets.strip.downcase
instead as it also gets rid of any potential whitespace.
To elaborate on the string thing, 'gets' stands for 'get string' (or at least so I was taught), so everything you get via 'gets' will be a string until you convert it further. Consider this:
2.2.1 :001 > puts "put in your input"
put in your input
=> nil
2.2.1 :002 > input = gets.strip
5
=> "5"
2.2.1 :003 > input.class
=> String
You would have to use .to_i to convert your input back to integer.

In Ruby, can I hand off variables from within a method without making new global variables?

I'm doing a game creation exercise from Learn Ruby the Hard Way. It's themed after Destiny since that's what I've got on the brain at the moment.
I wanted to have the player pick a character class, and then have that choice hand off some numbers as stats to be checked later in play. Below is the version that actually worked, but it involves creating a several global variables, which I keep reading is not a "best practice" in Ruby.
My question is, is there a way to do what I've got the code below doing without creating all of these global variables, or am I doing what needs to be done?
$might = 1
$agility = 1
$intellect = 1
def start
puts "Make all decisions by pressing the corresponding number."
puts "Choose your class:"
puts "1. Titan"
puts "2. Hunter"
puts "3. Warlock"
print "> "
choice = $stdin.gets.chomp
if choice == "1"
$might = 3
puts "You've chosen Titan!"
elsif choice == "2"
$agility = 3
puts "You've chosen Hunter!"
elsif choice == "3"
$intellect = 3
puts "You've chosen Warlock!"
else
puts "Try again."
start
end
end
puts start
puts "Might: #{$might}"
puts "Agility: #{$agility}"
puts "Intellect: #{$intellect}"
You can create a class and use instance variables:
class Game
def initialize
#might = 1
#agility = 1
#intellect = 1
end
attr_reader :might
attr_reader :agility
attr_reader :intellect
def start
puts "Make all decisions by pressing the corresponding number."
puts "Choose your class:"
puts "1. Titan"
puts "2. Hunter"
puts "3. Warlock"
print "> "
choice = $stdin.gets.chomp
case choice
when "1"
#might = 3
puts "You've chosen Titan!"
when "2"
#agility = 3
puts "You've chosen Hunter!"
when "3"
#intellect = 3
puts "You've chosen Warlock!"
else
puts "Try again."
start
end
end
end
game = Game.new
game.start
puts "Might: #{game.might}"
puts "Agility: #{game.agility}"
puts "Intellect: #{game.intellect}"
Remark:
I replaced your if-sequence with a case-statement
attr_reader defined the attribute accessor for the instance variables.
Maybe something like this. I prepare it for updates.
class Character
attr_accessor :might, :agility, :intelect, :type
def initialize(type, might = 1, agility = 1, intelect = 1)
#type = type
#might, #agility, #intelect = might, agility, intelect
end
def print_attributes
puts "Type: #{#type}"
puts "Might: #{#might}"
puts "Agility: #{#agility}"
puts "Intelect: #{#intelect}"
end
end
class Player
attr_reader :character
def initialize(character)
#character = character
end
end
class Game
CHARACTER_CLASSES = [
{:type => "Titan", :might => 3, :agility => 1, :intelect => 1},
{:type => "Hunter", :might => 1, :agility => 3, :intelect => 1},
{:type => "Warlock", :might => 1, :agility => 1, :intelect => 3}
]
attr_reader :player
def initialize
#player = nil
end
def start
puts "Make all decisions by pressing the corresponding number."
repeat = true
while repeat
puts "Choose your class:"
CHARACTER_CLASSES.each_with_index do |char_config, i|
puts "#{i+1}. #{char_config[:type]}"
end
choice = gets.chomp
choice_i = choice.to_i
unless choice_i == 0
if char_data = CHARACTER_CLASSES[choice_i - 1]
#player = Player.new( Character.new(char_data[:type], char_data[:might], char_data[:agility], char_data[:intelect]) )
repeat = false
end
end
end
end
end
game = Game.new
game.start
game.player.character.print_attributes
One option could be returning whatever you want from a method:
def start
puts "Make all decisions by pressing the corresponding number."
puts "Choose your class:"
puts "1. Titan"
puts "2. Hunter"
puts "3. Warlock"
print "> "
choice = $stdin.gets.chomp
case choice
when '1'
puts "You've chosen Titan!"
{agility: 1, might: 3, intellect: 1}
when '2'
puts "You've chosen Hunter!"
{agility: 3, might: 1, intellect: 1}
when '3'
puts "You've chosen Warlock!"
{agility: 1, might: 1, intellect: 3}
else
puts "Try again."
start
end
end
hero = start
puts "Might: #{hero[:might]}"
puts "Agility: #{hero[:agility]}"
puts "Intellect: #{hero[:intellect]}"

How to store and retrieve values using Ruby?

I am trying to build a "train game" based loosely on the old video game "Drug Wars." I am currently working my way through LRTHW, and I believe that I should be using OOP, but I'm not to that lesson yet.
The premise is that you have a set number of cars on your train and you can see what products are for sale in other cities (no limit on the amount you can buy or sale presuming you can fit them in your train). This code isn't complete, but I'm wondering if I'm even approaching this half way sanely in regard to creating and accessing the product prices in a reasonable manner.
#Initializing variables. Current_location should be changed to random
#in the future.
current_location = 'omaha'
train = []
new_york = []
chicago = []
omaha = []
dallas = []
seattle = []
def prompt()
print "> "
end
#Here is the selection menu. It is possible to exploit this and
#buy, sell and move all within the same turn.
#There needs to be a "safe selection" so that once you have moved you
#can't move again, but you can get info, buy and sell
#as many times as you would like.
def selection()
puts "Do you want to travel, buy, sell or get info?"
prompt; selection = gets.chomp
if selection.include? "travel"
puts "Where would you like to travel?"
prompt; city = gets.chomp
return 'city', city
elsif selection.include? "buy"
puts "Current Prices Are:"
puts "What would you like to Buy?"
elsif selection.include? "sell"
puts "Current Prices Are:"
puts "What would you like to sell?"
elsif selection.include? "info"
puts "What city or train would you like info on?"
else
puts "Would you like to exit selection or start selection again?"
end
end
#This generates a new cost for each good at the start of each turn.
def generate_costs(new_york, chicago, omaha, dallas, seattle)
new_york[0] = rand(10)
new_york[1] = rand(10) + 25
new_york[2] = rand(5) + 10
omaha[0] = rand(10)
omaha[1] = rand(10) + 25
omaha[2] = rand(5) + 10
chicago[0] = rand(25) + 5
chicago[1] = rand(5) + 10
chicago[2] = rand(4)
dallas[0] = rand(6) + 11
dallas[1] = rand(3) + 10
dallas[2] = rand(8)
seattle[0] = rand(6)
seattle[1] = rand(10) + 24
seattle[2] = rand(14) + 13
return new_york, chicago, omaha, dallas, seattle
end
# This is my main() loop. It drives the game forward.
for i in (0..5)
new_york, chicago, omaha, dallas, seattle = generate_costs(new_york, chicago, omaha, dallas, seattle)
turns = 5 - i
puts "You are currently in #{current_location}. You have #{turns} remaining."
puts "{ ___________________________ }"
#Code Here evaluates and accesses pricing based on current_location.
#Is this the correct way to do this?
fish = eval("#{current_location}[0]")
coal = eval("#{current_location}[1]")
cattle = eval("#{current_location}[2]")
puts "Fish is worth #{fish}"
puts "Coal is worth #{coal}"
puts "Cattle is worth #{cattle}"
puts "{ ___________________________ }"
change, value = selection()
if change == 'city'
current_location = value
elsif change == 'buy'
puts 'So you want to buy?'
else
puts "I don't understand what you want to do"
end
end
eval is a nasty way of accessing data ( When is `eval` in Ruby justified? ). You should consider moving things into an object.
I have improved the code slightly, storing the cities in a hash, which gets rid of the evals. I have stubbed out the generate_costs logic but you can assign it by doing:
cities[:new_york][0] = rand(10)
Ideally, the code should be re-written in an object-oriented syntax. If I get some time then I'll knock up an example for you.
Here is the code:
#Initializing variables. Current_location should be changed to random
#in the future.
current_location = :omaha
train = []
cities = {
:new_york => [],
:chicago => [],
:omaha => [],
:dallas => [],
:seattle => []
}
def prompt()
print "> "
end
#Here is the selection menu. It is possible to exploit this and
#buy, sell and move all within the same turn.
#There needs to be a "safe selection" so that once you have moved you
#can't move again, but you can get info, buy and sell
#as many times as you would like.
def selection()
puts "Do you want to travel, buy, sell or get info?"
prompt; selection = gets.chomp
if selection.include? "travel"
puts "Where would you like to travel?"
prompt; city = gets.chomp
return 'city', city
elsif selection.include? "buy"
puts "Current Prices Are:"
puts "What would you like to Buy?"
elsif selection.include? "sell"
puts "Current Prices Are:"
puts "What would you like to sell?"
elsif selection.include? "info"
puts "What city or train would you like info on?"
else
puts "Would you like to exit selection or start selection again?"
end
end
#This generates a new cost for each good at the start of each turn.
def generate_costs(cities)
cities.each do |key,city|
0.upto(2) do |i|
city[i] = rand(10)
end
end
end
# This is my main() loop. It drives the game forward.
for i in (0..5)
generate_costs(cities)
turns = 5 - i
puts "You are currently in #{current_location}. You have #{turns} remaining."
p cities
puts "{ ___________________________ }"
fish = cities[current_location][0]
coal = cities[current_location][1]
cattle = cities[current_location][2]
puts "Fish is worth #{fish}"
puts "Coal is worth #{coal}"
puts "Cattle is worth #{cattle}"
puts "{ ___________________________ }"
change, value = selection()
if change == 'city'
current_location = value
elsif change == 'buy'
puts 'So you want to buy?'
else
puts "I don't understand what you want to do"
end
end

Search box with Ruby Shoes

I have a short script that uses regular expressions to search a file for a specific phrase that a user types in. Basically, it's a simple search box.
I'm now trying to make this search box have a GUI, so that users are able to type into a box, and have their matches 'alerted' to them.
I'm new to using ruby shoes in any great detail, and have been using the examples on TheShoeBox website.
Can anyone point out where I'm going wrong with my code?
Here is my command line version that works:
string = File.read('db.txt')
puts "Enter what you're looking for below"
begin
while(true)
break if string.empty?
print "Search> "; STDOUT.flush; phrase = gets.chop
break if phrase.empty?
names = string.split(/\n/)
matches = names.select { |name| name[/#{phrase}/i] }
puts "\n \n"
puts matches
puts "\n \n"
end
end
Here is my attempt at using it within Ruby Shoes:
Shoes.app :title => "Search v0.1", :width => 300, :height => 150 do
string = File.read('db.txt')
names = string.split(/\n/)
matches = names.select { |name| name[/#{phrase}/i] }
def search(text)
text.tr! "A-Za-z", "N-ZA-Mn-za-m"
end
#usage = <<USAGE
Search - This will search for the inputted text within the database
USAGE
stack :margin => 10 do
para #usage
#input = edit_box :width => 200
end
flow :margin => 10 do
button('Search') { #output.matches }
end
stack(:margin => 0) { #output = para }
end
Many thanks
Well, for starters, the first code bit can be neatened up.
file = File.open 'db.txt', 'rb'
puts "Enter (regex) search term or quit:"
exit 1 unless file.size > 0
loop do
puts
print "query> "
redo if ( query = gets.chomp ).empty?
exit 0 if query == "quit"
file.each_line do |line|
puts "#{file.lineno}: #{line}" if line =~ /#{query}/i
end
file.rewind
end
The rb option lets it work as expected in Windows (especially with Shoes, you should try and be platform-independent). chomp strips off \r\n and \n but not a for example, while chop just blindly takes off the last character. loop do end is nicer than while true. Also why store matches in a variable? Just read through the file line by line (which allows for CRLF endings) as opposed to splitting by \n although the residual \r wouldn't really pose much of a problem...
As for the Shoes bit:
Shoes.app :title => "Search v0.2", :width => 500, :height => 600 do
#file = File.open 'db.txt', 'rb'
def search( file, query )
file.rewind
file.select {|line| line =~ /#{query}/i }.map {|match| match.chomp }
end
stack :margin => 10 do
#input = edit_line :width => 400
button "search" do
matches = search( #file, #input.text )
#output.clear
#output.append do
matches.empty? ?
title( "Nothing found :(" ) :
title( "Results\n" )
end
matches.each do |match|
#output.append { para match }
end
end
#output = stack { title "Search for something." }
end
end
You never defined #output.matches or called your search() method. See if it makes sense now.

Resources