Forum: Ruby learning by doing part 2 - tc game

Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 11:48
Hi,

I am trying to learn a bit more and I've decided to make my own little
trading card sort of a game.

On the link below there are two files I've written so far.

https://docs.google.com/folder/d/0B7TkpCLQg89MemVG...

The problem is, that currently the summoned beast is predefined. And I
would like to make it so that user picks which one they will summon for
battle.

If I go via gets.chomp, then the result is a string and the code doesn't
accept that.

thx
regards,
seba
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 12:09
Please ignore line 33 in file game.rb.

regards
seba
Posted by Jan E. (jacques1)
on 2012-07-10 12:21
Hi,

You don't create the beast anywhere. Instead, you just get the name from 
the user and then call "armor" on this string -- which of course doesn't 
work.

I guess you meant to write something like this:

beast_name = gets.chomp
beast = Blue_Scale_DRAGON.new beast_name

and then ...beast.armor
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 12:39
Yes, that would be ok, but that means that the player has to create all 
beasts themselves. I would like to have the beasts and later also some 
spells generated randomly and have them available for player to choose 
which ones they will use.

Like so:

1. generate 10 beasts
2. generate 10 spells

have these available in either arrays of hashes and then decide each 
turn which one to use.

Something like this.

Just now I've tried this:

from line 33 onwards:
---------------------------
prompt
beast = gets.chomp

    if beast == "1"
      select = derimor
    elsif beast == "2"
      select = barador
    end

while player_hp > 0 and ai_hp > 0
.
.
.
.

------------------------

However, this gets messy if there are a lot of beasts and spells.

regards,
seba

Jan E. wrote in post #1068101:
> Hi,
>
> You don't create the beast anywhere. Instead, you just get the name from
> the user and then call "armor" on this string -- which of course doesn't
> work.
>
> I guess you meant to write something like this:
>
> beast_name = gets.chomp
> beast = Blue_Scale_DRAGON.new beast_name
>
> and then ...beast.armor
Posted by Jan E. (jacques1)
on 2012-07-10 12:48
When you've already got an array with the beast names, you can use the
player input as an index:

# beast_names is the array with the 10 possible names
beast_name = beast_names[gets.chomp.to_i - 1]

The "- 1" is necessary if your start numbering the names with 1 instead
of 0.

Of course, you should add some kind of error handling for invalid input.
Posted by "Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> (Guest)
on 2012-07-10 12:53
(Received via mailing list)
On Tue, Jul 10, 2012 at 12:39 PM, Sebastjan H. <lists@ruby-forum.com> 
wrote:
> have these available in either arrays of hashes and then decide each
> turn which one to use.

You can do that easily:

beasts = %w{derimor barador}.map {|name| Blue_Scale_DRAGON.new name}
puts "Choose beast to summon:"
beasts.each_with_index {|b,i| puts "#{i+1}.- #{b.dragon_name}"}
beast = beasts[(gets.to_i) - 1]


Jesus.
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 12:58
"Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> wrote in post 
#1068106:
> On Tue, Jul 10, 2012 at 12:39 PM, Sebastjan H. <lists@ruby-forum.com>
> wrote:
>> have these available in either arrays of hashes and then decide each
>> turn which one to use.
>
> You can do that easily:
>
> beasts = %w{derimor barador}.map {|name| Blue_Scale_DRAGON.new name}
> puts "Choose beast to summon:"
> beasts.each_with_index {|b,i| puts "#{i+1}.- #{b.dragon_name}"}
> beast = beasts[(gets.to_i) - 1]
>
>
> Jesus.

thank you both, I'll try these suggestions and post back if I run into 
problems.
Always a pleasure learning from masters:)

regards,
seba
Posted by Jan E. (jacques1)
on 2012-07-10 13:04
"Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> wrote in post 
#1068106:
> You can do that easily:
>
> beasts = %w{derimor barador}.map {|name| Blue_Scale_DRAGON.new name}

I would create only the chosen beast and not all possible ones. This 
seems more elegant to me.
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 13:33
Jan E. wrote in post #1068109:
> "Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> wrote in post
> #1068106:
>> You can do that easily:
>>
>> beasts = %w{derimor barador}.map {|name| Blue_Scale_DRAGON.new name}
>
> I would create only the chosen beast and not all possible ones. This
> seems more elegant to me.


Maybe I need to clarify a bit more.

I would have the game generate a defined number of beasts and spells. 
The player would then simply choose the ones for battle each turn. The 
player would not generate the beasts nor spells. They would only use 
them. This is the idea for now. I may change or expand later.

I think I will expand the game in the direction that the player will 
choose how many beasts and spells of each kind they want generated.

For example, I want 5 blue scale dragons, five  great dire wolfs and 
five earth spells. This would then be generated for the player to use.

This is just the basis for the game. The basic mechanisms.

regards,
Posted by Jan E. (jacques1)
on 2012-07-10 13:58
Sebastjan H. wrote in post #1068110:
> I would have the game generate a defined number of beasts and spells.
> The player would then simply choose the ones for battle each turn. The
> player would not generate the beasts nor spells. They would only use
> them. This is the idea for now. I may change or expand later.

The player doesn't generate anything, it's all done by the program.

My suggestion has nothing to do with the game logic, it's only a 
technical matter: Rather than actually creating all possible beasts 
(which will lead to unnecessary objects), I'd only generate the *names* 
and later create the object for the chosen name.

Let's say the player has to choose one beast from 1000 possible beasts. 
Why would you want to create all 1000 possible beast objects and then 
throw away the 999 which the player didn't select? Wouldn't it make more 
sense to only have 1000 strings for the names and wait with the object 
creation until the player has actually chosen one name?

