BDCFF Object 0006: Rockford

Object number: $0006
Game class: Boulder Dash (by Peter Liepa)
Object name: Rockford

In this document:


Properties

Animate: yes
Impact explosive: yes
Chain explosion action: consumed
Explosion type: explodeToSpace
Rounded: no


Attributes

Attribute format: %00000000 00000000

There are no attributes for this object type.


Graphics

Rockford animation sequeneces
This GIF shows the animation sequences of Rockford from the C64 implementation of Boulder Dash (hence the graphics are 8 double-width pixels wide and 16 pixels high).

There are six rows of graphics:

  1. Rockford facing forward (no animation sequence; just one graphic);
  2. Rockford facing forward, blinking
  3. Rockford facing forward, tapping foot
  4. Rockford facing forward, blinking and tapping foot
  5. Rockford facing left
  6. Rockford facing right

Rockford Animation

This description of the Rockford animation sequences is based on the C64 implementation. Other implementations may do things differently. However, the Rockford blinking and tapping was considered an important part of the look and feel of the C64 implementation at least.

Facing left or right

There are animation sequences for when Rockford is moving left or right, but not up or down. What happens is that if Rockford is moving up or down, then the animation sequence of the last horizontal movement is used. For example, if Rockford moves right and then up, the "moving-right" animation sequence will be used for that time. If Rockford then moves left and up, the "moving-left" animation sequence is used during this time. This brings up an obvious question: what happens at the beginning of a cave if Rockford moves up or down without first having moved horizontally? The answer is that Rockford will face left as he moves up or down initially.

Tapping foot and blinking

When Rockford is not moving, he faces forward (out of the screen towards the player). Rockford gets bored; he taps his foot and blinks his eyes. Blinking and tapping are independent of each other; in the C64 implementation, the upper and lower halves of his body are controlled separately.

Animation sequences are eight frames long, and each frame is displayed for two ticks, so it takes 16 ticks (about 0.27 seconds) to complete each animation sequence. At the beginning of each new animation sequence (ie every 0.27 seconds), if Rockford is not currently moving, it is decided whether Rockford will be idle, blink, tap his foot, or blink and tap his foot. There is a 25% (1/4) chance he will blink each animation sequence, and a 6.25% (1/16) chance that he will stop tapping (if he is currently tapping) or start tapping if he isn't.


Interactions with other objects

Rockford interacts with the following objects:

Specification

Rockford is the player, and so Rockford's movements are controlled by the player. The basic objective being, of course, to collect enough diamonds to activate the exit, and to get out alive (without being killed by various means or running out of time).

Rockford movement performed during scan routine

There are three posibilities for when an implementation of BoulderDash can check the player controls (joystick) and move Rockford:
  1. Before the cave scan routine
  2. During the cave scan routine
  3. After the cave scan routine
When the scan is performed can make a difference as to how the objects in the cave interact. If Rockford and another object both attempt to move into the same position, who succeeds depends on when the Rockford move routine is performed. If it is done before the cave scan, Rockford will always get priority. If it is done during the cave scan, who gets priority depends on the direction Rockford is moving in. And if the Rockford move routine is done after the cave scan routine, then the other object will get priority.

In BoulderDash I on the C64, the Rockford move routine is done during the cave scan routine: that is, when the scan routine comes across Rockford, it then looks at the joystick and processes it appropriately. This technique has an advantage: it doesn't presume that Rockford actually exists in the cave (Rockford doesn't exist during the pre-Rockford sequence or after he dies). Doing it this way makes it easier for the cave to continue on in its normal way regardless of whether Rockford exists or not.

Movement controls

Using the joystick or other control device, the player can control Rockford's movement. Rockford can move up, down, left and right only, not diagonally. In BoulderDash I for the C64, if the player presses diagonally, Rockford moves horizontally (the horizontal component has priority over the vertical component). Although perhaps not critical, I personally am used to the player controls behaving in this fashion, and I initially found it frustrating when playing an implementation that behaved differently. Each implementation may well have its own idiosyncrasies, but if you're out to clone the C64 look & feel, then you might want to take note of the C64's particular behaviour.

The fire button on the joystick can be used to "move without moving"; that is, everything happens as if Rockford did move in the indicated direction, but Rockford isn't actually moved. This is critical for some caves which require this technique.


General Algorithm

