Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

Python Object-Oriented Python Dice Roller Giving a Hand

int object has no attribute 'num_die'

Hi. I'm stuck on this one. Much appreciation to anyone who can get me out of the mud! I set this up a bit differently from the video, but it seems like it should work. PyCharm tells me the num_die attribute is an integer, but when the code runs, it says int object has no attribute 'num_die' The die class and the die roller class work just fine, but then it stops in the for-loop of the dice_hand method.

It also seems like I should be able to call dice_hand without passing in arguments, because all of them are listed with default values, but it requires something. A number (ex. 10) or a variable (see below) work equally well.

In the DiceHand class, it seems like the initilaization of the dictionary should be self.dh_dict - but this returns and unresolved reference to self. Any idea why this is?

  # main.py


from die import Die, DieRoll
from dice_hand_file import DiceHand

try_die = Die()
try_die_roll = DieRoll.roll_die(try_die)
print(try_die_roll)

try_die_hand = DiceHand.dice_hand(try_die_roll)
print(try_die_hand)
 # die.py 

import random


class Die:
    # creates a single die
    def __init__(self, sides=6, value=None):
        self.sides = sides
        self.value = value

    def __int__(self):
        return int(self)

    def __add__(self, other):
        return int(self) + other


class DieRoll(Die):
    def __init__(self):
        super().__init__()
    # rolls a single die
    def roll_die(self):
        if self.value is None:
            self.value = random.randint(1, self.sides + 1)
            return self.value
        else:
            return self.value
# die_hand_file.py

from die import DieRoll


class DiceHand(DieRoll):
    def __init__(self, num_die=5, dh_dict=None, die_counter=0):
        super(DieRoll).__init__()
        self.num_die = num_die
        self.dh_dict = dh_dict
        self.die_counter = die_counter

    # returns a dictionary with the die number(key) and the value rolled by that die(value)
    dh_dict = {}

    def dice_hand(self):
        for _ in range(self.num_die + 1):
            self.die_counter += 1
            self.dh_dict[self.die_counter] = self.roll_die()
        return self.dh_dict
Chris Freeman
Chris Freeman
Treehouse Moderator 67,736 Points

Comment move to end of thread (sorry using iPhone and added my comment in the wrong place)

Hi, Chris. Thank you for continuing to help! Once I get this figured out, I think so much will fall together.

So, I'm guessing, from your question, that dh_dict=dh_dict isn't defined anywhere. But, it also shouldn't be dh_dict2. dh_dict2 is created in the DiscardHand.save_dice() function and will be passed to the NextRoll class (still being written).

The dh_dict is returned from the DiceHand.dice_hand() function. This filled dictionary needs to come into the DiscardHand class to be used by the DiscardHand.reroll() and DiscardHand.save_dice() functions. So, what I was trying to do (unsuccessfully - you can see my confusion) is to tell the super().__init__ () function to pack up dh_dict in the passenger seat and bring it along - because it is defined in the parent class (DiceHand).

If I don't give any arguments to super().__init__(), it will bring in dh_dict, but it is always empty.

Does what I'm trying to do make sense? What am I missing?

3 Answers

The value of try_die_roll is set at try_die_roll = DieRoll.roll_die(try_die), and is a random number. You then used try_die_hand = DiceHand.dice_hand(try_die_roll), which passes a random number into DiceHand.dice_hand and uses it as the value for self, so self.num_die tries to get the attribute num_die from your random number.

You could create an instance of DiceHand with curr_hand = DiceHand() and use it to get a value for try_die_hand by try_die_hand = curr_hand.dice_hand(). You could also simplify try_die_roll = DieRoll.roll_die(try_die) as try_die_roll = try_die.roll_die().

Instead of def __init__(self, num_die=5, dh_dict=None, die_counter=0):, have you considered using {} as the default value for dh_dict, as in def __init__(self, num_die=5, dh_dict={}, die_counter=0):?

Thank you. I'm still working through the whole answer, but I have one question right off. Setting the dh_dict to an empty dictionary by default is exactly how I started, but PyCharm threw warning - not an error, but a warning. It said a default parameter should not be mutable. Any ideas why, or if it is the best solution, despite the warning?

I have more questions. I am really trying to understand this and finish an object oriented project, so I'm just going to keep asking questions, because I'm not understanding what I'm not understanding. Any help is appreciated.

