Turbo Pascal 2.0 course cover
← Back to Courses
module
27

The Warehouse On Screen

Introduction

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.

The Pieces

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.

A Level

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.

Levels As Text

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:

CharacterMeans
#wall
spacefloor
.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.

Big Squares

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.

In The Middle

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.

The Code

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:

The first level of Warehouse

One crate, one goal, and the worker below them - every square two characters wide and two high. Nothing moves yet.

Change One Thing

  • In Top, change the first '##' to 'XX'. What are the walls made of now?
  • Change N := 1; to N := 2;. Which level is drawn?
  • In Show, take out the last three lines - the second GotoXY and Write(B);. What happens to the warehouse?

Exercises

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.

When It Goes Wrong

SymptomCause
Error 3: ',' expected with the cursor in PlansA 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 shapeA 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 missingThe plan has no @, or two of them.
Every square is half the height it should beShow writes only the top half: the second GotoXY and Write(B) are missing.

Summary

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.

Next

S28, The Worker Moves - the arrow keys, and walls that stop him.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.