The Apprentice Remade For PC - Inner workings

Some of the content here are the comments I added to my code. The 'show' and 'hide' blocks may not work properly when nested

Some of the terms used in this page:

FPSFrames Per SecondThe rate at which frames of the game are shown. The higher it is, the smoother the game plays.
SPCThe Vision Factory, SPC-VisionThe original creators of The Apprentice
StickyMarvin cannot moveMarvin's position cannot be affected by the player. Usually while loading, attacking, dying, stage complete.
CastmemberNumber of member in castA cast is a collection of assets, be it an image, a sound, text, or code. Each member (asset) has a number.

Global setup

The Score

This is the place that show which sprites/sounds/scripts are used on each frame.
I've grouped most of them by color and gave them a (group)-name.
Sprites will be overlayed by sprites with higher numbers.
The target framerate (FPS) is 50. Sound effects and music is handled by code and thus not visible in the score.

Overview of the score:
The Score
Method of scrolling:
Scrolling explained
Method of layering:
Layering explained

The short: Set all constants and initialize many variables. Clears all global variables (except a chosen few).

Engine constants
Level variables
- Move subsection
- Constants subsection
- Jump subsection
- Animation subsection
- Attack subsection
- Protection subsection
Initialize sound channels
- Music
- Sound effects
Go to appropriate frame
- Intro sequence
- Castle map
- Level (boss map)
- Level (dream)

Intro sequences

The short: Show the correct intro sequence.

Check if import required
Set frame counter
- Hide all sprites
- Start fully black
- Clear the animation items of the intro
Show the intro sequence (sub-function)
Check if a button is pressed
- Start fade to black
- Fully faded: go to map

Example: intro Medieval Tower
The following has all the comments in the code, with some structural code to keep this readable.
if (introFrameCount = 1) then
  -- set all sprites to intro 1 library
  -- play music
  -- clear intro items
else if (introFrameCount = 2) then
  -- initialize first screen: the lab
  -- set background
  -- set Torch Flames
  -- set Gandorf (base)
  -- set Gandorf (point)
  -- set Gandorf (blink)
  -- set bottle bob
  -- set Marvin (base)
  -- set Marvin (blink)
else if ((introFrameCount > 1) and (introFrameCount <= 1775)) then
  -- animate first screen
  -- fade from black
  -- animate Torch Flames
  -- set Gandorf (point)
  -- animate Gandorf (blink)
      -- new blink animation
      -- reverse animation
      -- end animation, blink again in 1..3 seconds
    -- next frame
  -- animate the bottle bob
  -- animate Marvin (blink)
      -- new blink animation
      -- reverse animation
      -- end animation, blink again in 1..3 seconds
    -- next frame
  -- show the texts
  -- fade to black
else if (introFrameCount = 1776) then
  -- initialize second screen
  -- clear intro items
  -- set the background
  -- set Marvin (walk)
  -- set closed door
  -- set eyes (4..11)
else if ((introFrameCount > 1775) and (introFrameCount <= 2230)) then
  -- animate second screen
  -- fade from black
    -- open the door
    -- show the eyes
  -- animate the eyes
        -- new blink animation
        -- reverse animation
        -- end animation, blink again in 1..3 seconds
      -- next frame
    -- fade to black
    -- fade music 255->0
  -- end of sequence: go to castle map
end if
-- default: continue sequence

The short: Show the map intro sequence.

Check if import required
Set next frame
Set frame counter
Fade from black
Move monster from left to right (with tower name)
Move monster from right to left (with stage name)
Fade to black
Go to the level

Import Assets

The short: Handles the extraction and importing of the indicated required assets.
I'd rather keep the details to the minimum to prevent possible abuse.

Start extraction processes (if required)
Check all ongoing extraction processes
- Extraction process in progress: check if completed
- Completed: import extracted assets
Determine if all extraction processes are completed
- All extraction processes have finished: go to next frame
- One or more extraction processes are ongoing: stay on this frame

Read Level

The short: Read the level data and process it to game format. Initialize a ton variables.

Overview of function call-tree

Orange lines are always called, while red lines are optional.
Minor functions omitted, mostly statusbar related.