When I use try_die_roll = try_die.roll_die() I get an attribute error - Die object has no attribute 'roll_die.'

When I use try_die_roll = try_die.DiceRoll.roll_die() I get an attribute error - Die object has no attribute 'DiceRoll.'

But, try_die_roll = DieRoll.roll_die(try_die) functions correctly.

What am I missing here?

Thanks, again!

If you want to use the roll_die method on try_die, then try_die would need to be an instance of DieRoll rather than of Die, so you would need to have try_die = DieRoll(), since the class DieRoll has a roll_die method and the class Die does not.

Next question...

Thank you in advance for everyone's patience.

When I implement

try_curr_hand = DiceHand()

try_die_hand = try_curr_hand.dice_hand()

it returns "DiceHand object has no attribute 'value'"

whereas the orginal - try_die_hand = DiceHand.dice_hand(try_die_roll)

or try_die_hand = DiceHand.dice_hand(5)'

returns 'int' object has no attribute 'num_die'

It is a different error, and sometimes that is a good start, but why does self.value in Die suddenly not exist?

Yea! Okay. I got the roll_die() method to work on try_die! This makes more sense to me. I didn't understand I could call a class without first instantiating the parent. I don't think we ever did that in the video. So, if I'm understanding correctly, I only need to call the Die class if I want to change the default parameters. For example, when I wanted to use a coin instead of a standard die, or to add a value for testing purposes. Thank you!

In the class DiceHand, instead of super(DieRoll).__init__(), use super().__init__().

Apparently, Python does weird things with mutable default parameters, so keep dh_dict=None, and inside your init function, you can check if dh_dict is None and set it to {}.

Thank you for the clear explanation of how I was calling in a random number. I understand much better (and had a true face-palm moment).
Here's my follow-up question.

When I call dice_hand, why do I need arguments at all? Every parameter has a default, so it seems like it should call with just the parenthesis.

It works!

Yea!

Thank you so much!!

Never mind the previous question. It solved itself when I changed the super argument.

Ummmm... sigh.... I got excited too soon...

It does run now, but it always calls the same random number. Every time I run the program, it is a different number, but all six values in the dictionary are the same.

For example:

{1: 3, 2: 3, 3: 3, 4: 3, 5: 3, 6: 3}

Any ideas?

If I'm seeing it correctly, self.roll_die is only running once. This goes back to the if-else clause in the roll_die function. It states that if value is already assigned, then it takes that value. Kenneth started with this logic in the videos to add testing functionality. I obviously haven't thought about it too much, but I don't know how to preserve that testing functionality while still calling for consecutive random numbers.

You could create a new instance of DiceRoll whenever you want a new random value, and call roll_die() on the new instance of DiceRoll, but then you might want to reconsider DiceHand inheriting from DiceRoll,

You could change def roll_die(self): to def roll_die(self, modify_value=False):and then recalculate the value if you pass in True, but I don't know if that will preserve testing functionality enough for you.

Chris Freeman
Chris Freeman
Treehouse Moderator 67,736 Points

PyCharm is correct that a mutable object is a poor choice for a parameter default since the values in the mutable object may persist between calls. This side effect and be purposefully used but with caution.

Chris Freeman
Chris Freeman
Treehouse Moderator 67,736 Points

Since DiceHand derives from DieRoll which derives from Die there is only one value attribute that is set on the first call of roll_die. Subsequent calls to roll_die see self.value is None as False returning literally the same value each time.

Perhaps DiceHand is not a subclass of DieRoll but instead, calls self.dice_hand() from __init__ to create self.dh_dict using separate instances of DieRoll().

refactoring now.... Conflating classes and figuring out what to do with the value= for testing. I'm not sure if I need it, but I suppose it is good practice. It will be helpful when it comes to scoring.

This is soooo helpful! You all are the best!
I will post back when I have more - hopefully a solution!

Okay. Here's my solution so far. It works! So exciting!

Another question follows these two sets of code...

# die.py

import random


