← Computer Programming II

Problem 1: The Animal Kingdom

A zoo is building a simple tracking system. They already have a base Animal class and need a Dog class that inherits from it.

  1. Create a class Animal with an __init__ method that accepts name and sound, and stores them as instance attributes.
  2. Add a method speak to Animal that prints: <name> says <sound>!
  3. Create a class Dog that inherits from Animal.
  4. The Dog class’s __init__ should accept name and breed, call the parent’s __init__ with name and the sound "Woof", and store breed as an instance attribute.
  5. Add a method info to Dog that prints: <name> is a <breed>

Input

dog = Dog("Buddy", "Labrador")
dog.speak()
dog.info()

Expected Output

Buddy says Woof!
Buddy is a Labrador

Problem 2: Employee Roster

A company needs a simple employee system. There is a base Employee class, and a Manager class that extends it with override behavior.

  1. Create a class Employee with an __init__ that accepts name and salary, and stores them as instance attributes.
  2. Add a method describe to Employee that prints: <name>, salary: <salary>
  3. Create a class Manager that inherits from Employee.
  4. The Manager class’s __init__ should accept name, salary, and department, call the parent’s __init__, and store department as an instance attribute.
  5. Override the describe method in Manager so it first calls the parent’s describe, then prints: Department: <department>

Input

e = Employee("Ali", 5000)
m = Manager("Nilufar", 9000, "Engineering")
e.describe()
print("---")
m.describe()

Expected Output

Ali, salary: 5000
---
Nilufar, salary: 9000
Department: Engineering

Problem 3: Smart Home

A smart home system is made up of individual devices. Each device has a name and can be turned on or off. A House brings multiple devices together and can report the status of the entire home.

Design the following classes:

  1. Device — stores name and tracks whether it is on or off. Provides turn_on(), turn_off(), and status() (returns "<name>: ON" or "<name>: OFF").
  2. SmartLight(Device) — a device with an additional brightness level (integer, 0–100). Overrides status() to also show brightness, but only when the light is on.
  3. House — accepts a name and holds a list of devices. Provides add_device(device) and report() which prints the house name followed by the status of every device, each on its own line.

Input

light = SmartLight("Living Room Light", 75)
fan = Device("Ceiling Fan")

light.turn_on()

house = House("My Home")
house.add_device(light)
house.add_device(fan)
house.report()

Expected Output

My Home
Living Room Light: ON (brightness: 75)
Ceiling Fan: OFF

Problem 4: Media Library

A streaming service organizes its catalog of media. Every item shares common traits, but books and audiobooks have additional details. A Library manages the full collection.

  • Media — has title and year. Its info() returns "<title> (<year>)".
  • Book(Media) — adds author. Its info() extends the parent’s result with " by <author>".
  • AudioBook(Book) — adds narrator. Its info() extends the parent’s result with ", narrated by <narrator>".
  • Library — has name and manages a collection of media items. Provides add(media) and catalog() which prints the library name, then each item’s info() on its own line.

Each level of info() should build on the previous using super().

Input

m = Media("Interstellar OST", 2014)
b = Book("Clean Code", 2008, "Robert C. Martin")
a = AudioBook("Dune", 1965, "Frank Herbert", "Scott Brick")

lib = Library("City Library")
lib.add(m)
lib.add(b)
lib.add(a)
lib.catalog()

Expected Output

City Library
Interstellar OST (2014)
Clean Code (2008) by Robert C. Martin
Dune (1965) by Frank Herbert, narrated by Scott Brick

Problem 5: Package Processing Pipeline

A logistics company processes packages through different stations. Each station performs a step and then delegates to the next handler in the chain via cooperative super() calls.

  • Handler — base class. Its process(package) prints "Handling <label>".
  • Scanner(Handler) — prints "Scanning <label>", then delegates up.
  • Weigher(Handler) — prints "Weighing <label>: <weight>kg", then delegates up.
  • AutomatedStation(Scanner, Weigher) — combines both capabilities. Prints "--- Automated Station ---", then delegates up.
  • Package — has label and weight.
  • Warehouse — has name and a list of stations. Provides add_station(station) and process_package(package) which prints the warehouse name, then runs the package through every station.