procedure ScanRockford(in positionType currentScanPosition;
                       inout positionType RockfordLocation;
                       inout Boolean RockfordAnimationFacingDirection;
                       in Boolean demoMode;
                       in Boolean twoJoystickMode;
                       in playerType currentPlayer;
                       inout integer numRoundsSinceRockfordSeenAlive)
# We have come across Rockford during the scan routine. Read the joystick or
# demo data to find out where Rockford wants to go, and call a subroutine to
# actually do it.

    ASSERT(numRoundsSinceRockfordSeenAlive >= 0);

# Local variables
    joystickDirectionRecord JoyPos;

# If we're in demo mode, we get our joystick movements from the demo data
    if (demoMode) then
        JoyPos := GetNextDemoMovement();
    else

# Otherwise if we're in a real game, we get our joystick movements from
# the current player's input device (joystick, keyboard, whatever).
        JoyPos := GetJoystickPos();
    endif

# Call a subroutine to actually deal with the joystick movement.
    MoveRockfordStage1(currentScanPosition, RockfordLocation, JoyPos, RockfordAnimationFacingDirection);

# Rockford has been seen alive, so reset the counter indicating the number
# of rounds since Rockford was last seen alive.
    numRoundsSinceRockfordSeenAlive := 0;
endprocedure ScanRockford

####################

procedure MoveRockfordStage1(in positionType currentScanPosition;
                             inout positionType RockfordLocation;
                             in joystickDirectionRecord JoyPos;
                             inout Boolean RockfordAnimationFacingDirection)
# Note: in this routine, if the user presses diagonally, the horizontal movement takes
# precedence over the vertical movement; ie Rockford moves horizontally.

# Local variables
    Boolean ActuallyMoved;
    positionType NewPosition;

# Determine Rockford's new location if he actually moves there (ie he isn't
# blocked by a wall or something, and isn't holding the fire button down).
    switch (JoyPos.direction) of
        case down:
            RockfordMoving := true;
            NewPosition := GetRelativePosition(currentScanPosition, down1);
        elscase up:
            RockfordMoving := true;
            NewPosition := GetRelativePosition(currentScanPosition, up1);
        elscase right:
            RockfordMoving := true;
            RockfordAnimationFacingDirection := facingRight;
            NewPosition := GetRelativePosition(currentScanPosition, right1);
        elscase left:
            RockfordMoving := true;
            RockfordAnimationFacingDirection := facingLeft;
            NewPosition := GetRelativePosition(currentScanPosition, left1);
        else
            RockfordMoving := false;
        endcase

# Call a subroutine to actually deal with this further.
        ActuallyMoved := MoveRockfordStage2(currentScanPosition, NewPosition, JoyPos);

# If Rockford did in fact physically move, we update our record of Rockford's
# position (used by the screen scrolling algorithm to know where to scroll).
        if (ActuallyMoved) then
            RockfordLocation := NewPosition;
        endif
    endswitch
endprocedure MoveRockfordStage1

####################

function MoveRockfordStage2(in positionType originalPosition;
                            in positionType newPosition;
                            in joystickDirectionRecord JoyPos):Boolean
# Part of the Move Rockford routine. Call MoveRockfordStage3 to do all the work.
# All this routine does is check to see if the fire button was down, and
# so either move Rockford to his new position or put a space where he would
# have moved. Returns true if Rockford really did physically move.

# Local variables
    Boolean ActuallyMoved;

# Call a subroutine to move Rockford. It returns true if the movement was
# successful (without regard to the fire button).
    ActuallyMoved := MoveRockfordStage3(newPosition, JoyPos);

# If the movement was successful, we check the fire button to determine
# whether Rockford actually physically moves to the new positon or not.
    if (ActuallyMoved) then
        if (JoyPos.fireButtonDown) then
            PlaceSpace(newPosition);
            ActuallyMoved := false;
        else
            PlaceRockford(newPosition);
            PlaceSpace(originalPosition);
        endif
    endif

# Tell our caller whether or not Rockford physically moved to a new position.
    return ActuallyMoved;
endfunction MoveRockfordStage2

####################

function MoveRockfordStage3(in positionType newPosition;
                            in joystickDirectionRecord JoyPos):Boolean
# See what object is in the space where Rockford is moving and deal with it
# appropriately. Returns true if the movement was successful, false otherwise.