class DieRoll:
    # creates a single die
    def __init__(self, sides=6, value=None):
        self.sides = sides
        self.value = value

    def __int__(self):
        return int(self)

    def __add__(self, other):
        return int(self) + other


    # rolls a single die
    def roll_die(self):

        self.value = random.randint(1, self.sides)
        return self.value

        # This code is for testing the score-sheets - it is activated when needed and then rehashed out.
        #
        # if self.value is None:
        #     self.value = random.randint(1, self.sides)
        #     return self.value
        # else:
        #     return self.value

I decided to go the simple route and simply employ different code when I need to test the score-sheet. It isn't the most graceful way, but it is the simplest to keep me moving on this project. I might come back at the very end and see if this can be improved.

I also conflated these two classes (Die and DieRoll). It seems like a die would always be rolled, so it wasn't of much value to have them separately.

#dice_hand_file.py

from die import DieRoll


class DiceHand(DieRoll):
    def __init__(self, num_die=5, dh_dict=None, die_counter=0):
        super().__init__()
        # creates a dictionary or clears the dictionary for the next hand
        if dh_dict is None:
            dh_dict = {}
        else:
            dh_dict.clear()
        self.num_die = num_die
        self.dh_dict = dh_dict
        self.die_counter = die_counter


    # returns a dictionary with the die number(key) and the value rolled by that die(value)
    def dice_hand(self):
        for _ in range(self.num_die):
            self.die_counter += 1
            self.dh_dict[self.die_counter] = self.roll_die()
        return self.dh_dict

This part works great! Thank you for your help! At least for now, I have decided to keep this class separate in order to have clarity with the next class, but I might eventually follow your advice and conflate it with the DieRoll class. I just can't quite see how that would work right now.

I can't figure out how to initialize the next class. I seem to be missing an important key in understanding super(). I'm pretty sure there are some mistakes in the methods of the code, also, but I can't get past the instantiation to work on them. For example, I'm pretty sure the reroll_ask will return the location of the map object in memory instead of a list, and I'm not sure how to fix that.

# continues from above in die_hand_file.py

class DiscardHand(DiceHand):
    def __init__(self):
        super().__init__()

    #  This was my first try at the init  -- to say the least, it didn't work.... sigh...
    # def __init__(self, dh_dict, reroll_input, reroll, num_die_2, dh_dict_2):
    #     super().__init__(num_die=self.num_die, dh_dict=self.dh_dict, die_counter=self.die_counter)
    #     self.dh_dict = dh_dict
    #     self.reroll_input = reroll_input
    #     self.reroll = reroll
    #     self.num_die_2 = num_die_2
    #     self.dh_dict_2 = dh_dict_2

    # chooses the dice to reroll in next hand and creates list of the index of those dice
    def reroll_ask(self):
        self.reroll_input = input(f'Which die would you like to re-roll, by die number? (ex 1, 3, 4)   ')
        self.reroll = self.reroll_input.split(',*')
        self.reroll = map(int, self.reroll)
        return self.reroll

    # The number of dice to be rerolled in next hand
    def next_num_die(self):
        self.num_die_2 = len(self.dh_dict) - len(self.reroll)
        return self.num_die_2

    # removes the reroll dice from hand and keeps the saved dice in the dictionary
    def save_dice(self):
        self.dh_dict_2 = self.dh_dict
        for i in self.reroll:
            self.dh_dict_2 = self.dh_dict.pop(i)
        return self.dh_dict_2

The first attempt just broke the program. The second thing I tried was to add the dh_dict to both init's . That also brought errors. The version above doesn't draw errors, but it also doesn't bring in the dictionary that was just created.

Just for a complete view, here's my main.py file, also.

# main.py


from die import DieRoll
from dice_hand_file import DiceHand, DiscardHand

try_die = DieRoll()
try_die_roll = try_die.roll_die()
print(try_die_roll)


curr_try_hand = DiceHand()
try_die_hand = curr_try_hand.dice_hand()
print(try_die_hand)

try_discard = DiscardHand()
print(try_discard.reroll_ask())
print(try_discard.next_num_die())
print(try_discard.save_dice())

Again, thank you so much for your help, and thank you in advance to everyone who helps from here on out!
I will understand OOP! Soon!

I added the =False functionality to the RollDie class (see below). It works exactly as it should for one die. Thank you!
My sticking point is that I can't figure out how to pass that die along to the DiceHand class to roll an entire hand of the appointed value. How does that work?