Input

pkg = Package("PKG-001", 3.5)

auto = AutomatedStation()
manual = Scanner()

wh = Warehouse("Central Hub")
wh.add_station(auto)
wh.add_station(manual)
wh.process_package(pkg)

Expected Output

Central Hub
--- Automated Station ---
Scanning PKG-001
Weighing PKG-001: 3.5kg
Handling PKG-001
Scanning PKG-001
Handling PKG-001

Problem 6: Shopping Cart

An online store needs a cart system. Items have prices, some items are discounted, and the system must gracefully reject bad data.

  • Item — has name and price. The price must be protected with a property that raises ValueError if set to a negative value. Supports __str__ (format: "<name>: $<price>" with 2 decimal places) and __eq__ (two items are equal if they share the same name). Has a cost() method that returns the price.
  • DiscountedItem(Item) — adds discount (a float between 0 and 1). Overrides cost() to apply the discount. Overrides __str__ to show original and final price (format: "<name>: $<price> -> $<cost> (-<percent>%)").
  • Cart — holds items via composition. Supports add(item), __len__ (number of items), __add__ (merging two carts into a new one), total() (sum of all item costs), and summary() (prints each item then a total line).
  • load_cart(data) — a standalone function that takes a list of dictionaries, creates the appropriate Item or DiscountedItem for each entry, catches any ValueError, prints a skip message, and returns a filled Cart.

Input

data1 = [
    {"name": "Laptop", "price": 1000},
    {"name": "Mouse", "price": -50},
    {"name": "Keyboard", "price": 75, "discount": 0.2},
]
data2 = [
    {"name": "Monitor", "price": 300, "discount": 0.1},
]

cart1 = load_cart(data1)
cart2 = load_cart(data2)

merged = cart1 + cart2
merged.summary()

Expected Output

Skipped: Price cannot be negative: -50
Laptop: $1000.00
Keyboard: $75.00 -> $60.00 (-20%)
Monitor: $300.00 -> $270.00 (-10%)
Total: $1330.00 (3 items)

Problem 7: Dungeon Crawler

Problem 7

A text-based RPG needs a character and combat system. Design it using inheritance and composition.

Composition pieces:

  1. Weapon — has name and power (int). Method strike() returns the power value.
  2. Shield — has name and block_value (int). Method block(damage) returns the damage reduced by block_value (minimum 0).

Character hierarchy:

  1. Character — has name, hp (int), and an optional weapon (starts as None).
    • equip(weapon) — sets the character’s weapon.
    • attack(other) — if a weapon is equipped, calls other.take_damage(weapon.strike()) and prints: <name> attacks <other.name> for <damage> damage; if no weapon, prints: <name> has no weapon!
    • take_damage(amount) — reduces hp by amount (minimum 0). Prints: <name> takes <amount> damage (<hp> HP remaining)
    • is_alive() — returns True if hp > 0.
  2. Knight(Character) — adds a shield attribute (starts as None).
    • equip_shield(shield) — sets the knight’s shield.
    • Overrides take_damage: if a shield is equipped, uses the shield’s block() to reduce the incoming damage first, then calls the parent’s take_damage with the reduced amount.
  3. Berserker(Character) — adds a rage attribute (starts at 0).
    • Overrides attack: if rage >= 5 and a weapon is equipped, calls rage_strike(other) instead of a normal attack. Otherwise, calls the parent’s attack, then increases rage by 2.
    • Overrides take_damage: calls the parent’s take_damage, then increases rage by 1.
    • rage_strike(other) — if a weapon is equipped, deals weapon.strike() + rage damage to other via other.take_damage(), prints <name> RAGE STRIKES <other.name> for <total_damage> damage, then resets rage to 0. If no weapon, prints: <name> has no weapon!