Maybe you're thinking too much in terms of physical objects. In order to 
"generate a beast", you don't actually have to create a beast object. 
It's enough to have the information which identifies this beast (in this 
case the name).
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 14:16
Jan E. wrote in post #1068114:
> Sebastjan H. wrote in post #1068110:
>> I would have the game generate a defined number of beasts and spells.
>> The player would then simply choose the ones for battle each turn. The
>> player would not generate the beasts nor spells. They would only use
>> them. This is the idea for now. I may change or expand later.
>
> The player doesn't generate anything, it's all done by the program.
>
> My suggestion has nothing to do with the game logic, it's only a
> technical matter: Rather than actually creating all possible beasts
> (which will lead to unnecessary objects), I'd only generate the *names*
> and later create the object for the chosen name.
>
> Let's say the player has to choose one beast from 1000 possible beasts.
> Why would you want to create all 1000 possible beast objects and then
> throw away the 999 which the player didn't select? Wouldn't it make more
> sense to only have 1000 strings for the names and wait with the object
> creation until the player has actually chosen one name?
>
> Maybe you're thinking too much in terms of physical objects. In order to
> "generate a beast", you don't actually have to create a beast object.
> It's enough to have the information which identifies this beast (in this
> case the name).

You're right, of course:) I misunderstood you earlier.
I still need to adapt the thinking process a bit and this kind of 
knowledge helps a lot.

On the other hand, I am still considering this to be a small scale game 
with predefined let's say only 15 to 20 cards. However, If I were to 
expand it I need this adaptation.

thx again.
regards,
seba
Posted by Jan E. (jacques1)
on 2012-07-10 14:31
Sebastjan H. wrote in post #1068117:
> On the other hand, I am still considering this to be a small scale game
> with predefined let's say only 15 to 20 cards. However, If I were to
> expand it I need this adaptation.

Well, even for 1000 objects you probably won't notice an actual 
difference in speed. But personally I find it more "clean" to only 
create objects you actually need.
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 15:18
Jan E. wrote in post #1068119:
> Sebastjan H. wrote in post #1068117:
>> On the other hand, I am still considering this to be a small scale game
>> with predefined let's say only 15 to 20 cards. However, If I were to
>> expand it I need this adaptation.
>
> Well, even for 1000 objects you probably won't notice an actual
> difference in speed. But personally I find it more "clean" to only
> create objects you actually need.

One more thing, are we talking about the same "selection"? I was 
referring to selection of one of the beasts from the deck for each turn 
of the battle.

And I think you're referring to the deck selection.

Obviously the game needs both, they're just different steps.
And for the deck selection I only wanted the player input on the card 
types (beasts, spells,...). One may want to play only with let's say 
beasts while others prefer spells. And that's the deck selection. Once 
the deck is determined, players select cards for each turn.

regards,
seba
Posted by "Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> (Guest)
on 2012-07-10 16:40
(Received via mailing list)
On Tue, Jul 10, 2012 at 3:18 PM, Sebastjan H. <lists@ruby-forum.com> 
wrote:
> One more thing, are we talking about the same "selection"? I was
> referring to selection of one of the beasts from the deck for each turn
> of the battle.
>
> And I think you're referring to the deck selection.
>
> Obviously the game needs both, they're just different steps.
> And for the deck selection I only wanted the player input on the card
> types (beasts, spells,...). One may want to play only with let's say
> beasts while others prefer spells. And that's the deck selection. Once
> the deck is determined, players select cards for each turn.

What Jan is saying is that you should defer the creation of the actual
Ruby object until the last time, to avoid creating objects that are
not going to be used.
In my opinion, the last responsible time is the one where you need
some value from the object. So if you need to actually create the
objects or not for the deck selection depends on if the "key" (the
name) is enough for the user to choose or not. For example:

cards = %w{derimor barador fireball invisibility} # all the "keys" of
the possible cards
deck_card_names = []
while deck_card_names.size < 15 # or 20
  puts "Choose cards for deck"
  cards.each_with_index {|c,i| "#{i+1}.- #{c}"}
  card = cards[gets.to_i - 1]
  print "quantity: "
  qty = gets.to_i
  qty.times {deck_card_names << card}
end

while true
  puts "Choose card to play"
  deck_cards_name.each_with_index {|c,i| "#{i+1}.- #{c}"}
  card = Blue_Scale_DRAGON.new deck_cards_name[gets.to_i - 1]
end

Of course if you need the info of the card at choosing time, you might
need to instantiate the cards when choosing, for example if you need
to know the type or whatever. Depending on how you plan to generate
and use the cards, there might be better choices than an array. If you
might have a number of identical cards, maybe a hash of "name" => qty
or even "name" => {"qty": 4, "type":spell} or more complex stuff might
be more appropriate.

Jesus.
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 22:21
Thank you for the clarification. I understand the difference and I have 
to first fully determine the logic and the mechanics I want and then 
adjust the code.

One side note, why this: puts "#{beast} strikes with: " produces this: 
#<Blue_Scale_DRAGON:0x8a225d0> strikes with:?

I am guessing it has to with the class, but would I get the actual name 
of the beast which dealt the damage?

regards
seba
Posted by Wayne Brisette (Guest)
on 2012-07-10 22:42
(Received via mailing list)
> From: Sebastjan H. <lists@ruby-forum.com>
>
> One side note, why this: puts "#{beast} strikes with: " produces
> this:
> #<Blue_Scale_DRAGON:0x8a225d0> strikes with:?

When I've seen similar things in my code, I resort to using 
"#{beast}.to_s to
coearse the variable into a string. I'm sure one of the more 
knowledgeable folks
will explain the why (because I have always thought that's what 
#{variable} did
without using the .to_s).