I'm guessing that problem wouldn't happen if I had combined the DieRoll and DieHand classes, as you suggested. I'm starting to see the logic there. My confusion is why Kenneth kept them apart in the video. It could make sense to me if everything about rolling the dice was in one class, with other classes for players, scoring, and scoresheets. It could also make sense that the dice hand is a different object than an individual die. I'm trying to understand why it would be better one way or the other - or is it just preference?

    # rolls a single die
    def roll_die(self, test_vlaue=False):

        if test_vlaue is False:
            self.value = random.randint(1, self.sides)
            return self.value
        else:
            return self.value
test_die = DieRoll(6, 5)
test_die_roll = test_die.roll_die(True)
# this works.  It will roll a 5 every time.
print(test_die_roll)
test_hand = DiceHand()
curr_try_hand  = test_hand.dice_hand()
# this doesn't work.  It will give random numbers.
print(f'test hand{curr_try_hand}')
    #     super().__init__(num_die=self.num_die, dh_dict=self.dh_dict, die_counter=self.die_counter)

You haven't defined self.num_die, self.dh_dict, or self.die_counter before that point in the program. You could try passing your parameters into init using super().__init__(num_die=num_die, dh_dict=dh_dict, die_counter=die_counter) and remove the other lines in the constructor that set self.num_die, self.dh_dict, and self.die_counter after calling super.

If you want self.reroll to be a list, you could try self.reroll = list(map(int, self.reroll)).

Chris Freeman
Chris Freeman
Treehouse Moderator 67,736 Points

It looks like DiscardHand is a DiceHand with a few more methods. This structure is fine if that is the goal. You don't have to define DiscardHand.__init__ if it is to behave exactly like the __init__ in DiceHand (that's how inheritance is supposed to work!). A call to DiscardHand() will call theDiceHand version automatically and will expect the same parameters.

If the DiscardHand.__init__ method has additional work to be done, then use the catchall:

class DiscardHand(DiceHand):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # other work to be done here

Instead of

    def dice_hand(self):
        for _ in range(self.num_die):
            self.die_counter += 1
            self.dh_dict[self.die_counter] = self.roll_die()
        return self.dh_dict

you could try something like

    def dice_hand(self):
        temp_sides = self.sides
        self.sides = self.sides ** self.num_die
        temp_value = self.roll_die()
        self.sides = temp_sides
        for counter in range(self.num_die):
            self.dh_dict[counter] = (temp_value % self.sides) + 1
            temp_value = temp_value // self.sides
        return self.dh_dict

This won't directly work when you start re-rolling values, but it would give you random values for each of your dice. Instead of calling self.roll_die() for each dice, it calls it once for a die with more sides, one side for each possible permutation of rolls of the dice. Then for each die, it sets self.dh_dict[counter] to a number between 1 and self.sides based on the random value. It then does integer division on the random value to get another value to compare against.

When I tried running dice_hand multiple times while using self.die_counter to place things in self.dh_dict, the dictionary kept getting longer as the same values were added to it each time I called dice_hand, since self.die_counter was not reset and never decreased.

jb30 - Thank you so much for continuing to help!
I noticed the same thing. I got around it by creating a new instance of the DiceHand for each call, which is SLOPPY, but it worked. I cannot figure out why it is doing that, because there is nothing in obvious logic which would cause it! I was worried that when I got to the re-roll of the second dice, that it would give me a double dictionary, so maybe your way would work better. Right now, I'm still trying to get the dictionary passed into the DiscardHand function. That function seems to be working, but it always starts with an empty dictionary... As I said earlier, I think there is something fundamental I'm not understanding about super() and init()... More about that in my next question.

I think I figured out the map function in the DiscardHand class. If I specifically change it to a list in every place, it will work. I still haven't figured out how to take the dictionary from the DiceHand class and use it in the DiscardHand class. I had the same problem with the test value for the Die passing into the DiceHand class, and I decided it wasn't important enough to mess with just for testing. But this, on the other hand, is necessary.

In the code below, other than dh_dict staying outside of the class, everything works (as far as I can tell). In order to make it work, I had to give every parameter a default value. This doesn't make sense, and isn't what I've seen in the (many) videos and blogs I've watched/read. The other option would be to define those attributes outside of the init, but PyCharm tells me this is unacceptable, even though it does run.

