🏗️ Lesson 6.1: Classes and Objects
So far, you've used built-in data types — strings, numbers, lists, dictionaries. But what if you need a custom type? A Player with a name and health? A BankAccount with a balance and withdrawal rules? That's what classes are for. They let you create your own blueprints for data and behavior.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain why object-oriented programming (OOP) exists and when it helps
- Define a class and create objects (instances) from it
- Add attributes (data) and methods (behavior) to a class
- Understand self / this — the reference to the current object
- Write a constructor that initializes objects with starting data
- Create multiple objects from the same class with different data
Estimated Time: 60 minutes
Project: Build a Player class for a simple RPG inventory system
📑 In This Lesson
Why Object-Oriented Programming?
Until now, your data and your functions have lived separately. You might have a dictionary for a player and a function that operates on it:
# Data — just a dictionary
player = {"name": "Alice", "health": 100, "inventory": []}
# Functions that operate on that data
def take_damage(player, amount):
player["health"] -= amount
if player["health"] < 0:
player["health"] = 0
def add_item(player, item):
player["inventory"].append(item)
take_damage(player, 30)
add_item(player, "sword")
print(f"{player['name']}: {player['health']} HP") # Alice: 70 HP
// Data — just an object
let player = { name: "Alice", health: 100, inventory: [] };
// Functions that operate on that data
function takeDamage(player, amount) {
player.health -= amount;
if (player.health < 0) player.health = 0;
}
function addItem(player, item) {
player.inventory.push(item);
}
takeDamage(player, 30);
addItem(player, "sword");
console.log(`${player.name}: ${player.health} HP`); // Alice: 70 HP
// Without OOP, C# would use static methods + arrays/dicts
// But C# is built for OOP — this is the "fighting the language" way
// You'd have to use dictionaries or parallel arrays
// and pass them into every function. It gets messy fast.
This works for small programs, but it has problems as your code grows:
| Problem | Why It Matters |
|---|---|
| Data and behavior are separated | You have to remember which functions go with which data. Nothing enforces it. |
| No structure guarantee | Any code could set player["health"] = "banana" — no guardrails. |
| Hard to reuse | Creating 50 enemies means 50 dictionaries with no guarantee they all have the right keys. |
| Difficult to scale | As features grow, you end up with dozens of loose functions and no clear organization. |
OOP solves this by bundling data and behavior together into a single unit called a class.
🎓 Instructor Note: Delivery Guidance
Start by showing the "before" code above and asking: "What happens if we misspell a key? What if we forget to initialize inventory?" Let students feel the pain of loose data before introducing classes as the solution. The RPG/game metaphor works extremely well here — students immediately grasp "a blueprint for creating characters." C# students will feel right at home since the language was designed around OOP. Python and JS students may need more motivation since those languages work fine without classes for small programs.
Classes vs. Objects
These two terms get used a lot, so let's nail them down:
🏗️ Class = Blueprint
A class defines what something is — what data it holds and what it can do. Think of it like a cookie cutter or an architect's blueprint. It's not a cookie or a house — it's the plan for making them.
🎯 Object = Instance
An object (also called an instance) is a specific thing created from a class. From a single Player class, you can create player1, player2, player3 — each with their own name, health, and inventory.
name, health, inventory
take_damage(), add_item()"] A -->|"new"| B["🎮 player1
Alice, 100 HP
[sword]"] A -->|"new"| C["🎮 player2
Bob, 85 HP
[shield, potion]"] A -->|"new"| D["🎮 player3
Charlie, 100 HP
[]"] style A fill:#3b82f6,stroke:#2563eb,color:#fff style B fill:#22c55e,stroke:#16a34a,color:#fff style C fill:#22c55e,stroke:#16a34a,color:#fff style D fill:#22c55e,stroke:#16a34a,color:#fff
| Concept | Real-World Analogy | Code Example |
|---|---|---|
| Class | Cookie cutter / Blueprint / Recipe | class Player |
| Object | A specific cookie / A built house / A baked cake | player1 = Player("Alice", 100) |
| Attribute | Color of the house / Flavor of the cake | player1.name, player1.health |
| Method | Opening the door / Slicing the cake | player1.take_damage(30) |
Defining a Class
Let's start with the simplest possible class — one with just a constructor that stores some data.
# Define the class
class Player:
pass # Empty class (placeholder)
# Create objects (instances)
player1 = Player()
player2 = Player()
# They're separate objects
print(player1) # <__main__.Player object at 0x...>
print(player1 == player2) # False — different objects!
print(type(player1)) # <class '__main__.Player'>
// Define the class
class Player {
// Empty class (placeholder)
}
// Create objects (instances)
let player1 = new Player();
let player2 = new Player();
// They're separate objects
console.log(player1); // Player {}
console.log(player1 === player2); // false — different objects!
console.log(typeof player1); // "object"
console.log(player1 instanceof Player); // true
// Define the class
class Player
{
// Empty class (placeholder)
}
// Create objects (instances)
Player player1 = new Player();
Player player2 = new Player();
// They're separate objects
Console.WriteLine(player1); // Player
Console.WriteLine(player1 == player2); // False
Console.WriteLine(player1.GetType().Name); // Player
⚠️ Naming Convention: Classes Use PascalCase
In all three languages, class names start with a capital letter and use PascalCase: Player, BankAccount, ShoppingCart. Variables holding instances use camelCase (JS/C#) or snake_case (Python): player1, myAccount, shopping_cart.
Syntax Comparison
| Feature | 🐍 Python | ⚡ JavaScript | 🔷 C# |
|---|---|---|---|
| Define a class | class Player: |
class Player { } |
class Player { } |
| Create an instance | Player() |
new Player() |
new Player() |
Keyword new? |
No | Yes | Yes |
| Braces or indentation? | Indentation | Braces { } |
Braces { } |
🎓 Instructor Note: Delivery Guidance
Highlight that Python doesn't use new — calling Player() looks just like calling a function. JS and C# both require new. This is a great time to reinforce the JS/C# "syntax siblings" theme from earlier modules. Also note: the empty class is useless on its own — it's just to show the minimal syntax. The constructor in the next section is where it gets useful.
Constructors: Initializing Objects
A constructor is a special method that runs automatically when you create an object. It sets up the object's initial data (attributes). Every language has its own syntax for this.
class Player:
def __init__(self, name, health=100):
self.name = name # Instance attribute
self.health = health # Instance attribute with default
self.inventory = [] # Always starts empty
# Create players
player1 = Player("Alice")
player2 = Player("Bob", 85)
print(player1.name) # Alice
print(player1.health) # 100
print(player2.health) # 85
print(player1.inventory) # []
class Player {
constructor(name, health = 100) {
this.name = name; // Instance property
this.health = health; // Instance property with default
this.inventory = []; // Always starts empty
}
}
// Create players
let player1 = new Player("Alice");
let player2 = new Player("Bob", 85);
console.log(player1.name); // Alice
console.log(player1.health); // 100
console.log(player2.health); // 85
console.log(player1.inventory); // []
class Player
{
public string Name;
public int Health;
public List<string> Inventory;
// Constructor — same name as the class
public Player(string name, int health = 100)
{
Name = name;
Health = health;
Inventory = new List<string>();
}
}
// Create players
Player player1 = new Player("Alice");
Player player2 = new Player("Bob", 85);
Console.WriteLine(player1.Name); // Alice
Console.WriteLine(player1.Health); // 100
Console.WriteLine(player2.Health); // 85
Console.WriteLine(player1.Inventory.Count); // 0
Constructor Syntax Comparison
| Feature | 🐍 Python | ⚡ JavaScript | 🔷 C# |
|---|---|---|---|
| Constructor name | __init__ |
constructor |
Same as class name |
| Self reference | self (explicit first param) |
this (implicit) |
this (implicit, often omitted) |
| Set attribute | self.name = name |
this.name = name; |
Name = name; |
| Default params | health=100 |
health = 100 |
int health = 100 |
💡 Python's self — The Explicit Difference
Python is unique: you must write self as the first parameter of every method. JavaScript and C# provide this automatically — you never list it as a parameter. This is one of the biggest syntax differences in OOP across these three languages.
Methods: Adding Behavior
Attributes store data. Methods are functions defined inside a class that define what the object can do. Methods can read and modify the object's own attributes.
class Player:
def __init__(self, name, health=100):
self.name = name
self.health = health
self.inventory = []
def take_damage(self, amount):
self.health -= amount
if self.health < 0:
self.health = 0
print(f"{self.name} takes {amount} damage! HP: {self.health}")
def heal(self, amount):
self.health += amount
if self.health > 100:
self.health = 100
print(f"{self.name} heals {amount}! HP: {self.health}")
def add_item(self, item):
self.inventory.append(item)
print(f"{self.name} picked up {item}")
def show_status(self):
items = ", ".join(self.inventory) if self.inventory else "empty"
print(f"{self.name} | HP: {self.health} | Items: {items}")
# Use it
hero = Player("Alice")
hero.add_item("sword")
hero.add_item("potion")
hero.take_damage(35)
hero.heal(20)
hero.show_status()
# Alice | HP: 85 | Items: sword, potion
class Player {
constructor(name, health = 100) {
this.name = name;
this.health = health;
this.inventory = [];
}
takeDamage(amount) {
this.health -= amount;
if (this.health < 0) this.health = 0;
console.log(`${this.name} takes ${amount} damage! HP: ${this.health}`);
}
heal(amount) {
this.health += amount;
if (this.health > 100) this.health = 100;
console.log(`${this.name} heals ${amount}! HP: ${this.health}`);
}
addItem(item) {
this.inventory.push(item);
console.log(`${this.name} picked up ${item}`);
}
showStatus() {
let items = this.inventory.length ? this.inventory.join(", ") : "empty";
console.log(`${this.name} | HP: ${this.health} | Items: ${items}`);
}
}
// Use it
let hero = new Player("Alice");
hero.addItem("sword");
hero.addItem("potion");
hero.takeDamage(35);
hero.heal(20);
hero.showStatus();
// Alice | HP: 85 | Items: sword, potion
class Player
{
public string Name;
public int Health;
public List<string> Inventory;
public Player(string name, int health = 100)
{
Name = name;
Health = health;
Inventory = new List<string>();
}
public void TakeDamage(int amount)
{
Health -= amount;
if (Health < 0) Health = 0;
Console.WriteLine($"{Name} takes {amount} damage! HP: {Health}");
}
public void Heal(int amount)
{
Health += amount;
if (Health > 100) Health = 100;
Console.WriteLine($"{Name} heals {amount}! HP: {Health}");
}
public void AddItem(string item)
{
Inventory.Add(item);
Console.WriteLine($"{Name} picked up {item}");
}
public void ShowStatus()
{
string items = Inventory.Count > 0
? string.Join(", ", Inventory) : "empty";
Console.WriteLine($"{Name} | HP: {Health} | Items: {items}");
}
}
// Use it
Player hero = new Player("Alice");
hero.AddItem("sword");
hero.AddItem("potion");
hero.TakeDamage(35);
hero.Heal(20);
hero.ShowStatus();
// Alice | HP: 85 | Items: sword, potion
✅ Notice the Pattern
Compare the "before OOP" code at the top of this lesson with the class version. Instead of take_damage(player, 30), you write hero.take_damage(30). The data and behavior live together — the object knows how to operate on itself. This is the core idea of OOP.
Method Syntax Comparison
| Feature | 🐍 Python | ⚡ JavaScript | 🔷 C# |
|---|---|---|---|
| Define a method | def heal(self, amount): |
heal(amount) { } |
public void Heal(int amount) { } |
| Call a method | hero.heal(20) |
hero.heal(20) |
hero.Heal(20) |
function keyword? |
def |
No (inside class) | No (return type instead) |
| Access modifier? | Not required | Not required | public / private |
🎓 Instructor Note: Delivery Guidance
Walk through building the class method by method, not all at once. Start with just __init__, create an object, access its attributes. Then add take_damage, test it. Then heal, test it. This incremental approach lets students see how each piece works before the full class appears. Emphasize: methods are just functions that live inside a class and have access to self/this.
Understanding self / this
The trickiest concept for beginners: when a method runs, how does it know which object's data to use? That's what self (Python) and this (JS/C#) do — they're a reference to the specific object that called the method.
class Dog:
def __init__(self, name):
self.name = name # self = the object being created
def bark(self):
# self = the object calling this method
print(f"{self.name} says Woof!")
fido = Dog("Fido")
rex = Dog("Rex")
fido.bark() # "Fido says Woof!" — self is fido
rex.bark() # "Rex says Woof!" — self is rex
# What Python actually does behind the scenes:
# fido.bark() → Dog.bark(fido)
# rex.bark() → Dog.bark(rex)
class Dog {
constructor(name) {
this.name = name; // this = the object being created
}
bark() {
// this = the object calling this method
console.log(`${this.name} says Woof!`);
}
}
let fido = new Dog("Fido");
let rex = new Dog("Rex");
fido.bark(); // "Fido says Woof!" — this is fido
rex.bark(); // "Rex says Woof!" — this is rex
// Note: 'this' in JavaScript can be tricky in callbacks
// (we'll cover that in Lesson 17)
class Dog
{
public string Name;
public Dog(string name)
{
Name = name; // 'this' is implicit
// this.Name = name; ← also valid, but not needed here
}
public void Bark()
{
// 'this' is the object calling the method
Console.WriteLine($"{Name} says Woof!");
// Console.WriteLine($"{this.Name} says Woof!"); ← same thing
}
}
Dog fido = new Dog("Fido");
Dog rex = new Dog("Rex");
fido.Bark(); // "Fido says Woof!" — this is fido
rex.Bark(); // "Rex says Woof!" — this is rex
name = "Fido" fido-->>Code: "Fido says Woof!" Code->>rex: rex.bark() Note right of rex: self/this = rex
name = "Rex" rex-->>Code: "Rex says Woof!"
💡 Key Insight
self / this is like a pronoun. When you write self.name, you're saying "my name" — where "my" refers to whichever specific object is running the method. The same method code runs on different data depending on which object calls it.
Multiple Objects, One Blueprint
The whole point of classes is creating multiple objects that share structure but have independent data.
class Player:
def __init__(self, name, health=100):
self.name = name
self.health = health
self.inventory = []
def add_item(self, item):
self.inventory.append(item)
def show_status(self):
items = ", ".join(self.inventory) if self.inventory else "empty"
print(f"{self.name} | HP: {self.health} | Items: {items}")
# Create three independent players
alice = Player("Alice")
bob = Player("Bob", 85)
charlie = Player("Charlie")
# Each has their OWN data
alice.add_item("sword")
bob.add_item("shield")
bob.add_item("potion")
charlie.add_item("bow")
alice.show_status() # Alice | HP: 100 | Items: sword
bob.show_status() # Bob | HP: 85 | Items: shield, potion
charlie.show_status() # Charlie | HP: 100 | Items: bow
# Modifying one doesn't affect the others!
alice.health = 50
print(bob.health) # 85 — unchanged
class Player {
constructor(name, health = 100) {
this.name = name;
this.health = health;
this.inventory = [];
}
addItem(item) { this.inventory.push(item); }
showStatus() {
let items = this.inventory.length ? this.inventory.join(", ") : "empty";
console.log(`${this.name} | HP: ${this.health} | Items: ${items}`);
}
}
// Create three independent players
let alice = new Player("Alice");
let bob = new Player("Bob", 85);
let charlie = new Player("Charlie");
// Each has their OWN data
alice.addItem("sword");
bob.addItem("shield");
bob.addItem("potion");
charlie.addItem("bow");
alice.showStatus(); // Alice | HP: 100 | Items: sword
bob.showStatus(); // Bob | HP: 85 | Items: shield, potion
charlie.showStatus(); // Charlie | HP: 100 | Items: bow
// Modifying one doesn't affect the others!
alice.health = 50;
console.log(bob.health); // 85 — unchanged
class Player
{
public string Name;
public int Health;
public List<string> Inventory;
public Player(string name, int health = 100)
{
Name = name;
Health = health;
Inventory = new List<string>();
}
public void AddItem(string item) => Inventory.Add(item);
public void ShowStatus()
{
string items = Inventory.Count > 0
? string.Join(", ", Inventory) : "empty";
Console.WriteLine($"{Name} | HP: {Health} | Items: {items}");
}
}
// Create three independent players
Player alice = new Player("Alice");
Player bob = new Player("Bob", 85);
Player charlie = new Player("Charlie");
// Each has their OWN data
alice.AddItem("sword");
bob.AddItem("shield");
bob.AddItem("potion");
charlie.AddItem("bow");
alice.ShowStatus(); // Alice | HP: 100 | Items: sword
bob.ShowStatus(); // Bob | HP: 85 | Items: shield, potion
charlie.ShowStatus(); // Charlie | HP: 100 | Items: bow
// Modifying one doesn't affect the others!
alice.Health = 50;
Console.WriteLine(bob.Health); // 85 — unchanged
Objects in Collections
Objects are values — you can store them in lists, loop over them, filter them, and pass them to functions. This is where OOP starts to feel powerful.
class Player:
def __init__(self, name, health=100):
self.name = name
self.health = health
self.inventory = []
def is_alive(self):
return self.health > 0
def show_status(self):
status = "alive" if self.is_alive() else "defeated"
print(f" {self.name}: {self.health} HP ({status})")
# Create a party of adventurers
party = [
Player("Alice", 100),
Player("Bob", 45),
Player("Charlie", 0),
Player("Diana", 78)
]
# Loop over objects
print("=== Party Status ===")
for player in party:
player.show_status()
# Filter: who's still alive?
alive = [p for p in party if p.is_alive()]
print(f"\nSurvivors: {[p.name for p in alive]}")
# Survivors: ['Alice', 'Bob', 'Diana']
# Find: who has the lowest health (among the living)?
weakest = min(alive, key=lambda p: p.health)
print(f"Needs healing: {weakest.name} ({weakest.health} HP)")
# Needs healing: Bob (45 HP)
# Total party health
total_hp = sum(p.health for p in party)
print(f"Total party HP: {total_hp}") # 223
class Player {
constructor(name, health = 100) {
this.name = name;
this.health = health;
this.inventory = [];
}
isAlive() { return this.health > 0; }
showStatus() {
let status = this.isAlive() ? "alive" : "defeated";
console.log(` ${this.name}: ${this.health} HP (${status})`);
}
}
// Create a party of adventurers
let party = [
new Player("Alice", 100),
new Player("Bob", 45),
new Player("Charlie", 0),
new Player("Diana", 78)
];
// Loop over objects
console.log("=== Party Status ===");
party.forEach(p => p.showStatus());
// Filter: who's still alive?
let alive = party.filter(p => p.isAlive());
console.log(`\nSurvivors: ${alive.map(p => p.name)}`);
// Find: lowest health among the living
let weakest = alive.reduce((min, p) => p.health < min.health ? p : min);
console.log(`Needs healing: ${weakest.name} (${weakest.health} HP)`);
// Total party health
let totalHP = party.reduce((sum, p) => sum + p.health, 0);
console.log(`Total party HP: ${totalHP}`); // 223
class Player
{
public string Name;
public int Health;
public Player(string name, int health = 100)
{
Name = name;
Health = health;
}
public bool IsAlive() => Health > 0;
public void ShowStatus()
{
string status = IsAlive() ? "alive" : "defeated";
Console.WriteLine($" {Name}: {Health} HP ({status})");
}
}
// Create a party of adventurers
var party = new List<Player>
{
new Player("Alice", 100),
new Player("Bob", 45),
new Player("Charlie", 0),
new Player("Diana", 78)
};
// Loop over objects
Console.WriteLine("=== Party Status ===");
party.ForEach(p => p.ShowStatus());
// Filter: who's still alive?
var alive = party.Where(p => p.IsAlive()).ToList();
Console.WriteLine($"\nSurvivors: {string.Join(", ", alive.Select(p => p.Name))}");
// Find: lowest health among the living
var weakest = alive.OrderBy(p => p.Health).First();
Console.WriteLine($"Needs healing: {weakest.Name} ({weakest.Health} HP)");
// Total party health
int totalHP = party.Sum(p => p.Health);
Console.WriteLine($"Total party HP: {totalHP}"); // 223
✅ Collections + OOP = Real Programs
This is exactly how real applications work: a shopping cart is a list of Product objects, a chat app stores a list of Message objects, a game manages a list of Entity objects. The collection patterns you learned in Module 5 (filter, map, reduce, find) apply directly to lists of your own objects.
🎓 Instructor Note: Delivery Guidance
This section ties Module 5 directly to Module 6. Point out that party.filter(p => p.isAlive()) is the same pattern as scores.filter(s => s >= 70) — just with objects instead of numbers. Students who were comfortable with filter/map/reduce should see this as a natural extension, not something new. The RPG party example is engaging and immediately relatable.
Exercises
🏋️ Exercise 1: BankAccount Class
Objective: Create a BankAccount class with:
- Constructor that takes
owner(string) and optionalbalance(default 0) deposit(amount)— adds to balance, prints confirmationwithdraw(amount)— subtracts from balance, but rejects if insufficient fundsget_balance()/getBalance()— returns the current balance
Test with two accounts performing deposits and withdrawals.
✅ Solution
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount <= 0:
print("Deposit must be positive!")
return
self.balance += amount
print(f"{self.owner}: Deposited ${amount:.2f}. Balance: ${self.balance:.2f}")
def withdraw(self, amount):
if amount <= 0:
print("Withdrawal must be positive!")
return
if amount > self.balance:
print(f"{self.owner}: Insufficient funds! Balance: ${self.balance:.2f}")
return
self.balance -= amount
print(f"{self.owner}: Withdrew ${amount:.2f}. Balance: ${self.balance:.2f}")
def get_balance(self):
return self.balance
# Test
acct1 = BankAccount("Alice", 1000)
acct2 = BankAccount("Bob")
acct1.deposit(500) # Alice: Deposited $500.00. Balance: $1500.00
acct1.withdraw(200) # Alice: Withdrew $200.00. Balance: $1300.00
acct2.deposit(100) # Bob: Deposited $100.00. Balance: $100.00
acct2.withdraw(150) # Bob: Insufficient funds! Balance: $100.00
class BankAccount {
constructor(owner, balance = 0) {
this.owner = owner;
this.balance = balance;
}
deposit(amount) {
if (amount <= 0) { console.log("Deposit must be positive!"); return; }
this.balance += amount;
console.log(`${this.owner}: Deposited $${amount.toFixed(2)}. Balance: $${this.balance.toFixed(2)}`);
}
withdraw(amount) {
if (amount <= 0) { console.log("Withdrawal must be positive!"); return; }
if (amount > this.balance) {
console.log(`${this.owner}: Insufficient funds! Balance: $${this.balance.toFixed(2)}`);
return;
}
this.balance -= amount;
console.log(`${this.owner}: Withdrew $${amount.toFixed(2)}. Balance: $${this.balance.toFixed(2)}`);
}
getBalance() { return this.balance; }
}
// Test
let acct1 = new BankAccount("Alice", 1000);
let acct2 = new BankAccount("Bob");
acct1.deposit(500); // Alice: Deposited $500.00. Balance: $1500.00
acct1.withdraw(200); // Alice: Withdrew $200.00. Balance: $1300.00
acct2.deposit(100); // Bob: Deposited $100.00. Balance: $100.00
acct2.withdraw(150); // Bob: Insufficient funds! Balance: $100.00
class BankAccount
{
public string Owner;
public decimal Balance;
public BankAccount(string owner, decimal balance = 0)
{
Owner = owner;
Balance = balance;
}
public void Deposit(decimal amount)
{
if (amount <= 0) { Console.WriteLine("Deposit must be positive!"); return; }
Balance += amount;
Console.WriteLine($"{Owner}: Deposited ${Balance:F2}. Balance: ${Balance:F2}");
}
public void Withdraw(decimal amount)
{
if (amount <= 0) { Console.WriteLine("Withdrawal must be positive!"); return; }
if (amount > Balance)
{
Console.WriteLine($"{Owner}: Insufficient funds! Balance: ${Balance:F2}");
return;
}
Balance -= amount;
Console.WriteLine($"{Owner}: Withdrew ${amount:F2}. Balance: ${Balance:F2}");
}
public decimal GetBalance() => Balance;
}
// Test
BankAccount acct1 = new BankAccount("Alice", 1000);
BankAccount acct2 = new BankAccount("Bob");
acct1.Deposit(500); // Alice: Deposited $500.00. Balance: $1500.00
acct1.Withdraw(200); // Alice: Withdrew $200.00. Balance: $1300.00
acct2.Deposit(100); // Bob: Deposited $100.00. Balance: $100.00
acct2.Withdraw(150); // Bob: Insufficient funds! Balance: $100.00
🏋️ Exercise 2: RPG Party Manager
Objective: Using the Player class from this lesson, write code that:
- Creates a party of 4 players with different names and health values
- Gives each player at least one inventory item
- Simulates a battle: deal random damage (10–40) to each player
- After the battle, prints: who survived, who was defeated, total party HP remaining
- Finds the player with the most items in their inventory
✅ Solution
import random
class Player:
def __init__(self, name, health=100):
self.name = name
self.health = health
self.inventory = []
def take_damage(self, amount):
self.health -= amount
if self.health < 0:
self.health = 0
def add_item(self, item):
self.inventory.append(item)
def is_alive(self):
return self.health > 0
# Build the party
party = [
Player("Alice", 100),
Player("Bob", 80),
Player("Charlie", 90),
Player("Diana", 70)
]
# Give items
party[0].add_item("sword")
party[0].add_item("shield")
party[1].add_item("bow")
party[2].add_item("staff")
party[2].add_item("potion")
party[2].add_item("scroll")
party[3].add_item("dagger")
# Simulate battle
print("=== Battle! ===")
for player in party:
damage = random.randint(10, 40)
player.take_damage(damage)
status = "survived" if player.is_alive() else "DEFEATED"
print(f" {player.name} took {damage} damage → {player.health} HP ({status})")
# Results
survivors = [p for p in party if p.is_alive()]
defeated = [p for p in party if not p.is_alive()]
total_hp = sum(p.health for p in party)
print(f"\nSurvivors: {[p.name for p in survivors]}")
print(f"Defeated: {[p.name for p in defeated]}")
print(f"Total HP remaining: {total_hp}")
# Most items
most_items = max(party, key=lambda p: len(p.inventory))
print(f"Most items: {most_items.name} ({len(most_items.inventory)} items)")
class Player {
constructor(name, health = 100) {
this.name = name;
this.health = health;
this.inventory = [];
}
takeDamage(amount) {
this.health -= amount;
if (this.health < 0) this.health = 0;
}
addItem(item) { this.inventory.push(item); }
isAlive() { return this.health > 0; }
}
// Build the party
let party = [
new Player("Alice", 100),
new Player("Bob", 80),
new Player("Charlie", 90),
new Player("Diana", 70)
];
// Give items
party[0].addItem("sword"); party[0].addItem("shield");
party[1].addItem("bow");
party[2].addItem("staff"); party[2].addItem("potion"); party[2].addItem("scroll");
party[3].addItem("dagger");
// Simulate battle
console.log("=== Battle! ===");
for (let player of party) {
let damage = Math.floor(Math.random() * 31) + 10;
player.takeDamage(damage);
let status = player.isAlive() ? "survived" : "DEFEATED";
console.log(` ${player.name} took ${damage} damage → ${player.health} HP (${status})`);
}
// Results
let survivors = party.filter(p => p.isAlive());
let defeated = party.filter(p => !p.isAlive());
let totalHP = party.reduce((sum, p) => sum + p.health, 0);
console.log(`\nSurvivors: ${survivors.map(p => p.name)}`);
console.log(`Defeated: ${defeated.map(p => p.name)}`);
console.log(`Total HP remaining: ${totalHP}`);
let mostItems = party.reduce((max, p) =>
p.inventory.length > max.inventory.length ? p : max);
console.log(`Most items: ${mostItems.name} (${mostItems.inventory.length} items)`);
class Player
{
public string Name;
public int Health;
public List<string> Inventory = new();
public Player(string name, int health = 100)
{ Name = name; Health = health; }
public void TakeDamage(int amount)
{ Health -= amount; if (Health < 0) Health = 0; }
public void AddItem(string item) => Inventory.Add(item);
public bool IsAlive() => Health > 0;
}
var party = new List<Player>
{
new Player("Alice", 100), new Player("Bob", 80),
new Player("Charlie", 90), new Player("Diana", 70)
};
party[0].AddItem("sword"); party[0].AddItem("shield");
party[1].AddItem("bow");
party[2].AddItem("staff"); party[2].AddItem("potion"); party[2].AddItem("scroll");
party[3].AddItem("dagger");
var rng = new Random();
Console.WriteLine("=== Battle! ===");
foreach (var p in party)
{
int damage = rng.Next(10, 41);
p.TakeDamage(damage);
string status = p.IsAlive() ? "survived" : "DEFEATED";
Console.WriteLine($" {p.Name} took {damage} damage → {p.Health} HP ({status})");
}
var survivors = party.Where(p => p.IsAlive()).ToList();
var defeated = party.Where(p => !p.IsAlive()).ToList();
int totalHP = party.Sum(p => p.Health);
Console.WriteLine($"\nSurvivors: {string.Join(", ", survivors.Select(p => p.Name))}");
Console.WriteLine($"Defeated: {string.Join(", ", defeated.Select(p => p.Name))}");
Console.WriteLine($"Total HP remaining: {totalHP}");
var mostItems = party.OrderByDescending(p => p.Inventory.Count).First();
Console.WriteLine($"Most items: {mostItems.Name} ({mostItems.Inventory.Count} items)");
🎓 Instructor Note: Delivery Guidance
Exercise 1 (BankAccount) reinforces the core class mechanics: constructor, methods, guarding data with conditions. Exercise 2 (RPG Party) ties OOP to collections from Module 5 and adds randomness for engagement. Students will get different results each run, which makes it fun to compare. Challenge fast students: add a transfer(from_account, to_account, amount) function or a Player.attack(target) method that deals damage to another player.
Summary
🎉 Key Takeaways
- OOP bundles data (attributes) and behavior (methods) into classes
- A class is a blueprint; an object is a specific instance created from it
- Constructors initialize objects: Python
__init__, JSconstructor, C# same-name-as-class - self / this refers to the current object — Python requires it as an explicit parameter, JS/C# provide it implicitly
- Methods are functions that live inside a class and operate on the object's data
- Objects are values — you can store them in lists, filter them, loop over them, and pass them to functions
- JS and C# use
newto create instances; Python does not
🚀 What's Next?
You can now create classes with attributes and methods. In the next lesson, we'll go deeper into properties, methods, and constructors — including return values from methods, string representations (__str__ / toString), and controlling access to data with public/private visibility.
🎯 Quick Check
Question 1: What is the Python constructor method called?
Question 2: Which language requires new to create an object?
Question 3: What does self (Python) or this (JS/C#) refer to inside a method?