Turbo Pascal 2.0 course cover
← Back to Courses
module
33

Undo

Introduction

One careless push, and a crate is in a corner for good. So far the only cure is R and start again - which, forty moves into a level, hurts. Every game of this kind lets you take moves back, one at a time, as far as you like. That is what S22's list was for.

What To Remember

To take a move back, the game needs to know two things about it: which way the worker went, and whether he pushed a crate. That is a small record:

MoveP = ^Move;
Move = record
  Key: Char;
  Pushed: Boolean;
  Next: MoveP;
end;

Key is the arrow that made the move - Delta can turn it back into a direction. Next points at the move before, so the moves form a list, newest first, starting from the pointer Undo. It is S22's list, used for the job it was built for.

Each move costs four bytes. There is room for thousands.

Remembering

At the end of every step, Go makes a new Move with New, fills it in, and puts it at the front of the list:

New(P);
P^.Key := Key;
P^.Pushed := Pushed;
P^.Next := Undo;
Undo := P;

Pushed is set to True in the same if that does the pushing.

Taking Back

Back takes the newest move off the front of the list and does it in reverse. The worker steps back the way he came. If the move was a push, the crate comes back too: it is lifted from the square beyond, and dropped on the square the worker has just left - one step ahead of where he now stands.

Delta(Undo^.Key, DR, DC);
R := R - DR;
C := C - DC;
if Undo^.Pushed then
begin
  Lift(R + 2 * DR, C + 2 * DC);
  Drop(R + DR, C + DC);
end;

Then the move is taken off the list and its memory given back with Dispose, and Moves goes down by one.

Forgetting

When a level starts - at the beginning, with R, or after a win - the moves of the last level mean nothing. Forget empties the list, disposing of every move in it, and Start calls it. Without it, the list would grow for as long as the game was played, and U could take back a move from a level you were no longer playing.

The Code

Start WARE7 from WARE6.

In the type part, after Level's end;:

  MoveP = ^Move;
  Move = record
    Key: Char;
    Pushed: Boolean;
    Next: MoveP;
  end;

Add Undo to the variables, after Wide, High: Integer;:

  Undo: MoveP;

In DrawAll, the bottom line now lists U, in place of the arrows:

  Write('U undo  R restart  Q quit');

Before Start, add Forget:

procedure Forget;
var
  P: MoveP;
begin
  while Undo <> nil do
  begin
    P := Undo;
    Undo := Undo^.Next;
    Dispose(P);
  end;
end;

and in Start, after Moves := 0;:

  Forget;

Go becomes:

procedure Go(Key: Char);
var
  DR, DC, NR, NC: Integer;
  P: MoveP;
  Pushed: Boolean;
begin
  Delta(Key, DR, DC);
  NR := R + DR;
  NC := C + DC;
  Pushed := False;
  if (L.Grid[NR, NC] in
       [Crate, Stored]) and
     (L.Grid[NR + DR, NC + DC] in
       [Floor, Goal]) then
  begin
    Lift(NR, NC);
    Drop(NR + DR, NC + DC);
    Pushed := True;
  end;
  if L.Grid[NR, NC] in [Floor, Goal]
  then
  begin
    R := NR;
    C := NC;
    Show(R - DR, C - DC);
    Show(R, C);
    Moves := Moves + 1;
    Status;
    New(P);
    P^.Key := Key;
    P^.Pushed := Pushed;
    P^.Next := Undo;
    Undo := P;
  end;
end;

After Go, add Back:

procedure Back;
var
  DR, DC: Integer;
  P: MoveP;
begin
  if Undo <> nil then
  begin
    Delta(Undo^.Key, DR, DC);
    R := R - DR;
    C := C - DC;
    if Undo^.Pushed then
    begin
      Lift(R + 2 * DR, C + 2 * DC);
      Drop(R + DR, C + DC);
    end;
    Show(R + DR, C + DC);
    Show(R, C);
    P := Undo;
    Undo := Undo^.Next;
    Dispose(P);
    Moves := Moves - 1;
    Status;
  end;
end;

In the main program, before OpenFile;, the list must start empty:

  Undo := nil;

and the case gets a line:

      'U': Back;

Starting from

WARE6 from S32, read into a new work file WARE7. Make the changes, CTRL-K CTRL-D, S, R.

What you should see

U undo among the keys. Push the first crate one square - LEFT, LEFT, UP, RIGHT - then press U. The worker steps back and the crate comes with him, and Moves drops from 4 to 3:

A push taken back

Keep pressing U and the whole level unwinds to where it started, then U does nothing more.

Change One Thing

  • In Back, take out Drop(R + DR, C + DC);. Push the crate, then undo. Where has the crate gone - and what does the game make of that?
  • In Start, take out Forget;. Take two steps LEFT, press R, then U. Where does the worker go, and what does Moves say?
  • In Go, change P^.Pushed := Pushed; to P^.Pushed := False;. Push the crate and undo.

Exercises

33.1 Make Z take back every move, all the way to the start of the level.

33.2 Show Free and MemAvail on the line under the board, after every key. How much does a move cost, and what does undo give back?

33.3 Add redo: Y puts back the last move undone, as many times as U was pressed - until a new move is made, which forgets them.

Worked solutions are in Appendix II.

When It Goes Wrong

SymptomCause
Undo leaves the crate where it wasPushed is never set to True, or never stored in the record.
A crate vanishes on undoBack lifts the crate but never drops it.
U after R moves the worker somewhere odd, and Moves goes below 0Start does not call Forget, so the old level's moves are still in the list.
Run-time error FF after a very long gameThe list is never emptied: Dispose is missing from Forget or Back.
The game goes wrong at the first UUndo := nil; is missing, so the list does not start empty.

Summary

Every step makes a Move record with New - the arrow and whether a crate was pushed - and puts it at the front of the list. Back takes the front one, walks the worker back, brings the crate back if there was one, and Disposes the record. Forget empties the list whenever a level starts.

Next

S34, Where Can I Reach - a procedure that calls itself, to show every square the worker can walk to.

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.