In the code below, I tried many things in the super()__init__(), but the one which seems most likely to work is dh_dict=dh_dict. This returns dh_dict is undefined. I also commented out the dh_dict.clear from the init method of the DiceHand, just in case I was somehow clearing it before I passed it on. But, this doesn't seem to make a difference.

# --- main.py ---

from die import DieRoll
from dice_hand_file import DiceHand, DiscardHand# , DiceHand2

# prints one roll of one die
try_die = DieRoll()
try_die_roll = try_die.roll_die()
print(try_die_roll)

# *******************************************************************
# prints one die with a value of 5.  In theory, this should also print an entire hand with values of 5, but it doesn't.
# It also only accepts arguments in this format.  Using value=5, isn't accepted.
test_die = DieRoll(6, 5)
test_die_roll = test_die.roll_die(True)
print(test_die_roll)
test_hand = DiceHand()
curr_try_hand = test_hand.dice_hand()
print(f'test hand{curr_try_hand}')

# ***************************************************************
curr_try_hand = DiceHand()
try_die_hand = curr_try_hand.dice_hand()
print(try_die_hand)

# **************************************************************
try_discard = DiscardHand()
print(try_discard.reroll_ask())
print(try_discard.next_num_die())
print(try_discard.save_dice())
# die.py
# I will probably change this per suggestion, but this is as it stands for the moment.

import random


class DieRoll:
    # creates a single die
    def __init__(self, sides=6, value=None):
        self.sides = sides
        self.value = value

    def __int__(self):
        return int(self)

    def __add__(self, other):
        return int(self) + other


    # rolls a single die
    def roll_die(self, test_vlaue=False):

        if test_vlaue is False:
            self.value = random.randint(1, self.sides)
            return self.value
        else:
            return self.value
#dice_hand_file.py

from die import DieRoll


class DiceHand(DieRoll):
    def __init__(self, num_die=5, dh_dict=None, die_counter=0, *args, **kwargs):
        super().__init__()
        if dh_dict is None:
            dh_dict = {}
        # else:
         #    dh_dict.clear()
        self.num_die = num_die
        self.dh_dict = dh_dict
        self.die_counter = die_counter

    # returns a dictionary with the die number(key) and the value rolled by that die(value)
    def dice_hand(self):
        for _ in range(self.num_die):
            self.die_counter += 1
            self.dh_dict[self.die_counter] = self.roll_die()
        return self.dh_dict


class DiscardHand(DiceHand):

    def __init__(self, reroll_input='', reroll=None, num_die_2=0, dh_dict_2=None, *args, **kwargs):
        super().__init__(dh_dict=dh_dict)
        if reroll is None:
            reroll = []
        if dh_dict_2 is None:
            dh_dict_2 = {}
        # self.dh_dict = dh_dict
        self.reroll_input = reroll_input
        self.reroll = reroll
        self.num_die_2 = num_die_2
        self.dh_dict_2 = dh_dict_2

    # chooses the dice to reroll in next hand and creates list of the index of those dice
    def reroll_ask(self):
        self.reroll_input = input(f'Which die would you like to re-roll, by die number? (ex 1, 3, 4)   ')
        self.reroll = self.reroll_input.split(',*')
        self.reroll = map(int, self.reroll)
        return list(self.reroll)

    # The number of dice to be rerolled in next hand
    def next_num_die(self):
        self.num_die_2 = len(self.dh_dict) - len(list(self.reroll))
        return self.num_die_2

    # removes the reroll dice from hand and keeps the saved dice in the dictionary
    def save_dice(self):
        self.dh_dict_2 = self.dh_dict
        for i in self.reroll:
            self.dh_dict_2 = self.dh_dict.pop(i)
        return self.dh_dict_2
Chris Freeman
Chris Freeman
Treehouse Moderator 67,736 Points

(comment moved from above)

In this snippet, where is dh_dict defined?

class DiscardHand(DiceHand):

    def __init__(self, reroll_input='', reroll=None, num_die_2=0, dh_dict_2=None, *args, **kwargs):
        super().__init__(dh_dict=dh_dict)
# should this be dh_dict2?      ^^^^^^^

In your code

class DiceHand(DieRoll):
    def __init__(self, num_die=5, dh_dict=None, die_counter=0, *args, **kwargs):
        super().__init__()