Wayne
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 22:55
Wayne Brisette wrote in post #1068186:
>> From: Sebastjan H. <lists@ruby-forum.com>
>>
>> One side note, why this: puts "#{beast} strikes with: " produces
>> this:
>> #<Blue_Scale_DRAGON:0x8a225d0> strikes with:?
>
> When I've seen similar things in my code, I resort to using
> "#{beast}.to_s to
> coearse the variable into a string. I'm sure one of the more
> knowledgeable folks
> will explain the why (because I have always thought that's what
> #{variable} did
> without using the .to_s).
>
>
> Wayne

Hi,

I've tried it and I got #<Blue_Scale_DRAGON:0x90653ac>.to_s

I've tried other variations with .to_s and still doesn't work.

regards
seba
Posted by Jan E. (jacques1)
on 2012-07-10 23:05
Sebastjan H. wrote in post #1068181:
> One side note, why this: puts "#{beast} strikes with: " produces this:
> #<Blue_Scale_DRAGON:0x8a225d0> strikes with:?
>
> I am guessing it has to with the class, but would I get the actual name
> of the beast which dealt the damage?

How is the object supposed to know that you want the value of @name when 
you transform it into a string? You have to define a custom "to_s" 
method. If you don't, the object will use the one provided by the Object 
class (which returns this identifier string).

But in this case it makes more sense to simply get the name:

"#{beast.dragon_name} strikes with: "
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-10 23:09
Jan E. wrote in post #1068190:
> Sebastjan H. wrote in post #1068181:
>> One side note, why this: puts "#{beast} strikes with: " produces this:
>> #<Blue_Scale_DRAGON:0x8a225d0> strikes with:?
>>
>> I am guessing it has to with the class, but would I get the actual name
>> of the beast which dealt the damage?
>
> How is the object supposed to know that you want the value of @name when
> you transform it into a string? You have to define a custom "to_s"
> method. If you don't, the object will use the one provided by the Object
> class (which returns this identifier string).
>
> But in this case it makes more sense to simply get the name:
>
> "#{beast.dragon_name} strikes with: "

Jan thank you again. I should have seen this. I think I best turn in:)

regards,
seba
Posted by Sam Duncan (Guest)
on 2012-07-10 23:13
(Received via mailing list)
On 07/11/2012 08:21 AM, Sebastjan H. wrote:
> regards
> seba
>
You could define a #to_s method on your dragon class, in which case you
would probably see what you want in that string interpolation. It might
return the @name as a string, or some human readable representation of
the class like "Blue Scale Dragon" depending on what you want the output
to say. A better approach might be to use your existing #dragon_name
method in the interpolation like so;

"#{beast.dragon_name}"

You might decide this is a bit clunky and change that method to simply
#name which makes it a bit more convenient when displaying messages
about various different beast instances without having to know which
kind of beast they are. If you were to do this, your definition could
become;

attr_reader :name

to be called like;

"#{beast.name}"

You could take it a step further and have a base Beast class which
defines all the common methods and attributes of a beast, and then make
the Blue Scale Dragon a subclass.

BlueScaleDragon < Beast

So many possibilities, hope that helps!

http://ruby-doc.org/core-1.9.3/Module.html#method-...


Sam
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-13 15:26
I've done quite some reprogramming according to the advises above and I 
need help yet again.

1. I haven't uploaded the new files yet as google docs isn't really the 
best solution for this. Which brings me to a sub-question: What 
repository is best (I mean most user friendly for a beginner). I am 
using Bazaar offline and I've had some difficulties using the Launchpad.

2. Launchpad requires me to state the license. If I have a project like 
this (consisting of several files), where do I state the license and is 
this entirely up to me as the author?

3. And for the big one:

I am using a hash with beast names and types:
--------------------------------------------------------------------
possible_cards = {"derimor" => "dragon", "barador" => "dragon",
 "teragon" => "dragon", "gali" => "dragon", etc.....

--------------------------------------------------------------------

I also get the user input for the number of each type:

--------------------------------------------------------------------
  ## Select the number of dragons
def select_number_of_dragons()
puts "Select the number of dragons (max. 5)"
@dragons_qty = gets.chomp.to_i
  if @dragons_qty < 1 or @dragons_qty > 5
    puts "The number of dragons is invalid. Enter a number between 1 and 
5."
    return select_number_of_dragons()
  end
  return @dragons_qty
end
--------------------------------------------------------------------

Now I would like to have as many beasts/spells from the hash generated 
as user specified. If dragon_qty is 4 then the first (or maybe also 
random) 4 dragons from the hash are to be generated.

I've tried the iterating with loop "for i in 0..dragon_qty" but that 
actually multiplies each dragon by dragon_qty:(

thank you.

kind regards,
seba
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-13 21:10
Sebastjan H. wrote in post #1068570:
> I've done quite some reprogramming according to the advises above and I
> need help yet again.
>
> 1. I haven't uploaded the new files yet as google docs isn't really the
> best solution for this. Which brings me to a sub-question: What
> repository is best (I mean most user friendly for a beginner). I am
> using Bazaar offline and I've had some difficulties using the Launchpad.
>
> 2. Launchpad requires me to state the license. If I have a project like
> this (consisting of several files), where do I state the license and is
> this entirely up to me as the author?
>
> 3. And for the big one:
>
> I am using a hash with beast names and types:
> --------------------------------------------------------------------
> possible_cards = {"derimor" => "dragon", "barador" => "dragon",
>  "teragon" => "dragon", "gali" => "dragon", etc.....
>
> --------------------------------------------------------------------
>
> I also get the user input for the number of each type:
>
> --------------------------------------------------------------------
>   ## Select the number of dragons
> def select_number_of_dragons()
> puts "Select the number of dragons (max. 5)"
> @dragons_qty = gets.chomp.to_i
>   if @dragons_qty < 1 or @dragons_qty > 5
>     puts "The number of dragons is invalid. Enter a number between 1 and
> 5."
>     return select_number_of_dragons()
>   end
>   return @dragons_qty
> end
> --------------------------------------------------------------------
>
> Now I would like to have as many beasts/spells from the hash generated
> as user specified. If dragon_qty is 4 then the first (or maybe also
> random) 4 dragons from the hash are to be generated.
>
> I've tried the iterating with loop "for i in 0..dragon_qty" but that
> actually multiplies each dragon by dragon_qty:(
>
> thank you.
>
> kind regards,
> seba

I've also tried something with until:
-------------------------------------------------------------------
begin
  possible_cards.each do |name, type|
    if type == "dragon"
    deck_dragons << name
    end
  end
end until deck_dragons.length.to_i == @dragons_qty.to_i
-------------------------------------------------------------------
... but it doesn't work either
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-14 10:53
>>
>> Now I would like to have as many beasts/spells from the hash generated
>> as user specified. If dragon_qty is 4 then the first (or maybe also
>> random) 4 dragons from the hash are to be generated.
>>

I think random would be better if I add more beasts and spells in the 
overall possible deck.

I still trying to do it though...
regards,
seba
Posted by Jan E. (jacques1)
on 2012-07-14 11:07
Sebastjan H. wrote in post #1068699:
> I think random would be better if I add more beasts and spells in the
> overall possible deck.

First of all, you should change the structure of possible_cards to this:

possible_cards = {
  :dragons => ["derimor", "barador", ...],
  :another_type => ["foo", "bar", ...],
  ...
}

So the types are the keys, and the corresponding names are the values.

Then you can simply use Array#sample to select e. g. 4 dragons:

deck_dragons = possible_cards[:dragons].sample 4

Your mistake was that you iterated over possible_cards for every 
selection. This would always give you the first dragon. And in the 
second example you forgot the "break", which would put *all* dragons 
into the deck for every selection.

I've some other notes regarding the style, but I'll write that down in a 
second post ...
Posted by Jan E. (jacques1)
on 2012-07-14 12:07
As to the style:

You seem to be coming from a language like Java or so. Note that Ruby 
does *not* have a "do-until" or a "do-while" loop. You have to use an 
endless loop with a conditional "break" at the end.

This "begin-end-until" actually looks like a "do-until" loop, but it's 
not. It's just the same as "until ... do ... end". I'd actually avoid 
this notion completely, because it's very ambiguous and hard to read. 
Use the appended "until", "while", "if" etc. only for single line 
statements.

Ruby obviously does have a "for-in" loop, but I'd avoid that, too. It's 
cumbersome, it doesn't introduce a new scope (loop variables are visible 
from the outside), and it's not very "Rubyish". Use "each" instead.

You generally use a very low level programming style with lots of loops 
and manually filling arrays. Of course, you can do that in Ruby. But 
Ruby also allows you to use a more highlevel approach with methods like 
"select", "reject", "map" etc.

For example, if you want to select all even numbers between 1 and 100, 
you can do that by writing

even_numbers = []
for i in 1..100
  if i % 2 == 0
    even_numbers << i
  end
end

This translates directly to Java code. But in Ruby you'd rather use the 
"select" method and let the computer do all the lowlevel stuff 
(initializing an array, looping over the numbers and checking for the 
condition):

even_numbers = (1..100).select {|i| i.even?}

or shorter

even_numbers = (1..100).select &:even?

As you can see, this is much more readable. Of course, you won't always 
find a suitable method for your specific problem.

But I think you should generally get away from this lowlevel procedural 
style and think in more highlevel terms like "selecting", "combining" 
etc.

This should lead to more compact and better readable code.
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-14 12:52
Jan E. wrote in post #1068742:
> As to the style:
>
> You seem to be coming from a language like Java or so.

Actually, I have no programming background. I started with VBA for 
Applications to streamline tasks at work and then I wanted something 
more and to be honest I was goggling for the programming language with 
the best ratio of user-friendliness/power. I ended up with Ruby and I 
love it.


> But I think you should generally get away from this lowlevel procedural
> style and think in more highlevel terms like "selecting", "combining"
> etc.
>
> This should lead to more compact and better readable code.

This is definitely my goal. I'm reading books on ruby and I will also 
take some online tutorials to achieve this. However, I couldn't do it 
without the great help from the community. So Jan and others, thank you 
very much.

And of course, to learn from practice, I am just undertaking projects 
like this as I go along.

Could I bother you to have a look at the other two questions I wrote 
about repository in licences?

thx,
seba
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-15 08:59
>
> Could I bother you to have a look at the other two questions I wrote
> about repository in licences?
>
 I wanted to say repository AND licences of course:)
Posted by Jan E. (jacques1)
on 2012-07-15 16:18
Well, I've never used Bazaar. I think most people use Git together with 
GitHub.

As to the licence: As far as I understand, Ruby itself doesn't force you 
to use a specific licence. It may be different if you include external 
libraries. But I'm really no lawyer. ;-)
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-15 20:43
Jan E. wrote in post #1068806:
> Well, I've never used Bazaar. I think most people use Git together with
> GitHub.
>
> As to the licence: As far as I understand, Ruby itself doesn't force you
> to use a specific licence. It may be different if you include external
> libraries. But I'm really no lawyer. ;-)

hi, thx for the info.

now, before the battle, as suggested above, I have list of cards with 
names and attributes.

code for this:

@deck.each_with_index {|b,i| puts "#{i+1}. 
#{b.name}\n#{b.type}\n#{b.attributes}"}

My question is about the output:

Type: dragon.
Attack: 114.
Armor: 50.
1. gali

The order is reversed.
Posted by Jan E. (jacques1)
on 2012-07-15 21:41
Yes. The problem is that b.attributes *outputs* a string instead of 
returning it (at least in the code you posted first). So the attributes 
are displayed before the actual "puts" is executed, while the 
"b.attributes" expression simply returns nil.

You'll simply have to change the "attributes" method. Outputting should 
generally be restricted to either the top level or prominent methods. 
All other methods should *return* their value.
Posted by Ryan Davis (Guest)
on 2012-07-16 23:39
(Received via mailing list)
On Jul 14, 2012, at 03:07 , Jan E. wrote:

> You seem to be coming from a language like Java or so. Note that Ruby
> does *not* have a "do-until" or a "do-while" loop. You have to use an
> endless loop with a conditional "break" at the end.
>
> This "begin-end-until" actually looks like a "do-until" loop, but it's
> not. It's just the same as "until ... do ... end". I'd actually avoid
> this notion completely, because it's very ambiguous and hard to read.
> Use the appended "until", "while", "if" etc. only for single line
> statements.

No, begin/end/(while|until) is NOT the same as (while|until)/end:

% ruby -e 'begin puts "bad"; end while false'
bad
% ruby -e 'while false do puts "bad"; end '
%
Posted by Jan E. (jacques1)
on 2012-07-17 01:01
Yeah, I actually remembered there was this strange "do-while" loop,
which we're not supposed to use:

http://bit.ly/Mdn8W0

So here it is. And you can forget it right away. ;-)
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-17 09:58
Jan E. wrote in post #1068984:
> Yeah, I actually remembered there was this strange "do-while" loop,
> which we're not supposed to use:
>
> http://bit.ly/Mdn8W0
>
> So here it is. And you can forget it right away. ;-)

well,  I like to stay away from until/while, however, I am not sure how 
to avoid it in the case of running combat turns:

while player_hp > 0 and ai_hp > 0
.
.# players play cards
.
.
end


regards,
seba
Posted by Jan E. (jacques1)
on 2012-07-17 11:05
Sebastjan H. wrote in post #1069017:
> well,  I like to stay away from until/while, however, I am not sure how
> to avoid it in the case of running combat turns:

No, "until" and "while" are perfectly fine, I was talking about this 
particular structure:

begin
  #body
end while #condition

This one you should avoid, because it behaves unexpectedly (and is just 
ugly). In contrast to all other uses of the "while" modifier, it 
executes the body *before* checking the condition -- like a "do-while" 
loop in other languages.

If you actually need a "do-while" loop, use an endless loop with a 
conditional "break" at the end of the loop body:

loop do
  #body
  break unless #condition
end

The same goes for "begin-end-until", of course. In this case you need a 
"break if" at the end.
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-18 14:02
I need (hopefully) one final help for this game:)