Check if import required

Initialize sprites
- Show all tiles: background
- Hide the boss background
- Show all tiles: items
- Hide the level darkener
- Show all tiles: active items
- Show Marvin
- Hide all tiles (on-top (active) items)
- Hide optional overlays
- Show or hide the status bar (normal)
- Show or hide the status bar (dream)
- Set fullscreen overlay to fully visible

No active flag: indicates the first time this code is run
- Read and parse level data
- Set the bottom flag (or top flag in The Well)
- Read and parse tile data (for platforms & ceilings)
- Create all platforms, walls and ceilings, and moving platform paths
-- List of platforms contains: [StartX, StartY, EndX, EndY, PlatCeil], etc
-- List of walls contains: [StartY, EndY, locX], etc
-- List of moving platforms contains: [PathPoints[],StartX,StartY,EndX,EndY], etc
-- List of walls that can be removed (doors, breakables) contains: [StartY, EndY, locX], etc
-- Obstacles (doors, whale spouts, dinosaurs) generate additional walls/ceilings
- Initialize statusbar (keys, time)
- Save the number of seconds at the time of the first flag activating (should be 600)
Active flag : this code is re-run after Marvin died
- Reset level frame counter (used for fade in animation timing)

Show statusbar
Create the boss (and it's area)
Put Marvin at the active flag's position
Convert interactibles (coins, food items, extra life, angel, invincibility, keys, doors) to active matrix
Set the time to the same value it was when Marvin activated the flag

ReadFile subfunction: makeAllPlatforms
Platforms are defined with 2 coordinates and 1 behavior (platform, ceiling, wall blocking L->R, wall blocking R->L)

Add below-level failsafe
Add all platforms of all tiles
ReadFile subfunction: makeAllWalls

Walls are marked with codes 34 and 33 in LDtiles as top and bottom
Due to the behavior of the walls, ALL walls are added (not just the on-screen ones
There are never that many (at most 20), so there should be little to no speed impact
Towers 2 and 6 (The Well, and Toy Tower) will need to be built differently due to swim/flight

Add outer walls
- Left wall
- Right wall
Define search codes
Find all walls in items
- Item code is top of a wall
- Find the bottom of the wall
- Add wall blocking L->R
- Add wall blocking R->L

Add all walls of all tiles (The Well and Toy Tower)
- Search full range of tiles
- Tile is wall (blocking L->R) or wall (blocking R->L)
-- Set locations
-- Add wall blocking L->R
-- Add wall blocking R->L
ReadFile subfunction: makeAllMovingPlatforms

Moving platforms are marked with 16..19 as start point (and starting direction)
Paths are marked with 15
Location (and animation) of platforms is done elsewhere

Initialize output lists
Initialize codes to use as paths
Set start checking direction
Initialize new path and add first point

Find full path
repeat while (returned and startPointFound not one of the below value combinations)
- Returned=2,startPointFound=3: returning path, start at center
- Returned=3,startPointFound=2: returning path, start at edge
- Returned=0,startPointFound=2: circular path
- Get position of next point
-- Check if previous point was start point
-- Add half-delta point (slows down before returning)
-- Add current point (slowly start up again)
- Add deltas to point
- Add new point
- Check if new point equals the start point
end repeat

Trim off excess path points
Add path to list
Add basic platform info to moving platforms list

Helper functions:
Helper function 'findNextPoint' for makeAllMovingPlatforms
- Finds the next path point in the list of items (find 15, 16..19)

Helper function 'getNextdXdY' for findNextPoint
- Finds the next delta XY based on the current delta
- If newPath, then check all 8 directions, else only check the valid 5 (hard left to hard right)

Pathfinding in action
ReadFile subfunction: makeAllTempWalls

Doors (Medieval and Abandoned Tower), whale spouts (The Well) and dinosaurs (Toy Tower) obstacles
are removed when using a key, fish or 'Oscar' item. Breakable walls can be destroyed.
These walls are temporary, so are stored in a separate list

Find all walls in items
Check if temp wall code
- Door
- Breakable wall
Item code is bottom of a wall
- Set the top of the wall
- Add wall blocking L->R
- Add wall blocking R->L
ReadFile subfunction: makeAllObstacles

Obstacles (doors,whale spouts,dinosaurs) generate additional walls/ceilings

Add all platforms of all items with obstacles
Medieval Tower
- Doors (YRPG)
The Well
- Whales (to do)
Hi-Tech Tower
Arctic Tower
Abandoned Tower
Toy Tower
- Dinos (to do)
ReadFile subfunction: findMarvinStart

Search through item data for flags
If the active flag is found, set Marvin's starting position there

Codes: 1=game start 2=flag start 29=end flag
Keep track of flags
The Well: run top to bottom, else run bottom to top
- Code is possible start position
- Check if flag is active
ReadFile subfunction: doBossActions

Perform the actions required to set the level for the boss
- Set the background
- Hardcoded platforms
- Hardcoded walls

The short: Fade to and from black while continuing normal gameplay. No user input accepted.

If Marvin is dead:
- Fade to black
- Game over if no more lives
- Else reload last checkpoint

If time ran out:
- Show the "Time's up!" overlay
- Fade to black
- Game over if no more lives
- Else reload last checkpoint

If Marvin is not dead:
- Fade to normal
- Show the "Get ready!" overlay
- Go to normal gameplay

Keep animating the screen normally


The short: The game itself.

Overview of function call-tree
Main program functions call tree
Orange lines are always called, while red lines are optional.
Minor functions omitted, mostly statusbar related.

Set next frame

Check which keys (if any) are pressed
This may affect which frame is next
Main subfunction: doCheckKey

The Short: Player keys input processing: movement & jumping
If no-input: boredom of Marvin

Check input keys
- 1, 2, 3, left arrow, up arrow, right arow, down arrow
Map keys to actions
- Default key actions
- Tower specific actions

Check if pause button is pressed

If the last flag is activated, ignore keys
Check horizontal keys
Check vertical keys
Set look direction

Ignore block if Marvin is sticky
- If up-arrow is pressed, perform jump checks
- Up-arrow must be not-pressed while on a platform before another jump
- This disallows air-jump and bouncing

Check if Marvin is attacking
Not every tower has the same requirements
Medieval Tower Not jumping, not sticky
The Well Anytime
Hi-Tech Tower Not jumping, not sticky
Arctic Tower Not jumping, not sticky
Abandoned TowerNot jumping, not sticky
Toy Tower Anytime, does not become sticky
Dreams Can not attack

Marvin will be bored after a while and look at the player
Requires no actions to happen for <boredDelay> nr of frames

Check if boss has died

Move and animate Marvin
Update vertical scrolling
Prevent scrolling too far
Fix scrolling if on a boss map
Offset scrolling on a dream map
subfunction: moveMarvin

The short:
- Set Marvin's speed and next-position
- Detect platforms and walls
- Set Marvin's new position using the adjusted next-position
- If Marvin is sticky player input is ignored

Disable horizontal movement if Marvin is sticky

Initialize movement variables
- Hortizontal speed
- Vertical speed (with gravity)
-- Marvin falls with a constant (slow) speed when dying
- Vertical speed (no gravity, but swimming)
-- Add vertical bobbing

Set start and end positions, initialize collision markers

Platform detection using 3 feelers (feet of Marvin)
Detect platforms with the center feeler
If hit platform: use feeler
Else detect platforms with the left and right feelers

Select feeler to use and pass information

Wall detection using 2 feelers (Marvin's looking side)
Skip wall detection if Marvin doesn't move horizontally
- Select feeler to use and pass information
- Adjust X offset of feeler

Set Marvin's final position

Both the platform detection and wall detection contain a ton of math, comparisons, skips, loops, restarts, and are a general pain to explain.
In it's core form, it compares the path of Marvin with the line of the platform/wall, adjusted by previous platforms/walls hit.

Check if jumping
- Separate function to set if Marvin is jumping (or just airborne)
- This ensures all globals are correctly set

if all of the following = true
- Marvin is on a platform
- Marvin is not initiating a jump
- Marvin has released the jump-key
- Allow another jump

Falling off a platform equals 'jumping'
subfunction: animateMarvin
The short: Change Marvin's sprite according to his action

Current animation stats
Get animation settings
Lower invincibility counters
If show next frame: perform animation

Helper functions:
Helper function 'getMarvinAnimationSettings' for animateMarvin
- Get the settings (start and end castmember number) based on current action (jump, walk, attack, die, bored)
Helper function 'animateMarvinSprite' for animateMarvin
- Actual processing of action with animation settings (animation frame counter, frame delay, current castmember number)
Helper function 'createActiveItem' for animateMarvin
- When a new onscreen-sprite is required, this function creates one. In case of Marvin it's his invincibility sparkle trail.

Set all tiles, sprites, and items on screen
subfunction: setScreen
The short: Set all tiles (tower walls and background, level tiles), sprites (moving platforms, enemies, animated background), and items (keys, coins, food) on screen. Movement and animation included.

Helper functions:
Helper function 'setScreenTiles' for setScreen
- Set location and content of level and background tiles
- Background sprites: fountain, candle-flame, air-bubbles
Content setScreenTiles:
Set start tile of level and background of visible screen
Set location and content of background matrix
Set location and content of level matrix
- Hide unused tile
- Show and position tile

Helper function 'animateScreen' for setScreen
- Animate the onscreen items
Content animateScreen:
Increment frame count
Set index of mobile active items
Only animate visible content of items matrix
Get itemNr (castMemberNr)

Static activeItem -> getItemNextAnimationFrame

Mobile activeItem
- Get next mobile item (uses empty tile)
- If item is far offscreen -> resetActiveItem
- Fetch next item
Check if mobile item number is still valid -> getItemNextAnimationFrame

Helper functions:
Helper function 'getItemNextAnimationFrame' for animateScreen
- Using the information on the current state of the item, determine the next animation frame and position (full behavior description)
Coin behavior description:
if (itemNr = 707) then                      -- last frame
  set activeItem["activeNr"] = 700          -- reset to first frame
  set activeItem["activeNr"] = itemNr+1     -- increment to next frame (animation: 700->701->702->703->704->705->706->707->700 .. etc)
end if

set touch = MarvinTouch(activeItem,colNr,rowNr,"touch","square")    -- check if the coin touched Marvin (detection shape:square)
if (touch["touch"]) then
  addPoints(100)                            -- add 100 points to score counter
  addCoins(1)                               -- add 1 coin to coin counter
  playSoundNumber(1)                        -- play 'picked up item' sound
  set activeItem["activeNr"] = 728          -- turn current coin sprite into first frame of sparkle sprite
  set activeItem["frameDelay"] = 1          -- set frame delay to 1 (animate every frame)
  set activeItem["offsetY"] = -1            -- set vertical offset
  set activeItem["params"][1] = "single"    -- sparkle parameter 1: 'single' for loop once (other mode is invincibility sparkle animation)
end if
Added line-by-line comments for example only. Full code has no comments

Helper function 'MarvinTouch' for getItemNextAnimationFrame
- Check if the item touched Marvin ('touch', 'from where' and detection shape)
Helper function 'movePlatform' for getItemNextAnimationFrame
- Move a mobile platform. If it carries Marvin, move Marvin along with it.
Helper function 'detectMarvin' for movePlatform
- Check if the platform carries Marvin around
Helper function 'touchActiveItem' for getItemNextAnimationFrame
- Some activeItems can collide with each other with various results:
- If an enemy/breakable wall is hit, it'll lose hit points.
Helper function 'touchActiveItem_skip' for touchActiveItem
- Determine if the current item can collide
Helper function 'touchActiveItem_action' for touchActiveItem
- The action to perform when this item collides with the other

Helper function 'resetActiveItem' for animateScreen
- When an item goes too far offscreen, it needs to be reset to starting position (ie: enemies) or destroyed (ie: projectiles).

Helper function 'setScreenSprites' for setScreen
- Set the onscreen sprites
- Marvin, moving enemies, static enemies, enemy projectiles
- Coins, food items, extra life, angel, invincibility, keys, doors
Content setScreenSprites:
Set Marvin's location on screen
Set index of mobile active items
Hide all on-top sprites (must be reset every frame)
Set location and content of items matrix
Looping bottom to top will cause activeItemsMobile to be 'on top'
- Check if on-top item
-- Show the on-top sprite
- Set location (including offsets)
- Set visuals
Get next mobile item (uses empty tile)
- Check if mobile item is on screen, if not get next
- Check if mobile item number is still valid
- Check if on-top item
-- Set location
-- Set visuals
-- It is not: hide
- No more mobile items usable: hide

Update the statusbar
This may affect which frame is next
Ignore block if boss is dead

Marvin ran out of time on the bonus stage
Or has Marvin died, or ran out of time?

If the last flag is active, stage complete

If the boss has died, level complete

Play the correct background music


The short: Paused the game.

Show pause menu
During the pause screen theTime must not trigger a loss of time
Initialize values
Check input keys
- 1
- Down arrow
- Up arrow

Check key pressed
- Select one choice up
- Select one choice down
- Choice made

- Continue playing: return to normal gameplay
-- Destroy global and hide pause menu
- Quit game
-- Destroy global and hide pause menu
- No choice made
-- Show pause menu + selected option arrows

Stage Complete

The short: Mostly like Main, with several exceptions and special cases
- Move Marvin according to physics
- Animate Marvin according to his action
- Update vertical scrolling
- Set all sprites according to scroll height and other data

Save Marvin's protection state (so it can't be overwritten by 'normal' animation)
Set standard 'next frame'
Initialize frameCount

Stage complete
- If boss level: Show "Level complete" overlay
- Else show "Stage <n> complete" overlay
-- Kill the music
- Fade to black
- Go to treasure screen

Bonus stage complete
- Initialize screen sprites
- Increment coin bonus (5 coins per second)
- Check if 50 coins (add 10k points if so)
- Add coin bonus (5 coin pairs per second)
- Check if MAGIC (add life if so)
- Fade tiles (background,tiles,item) to 0
- Fade to black
- Go to intro screen

Keep animating the screen normally
- Only during boss stage complete allow movement
- During all other stage complete: sticky Marvin
- Move Marvin to his new position
- Animate Marvin
- Prevent scrolling too far
- No scrolling if on a boss map
- Offset scrolling on a dream map
- Set all tiles, sprites and items on screen
- All actions performed: show results on screen
- Restore Marvin's protection state (so he's immortal)

Go to next level
- In case of boss win: save globals and start dream
- In case of dream complete: save globals and go to next tower
- In case of stage complete: go to treasure page


The short: The screen where Marvin pops up from a big treasure chest. Here you get bonus points, yay!

Set the tower and stage to the next level
- From stage (tower 1..6) -> to next stage/boss (tower 1..6)
- From boss (tower 1..5) -> to dream (tower 1..5)
- From dream (tower 1..5) -> to first stage of next tower (tower 2..6)

Fade from black
Raise bonus and numbers sprites
- Partway: dim background slightly
Count down coins (100 points each)
Count down time (5 points per second)
Add angel bonus (4795 points)
Wait for button input
Fade to black
Go to next level

Helper functions:
Helper function 'showTreasureCoins' for Treasure
- Shows the number of coins (clipped to 99)
Helper function 'showTreasureTime' for Treasure
- Shows the time (m:ss format, clipped to 9:59)
Helper function 'showTreasureBonusPoints' for Treasure
- Shows the number of bonus points (clipped to 999999)
Helper function 'createBouncingCoin' for Treasure
- Creates a bouncing coin (alternating to the left and right, with a random speed and angle)
Helper function 'animateTreasure' for Treasure
- Animates the bouncing coins

Start Dream

The short: After a boss battle, show this screen, then continue to the dream

Fade from black
Show screen for a few seconds
Fade to black
Go to dream

Game Over

The short: The Game Over screen

Fade from black
Move Marvin left to center
- Listen to hot coffee input
If hotCoffee
- Do hotCoffee not built yet
- Marvin looks up
- Move panda
- With screenshake
End if
Fade to black
Restart game (go to initialize)