have you tried using super().__init__(*args, **kwargs) instead? That should allow you to do test_hand = DiceHand(value=5).

super() gets called as the first line of the function. Changing values of variables later in the function shouldn't affect what happens when super() gets called.

If you don't pass arguments into super(), then your superclass would need default values for its parameters because it's expecting values for all of its parameters. Your superclass won't need default values for parameters that you pass to it every time.

# The number of dice to be rerolled in next hand
    def next_num_die(self):
        self.num_die_2 = len(self.dh_dict) - len(list(self.reroll))
        return self.num_die_2

It's possible to get a negative number as self.num_die_2. It also looks like you are returning the number of dice that you aren't rerolling in the next hand contrary to the comment above it.

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 67,736 Points

Hey amandae, I wanted to start a new thread regarding super(). It doesn't start with the inheritance. It ends there.

A class is defined with it's attributes and methods. When running the class code, any reference not found locally is searched for in any inherited ("parent") classes. Inheriting is really just including the parent classes namespace.

When a new class instance is created, the default __new__ method (usually inherited from the base object class) is run. By default the __new__ methods calls the __init__ method. The first __init__ found will be run (locally or from an inherited class). But what if we want to run the local __init__ and one from a parent? This is the purpose of calling super().

Calling super() simply says "pause here to run the next found init method down the chain of inherited classes". This can cause a daisy chain of calls if an inherited class also utilizes super().

So, running the code try_discard = DiscardHand() does the following

* calls DiscardHand.__new__(*args, **kwargs)
    * calls DiscardHand.__init__(*args, **kwargs)
        * running DiscardHand.__init__(*args, **kwargs)
        * calls super().__init__(dh_dict=dh_dict) ==> DiceHand.__init__(dh_dict=dh_dict)
            * running DiceHand.__init__(dh_dict=dh_dict)  **(note `dh_dict` not defined yet)**
            * calls super().__init__()  ==>  DieRoll.__init__()
                * running DieRoll.__init__()

                    self.sides = sides
                    self.value = value

                * return to `DiceHand` 
            * contiinue in DiceHand.__init__

                if dh_dict is None:
                    dh_dict = {}
                # else:
                #     dh_dict.clear()
                self.num_die = num_die
                self.dh_dict = dh_dict
                self.die_counter = die_counter

            * return to `DiscardHand`
        * contiinue in DiscardHand.__init__

            if reroll is None:
                reroll = []
            if dh_dict_2 is None:
                dh_dict_2 = {}
            # self.dh_dict = dh_dict
            self.reroll_input = reroll_input
            self.reroll = reroll
            self.num_die_2 = num_die_2
            self.dh_dict_2 = dh_dict_2

        * return to DiscardHand.__new__
    * return new instance of `DiscardHand` which is assigned to `try_discard`

Note that the self in all this code points to the same newly created instance.

Running your code gets as predicted:

$ python main.py 
5
5
test hand{1: 3, 2: 4, 3: 6, 4: 6, 5: 1}
{1: 3, 2: 1, 3: 2, 4: 3, 5: 3}
Traceback (most recent call last):
  File "main.py", line 25, in <module>
    try_discard = DiscardHand()
  File "/home/chris/devel/Treehouse/forum/amandae/dice_hand_file.py", line 28, in __init__
    super().__init__(dh_dict=dh_dict)
NameError: name 'dh_dict' is not defined

jb30 and Chris Freeman - Thank you, so much! I will work on this more and post back later - probably Monday.

Half of my original answer is inaccurate, so this answer should get Best Answer.

Chris Freeman
Chris Freeman
Treehouse Moderator 67,736 Points

jb30 , one can always edit comments for clarity and correctness 🙂

Hi. Chris Freeman and jb30.
Thank you so much for all of your help on this one! I have put lots of the little pieces together, and understand SO MUCH better than I did before. I am going to leave this for a while and come back to it at a later point. I think to go any farther is above my head right now. I wish I could give both of you best answers!
Thank you, again!

Chris Freeman
Chris Freeman
Treehouse Moderator 67,736 Points

You’re very welcome! Please give jb30 the Best Answer award. (I’ve also recommended them to be a moderator for the quality of their answers)