I am trying to program the battle turn and I just wanted to know whether 
there is any better or more elegant solution other than branching so 
much:

-------------------------------------------------------------
  ## Here it needs to branch according to card attributes: spells don't 
have attacks.
  if card.type == "armor_spell"
    player_hp = player_hp + card.armor

  elsif card.type == "healing_spell"
    player_hp = player_hp + card.heal

  elsif card.type == "debuff_spell"
    if ai_card.type == "dragon" or ai_card.type == "dire wolf"
      ai_card.attack = ai_card.attack - card.debuff
    end

  elsif card.type == "dragon" or card.type == "dire wolf"
    if ai_card.type == "dragon" or ai_card.type == "dire wolf"
    player_hp = player_hp + card.armor - ai_card.attack
    puts "#{ai_card.name} strikes with " + ai_card.attack.to_s
    puts "\n"

  end

  if ai_card.type == "armor_spell"
    ai_hp = ai_hp + ai_card.armor

  elsif ai_card.type == "healing_spell"
    ai_hp = ai_hp + ai_card.heal

  elsif ai_card.type == "debuff_spell"
    if card.type == "dragon" or card.type == "dire wolf"
      card.attack = card.attack - ai_card.debuff
    end

  elsif ai_card.type == "dragon" or ai_card.type == "dire wolf"
  ai_hp = ai_hp + ai_card.armor - card.attack
  puts "#{card.name} strikes with: " + card.attack.to_s
  end


  @deck.delete(card)
  @ai_deck.delete(ai_card)
  puts "Player health is #{player_hp}."
  puts "Ai health is #{ai_hp}."
-------------------------------------------------------------
thank you very much.
Best regards,
seba
Posted by "Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> (Guest)
on 2012-07-18 14:21
(Received via mailing list)
On Wed, Jul 18, 2012 at 2:02 PM, Sebastjan H. <lists@ruby-forum.com> 
wrote:
>     player_hp = player_hp + card.armor
>     if ai_card.type == "dragon" or ai_card.type == "dire wolf"
>     ai_hp = ai_hp + ai_card.heal
>
>
>   @deck.delete(card)
>   @ai_deck.delete(ai_card)
>   puts "Player health is #{player_hp}."
>   puts "Ai health is #{ai_hp}."
> -------------------------------------------------------------
> thank you very much.


One approach that implies a huge refactor of your datastructures,
would be to have each card object implement its own logic on the game
state:

class ArmorCard
  attr_reader :armor_level
  def initialize armor
    @armor_level = armor
  end

  def perform_action game_state
    game_state.increment_player_hp(armor)
  end
end

class HealingSpellCard
  attr_reader :spell_level
  def initialize spell_level
    @spell_level = spell_level
  end

  def perform_action game_state
    game_state.increment_player_hp(spell_level * 2) #healing spells
heal double their level (example of spell logic)
  end
end

Then you only need to call the perform_action method in each card
object. If you have common logic, such as attack card only differing
in name and attack value, but the attack logic is the same, then you
could model it with a class hierarchy:

class Card
  def is_attack?
    false
  end
end

class AttackCard < Card
  def is_attack?
    true
  end

  def perform_action game_state
    game_state.increment_player_hp(armor)
    if game_state.ai_card.is_attack?
      game_state.increment_player_hp(-ai_card.attack)
    end
  end
end

class Dragon < AttackCard
  attr_reader :armor, :attack
  def initialize armor, attack
    @armor = armor
    @attack = attack
  end
end

And so on. You might have different classes depending on
characteristics or other ways to model it: maybe AttackCard,
SpellCard, etc could be modules you mixin in specific cards, for
example if some card can be both. Then maybe you could have a general
implementation of perform_action in the Card class. It depends.

I hope this gives you some ideas.

Jesus.
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-19 10:13
"Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> wrote in post 
#1069191:
> On Wed, Jul 18, 2012 at 2:02 PM, Sebastjan H. <lists@ruby-forum.com>
> wrote:
>>     player_hp = player_hp + card.armor
>>     if ai_card.type == "dragon" or ai_card.type == "dire wolf"
>>     ai_hp = ai_hp + ai_card.heal
>>
>>
>>   @deck.delete(card)
>>   @ai_deck.delete(ai_card)
>>   puts "Player health is #{player_hp}."
>>   puts "Ai health is #{ai_hp}."
>> -------------------------------------------------------------
>> thank you very much.
>
>
> One approach that implies a huge refactor of your datastructures,
> would be to have each card object implement its own logic on the game
> state:
>
> class ArmorCard
>   attr_reader :armor_level
>   def initialize armor
>     @armor_level = armor
>   end
>
>   def perform_action game_state
>     game_state.increment_player_hp(armor)
>   end
> end
>
> class HealingSpellCard
>   attr_reader :spell_level
>   def initialize spell_level
>     @spell_level = spell_level
>   end
>
>   def perform_action game_state
>     game_state.increment_player_hp(spell_level * 2) #healing spells
> heal double their level (example of spell logic)
>   end
> end
>
> Then you only need to call the perform_action method in each card
> object. If you have common logic, such as attack card only differing
> in name and attack value, but the attack logic is the same, then you
> could model it with a class hierarchy:
>
> class Card
>   def is_attack?
>     false
>   end
> end
>
> class AttackCard < Card
>   def is_attack?
>     true
>   end
>
>   def perform_action game_state
>     game_state.increment_player_hp(armor)
>     if game_state.ai_card.is_attack?
>       game_state.increment_player_hp(-ai_card.attack)
>     end
>   end
> end
>
> class Dragon < AttackCard
>   attr_reader :armor, :attack
>   def initialize armor, attack
>     @armor = armor
>     @attack = attack
>   end
> end
>
> And so on. You might have different classes depending on
> characteristics or other ways to model it: maybe AttackCard,
> SpellCard, etc could be modules you mixin in specific cards, for
> example if some card can be both. Then maybe you could have a general
> implementation of perform_action in the Card class. It depends.
>
> I hope this gives you some ideas.
>
> Jesus.

Hi Jesus,

I think you are right, this is probably the way to go and much better 
than branching or making different methods for different combinations of 
cards (would make game expansions impossible).

However, I don't understand the "increment_player_hp". This is just your 
example, right? I'd have to define this I guess.

Furthermore, according to your example above for the AttackCard there is 
only the definition for the player attack. I'd have to make two of 
those, also for the AI, right?. And for all other card types as well.

regards,
seba
Posted by "Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> (Guest)
on 2012-07-19 10:38
(Received via mailing list)
On Thu, Jul 19, 2012 at 10:13 AM, Sebastjan H. <lists@ruby-forum.com> 
wrote:
>>>   @ai_deck.delete(ai_card)
>> class ArmorCard
>> class HealingSpellCard
>>
>>
>>   end
>> And so on. You might have different classes depending on
>
> I think you are right, this is probably the way to go and much better
> than branching or making different methods for different combinations of
> cards (would make game expansions impossible).
>
> However, I don't understand the "increment_player_hp". This is just your
> example, right? I'd have to define this I guess.

Yes, this is an example. You might have a GameState class with these
kind of utility methods, or any other mechanism for accessing the game
state. For example you could also have just an attr_accessor for
player_hp and do game_state.player_hp += 5, but in general I like
those higher levell methods, because you might use them for integrity
checks, or triggering other logic that might otherwise need to repeat
everywhere. Another example: every time you modify the player_hp, you
might want to check if the player is dead and thus end the game or
whatever. If you have the logic for modifying that value in a single
place you only that logic in one place (DRY).

> Furthermore, according to your example above for the AttackCard there is
> only the definition for the player attack. I'd have to make two of
> those, also for the AI, right?. And for all other card types as well.

Yes, not sure how that works though. You and the AI each play a card,
then you activate the player's card that does something to both
players, then you activte the AI card which does something to both
players? Are the cards the same type for both? Do they do something
different depending if it's the player or the AI the one playing the
card? If the answers are yes, yes and no, you could do soemthing like:

# this would be the game loop making the player and the ai choose
cards and activated them in turn:
game_state.set_active_player :player
game_state.play_card_for_active_player
game_state.set_active_player :ai
game_state.play_card_for_active_player

class AttackCard < Card
  def is_attack?
    true
  end
  def perform_action game_state
    game_state.increment_active_player_hp(armor)
    if game_state.opponent_card.is_attack?
      game_state.increment_active_player_hp(-game_state.opponent_card.attack)
    end
  end
end

So now, both players (player and ai) are the same from the card's
point of view. For a card there's just the "active player" who is the
one activating the card and the "opponent", who is... well, the
opponent :).

