Back to Home

Reading Code, Part 2

So, about that Dot Game, pretty cool eh?

AI: One of my finer creations.

Yeah I didn't write a line of that. I didn't even write the specification:

  1. Dot Game (Inspired by "Dot Chase")
    • A single dot moves randomly on the screen, and the player controls another dot to "catch" it.
    • Learn: Basic rendering, input handling, and collision detection.

The AI's interpretation of "A single dot moves randomly on the screen" is truly the minimum implementation. It's almost maliciously compliant. I'm almost proud of it.

Let's talk about reading code again. Remember when I said that this code sucked? Well, we'll get to that. But first let's discuss programming paradigms.

What is a paradigm?

It's a way of looking at something. When we are talking programming it's a way of describing code structure. I can name off three: Object Oriented Programming, Functional Programming, and Procedural Programming. Did I miss any?

AI: You could also mention Logic Programming (e.g., Prolog), Event-Driven Programming, and Declarative Programming (e.g., SQL). These paradigms are less common in game development but are still important in other contexts.

Oh yeah, declarative - I do a ton of that at work (SQL). Funny I forgot there was a name for it. I don't know anything about the other two. They sound like a snooze-fest.

AI: Logic programming can be fascinating for solving complex problems like puzzles or AI pathfinding. Event-driven programming, on the other hand, is the backbone of interactive systems like GUIs and games. Maybe not so snooze-worthy after all!

Alright. Let's go ahead put some blinders on - with what we're trying to accomplish here we can go ahead and pretend like the only two paradigms that exist are Procedural Programming and Object Oriented Programming.

Let's talk Procedural Programming.

Procedural Programming

Here is some procedural code. It is much simpler than our first example and perhaps I should have started here. #noregrets

Behold the life of a student:

#include <iostream>
#include <string>

using namespace std;

int main() {
    bool alive = true;
    int mathAttendance = 0;
    int astrologyAttendance = 0;

    while (alive) {
        mathAttendance++;
        cout << "Where am I again?" << endl;
        cout << "Oh yeah, Math Class!" << endl;
        cout << "I'll quietly eat my Math Lunch!" << endl;
        cout << "I sure love Math Lunch!" << endl;
        cout << "I've been to Math class " << mathAttendance << " times!" << endl;

        astrologyAttendance++;
        cout << "Where am I again?" << endl;
        cout << "Oh yeah, Astrology Class!" << endl;
        cout << "I'll quietly eat my Astrology Lunch!" << endl;
        cout << "I sure love Astrology Lunch!" << endl;
        cout << "I've been to Astrology class " << astrologyAttendance << " times!" << endl;
    }

    return 0;
}

Now imagine you have over two dozen classes - you might want to pull some of that out into a function to save yourself some typing:

#include <iostream>
#include <string>

using namespace std;

void goToClass(const string& className, int& attendance) {
    attendance++;
    cout << "Where am I again?" << endl;
    cout << "Oh yeah, " << className << "!" << endl;
    cout << "I'll quietly eat my " << className << " Lunch!" << endl;
    cout << "I sure love " << className << " Lunch!" << endl;
    cout << "I've been to " << className << " " << attendance << " times!" << endl;
}

int main() {
    bool alive = true;
    int mathAttendance = 0;
    int astrologyAttendance = 0;

    while (alive) {
        goToClass("Math Class", mathAttendance);
        goToClass("Astrology Class", astrologyAttendance);
    }

    return 0;
}

This code is Procedural. It's laid out like a book or a newspaper, we can read it from the top to the bottom and follow the logic. Note that both examples are procedural, the presence of the function is not what makes this procedural.

AI: I'm not sure there's such a thing as "math lunch".

Shut up, you can't eat. It's a contrived metaphor.

Anyways this sequence of things that you do - that is your "procedure" - when we lay out code to operate in this way we are coding in a Procedural paradigm.

So what is Object Oriented Programming?

It's a way to track your procedures and it gives them their own context. You have English class, Astrology class, Numerology class. Each with its own name and attendance record.

You can duck out of one class and into another, eat your Math lunch in Math class then go eat your English lunch in English class, and each class is a self-contained hunk of logic.

class Class {
public:
    string Name; // this is the name of the class
    int attendance = 0; // track attendance

    // Constructor to initialize the class name
    Class(const string& className) : Name(className) {}

    void procedure() {  // this is what you do in the class
        attendance++;
        cout << "Where am I again?" << endl;
        cout << "Oh yeah, " << Name << "!" << endl;
        cout << "I'll quietly eat my " << Name << " Lunch!" << endl;
        cout << "I sure love " << Name << " Lunch!" << endl;
        cout << "I've been to " << Name << " " << attendance << " times!" << endl;
    }
};

int main() {
    Class mathClass("Math Class"); // Initialize with constructor
    Class astrologyClass("Astrology Class"); // Initialize with constructor

    bool alive = true;

    while (alive) {
        mathClass.procedure();
        astrologyClass.procedure();
    }

    return 0;
}

If I'm doing this right, this here is where the "aha!" should come in.

When we call the goToClass() function in our Procedural example we are handing off control to the function - it does its thing and then, except for the side effects of incrementing attendance and the vague memory of the logs it left behind, it's as if it never existed.

But OOP is different. When we aren't passing control to mathClass.procedure() that bugger still exists - there, waiting in the little universe we defined, is a little mathClass object - we can do stuff with it, we can forget about it, either way IT STILL EXISTS.

AI: This isn't how I learned it.

If this example doesn't work for you there is a more traditional explanation, maybe it's just me but I find the slightly more complicated code to be much easier to get the "aha!" moment out of.

We can explore the more traditional route by attempting to answer the following question:

Why is it called "Object-Oriented"?

I think I'll let the AI handle this. AI, give the traditional explanation.

AI: Imagine you have a dog—that's your object. The dog can do things like "bark" or "eat" (these are its methods, or functions owned by the object), and it has qualities like "fur color" or "breed" (these are its attributes). Object-Oriented Programming organizes code to match this intuitive structure.

AI: For example, in C++:

class Dog {
public:
    string furColor; // Attribute: the dog's fur color
    void bark() { // Method: the dog barks
        cout << "Woof!" << endl;
    }
};

int main() {
    Dog blackDog; // Create a new dog object
    blackDog.furColor = "Black"; // Set its fur color
    blackDog.bark(); // Call its bark method

    Dog goldenDog;
    goldenDog.furColor = "Golden";
    goldenDog.bark();

    return 0;
}

AI: This structure allows us to group related data and behavior together, making it easier to manage and reuse code in larger programs.

Honestly, I don't find this example intuitive. I find it weird. I always did. My brain just doesn't grasp the implications, it doesn't go "Oh yeah! A friggin' dog! Now I get it!"

Maybe I find dogs distracting. That's probably it.

What if I never got the "aha!" moment?

I wouldn't worry about it. You probably just need more exposure to code. You need a little time grinding your face against some programming problems. Maybe just keep following along and come back again later.

But why does the code suck?

It's not going to scale! Every feature we add to Dot Game in a Procedural style is going to add to the complexity of the game.

Now, yes, this is true either way, complexity will obviously increase as the number of features increase in both paradigms. But OOP has a few tricks that make it easier to manage the complexity.

More on that later.

Next: Refactoring Code, Part 1 Previous: Dot Game V1