CompSci.rocks
FRQcsapa

Robot: 2004 FRQ 4

A step-by-step solution to the 2004 AP CSA FRQ 4 (Robot), covering conditional wall-detection logic, a direction-reversing state machine, and a counting while loop in Java.

A tile-cleaning robot that reverses direction whenever it hits a wall is the subject of this AP Computer Science A free-response question — its behavior is built up in three small pieces: detecting a wall, making a single move, and running moves in a loop until the whole hallway is empty.

What This FRQ Tests

  • AP CSA units: Unit 3 (Boolean Expressions and if Statements), Unit 4 (Iteration), and Unit 5 (Writing Classes)
  • Core skill: tracking and updating an object's own state (its position and facing direction) correctly across repeated method calls
  • Secondary skill: writing a counting while loop that calls other instance methods instead of duplicating their logic
  • Official category: "Methods and Control Structures," which on the 2004 exam was FRQ 4 — the fixed FRQ 1–4 category order used in more recent years (Methods and Control Structures, Classes, Array/ArrayList, 2D Array, always in that sequence) wasn't standardized until the 2019–2020 Course and Exam Description redesign. 2004's actual printed order put this question last, after WordList (Array/ArrayList, FRQ 1), the Pet/Cat/LoudDog/Kennel hierarchy (Classes, FRQ 2), and a Marine Biology Simulation question (FRQ 3, skipped on this site — see the note below).

The Setup

  • The Robot class has three fields (given, not written):
    • private int[] hall — the number of items remaining on each tile
    • private int pos — the robot's current tile index
    • private boolean facingRighttrue means facing toward higher-numbered tiles
  • A move, as defined by the question, works like this:
    1. If there are items on the current tile, one is removed.
    2. If items still remain on the current tile after that, the robot stays put, facing the same way.
    3. If no items remain on the current tile: (a) advance forward if that direction isn't blocked by a wall; (b) otherwise, reverse direction and stay on the same tile.
  • hallIsClear() — already implemented, not something you write — returns true once every tile is empty.
  • You're asked to write three methods: forwardMoveBlocked(), move(), and clearHall().

Part (a): Writing forwardMoveBlocked()

The Rule, Broken Down

  • Returns true only when a wall sits immediately in the direction the robot is currently facing.
  • Facing right, the only blocking tile is the highest-numbered one (hall.length - 1).
  • Facing left, the only blocking tile is 0.

Step-by-Step Approach

  1. Check facingRight.
  2. If it's true, the robot is blocked exactly when pos is the last valid index.
  3. If it's false, the robot is blocked exactly when pos is 0.

The Code

private boolean forwardMoveBlocked()
{
    if (facingRight)
    {
        return pos == hall.length - 1;
    }
    else
    {
        return pos == 0;
    }
}

Why Each Piece Matters

  • hall.length - 1, not hall.length — the last valid tile index is always one less than the array's length, since indices start at 0.
  • Two separate returns, one per direction — "immediately in front" refers to a different tile depending on which way the robot is facing, so the check has to branch on facingRight first.

Common Mistakes to Avoid

  • Using hall.length instead of hall.length - 1 for the right-wall check — this compares pos against an index that's one past the last tile, so the check never triggers, leaving the robot permanently unable to detect the right wall.
  • Swapping which condition belongs to which direction (checking pos == 0 while facing right, for example).
  • Changing pos or facingRight inside this method. It only reports whether a wall is there — it must not alter the robot's state.

Part (b): Writing move()

The Rule, Broken Down

  1. Remove one item from the current tile, if any remain.
  2. If that leaves the tile still non-empty, stop there — nothing else about the robot's state changes.
  3. If that leaves the tile empty, either advance (if possible) or reverse (if blocked).

Step-by-Step Approach

  1. If hall[pos] > 0, decrement it by one.
  2. Re-check hall[pos], after that possible decrement.
  3. If it's now 0 (no items remain), decide whether to advance or reverse using forwardMoveBlocked().
  4. Not blocked → move pos one step in the current facing direction. Blocked → flip facingRight instead, and leave pos alone.
  5. If hall[pos] is still greater than 0, do nothing further — the robot simply stays where it is.

The Code

private void move()
{
    if (hall[pos] > 0)
    {
        hall[pos]--;
    }

    if (hall[pos] == 0)
    {
        if (forwardMoveBlocked())
        {
            facingRight = !facingRight;
        }
        else if (facingRight)
        {
            pos++;
        }
        else
        {
            pos--;
        }
    }
}