# Local Variables
    Boolean movementSuccessful;
    objectType theObject;
    positionType NewBoulderPosition;

# Determine what object is in the place where Rockford is moving.
    movementSuccessful := false;
    theObject := GetObjectAtPosition(newPosition);
    switch (theObject) of

# Space: move there, and play a sound (lower pitch white noise)
        case objSpace:
            movementSuccessful := true;
            RequestRockfordMovementSound(movingThroughSpace);

# Dirt: move there, and play a sound (higher pitch white noise)
        elscase objDirt:
            movementSuccessful := true;
            RequestRockfordMovementSound(movingThroughDirt);

# Diamond: pick it up
        elscase objStationaryDiamond:
            movementSuccessful := true;
            PickUpDiamond();

# OutBox: flag that we've got out of the cave
        elscase objOutBox:
            movementSuccessful := true;
            FlagThatWeAreExitingTheCave(and that we got out alive);

# Boulder: push it
        elscase objStationaryBoulder:
            if (JoyPos.direction == left) then
                NewBoulderPosition := GetRelativePosition(newPosition, left1);
                if (GetObjectAtPosition(NewBoulderPosition) == objSpace) then
                    movementSuccessful := PushBoulder(NewBoulderPosition);
                endif
            elsif (JoyPos.direction == right) then
                NewBoulderPosition := GetRelativePosition(newPosition, right1);
                if (GetObjectAtPosition(NewBoulderPosition) == objSpace) then
                    movementSuccessful := PushBoulder(NewBoulderPosition);
                endif
            endif
        endcase
    endswitch

# Return an indication of whether we were successful in moving.
    return movementSuccessful;
endfunction MoveRockfordStage3

####################

function PushBoulder(in positionType newBoulderPosition)
# There is a 12.5% (1 in 8) than Rockford will succeed in pushing the boulder.
# Return true if boulder successfully pushed, false if not.

# Local variables
    Boolean pushSuccessful;

    pushSuccessful := (GetRandomNumber(0, 7) == 0);
    if (pushSuccessful) then
        RequestSound(boulderSound);
        PlaceBoulder(newBoulderPosition);
    endif

    return pushSuccessful;
endfunction PushBoulder

####################

procedure PickUpDiamond()
# Player has picked up a diamond. Increase their score, increase their number
# of diamonds collected, and check whether they have enough diamonds now.

    RequestSound(pickedUpDiamondSound);
    CurrentPlayerData.score += CurrentPlayerData.currentDiamondValue;
    CheckForBonusLife();
    CurrentPlayerData.diamondsCollected++;
    CheckEnoughDiamonds();
endprocedure PickUpDiamond

####################

procedure CheckEnoughDiamonds()
    if (CurrentPlayerData.diamondsCollected == CaveData.diamondsNeeded) then
        CurrentPlayerData.gotEnoughDiamonds := true;
        CurrentPlayerData.currentDiamondValue := CaveData.extraDiamondValue;
        Update statusbar;
        RequestSound(crackSound);
        Request screen to flash white to indicate got enough diamonds;
    endif
endprocedure CheckEnoughDiamonds

####################

procedure AnimateRockford(inout Boolean Tapping;
                          inout Boolean Blinking;
                          in Boolean RockfordAnimationFacingDirection)
# Called by the animation routine every animation frame

# If Rockford is currently moving, we display the right-moving or left-moving animation
# sequence.
    if (RockfordMoving)

# Can't tap or blink while moving
        Tapping := false;
        Blinking := false;

# Set up animation left or right as appropriate
        if (RockfordAnimationFacingDirection == facingRight)
            doing right-facing Rockford animation sequence
        else
            doing left-facing Rockford animation sequence
        endif

# If Rockford is not currently moving, we display a forward facing animation sequence.
# Rockford might be idle, tapping, blinking, or both.
    else

# If we're at the beginning of an animation sequence, then check whether we
# will blink or tap for this sequence
    if (AnimationStage == 1)

# 1 in 4 chance of blinking
        Blinking := (GetRandomNumber(0, 3) == 0);

# 1 in 16 chance of starting or stopping foot tapping
        if (GetRandomNumber(0, 15) == 0)
            Tapping := not Tapping;
        endif       
    endif

    doing forward-facing Rockford animation sequence (idle, blink, tap, or blink&tap)

endprocedure AnimateRockford

Web page design by Peter Broadribb