Composition container:

  1. Arena — has name. Method duel(c1, c2) prints the arena name, then simulates rounds: in each round c1 attacks c2, then if c2 is still alive, c2 attacks c1. Rounds repeat until one character falls. After the loop, prints: Winner: <name>

Input

sword = Weapon("Iron Sword", 15)
axe = Weapon("Battle Axe", 20)
buckler = Shield("Buckler", 8)

knight = Knight("Arthas", 50)
knight.equip(sword)
knight.equip_shield(buckler)

berserker = Berserker("Grom", 50)
berserker.equip(axe)

arena = Arena("Thunderdome")
arena.duel(knight, berserker)

Expected Output

Thunderdome
Arthas attacks Grom for 15 damage
Grom takes 15 damage (35 HP remaining)
Grom attacks Arthas for 20 damage
Arthas takes 12 damage (38 HP remaining)
Arthas attacks Grom for 15 damage
Grom takes 15 damage (20 HP remaining)
Grom attacks Arthas for 20 damage
Arthas takes 12 damage (26 HP remaining)
Arthas attacks Grom for 15 damage
Grom takes 15 damage (5 HP remaining)
Grom RAGE STRIKES Arthas for 27 damage
Arthas takes 19 damage (7 HP remaining)
Arthas attacks Grom for 15 damage
Grom takes 15 damage (0 HP remaining)
Winner: Arthas

Problem 8: Robot Race

Problem 8

A robotics competition simulates a race on a straight track. Different robot types have different movement strategies. The one that crosses the finish line first wins — but burning fuel too fast might leave you stranded.

Composition pieces:

  1. Battery — has capacity (int) and charge (starts equal to capacity).
    • use(amount) — if charge >= amount, reduces charge and returns True; otherwise returns False.
    • is_empty() — returns True if charge <= 0.

Robot hierarchy:

  1. Robot — has name, position (starts at 0), and a battery (composition).
    • move() — tries to use 1 charge from the battery. If successful, increases position by 1 and prints: <name> moves to position <position>. Otherwise prints: <name> is out of charge!
  2. TurboBot(Robot) — a fast but fuel-hungry robot.
    • Overrides move(): tries to use 2 charge. If successful, increases position by 3 and prints the move message. Otherwise prints the out-of-charge message.
  3. SteadyBot(Robot) — a consistent robot that rests every 3rd move to conserve energy.
    • Has an internal _step_count (starts at 0).
    • Overrides move(): increments _step_count by 1. If _step_count equals 3, prints <name> rests and resets _step_count to 0 (no charge used, no movement). Otherwise, tries to use 1 charge; if successful, increases position by 2 and prints the move message. Otherwise prints the out-of-charge message.

Race simulation:

  1. Racetrack — has name and finish_line (int).
    • race(robots) — prints === <name> (finish: <finish_line>) ===, then simulates rounds starting from 1:
      1. Print -- Round <n> --
      2. Each robot calls move()
      3. If any robot’s position >= finish_line, print Winner: <name> and stop
      4. If all robots’ batteries are empty, print No one finished! and stop

Input

blaze = TurboBot("Blaze", Battery(4))
pixel = SteadyBot("Pixel", Battery(6))

track = Racetrack("Robo Rally", 7)
track.race([blaze, pixel])

Expected Output

=== Robo Rally (finish: 7) ===
-- Round 1 --
Blaze moves to position 3
Pixel moves to position 2
-- Round 2 --
Blaze moves to position 6
Pixel moves to position 4
-- Round 3 --
Blaze is out of charge!
Pixel rests
-- Round 4 --
Blaze is out of charge!
Pixel moves to position 6
-- Round 5 --
Blaze is out of charge!
Pixel moves to position 8
Winner: Pixel