Why Each Piece Matters

  • hall[pos]-- runs unconditionally first, guarded only by "if there are items" — every call to move() touches the current tile before anything else is decided.
  • The second if (hall[pos] == 0) re-checks the tile after the possible decrement, not before — this is what makes "if there are more items, stay" happen automatically: when the tile still has items left, this whole block is skipped and the method simply ends.
  • forwardMoveBlocked() is called, not reimplemented — the original problem explicitly says solutions that reimplement its logic instead of calling it "will not receive full credit."
  • facingRight = !facingRight flips the boolean directly instead of writing an if/else that assigns true or false — a safe, standard shortcut for "toggle this flag."

Tracing the Example

Using the question's own hallway, hall = [1, 1, 2, 2], and its 9-move sequence — the diagrams don't label the robot's exact starting index in the extracted text, but working backward from the officially given "after move 1" result ([1, 0, 2, 2], meaning tile 1 lost an item) pins the starting position at pos = 1, facing right:

Move Tile acted on hall[pos] after decrement Tile empty now? Blocked? Result
1 1 0 yes no advance to pos = 2
2 2 1 no stay at pos = 2
3 2 0 yes no advance to pos = 3
4 3 1 no stay at pos = 3
5 3 0 yes yes (pos == hall.length - 1) reverse to facing left, stay at pos = 3
6 3 already 0 yes no advance (leftward) to pos = 2
7 2 already 0 yes no advance to pos = 1
8 1 already 0 yes no advance to pos = 0
9 0 0 yes yes (pos == 0) reverse to facing right, stay at pos = 0

This reproduces the hallway contents and the robot's exact position and direction shown in all 9 of the question's diagrams — including the direction flips at move 5 (hitting the right wall) and move 9 (hitting the left wall), and the fact that moves 6 through 8 never touch the array at all, since those tiles are already empty by the time the robot passes back over them.

Common Mistakes to Avoid

  • Checking forwardMoveBlocked() before decrementing hall[pos]. Whether to advance or reverse only matters once the current tile has actually been confirmed empty, which can only be known after the removal happens.
  • Updating both facingRight and pos in the same move by mistake. The rule is strictly either/or: advance forward and keep facing the same way, or reverse direction and don't move at all.
  • Forgetting that a tile can already be empty when move() is called — this happens whenever the robot passes back over a tile it already cleared. The if (hall[pos] > 0) guard on the decrement, followed by unconditionally re-checking hall[pos] == 0 afterward, already handles this correctly without any extra special-casing.

Part (c): Writing clearHall()

The Rule, Broken Down

  • Repeatedly call move() until the hallway has no items left.
  • Return however many moves that took.

Step-by-Step Approach

  1. Start a counter at 0.
  2. Loop while hallIsClear() is false.
  3. Each iteration: call move(), then increment the counter.
  4. Once the loop ends, return the counter.

The Code

public int clearHall()
{
    int numMoves = 0;

    while (!hallIsClear())
    {
        move();
        numMoves++;
    }

    return numMoves;
}

Why Each Piece Matters

  • while (!hallIsClear()), not a loop with a fixed bound — the number of moves needed isn't known ahead of time; it depends entirely on how the simulation plays out.
  • move() and hallIsClear() are both called, not reimplemented — the same "don't duplicate provided functionality" instruction from part (b) applies here too.
  • numMoves++ runs once per call to move(), so the final count always matches the number of times the loop body actually executed.

Tracing the Example

Using the same 9-move sequence traced in part (b): the while loop calls move() once per row of that table, incrementing numMoves each time, and hallIsClear() only returns true once the hallway reaches [0, 0, 0, 0] — which happens right after move 9. So the loop runs exactly 9 times, and clearHall() returns 9 — matching the value the question states directly ("clearHall would take the robot through the moves shown and return 9").

Common Mistakes to Avoid

  • Using an if instead of a while, or omitting the loop entirely. A single move() call only advances the simulation by one step; clearHall() needs to keep going until the hallway is actually empty.
  • Incrementing numMoves in the wrong place relative to the loop, such as before calling move() or outside the loop altogether — the count needs to match the number of times move() actually ran.
  • Re-scanning the hall array directly instead of calling the already-provided hallIsClear(). This duplicates functionality the class already gives you, which the original problem specifically warns against.

Key Takeaways

  • Splitting a stateful simulation into a "detect a condition" method, a "make one state change" method, and a "repeat until done" method keeps each piece small enough to verify against a worked example on its own, while keeping the top-level loop dead simple.
  • Re-checking a condition immediately after acting on it (like re-testing hall[pos] == 0 right after the decrement) is a clean way to express "did that action just finish the job" without duplicating any logic.
  • A while loop bounded by a provided boolean-returning method — not a fixed count — is the right shape whenever the number of iterations needed isn't knowable in advance.

Related FRQs