Concepts / Designing Object State with Instance Variables

Designing Object State with Instance Variables

A GUI object such as the SubKiller boat, bomb, or sub keeps its state in its own instance variables, so state questions are answered by looking at the specific object.

  • Programming
Interactive lab

Try it: Classes, Instances and Attributes

How a class creates instances with __init__ and self, why an attribute read checks the instance's __dict__ before the class, why assigning through an instance never changes the class, how a class-level list is shared by every instance, and why obj.method(x) is Class.method(obj, x).

How it works

  1. Dog(...) makes a new, empty instance and calls Dog.__init__(new_object, ...); self.name = ... stores into that object's __dict__.
  2. Reading obj.attr looks in obj.__dict__ first, then in the class.
  3. Assigning obj.attr = value always writes obj.__dict__, so it shadows a class attribute for that one object only.
  4. Mutating a class-level list (self.tricks.append) changes the one list every instance shares.
  5. obj.method is found on the class and bound to obj, so obj.method(x) runs Class.method(obj, x).

Default run (30 steps): Ready: class Dog and the statements you picked. Watch where each attribute is found and where each assignment writes. … Finished. Printed: ['sit'] / Wolf / Canis familiaris / Fido is 5.

Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. Object numbers are illustrative, not real id() values.

Educational simulation

Loading the simulation…

Why a Game Seems to Have a Mind of Its Own

A graphical game can look like it has a mind of its own. A submarine slides left and right, a bomb drops when you press a key, and a boat moves when you tap an arrow. None of these behaviors is written as one single step in the program. Each one is the visible result of a value being updated somewhere in the code.

This article uses the SubKiller game as a case study to answer one practical question: how do the values that describe these objects get changed, and which event is responsible for each change? Once you can answer that question, you can predict what the program will do next and, when it does something unexpected, point to the exact place where it went wrong.

Where an Object Keeps Its State

Definition

A GUI object keeps its state in its own instance variables. A state question, such as where the sub is right now, is answered by looking at the specific object that holds that information.

In SubKiller, the boat, the depth charge (the bomb), and the submarine are each represented as an object. Each object is defined by its own nested class inside the main panel class. The panel class holds three variables, named boat, bomb, and sub, that refer to those objects. The panel does not store the sub's position itself. The position belongs to the sub object.

This separation has a useful consequence. Because each object carries its own state, you can examine one object without disturbing the others. If you want to know whether the bomb is falling, you look at the bomb's variables, not at the sub's or the boat's.

Two Sources of State Change

Once you know where state is stored, the next question is what causes it to change. The source identifies two distinct sources. The first is the timer, which fires at regular intervals and drives per-frame updates. On each timer event, the action listener coordinates the updates, and each object's updateForNextFrame() method is called so the object can update its own state. The second is the user, whose key presses are handled in a KeyListener's keyPressed() method.

The action listener coordinates the timer-driven updates, but it does not do the work of changing each object. Each object is responsible for updating its own state. Keeping the two sources separate in your mind is the first step toward reading event-driven code without getting lost.

Timerfires every frameKeyListenerkeyPressed()Bomb objectowns its own statedown arrow: start fallingtick: updateForNextFrame()
When the bomb's status changes, which event fires first, which method handles it, and which instance variable does it update?

Tracing the Bomb's Condition

The bomb offers the clearest small picture of a state machine in this program. A state machine treats an object as a set of stored values that move from one condition to another in response to events. The source describes the bomb's condition as either not-falling or falling. Pressing the down arrow key moves it from the first condition to the second.

not fallingstarting conditionfallingupdated each framedown arrow key presstimer tick moves it
After a down arrow press, which condition is the bomb in, and what event keeps it moving on later frames?

The diagram shows only the key-press transition, because the timer does not produce this change. Once the bomb is falling, however, the timer takes over. Every timer event calls updateForNextFrame(), so the falling bomb is moved on each frame. One event type starts the condition, and the other event type carries the object through the frames that follow.

Stand-In Bomb: Reading a State Change Across Two Methods

The SubKiller source is the real program, and its code is more involved than we need here. The short class below is a generated stand-in. It shows the same pattern in a few lines: a bomb-like object with a falling flag, a per-frame update method, and a method that starts the fall. It is not the SubKiller code and does not claim to match the game's numbers. Its purpose is to help you practice reading a state change that is spread across two method calls.

python

