Learning OOP: Encapsulation, inheritance, aggregation vs composition, and more!

I’m taking a Udemy course by an instructor named Estafania Navone who is teaching Python OOP. While it is not for a college/school credit, I am treating this as a real homework assignment so for those who are willing to read my thread and provide feedback, I ask that you do not provide the solution or even a partial solution. Please just provide hints and tips.

I am about one third through the course. The first major project is a “Dice Game” intended to teach OOP principles covered so far such as:

For this project the instructor essentially provides the specs and pseudo code. Here it is. The task is for her students to model the game in OOP.

My best attempt you can find at the bottom of this post. When I run the script in my shell, nothing happens. It doesn’t even get stuck in a perpetual loop (I use two while loops). To remedy this I declared a game_obj at the very bottom like:

game_obj = DiceGame.play()

and:

game_obj = DiceGame(human).play

I am way off. I am stabbing in the dark here. Although I can already identify a number of problem areas like:

  1. At lines 33 and 34 for the init method of the third class definition, I really struggled to distinguish the human player as True and the computer player as False. I realize I need a switch somehow to toggle between the two bools but the required Python syntax to achieve this desired result escapes me. I figure the answer to this frustrating pain point is trivial and obvious.
  2. At line 53 (and even lines 55-63), I am pretty sure that the way I am referring to the two counters for the different player types (e.g. self.human.counter) is wrong. Any guidance and suggestions on what I could try instead here would be great.
  3. For the two setters in the second class (Player) for the winning and losing mechanic I am not sure if I should be using a return statement or if I could just increment / decrement the self.counter attribute and leave it at that.
  4. I’ve added the suffix self liberally to every attribute whenever my text editor’s linter complained but I am thinking there may be certain variables with self added where there shouldn’t be.

Tips / guidance / suggestions welcome.

Script so far:

from random import randint


class Die:
    def __init__(self):
        self.value = None

    def roll(self):
        value = randint(1,6)
        return value
    

class Player:
    def __init__(self, Dice):
        self.die_inst = Dice        
        self.player_type = True # True = human (default) or False = computer
        self.counter = 10

    def set_win(self):
        self.counter -= 1
        return self.counter
    
    def set_lose(self):
        self.counter += 1
        return self.counter
    
    def get_roll(self, Die):
        return Die.roll

class DiceGame(Die,Player):

    def __init__(self,player_type):
        self.human = Player.player_type # True
        self.computer = Player.player_type # False


    def play(self):
        print("Welcome to the OOP dice game!")
        while self.counter != 0:
            self.start_round()
            continue
        print(f"Game Over! The winner is {self.Player.counter==0}")


    def start_round(self):
        input("Press any key to begin the (next) round.")

        dice_roll1 = Player.get_roll
        dice_roll2 = Player.get_roll
        
        while self.human:
            
            print(f"Current score\n- human: {self.human.counter} and computer: {self.computer.counter}")
            
            if dice_roll1 > dice_roll2:
                self.human.set_win
                self.computer.set_lose
                print(f"Round winner is {self.human}")
                
            elif dice_roll1 < dice_roll2:
                self.computer.set_win
                self.human.set_lose
                print(f"Round winner is {self.computer}")
            
            elif dice_roll1 == dice_roll2:
                print("Tie. Play another round.")

            continue

A few points:

You use the inheritance in the wrong way. DiceGame is NOT a Die and definitely is NOT a Player. Your DiceGame should contain a die and two players.

In this way you return the function object. If rather you want to call that method then you must add () at the end of roll

Same story here.

This may be a surprise, but your program does not contain any player! The class player is sort of a template to create different players. If you want to create a player, you need to instantiate a player from the class.

I created a smaller Player class to show the mechanics:

>>> class Player():
...     def __init__(self, name):
...         self.name = name
...     def roll_dice(self):
...         print(self.name, "rolled", randint(1,6))
... 
>>> person = Player("George")
>>> computer = Player("Notebook")
>>> computer.roll_dice()
Notebook rolled 2
>>> person.roll_dice()
George rolled 5

person and computer are the actual players. They can do anything defined in the Player class, like roll_dice and have properties defined in the Player class, like name.

So you do not need to :

Each player is an instance of Player.

There are more issues in your program, to me finding those out is one of the interesting tasks in programming, so I will not go into those. Just one:

If you write

that does not execute the method. You need to follow it by the parenthesis, just as in

Have fun!

What exactly do you want to happen, instead?

In your own words, exactly what do you believe is the overall purpose of the existing code - how much do you expect it to accomplish? (Hint: what do you believe the class keyword does; and what do you believe is the purpose of a class?)

Also, so that I can understand how we got to this point: what did your previous exercises and assignments look like? Have you had to write class at all before this project, for example?