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
whileloop 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
Robotclass has three fields (given, not written):private int[] hall— the number of items remaining on each tileprivate int pos— the robot's current tile indexprivate boolean facingRight—truemeans facing toward higher-numbered tiles
- A move, as defined by the question, works like this:
- If there are items on the current tile, one is removed.
- If items still remain on the current tile after that, the robot stays put, facing the same way.
- 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 — returnstrueonce every tile is empty.- You're asked to write three methods:
forwardMoveBlocked(),move(), andclearHall().
Part (a): Writing forwardMoveBlocked()
The Rule, Broken Down
- Returns
trueonly 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
- Check
facingRight. - If it's
true, the robot is blocked exactly whenposis the last valid index. - If it's
false, the robot is blocked exactly whenposis0.
The Code
private boolean forwardMoveBlocked()
{
if (facingRight)
{
return pos == hall.length - 1;
}
else
{
return pos == 0;
}
}
Why Each Piece Matters
hall.length - 1, nothall.length— the last valid tile index is always one less than the array's length, since indices start at0.- 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 onfacingRightfirst.
Common Mistakes to Avoid
- Using
hall.lengthinstead ofhall.length - 1for the right-wall check — this comparesposagainst 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 == 0while facing right, for example). - Changing
posorfacingRightinside 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
- Remove one item from the current tile, if any remain.
- If that leaves the tile still non-empty, stop there — nothing else about the robot's state changes.
- If that leaves the tile empty, either advance (if possible) or reverse (if blocked).
Step-by-Step Approach
- If
hall[pos] > 0, decrement it by one. - Re-check
hall[pos], after that possible decrement. - If it's now
0(no items remain), decide whether to advance or reverse usingforwardMoveBlocked(). - Not blocked → move
posone step in the current facing direction. Blocked → flipfacingRightinstead, and leaveposalone. - If
hall[pos]is still greater than0, 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 tomove()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 = !facingRightflips the boolean directly instead of writing anif/elsethat assignstrueorfalse— 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 decrementinghall[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
facingRightandposin 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. Theif (hall[pos] > 0)guard on the decrement, followed by unconditionally re-checkinghall[pos] == 0afterward, 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
- Start a counter at
0. - Loop while
hallIsClear()isfalse. - Each iteration: call
move(), then increment the counter. - 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()andhallIsClear()are both called, not reimplemented — the same "don't duplicate provided functionality" instruction from part (b) applies here too.numMoves++runs once per call tomove(), 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
ifinstead of awhile, or omitting the loop entirely. A singlemove()call only advances the simulation by one step;clearHall()needs to keep going until the hallway is actually empty. - Incrementing
numMovesin the wrong place relative to the loop, such as before callingmove()or outside the loop altogether — the count needs to match the number of timesmove()actually ran. - Re-scanning the
hallarray directly instead of calling the already-providedhallIsClear(). 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] == 0right after the decrement) is a clean way to express "did that action just finish the job" without duplicating any logic. - A
whileloop 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.