Ruby RPG: undefined method for creature traits - ruby

Ok, so I've written a few simple RPGs before in other languages (namely python), so I decided to try writing one in ruby. So, this morning, I sat down and typed up the basics of combat mechanics, made a few creatures, and started on the story. However, for some reason, when I try to run the program I get this error:
herosquest.rb:67: undefined method 'traits' for Creature::Hero:Class (NoMethodError)
I looked back at the code (which I'll provide in full at the end) and found the definition in lines 26 to 55, exactly where it should be.
class Creature
# Creature attributes are used to determine the outcome of combat.
traits :life, :strength, :charisma, :weapon
attr_accessor :life, :strength, :charisma, :weapon
#New class methods for hp, str, char, wep
def self.life( val )
#traits ||= {}
#traits['life'] = val
end
def self.strength( val )
#traits ||= {}
#traits['strength'] = val
end
def self.charisma( val )
#traits ||= {}
#traits['charisma'] = val
end
def self.weapon( val )
#traits ||= {}
#traits['weapon'] = val
end
#initialize sets defaults for each attr
def initialize
self.class.traits.each do |k,v|
instance_variable_set("##{k}", v)
end
end
end
Why isn't it working?
Here's the rest of the code, if that helps.
Note: When I copied my code, some of the indents got screwed up. It's much neater than it looks.
#CREATURE MECHANICS
#Create critters. Make them do stuff.
class Creature
def self.metaclass; class << self; self; end; end
def self.traits( *arr )
return #traits if arr.empty?
attr_accessor( *arr )
arr.each do |a|
metaclass.instance_eval do
define_method( a ) do |val|
#traits ||= {}
#traits[a] = val
end
end
end
class_eval do
define_method( :initialize ) do
self.class.traits.each do |k,v|
instance_variable_set("##{k}", v)
end
end
end
end
class Creature
# Creature attributes are used to determine the outcome of combat.
traits :life, :strength, :charisma, :weapon
attr_accessor :life, :strength, :charisma, :weapon
#New class methods for hp, str, char, wep
def self.life( val )
#traits ||= {}
#traits['life'] = val
end
def self.strength( val )
#traits ||= {}
#traits['strength'] = val
end
def self.charisma( val )
#traits ||= {}
#traits['charisma'] = val
end
def self.weapon( val )
#traits ||= {}
#traits['weapon'] = val
end
#initialize sets defaults for each attr
def initialize
self.class.traits.each do |k,v|
instance_variable_set("##{k}", v)
end
end
end
#CREATURE TYPES
#Hero
#Our brave Hero! Chosen by the gods! Mighty slayer
#of beasts! Also, broke. Which is why he's hunting
#a dragon. Poor, foolish mortal...
class Hero < Creature
traits :bombs, :arrows, :money
life 10
strength 4
charisma 4
weapon 4
bombs rand(3..10)
arrows rand(15..100)
money rand(0..25) #Should be used with Traders for buying stuff
#Bow. Needs arrows. Duh. Hero should start with, say, 50?
def ^( enemy )
if #arrows.zero?
puts "[Twang. Your imaginary arrow, alas, fails to slay the creature. Or have any effect at all. Moron.]"
return
end
#arrows -= 1
fight( enemy, rand(0+((charisma+(0..4))*2))) #Zero means you missed
end
end
#The hero's sword, given to him by the gods.
#Unlimited uses. Also, shiny.
def /( enemy )
fight( enemy, rand(4+weapon+(strength*2)))
end
#Trading. Ah, commerce! Doesn't quite work yet.
#Need a way to make sure money stays positive,
#and some sort of way to make sure you can't
#cheat the poor trader out of his goods.
def trade( enemy )
if #money.zero?
puts "[ The trader ignores you. You need money.]"
return
else puts "[What do you want to buy?\n Arrows = 10 for 2 coins\n Bombs = 5 for 5 coins]" #Should add new weapons someday
purchase = gets.chomp
if purchase == "Arrows"
puts "[You buy 10 arrows for 2 coins]"
#arrows += 10
#money -= 2
elsif purchase == "Bombs"
puts "[You buy 5 bombs for 5 coins]"
#bombs += 5
#money -= 5
end
end
#Mmm, tasty lembas. Elvish waybread. One small
#bite is enough to fill the stomach of a grown man.
def %( enemy )
lembas = rand( charisma )
puts "[Tasty lembas gives you #{ lembas } life.]"
#life += lembas
fight( enemy, 0 )
end
#Bombs. They explode. Hopefully, far away from you.
def *( enemy )
if #bombs.zero?
puts "[You light your finger on fire instead of the fuze. Then, you realize you're out of bombs.]"
#life -= 1
return
end
#bombs -= 1
fight( enemy, rand(weapon+(2..25)))
end
end
#Man
#Doesn't do much. Stands around and says boring things. Well, someday, anyway...
class Man < Creature
traits :chat #Will eventually say random stuff about the weather or whatever.
life 4
strength 2
charisma 4
weapon 1
end
#Woman
#Like Man. Except she smells nicer.
#Oh, and she's got a frying pan...
#Looks fierce.
class Woman < Creature
life 4
strength 2
charisma 8
weapon 4
end
#Trader
#Hangs around taverns and pushes
#useless stuff on passers-by.
class Trader < Creature
traits :bombs, :arrows, :money
life 4
strength 4
charisma 16
weapon 10 #Armed and dangerous. Sort of...
bombs rand(0..100)
arrows rand(0..500)
money rand(100..2500)
end
#Monkey
#Pain in the butt. Travels in packs. Makes
#faces and throws bananas.
class Monkey < Creature
life 8
strength 5
charisma 11
weapon 2 #Beats you with its cute little fists.
end
#Cow
#Cows won't attack you unless you hit them, or try to ride them,
#or make fun of them for chewing cud.
#Also, they don't like it when people stare
#at their udders. It makes them uncomfortable.
class Cow < Creature
life 18
strength 15
charisma 2 #It's a cow, not a rocket scientist
weapon 3
end
#Deer
#The deer is not as timid as it appears. Serves you right
#for picking on a poor, "helpless" creature. Jerk.
class Deer < Creature
life 30
strength 22
charisma 5
weapon 15
end
#Biker
#Found on paths. Rides a bike.
#Not *super* tough, but should be
#strong enough to make you fear
#for your life.
class Biker < Creature
life 34
strength 10
charisma 4 #They aren't very bright...
weapon 8
end
#Angel
#Heavenly host. "Information: Kill"
#Seriously, don't screw around with these guys.
class Angel < Creature
life 85
strength 8
charisma 60 #Seriously smart, since it's
weapon 25 #secretly a robot.
end
#Tentacle
#Found in lakes by throwing things ala Merry and Pippin.
#NOTE: Should include this in the first scene with a lake:
#"I am afraid of the pool. Don't disturb it!"
#Should preferably be said by a smallish man, muttering about
#"My precious," Mordor, and someone called Sam.
class Tentacle < Creature
life 9
strength 3 #Not strong, but there's a lot of them.
charisma 4
weapon 8
end
#Watcher in the Water
#Found after killing a bunch of tentacles.
#Should be tough enough to scare you off,
#but weak enough that you won't die
#instantly.
class Watcher_in_the_Water < Creature
life 250
strength 10
charisma 35
weapon 4 #I don't want him to be too powerfull
end
#Dragon
#Will totally kill you to death. A lot.
#Final boss type thing. Surrounded
#by his hoard of glittering treasure.
#Oooh, shiny...
class Dragon < Creature
life 500 # tough scales
strength 95 # bristling veins
charisma 65 # toothy smile
weapon 157 # fire breath
end
#COMBAT MECHANICS
#Hitting in combat
def hit( damage )
p_up = rand( charisma )
if p_up % 9 == 7
#life += p_up / 4
puts "[#{ self.class } magic powers up #{ p_up }!]"
end
#life -= damage
puts "[#{ self.class } has died.]" if #life <= 0
end
#Turn in combat
def fight( enemy, weapon )
if life <= 0
puts "[#{ self.class } is dead, Jim. You can't fight!]"
return
end
#You attack
your_hit = rand( strength + weapon )
puts "[Your #{weapon} hit the #{enemy} for #{ your_hit } points of damage!]"
enemy.hit( your_hit )
#Enemy fights back
p enemy
if enemy.life > 0
enemy_hit = rand( enemy.strength + enemy.weapon )
puts "[The #{enemy} hit with #{ enemy_hit } points of damage!]"
self.hit( enemy_hit )
end
end
end
#PLACES, EVENTS, & STORY
#First, generate the Player
h = Hero.new
#Determine player name and gender
print "What is your name, adventurer?\n"
name=gets.chomp.to_s
print "#{name}, are you male or female?\n"
gender=gets
print "\n"
#player chooses his/her class
until heroclass = (1..3)
print"What class do you want?\n(1 for Scout, 2 for Fighter, or 3 for Ranger)\n"
heroclass = gets.to_i
if heroclass == 1
heroclass = "Scout"
Hero.charisma = charisma + 3
Hero.strength = strength - 1
Hero.weapon = weapon + 1
Hero.arrows = arrows + 50
print"Scouts are quick and intelligent, but they are a bit weak.\nTheir prefered weapon is the bow.\n"
break
elsif heroclass == 2
heroclass = "Fighter"
Hero.strength = strength + 3
Hero.charisma = charisma - 3
Hero.weapon = weapon + 3
Hero.life = life + 5
print"Fighters are skilled at close combat, and their endurance is unmached by lesser mortals.\n(They aren't very bright though...)\nTheir prefered weapon is the sword\n"
break
elsif heroclass == 3
heroclass = "Ranger"
Hero.strength = strength - 1
Hero.charisma = charisma + 2
Hero.life = life + 2
Hero.bombs = bombs + 5
Hero.weapon = weapon + 2
print"Rangers are highly skilled, but not as strong as fighters or as agile as scouts.\nThey fight well with all weapons, but they particularly enjoy blowing stuff up.\n"
break
else print "Please enter a valid class number."
end
print "Welcome, #{name}, the #{gender} #{heroclass}, Chosen Hero of the gods!\n"
print "The disembodied voice you are now hearing is your Spirit Guide"
end

When I tried running your code, I got the error
so_question.rb:28:in `<class:Creature>': undefined method `traits' for Creature::Creature:Class (NoMethodError)
from so_question.rb:26:in `<class:Creature>'
from so_question.rb:3:in `<main>'
because you have a class called Creature within the class Creature, and traits is a class method on Creature, not on Creature::Creature.

Related

How to implement Aces in 5-Card-Draw game?

I'm taking an online course in Ruby programming and I need to make 5-Card Draw game as one of the projects. It all went well until I realized that Ace can have two values.
I've made 3 classes so far: Card, Deck and Hand. I'm currently working on a Hand class. The other two classes are below:
class Card
attr_reader :number, :sign, :color
def initialize(number, sign, color)
#number = number
#sign = sign
#color = color
end
end
require_relative 'card.rb'
class Deck
def initialize
#deck = make_deck
end
def make_deck
deck = []
signs = {'Club' => 'black', 'Spade' => 'black', 'Heart' => 'red', 'Diamond' => 'red'}
n = 1
while n < 15
if n == 11
n += 1
next
end
i = 0
4.times do
sign = signs.keys[i]
color = signs[sign]
deck << Card.new(n, sign, color)
i += 1
end
n += 1
end
deck
end
end
So, the problem appeared when I started coding the Poker Hands in Hand class. I'm not sure how to deal with the Ace because it can have a value of either 1 or 15. Any help/suggestion is welcomed.
"Ace can have two values" isn't the right way to think of it. Just make Aces high, always. Then, in the code that checks for straights you have to special-case the wheel. That is, a straight is defined as "5 cards in rank sequence, or A-2-3-4-5".

Undefined Method "attack" for Player::Class

So I am trying to make a text based game with classes for an assignment. I have worked for 2 hours and could not find what the problem is.
class Rankuun
attr_accessor :rankuun_damage, :rankuun_health
def initialize
rankuun_health = 200
rankuun_damage = 100
end
def monolouge
puts 'Rankuun: "So, I see that you have lived this long. I am suprised.'
puts "Not a single libing creature has lived for this long inside my dungeon."
puts "But it's time that your endless slaughter of my brethren are halted."
puts "Now face what true fear really is!"
puts "Hoc vanitas est, et non est fere ut serves!"
puts "You see a mystical aura rise around Rankuun, and hear the shouts of agony"
puts "Rankuun has grown twice in size, and has taken the form of some kind of lich"
puts 'Rankuun: WELCOME TO DIE!"'
end
end
class Player
attr_accessor :health, :gold
def initialize
health = 100
money = 200
puts "Health: #{health}"
puts "Gold: #{money}"
end
def attack
puts "You attack the monster!"
hitmiss = 1
if hitmiss == 1
dmg = rand(5..10)
puts "You hit the monster, and do #{dmg} damage!"
monster_health = monster_health - dmg
elsif hitmiss == 2
puts "You missed!"
end
end
def guard
puts "You attempt to defend yourself"
guard = rand(1..2)
if guard == 1
counter = rand(5..10)
puts "You block the damage, and counterstrike for #{counter} damage"
monster_health = monster_health - counter
elsif guard == 2
monster_counter = rand(1..5)
puts "You try to guard, but the enemy hits harder than you expected, and you get dealt #{monster_counter}"
health = health = monster_counter
end
end
def loot
puts "You search the room and find:"
loot_item = rand (2..3)
if loot_item == 2
puts "You find some gold!"
money = money + 50
puts "Health: #{health}"
puts "Gold: #{money}"
elsif loot_item == 3
puts "You find a curious potion that seems to heal you"
health = health + 50
puts "Health: #{health}"
puts "Gold: #{money}"
end
end
def encounter
encounter = rand(1..2)
if encounter == 1
puts "A monster confronts you!"
monster = Monster.new
elsif encounter == 2
puts "There appears to be no monsters in this room"
end
end
end
class Monster
attr_accessor :monster_health, :monster_damage
def initialize
monster_health = 50
monster_damage = 10
end
def monster_attack
puts "The monster attacks you!"
end
end
puts "There has been a saying in your town for as long as you can remember:"
puts "Ne pas entrer dans le Donjon De Rankuun"
puts 'It means: "Do not enter The Dungeon of Rankuun"'
puts "Many adventurers died inside, and the only living creature in there is the man named Rankuun"
puts "He has great power over the Dungeon, reviving the dead and casting black magic"
puts "You have been selected by the village to go into the Dungeon and exterminate Rankuun"
puts "You have been given a sword, a shield, and some gold. Now you must enter:"
puts "T H E D U N G E O N O F R A N K U U N!"
puts ""
puts ""
player = Player.new
player.encounter
room1 = gets.chomp
if room1 == "attack"
player.attack
elsif room1 == "loot"
player.loot
end
It would be great if this problem were solved. Thanks for responding and aiding me in my assignment.
Welcome to the exciting world of object-oriented design. Many adventurers died inside.
I think you may have a small misunderstanding about the difference between classes and instances. If so, I strongly advise you to read about it before continuing.
You created a new instance of Player when you called Player.new. Your first mistake was not putting it in a variable.
Try something like this:
my_player = Player.new
Secondly, you are trying to call encounter on the Player class, while you should call it on the new instance.
my_player.encounter
You do the same thing inside the Monster class with Player.attack.
I could tell you how to solve each of these problems individually, but I think you would benefit more from redesigning some parts of the project to be easier to change in the future. Hopefully, most of the problems will resolve themselves along the way.
Generally speaking, the shorter a method is, the better. When you tell the Player to attack, that is all it should do. Instead, it does all sorts of things, including getting the monster to attack!
It suddenly becomes apparent that the two classes have quite a lot in common: they both attack; they both take damage, and they both die. It's time to make a superclass. (If you are not familiar with how classical inheritance works, you should learn - this truly is the perfect use case for it.)
class Character
attr_accessor :health
def attack damageable, damage
damageable.take_damage damage
end
def take_damage damage
health -= damage # Equivenent to health = health - damage
potential_death
end
def potential_death
if dead?
die
end
end
def dead?
health <= 0 # With random damage, it could be less than 0.
end
def die # overruled by subclass
end
end
The greatest advantage to doing it like this is you only have to write the code in one place, and it will work for everything. If you change your mind about a design decision, you can change it in one place and know that everything will be adjusted.
You can make a subclass similar to this:
class Monster < Character
def die
super # Call the copy of die in Character, in case it contains something important
reward killer
puts "You kill the monster..."
end
def reward rewardable
rewardable.gain_money 30
end
end
class Player < Character
def die
super # Call the copy of die in Character, in case it contains something important
puts "You died..."
game.over
end
end
(These are only examples; they are not as complete as the code you already have.)
Do you see how each method only does one thing? If you apply that principle to everything you write, it will become much easier to reuse bits and pieces.
I hope this has been useful. If you decide to stick with what you have and just fix the errors, just say so in the comments, and I'll help you with that.
Good luck!

Ruby OOP correct concept?

The exercise questions are below with my answers.
#Create a Tree class with a rings attribute and getter method.
#Trees create a ring for every winter that passes
#It should have a bear_fruit? method which should return true if the
#has fruit that year. the tree produces fruit when it has
#more than 7 rings but less than 15, but false otherwise.
#The class should also have an winter_season method that increases #rings attr by 1.
Can anyone give me constructive criticism on this code?
class Tree
attr_accessor :winters, :rings, :bear_fruit?
def initialize(winters, rings)
#winters = winters
#rings = rings
end
def rings_created
#winters = 0
#rings = 0
while #winters == #rings do
#winters +=1
#rings +=1
break if #winters == 100
end
end
end
def bear_fruit
if #rings > 6 || < 16
#bear_fruit? = true
else
#bear_fruit? = false
end
end
def winter_season
#winters = 0
#rings = 0
while #winters < #rings do
#winters +=1
#rings +=2
break if #winters == 100
end
end
end
end
According to the exercise, you are supposed to create a class Tree with a single attribute rings and two methods, bear_fruit? and winter_season:
Create a Tree class with
a rings attribute and getter method
a bear_fruit? method which
returns true if the tree has more than 7 rings but less than 15
returns false otherwise
a winter_season method that
increases rings by 1
That's all. It doesn't say that a tree should track winters and it doesn't mention any loops.
Here's how I would implement it:
class Tree
attr_reader :rings
def initialize
#rings = 0
end
def bear_fruit?
#rings > 7 && #rings < 15
end
def winter_season
#rings += 1
end
end
First, does it work? I'm guessing not. Run it and see what the error is.
Ruby provides a number of ways of looping which you can look up in the ruby docs. I prefer not to use while loops if I can avoid it, partly because it can lead to less readable code with the use of break. Look up the times method and other enumerables.

Comparing values in a card game

I want to make a card game program that compares the values of cards assigned to player_value and dealer_value. If player_value is greater than dealer_value, it should display "you win". Here is my code:
def get_card (card)
type = case ((card-1)/13)
when 0 then "of clubs"
when 1 then "of diamonds"
when 2 then "of hearts"
when 3 then "of spades"
end
card = case (card%13)
when 0 then "king #{type}"
when 1 then "ace #{type}"
when 11 then "jack #{type}"
when 12 then "queen #{type}"
else card%13
end
"#{card} #{type}"
end
def deal_cards
total_cards = (1..52).to_a.shuffle
player_value = [total_cards.pop, total_cards.pop]
dealer_value = [total_cards.pop, total_cards.pop]
puts "Your cards are #{get_card(player_value[0]).to_s} and #{get_card(player_value[1]).to_s}"
puts "The dealer shows #{get_card(dealer_value[0])}"
if(dealer_value > player_value)
puts "You lose"
else (player_value > dealer_value)
puts "You win"
end
end
deal_cards()
It is not clear to me why this is not working, and I would appreciate any help with this.
I don't really understand why you assign an array to player_value and dealer_value, but you can't compare an array using > or <.
You have to retrieve the element from the array that you want to compare, and then use it in the if-else clause.
Also an else clause does not take another condition. An else will be used if all previous conditions fail. In your case, you should use elsif.
e.g.:
if(dealer_value[0] > player_value[0])
puts "You lose"
elsif (player_value[0] > dealer_value[0])
puts "You win"
end
Let me offer this object oriented solution to the same problem, since this is where Ruby really shines, and seeing it used in a procedural way really irks me. Object orientation adds a few more lines for scaffolding, but adds so much more in terms of legibility, reusability, and conceptual clarity.
We can represent the domain using three basic building blocks.
First, we need a Card object, able to hold some data (a rank and a suit) about itself, as well as the ability to represent itself as a string:
class Card
SUITS = [:clubs, :diamonds, :spades, :hearts]
RANKS = [:ace, *2..10, :jack, :queen, :king]
attr_reader :suit, :rank
def initialize(n)
#suit = (n - 1) / 13
#rank = (n - 1) % 13
end
def to_s
"#{ RANKS[#rank] } of #{ SUITS[#suit] }"
end
end
Next, we need a Hand object. Basically a collection of cards that can compare its strength to other hands, and also represent itself as a string:
class Hand
attr_reader :cards
def initialize(cards)
#cards = cards
end
def <=>(other_hand)
#cards.strength <=> other_hand.strength
end
def to_s
#cards.map(&:to_s).join(", ")
end
private
def strength
#cards.map(&:rank).inject(:+)
end
end
It is not clear from the question, how hand strength is determined. In this primitive implementation, it is simply the sum of the ranks of the cards in the hand.
Lastly, we need a Deck object. Something from which we can draw cards. We'll go with a standard 52-card deck:
class Deck
def initialize
#cards = (1..52).map { |n| Card.new(n) }.shuffle
end
def draw(number_of_cards = 1)
[*#cards.pop(number_of_cards)]
end
end
Now that we have our basic building blocks set up, using them is trivial:
def deal_cards
deck = Deck.new
player_hand = Hand.new(deck.draw(2))
dealer_hand = Hand.new(deck.draw(2))
puts "Your have: #{ player_hand }"
puts "The dealer has: #{ dealer_hand }"
if(player_hand > dealer_hand)
puts "You win!"
elsif(dealer_hand < player_hand)
puts "Aw. You lose."
else
puts "Woah! It's a tie!"
end
end
Notably this solution lacks error handling, for cases like passing an unknown n to the Card constructor, or drawing from an empty deck, but can easily be added in.

trying to count the number of times something comes up on a dice. ruby code

This is my code for a dice that shows a direction.
It shows either north, south, east or west when rolled.
I'm trying to figure out a way to count how many times each one of these appears anytime I roll the dice.
Any one any ideas?
class Dice
#def initialize()
#end
def roll
#dice = Array['north','south','east','west'] # makes dice with four sides (directions)
#dice_index = 0 + rand(4) # gets the random index of the array
puts #dice[#dice_index] # prints random direction like a dice
end
def stats
puts #dice_index
north_count =0;
south_count =0;
east_count=0;
west_count=0;
end
end
game_dice = Dice.new
game_dice.roll
game_dice.stats
Your class should look something like this:
class Dice
SIDES = [:north, :south, :east, :west]
def initialize
#rolls = Hash.new(0)
#num_of_sides = SIDES.count
end
def roll
roll = SIDES[rand(#num_of_sides)]
#rolls[roll] += 1
roll
end
def stats
puts #rolls.inspect
end
end

Resources