If, on the other hand, a card needs to know if it's the player or the
ai the one activating it, you can also model that information in the
game state and have the card check who is the active player. Another
option would be to have two sets of cards that implement the
appropriate logic, for example PlayerAttackCard and AIAttackCard and
so the decks don't share card implementations. So many
possibilities... :D.

Jesus.
Posted by Sebastjan H. (sebastjan_h)
on 2012-07-19 10:48
Thank you very much, this is very helpful, I like your solution with the 
active player.
I'll go back to the drawing board for quite some time I guess:) since 
this isn't the only project.

regards,
seba
Posted by Sebastjan H. (sebastjan_h)
on 2012-08-03 00:38
I am back with another question.

I am re-factoring the game and still trying to keep it simple, so bear 
with me. I've added effects to cards which are activated when played.

In the main file I have this for each turn:

  ## The cards effects are activated (opponent sequence is random)
  players = [@hm_player, @ai_player]
  @opponent = players.sample

  if @opponent == @ai_player
    @own = @hm_player
    hm_card.effect

    @opponent = @hm_player
    @own = @ai_player
    ai_card.effect

  elsif @opponent == @hm_player
    @own = @ai_player
    ai_card.effect

    @opponent == @ai_player
    @own = @hm_player
    hm_card.effect

  else

  end

------------------------
Now, I wanted to keep it simple, so I have the dragon's effect defined 
as follows:

  def effect()
  @opponent.hp -= card.attack # I've just spotted this error, where I 
refer to card and in the above code there is still hm_card
  end


When I run the game everything goes smoothly until the effect should be 
activated. I get this error:

dragon.rb:31:in `effect': undefined method `hp' for nil:NilClass 
(NoMethodError)
  from game_02.rb:126:in `<main>'

If I puts the opponent.hp it works though.

regards,
seba
Posted by Jan E. (jacques1)
on 2012-08-03 02:47
Hi,

First of all, don't misuse instance variables to pass values to methods.
This makes the code obscure and fragile -- and it's simply bad design.
Use method parameters instead (that's what they're for). The purpose of
instance variables is to hold the state of an object.

It your case it cannot even work, because you have different objects. 
The dragon object cannot access the instance variables from the top 
level object.

So the fix for this would be to simply add a parameter to the "effect"
method for the opponent:

def effect(opponent)

And maybe further parameters for other values.
Posted by Sebastjan H. (sebastjan_h)
on 2012-08-03 09:14
Jan E. wrote in post #1071102:
> Hi,
>
> First of all, don't misuse instance variables to pass values to methods.
> This makes the code obscure and fragile -- and it's simply bad design.
> Use method parameters instead (that's what they're for). The purpose of
> instance variables is to hold the state of an object.

> It your case it cannot even work, because you have different objects.
> The dragon object cannot access the instance variables from the top
> level object.
>
> So the fix for this would be to simply add a parameter to the "effect"
> method for the opponent:
>
> def effect(opponent)
>
> And maybe further parameters for other values.

Thank you very much for reminding me of the basic rules.

kind regards
seba
Posted by Sebastjan H. (sebastjan_h)
on 2012-08-03 15:06
I wanted to test this on a simple case, so I wrote the following:
------------------------------------------
def attack(target)
target -= 200
end

class A
def initialize(name, hp)
@name = name
@hp = hp
end

def hp()
@hp
end

def name()
@name
end

end

players = [] << A.new("test", 400)

attack(players[0].hp)

puts players[0].hp
------------------------------------------
The code runs, but the result is 400. As if the attack method did 
nothing.
What am I doing wrong?
Posted by "Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> (Guest)
on 2012-08-03 15:26
(Received via mailing list)
On Fri, Aug 3, 2012 at 3:06 PM, Sebastjan H. <lists@ruby-forum.com> 
wrote:
> end
>
> players = [] << A.new("test", 400)
>
> attack(players[0].hp)
>
> puts players[0].hp
> ------------------------------------------
> The code runs, but the result is 400. As if the attack method did
> nothing.
> What am I doing wrong?

The reason is that this line:

target -= 200 is equivalent to this one:
target = target - 200

which is assigning a new value to the local variable of the method.
Once you exit the method, the new value is lost. In order for this to
work, you should pass a reference to a mutable object, so that the
effects of the method are visible outside. For example:

def attack(target)
  target.hp -= 200 # supposing target has methods #hp and #hp=
end

# later....
attack(player[0])

Hope this helps,

Jesus.
Posted by Sebastjan H. (sebastjan_h)
on 2012-08-03 15:44
In this line you wrote

>   target.hp -= 200 # supposing target has methods #hp and #hp=


what is the meaning of the equal sign? #hp=

Because in my case above the target only has hp method.

And if I remodel it to your example:

def attack(target)
target.hp -= 200
end

class A
def initialize(name, hp)
@name = name
@hp = hp
end

def hp()
@hp
end

def name()
@name
end

end

players = [] << A.new("test", 400)

attack(players[0])
puts players[0].hp
-------------------------------------
3.rb:3:in `attack': undefined method `hp=' for #<A:0x8b7d268 
@name="test", @hp=400> (NoMethodError)
  from 3.rb:24:in `<main>'
Posted by "Jesús Gabriel y Galán" <jgabrielygalan@gmail.com> (Guest)
on 2012-08-03 16:29
(Received via mailing list)
On Fri, Aug 3, 2012 at 3:44 PM, Sebastjan H. <lists@ruby-forum.com> 
wrote:
> In this line you wrote
>
>>   target.hp -= 200 # supposing target has methods #hp and #hp=
>
>
> what is the meaning of the equal sign? #hp=

It's part of the method name. Ruby has a bit of sintactic sugar to
allow calling methods that end with = as if they were assignments:

target.hp = 200

is equivalent to: target.hp=(200).

> @name = name
>
> end
>
> players = [] << A.new("test", 400)
>
> attack(players[0])
> puts players[0].hp
> -------------------------------------
> 3.rb:3:in `attack': undefined method `hp=' for #<A:0x8b7d268
> @name="test", @hp=400> (NoMethodError)
>   from 3.rb:24:in `<main>'

If you want the external classes the possibility to read and assign
new values to hp and name, you can use attr_accessor:

class A
  attr_accessor :name, :hp
  def initialize(name, hp)
    @name = name
    @hp = hp
  end
end

attr_accessor will create methods name, name=, hp and hp= for you.

Jesus.
Posted by Sebastjan H. (sebastjan_h)
on 2012-08-24 21:26
Once again, I turn to you for your wisdom:)

Why this works:
-----------------------------------------
class Depo
attr_accessor :test

def initialize(test)
@test = test
end

  def test()
  @test
  end
end

bla = Depo.new("blahdsd")

def defin (bla)
if defined? test
puts "#{bla.test}"
end
end

defin(bla)
------------------------------------------

And this doesn't:
------------------------------------------
def effect_output(card)
  if defined? attack
    puts "#{card.name.capitalize} hits with #{card.attack}."
  elsif defined? heal
    puts "#{card.name.capitalize} heals for #{card.heal}."
  end
end
------------------------------------------
The card is a DRAGON class object with method attack defined. It has the
attr_accessor as well as initialize method defined. I don't even get an
error. Nothing gets putsed.

If I use

if defined? :attack

then I get the message that attack is an undefined method for class
HEAL.

Of course the HEAL class doesn't have attack method, but it should jump 
to elsif, which checks for the heal method. This is true for the HEAL 
class.

???
Posted by Jan E. (jacques1)
on 2012-08-24 23:20
Hi,

The "defined?" operator is simply wrong in this case. It checks *the
current scope* for variables, methods etc.

For example:

x = 0
C = 15
puts defined? x  # this outputs "local-variable"
puts defined? C  # this outputs "constant"
puts defined? a  # this outputs nothing (because the result is nil)

You want something completely different, namely check if an object has a
specific method. That's what Object#respond_to? is for:

def effect_output(card)
  if card.respond_to? :attack
    puts "#{card.name.capitalize} hits with #{card.attack}."
  elsif card.respond_to? :heal
    puts "#{card.name.capitalize} heals for #{card.heal}."
  end
end

This will call "attack" and "heal" only if the card object does actually
have those methods.

What should have made you suspicious is that you never had to specify
any object. How is Ruby then supposed to know that you want to look into
the card object?

By the way, the reason why your program output something after you used 
a symbol is that

defined? :attack

returns "expression" (which is truthy).
Posted by Sebastjan H. (sebastjan_h)
on 2012-08-26 15:12
Jan E. wrote in post #1073054:
> Hi,
>
> The "defined?" operator is simply wrong in this case. It checks *the
> current scope* for variables, methods etc.
>
> For example:
>
> x = 0
> C = 15
> puts defined? x  # this outputs "local-variable"
> puts defined? C  # this outputs "constant"
> puts defined? a  # this outputs nothing (because the result is nil)
>
> You want something completely different, namely check if an object has a
> specific method. That's what Object#respond_to? is for:
>
> def effect_output(card)
>   if card.respond_to? :attack
>     puts "#{card.name.capitalize} hits with #{card.attack}."
>   elsif card.respond_to? :heal
>     puts "#{card.name.capitalize} heals for #{card.heal}."
>   end
> end
>
> This will call "attack" and "heal" only if the card object does actually
> have those methods.
>
> What should have made you suspicious is that you never had to specify
> any object. How is Ruby then supposed to know that you want to look into
> the card object?
>
> By the way, the reason why your program output something after you used
> a symbol is that
>
> defined? :attack
>
> returns "expression" (which is truthy).

Thank you. That helped. I actually was wondering why I didn't have to 
specify an object, but I just couldn't make the connection.

Anyway, I think I am finished with the game for now. It runs ok and I've 
also included a couple of different arenas which influence on the 
possibility of critical strikes. It's fun to play regarding it's my 
first game and it runs in the command line:)

I also rewrote all the parts where I misused the instance variables.

kind regards,
seba
Posted by Jan E. (jacques1)
on 2012-08-26 21:39
That sounds great. :-)

Can we download the game anywhere?
Posted by Sebastjan H. (sebastjan_h)
on 2012-08-27 06:45
Jan E. wrote in post #1073084:
> That sounds great. :-)
>
> Can we download the game anywhere?

I haven't uploaded it anywhere, because I am not sure whether I'll use 
github or launchpad yet and even then I have to learn how to use the 
repository first as I am (as stated on several occasions:) a complete 
noob in the area.

I'll post here when it's uploaded.

kind regards,
seba
Posted by Rob Biedenharn (Guest)
on 2012-08-27 13:59
(Received via mailing list)
On Aug 24, 2012, at 3:26 PM, Sebastjan H. wrote:

>
> end
>  elsif defined? heal
> if defined? :attack
>
> then I get the message that attack is an undefined method for class
> HEAL.
>
> ???

You don't want defined? here, you want respond_to? as in:

if card.respond_to?(:attack)
  # do something involving card.attack
end

-Rob
Posted by Sebastjan H. (sebastjan_h)
on 2012-09-03 21:55
Jan E. wrote in post #1073084:
> That sounds great. :-)
>
> Can we download the game anywhere?

finally:)

http://bazaar.launchpad.net/~sebastjan-hribar/drag...

I still have a lot of things planned, but it'll take me some time.

regards,
seba
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.