Introduction
Build legal walking and single-box pushing in a playable C++ terminal warehouse game.
You will work on a small terminal game with fixed terrain and separate moving objects. You will read how those parts cooperate in WebIDE, implement small sections in engine.cpp, then compile and play the result in Terminal. The input loop, renderer and original levels are prepared. You will learn the editor navigation, saving and local recovery needed here; no graphical game engine is required.
Walk Without Crossing Walls
In this step, play a working example, then make your own player's movement respect the board. Start in the Terminal tab. All commands below run in that shell. cd changes this shell's working directory; it does not carry between new terminal sessions.
cd /home/labex/project/sokoban
The prepared sample is a complete demonstration, separate from your incomplete program. ./ runs a program from the current directory.
./sample
Enter d, press Enter, then enter d and press Enter again. These two moves walk toward the box and push it onto the goal. Solved! appears, and $ becomes *. Enter u and press Enter: both player and box return to their previous squares, and the message becomes Not solved. Enter q and press Enter to return to the shell. Commands are one letter per line; empty or unknown commands show help and change nothing. The reference demonstrates your destination; running it is not completion of your own engine.
The prepared Game class is declared in engine.hpp. Point holds integer x and y coordinates: x grows right and y grows down. State holds the player and a std::vector<Point> containing every box. terrain[y][x] contains only # walls, spaces, and . goals. const makes this terrain read-only after construction, so a moving box cannot erase a goal. Rendering combines the two layers: * means a box on a goal; + means the player on a goal.
floor(point) reports whether a coordinate is inside the board and not a wall. boxAt(point) returns a box's vector index, or -1 when no box occupies it. These helpers, loading, rendering, and command reading are prepared. You implement move, won, and later undo in engine.cpp; preserve engine.hpp, support.cpp, main.cpp, levels/, and sample. You may add helper functions and use equivalent algorithms. A check tests behavior, not a particular spelling or route.
Switch to the WebIDE tab. In the editor's top File menu, choose Open Folder..., enter /home/labex/project/sokoban in the folder path field, and click OK. This opens the existing project; it does not recreate its files. The Explorer file tree is on the left. If it is hidden, click the two-page Explorer icon in the left sidebar. Expand the sokoban folder if necessary, then click engine.hpp to open it.
To find text in the open file, first click inside the source editor so the file has focus. Press Ctrl+F on Windows/Linux or Command+F on macOS, type struct State in Find, and press Escape to close Find after locating it. Using Find while the tree has focus can search file names instead; return focus to the source text when locating code. You can use the same action for the other method names below. Locate struct State. Which fields must return together when a box push is undone? It is not enough to restore just the player: every box position is part of the same value.
Open support.cpp and find Game::floor and Game::boxAt. Read the guards before the return in floor: an out-of-board point is not floor. In boxAt, a matching box returns its index and exhausting the loop returns -1. Then find Game::render. It begins with a copy of terrain and overlays boxes and the player. Moving an object changes the overlay, leaving the original goal beneath it intact. Do not edit these prepared functions.
Open main.cpp and locate the while (true) loop. It renders the board and history, reads one line, and calls the engine methods. q ends the game loop; this is why you must quit the game before typing shell commands such as a compile command. Open engine.cpp to see the three methods you own. Return to Terminal for the initial self-check.
The starter refuses every move. This intentional self-check therefore prints FAIL walk changes only the player and exits nonzero; that means there is work left, not a broken environment:
bash /opt/labex-sokoban/check.sh walk
Supported maps are enclosed rectangular ASCII grids at least three rows and columns wide. They contain exactly one player, at least one box, and the same number of goals as boxes, with no overlapping objects. Symbols are # wall, space floor, . goal, @ player, $ box, + player on goal, and * box on goal. A map can be unsolvable or already completed. The prepared loader rejects other input; you do not need to write a parser.
move(dx, dy) accepts only (1,0), (-1,0), (0,1), or (0,-1). Any other integer pair must return false without changing state or history. A legal action returns true; an illegal action returns false and changes nothing. Box vector order is not significant for occupancy. Later, undo restores the actual complete state saved before that action, including its then-current box order.
Start with walking only. A Point{...} constructs a coordinate value. && means both conditions must hold; || means either may hold. We explicitly list four directions before adding coordinates, which also safely rejects unusually large integers. >= 0 identifies an occupied box square. At this stage, boxes block walking; pushing comes next. The return false branches leave all state untouched.
Before editing, predict two actions on the tiny board. A left step meets the wall: no player or box coordinate should change. A right step moves only the player to the next floor square. A second right step reaches the box and must still refuse in this walking-only stage.
Switch to WebIDE and click engine.cpp in Explorer. Click inside its text and use Find to locate Game::move, then close Find with Escape. You will change a small selected range, not the whole file. Drag over the intended text with the mouse and inspect the highlight before typing: typing replaces only that selection. For a method body, start at the first line inside its opening { and stop after its final statement, leaving the method's outer { and } unselected. Keep braces belonging to any new inner condition or loop in the body you write.
If you change the wrong text, focus the editor and press Ctrl+Z on Windows/Linux or Command+Z on macOS to undo that edit, then inspect the restored text. This editor action changes source text; the game's u command changes an in-memory game state. They are separate histories. Use File > Save for engine.cpp after each completed edit. Auto Save may already be enabled, but an unfinished edit is not a build: save explicitly and wait for the file tab's unsaved-change marker to disappear before switching to compile. Close a saved source tab with its x when you no longer need it; opening it again from Explorer reads the same saved file. If you accidentally type in a prepared file while reading it, undo that edit and save the restored text before continuing. Do not rerun setup as an editor recovery step.
Find the definition bool Game::move(int, int). The unnamed parameters are valid in a stub that ignores them, but your body will use their values. Change only that signature to:
bool Game::move(int dx, int dy) {
Keep the function's existing closing brace. Select only the comment and return false; inside this move body, leaving the includes and both other methods untouched. Write the walking logic at that selection:
if (!((dx == -1 && dy == 0) || (dx == 1 && dy == 0) ||
(dx == 0 && dy == -1) || (dx == 0 && dy == 1))) return false;
Point next{state.player.x + dx, state.player.y + dy};
if (!floor(next) || boxAt(next) >= 0) return false;
state.player = next;
return true;
Read the guards from top to bottom before saving: reject an unsupported direction, then an unavailable next square, and only then assign the new player position. Keep undo and won refusing for now. Save engine.cpp and switch back to Terminal, at the shell prompt.
Compile the three source files. -std=c++17 selects C++17, -Wall -Wextra enables useful warnings, and -o sokoban names the executable. Compilation errors include a filename and line number. For an error such as engine.cpp:7:..., engine.cpp is the file and the first number is its line. Click that file in WebIDE, use Find to reach the named method, then use the line numbers beside the text to locate the reported line and nearby punctuation/braces. Correct that local text, use File > Save, wait until it is saved, then run the same build again. Do not run a previous sokoban binary after a failed build and mistake it for your edited program.
g++ -std=c++17 -Wall -Wextra engine.cpp support.cpp main.cpp -o sokoban
./sokoban
Enter a then Enter: the left wall gives Blocked, and the board stays unchanged. Enter d then Enter: the player walks right. A second d is still blocked by the box, which is expected in this step. u reports No earlier move: undo has not been implemented. Enter q to leave.
bash /opt/labex-sokoban/check.sh walk
All walk checks passed confirms four-direction walking and safe refusals. The checks compile your actual engine in a temporary directory and also run your existing sokoban executable in isolated game instances. Rebuild sokoban after editing; a missing or stale executable is incomplete. Checks do not move your live game or change your source.
Push One Box and Keep the Goal
In this step, extend your engine to push one box and derive completion from its actual position. Continue in Terminal, in /home/labex/project/sokoban. Each run starts a fresh game in memory; editing or rebuilding the program does not save a live game.
Before pushing, check the square beyond the box. It must be floor or goal with no other box. Reject first, then change both the box and player together. This prevents pushing through walls or pushing a chain of two boxes. The box's index is an int because -1 means absent; only use it to index the vector after checking box >= 0.
won() const observes the game without changing it. The range loop for (Point box : state.boxes) visits every box coordinate by value. Every box must be on a . in fixed terrain; one completed box in a two-box map is insufficient. An empty box vector is never a completed game under our contract. Victory is calculated each time, so it becomes false again when a box leaves its goal. undo still deliberately refuses in this lab; history is the next lab's new responsibility.
Before editing, reason about next and the square beyond it for a push. If another box occupies that destination, which coordinates may change? None: both objects must stay where they were. If it is free floor, the box enters that square and the player enters the box's old square.
Switch to WebIDE and reopen engine.cpp. In your existing Game::move, keep the cardinal-direction guard and Point next. Select only this walking-only refusal line:
if (!floor(next) || boxAt(next) >= 0) return false;
Replace that one line with the following local checks and conditional box update. Keep the existing state.player = next; and return true; immediately after the new section:
if (!floor(next)) return false;
int box = boxAt(next);
Point beyond{next.x + dx, next.y + dy};
if (box >= 0 && (!floor(beyond) || boxAt(beyond) >= 0)) return false;
if (box >= 0) state.boxes[box] = beyond;
The refusal tests now finish before either object changes. For a walk, box is -1 and the conditional box update does nothing; the player update you already wrote still handles that walk.
Next find Game::won. Select only its comment and return false; body between the braces. Write the predicate below, leaving its signature and braces in place:
if (state.boxes.empty()) return false;
for (Point box : state.boxes) {
if (terrain[box.y][box.x] != '.') return false;
}
return true;
Trace the loop for a two-box board with just one box on a goal: the other box makes the result false. Keep undo unchanged. Save your local edits and switch to Terminal. If the game is still running, enter q and press Enter before compiling.
g++ -std=c++17 -Wall -Wextra engine.cpp support.cpp main.cpp -o sokoban
./sokoban
Enter d, d, each followed by Enter. The board shows * and Solved!. Enter another d: the box moves off the goal and the player stands on it as +; the game returns to Not solved. A further d is blocked because the box would enter the wall. The fixed goal still exists under the player. Enter q.
Try the prepared two-box warehouse by passing its file path as the program's optional argument:
./sokoban levels/warehouse.txt
One possible route is w, s, d, d, w, each followed by Enter. You can choose another legal route; no shortest solution is required. Both boxes must show * before Solved! appears. Enter q.
A misspelled level path produces Cannot open level: ... and exits without changing any level file. To see this ordinary error intentionally, run:
./sokoban levels/missing.txt
Recover by repeating the earlier command with levels/warehouse.txt. This is a filename correction, not a reason to reset the environment. Replaying a level starts a new in-memory game.
bash /opt/labex-sokoban/check.sh move
All move checks passed covers legal walks and pushes, invalid actions, goal preservation, an already completed map, and different two-box solutions. Keep the engine and prepared files; you now have a playable movement system.
Summary
You navigated the cooperating source files and changed only the engine sections needed for each result. You separated fixed terrain from moving objects, refused invalid actions without side effects, and completed two small warehouses. The next independent lab adds history and undo.



