
This is where the course has been heading. From here on, each section adds one piece to Warehouse, and at the end of each you save the game so far, as WARE1, WARE2 and so on, until it is finished.
The rules are the ones S1 described. A warehouse worker walks around a floor plan; he can push a crate, one at a time, but never pull one; every crate must end up on a goal square. That is all - and it is enough to be fiendish.
This first piece does no playing at all. It sets down what a warehouse is, in Pascal's types, and draws one on the screen.
Every square of a warehouse is one of five things:
type
Tile = (Wall, Floor, Goal, Crate,
Stored);
Stored is a crate standing on a goal - it has to be a thing of its own, because when it is pushed off, the goal must still be there underneath.
The worker is not one of them. He is kept separately, as a row R and a column C. That way, when he walks over a goal, nothing has to remember what he is standing on: the square is still a Goal, and he is simply drawn on top of it.
Board = array[1..Rows, 1..Cols]
of Tile;
Level = record
Name: Str20;
Grid: Board;
StartR, StartC: Integer;
Best: Integer;
end;
A level is a record: its name, its grid of squares - up to ten rows of sixteen - and where the worker starts. Best will hold the best score, from S32; for now it is simply set to 0.
Designing a level as numbers would be miserable. Instead, each level is written as rows of characters, the way block-pushing puzzles have always been written down:
| Character | Means |
|---|---|
# | wall |
| space | floor |
. | goal |
$ | crate |
* | crate on a goal |
@ | the worker |
+ | the worker on a goal |
Plans is a typed constant (S19) holding every level's rows, eight to a level; a level with fewer than eight uses '' - an empty string - for the rest. Because it is an array of two dimensions, it is written as a list of lists: each level's rows in brackets, and the whole lot in brackets again. Names holds each level's name.
MakeLevel turns one plan into a Level: it goes through every row and column, reads the character there (a space past the end of a short row), and uses a case to choose the tile. The @ and + both set the worker's start.
One character is small for a square - a warehouse would be a little island in the corner of the screen. So every square is drawn two characters wide and two high: a level sixteen squares across takes 32 columns, and ten rows take 20 lines of the screen.
Each tile's look is two short strings, one for the top half and one for the bottom - Top and Bot, arrays indexed by Tile, of Str2, a string of at most two characters. A wall is '##' over '##', a crate '$$' over '$$'. The worker, who is not a tile, has ManTop and ManBot.
At moves the cursor to the top-left corner of a square: each square is two columns and two lines on from the one before, so square (Row, Col) is at column LeftCol + 2 * (Col - 1) and line TopRow + 2 * (Row - 1). Show draws one square: it picks the worker's two halves if he is there, or the tile's, writes the top half at At, and the bottom half on the line below.
A small level drawn at the top left of the screen would leave most of it empty, so the game centres each level. Place finds how far the level reaches - Wide, its last column with anything but floor in it, and High, its last such row - and works out where to start so that there is as much space on one side as the other:
LeftCol := 1 + (40 - 2 * Wide) div 2;
TopRow := 3 + (20 - 2 * High) div 2;
The board has lines 3 to 22 to itself; the lines above and below it are kept for words, later. Squares draws every square of the level - only as far as Wide and High, never the empty grid beyond them, which would run off the edge of the screen - and DrawAll clears the screen, places the level and draws it.
Show will do almost all the drawing in the game. S23 found that redrawing the whole screen is slow; from the next section on, the game draws only the squares that change, one Show at a time.
program Warehouse;
const
Rows = 10;
Cols = 16;
Count = 2;
type
Tile = (Wall, Floor, Goal, Crate,
Stored);
Str2 = string[2];
Str20 = string[20];
Board = array[1..Rows, 1..Cols]
of Tile;
Level = record
Name: Str20;
Grid: Board;
StartR, StartC: Integer;
Best: Integer;
end;
const
Top: array[Tile] of Str2 =
('##', ' ', '..', '$$', '**');
Bot: array[Tile] of Str2 =
('##', ' ', '..', '$$', '**');
ManTop: Str2 = '@@';
ManBot: Str2 = '@@';
Names: array[1..Count] of Str20 = (
'First Steps',
'Two Crates');
Plans: array[1..Count, 1..8]
of Str20 = (
('#######',
'# #',
'# $ . #',
'# @ #',
'#######',
'',
'',
''),
('########',
'# #',
'# $$ @ #',
'# ## #',
'# . . #',
'########',
'',
''));
var
L: Level;
N, R, C: Integer;
LeftCol, TopRow: Integer;
Wide, High: Integer;
procedure MakeLevel(K: Integer);
var
I, J: Integer;
S: Str20;
T: Tile;
X: Char;
begin
L.Name := Names[K];
L.Best := 0;
for I := 1 to Rows do
begin
if I <= 8 then
S := Plans[K, I]
else
S := '';
for J := 1 to Cols do
begin
if J <= Length(S) then
X := S[J]
else
X := ' ';
case X of
'#': T := Wall;
'.', '+': T := Goal;
'$': T := Crate;
'*': T := Stored;
else
T := Floor;
end;
if X in ['@', '+'] then
begin
L.StartR := I;
L.StartC := J;
end;
L.Grid[I, J] := T;
end;
end;
end;
procedure At(Row, Col: Integer);
begin
GotoXY(LeftCol + 2 * (Col - 1),
TopRow + 2 * (Row - 1));
end;
procedure Show(Row, Col: Integer);
var
A, B: Str2;
begin
if (Row = R) and (Col = C) then
begin
A := ManTop;
B := ManBot;
end
else
begin
A := Top[L.Grid[Row, Col]];
B := Bot[L.Grid[Row, Col]];
end;
At(Row, Col);
Write(A);
GotoXY(LeftCol + 2 * (Col - 1),
TopRow + 2 * (Row - 1) + 1);
Write(B);
end;
procedure Place;
var
I, J: Integer;
begin
Wide := 0;
High := 0;
for I := 1 to Rows do
for J := 1 to Cols do
if L.Grid[I, J] <> Floor then
begin
if J > Wide then
Wide := J;
if I > High then
High := I;
end;
LeftCol := 1 + (40 - 2 * Wide) div 2;
TopRow := 3 + (20 - 2 * High) div 2;
end;
procedure Squares;
var
I, J: Integer;
begin
for I := 1 to High do
for J := 1 to Wide do
Show(I, J);
end;
procedure DrawAll;
begin
ClrScr;
Place;
Squares;
end;
begin
N := 1;
MakeLevel(N);
R := L.StartR;
C := L.StartC;
DrawAll;
GotoXY(1, 23);
end.
Type the levels exactly: the spaces inside the quotes are floor, and they matter. Every line of the program fits the screen, so the editor never has to scroll sideways.
Starting from
Turbo freshly started, Y, a new work file called WARE1. Type it, leave the editor with CTRL-K CTRL-D, and S to save it before you run it - this is a long program to lose.
What you should see
The first warehouse, in the middle of a cleared screen:

One crate, one goal, and the worker below them - every square two characters wide and two high. Nothing moves yet.
Top, change the first '##' to 'XX'. What are the walls made of now?N := 1; to N := 2;. Which level is drawn?Show, take out the last three lines - the second GotoXY and Write(B);. What happens to the warehouse?27.1 After drawing the level, count its crates and print Crates: and the number on the top line of the screen.
27.2 Design a third level of your own, add it to the program, and draw it. (Remember Count, and a name.)
27.3 Print where the worker starts, as Worker at row 4, column 4, on the top line.
Worked solutions are in Appendix II.
| Symptom | Cause |
|---|---|
Error 3: ',' expected with the cursor in Plans | A level has fewer than eight rows. Each needs exactly eight, '' for the blank ones. Look just before the cursor. |
| The level is drawn out of shape | A row of the plan has a space too many or too few. Count them against the listing. |
| The worker is in the wrong place, or missing | The plan has no @, or two of them. |
| Every square is half the height it should be | Show writes only the top half: the second GotoXY and Write(B) are missing. |
A warehouse is a grid of five kinds of tile, an enumerated type; the worker is kept apart, as a row and a column. A level is a record. Levels are written as text in a two-dimensional typed constant, and MakeLevel turns the text into tiles with a case. Each square is drawn two characters wide and two high from Top and Bot; Place centres the level, Show draws one square, and Squares draws them all.
S28, The Worker Moves - the arrow keys, and walls that stop him.