{
 ReverseDirection makes the actor to go in the opposite direction,
 it only works when the actor is moving left or right
}
PROCEDURE ReverseDirection(VAR a : ActorType);
BEGIN
  CASE a.Dir OF
    LEFT : a.Dir := RIGHT;
    RIGHT : a.Dir := LEFT;
  END;
END;

{
  OnSolid returns true if standing on something solid i.e. Floor,
  Disappearing floor or a Ladder
}
FUNCTION OnSolid(a : ActorType) : BOOLEAN;
BEGIN
  OnSolid := (m.Field[a.Y + 1][a.X] IN ['=', '-', 'H', '|'])
    OR (m.Field[a.Y][a.X] = 'H');
END;

FUNCTION EmptySpace(x, y : INTEGER) : BOOLEAN;
BEGIN
  IF (x < 1) OR (x > LevelCols) THEN
    EmptySpace := TRUE
  ELSE
    EmptySpace := NOT (m.Field[y][x] IN ['|', '=']);
END;

{
  AboveLadder returns true when the actor is a above a Ladder
}
FUNCTION AboveLadder(a : ActorType) : BOOLEAN;
BEGIN
  AboveLadder := m.Field[a.Y + 1][a.X] = 'H';
END;

{
 OnEater returns true when the actor is standing on a Eater
}
FUNCTION OnEater(a : ActorType) : BOOLEAN;
BEGIN
  OnEater := m.Field[a.Y][a.X] = '*';
END;

{
  ClampToPlayfield makes sure that if the actor tries to walk or jump off
  the playfield edges the actor stays inside the playfield and starts falling
}
PROCEDURE ClampToPlayfield(VAR a : ActorType);
BEGIN
  IF a.Dir IN [LEFT, JUMPLEFT] THEN BEGIN
    IF a.X < 1 THEN BEGIN
      a.X := 1;
      a.Dir := STOPPED;
      a.DirRequest := NONE;
    END;
  END;

  IF a.Dir IN [RIGHT, JUMPRIGHT] THEN BEGIN
    IF a.X > LevelCols THEN BEGIN
      a.X := LevelCols;
      a.Dir := STOPPED;
      a.DirRequest := NONE;
    END;
  END;
END;

{
 InitActor set the fields of an Actor type to reasonable
 initial values
}
PROCEDURE InitActor(VAR a : ActorType; t : KindType; xy : XYtype);
BEGIN
  a.AType := t;
  a.X := xy.x;
  a.Y := xy.y;
  a.Ch := 'X';
  a.JumpStep := 0;
  CASE t OF
    ALAD:
      BEGIN
        a.Ch := 'g';
        a.Dir := STOPPED;
        a.DirRequest := NONE;
      END;
    AROCK:
      BEGIN
        a.Ch := 'o';
        a.Dir := PENDING;
        a.DirRequest := NONE;
      END;
  END;
END;

{
  Set the Lad's character based on their current direction
}
PROCEDURE UpdateLadChar(VAR a : ActorType);
BEGIN
  WITH a DO
    CASE Dir OF
      STOPPED:           Ch := 'g';
      RIGHT, JUMPRIGHT:  Ch := 'p';
      LEFT, JUMPLEFT:    Ch := 'q';
      FALLING:           Ch := 'b';
      UP, DOWN:          Ch := 'p';
      JUMP:
        CASE a.DirRequest OF
          NONE, STOPPED:    Ch := 'g';
          RIGHT, JUMPRIGHT: Ch := 'p';
          LEFT, JUMPLEFT:   Ch := 'q';
        END;
    END;
END;

{
  Update an actor's direction to the latest's request
}
PROCEDURE UpdateDir(VAR a : ActorType);
BEGIN
  a.Dir := a.DirRequest;
  a.DirRequest := NONE;
END;

{
  MoveActor handles the movements of an Actor
}
PROCEDURE MoveActor(VAR a : ActorType);
LABEL
  loopAgain;
VAR
  jd : ActionType;
  i : INTEGER;
  dispenser : DispenserPointerType;
BEGIN