Now trace a run. Assume the timer ticks three times before the down arrow is pressed, the key press calls start_falling(), and the timer ticks twice more. Record two values after each event: the falling flag and y. Watch which event changes which value.

Worked example

Tracing the stand-in bomb through timer ticks and a key press

Starting from a new Bomb, three timer ticks occur, then a down arrow press, then two more timer ticks. What are falling and y after each event, and what is the final y?

  1. 1
    Initial state

    A new Bomb has falling set to False and y set to 0.

  2. 2
    Ticks one to three

    Each tick calls update_for_next_frame(). Because falling is False, the if check fails and y stays at 0. The state is unchanged.

  3. 3
    Down arrow press

    The key press calls start_falling(), which sets falling to True. It does not touch y, so y is still 0.

  4. 4
    Tick four

    falling is now True, so the if check passes and y increases by 5 to 5.

  5. 5
    Tick five

    falling is still True, so y increases by another 5 to 10.

Result

After the final tick, falling is True and y is 10. Only the two updates after start_falling() added to y, each by 5.

Output
Event                  falling   y
Start                  False     0
Tick 1                 False     0
Tick 2                 False     0
Tick 3                 False     0
Down arrow             True      0
Tick 4                 True      5
Tick 5                 True      10

Finding Where a Result Diverges

When a program produces a wrong result, the most useful question is not what went wrong overall but at which event the actual state first differed from what you expected. A state machine view helps because every change has a named cause. Walk through the events in order, write down the state after each one, and stop at the first event where your expected state and the actual state disagree.

Next eventtimer tick or key pressApply handlerrecord falling and yMatches expected?compare with tracked valuesFirst divergenceinspect this handlerMore events?continue the traceyesnoyes
Following the timer and key-press events in order, at which step does the object's state stop matching the expected value?

The fault is almost always in the handler for the event where the divergence first appears, or in a state variable that the handler should have changed. In the stand-in bomb, suppose the expected final y is 10 but a program reports 15. Tracing the events in order, the first event whose result departs from the trace is the one where the bomb was expected to start falling. That tells you to inspect start_falling() and the flag it sets, rather than the whole program.

Mistakes When Tracing Object State

  • Common mistake

    Looking in the panel for an object's position

    What it looks like

    Searching the main panel class for where the sub's position is stored.

    Why it fails

    The sub's position is kept in the sub object's own instance variables, not in the panel's variables.

    Do this instead

    Find the object the question is about, then look at its instance variables.

  • Common mistake

    Treating the key press as the movement itself

    What it looks like

    Assuming the bomb moves as soon as the down arrow is pressed, so y should already be 5 after the key press in the stand-in trace.

    Why it fails

    The key press only calls start_falling(), which sets the falling flag. The movement happens in update_for_next_frame() on later timer events.

    Do this instead

    After a key press, check which flag changed. Then look for the timer-driven update that uses that flag.

  • Common mistake

    Ignoring the condition that gates an update

    What it looks like

    Adding 5 to y on every tick, which would give 15 after the three early ticks are counted as well.

    Why it fails

    The update only adds to y when falling is True, so ticks before the key press change nothing.

    Do this instead

    Read the if check in each update method before deciding whether that event changes the state.

  • Common mistake

    Forgetting that each object has its own copy of the same variable name

Practice Tracing the Bomb

MEDIUM

Using the stand-in Bomb class, start from a new Bomb and apply these events in order: one timer tick, a down arrow press, three timer ticks, a second down arrow press, and one more timer tick. Record falling and y after each event. What is y at the end, and at which event did it first change?

Hints
  • Only update_for_next_frame() can change y, and only when falling is True.
  • A second start_falling() call sets falling to True again, but it does not reset y.
  • Count how many ticks happen after the first key press, since each one adds 5 once falling is True.

Keeping the Trace Readable

  • Find the object the question is about, then read its own instance variables.
  • Separate the two sources of change: the timer drives per-frame updates, and key presses are handled in keyPressed().
  • Trace the events in order and record the state after each one.
  • Stop at the first event where the actual state differs from the expected state, then inspect that event's handler.

Key Takeaways

  • A GUI object such as the boat, bomb, or sub keeps its state in its own instance variables, so state questions are answered by looking at that object.
  • State changes come from two sources: the timer, which calls each object's update method on every frame, and user key presses, which are handled in keyPressed().
  • The bomb's condition moves from not-falling to falling on a down arrow press, and the timer then carries it forward on each frame.
  • When a result is wrong, trace the events in order and find the first event where the actual state differs from the expected state.