loopAgain: { If just started falling we need to retest all conditions }

  { A STONE can only be LEFT/RIGHT/DOWN/FALLING or PENDING }
  IF a.AType = AROCK THEN BEGIN
    IF (a.Dir = PENDING) AND (a.DirRequest = NONE) THEN
      EXIT;

    { If stopped select a random direction }
    IF a.Dir = STOPPED THEN BEGIN
      CASE Random(2) OF
        0: IF (a.X > 1) THEN
             IF EmptySpace(a.X - 1, a.Y) THEN
               a.DirRequest := LEFT
             ELSE
               a.DirRequest := RIGHT
           ELSE
             a.DirRequest := RIGHT;
        1: IF (a.X < LevelCols) THEN
             IF EmptySpace(a.X + 1, a.Y) THEN
               a.DirRequest := RIGHT
             ELSE
               a.DirRequest := LEFT
           ELSE
             a.DirRequest := LEFT;
      END;
    END;

    { Just reverse direction if at the playfield edge }
    IF (a.X = 1) OR (a.X = LevelCols) THEN
      ReverseDirection(a);

    { Start to fall if not on solid ground }
    IF (a.Dir <> FALLING) AND NOT OnSolid(a) THEN
      a.DirRequest := FALLING;

    { If Der rock just rolled over the top of a ladder then randomize direction }
    IF AboveLadder(a) AND (a.Dir IN [LEFT, RIGHT]) THEN
      CASE Random(4) OF
        0: a.DirRequest := LEFT;
        1: a.DirRequest := RIGHT;
        ELSE a.DirRequest := DOWN;
      END;

    { If on an Eater kill the stone }
    IF OnEater(a) THEN BEGIN
      dispenser := dispensers;
      IF numDispensers > 1 THEN BEGIN
        FOR i := 1 TO Random(numDispensers) DO
          dispenser := dispenser^.next;
      END;
      InitActor(a, AROCK, dispenser^.xy);
      m.AnyRocksPending := TRUE;
    END;
  END; { of stone only handling --------------------- }

  { If stopped or going left or going right and     }
  { request to do something else, then try to do it }
  IF (a.DirRequest <> NONE) THEN BEGIN
    CASE a.Dir OF
      STOPPED, PENDING:
        IF a.DirRequest IN [LEFT, RIGHT, UP, DOWN, FALLING] THEN
          UpdateDir(a);

      JUMPUP:
        IF a.DirRequest = LEFT THEN
          a.Dir := JUMPLEFT
        ELSE IF a.DirRequest = RIGHT THEN
          a.Dir := JUMPRIGHT;

      RIGHT:
        IF a.DirRequest IN [LEFT, STOPPED] THEN
          UpdateDir(a);

      LEFT:
        IF a.DirRequest IN [RIGHT, STOPPED] THEN
          UpdateDir(a);

      UP, DOWN:
        IF a.DirRequest IN [STOPPED, UP, DOWN, RIGHT, LEFT] THEN
          UpdateDir(a);

      JUMPUP:
        IF a.DirRequest = LEFT THEN
          a.Dir := JUMPLEFT
        ELSE
          a.Dir := JUMPRIGHT;

      JUMPRIGHT, JUMPLEFT:
        IF a.DirRequest = STOPPED THEN
          UpdateDir(a);

      PENDING:
        UpdateDir(a);

    END;
  END;

  { Handle starting of jumps }
  IF (a.DirRequest = JUMP) THEN BEGIN
    IF OnSolid(a) THEN BEGIN
      CASE a.Dir OF

        STOPPED, FALLING: BEGIN
          a.DirRequest := STOPPED;
          a.Dir := JUMPUP;
          a.JumpStep := 1;
        END;

        LEFT: BEGIN
          a.DirRequest := a.Dir;
          a.Dir := JUMPLEFT;
          a.JumpStep := 1;
        END;

        JUMPLEFT: BEGIN
          a.DirRequest := LEFT;
          a.Dir := JUMPLEFT;
          a.JumpStep := 1;
        END;

        RIGHT: BEGIN
          a.DirRequest := a.Dir;
          a.Dir := JUMPRIGHT;
          a.JumpStep := 1;
        END;

        JUMPRIGHT: BEGIN
          a.DirRequest := RIGHT;
          a.Dir := JumpRIGHT;
          a.JumpStep := 1;
        END;
      END
    END ELSE BEGIN
      CASE a.Dir OF
        JUMPUP, FALLING: a.DirRequest := STOPPED;
        JUMPRIGHT:       a.DirRequest := RIGHT;
        JUMPLEFT:        a.DirRequest := LEFT;
      END;
    END;
  END ELSE IF (a.DirRequest = UP) AND (m.Field[a.Y][a.X] = 'H') THEN BEGIN
    { If at a ladder and want to go up }
    a.Dir := UP;
    a.DirRequest := NONE;
  END ELSE IF(a.DirRequest = DOWN) AND
    ((m.Field[a.Y][a.X] = 'H') OR (m.Field[a.Y + 1][a.X] = 'H')) THEN BEGIN
    a.Dir := DOWN;
    a.DirRequest := NONE;
  END;

  CASE a.Dir OF

    JUMPUP, JUMPLEFT, JUMPRIGHT: BEGIN
      { Do the jumping }
      jd := jumpPaths[a.Dir, a.JumpStep];
      IF (a.X + dirs[jd].x >= 1) AND (a.X + dirs[jd].x <= LevelCols) THEN BEGIN
        CASE m.Field[a.Y + dirs[jd].y][a.X + dirs[jd].x] OF

          '=', '|', '-' : BEGIN
            { If bumped into something try falling }
            IF OnSolid(a) THEN BEGIN
              a.Dir := a.DirRequest;
              a.DirRequest := NONE;
            END ELSE BEGIN
              CASE a.Dir OF
                JUMPUP:    a.DirRequest := STOPPED;
                JUMPRIGHT: a.DirRequest := RIGHT;
                JUMPLEFT:  a.DirRequest := LEFT;
              END;
              a.Dir := FALLING;
            END;
            a.JumpStep := 0;
          END;

          'H': BEGIN { Jumping onto a ladder }
            a.Y := a.Y + dirs[jd].y;
            a.X := a.X + dirs[jd].x;
            a.Dir := STOPPED;
            a.DirRequest := NONE;
            a.JumpStep := 0;
          END;

          ELSE BEGIN
            { Just jumping in air }
            a.X := a.X + dirs[jd].x;
            a.Y := a.Y + dirs[jd].y;
            a.JumpStep := SUCC(a.JumpStep);
            IF (a.JumpStep > JumpsLen) THEN BEGIN
              UpdateDir(a);
              a.JumpStep := 0;
            END ELSE IF (jumpPaths[a.Dir][a.JumpStep] = ACTIONEND) THEN BEGIN
              UpdateDir(a);
              a.JumpStep := 0;
            END;
          END;
        END;
      END ELSE BEGIN
        IF OnSolid(a) THEN BEGIN
          a.Dir := a.DirRequest;
          a.DirRequest := NONE;
        END ELSE BEGIN
          a.Dir := FALLING;
          a.DirRequest := STOPPED;
          a.JumpStep := 0;
        END;
      END;
    END;

    STOPPED:
      IF NOT OnSolid(a) THEN
        a.Dir := FALLING;

    FALLING: BEGIN
      { If falling then continue doing that until not in free space anymore, }
      { then continue the previous direction (if any)                        }
      IF OnSolid(a) THEN
        IF a.DirRequest = NONE THEN
          a.DirRequest := STOPPED
        ELSE
          a.Dir := a.DirRequest
      ELSE
        a.Y := Succ(a.Y);
    END;

    UP:
      { Climb up until ladder is no more }
      IF m.Field[a.Y - 1][a.X] IN ['H', '&', '$'] THEN
        a.Y := Pred(a.Y)
      ELSE
        a.Dir := STOPPED;

    DOWN:
      { Climb down until ladder is no more }
      IF a.Dir = DOWN THEN
        IF m.Field[a.Y + 1][a.X] IN ['H', '&', '$', ' ', '^', '.'] THEN
          a.Y := Succ(a.Y)
        ELSE
          a.Dir := STOPPED;

    LEFT: BEGIN
      { Stepped out into the void? Then start falling, }
      { but remember the previous direction }
      IF NOT OnSolid(a) THEN BEGIN
        a.DirRequest := a.Dir;
        a.Dir := FALLING;
        GOTO loopAgain;
      END;
      IF EmptySpace(a.X - 1, a.Y) THEN
        a.X := Pred(a.X)
      ELSE
        a.DirRequest := STOPPED;
    END;

    RIGHT: BEGIN
      { Stepped out into the void? Then start falling, }
      { but remember the previous direction }
      IF NOT OnSolid(a) THEN BEGIN
        a.DirRequest := a.Dir;
        a.Dir := FALLING;
        GOTO loopAgain;
      END;
      IF EmptySpace(a.X + 1, a.Y) THEN
        a.X := Succ(a.X)
      ELSE
        a.DirRequest := STOPPED;
    END;
  END;

  { Don't allow actor to end up outside of the playfield }
  ClampToPlayfield(a);
  IF a.AType = ALAD THEN
    UpdateLadChar(a);
END;

