/*
 * This file is part of the OpenSupaplex distribution (https://github.com/sergiou87/open-supaplex).
 * Copyright (c) 2020 Sergio Padrino
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "animations.h"
#include "audio.h"
#include "buttonBorders.h"
#include "commandLineParser.h"
#include "conditionals.h"
#include "config.h"
#include "controller.h"
#include "demo.h"
#include "file.h"
#include "globals.h"
#include "graphics.h"
#include "input.h"
#include "keyboard.h"
#include "logging.h"
#include "menu.h"
#include "savegame.h"
#include "touchscreen.h"
#include "utils.h"
#include "video.h"
#include "virtualKeyboard.h"
#include "system.h"

#ifdef __PSP__
#include <pspkernel.h>

// PSP_MODULE_INFO("OpenSupaplex", 0, 7, 1); -> SDL_main sets this for us, for now at least
PSP_MAIN_THREAD_ATTR(THREAD_ATTR_USER | THREAD_ATTR_VFPU);
PSP_HEAP_SIZE_KB(-1024);
#endif

// title1DataBuffer -> A000:4DAC - A000:CAAC
// title2DataBuffer -> 0x4DD4 - 0xCAD4

// maps are 58 x 22 tiles

typedef enum
{
    UserInputNone = 0,
    UserInputUp = 1,
    UserInputLeft = 2,
    UserInputDown = 3,
    UserInputRight = 4,
    UserInputSpaceUp = 5,
    UserInputSpaceLeft = 6,
    UserInputSpaceDown = 7,
    UserInputSpaceRight = 8,
    UserInputSpaceOnly = 9,
} UserInput;

static const uint8_t kUserInputSpaceAndDirectionOffset = (UserInputSpaceUp - 1);

uint8_t byte_50919 = 0;
uint8_t byte_5091A = 0;
UserInput gCurrentUserInput = 0; // byte_50941 -> 0x0631
uint8_t byte_50946 = 0;
uint16_t word_50947 = 0;
uint16_t word_50949 = 0;
uint8_t byte_50953 = 0;
uint8_t byte_50954 = 0;
uint8_t gShouldAutoselectNextLevelToPlay = 0; // byte_51ABE
uint8_t gHasChangedLevelSetFromAdvancedMenu = 0;
uint8_t byte_58D47 = 0;                              //
uint8_t byte_59821 = 0;                              //
uint8_t byte_59822 = 0;                              //
uint8_t byte_59823 = 0;                              //
uint8_t gDemoRecordingRandomGeneratorSeedHigh = 0;   // byte_59B5C
uint8_t gDemoRecordingRandomGeneratorSeedLow = 0;    // byte_59B5F
uint8_t gToggleFancyEasyTilesThrottleCounter = 0;    // byte_59B7A -> data_subrest
uint8_t gIsShowingFancyTiles = 1;                    // byte_59B7B -> data_subrstflg
uint8_t gToggleGravityAutorepeatFlag = 0;            // byte_59B7C
uint8_t gToggleZonksFrozenAutorepeatFlag = 0;        // byte_59B7D
uint8_t gToggleEnemiesFrozenAutorepeatFlag = 0;      // byte_59B7E
uint8_t gDebugSkipPreviousLevelAutorepeatFlag_1 = 0; // byte_59B7F
uint8_t gDebugSkipPreviousLevelAutorepeatFlag_2 = 0; // byte_59B80
uint8_t gDebugSkipNextLevelAutorepeatFlag_1 = 0;     // byte_59B81
uint8_t gDebugSkipNextLevelAutorepeatFlag_2 = 0;     // byte_59B82
uint8_t byte_59B83 = 0;
uint8_t byte_59B86 = 0;
uint16_t gDemoRecordingRandomGeneratorSeed = 0; // word_5A199
// uint8_t byte_5A140 = 0; // speedFixMagicNumber inside of level
uint8_t byte_5A19B = 0;
uint8_t gIsLevelStartedAsDemo = 0;     // byte_5A19C
uint8_t gDemoRecordingJustStarted = 0; // byte_5A2F8
uint8_t gHasUserCheated = 0;           // byte_5A2F9
uint8_t byte_5A323 = 0;
uint16_t word_5A33C = 0;
uint8_t gHasUserInterruptedDemo = 0; // byte_5A33E
uint8_t gIsGameBusy = 0;             // byte_5A33F -> this was used mainly to avoid some graphic glitches when some text from the main menu was written on the game field
// uint8_t gIsMouseAvailable = 0; // byte_58487
uint8_t gLevelListButtonPressed = 0;       // byte_50918
uint8_t gLevelListDownButtonPressed = 0;   // byte_50916
uint8_t gLevelListUpButtonPressed = 0;     // byte_50917
uint8_t gNewPlayerEntryIndex = 0;          // byte_59820
uint8_t gPlayerListButtonPressed = 0;      // byte_50912
uint8_t gPlayerListDownButtonPressed = 0;  // byte_50910
uint8_t gPlayerListUpButtonPressed = 0;    // byte_50911
uint8_t gRankingListButtonPressed = 0;     // byte_50915
uint8_t gRankingListDownButtonPressed = 0; // byte_50913
uint8_t gRankingListUpButtonPressed = 0;   // byte_50914
uint16_t gCurrentSelectedLevelIndex = 0;   // word_51ABC
uint16_t gNewPlayerNameLength = 0;         // word_58475

uint16_t word_5157A = 0x4A62;                    // -> 0x126A -> (64, 132)
uint16_t word_5157C = 0x0502;                    // -> 0x126C -> (97, 132)
uint16_t kMurphyStillSpriteCoordinates = 0x4A80; // word_5157E -> 0x126E -> (304, 132)
uint16_t word_51580 = 0x1AB2;                    // -> 0x1270 -> (0, 32)
uint16_t word_515A2 = 0x32A2;                    // -> 0x1292 -> (224, 82) ??

uint16_t word_515C4 = 0x1358; // (240, 178) -> 0x12B4

// { 128, 64 }, // -> 0x2a02 -> 12f6 -> orange disk falling

uint16_t word_5177E = 0x0CAE;                   //  -> 0x129 -> (256, 164)
uint16_t word_51790 = 0x4A7E;                   //  -> 0x129 -> (288, 132)
uint16_t word_5182E = 0x2A64;                   // -> (272, 388)
uint16_t word_51840 = 0x2A06;                   //  -> 0x129 -> (160, 64)
uint16_t word_51842 = 0x132C;                   //  -> 0x129 -> (208, 16) confirmed
uint16_t word_51844 = 0x2A08;                   //  -> 0x129 -> (176, 64)
uint16_t word_51846 = 0x132A;                   //  -> 0x129 -> (192, 16)
uint16_t kTerminalOnSpriteCoordinates = 0x2A62; // word_51848 -> 0x1268 -> (256, 388) I don't get the math for this one, but the coordinates are right
uint16_t word_5184A = 0x2A66;                   //  -> 0x1268 -> (
uint16_t word_5184C = 0x2A67;                   //  -> 0x1268 -> (
uint16_t word_5184E = 0x2E36;                   //  -> 0x1268 -> (
uint16_t word_51850 = 0x2E37;                   //  -> 0x1268 -> (
uint16_t word_51852 = 0x2A68;                   //  -> 0x1268 -> (
uint16_t word_51854 = 0x2A69;                   //  -> 0x1268 -> (
uint16_t word_51856 = 0x2E38;                   //  -> 0x1268 -> (
uint16_t word_51858 = 0x2E39;                   //  -> 0x1268 -> (
uint16_t gIsMoveScrollModeEnabled = 0;          // word_51A01
uint16_t gDebugExtraRenderDelay = 1;            // this was used to add an extra delay in debug mode using keys 1-9
uint16_t word_58463 = 0;
uint8_t gIsInMainMenu = 0;
uint16_t gAutomaticDemoPlaybackCountdown = 0; // word_58465
uint16_t word_58467 = 0;
uint16_t gLevelListThrottleCurrentCounter = 0; // word_58469
uint16_t gLevelListThrottleNextCounter = 0;
uint16_t gPlayerListThrottleCurrentCounter = 0;  // word_5846D
uint16_t gPlayerListThrottleNextCounter = 0;     // word_5846F
uint16_t gRankingListThrottleCurrentCounter = 0; // word_58471
uint16_t gRankingListThrottleNextCounter = 0;    // word_58473
uint16_t gSelectedOriginalDemoIndex = 0;         // word_599D6 -> used loading old demo files demo
uint16_t gSelectedOriginalDemoLevelNumber = 0;   // word_599D8 -> used loading old demo files demo -> the high byte is set to -1 in readLevels for some unknown reason
// These two store the scroll offset to get back to Murphy when we're in "free mode"
uint16_t gMurphyScrollOffsetX = 0;                    // word_59B88
uint16_t gMurphyScrollOffsetY = 0;                    // word_59B8A
uint16_t gLevelSetRotationThrottleCurrentCounter = 0; // word_59B8C
uint16_t gLevelSetRotationThrottleNextCounter = 0;    // word_59B8E
uint16_t gLastDrawnMinutesAndSeconds;                 // word_510B7
uint8_t gLastDrawnHours;                              // byte_510B9
FILE *gCurrentRecordingDemoFile;                      // word_510E4
uint8_t gDemoRecordingLowestSpeed;                    // speed?2
int16_t gAdditionalScrollOffsetX;                     // word_51963
int16_t gAdditionalScrollOffsetY;                     // word_51965
uint8_t isJoystickEnabled = 0;                        // byte_50940
uint8_t isMusicEnabled = 0;                           // byte_59886
uint8_t isFXEnabled = 0;                              // byte_59885

uint8_t gIsFlashingBackgroundModeEnabled = 0; // flashingbackgroundon
const float kSpeedTimeFactors[kNumberOfGameSpeeds] = {3.5, 3.0, 2.5, 2.0, 1.5, 1.0, 0.75, 2.0 / 3.0, 5.0 / 8.0, 3.0 / 5.0, 1.0 / 70.0};

// Used to measure game speed (reference is 35 iterations per second)
float gGameIterationRate = 0.f;
uint32_t gGameIterationRateReferenceTime = 0;

// Used to limit game speed
uint32_t gGameIterationStartTime = 0;
uint32_t gNumberOfGameIterations = 0;

static const uint16_t kFallAnimationGravityOffsets[18] = {
    0x0000, // -> 0x6C95
    0x007A, // -> 0x6C97
    0x00F4, // -> 0x6C99
    0x016E, // -> 0x6C9B
    0x01E8, // -> 0x6C9D
    0x0262, // -> 0x6C9F
    0x02DC, // -> 0x6CA1
    0x0356, // -> 0x6CA3
    0x03D0, // -> 0x6CA5
    0x044A, // -> 0x6CA7
    0x04C4, // -> 0x6CA9
    0x053E, // -> 0x6CAB
    0x05B8, // -> 0x6CAD
    0x0632, // -> 0x6CAF
    0x06AC, // -> 0x6CB1
    0x0726, // -> 0x6CB3
    0x07A0, // -> 0x6CB5
    0x081A, // -> 0x6CB7
};

static const int kConfigDataLength = 4;

uint16_t gRandomGeneratorSeed = 0;

enum MouseButton
{
    MouseButtonLeft = 1 << 0,
    MouseButtonRight = 1 << 1,
};

uint8_t gShouldCloseAdvancedMenu = 0;
uint8_t gAdvancedMenuRecordDemoIndex = 0;
uint8_t gAdvancedMenuPlayDemoIndex = 0;

typedef struct
{
    uint16_t startX, startY;
    uint16_t endX, endY;
    void (*handler)(void);
} ButtonDescriptor;

void handleNewPlayerOptionClick(void);
void handlePlayerListScrollUp(void);
void handlePlayerListScrollDown(void);
void handlePlayerListClick(void);
void handleLevelListScrollUp(void);
void handleLevelListScrollDown(void);
void handleRankingListScrollUp(void);
void handleRankingListScrollDown(void);
void handleLevelCreditsClick(void);
void handleGfxTutorOptionClick(void);
void handleDemoOptionClick(void);
void handleSkipLevelOptionClick(void);
void handleFloppyDiskButtonClick(void);
void handleDeletePlayerOptionClick(void);
void handleStatisticsOptionClick(void);
void handleControlsOptionClick(void);
void handleOkButtonClick(void);

#define kNumberOfMainMenuButtons 17
static const ButtonDescriptor kMainMenuButtonDescriptors[kNumberOfMainMenuButtons] = { // located in DS:0000
    {
        5, 6,
        157, 14,
        handleNewPlayerOptionClick, // New player
    },
    {
        5, 15,
        157, 23,
        handleDeletePlayerOptionClick, // Delete player
    },
    {
        5, 24,
        157, 32,
        handleSkipLevelOptionClick, // Skip level
    },
    {
        5, 33,
        157, 41,
        handleStatisticsOptionClick, // Statistics
    },
    {
        5, 42,
        157, 50,
        handleGfxTutorOptionClick, // GFX-tutor
    },
    {
        5, 51,
        157, 59,
        handleDemoOptionClick, // Demo
    },
    {
        5, 60,
        157, 69,
        handleControlsOptionClick, // Controls
    },
    {
        140, 90,
        155, 108,
        handleRankingListScrollUp, // Rankings arrow up
    },
    {
        140, 121,
        155, 138,
        handleRankingListScrollDown, // Rankings arrow down
    },
    {
        96, 140,
        115, 163,
        handleOkButtonClick, // Ok button
    },
    {
        83, 168,
        126, 192,
        handleFloppyDiskButtonClick, // Insert data disk according to https://supaplex.fandom.com/wiki/Main_menu
    },
    {
        11, 142,
        67, 153,
        handlePlayerListScrollUp, // Players arrow up
    },
    {
        11, 181,
        67, 192,
        handlePlayerListScrollDown, // Players arrow down
    },
    {
        11, 154,
        67, 180,
        handlePlayerListClick, // Players list area
    },
    {
        142, 142,
        306, 153,
        handleLevelListScrollUp, // Levels arrow up
    },
    {
        142, 181,
        306, 192,
        handleLevelListScrollDown, // Levels arrow down
    },
    {
        297, 37,
        312, 52,
        handleLevelCreditsClick, // Credits
    }};

void handleOptionsExitAreaClick(void);
void handleOptionsMusicClick(void);
void handleOptionsAdlibClick(void);
void handleOptionsSoundBlasterClick(void);
void handleOptionsRolandClick(void);
void handleOptionsCombinedClick(void);
void handleOptionsStandardClick(void);
void handleOptionsSamplesClick(void);
void handleOptionsInternalClick(void);
void handleOptionsFXClick(void);
void handleOptionsKeyboardClick(void);
void handleOptionsJoystickClick(void);

#define kNumberOfOptionsMenuButtons 13
static const ButtonDescriptor kOptionsMenuButtonDescriptors[kNumberOfOptionsMenuButtons] = {
    // located in DS:00AC
    {
        12, 13,
        107, 36,
        handleOptionsAdlibClick, // Adlib
    },
    {
        12, 49,
        107, 72,
        handleOptionsSoundBlasterClick, // Sound Blaster
    },
    {
        12, 85,
        107, 108,
        handleOptionsRolandClick, // Roland
    },
    {
        12, 121,
        107, 144,
        handleOptionsCombinedClick, // Combined
    },
    {
        132, 13,
        211, 31,
        handleOptionsInternalClick, // Internal
    },
    {
        126, 43,
        169, 54,
        handleOptionsStandardClick, // Standard
    },
    {
        174, 43,
        217, 54,
        handleOptionsSamplesClick, // Samples
    },
    {
        132, 86,
        175, 120,
        handleOptionsMusicClick, // Music
    },
    {
        134, 132,
        168, 152,
        handleOptionsFXClick, // FX
    },
    {
        201, 80,
        221, 154,
        handleOptionsKeyboardClick, // Keyboard
    },
    {
        233, 80,
        252, 154,
        handleOptionsJoystickClick, // Joystick
    },
    {
        0, 181,
        319, 199,
        handleOptionsExitAreaClick, // Exit (bottom)
    },
    {
        284, 0,
        319, 180,
        handleOptionsExitAreaClick, // Exit (right)
    },
};

typedef void (*MovingFunction)(int16_t);
typedef void (*FrameBasedMovingFunction)(int16_t, uint8_t);

void updateZonkTiles(int16_t position);
void updateInfotronTiles(int16_t position);
void updateOrangeDiskTiles(int16_t position);
void updateSnikSnakTiles(int16_t position);
void updateTerminalTiles(int16_t position);
void updateElectronTiles(int16_t position);
void updateBugTiles(int16_t position);
void updateExplosionTiles(int16_t position);

static const MovingFunction movingFunctions[32] = {
    NULL,
    updateZonkTiles,
    NULL,
    NULL,
    updateInfotronTiles,
    NULL,
    NULL,
    NULL,
    updateOrangeDiskTiles,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    updateSnikSnakTiles,
    NULL,
    updateTerminalTiles,
    NULL,
    NULL,
    NULL,
    NULL,
    updateElectronTiles,
    updateBugTiles,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    updateExplosionTiles,
};

void updateElectronTurnLeft(int16_t position, uint8_t frame);
void updateElectronTurnRight(int16_t position, uint8_t frame);
void updateElectronMovementUp(int16_t position, uint8_t frame);
void updateElectronMovementDown(int16_t position, uint8_t frame);
void updateElectronMovementRight(int16_t position, uint8_t frame);
void updateElectronMovementLeft(int16_t position, uint8_t frame);

static const FrameBasedMovingFunction kElectronMovingFunctions[] = {
    updateElectronTurnLeft,
    updateElectronTurnLeft,
    updateElectronTurnLeft,
    updateElectronTurnLeft,
    updateElectronTurnLeft,
    updateElectronTurnLeft,
    updateElectronTurnLeft,
    updateElectronTurnLeft,
    updateElectronTurnRight,
    updateElectronTurnRight,
    updateElectronTurnRight,
    updateElectronTurnRight,
    updateElectronTurnRight,
    updateElectronTurnRight,
    updateElectronTurnRight,
    updateElectronTurnRight,
    updateElectronMovementUp,
    updateElectronMovementUp,
    updateElectronMovementUp,
    updateElectronMovementUp,
    updateElectronMovementUp,
    updateElectronMovementUp,
    updateElectronMovementUp,
    updateElectronMovementUp,
    updateElectronMovementDown,
    updateElectronMovementDown,
    updateElectronMovementDown,
    updateElectronMovementDown,
    updateElectronMovementDown,
    updateElectronMovementDown,
    updateElectronMovementDown,
    updateElectronMovementDown,
    updateElectronMovementRight,
    updateElectronMovementRight,
    updateElectronMovementRight,
    updateElectronMovementRight,
    updateElectronMovementRight,
    updateElectronMovementRight,
    updateElectronMovementRight,
    updateElectronMovementRight,
    updateElectronMovementLeft,
    updateElectronMovementLeft,
    updateElectronMovementLeft,
    updateElectronMovementLeft,
    updateElectronMovementLeft,
    updateElectronMovementLeft,
    updateElectronMovementLeft,
    updateElectronMovementLeft,
};

void updateSnikSnakTurnLeft(int16_t position, uint8_t frame);
void updateSnikSnakTurnRight(int16_t position, uint8_t frame);
void updateSnikSnakMovementUp(int16_t position, uint8_t frame);
void updateSnikSnakMovementLeft(int16_t position, uint8_t frame);
void updateSnikSnakMovementDown(int16_t position, uint8_t frame);
void updateSnikSnakMovementRight(int16_t position, uint8_t frame);

static const FrameBasedMovingFunction kSnikSnakMovingFunctions[48] = {
    updateSnikSnakTurnLeft,
    updateSnikSnakTurnLeft,
    updateSnikSnakTurnLeft,
    updateSnikSnakTurnLeft,
    updateSnikSnakTurnLeft,
    updateSnikSnakTurnLeft,
    updateSnikSnakTurnLeft,
    updateSnikSnakTurnLeft,
    updateSnikSnakTurnRight,
    updateSnikSnakTurnRight,
    updateSnikSnakTurnRight,
    updateSnikSnakTurnRight,
    updateSnikSnakTurnRight,
    updateSnikSnakTurnRight,
    updateSnikSnakTurnRight,
    updateSnikSnakTurnRight,
    updateSnikSnakMovementUp,
    updateSnikSnakMovementUp,
    updateSnikSnakMovementUp,
    updateSnikSnakMovementUp,
    updateSnikSnakMovementUp,
    updateSnikSnakMovementUp,
    updateSnikSnakMovementUp,
    updateSnikSnakMovementUp,
    updateSnikSnakMovementLeft,
    updateSnikSnakMovementLeft,
    updateSnikSnakMovementLeft,
    updateSnikSnakMovementLeft,
    updateSnikSnakMovementLeft,
    updateSnikSnakMovementLeft,
    updateSnikSnakMovementLeft,
    updateSnikSnakMovementLeft,
    updateSnikSnakMovementDown,
    updateSnikSnakMovementDown,
    updateSnikSnakMovementDown,
    updateSnikSnakMovementDown,
    updateSnikSnakMovementDown,
    updateSnikSnakMovementDown,
    updateSnikSnakMovementDown,
    updateSnikSnakMovementDown,
    updateSnikSnakMovementRight,
    updateSnikSnakMovementRight,
    updateSnikSnakMovementRight,
    updateSnikSnakMovementRight,
    updateSnikSnakMovementRight,
    updateSnikSnakMovementRight,
    updateSnikSnakMovementRight,
    updateSnikSnakMovementRight,
};

void throttledRotateLevelSet(uint8_t descending);
void rotateLevelSet(uint8_t descending);
void updateMenuAfterLevelSetChanged(void);
void initializeGameStateData(void);
void startDirectlyFromLevel(uint8_t levelNumber);
void stopDemoAndPlay(void);
void emulateClock(void);
void loadScreen2(void);
void readEverything(void);
void drawSpeedFixTitleAndVersion(void);
void openCreditsBlock(void);
void drawSpeedFixCredits(void);
void readConfig(void);
void activateAdlibSound(void);
void activateSoundBlasterSound(void);
void activateRolandSound(void);
void activateCombinedSound(void);
void activateInternalStandardSound(void);
void activateInternalSamplesSound(void);
void prepareDemoRecordingFilename(void);
void runMainMenu(void);
void convertNumberTo3DigitPaddedString(uint8_t number, char numberString[3], char useSpacesForPadding);
void stopMusicAndSounds(void);
void playMusicIfNeeded(void);
void stopMusic(void);
void playExplosionSound(void);
void playInfotronSound(void);
void playPushSound(void);
void playFallSound(void);
void playBugSound(void);
void playBaseSound(void);
void playExitSound(void);
void sound11(void);
void savePlayerListData(void);
void saveHallOfFameData(void);
void getMouseStatus(uint16_t *mouseX, uint16_t *mouseY, uint16_t *mouseButtonStatus);
void drawMainMenuButtonBorders(void);
void drawMainMenuButtonBorder(ButtonBorderDescriptor border, uint8_t color);
void generateRandomSeedFromClock(void);
void initializeFadePalette(void);
void initializeMouse(void);
void prepareLevelDataForCurrentPlayer(void);
void drawPlayerList(void);
void drawLevelList(void);
void drawHallOfFame(void);
void drawRankings(void);
void drawTextWithChars6FontWithOpaqueBackgroundIfPossible(size_t destX, size_t destY, uint8_t color, const char *text);
void drawTextWithChars6FontWithTransparentBackgroundIfPossible(size_t destX, size_t destY, uint8_t color, const char *text);
void waitForKeyMouseOrJoystick(void);
void drawMenuTitleAndDemoLevelResult(void);
void scrollRightToNewScreen(void);
void scrollLeftToMainMenu(void);
void convertNumberTo3DigitStringWithPadding0(uint8_t number, char numberString[3]);
void changePlayerCurrentLevelState(void);
void updateHallOfFameEntries(void);
void drawSoundTypeOptionsSelection(uint8_t *destBuffer);
void dimOptionsButtonText(size_t startX, size_t startY, size_t width, size_t height, uint8_t *destBuffer);
void drawOptionsMenuLine(ButtonBorderDescriptor border, uint8_t color, uint8_t *destBuffer);
void highlightOptionsButtonText(size_t startX, size_t startY, size_t width, size_t height, uint8_t *destBuffer);
void drawAudioOptionsSelection(uint8_t *destBuffer);
void drawInputOptionsSelection(uint8_t *destBuffer);
void updateOptionsMenuState(uint8_t *destBuffer);
void convertLevelNumberTo3DigitStringWithPadding0(uint8_t number);
void readLevels(void);
void initializeGameInfo(void);
void drawGamePanel(void);
void drawNumberOfRemainingInfotrons(void);
void drawGameTime(void);
uint16_t convertToEasyTiles(void);
void resetNumberOfInfotrons(uint16_t numberOfInfotronsInGameField);
void findMurphy(void);
void drawGamePanelText(void);
void scrollToMurphy(void);
void runLevel(void);
void slideDownGameDash(void);
void updateScrollOffset(void);
uint16_t generateRandomNumber(void);
void handleGameUserInput(void);
void loc_49C41(void);
void loc_49C2C(char text[3]);
void showSavegameOperationError(void);
void saveGameSnapshot(void);
void loadGameSnapshot(void);
void checkDebugKeys(void);
void loc_4988E(void);
void restoreOriginalFancyTiles(void);
void updateMovingObjects(void);
int16_t updateMurphy(int16_t position);
int16_t updateMurphyAnimation(int16_t position);
int16_t updateMurphyAnimationInfo(int16_t position, MurphyAnimationDescriptor unknownMurphyData);
int16_t handleMurphyDirectionRight(int16_t position);
int16_t handleMurphyDirectionDown(int16_t position);
int16_t handleMurphyDirectionLeft(int16_t position);
int16_t handleMurphyDirectionUp(int16_t position);
void detonateYellowDisks(void);
void updateUserInputInScrollMovementMode(void);
void updateUserInput(void);
void saveInputForDemo(void);
void simulateDemoInput(void);
void fetchAndInitializeLevel(void);
void removeTiles(LevelTileType tileType);
void restartLevel(void);
void restartLevelWithoutAddingCurrentGameTimeToPlayer(void);
void recordDemo(uint16_t demoIndex);
void stopRecordingDemo(void);
void debugSkipLevel(void);
void forceRestoreOriginalFancyTiles(void);
void drawNumberOfRemainingRedDisks(void);
void clearAdditionalInfoInGamePanelIfNeeded(void);
void updatePlantedRedDisk(void);
void updateExplosionTimers(void);
void addCurrentGameTimeToPlayer(void);
void drawFailedLevelResultScreen(void);
void handleZonkStateAfterFallingOneTile(int16_t position);
void detonateBigExplosion(int16_t position);
void detonateZonk(int16_t position, uint8_t state, uint8_t tile);
void sub_4AA34(int16_t position, uint8_t state, uint8_t tile);
void sub_4AAB4(int16_t position);
uint8_t checkMurphyMovementToPosition(int16_t position, UserInput userInput);
void handleZonkPushedByMurphy(int16_t position);
void decreaseRemainingRedDisksIfNeeded(int16_t position);
void updateSpecialPort(int16_t position);
void handleInfotronStateAfterFallingOneTile(int16_t position);
void int9handler(uint8_t shouldYieldCpu);
void updateDemoRecordingLowestSpeed(void);
void playDemo(uint16_t demoIndex);

static const char *kAdvancedConfigGeneralSection = "general";
static const char *kAdvancedConfigDebugSection = "debug";

static const char *kAdvancedConfigPlayerKey = "player";
static const char *kAdvancedConfigLevelSetKey = "levelSet";
static const char *kAdvancedConfigGameSpeedKey = "gameSpeed";
static const char *kAdvancedConfigMusicVolumeKey = "musicVolume";
static const char *kAdvancedConfigFXVolumeKey = "fxVolume";
static const char *kAdvancedConfigScalingModeKey = "scalingMode";
static const char *kAdvancedConfigFullscreenKey = "fullscreen";
static const char *kAdvancedConfigDisplayFPSKey = "displayFPS";
static const char *kAdvancedConfigLimitFPSKey = "limitFPS";

void readAdvancedConfig()
{
    Config *config = initializeConfigForReading("ADVANCED.CFG");

    if (config == NULL)
    {
        spLogInfo("Couldn't read advanced config");
        return;
    }

    char currentSuffix[3] = "AT";
    strcpy(currentSuffix, &gLevelsDatFilename[8]);

    int player = readConfigInt(config, kAdvancedConfigGeneralSection, kAdvancedConfigPlayerKey, 0);
    player = CLAMP(player, 0, kNumberOfPlayers - 1);
    gCurrentPlayerIndex = player;

    // Only apply level set from config if it wasn't overriden before (by command line)
    if (strcmp(currentSuffix, "AT") == 0)
    {
        int levelSet = readConfigInt(config, kAdvancedConfigGeneralSection, kAdvancedConfigLevelSetKey, 0);

        if (levelSet != 0)
        {
            char newSuffix[3] = "00";
            snprintf(newSuffix, 3, "%02d", levelSet);

            strcpy(&gLevelsDatFilename[8], newSuffix);
            strcpy(&gLevelLstFilename[7], newSuffix);
            strcpy(&gDemo0BinFilename[7], newSuffix);
            strcpy(&gPlayerLstFilename[8], newSuffix);
            strcpy(&gHallfameLstFilename[10], newSuffix);

            if (gShouldAlwaysWriteSavegameSav == 0) // cmp byte ptr gShouldAlwaysWriteSavegameSav, 0
            {
                strcpy(&gSavegameSavFilename[10], newSuffix);
            }
        }
    }

    gGameSpeed = readConfigInt(config, kAdvancedConfigGeneralSection, kAdvancedConfigGameSpeedKey, kDefaultGameSpeed);

    int volume = readConfigInt(config, kAdvancedConfigGeneralSection, kAdvancedConfigMusicVolumeKey, getMusicVolume());
    setMusicVolume(volume);

    volume = readConfigInt(config, kAdvancedConfigGeneralSection, kAdvancedConfigFXVolumeKey, getSoundEffectsVolume());
    setSoundEffectsVolume(volume);

    int scalingMode = readConfigInt(config, kAdvancedConfigGeneralSection, kAdvancedConfigScalingModeKey, getScalingMode());
    setScalingMode(scalingMode);

    gShouldShowFPS = readConfigInt(config, kAdvancedConfigDebugSection, kAdvancedConfigDisplayFPSKey, gShouldShowFPS);
    gShouldLimitFPS = readConfigInt(config, kAdvancedConfigDebugSection, kAdvancedConfigLimitFPSKey, gShouldLimitFPS);

    int fullscreen = readConfigInt(config, kAdvancedConfigGeneralSection, kAdvancedConfigFullscreenKey, getFullscreenMode());
    setFullscreenMode(fullscreen);

    destroyConfig(config);
}

void writeAdvancedConfig()
{
    Config *config = initializeConfigForWriting("ADVANCED.CFG");

    if (config == NULL)
    {
        spLogInfo("Couldn't write advanced config");
        return;
    }

    writeConfigSection(config, kAdvancedConfigGeneralSection);

    char currentSuffix[3] = "AT";
    strcpy(currentSuffix, &gLevelsDatFilename[8]);

    int levelSet = 0;

    if (strcmp(currentSuffix, "AT") != 0)
    {
        levelSet = atoi(currentSuffix);
    }

    writeConfigInt(config, kAdvancedConfigPlayerKey, gCurrentPlayerIndex);
    writeConfigInt(config, kAdvancedConfigLevelSetKey, levelSet);
    writeConfigInt(config, kAdvancedConfigGameSpeedKey, gGameSpeed);
    writeConfigInt(config, kAdvancedConfigMusicVolumeKey, getMusicVolume());
    writeConfigInt(config, kAdvancedConfigFXVolumeKey, getSoundEffectsVolume());
    writeConfigInt(config, kAdvancedConfigScalingModeKey, getScalingMode());
    writeConfigInt(config, kAdvancedConfigFullscreenKey, getFullscreenMode());

    writeConfigSection(config, kAdvancedConfigDebugSection);
    writeConfigInt(config, kAdvancedConfigDisplayFPSKey, gShouldShowFPS);
    writeConfigInt(config, kAdvancedConfigLimitFPSKey, gShouldLimitFPS);

    destroyConfig(config);
}

/// @return 1 if the action was to go back / close the menu
uint8_t handleAdvancedOptionsMenuInput(AdvancedOptionsMenu *menu)
{
    if (isUpButtonPressed())
    {
        playBaseSound();
        moveUpAdvancedOptionsSelectedEntry(menu);
    }

    if (isDownButtonPressed())
    {
        playBaseSound();
        moveDownAdvancedOptionsSelectedEntry(menu);
    }

    if (isLeftButtonPressed())
    {
        playBaseSound();
        decreaseAdvancedOptionsSelectedEntry(menu);
    }

    if (isRightButtonPressed())
    {
        playBaseSound();
        increaseAdvancedOptionsSelectedEntry(menu);
    }

    if (isMenuConfirmButtonPressed())
    {
        playInfotronSound();
        selectAdvancedOptionsSelectedEntry(menu);
    }

    if (isMenuCancelButtonPressed())
    {
        playPushSound();
        return 1;
    }

    if (isPauseButtonPressed())
    {
        playPushSound();
        gShouldCloseAdvancedMenu = 1;
        return 1;
    }

    if (gShouldCloseAdvancedMenu)
    {
        return 1;
    }

    return 0;
}

void advancedOptionsMenuWaitForKeyPress()
{
    do
    {
        int9handler(1);
    } while (isUpButtonPressed() == 0 && isDownButtonPressed() == 0 && isLeftButtonPressed() == 0 && isRightButtonPressed() == 0 && isMenuBackButtonPressed() == 0 && isMenuConfirmButtonPressed() == 0 && isMenuCancelButtonPressed() == 0 && isPauseButtonPressed() == 0);
}

void advancedOptionsMenuWaitForKeyRelease()
{
    do
    {
        int9handler(1);
    } while (isUpButtonPressed() || isDownButtonPressed() || isLeftButtonPressed() || isRightButtonPressed() || isMenuBackButtonPressed() || isMenuConfirmButtonPressed() || isMenuCancelButtonPressed() || isPauseButtonPressed());
}

void runAdvancedOptionsMenu(AdvancedOptionsMenu *menu)
{
    playFallSound();

    advancedOptionsMenuWaitForKeyRelease();

    do
    {
        if (handleAdvancedOptionsMenuInput(menu))
        {
            break;
        }

        renderAdvancedOptionsMenu(menu);
        advancedOptionsMenuWaitForKeyRelease();
        advancedOptionsMenuWaitForKeyPress();
    } while (1);

    advancedOptionsMenuWaitForKeyRelease();
}

void buildGameSpeedOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength])
{
    snprintf(output, kMaxAdvancedOptionsMenuEntryTitleLength, "GAME SPEED: %d", gGameSpeed);
}

void buildMusicVolumeOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength])
{
    snprintf(output, kMaxAdvancedOptionsMenuEntryTitleLength, "MUSIC VOLUME: %d", getMusicVolume());
}

void buildFXVolumeOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength])
{
    snprintf(output, kMaxAdvancedOptionsMenuEntryTitleLength, "FX VOLUME: %d", getSoundEffectsVolume());
}

void buildScalingModeOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength])
{
    static const char *kAspectFitScalingModeString = "NORMAL";
    static const char *kAspectFillScalingModeString = "ZOOM";
    static const char *kIntegerFactorScalingModeString = "INTEGER FACTOR";
    static const char *kFullscreenScalingModeString = "FULLSCREEN";
    static const char *kAspectCorrectScalingModeString = "4:3";

    const char *mode = kAspectFitScalingModeString;

    switch (getScalingMode())
    {
    case ScalingModeAspectFill:
        mode = kAspectFillScalingModeString;
        break;
    case ScalingModeIntegerFactor:
        mode = kIntegerFactorScalingModeString;
        break;
    case ScalingModeFullscreen:
        mode = kFullscreenScalingModeString;
        break;
    case ScalingModeAspectCorrect:
        mode = kAspectCorrectScalingModeString;
        break;
    default:
        mode = kAspectFitScalingModeString;
        break;
    }

    snprintf(output, kMaxAdvancedOptionsMenuEntryTitleLength, "SCALING MODE: %s", mode);
}

void buildBooleanOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength], char title[kMaxAdvancedOptionsMenuEntryTitleLength], uint8_t value)
{
    snprintf(output, kMaxAdvancedOptionsMenuEntryTitleLength, "%s: %s", title, (value == 0 ? "OFF" : "ON"));
}

void buildDisplayFPSOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength])
{
    buildBooleanOptionTitle(output, "DISPLAY FPS", gShouldShowFPS);
}

void buildLimitFPSOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength])
{
    buildBooleanOptionTitle(output, "LIMIT FPS", gShouldLimitFPS);
}

void buildPlayDemoOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength])
{
    snprintf(output, kMaxAdvancedOptionsMenuEntryTitleLength, "PLAY DEMO: %d", gAdvancedMenuPlayDemoIndex);
}

void buildRecordDemoOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength])
{
    snprintf(output, kMaxAdvancedOptionsMenuEntryTitleLength, "RECORD DEMO: %d", gAdvancedMenuRecordDemoIndex);
}

void increaseGameSpeed()
{
    if (gGameSpeed < kNumberOfGameSpeeds - 1)
    {
        gGameSpeed++;
    }

    updateDemoRecordingLowestSpeed();
}

void decreaseGameSpeed()
{
    if (gGameSpeed > 0)
    {
        gGameSpeed--;
    }

    updateDemoRecordingLowestSpeed();
}

void updateDemoRecordingLowestSpeed()
{
    if (gGameSpeed < gDemoRecordingLowestSpeed)
    {
        gDemoRecordingLowestSpeed = gGameSpeed;
    }
}

void increaseMusicVolume()
{
    uint8_t volume = getMusicVolume();

    if (volume < kMaxVolume)
    {
        setMusicVolume(volume + 1);
    }
}

void decreaseMusicVolume()
{
    uint8_t volume = getMusicVolume();

    if (volume > 0)
    {
        setMusicVolume(volume - 1);
    }
}

void increaseFXVolume()
{
    uint8_t volume = getSoundEffectsVolume();

    if (volume < kMaxVolume)
    {
        setSoundEffectsVolume(volume + 1);
    }
}

void decreaseAdvancedMenuRecordDemoIndex()
{
    if (gAdvancedMenuRecordDemoIndex > 0)
    {
        gAdvancedMenuRecordDemoIndex--;
    }
}

void increaseAdvancedMenuRecordDemoIndex()
{
    if (gAdvancedMenuRecordDemoIndex < kNumberOfDemos - 1)
    {
        gAdvancedMenuRecordDemoIndex++;
    }
}

void decreaseAdvancedMenuPlayDemoIndex()
{
    if (gAdvancedMenuPlayDemoIndex > 0)
    {
        gAdvancedMenuPlayDemoIndex--;
    }
}

void increaseAdvancedMenuPlayDemoIndex()
{
    if (gAdvancedMenuPlayDemoIndex < kNumberOfDemos - 1)
    {
        gAdvancedMenuPlayDemoIndex++;
    }
}

void decreaseAdvancedMenuScalingMode()
{
    ScalingMode mode = getScalingMode();
    if (mode > 0)
    {
        setScalingMode(mode - 1);
    }
    else
    {
        setScalingMode(ScalingModeCount - 1);
    }
}

void increaseAdvancedMenuScalingMode()
{
    ScalingMode mode = getScalingMode();
    if (mode < ScalingModeCount - 1)
    {
        setScalingMode(mode + 1);
    }
    else
    {
        setScalingMode(0);
    }
}

void decreaseFXVolume()
{
    uint8_t volume = getSoundEffectsVolume();

    if (volume > 0)
    {
        setSoundEffectsVolume(volume - 1);
    }
}

void toggleDisplayFPSOption()
{
    TOGGLE_BOOL(gShouldShowFPS);
}

void toggleLimitFPSOption()
{
    TOGGLE_BOOL(gShouldLimitFPS);
}

void handleResumeOptionSelection()
{
    gShouldCloseAdvancedMenu = 1;
}

void handleRestartLevelOptionSelection()
{
    restartLevel();
    gShouldCloseAdvancedMenu = 1;
}

void handleRemoveZonksOptionSelection()
{
    removeTiles(LevelTileTypeZonk);
    gShouldCloseAdvancedMenu = 1;
}

void handleRemoveHardwareOptionSelection()
{
    removeTiles(LevelTileTypeHardware);
    gShouldCloseAdvancedMenu = 1;
}

void handleRemoveBaseOptionSelection()
{
    removeTiles(LevelTileTypeBase);
    gShouldCloseAdvancedMenu = 1;
}

void handleRemoveChipsOptionSelection()
{
    removeTiles(LevelTileTypeChip);
    gShouldCloseAdvancedMenu = 1;
}

void handleRemoveSnikSnakOptionSelection()
{
    removeTiles(LevelTileTypeSnikSnak);
    gShouldCloseAdvancedMenu = 1;
}

void handleMoveScrollOptionSelection()
{
    gIsMoveScrollModeEnabled = 1;
    gShouldCloseAdvancedMenu = 1;
}

void handleRecordDemoOptionSelection()
{
    recordDemo(gAdvancedMenuRecordDemoIndex);
    gShouldCloseAdvancedMenu = 1;
}

void handlePlayDemoOptionSelection()
{
    playDemo(gAdvancedMenuPlayDemoIndex);
    gShouldCloseAdvancedMenu = 1;
}

void handleStopDemoAndPlayOptionSelection()
{
    stopDemoAndPlay();
    gShouldCloseAdvancedMenu = 1;
}

void handleStopRecordingDemoOptionSelection()
{
    stopRecordingDemo();
    gShouldCloseAdvancedMenu = 1;
}

void handleExitLevelOptionSelection()
{
    gShouldKillMurphy = 1;
    gShouldCloseAdvancedMenu = 1;
}

void handleExitGameOptionSelection()
{
    gShouldExitGame = 1;
    gShouldCloseAdvancedMenu = 1;
}

void runAdvancedOptionsSubMenu(AdvancedOptionsMenu menu)
{
    runAdvancedOptionsMenu(&menu);
}

void handleDebugOptionSelection()
{
    AdvancedOptionsMenu menu;
    initializeAdvancedOptionsMenu(&menu);

    strncpy(menu.title, "DEBUG (DANGER)", kMaxAdvancedOptionsMenuEntryTitleLength);

    addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                       "",
                                       buildDisplayFPSOptionTitle,
                                       toggleDisplayFPSOption,
                                       toggleDisplayFPSOption,
                                       toggleDisplayFPSOption,
                                   });
    addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                       "",
                                       buildLimitFPSOptionTitle,
                                       toggleLimitFPSOption,
                                       toggleLimitFPSOption,
                                       toggleLimitFPSOption,
                                   });
    if (gIsInMainMenu == 0)
    {
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "LOAD GAME STATE",
                                           NULL,
                                           loadGameSnapshot,
                                           NULL,
                                           NULL,
                                       });
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "SAVE GAME STATE",
                                           NULL,
                                           saveGameSnapshot,
                                           NULL,
                                           NULL,
                                       });
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "MOVE FREELY",
                                           NULL,
                                           handleMoveScrollOptionSelection,
                                           NULL,
                                           NULL,
                                       });
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "REMOVE ZONKS",
                                           NULL,
                                           handleRemoveZonksOptionSelection,
                                           NULL,
                                           NULL,
                                       });
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "REMOVE HARDWARE",
                                           NULL,
                                           handleRemoveHardwareOptionSelection,
                                           NULL,
                                           NULL,
                                       });
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "REMOVE SNIK SNAKS",
                                           NULL,
                                           handleRemoveSnikSnakOptionSelection,
                                           NULL,
                                           NULL,
                                       });
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "REMOVE CHIPS",
                                           NULL,
                                           handleRemoveChipsOptionSelection,
                                           NULL,
                                           NULL,
                                       });
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "REMOVE HARDWARE",
                                           NULL,
                                           handleRemoveHardwareOptionSelection,
                                           NULL,
                                           NULL,
                                       });
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "REMOVE BASE",
                                           NULL,
                                           handleRemoveBaseOptionSelection,
                                           NULL,
                                           NULL,
                                       });
    }

    runAdvancedOptionsSubMenu(menu);
}

void buildLevelSetOptionTitle(char output[kMaxAdvancedOptionsMenuEntryTitleLength])
{
    const char *kOriginalLevelSetName = "ORIGINAL";
    char currentSuffix[3] = "AT";
    strcpy(currentSuffix, &gLevelsDatFilename[8]);

    const char *levelSetName = kOriginalLevelSetName;
    if (strcmp(currentSuffix, "AT") != 0)
    {
        levelSetName = currentSuffix;
    }

    snprintf(output, kMaxAdvancedOptionsMenuEntryTitleLength, "LEVEL SET: %s", levelSetName);
}

void decreaseLevelSet()
{
    rotateLevelSet(1);
    gHasChangedLevelSetFromAdvancedMenu = 1;
}

void increaseLevelSet()
{
    rotateLevelSet(0);
    gHasChangedLevelSetFromAdvancedMenu = 1;
}

void runAdvancedOptionsRootMenu()
{
    AdvancedOptionsMenu menu;
    initializeAdvancedOptionsMenu(&menu);

    strncpy(menu.title, "OPENSUPAPLEX " VERSION_STRING, kMaxAdvancedOptionsMenuEntryTitleLength);

    if (gIsPlayingDemo)
    {
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "RESUME DEMO",
                                           NULL,
                                           handleResumeOptionSelection,
                                           NULL,
                                           NULL,
                                       });
    }
    else
    {
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "RESUME GAME",
                                           NULL,
                                           handleResumeOptionSelection,
                                           NULL,
                                           NULL,
                                       });
    }

    if (gIsInMainMenu)
    {
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "",
                                           buildLevelSetOptionTitle,
                                           NULL,
                                           decreaseLevelSet,
                                           increaseLevelSet,
                                       });
    }

    if (gIsPlayingDemo && gIsInMainMenu == 0)
    {
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "STOP DEMO AND PLAY",
                                           NULL,
                                           handleStopDemoAndPlayOptionSelection,
                                           NULL,
                                           NULL,
                                       });
    }

    if (gIsInMainMenu == 0)
    {
        if (gIsPlayingDemo)
        {
            addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                               "RESTART DEMO",
                                               NULL,
                                               handleRestartLevelOptionSelection,
                                               NULL,
                                               NULL,
                                           });
        }
        else
        {
            addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                               "RESTART LEVEL",
                                               NULL,
                                               handleRestartLevelOptionSelection,
                                               NULL,
                                               NULL,
                                           });
        }
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "",
                                           buildGameSpeedOptionTitle,
                                           NULL,
                                           decreaseGameSpeed,
                                           increaseGameSpeed,
                                       });
    }
    addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                       "",
                                       buildMusicVolumeOptionTitle,
                                       NULL,
                                       decreaseMusicVolume,
                                       increaseMusicVolume,
                                   });
    addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                       "",
                                       buildFXVolumeOptionTitle,
                                       NULL,
                                       decreaseFXVolume,
                                       increaseFXVolume,
                                   });
    addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                       "",
                                       buildScalingModeOptionTitle,
                                       NULL,
                                       decreaseAdvancedMenuScalingMode,
                                       increaseAdvancedMenuScalingMode,
                                   });
    if (gIsInMainMenu)
    {
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "",
                                           buildPlayDemoOptionTitle,
                                           handlePlayDemoOptionSelection,
                                           decreaseAdvancedMenuPlayDemoIndex,
                                           increaseAdvancedMenuPlayDemoIndex,
                                       });
    }
    if (gIsInMainMenu == 0 && gIsPlayingDemo == 0)
    {
        if (gIsRecordingDemo)
        {
            addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                               "STOP RECORDING DEMO",
                                               NULL,
                                               handleStopRecordingDemoOptionSelection,
                                               NULL,
                                               NULL,
                                           });
        }
        else
        {
            addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                               "",
                                               buildRecordDemoOptionTitle,
                                               handleRecordDemoOptionSelection,
                                               decreaseAdvancedMenuRecordDemoIndex,
                                               increaseAdvancedMenuRecordDemoIndex,
                                           });
        }
    }
    addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                       "DEBUG (DANGER)",
                                       NULL,
                                       handleDebugOptionSelection,
                                       NULL,
                                       NULL,
                                   });
    if (gIsInMainMenu == 0)
    {
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "EXIT LEVEL",
                                           NULL,
                                           handleExitLevelOptionSelection,
                                           NULL,
                                           NULL,
                                       });
    }
    else
    {
        addAdvancedOptionsEntry(&menu, (AdvancedOptionsMenuEntry){
                                           "EXIT GAME",
                                           NULL,
                                           handleExitGameOptionSelection,
                                           NULL,
                                           NULL,
                                       });
    }

    gShouldCloseAdvancedMenu = 0;

    saveScreenForAdvancedMenu();

    setPalette(gGameDimmedPalette);

    runAdvancedOptionsMenu(&menu);

    writeAdvancedConfig();

    restoreScreenFromAdvancedMenu();
    videoLoop();

    setPalette(gGamePalette);

    if (gHasChangedLevelSetFromAdvancedMenu)
    {
        updateMenuAfterLevelSetChanged();
        gHasChangedLevelSetFromAdvancedMenu = 0;
    }
}

int main(int argc, char *argv[])
{
    parseCommandLineOptions(argc, argv);

    if (gFastMode == FastModeTypeUltra)
    {
        setLogLevel(LogLevelDemo);
    }

    initializeLogging();
    initializeSystem();
    initializeVideo(gFastMode);
    initializeControllers();

    if (gFastMode != FastModeTypeUltra)
    {
        initializeAudio();
        readAdvancedConfig();
    }

    // Override the initial game speed with the one from the command line if needed
    if (gForcedInitialGameSpeed != kInvalidForcedInitialGameSpeed)
    {
        gGameSpeed = gForcedInitialGameSpeed;
    }

    handleSystemEvents();

    initializeGameStateData();

    // doesNotHaveCommandLine:         //; CODE XREF: start+13j start+23Fj ...
    generateRandomSeedFromClock();
    // checkVideo();

    // leaveVideoStatus:           //; CODE XREF: start+28Aj
    initializeFadePalette(); // 01ED:026F
    initializeMouse();

    if (gFastMode == FastModeTypeNone)
    {
        setPalette(gBlackPalette);
        videoLoop();
        readAndRenderTitleDat();
        ColorPalette titleDatPalette; // si = 0x5F15;
        convertPaletteDataToPalette(gTitlePaletteData, titleDatPalette);
        fadeToPalette(titleDatPalette);
    }

    // isFastMode:              //; CODE XREF: start+2ADj
    loadMurphySprites(); // 01ED:029D
    // Conditions to whether show
    if (gShouldStartFromSavedSnapshot || gIsForcedLevel || gIsSPDemoAvailableToRun || gFastMode)
    {
        readEverything();
    }
    else
    {
        // openingSequence:
        loadScreen2();                 // 01ED:02B9
        readEverything();              // 01ED:02BC
        drawSpeedFixTitleAndVersion(); // 01ED:02BF
        openCreditsBlock();            // credits inside the block // 01ED:02C2
        drawSpeedFixCredits();         // credits below the block (herman perk and elmer productions) // 01ED:02C5
    }

    // afterOpeningSequence:              //; CODE XREF: start+2DEj
    readConfig();
    if (byte_50946 == 0)
    {
        isJoystickEnabled = 0;
    }
    else
    {
        isJoystickEnabled = 1;
    }

    // loc_46F25:              //; CODE XREF: start+2FEj
    if (gFastMode == FastModeTypeNone)
    {
        // isNotFastMode:              //; CODE XREF: start+30Aj
        fadeToPalette(gBlackPalette);
        word_58467 = 1;
    }

    // Can't think of a better way to handle this right now, but at this point we had a "jmp loc_46FBE" which basically
    // skipped a few lines of the loop in the first iteration. Will revisit later when I have more knowledge of the code.
    //
    uint8_t shouldSkipFirstPart = 1;

    do
    {
        // loc_46F3E:              //; CODE XREF: start+428j start+444j
        if (shouldSkipFirstPart == 0)
        {
            readLevels(); // 01ED:02F7
            fadeToPalette(gBlackPalette);
            gIsGameBusy = 0;
            drawPlayerList();
            initializeGameInfo();
            drawFixedLevel();
            drawGamePanel(); // 01ED:0311
            uint16_t numberOfInfotrons = convertToEasyTiles();
            resetNumberOfInfotrons(numberOfInfotrons);
            findMurphy();
            gCurrentPanelHeight = kPanelBitmapHeight;
            drawCurrentLevelViewport(gCurrentPanelHeight); // Added by me
            if (gFastMode != FastModeTypeUltra)
            {
                fadeToPalette(gGamePalette); // At this point the screen fades in and shows the game
            }

            if (isMusicEnabled == 0)
            {
                stopMusic();
            }

            // loc_46F77:              //; CODE XREF: start+352j
            gIsGameBusy = 1;
            runLevel();
            gIsSPDemoAvailableToRun = 0;
            if (gShouldExitGame != 0)
            {
                break; // goto loc_47067;
            }

            // loc_46F8E:              //; CODE XREF: start+369j
            if (gFastMode != FastModeTypeNone)
            {
                break;
            }

            // isNotFastMode2:              //; CODE XREF: start+373j
            slideDownGameDash(); // 01ED:0351
            if (byte_59B71 != 0)
            {
                loadMurphySprites();
            }

            // loc_46FA5:              //; CODE XREF: start+380j
            gIsGameBusy = 0;
            if (gShouldExitGame != 0)
            {
                break; // goto loc_47067;
            }

            // loc_46FB4:              //; CODE XREF: start+38Fj
            if (isMusicEnabled == 0)
            {
                playMusicIfNeeded();
            }
        }

        shouldSkipFirstPart = 0;

        // loc_46FBE:              //; CODE XREF: start+30Cj start+31Bj ...
        prepareDemoRecordingFilename(); // 01ED:037A

        uint8_t levelNumberForcedToLoad = 0;

        if (gIsSPDemoAvailableToRun == 2)
        {
            gIsSPDemoAvailableToRun = 1;
            if (fileIsDemo == 1)
            {
                playDemo(0);
            }
            else
            {
                // loc_46FDF:              //; CODE XREF: start+3B5j
                gIsPlayingDemo = 0;
            }

            // loc_46FE4:              //; CODE XREF: start+3BDj
            gShouldUpdateTotalLevelTime = 0;
            gHasUserCheated = 1;
            memcpy(&gSPDemoFileName[3], "---", 3);
            // loc_4701A:              //; CODE XREF: start+3DDj start+433j
            startDirectlyFromLevel(1);
            continue;
        }
        else
        {
            // loc_46FFF:              //; CODE XREF: start+3A9j
            levelNumberForcedToLoad = gIsForcedLevel;
            gIsForcedLevel = 0;
            gIsPlayingDemo = 0;

            if (levelNumberForcedToLoad > 0)
            {
                convertLevelNumberTo3DigitStringWithPadding0(levelNumberForcedToLoad);
            }
        }

        if (levelNumberForcedToLoad > 0)
        {
            // loc_4701A:              //; CODE XREF: start+3DDj start+433j
            startDirectlyFromLevel(levelNumberForcedToLoad);
            continue;
        }

        // loc_4704B:              //; CODE XREF: start+3EEj start+3F2j
        if (gShouldStartFromSavedSnapshot != 0)
        {
            // loc_4701A:              //; CODE XREF: start+3DDj start+433j
            startDirectlyFromLevel(1);
            continue;
        }
        gHasUserCheated = 0;
        runMainMenu();
    } while (gShouldExitGame == 0);

    int runResult = 0;

    if (gFastMode != FastModeTypeNone)
    {
        char *message = "";
        if (gIsLevelStartedAsDemo == 0)
        {
            // loc_47094:              //; CODE XREF: start+45Fj
            if (byte_5A19B == 0)
            {
                // loc_470A1:              //; CODE XREF: start+479j
                message = "\"@\"-ERROR: Level(?) failed:     ";
                runResult = 1;
            }
            else
            {
                message = "\"@\"-ERROR: Level(?) successful: ";
            }
        }
        else if (byte_5A19B == 0)
        {
            // loc_4708E:              //; CODE XREF: start+466j
            message = "Demo failed:     ";
            runResult = 1;
        }
        else
        {
            message = "Demo successful: ";
        }

        // printMessageAfterward:          //; CODE XREF: start+46Cj start+472j ...
        spLogDemo("%s%s", message, demoFileName);
    }
    else
    {
        // loc_47067:              //; CODE XREF: start+36Bj start+391j ...
        fadeToPalette(gBlackPalette); // 0x60D5

        writeAdvancedConfig();
    }

    // Tidy up
    destroyAudio();
    destroyLogging();
    destroyVideo();
    destroySystem();

    return runResult;
}

void initializeGameStateData()
{
    // Initialize game state with the same values as in the original game
    static const uint16_t kLevelStatePrecedingPadding[kSizeOfLevelStatePrecedingPadding] = {
        0x8995, 0x8995, 0x8995, 0x8a3b, 0x8a3b, 0x8a3b, 0x8a3b, 0x8a3b,
        0x8a3b, 0x8a3b, 0x8a3b, 0x8ae8, 0x8ae8, 0x8ae8, 0x8ae8, 0x8ae8,
        0x8ae8, 0x8ae8, 0x8ae8, 0x8bb1, 0x8bb1, 0x8bb1, 0x8bb1, 0x8bb1,
        0x8bb1, 0x8bb1, 0x8bb1, 0x8c85, 0x8c85, 0x8c85, 0x8c85, 0x8c85,
        0x8c85, 0x8c85, 0x8c85, 0x8d5b, 0x8d5b, 0x8d5b, 0x8d5b, 0x8d5b,
        0x8d5b, 0x8d5b, 0x8d5b, 0x8e06, 0x8e06, 0x8e06, 0x8e06, 0x8e06,
        0x8e06, 0x8e06, 0x8e06, 0x8eac, 0x8eac, 0x8eac, 0x8eac, 0x8eac,
        0x8eac, 0x8eac, 0x8eac, 0x8f59, 0x8f59, 0x8f59, 0x8f59, 0x8f59,
        0x8f59, 0x8f59, 0x8f59, 0x0000, 0x1370, 0x0000, 0x0000, 0x17e8,
        0x0000, 0x0000, 0x0000, 0x3869, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x86d0, 0x0000, 0x34b2, 0x0000,
        0x0000, 0x0000, 0x0000, 0x8b8f, 0x341d, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x3923, 0x0909, 0x0c00, 0x0800, 0x5800, 0x0000,
        0x0000, 0x2500, 0x0677, 0x007f, 0x0000, 0x0001, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0xec00, 0x2606, 0x0005, 0x0000,
        0x0000, 0x0100, 0x0000, 0x0000, 0x3231, 0x3433, 0x3635, 0x3837,
        0x3039, 0x002d, 0x0008, 0x5751, 0x5245, 0x5954, 0x4955, 0x504f,
        0x0000, 0x000a, 0x5341, 0x4644, 0x4847, 0x4b4a, 0x004c, 0x0000,
        0x0000, 0x585a, 0x5643, 0x4e42, 0x004d, 0x0000, 0x0000, 0x2000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x002e, 0x001e, 0x0031, 0x0014, 0x0039,
        0x001f, 0x0014, 0x0018, 0xffff, 0x0001, 0x4c01, 0x5645, 0x4c45,
        0x2e53, 0x4144, 0x0054, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
        0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};

    for (int idx = 0; idx < kSizeOfLevelStatePrecedingPadding; ++idx)
    {
        uint16_t value = kLevelStatePrecedingPadding[idx];
        value = convert16LE(value);
        StatefulLevelTile *tile = &gCurrentLevelStateWithPadding[idx];
        tile->tile = (value & 0xFF);
        tile->state = (value >> 8);
    }

    gFrameCounter = 0xF000;
}

void startDirectlyFromLevel(uint8_t levelNumber)
{
    gIsGameBusy = 1;
    gShouldAutoselectNextLevelToPlay = 1;
    prepareLevelDataForCurrentPlayer();
    drawPlayerList();
    word_58467 = 0;
    playMusicIfNeeded();
    gCurrentSelectedLevelIndex = levelNumber;
    restoreLastMouseAreaBitmap();
    drawLevelList();
    gShouldLeaveMainMenu = 0;
    byte_5A19B = 0;
}

void slideDownGameDash() // proc near     ; CODE XREF: start:isNotFastMode2p
{
    // 01ED:04ED
    if (gShouldShowGamePanel == 0)
    {
        return;
    }

    for (int panelHeight = kPanelBitmapHeight; panelHeight > 0; --panelHeight)
    {
        // Move the bottom panel line by line to the bottom of the screen
        for (int y = kScreenHeight - 2; y >= kScreenHeight - panelHeight; --y)
        {
            uint16_t srcAddress = y * kScreenWidth;
            uint16_t dstAddress = (y + 1) * kScreenWidth;
            memcpy(&gScreenPixels[dstAddress], &gScreenPixels[srcAddress], kScreenWidth);
        }

        drawCurrentLevelViewport(panelHeight);
        videoLoop();
    }
}

/// This alternative int9 handler seems to control the keys +, -, *, / in the numpad
/// to alter the game speed, and also the key X for something else.
/// @param shouldYieldCpu If 1, will sleep the thread for a bit to prevent 100% CPU usage.
void int9handler(uint8_t shouldYieldCpu) // proc far        ; DATA XREF: setint9+1Fo
{
    updateKeyboardState();

    // 01ED:0659

    if (gIsLeftAltPressed && gIsEnterPressed)
    {
        toggleFullscreen();
    }

    // storeKey:               ; CODE XREF: int9handler+2Bj
    if (gIsInMainMenu == 0)
    {
        if (gIsNumpadMultiplyPressed) // Key * in the numpad, restore speed
        {
            gGameSpeed = kDefaultGameSpeed;
            updateDemoRecordingLowestSpeed();
        }
        // checkSlash:             ; CODE XREF: int9handler+3Ej
        //                 ; int9handler+45j
        else if (gIsNumpadDividePressed) // Keypad / -> fastest playback seed
        {
            gGameSpeed = kNumberOfGameSpeeds - 1;
            updateDemoRecordingLowestSpeed();
        }
        // checkPlus:              ; CODE XREF: int9handler+54j
        else if (gIsGameSpeedChangeButtonPressed == 0)
        {
            if (isIncreaseGameSpeedButtonPressed())
            {
                increaseGameSpeed();
            }
            // checkMinus:             ; CODE XREF: int9handler+65j
            //                 ; int9handler+71j
            else if (isDecreaseGameSpeedButtonPressed())
            {
                decreaseGameSpeed();
            }
        }

        gIsGameSpeedChangeButtonPressed = (isIncreaseGameSpeedButtonPressed() || isDecreaseGameSpeedButtonPressed());
    }

    // checkX:                 ; CODE XREF: int9handler+39j
    //                 ; int9handler+60j ...
    if (gIsXKeyPressed && gIsLeftAltPressed != 0)
    {
        gShouldExitLevel = 1;
        gShouldExitGame = 1;
    }

    if (shouldYieldCpu)
    {
        waitTime(10); // Avoid burning the CPU
    }
}

void readConfig() //  proc near       ; CODE XREF: start:loc_46F0Fp
{
    if (gFastMode == FastModeTypeUltra)
    {
        return;
    }

    FILE *file = openWritableFile("SUPAPLEX.CFG", "rb");
    if (file == NULL)
    {
#ifdef __PS2__ // PS2 for now is not returning errno code properly
        if (1) // ax == 2? ax has error code, 2 is file not found (http://stanislavs.org/helppc/dos_error_codes.html)
#else
        if (errno == ENOENT || errno == ENOSYS || errno == EIO) // ax == 2? ax has error code, 2 is file not found (http://stanislavs.org/helppc/dos_error_codes.html)
#endif
        {
            // loc_47551:              //; CODE XREF: readConfig+Fj
            //  ; readConfig+17j
            activateCombinedSound();
            isMusicEnabled = 1;
            isFXEnabled = 1;
            isJoystickEnabled = 0;
            return;
        }
        else
        {
            exitWithError("Error opening SUPAPLEX.CFG\n");
        }
    }

    // loc_474BE:             // ; CODE XREF: readConfig+8j

    uint8_t configData[kConfigDataLength];

    size_t bytes = fileReadBytes(configData, sizeof(configData), file);

    if (fclose(file) != 0)
    {
        exitWithError("Error closing SUPAPLEX.CFG\n");
    }

    // loc_474DF:             // ; CODE XREF: readConfig+39j
    if (bytes < sizeof(configData))
    {
        exitWithError("Error reading SUPAPLEX.CFG\n");
    }

    // loc_474E5:             // ; CODE XREF: readConfig+3Fj
    uint8_t soundSetting = configData[0];

    if (soundSetting == 's')
    {
        activateInternalSamplesSound();
    }
    else if (soundSetting == 'a')
    {
        activateAdlibSound();
    }
    else if (soundSetting == 'b')
    {
        activateSoundBlasterSound();
    }
    else if (soundSetting == 'r')
    {
        activateRolandSound();
    }
    else if (soundSetting == 'c')
    {
        activateCombinedSound();
    }
    else
    {
        activateInternalStandardSound();
    }

    // loc_4751D:             // ; CODE XREF: readConfig+4Fj
    //  ; readConfig+59j ...

    isJoystickEnabled = 0;
    if (configData[1] == 'j')
    {
        isJoystickEnabled = 1;
        // calibrateJoystick(); not needed anymore
    }

    // loc_47530:              //; CODE XREF: readConfig+85j
    isMusicEnabled = (configData[2] == 'm');

    // loc_47540:              //; CODE XREF: readConfig+98j
    isFXEnabled = (configData[3] == 'x');
}

void saveConfiguration() // sub_4755A      proc near               ; CODE XREF: code:loc_4CAECp
{
    FILE *file = openWritableFile("SUPAPLEX.CFG", "wb");
    if (file == NULL)
    {
        spLogInfo("Error opening SUPAPLEX.CFG\n");
        return;
    }

    // loc_4756A:                              ; CODE XREF: saveConfiguration+Bj
    uint8_t configData[kConfigDataLength];

    if (sndType == SoundTypeInternalSamples)
    {
        configData[0] = 's';
    }
    else if (sndType == SoundTypeInternalStandard)
    {
        configData[0] = 'i';
    }
    else if (sndType == SoundTypeAdlib)
    {
        configData[0] = 'a';
    }
    else if (sndType == SoundTypeRoland)
    {
        configData[0] = 'r';
    }
    else if (musType == SoundTypeRoland)
    {
        configData[0] = 'c';
    }
    else
    {
        configData[0] = 'b';
    }

    // loc_475AF:                              ; CODE XREF: saveConfiguration+20j
    //                             ; saveConfiguration+2Cj ...
    if (isJoystickEnabled == 0)
    {
        configData[1] = 'k';
    }
    else
    {
        configData[1] = 'j';
    }

    // loc_475BF:                              ; CODE XREF: saveConfiguration+60j
    if (isMusicEnabled != 0)
    {
        configData[2] = 'm';
    }
    else
    {
        configData[2] = 'n';
    }

    // loc_475CF:                              ; CODE XREF: saveConfiguration+70j
    if (isFXEnabled != 0)
    {
        configData[3] = 'x';
    }
    else
    {
        configData[3] = 'y';
    }

    // loc_475DF:                              ; CODE XREF: saveConfiguration+80j
    size_t bytes = fileWriteBytes(configData, sizeof(configData), file);
    if (bytes < sizeof(configData))
    {
        exitWithError("Error writing SUPAPLEX.CFG\n");
    }

    if (fclose(file) != 0)
    {
        exitWithError("Error closing SUPAPLEX.CFG\n");
    }
}

/// @return Number of demo files read
uint8_t readDemoFiles() //    proc near       ; CODE XREF: readEverything+12p
                        //  ; handleDemoOptionClickp ...
{
    // 01ED:09A6

    gDemoCurrentInputIndex = 0;
    word_5A33C = 0;

    memset(&gDemos.demoFirstIndices, 0xFF, sizeof(gDemos.demoFirstIndices)); // fills 11 words (22 bytes) with 0xFFFF

    for (int i = 0; i < kNumberOfDemos; ++i)
    {
        // loc_47629:              //; CODE XREF: readDemoFiles+175j
        gSelectedOriginalDemoLevelNumber = 0;
        char *filename = gDemo0BinFilename;

        if (gIsSPDemoAvailableToRun == 1)
        {
            filename = demoFileName;
        }
        else
        {
            // loc_4763C:             // ; CODE XREF: readDemoFiles+2Cj
            gDemo0BinFilename[4] = '0' + i; // Replaces the number in "DEMO0.BIN" with the right value
        }

        // loc_47647:             // ; CODE XREF: readDemoFiles+31j
        FILE *file = openWritableFileWithReadonlyFallback(filename, "rb");
        if (file == NULL)
        {
            return i;
        }

        // loc_47651:              //; CODE XREF: readDemoFiles+43j
        if (gIsSPDemoAvailableToRun == 1)
        {
            if (gSelectedOriginalDemoFromCommandLineLevelNumber == 0)
            {
                fseek(file, kLevelDataLength, SEEK_SET);
            }
        }
        else
        {
            // loc_47674:             // ; CODE XREF: readDemoFiles+52j
            int result = fseek(file, 0, SEEK_END);
            long fileSize = ftell(file);

            // this is probably to support old level formats
            if (result == 0 && fileSize < kLevelDataLength)
            {
                gSelectedOriginalDemoLevelNumber = getLevelNumberFromOriginalDemoFile(file, fileSize);
            }

            // loc_47690:             // ; CODE XREF: readDemoFiles+76j readDemoFiles+7Aj ...
            fseek(file, 0, SEEK_SET);

            if (gSelectedOriginalDemoLevelNumber == 0)
            {
                Level *level = &gDemos.level[i];
                size_t bytes = fileReadBytes(level, kLevelDataLength, file);

                if (bytes < kLevelDataLength)
                {
                    return i;
                }

                // loc_476D3:           //   ; CODE XREF: readDemoFiles+C5j
                gDemoRandomSeeds[i] = level->randomSeed;
            }
        }

        // loc_476DB:             // ; CODE XREF: readDemoFiles+59j readDemoFiles+69j ...
        uint16_t maxNumberOfBytesToRead = kMaxDemoInputSteps + 1; // 48649
        maxNumberOfBytesToRead -= gDemoCurrentInputIndex;

        if (maxNumberOfBytesToRead > kMaxDemoInputSteps + 1) // weird way of checking if gDemoCurrentInputIndex < 0 ????
        {
            maxNumberOfBytesToRead = 0;
        }

        uint16_t numberOfDemoBytesRead = 0;

        // loc_476EA:             // ; CODE XREF: readDemoFiles+DDj
        if (maxNumberOfBytesToRead == 0)
        {
            numberOfDemoBytesRead = 0;
        }
        else
        {
            // loc_476F3:              // ; CODE XREF: readDemoFiles+E4j
            numberOfDemoBytesRead = fileReadBytes(&gDemos.demoData[gDemoCurrentInputIndex], maxNumberOfBytesToRead, file);

            if (numberOfDemoBytesRead == 0)
            {
                if (fclose(file) != 0)
                {
                    exitWithError("Error closing DEMO file");
                }
                return i;
            }

            // loc_47719:             // ; CODE XREF: readDemoFiles+FCj
        }

        // loc_4771A:             // ; CODE XREF: readDemoFiles+E8j
        if (fclose(file) != 0)
        {
            exitWithError("Error closing DEMO file");
        }

        // loc_47729:              ; CODE XREF: readDemoFiles+11Bj
        gDemos.demoData[gDemoCurrentInputIndex] = gDemos.demoData[gDemoCurrentInputIndex] & 0x7F; // this removes the MSB from the levelNumber that was added in the speed fix mods
        int isZero = (gSelectedOriginalDemoLevelNumber == 0);
        gSelectedOriginalDemoLevelNumber = 0;
        if (isZero)
        {
            gDemos.demoData[gDemoCurrentInputIndex] = gDemos.demoData[gDemoCurrentInputIndex] | 0x80; // This sets the MSB?? maybe the "interpreter" later needs it
        }

        // loc_47743:             // ; CODE XREF: readDemoFiles+134j
        uint16_t demoLastByteIndex = gDemoCurrentInputIndex + numberOfDemoBytesRead - 1;
        // cx = bx; // bx here has the value of gDemoCurrentInputIndex
        // bx += numberOfDemoBytesRead; // ax here has the number of bytes read regarding the level itself (levelNumber + inputSteps)
        // push(ds);
        // push(es);
        // pop(ds);
        // assume ds:nothing
        // bx--;
        if (demoLastByteIndex == 0xFFFF   // this would mean bx was 0. is this possible?
            || numberOfDemoBytesRead <= 1 // this means the demo is empty (only has levelNumber or nothing)
            || gDemos.demoData[demoLastByteIndex] != 0xFF)
        {
            // loc_4775A:             // ; CODE XREF: readDemoFiles+145j
            //  ; readDemoFiles+14Aj
            if (demoLastByteIndex < sizeof(BaseDemo))
            {
                numberOfDemoBytesRead++;
                gDemos.demoData[demoLastByteIndex + 1] = 0xFF;
            }
        }

        // loc_47765:             // ; CODE XREF: readDemoFiles+14Fj
        //  ; readDemoFiles+155j
        gDemos.demoFirstIndices[i] = gDemoCurrentInputIndex;
        gDemoCurrentInputIndex += numberOfDemoBytesRead;
    }

    return kNumberOfDemos;
}

void openCreditsBlock() // proc near      ; CODE XREF: start+2E9p
{
    static const int kEdgeWidth = 13;
    static const int kEdgeHeight = 148;
    static const int kEdgeStep = 4;
    static const int kEdgeTopY = 26;
    static const int kNumberOfFrames = 60;

    const uint32_t kAnimationDuration = kNumberOfFrames * 1000 / 70; // ~429 ms

    uint32_t animationTime = 0;

    static const int kInitialLeftEdgeX = 147;
    const int kInitialRightEdgeX = kInitialLeftEdgeX + kEdgeWidth + 1;

    const int kEdgeAnimationDistance = (kEdgeStep * kNumberOfFrames) / 2 + 1;

    int leftEdgeX = kInitialLeftEdgeX;
    int rightEdgeX = kInitialRightEdgeX;

    startTrackingRenderDeltaTime();

    while (animationTime < kAnimationDuration)
    {
        // loc_47800:             // ; CODE XREF: openCreditsBlock+AFj
        animationTime += updateRenderDeltaTime();
        animationTime = MIN(animationTime, kAnimationDuration);

        float animationFactor = (float)animationTime / kAnimationDuration;

        int previousLeftEdgeX = leftEdgeX;
        int previousRightEdgeX = rightEdgeX;

        int distance = ceilf(kEdgeAnimationDistance * animationFactor);

        leftEdgeX = kInitialLeftEdgeX - distance;
        rightEdgeX = kInitialRightEdgeX + distance;

        int leftEdgeStep = previousLeftEdgeX - leftEdgeX;
        int rightEdgeStep = rightEdgeX - previousRightEdgeX;

        // This loop moves both edges of the panel, and fills the inside of the panel with the contents of TITLE2.DAT
        for (int y = kEdgeTopY; y < kEdgeTopY + kEdgeHeight; ++y)
        {
            // Left edge
            for (int x = leftEdgeX; x < previousLeftEdgeX + kEdgeWidth - leftEdgeStep; ++x)
            {
                long addr = y * kScreenWidth + x;
                gScreenPixels[addr] = gScreenPixels[addr + leftEdgeStep]; // Move panel edge
            }

            // Content of visible panel unveiled by left edge
            for (int x = leftEdgeX + kEdgeWidth; x < previousLeftEdgeX + kEdgeWidth + 1; ++x)
            {
                long addr = y * kScreenWidth + x;
                gScreenPixels[addr] = gTitle2DecodedBitmapData[addr];
            }

            // Right edge
            for (int x = rightEdgeX + kEdgeWidth; x > previousRightEdgeX; --x)
            {
                long addr = y * kScreenWidth + x;
                gScreenPixels[addr] = gScreenPixels[addr - rightEdgeStep]; // Move panel edge
            }

            // Content of visible panel unveiled by right edge
            for (int x = previousRightEdgeX; x < rightEdgeX; ++x)
            {
                long addr = y * kScreenWidth + x;
                gScreenPixels[addr] = gTitle2DecodedBitmapData[addr];
            }
        }

        videoLoop();
    }

    // loc_47884:             // ; CODE XREF: openCreditsBlock+C7j
    //  Display now the contents of TITLE2.DAT starting at the y=panel_edge_top_y (to prevent removing the top title)
    //  This basically makes the edges of the panel docked at the sides of the screen look better (as intended in TITLE2.DAT
    //  compared to how they look when the "crafted" animation concludes).
    //
    size_t copyOffset = kEdgeTopY * kScreenWidth;
    memcpy(gScreenPixels + copyOffset, gTitle2DecodedBitmapData + copyOffset, sizeof(gTitle2DecodedBitmapData) - copyOffset);

    ColorPalette title2Palette;
    convertPaletteDataToPalette(gTitle2PaletteData, title2Palette);
    fadeToPalette(title2Palette); // fades current frame buffer into the title 2.dat (screen with the credits)
}

void loadScreen2() // proc near       ; CODE XREF: start:loc_46F00p
{
    readAndRenderTitle1Dat();

    // loc_4792E:              //; CODE XREF: loadScreen2+76j
    ColorPalette title1DatPalette;
    convertPaletteDataToPalette(gTitle1PaletteData, title1DatPalette);
    setPalette(title1DatPalette);
    videoLoop();

    readTitle2Dat();
}

void readLevelsLst() //   proc near       ; CODE XREF: readLevelsLst+CCj
                     // ; readEverything+Fp ...
{
    // 01ED:1038

    char paddingEntryText[kListLevelNameLength] = "                           ";
    for (int i = 0; i < kNumberOfLevelsWithPadding; ++i)
    {
        memcpy(&gPaddedLevelListData[i * kListLevelNameLength], paddingEntryText, sizeof(paddingEntryText));
    }
    memcpy(&gPaddedLevelListData[kLastLevelIndex * kListLevelNameLength],
           "- REPLAY SKIPPED LEVELS!! -",
           kListLevelNameLength);
    memcpy(&gPaddedLevelListData[(kLastLevelIndex + 1) * kListLevelNameLength],
           "---- UNBELIEVEABLE!!!! ----",
           kListLevelNameLength);

    FILE *file = openWritableFile(gLevelLstFilename, "rb");
    if (file == NULL)
    {
        // errorOpeningLevelLst:             // ; CODE XREF: readLevelsLst+8j
        FILE *file = openReadonlyFile(gLevelsDatFilename, "rb");
        if (file == NULL)
        {
            // errorOpeningLevelsDat:             // ; CODE XREF: readLevelsLst+17j
            exitWithError("Error opening LEVELS.DAT\n");
        }
        // successOpeningLevelsDat:             // ; CODE XREF: readLevelsLst+15j

        for (int i = 0; i < kNumberOfLevels; ++i)
        {
            // loc_47CC4:             // ; CODE XREF: readLevelsLst:loc_47CE4j
            char number[5];
            sprintf(number, "%03d ", i + 1);

            memcpy(gLevelListData + i * kListLevelNameLength, number, sizeof(number) - 1);
            gLevelListData[i * kListLevelNameLength + kListLevelNameLength - 1] = '\n';
            // loc_47CE4:            //  ; CODE XREF: readLevelsLst+3Aj
        }

        for (int i = 0; i < kNumberOfLevels; ++i)
        {
            // loc_47CF1:             //  ; CODE XREF: readLevelsLst+83j

            int seekOffset = 0x5A6 + i * kLevelDataLength;

            fseek(file, seekOffset, SEEK_SET); // position 1446
            size_t bytes = fileReadBytes(gLevelListData + i * kListLevelNameLength + 4, kLevelNameLength - 1, file);

            if (bytes < kLevelNameLength - 1)
            {
                fclose(file);
                exitWithError("Error reading LEVELS.DAT\n");
            }
        }
        if (fclose(file) != 0)
        {
            exitWithError("Error closing LEVELS.DAT\n");
        }

        if (gShouldRecreateLevelLstIfNeeded == 0)
        {
            return;
        }

        //    loc_47D35:             // ; CODE XREF: readLevelsLst+95j
        file = openWritableFile(gLevelLstFilename, "wb");
        if (file == NULL)
        {
            exitWithError("Error opening %s\n", gLevelLstFilename);
        }

        //    writeLevelLstData:             // ; CODE XREF: readLevelsLst+A5j
        size_t bytes = fileWriteBytes(gLevelListData, kLevelListDataLength, file);
        if (bytes < kLevelListDataLength)
        {
            exitWithError("Error writing %s\n", gLevelLstFilename);
        }

        //    loc_47D5B:             // ; CODE XREF: readLevelsLst+BBj
        if (fclose(file) != 0)
        {
            exitWithError("Error closing %s\n", gLevelLstFilename);
        }
        return;
    }

    // successOpeningLevelLst:             // ; CODE XREF: readLevelsLst+Aj
    size_t bytes = fileReadBytes(gLevelListData, kLevelListDataLength, file);
    if (bytes < kLevelListDataLength)
    {
        fclose(file);
        exitWithError("Error reading LEVEL.LST\n");
    }

    // loc_47D8D:             // ; CODE XREF: readLevelsLst+8Aj
    //  ; readLevelsLst:loc_47D5Bj ...
    if (fclose(file) != 0)
    {
        exitWithError("Error closing LEVEL.LST\n");
    }
}

void readPlayersLst() //  proc near       ; CODE XREF: readEverything+1Bp
                      // ; handleFloppyDiskButtonClick+149p
{
    if (gIsForcedCheatMode != 0)
    {
        return;
    }

    for (int i = 0; i < kNumberOfPlayers; ++i)
    {
        strcpy(gPlayerListData[i].name, "--------");
    }

    FILE *file = openWritableFile(gPlayerLstFilename, "rb");
    if (file == NULL)
    {
        return;
    }

    size_t bytes = fileReadBytes(gPlayerListData, sizeof(gPlayerListData), file);
    if (bytes == 0)
    {
        return;
    }

    fclose(file);
}

void readHallfameLst() // proc near       ; CODE XREF: readEverything+18p
                       // ; handleFloppyDiskButtonClick+146p
{
    if (gIsForcedCheatMode != 0)
    {
        return;
    }

    FILE *file = openWritableFile(gHallfameLstFilename, "rb");
    if (file == NULL)
    {
        return;
    }

    size_t bytes = fileReadBytes(gHallOfFameData, sizeof(gHallOfFameData), file);
    if (bytes == 0)
    {
        return;
    }

    fclose(file);
}

void readEverything() //  proc near       ; CODE XREF: start+2DBp start+2E3p ...
{
    // 01ED:1213
    readPalettes();
    readBitmapFonts();
    readPanelDat();
    readMenuDat();
    readControlsDat();
    readLevelsLst();
    readDemoFiles();
    readBackDat();
    readHallfameLst();
    readPlayersLst();
    readGfxDat();
}

void waitForKeyMouseOrJoystick() // sub_47E98  proc near       ; CODE XREF: recoverFilesFromFloppyDisk+4Ap
//                    ; handleStatisticsOptionClick+216p ...
{
    byte_59B86 = 0;

    do
    {
        // keyIsPressed:               ; CODE XREF: waitForKeyMouseOrJoystick+16j
        if (gIsEscapeKeyPressed != 0)
        {
            byte_59B86 = 0xFF;
        }

        int9handler(1);
        // loc_47EA9:              ; CODE XREF: waitForKeyMouseOrJoystick+Aj
    } while (isAnyKeyPressed());

    uint16_t mouseButtonsStatus = 0;

    do
    {
        // mouseIsClicked:              ; CODE XREF: waitForKeyMouseOrJoystick+1Ej
        getMouseStatus(NULL, NULL, &mouseButtonsStatus);
    } while (mouseButtonsStatus != 0);

    for (int i = 0; i < 4200; ++i)
    {
        // loc_47EC6:              ; CODE XREF: waitForKeyMouseOrJoystick+57j
        videoLoop();

        getMouseStatus(NULL, NULL, &mouseButtonsStatus);
        int9handler(0);
        updateUserInput();

        if (mouseButtonsStatus != 0)
        {
            break;
        }

        if (isAnyKeyPressed())
        {
            break;
        }

        if (gCurrentUserInput > kUserInputSpaceAndDirectionOffset)
        {
            break;
        }
    }

    if (mouseButtonsStatus != 0)
    {
        // loc_47F02:              ; CODE XREF: waitForKeyMouseOrJoystick+44j
        //                 ; waitForKeyMouseOrJoystick+70j
        do
        {
            getMouseStatus(NULL, NULL, &mouseButtonsStatus);
        } while (mouseButtonsStatus != 0);
    }
    else if (isAnyKeyPressed())
    {
        do
        {
            // loc_47F0C:              ; CODE XREF: waitForKeyMouseOrJoystick+4Bj
            //                 ; waitForKeyMouseOrJoystick+85j
            if (gIsEscapeKeyPressed != 0)
            {
                byte_59B86 = 0xFF;
            }

            int9handler(1);
        } while (isAnyKeyPressed());
    }
    else if (gCurrentUserInput > kUserInputSpaceAndDirectionOffset)
    {
        do
        {
            // loc_47F21:              ; CODE XREF: waitForKeyMouseOrJoystick+55j
            //                 ; waitForKeyMouseOrJoystick+9Dj
            if (gIsEscapeKeyPressed != 0)
            {
                byte_59B86 = 0xFF;
            }

            int9handler(1);
            updateUserInput();
        } while (gCurrentUserInput > kUserInputSpaceAndDirectionOffset);
    }
}

void updateZonkTiles(int16_t position) //   proc near       ; DATA XREF: data:160Co
{
    // 01ED:132D

    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *belowLeftTile = &gCurrentLevelState[position + kLevelWidth - 1];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *belowRightTile = &gCurrentLevelState[position + kLevelWidth + 1];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];
    StatefulLevelTile *aboveRightTile = &gCurrentLevelState[position - kLevelWidth + 1];

    if (currentTile->tile != LevelTileTypeZonk) // cmp byte ptr leveldata[si], 1
    {
        return;
    }

    // loc_47F98:              ; CODE XREF: movefun+5j
    uint8_t shouldSkipFirstPartOfLoop = 0;

    if (currentTile->tile != LevelTileTypeZonk || currentTile->state != 0)
    {
        shouldSkipFirstPartOfLoop = 1; // used to emulate "jmp loc_48035"
    }
    else
    {
        // loc_47FA4:              ; CODE XREF: movefun+Fj
        if (gAreZonksFrozen == 2)
        {
            return;
        }

        // loc_47FAC:              ; CODE XREF: movefun+19j
        //  Check if the zonk can just fall vertically
        if (belowTile->tile == LevelTileTypeSpace && belowTile->state == 0)
        {
            // loc_47FF4:              ; CODE XREF: movefun+23j
            currentTile->state = 0x40;
            shouldSkipFirstPartOfLoop = 1; // used to emulate "jmp loc_48035"
        }
        else
        {
            // Check if below the zonk is another object that could be used to slide down left or right
            if (belowTile->state != 0 || (belowTile->tile != LevelTileTypeZonk && belowTile->tile != LevelTileTypeInfotron && belowTile->tile != LevelTileTypeChip))
            {
                return;
            }

            // loc_47FC5:              ; CODE XREF: movefun+28j
            //                 ; movefun+2Dj ...
            //  Check if it can fall to the left side...
            if ((belowLeftTile->tile == LevelTileTypeSpace && belowLeftTile->state == 0) || (belowLeftTile->tile == 0x88 && belowLeftTile->state == 0x88) || (belowLeftTile->tile == 0xAA && belowLeftTile->state == 0xAA))
            {
                // loc_47FFB:              ; CODE XREF: movefun+3Aj
                //                 ; movefun+42j ...
                //  ...but only if the left tile is empty
                if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
                {
                    // loc_48004:              ; CODE XREF: movefun+70j
                    currentTile->state = 0x50;
                    leftTile->state = 0x88;
                    leftTile->tile = 0x88;
                    shouldSkipFirstPartOfLoop = 1; // used to emulate "jmp loc_48035"
                }
            }
        }
    }

    uint8_t shouldSkipTo_loc_481C6 = 0;

    do
    {
        // loc_47FDC:              ; CODE XREF: movefun+72j
        //                 ; movefun+1F1j
        if (shouldSkipFirstPartOfLoop == 0) // used to emulate "jmp loc_48035"
        {
            // Checks if it can fall to the right side
            if ((belowRightTile->state != 0 || belowRightTile->tile != LevelTileTypeSpace) && (belowRightTile->state != 0x88 || belowRightTile->tile != 0x88) && (belowRightTile->state != 0xAA || belowRightTile->tile != 0xAA))
            {
                return;
            }
            else
            {
                // loc_48011:              ; CODE XREF: movefun+51j
                //                 ; movefun+59j ...
                //  Only if the right tile is empty or... other circumstances?
                if ((rightTile->state != 0 || rightTile->tile != LevelTileTypeSpace) && ((rightTile->state != 0x99 || rightTile->tile != 0x99) || (aboveRightTile->state != 0 || aboveRightTile->tile != LevelTileTypeZonk)))
                {
                    return;
                }

                // loc_48028:              ; CODE XREF: movefun+86j
                //                 ; movefun+95j
                currentTile->state = 0x60;
                rightTile->state = 0x88;
                rightTile->tile = 0x88;
            }
        }

        shouldSkipFirstPartOfLoop = 0; // don't skip the first part in the next iteration

        // loc_48035:              ; CODE XREF: movefun+11j
        //                 ; movefun+69j ...
        uint8_t state = currentTile->state;
        uint8_t stateType = state & 0xF0;

        if (stateType != 0x10) // 16
        {
            // loc_48045:              ; CODE XREF: movefun+B1j
            if (stateType == 0x20) // 32
            {
                // loc_48212:              ; CODE XREF: movefun+B9j
                //  01ED:15AF
                uint8_t stateFrame = state & 0x7; // module 8?

                uint8_t tileX = (position % kLevelWidth);
                uint8_t tileY = (position / kLevelWidth);

                uint16_t dstX = tileX * kTileSize;
                uint16_t dstY = tileY * kTileSize;

                // mov si, 1294h
                // mov si, [bx+si]
                Point frameCoordinates = kZonkSlideLeftAnimationFrameCoordinates[stateFrame];

                drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                             frameCoordinates.y,
                                             kTileSize * 2,
                                             kTileSize,
                                             dstX, dstY);

                // 01ED:15D6
                state = currentTile->state; // mov bl, [si+1835h]
                state++;

                if (state == 0x24) // 36
                {
                    rightTile->state = 0xAA;
                    rightTile->tile = 0xAA;
                }
                // loc_4824A:              ; CODE XREF: movefun+2B2j
                if (state == 0x26) // 38
                {
                    currentTile->state = state;
                    handleZonkStateAfterFallingOneTile(position + 1);
                    return;
                }
                // loc_4825D:              ; CODE XREF: movefun+2BDj
                else if (state < 0x28) // 40
                {
                    currentTile->state = state;
                    return;
                }
                // loc_48267:              ; CODE XREF: movefun+2D0j
                else
                {
                    currentTile->state = 0xFF;
                    currentTile->tile = 0xFF;
                    belowTile->state = 0x10;
                    belowTile->tile = LevelTileTypeZonk;
                    return;
                }
            }
            // loc_4804C:              ; CODE XREF: movefun+B7j
            else if (stateType == 0x30) // 48
            {
                // loc_48277:              ; CODE XREF: movefun+C0j
                uint8_t stateFrame = state & 0x7; // module 8?

                uint8_t tileX = ((position - 1) % kLevelWidth);
                uint8_t tileY = ((position - 1) / kLevelWidth);

                uint16_t dstX = tileX * kTileSize;
                uint16_t dstY = tileY * kTileSize;

                // mov si, 12A4h
                // mov si, [bx+si]
                Point frameCoordinates = kZonkSlideRightAnimationFrameCoordinates[stateFrame];

                drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                             frameCoordinates.y,
                                             kTileSize * 2,
                                             kTileSize,
                                             dstX, dstY);

                state = currentTile->state;
                state++;
                if (state == 0x34) // 52
                {
                    leftTile->state = 0xAA;
                    leftTile->tile = 0xAA;
                }
                // loc_482AF:              ; CODE XREF: movefun+317j
                if (state == 0x36) // 54
                {
                    currentTile->state = state;
                    handleZonkStateAfterFallingOneTile(position - 1); // left tile
                    return;
                }
                // loc_482C2:              ; CODE XREF: movefun+322j
                else if (state < 0x38) // 54
                {
                    currentTile->state = state;
                    return;
                }
                // loc_482CC:              ; CODE XREF: movefun+335j
                else
                {
                    currentTile->state = 0xFF;
                    currentTile->tile = 0xFF;
                    belowTile->state = 0x10;
                    belowTile->tile = LevelTileTypeZonk;
                    return;
                }
            }
            // loc_48053:              ; CODE XREF: movefun+BEj
            else if (gAreZonksFrozen == 2)
            {
                return;
            }
            // loc_4805B:              ; CODE XREF: movefun+C8j
            else if (stateType == 0x40) // 64
            {
                // loc_482DC:              ; CODE XREF: movefun+CFj
                state++;
                if (state < 0x42) // 66
                {
                    currentTile->state = state;
                    return;
                }
                // loc_482E8:              ; CODE XREF: movefun+351j
                else if (belowTile->tile != LevelTileTypeSpace || belowTile->state != 0) // cmp word ptr [si+18ACh], 0
                {
                    state--;
                    currentTile->state = state;
                    return;
                }
                // loc_482F6:              ; CODE XREF: movefun+35Dj
                else
                {
                    currentTile->state = 0xFF;
                    currentTile->tile = 0xFF;
                    belowTile->state = 0x10;
                    belowTile->tile = LevelTileTypeZonk;

                    return;
                }
            }
            // loc_48062:              ; CODE XREF: movefun+CDj
            else if (stateType == 0x50) // Zonk sliding left
            {
                // loc_4830A:              ; CODE XREF: movefun+D6j
                uint8_t stateFrame = state & 0x7; // module 8?

                uint8_t tileX = ((position - 1) % kLevelWidth);
                uint8_t tileY = ((position - 1) / kLevelWidth);

                uint16_t dstX = tileX * kTileSize;
                uint16_t dstY = tileY * kTileSize;

                // mov si, 1294h
                // mov si, [bx+si]
                Point frameCoordinates = kZonkSlideLeftAnimationFrameCoordinates[stateFrame];

                drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                             frameCoordinates.y,
                                             kTileSize * 2,
                                             kTileSize,
                                             dstX, dstY);

                state = currentTile->state;
                state++;

                if (state < 0x52) // 82
                {
                    currentTile->state = state;
                    return;
                }
                // loc_48341:              ; CODE XREF: movefun+3AAj
                else if (belowLeftTile->state != 0 || belowLeftTile->tile != LevelTileTypeSpace) // cmp word ptr [si+18AAh], 0
                {
                    // loc_48371:              ; CODE XREF: movefun+3B6j
                    //                 ; movefun+3C5j
                    state--;
                    currentTile->state = state;
                    return;
                }
                else if ((leftTile->state != 0 || leftTile->tile != LevelTileTypeSpace) // cmp word ptr [si+1832h], 0
                         && (leftTile->state != 0x88 || leftTile->tile != 0x88))        // cmp word ptr [si+1832h], 8888h
                {
                    // loc_48371:              ; CODE XREF: movefun+3B6j
                    //                 ; movefun+3C5j
                    state--;
                    currentTile->state = state;
                    return;
                }
                else
                {
                    // loc_48357:              ; CODE XREF: movefun+3BDj
                    currentTile->state = 0xFF;
                    currentTile->tile = 0xFF;
                    leftTile->state = 0x22;
                    leftTile->tile = LevelTileTypeZonk;
                    belowLeftTile->state = 0xFF;
                    belowLeftTile->tile = 0xFF;
                    return;
                }
            }
            // loc_48069:              ; CODE XREF: movefun+D4j
            else if (stateType == 0x60) // 96
            {
                // loc_48378:              ; CODE XREF: movefun+DDj
                uint8_t stateFrame = state & 0x7; // module 8?

                uint8_t tileX = (position % kLevelWidth);
                uint8_t tileY = (position / kLevelWidth);

                uint16_t dstX = tileX * kTileSize;
                uint16_t dstY = tileY * kTileSize;

                // mov si, 12A4h
                Point frameCoordinates = kZonkSlideRightAnimationFrameCoordinates[stateFrame];

                drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                             frameCoordinates.y,
                                             kTileSize * 2,
                                             kTileSize,
                                             dstX, dstY);

                state = currentTile->state;
                state++;

                if (state < 0x62) // 98
                {
                    currentTile->state = state;
                    return;
                }
                // loc_483AF:              ; CODE XREF: movefun+418j
                else if (belowRightTile->state != 0 || belowRightTile->tile != LevelTileTypeSpace) // cmp word ptr [si+18AEh], 0
                {
                    // loc_483DF:              ; CODE XREF: movefun+424j
                    //                 ; movefun+433j
                    state--;
                    currentTile->state = state;
                    return;
                }
                else if ((rightTile->state != 0 || rightTile->tile != LevelTileTypeSpace) // cmp word ptr [si+1836h], 0
                         && (rightTile->state != 0x88 || rightTile->tile != 0x88))        // cmp word ptr [si+1836h], 8888h
                {
                    // loc_483DF:              ; CODE XREF: movefun+424j
                    //                 ; movefun+433j
                    state--;
                    currentTile->state = state;
                    return;
                }
                else
                {
                    // loc_483C5:              ; CODE XREF: movefun+42Bj
                    currentTile->state = 0xFF;
                    currentTile->tile = 0xFF;
                    rightTile->state = 0x32;
                    rightTile->tile = LevelTileTypeZonk;
                    belowRightTile->state = 0xFF;
                    belowRightTile->tile = 0xFF;
                    return;
                }
            }
            // loc_48070:              ; CODE XREF: movefun+DBj
            else if (stateType == 0x70) // 112
            {
                // loc_483E6:              ; CODE XREF: movefun+E4j
                if ((belowTile->state != 0 || belowTile->tile != LevelTileTypeSpace) // cmp word ptr [si+18ACh], 0
                    && (belowTile->state != 0x99 && belowTile->tile != 0x99))        // cmp word ptr [si+18ACh], 9999h
                {
                    return;
                }

                // loc_483F6:              ; CODE XREF: movefun+45Bj
                //                 ; movefun+463j
                currentTile->state = 0xFF;
                currentTile->tile = 0xFF;

                // Move down and update tiles
                position += kLevelWidth;

                currentTile = &gCurrentLevelState[position];
                belowTile = &gCurrentLevelState[position + kLevelWidth];
                belowLeftTile = &gCurrentLevelState[position + kLevelWidth - 1];
                leftTile = &gCurrentLevelState[position - 1];
                belowRightTile = &gCurrentLevelState[position + kLevelWidth + 1];
                rightTile = &gCurrentLevelState[position + 1];
                aboveRightTile = &gCurrentLevelState[position - kLevelWidth + 1];

                currentTile->state = 0x10;
                currentTile->tile = LevelTileTypeZonk;
            }
            // locret_48077:               ; CODE XREF: movefun+E2j
            else
            {
                return;
            }
        }

        // loc_48078:              ; CODE XREF: movefun+B3j
        //                 ; movefun+475j
        //  This animates the Zonk falling
        //  01ED:1415
        uint8_t somePositionThing = state;
        somePositionThing *= 2;
        somePositionThing &= 0x1F;

        uint16_t offset = kFallAnimationGravityOffsets[somePositionThing];

        uint16_t finalPosition = position - kLevelWidth;
        uint8_t tileX = (finalPosition % kLevelWidth);
        uint8_t tileY = (finalPosition / kLevelWidth);

        uint16_t dstX = tileX * kTileSize + (offset % 122);
        uint16_t dstY = tileY * kTileSize + (offset / 122);

        drawMovingSpriteFrameInLevel(224, 82,
                                     kTileSize,
                                     kTileSize + 2,
                                     dstX, dstY);

        uint8_t newState = currentTile->state;
        newState++;
        if (newState == 0x16) // 22
        {
            currentTile->state = newState;
            handleZonkStateAfterFallingOneTile(position - kLevelWidth); // Tile above
            return;
        }
        // loc_480BB:              ; CODE XREF: movefun+11Bj
        else if (newState < 0x18) // 24
        {
            currentTile->state = newState;
            return;
        }

        // loc_480C5:              ; CODE XREF: movefun+12Ej
        //  This part handles what to do when the zonk finished falling 1 tile
        //  01ED:1462
        currentTile->state = 0;
        if (gAreZonksFrozen == 2)
        {
            return;
        }

        // loc_480D2:              ; CODE XREF: movefun+13Fj
        if ((belowTile->tile == LevelTileTypeSpace && belowTile->state == 0) // cmp word ptr [si+18ACh], 0
            || (belowTile->tile == 0x99 && belowTile->state == 0x99))        // cmp word ptr [si+18ACh], 9999h
        {
            // loc_4816D:              ; CODE XREF: movefun+149j
            //                 ; movefun+154j
            currentTile->state = 0x70;
            currentTile->tile = LevelTileTypeZonk;
            belowTile->state = 0x99;
            belowTile->tile = 0x99;
            return;
        }

        // loc_480E7:              ; CODE XREF: movefun+152j
        if (belowTile->tile == LevelTileTypeMurphy) // cmp byte ptr [si+18ACh], 3
        {
            break;
        }

        // loc_480F1:              ; CODE XREF: movefun+15Cj
        if (belowTile->tile == LevelTileTypeSnikSnak) // cmp byte ptr [si+18ACh], 11h
        {
            // loc_481FE:              ; CODE XREF: movefun+168j
            //                 ; movefun+188j ...
            detonateBigExplosion(position + kLevelWidth); // Tile below
            return;
        }

        // loc_480FB:              ; CODE XREF: movefun+166j
        if (belowTile->tile == 0xBB && belowTile->state == 0x2) // cmp word ptr [si+18ACh], 2BBh
        {
            shouldSkipTo_loc_481C6 = 1;
            break;
        }

        // loc_48106:              ; CODE XREF: movefun+171j
        if (belowTile->tile == 0xBB && belowTile->state == 0x4) // cmp word ptr [si+18ACh], 4BBh
        {
            // loc_481E2:              ; CODE XREF: movefun+17Ej
            if (belowRightTile->tile == LevelTileTypeElectron) // cmp byte ptr [si+18AEh], 18h
            {
                belowTile->tile = LevelTileTypeElectron;
                belowTile->state = 0;
            }
            // loc_481EF:              ; CODE XREF: movefun+257j
            if (belowRightTile->tile != LevelTileTypeExplosion) // cmp byte ptr [si+18AEh], 1Fh
            {
                belowRightTile->tile = LevelTileTypeSpace;
                belowRightTile->state = 0;
            }
            // loc_481FE:              ; CODE XREF: movefun+168j
            //                 ; movefun+188j ...
            detonateBigExplosion(position + kLevelWidth); // Tile below
            return;
        }

        // loc_48111:              ; CODE XREF: movefun+17Cj
        if (belowTile->tile == LevelTileTypeElectron) // cmp byte ptr [si+18ACh], 18h
        {
            // loc_481FE:              ; CODE XREF: movefun+168j
            //                 ; movefun+188j ...
            detonateBigExplosion(position + kLevelWidth); // Tile below
            return;
        }

        // loc_4811B:              ; CODE XREF: movefun+186j
        if (belowTile->tile == LevelTileTypeOrangeDisk && belowTile->state == 0) // cmp word ptr [si+18ACh], 8
        {
            // loc_48205:              ; CODE XREF: movefun+192j
            gExplosionTimers[position + kLevelWidth] = 6;
            return;
        }

        // loc_48125:              ; CODE XREF: movefun+190j
        playFallSound();
        if ((belowTile->tile != LevelTileTypeZonk || belowTile->state != 0)        // cmp word ptr [si+18ACh], 1
            && (belowTile->tile != LevelTileTypeInfotron || belowTile->state != 0) // cmp word ptr [si+18ACh], 4
            && (belowTile->tile != LevelTileTypeChip || belowTile->state != 0))    // cmp word ptr [si+18ACh], 5
        {
            return;
        }

        // loc_4813E:              ; CODE XREF: movefun+19Dj
        //                 ; movefun+1A4j ...
        if ((belowLeftTile->state == 0 && belowLeftTile->tile == LevelTileTypeSpace) // cmp word ptr [si+18AAh], 0
            || (belowLeftTile->state == 0x88 && belowLeftTile->tile == 0x88)         // cmp word ptr [si+18AAh], 8888h
            || (belowLeftTile->state == 0xAA && belowLeftTile->tile == 0xAA))        // cmp word ptr [si+18AAh], 0AAAAh
        {
            // loc_4817A:              ; CODE XREF: movefun+1B3j
            //                 ; movefun+1BBj ...
            if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace) // cmp word ptr [si+1832h], 0
            {
                // loc_48184:              ; CODE XREF: movefun+1EFj
                currentTile->state = 0x50;
                leftTile->state = 0x88;
                leftTile->tile = 0x88;
                return;
            }
            else
            {
                continue;
            }
        }
        if ((belowRightTile->state == 0 && belowRightTile->tile == LevelTileTypeSpace) // cmp word ptr [si+18AEh], 0
            || (belowRightTile->state == 0x88 && belowRightTile->tile == 0x88)         // cmp word ptr [si+18AEh], 8888h
            || (belowRightTile->state == 0xAA && belowRightTile->tile == 0xAA))        // cmp word ptr [si+18AEh], 0AAAAh
        {
            // loc_48190:              ; CODE XREF: movefun+1CAj
            //                 ; movefun+1D2j ...
            if (rightTile->tile == LevelTileTypeSpace && rightTile->state == 0) // cmp word ptr [si+1836h], 0
            {
                // loc_48198:              ; CODE XREF: movefun+205j
                currentTile->state = 0x60;
                rightTile->state = 0x88;
                rightTile->tile = 0x88;
                return;
            }
            else
            {
                return;
            }
        }

        return;

    } while (1);

    if (shouldSkipTo_loc_481C6 == 0)
    {
        // loc_481A4:              ; CODE XREF: movefun+15Ej
        if (belowTile->state == 0xE || belowTile->state == 0xF || belowTile->state == 0x28 || belowTile->state == 0x29 || belowTile->state == 0x25 || belowTile->state == 0x26)
        {
            return;
        }
    }

    // loc_481C6:              ; CODE XREF: movefun+173j
    if (belowLeftTile->tile == LevelTileTypeElectron) // cmp byte ptr [si+18AAh], 18h
    {
        belowTile->tile = LevelTileTypeElectron;
        belowTile->state = 0;
    }
    // loc_481D3:              ; CODE XREF: movefun+23Bj
    if (belowLeftTile->tile != LevelTileTypeExplosion) // cmp byte ptr [si+18AAh], 1Fh
    {
        belowLeftTile->tile = LevelTileTypeSpace;
        belowLeftTile->state = 0;
    }

    // loc_481FE:              ; CODE XREF: movefun+168j
    //                 ; movefun+188j ...
    detonateBigExplosion(position + kLevelWidth); // Tile below
}

void updateInfotronTiles(int16_t position) // movefun2  proc near       ; DATA XREF: data:1612o
{
    // 01ED:17A5

    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *belowLeftTile = &gCurrentLevelState[position + kLevelWidth - 1];
    StatefulLevelTile *belowRightTile = &gCurrentLevelState[position + kLevelWidth + 1];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    if (currentTile->tile != LevelTileTypeInfotron)
    {
        return;
    }

    // loc_48410:              ; CODE XREF: movefun2+5j
    uint8_t shouldSkipFirstPartOfLoop = 0;

    if (currentTile->state != 0 || currentTile->tile != LevelTileTypeInfotron)
    {
        shouldSkipFirstPartOfLoop = 1; // used to emulate "jmp loc_48495"
    }
    else
    {
        // loc_4841B:              ; CODE XREF: movefun2+Fj
        if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
        {
            // loc_48463:              ; CODE XREF: movefun2+1Aj
            currentTile->state = 0x40;
            shouldSkipFirstPartOfLoop = 1; // used to emulate "jmp loc_48495"
        }
        else
        {
            if ((belowTile->state != 0 || belowTile->tile != LevelTileTypeZonk) && (belowTile->state != 0 || belowTile->tile != LevelTileTypeInfotron) && (belowTile->state != 0 || belowTile->tile != LevelTileTypeChip))
            {
                return;
            }

            // loc_48434:              ; CODE XREF: movefun2+1Fj
            //                 ; movefun2+24j ...
            if ((belowLeftTile->state == 0 && belowLeftTile->tile == LevelTileTypeSpace) || (belowLeftTile->state == 0x88 && belowLeftTile->tile == 0x88) || (belowLeftTile->state == 0xAA && belowLeftTile->tile == 0xAA))
            {
                // loc_4846A:              ; CODE XREF: movefun2+31j
                //                 ; movefun2+39j ...
                if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
                {
                    // loc_48473:              ; CODE XREF: movefun2+67j
                    currentTile->state = 0x50;
                    leftTile->state = 0x88;
                    leftTile->tile = 0x88;
                    shouldSkipFirstPartOfLoop = 1; // used to emulate "jmp loc_48495"
                }
            }
        }
    }

    do
    {
        if (shouldSkipFirstPartOfLoop == 0)
        {
            // loc_4844B:              ; CODE XREF: movefun2+69j
            //                 ; movefun2+1C7j
            if ((belowRightTile->state != 0 || belowRightTile->tile != LevelTileTypeSpace) && (belowRightTile->state != 0x88 || belowRightTile->tile != 0x88) && (belowRightTile->state != 0xAA || belowRightTile->tile != 0xAA))
            {
                return;
            }

            // loc_48480:              ; CODE XREF: movefun2+48j
            //                 ; movefun2+50j ...
            if (rightTile->state != 0 || rightTile->tile != LevelTileTypeSpace)
            {
                return;
            }

            // loc_48488:              ; CODE XREF: movefun2+7Dj
            currentTile->state = 0x60;
            rightTile->state = 0x88;
            rightTile->tile = 0x88;
        }

        shouldSkipFirstPartOfLoop = 0;

        // loc_48495:              ; CODE XREF: movefun2+11j
        //                 ; movefun2+60j ...
        uint8_t state = currentTile->state;
        uint8_t stateType = state & 0xF0;

        if (stateType != 0x10)
        {
            // loc_484A5:              ; CODE XREF: movefun2+99j
            if (stateType == 0x20)
            {
                // loc_4861B:              ; CODE XREF: movefun2+A1j
                uint8_t stateFrame = state & 0x7; // module 8?

                // mov si, 12B6h
                Point frameCoordinates = kInfotronSlideLeftAnimationFrameCoordinates[stateFrame];

                uint8_t tileX = (position % kLevelWidth);
                uint8_t tileY = (position / kLevelWidth);

                uint16_t dstX = tileX * kTileSize;
                uint16_t dstY = tileY * kTileSize;

                drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                             frameCoordinates.y,
                                             kTileSize * 2,
                                             kTileSize,
                                             dstX,
                                             dstY);

                state = currentTile->state;
                state++;
                if (state == 0x24) // 36
                {
                    rightTile->state = 0xAA;
                    rightTile->tile = 0xAA;
                }
                // loc_48653:              ; CODE XREF: movefun2+243j
                if (state == 0x26) // 38
                {
                    currentTile->state = state;
                    handleInfotronStateAfterFallingOneTile(position + 1);
                    return;
                }
                // loc_48666:              ; CODE XREF: movefun2+24Ej
                else if (state < 0x28) // 40
                {
                    currentTile->state = state;
                    return;
                }
                // loc_48670:              ; CODE XREF: movefun2+261j
                else
                {
                    currentTile->state = 0x70;
                    currentTile->tile = LevelTileTypeInfotron;
                    return;
                }
            }
            // loc_484AC:              ; CODE XREF: movefun2+9Fj
            else if (stateType == 0x30)
            {
                // loc_48677:              ; CODE XREF: movefun2+A8j
                uint8_t stateFrame = state & 0x7;

                uint8_t tileX = ((position - 1) % kLevelWidth);
                uint8_t tileY = ((position - 1) / kLevelWidth);

                uint16_t dstX = tileX * kTileSize;
                uint16_t dstY = tileY * kTileSize;

                // mov si, 12C6h
                Point frameCoordinates = kInfotronSlideRightAnimationFrameCoordinates[stateFrame];

                drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                             frameCoordinates.y,
                                             kTileSize * 2,
                                             kTileSize,
                                             dstX, dstY);

                state = currentTile->state;
                state++;
                if (state == 0x34) // 52
                {
                    leftTile->state = 0xAA;
                    leftTile->tile = 0xAA;
                }
                // loc_486AF:              ; CODE XREF: movefun2+29Fj
                if (state == 0x36) // 54
                {
                    currentTile->state = state;
                    handleInfotronStateAfterFallingOneTile(position - 1); // left tile
                }
                // loc_486C1:              ; CODE XREF: movefun2+2AAj
                if (state < 0x38) // 54
                {
                    currentTile->state = state;
                    return;
                }
                else
                {
                    currentTile->state = 0x70;
                    currentTile->tile = LevelTileTypeInfotron;
                    return;
                }
            }
            // loc_484B3:              ; CODE XREF: movefun2+A6j
            else if (stateType == 0x40)
            {
                // loc_486D2:              ; CODE XREF: movefun2+AFj
                state++;
                if (state < 0x42)
                {
                    currentTile->state = state;
                    return;
                }

                // loc_486DE:              ; CODE XREF: movefun2+2CFj
                if (belowTile->state != 0 || belowTile->tile != LevelTileTypeSpace)
                {
                    state--;
                    currentTile->state = state;
                    return;
                }

                // loc_486EC:              ; CODE XREF: movefun2+2DBj
                currentTile->state = 0xFF;
                currentTile->tile = 0xFF;
                belowTile->state = 0x10;
                belowTile->tile = LevelTileTypeInfotron;
                return;
            }
            // loc_484BA:              ; CODE XREF: movefun2+ADj
            else if (stateType == 0x50)
            {
                // loc_48700:              ; CODE XREF: movefun2+B6j
                uint8_t stateFrame = state & 0x7; // module 8?

                uint8_t tileX = ((position - 1) % kLevelWidth);
                uint8_t tileY = ((position - 1) / kLevelWidth);

                uint16_t dstX = tileX * kTileSize;
                uint16_t dstY = tileY * kTileSize;

                // mov si, 12B6h
                Point frameCoordinates = kInfotronSlideLeftAnimationFrameCoordinates[stateFrame];
                drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                             frameCoordinates.y,
                                             kTileSize * 2,
                                             kTileSize,
                                             dstX, dstY);
                state = currentTile->state;
                state++;

                if (state < 0x52) // 82
                {
                    currentTile->state = state;
                    return;
                }
                // loc_48737:              ; CODE XREF: movefun2+328j
                else if (belowLeftTile->state != 0 || belowLeftTile->tile != LevelTileTypeSpace) // cmp word ptr [si+18AAh], 0
                {
                    // loc_48767:              ; CODE XREF: movefun2+334j
                    //                 ; movefun2+343j
                    state--;
                    currentTile->state = state;
                    return;
                }
                else if ((leftTile->state != 0 || leftTile->tile != LevelTileTypeSpace) // cmp word ptr [si+1832h], 0
                         && (leftTile->state != 0x88 || leftTile->tile != 0x88))        // cmp word ptr [si+1832h], 8888h
                {
                    // loc_48767:              ; CODE XREF: movefun2+334j
                    //                 ; movefun2+343j
                    state--;
                    currentTile->state = state;
                    return;
                }
                else
                {
                    // loc_4874D:              ; CODE XREF: movefun2+33Bj
                    currentTile->state = 0xFF;
                    currentTile->tile = 0xFF;
                    leftTile->state = 0x22;
                    leftTile->tile = LevelTileTypeInfotron;
                    belowLeftTile->state = 0x99;
                    belowLeftTile->tile = 0x99;
                    return;
                }
            }
            // loc_484C1:              ; CODE XREF: movefun2+B4j
            else if (stateType == 0x60)
            {
                // loc_4876E:              ; CODE XREF: movefun2+BDj
                uint8_t stateFrame = state & 0x7; // module 8?

                uint8_t tileX = (position % kLevelWidth);
                uint8_t tileY = (position / kLevelWidth);

                uint16_t dstX = tileX * kTileSize;
                uint16_t dstY = tileY * kTileSize;

                // mov si, 12C6h
                Point frameCoordinates = kInfotronSlideRightAnimationFrameCoordinates[stateFrame];

                drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                             frameCoordinates.y,
                                             kTileSize * 2,
                                             kTileSize,
                                             dstX, dstY);

                state = currentTile->state;
                state++;

                if (state < 0x62) // 98
                {
                    currentTile->state = state;
                    return;
                }
                // loc_487A5:              ; CODE XREF: movefun2+396j
                else if (belowRightTile->state != 0 || belowRightTile->tile != LevelTileTypeSpace) // cmp word ptr [si+18AEh], 0
                {
                    // loc_487D5:              ; CODE XREF: movefun2+3A2j
                    //                 ; movefun2+3B1j
                    state--;
                    currentTile->state = state;
                    return;
                }
                else if ((rightTile->state != 0 || rightTile->tile != LevelTileTypeSpace) // cmp word ptr [si+1836h], 0
                         && (rightTile->state != 0x88 || rightTile->tile != 0x88))        // cmp word ptr [si+1836h], 8888h
                {
                    // loc_487D5:              ; CODE XREF: movefun2+3A2j
                    //                 ; movefun2+3B1j
                    state--;
                    currentTile->state = state;
                    return;
                }
                else
                {
                    // loc_487BB:              ; CODE XREF: movefun2+3A9j
                    currentTile->state = 0xFF;
                    currentTile->tile = 0xFF;
                    rightTile->state = 0x32;
                    rightTile->tile = LevelTileTypeInfotron;
                    belowRightTile->state = 0x99;
                    belowRightTile->tile = 0x99;
                    return;
                }
            }
            // loc_484C8:              ; CODE XREF: movefun2+BBj
            else if (stateType == 0x70)
            {
                // loc_487DC:              ; CODE XREF: movefun2:loc_484CCj
                if ((belowTile->state != 0 || belowTile->tile != LevelTileTypeSpace) && (belowTile->state != 0x99 || belowTile->tile != 0x99))
                {
                    return;
                }

                // loc_487EC:              ; CODE XREF: movefun2+3D9j
                //                     ; movefun2+3E1j
                currentTile->state = 0xFF;
                currentTile->tile = 0xFF;

                // add si, 78h ; 'x'
                position += kLevelWidth;

                currentTile = &gCurrentLevelState[position];
                belowTile = &gCurrentLevelState[position + kLevelWidth];
                belowLeftTile = &gCurrentLevelState[position + kLevelWidth - 1];
                leftTile = &gCurrentLevelState[position - 1];
                belowRightTile = &gCurrentLevelState[position + kLevelWidth + 1];
                rightTile = &gCurrentLevelState[position + 1];

                currentTile->state = 0x10;
                currentTile->tile = LevelTileTypeInfotron;
            }
            else
            {
                return;
            }
        }

        // loc_484D0:              ; CODE XREF: movefun2+9Bj
        //                 ; movefun2+3F3j
        //  This animates the Infotron falling
        uint8_t somePositionThing = state;
        somePositionThing *= 2;
        somePositionThing &= 0x1F;

        uint16_t offset = kFallAnimationGravityOffsets[somePositionThing];

        uint16_t finalPosition = position - kLevelWidth;
        uint8_t tileX = (finalPosition % kLevelWidth);
        uint8_t tileY = (finalPosition / kLevelWidth);

        uint16_t dstX = tileX * kTileSize + (offset % 122);
        uint16_t dstY = tileY * kTileSize + (offset / 122);

        // mov si, word_515C4
        drawMovingSpriteFrameInLevel(240, 178,
                                     kTileSize,
                                     kTileSize + 2,
                                     dstX, dstY);

        uint8_t newState = currentTile->state;
        newState++;
        if (newState == 0x16) // 22
        {
            currentTile->state = newState;
            handleInfotronStateAfterFallingOneTile(position - kLevelWidth); // Tile above
            return;
        }
        // loc_48513:              ; CODE XREF: movefun2+FBj
        else if (newState < 0x18) // 24
        {
            currentTile->state = newState;
            return;
        }

        // loc_4851D:              ; CODE XREF: movefun2+10Ej
        //  This part handles what to do when the Infotron finished falling 1 tile
        currentTile->state = 0;

        if ((belowTile->tile == LevelTileTypeSpace && belowTile->state == 0) // cmp word ptr [si+18ACh], 0
            || (belowTile->tile == 0x99 && belowTile->state == 0x99))        // cmp word ptr [si+18ACh], 9999h
        {
            // loc_485BB:              ; CODE XREF: movefun2+121j
            //                 ; movefun2+12Cj
            currentTile->state = 0x70;
            currentTile->tile = LevelTileTypeInfotron;
            belowTile->state = 0x99;
            belowTile->tile = 0x99;
            return;
        }

        // loc_48537:              ; CODE XREF: movefun2+12Aj
        if (belowTile->tile == LevelTileTypeMurphy) // cmp byte ptr [si+18ACh], 3
        {
            // loc_485F2:              ; CODE XREF: movefun2+136j
            if (belowTile->state == 0xE || belowTile->state == 0xF || belowTile->state == 0x28 || belowTile->state == 0x29 || belowTile->state == 0x25 || belowTile->state == 0x26)
            {
                return;
            }

            // loc_48614:              ; CODE XREF: movefun2+140j
            //                 ; movefun2+14Aj ...
            detonateBigExplosion(position + kLevelWidth);
            return;
        }

        // loc_48541:              ; CODE XREF: movefun2+134j
        if ((belowTile->tile == LevelTileTypeRedDisk && belowTile->state == 0)        // cmp word ptr [si+18ACh], 14h
            || belowTile->tile == LevelTileTypeSnikSnak                               // cmp byte ptr [si+18ACh], 11h
            || belowTile->tile == LevelTileTypeElectron                               // cmp byte ptr [si+18ACh], 18h
            || (belowTile->tile == LevelTileTypeYellowDisk && belowTile->state == 0)  // cmp word ptr [si+18ACh], 12h
            || (belowTile->tile == LevelTileTypeOrangeDisk && belowTile->state == 0)) // cmp word ptr [si+18ACh], 8
        {
            // loc_48614:              ; CODE XREF: movefun2+140j
            //                 ; movefun2+14Aj ...
            detonateBigExplosion(position + kLevelWidth);
            return;
        }

        // loc_48573:              ; CODE XREF: movefun2+166j
        playFallSound();
        if ((belowTile->tile != LevelTileTypeZonk || belowTile->state != 0)        // cmp word ptr [si+18ACh], 1
            && (belowTile->tile != LevelTileTypeInfotron || belowTile->state != 0) // cmp word ptr [si+18ACh], 4
            && (belowTile->tile != LevelTileTypeChip || belowTile->state != 0))    // cmp word ptr [si+18ACh], 5
        {
            return;
        }

        // loc_4858C:              ; CODE XREF: movefun2+173j
        //                 ; movefun2+17Aj ...
        if ((belowLeftTile->tile == LevelTileTypeSpace && belowLeftTile->state == 0) // cmp word ptr [si+18AAh], 0
            || (belowLeftTile->tile == 0x88 && belowLeftTile->state == 0x88)         // cmp word ptr [si+18AAh], 8888h
            || (belowLeftTile->tile == 0xAA && belowLeftTile->state == 0xAA))        // cmp word ptr [si+18AAh], 0AAAAh
        {
            // loc_485C8:              ; CODE XREF: movefun2+189j
            //                 ; movefun2+191j ...
            if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
            {
                // loc_485D2:              ; CODE XREF: movefun2+1C5j
                currentTile->state = 0x50;
                leftTile->state = 0x88;
                leftTile->tile = 0x88;
                return;
            }
            else
            {
                continue; // jmp loc_4844B
            }
        }
        if ((belowRightTile->tile == LevelTileTypeSpace && belowRightTile->state == 0) // cmp word ptr [si+18AEh], 0
            || (belowRightTile->tile == 0x88 && belowRightTile->state == 0x88)         // cmp word ptr [si+18AEh], 8888h
            || (belowRightTile->tile == 0xAA && belowRightTile->state == 0xAA))        // cmp word ptr [si+18AEh], 0AAAAh
        {
            // loc_485DE:              ; CODE XREF: movefun2+1A0j
            //                 ; movefun2+1A8j ...
            if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
            {
                // loc_485E6:              ; CODE XREF: movefun2+1DBj
                currentTile->state = 0x60;
                rightTile->state = 0x88;
                rightTile->tile = 0x88;
                return;
            }
            return;
        }
        return;
    } while (1);
}

void handleMurphyCollisionAfterMovement(int16_t position) // sub_487FE   proc near       ; CODE XREF: update?+E0Cp update?+E2Ap ...
{
    // 01ED:1B9B
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];
    StatefulLevelTile *aboveRightTile = &gCurrentLevelState[position - kLevelWidth + 1];
    StatefulLevelTile *aboveLeftTile = &gCurrentLevelState[position - kLevelWidth - 1];

    if (currentTile->tile != LevelTileTypeExplosion)
    {
        currentTile->state = 0;
        currentTile->tile = LevelTileTypeSpace;
    }

    // loc_4880B:              ; CODE XREF: handleMurphyCollisionAfterMovement+5j
    if ((aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace) || (aboveTile->state == 0x99 && aboveTile->tile == 0x99))
    {
        // loc_48835:              ; CODE XREF: handleMurphyCollisionAfterMovement+12j
        //                 ; handleMurphyCollisionAfterMovement+1Aj
        if (aboveLeftTile->state == 0 && aboveLeftTile->tile == LevelTileTypeZonk)
        {
            // loc_48852:              ; CODE XREF: handleMurphyCollisionAfterMovement+3Cj
            if ((leftTile->state == 0 && leftTile->tile == LevelTileTypeZonk) || (leftTile->state == 0 && leftTile->tile == LevelTileTypeInfotron) || (leftTile->state == 0 && leftTile->tile == LevelTileTypeChip))
            {
                // loc_48869:              ; CODE XREF: handleMurphyCollisionAfterMovement+59j
                //                 ; handleMurphyCollisionAfterMovement+60j ...
                aboveLeftTile->state = 0x60;
                aboveTile->state = 0x88;
                aboveTile->tile = 0x88;
                return;
            }
        }
        else if (aboveLeftTile->state == 0 && aboveLeftTile->tile == LevelTileTypeInfotron)
        {
            // loc_48897:              ; CODE XREF: handleMurphyCollisionAfterMovement+43j
            if ((leftTile->state == 0 && leftTile->tile == LevelTileTypeZonk) || (leftTile->state == 0 && leftTile->tile == LevelTileTypeInfotron) || (leftTile->state == 0 && leftTile->tile == LevelTileTypeChip))
            {
                // loc_488AE:              ; CODE XREF: handleMurphyCollisionAfterMovement+9Ej
                //                 ; handleMurphyCollisionAfterMovement+A5j ...
                aboveLeftTile->state = 0x60;
                aboveTile->state = 0x88;
                aboveTile->tile = 0x88;
                return;
            }
        }
        // loc_48843:              ; CODE XREF: handleMurphyCollisionAfterMovement+69j
        //                 ; handleMurphyCollisionAfterMovement+AEj
        if (aboveRightTile->state == 0 && aboveRightTile->tile == LevelTileTypeZonk)
        {
            // loc_48875:              ; CODE XREF: handleMurphyCollisionAfterMovement+4Aj
            if ((rightTile->state == 0 && rightTile->tile == LevelTileTypeZonk) || (rightTile->state == 0 && rightTile->tile == LevelTileTypeInfotron) || (rightTile->state == 0 && rightTile->tile == LevelTileTypeChip))
            {
                // loc_4888B:              ; CODE XREF: handleMurphyCollisionAfterMovement+7Cj
                //                 ; handleMurphyCollisionAfterMovement+83j ...
                aboveRightTile->state = 0x50;
                aboveTile->state = 0x88;
                aboveTile->tile = 0x88;
            }
        }
        else if (aboveRightTile->state == 0 && aboveRightTile->tile == LevelTileTypeInfotron)
        {
            // loc_488BA:              ; CODE XREF: handleMurphyCollisionAfterMovement+51j
            if ((rightTile->state == 0 && rightTile->tile == LevelTileTypeZonk) || (rightTile->state == 0 && rightTile->tile == LevelTileTypeInfotron) || (rightTile->state == 0 && rightTile->tile == LevelTileTypeChip))
            {
                // loc_488D0:              ; CODE XREF: handleMurphyCollisionAfterMovement+C1j
                //                 ; handleMurphyCollisionAfterMovement+C8j ...
                aboveRightTile->state = 0x50;
                aboveTile->state = 0x88;
                aboveTile->tile = 0x88;
            }
        }
    }
    else if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeZonk)
    {
        // loc_48829:              ; CODE XREF: handleMurphyCollisionAfterMovement+21j
        aboveTile->state = 0x40;
    }
    else if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeInfotron)
    {
        // loc_4882F:              ; CODE XREF: handleMurphyCollisionAfterMovement+28j
        aboveTile->state = 0x40;
    }
}

void handleZonkStateAfterFallingOneTile(int16_t position) // sub_488DC   proc near       ; CODE XREF: movefun+124p
                                                          // ; movefun+2C6p ...
{
    // 01ED:1C79
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];
    StatefulLevelTile *aboveAboveTile = &gCurrentLevelState[position - kLevelWidth * 2];
    StatefulLevelTile *aboveLeftTile = &gCurrentLevelState[position - kLevelWidth - 1];
    StatefulLevelTile *aboveRightTile = &gCurrentLevelState[position - kLevelWidth + 1];

    if (currentTile->tile != LevelTileTypeExplosion)
    {
        currentTile->state = 0;
        currentTile->tile = LevelTileTypeSpace;
    }

    // loc_488E9:              ; CODE XREF: handleZonkStateAfterFallingOneTile+5j
    if (aboveTile->state != 0 || aboveTile->tile != LevelTileTypeSpace) // cmp word ptr [si+17BCh], 0
    {
        if (aboveTile->state != 0x99 || aboveTile->tile != 0x99) // cmp word ptr [si+17BCh], 9999h
        {
            return;
        }

        // loc_488F9:              ; CODE XREF: handleZonkStateAfterFallingOneTile+1Aj
        if (aboveAboveTile->tile != LevelTileTypeInfotron) // cmp byte ptr [si+1744h], 4
        {
            return;
        }
    }

    // loc_48901:              ; CODE XREF: handleZonkStateAfterFallingOneTile+12j
    //                 ; handleZonkStateAfterFallingOneTile+22j
    if (aboveLeftTile->state == 0 && aboveLeftTile->tile == LevelTileTypeZonk) // cmp word ptr [si+17BAh], 1
    {
        // loc_48910:              ; CODE XREF: handleZonkStateAfterFallingOneTile+2Aj
        if (leftTile->state == 0 && (leftTile->tile == LevelTileTypeZonk || leftTile->tile == LevelTileTypeInfotron || leftTile->tile == LevelTileTypeChip))
        {
            // loc_48927:              ; CODE XREF: handleZonkStateAfterFallingOneTile+39j
            //                 ; handleZonkStateAfterFallingOneTile+40j ...
            //  mov word ptr [si+17BAh], 6001h
            aboveLeftTile->state = 0x60;
            aboveLeftTile->tile = LevelTileTypeZonk;
            // mov word ptr [si+17BCh], 8888h
            aboveTile->state = 0x88;
            aboveTile->tile = 0x88;
            return;
        }
    }

    // loc_48908:              ; CODE XREF: handleZonkStateAfterFallingOneTile+49j
    if (aboveRightTile->state == 0 && aboveRightTile->tile == LevelTileTypeZonk) // cmp word ptr [si+17BEh], 1
    {
        // loc_48934:              ; CODE XREF: handleZonkStateAfterFallingOneTile+31j
        if (rightTile->state != 0 || (rightTile->tile != LevelTileTypeZonk && rightTile->tile != LevelTileTypeInfotron && rightTile->tile != LevelTileTypeChip))
        {
            return;
        }

        // loc_4894A:              ; CODE XREF: handleZonkStateAfterFallingOneTile+5Dj
        //                 ; handleZonkStateAfterFallingOneTile+64j ...
        //  mov word ptr [si+17BEh], 5001h
        aboveRightTile->state = 0x50;
        aboveRightTile->tile = LevelTileTypeZonk;
        // mov word ptr [si+17BCh], 8888h
        aboveTile->state = 0x88;
        aboveTile->tile = 0x88;
        return;
    }
}

void handleInfotronStateAfterFallingOneTile(int16_t position) // sub_48957   proc near       ; CODE XREF: movefun2+104p
//                    ; movefun2+257p ...
{
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *aboveAboveTile = &gCurrentLevelState[position - kLevelWidth * 2];
    StatefulLevelTile *aboveLeftTile = &gCurrentLevelState[position - kLevelWidth - 1];
    StatefulLevelTile *aboveRightTile = &gCurrentLevelState[position - kLevelWidth + 1];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    if (currentTile->tile != LevelTileTypeExplosion)
    {
        currentTile->state = 0;
        currentTile->tile = LevelTileTypeSpace;
    }
    // loc_48964:              ; CODE XREF: handleInfotronStateAfterFallingOneTile+5j
    if (aboveTile->state != 0 || aboveTile->tile != LevelTileTypeSpace)
    {
        if (aboveTile->state == 0x99 && aboveTile->tile == 0x99)
        {
            // loc_48974:              ; CODE XREF: handleInfotronStateAfterFallingOneTile+1Aj
            if (aboveAboveTile->tile != LevelTileTypeZonk)
            {
                return;
            }
        }
        else
        {
            return;
        }
    }

    // loc_4897C:              ; CODE XREF: handleInfotronStateAfterFallingOneTile+12j
    //                 ; handleInfotronStateAfterFallingOneTile+22j
    if (aboveLeftTile->state == 0 && aboveLeftTile->tile == LevelTileTypeInfotron)
    {
        // loc_4898B:              ; CODE XREF: handleInfotronStateAfterFallingOneTile+2Aj
        if ((leftTile->state == 0 && leftTile->tile == LevelTileTypeZonk) || (leftTile->state == 0 && leftTile->tile == LevelTileTypeInfotron) || (leftTile->state == 0 && leftTile->tile == LevelTileTypeChip))
        {
            // loc_489A2:              ; CODE XREF: handleInfotronStateAfterFallingOneTile+39j
            //                 ; handleInfotronStateAfterFallingOneTile+40j ...
            aboveLeftTile->state = 0x60;
            aboveLeftTile->tile = LevelTileTypeInfotron;
            aboveTile->state = 0x88;
            aboveTile->tile = 0x88;
            return;
        }
    }

    // loc_48983:              ; CODE XREF: handleInfotronStateAfterFallingOneTile+49j
    if (aboveRightTile->state != 0 || aboveRightTile->tile != LevelTileTypeInfotron)
    {
        return;
    }

    // loc_489AF:              ; CODE XREF: handleInfotronStateAfterFallingOneTile+31j
    if ((rightTile->state != 0 || rightTile->tile != LevelTileTypeZonk) && (rightTile->state != 0 || rightTile->tile != LevelTileTypeInfotron) && (rightTile->state != 0 || rightTile->tile != LevelTileTypeChip))
    {
        return;
    }

    // loc_489C5:              ; CODE XREF: handleInfotronStateAfterFallingOneTile+5Dj
    //                 ; handleInfotronStateAfterFallingOneTile+64j ...
    aboveRightTile->state = 0x50;
    aboveRightTile->tile = LevelTileTypeInfotron;
    aboveTile->state = 0x88;
    aboveTile->tile = 0x88;
}

void initializeGameInfo() // sub_48A20   proc near       ; CODE XREF: start+32Fp
                          // ; runLevel:notFunctionKeyp ...
{
    // 01ED:1DBD
    // word_510BC = gMurphyTileX;
    // word_510BE = gMurphyTileY;
    gIsMurphyLookingLeft = 0;
    gShouldKillMurphy = 0;
    gShouldExitLevel = 0;
    gQuitLevelCountdown = 0;
    gNumberOfRemainingRedDisks = 0;
    gAdditionalInfoInGamePanelFrameCounter = 0;
    gMurphyYawnAndSleepCounter = 0;
    gLastDrawnMinutesAndSeconds = 0xFFFF;
    gLastDrawnHours = 0xFF; // 255
    gIsGameRunning = 1;
    gAuxGameSeconds20msAccumulator = 0;
    gGameSeconds = 0;
    gGameMinutes = 0;
    gGameHours = 0;
    gIsExplosionStarted = 0;
    gTerminalMaxFramesToNextScroll = 0x7F; // 127
    gAreYellowDisksDetonated = 0;
    gFrameCounter = 0;
    //    mov byte ptr word_510C1, 1
    //    mov byte ptr word_510C1+1, 0
    gShouldShowGamePanel = 1;
    gCurrentPanelHeight = kPanelBitmapHeight;
    gToggleGamePanelKeyAutoRepeatCounter = 0;
    gAreEnemiesFrozen = 0;
    gIsMurphyGoingThroughPortal &= 0xFF00; // mov byte ptr gIsMurphyGoingThroughPortal, 0
    gPlantedRedDiskCountdown = 0;
    gPlantedRedDiskPosition = 0;
}

void handleGameIterationStarted()
{
    gGameIterationStartTime = getTime();
}

void handleGameIterationFinished()
{
    const float kOriginalIterationDuration = 1000.0 / 35; // 35 iterations per second in the original game
    float currentIterationDuration = getTime() - gGameIterationStartTime;

    float targetIterationDuration = kOriginalIterationDuration * kSpeedTimeFactors[gGameSpeed];

    if (gFastMode != FastModeTypeNone)
    {
        targetIterationDuration = 0;
    }

    if (currentIterationDuration < targetIterationDuration)
    {
        waitTime(targetIterationDuration - currentIterationDuration);
    }

    gNumberOfGameIterations++;

    if (gGameIterationRateReferenceTime == 0)
    {
        gGameIterationRateReferenceTime = getTime();
    }
    else
    {
        uint32_t difference = getTime() - gGameIterationRateReferenceTime;

        if (difference > 1000)
        {
            gGameIterationRate = gNumberOfGameIterations * 1000.f / difference;
            gNumberOfGameIterations = 0;
            gGameIterationRateReferenceTime = getTime();
        }
    }
}

void runLevel() //    proc near       ; CODE XREF: start+35Cp
{
    // 01ED:1E58
    if (gIsPlayingDemo == 0)
    {
        // loc_48ACE:              ; CODE XREF: runLevel+5j
        gIsLevelStartedAsDemo = 0;
        gLevelFailed = 1;
    }
    else
    {
        gIsLevelStartedAsDemo = 1;
        gLevelFailed = 0;
    }

    // loc_48AD8:              ; CODE XREF: runLevel+11j
    if (gDemoRecordingJustStarted == 1)
    {
        // loc_48ADF:              ; CODE XREF: runLevel+BAj
        gDemoRecordingJustStarted = 0;
        drawGameTime();

        do
        {
            // isFunctionKey:              ; CODE XREF: runLevel+35j
            int9handler(1);
        } while (areAnyF1ToF10KeysPressed());

        // notFunctionKey:             ; CODE XREF: runLevel+31j
        initializeGameInfo();
        if (isMusicEnabled == 0)
        {
            stopMusic();
        }

        // loc_48AFF:              ; CODE XREF: runLevel+3Fj
        gIsLevelStartedAsDemo = 0;
        gLevelFailed = 1;
    }

    // loc_48B09:              ; CODE XREF: runLevel+22j
    gPlantedRedDiskCountdown = 0;
    byte_5A323 = 0;

    do
    {
        handleGameIterationStarted();

        int9handler(0);

        uint16_t mouseButtonsStatus;

        // loc_48B23:              ; CODE XREF: runLevel+63j
        getMouseStatus(NULL, NULL, &mouseButtonsStatus);

        // loc_48B38:              ; CODE XREF: runLevel+6Ej runLevel+75j
        if (gIsDebugModeEnabled != 0)
        {
            if (gToggleFancyEasyTilesThrottleCounter != 0)
            {
                gToggleFancyEasyTilesThrottleCounter--;
            }

            // loc_48B4A:              ; CODE XREF: runLevel+89j
            if (gIsEnterPressed == 0 && mouseButtonsStatus == MouseButtonLeft // cmp bx, 1
                && gToggleFancyEasyTilesThrottleCounter == 0)
            {
                gToggleFancyEasyTilesThrottleCounter = 0xA;
                restoreOriginalFancyTiles(); // 01ED:1EFF
                drawFixedLevel();
                convertToEasyTiles();
            }
        }

        // loc_48B6B:              ; CODE XREF: runLevel+82j runLevel+94j ...
        handleGameUserInput(); // 01ED:1F08
        if (gDemoRecordingJustStarted == 1)
        {
            // Restart the demo
            // loc_48ADF:              ; CODE XREF: runLevel+BAj
            gDemoRecordingJustStarted = 0;
            drawGameTime();

            do
            {
                // isFunctionKey:              ; CODE XREF: runLevel+35j
                int9handler(1);
            } while (areAnyF1ToF10KeysPressed());

            // notFunctionKey:             ; CODE XREF: runLevel+31j
            initializeGameInfo();
            if (isMusicEnabled == 0)
            {
                stopMusic();
            }

            // loc_48AFF:              ; CODE XREF: runLevel+3Fj
            gIsLevelStartedAsDemo = 0;
            gLevelFailed = 1;

            // loc_48B09:              ; CODE XREF: runLevel+22j
            gPlantedRedDiskCountdown = 0;
            byte_5A323 = 0;

            continue;

            // All the code in this "if" is equivalent to "jmp loc_48ADF"
        }

        // loc_48B78:              ; CODE XREF: runLevel+B8j
        if (gIsFlashingBackgroundModeEnabled != 0)
        {
            replaceCurrentPaletteColor(0, (Color){0x35, 0x35, 0x35});
        }

        // noFlashing:              ; CODE XREF: runLevel+C2j
        updateMovingObjects(); // 01ED:1F28
        if (gIsFlashingBackgroundModeEnabled != 0)
        {
            replaceCurrentPaletteColor(0, (Color){0x21, 0x21, 0x21});
        }

        // noFlashing2:              ; CODE XREF: runLevel+D8j
        drawGameTime();
        clearAdditionalInfoInGamePanelIfNeeded();
        if (gIsFlashingBackgroundModeEnabled != 0)
        {
            replaceCurrentPaletteColor(0, (Color){0x2d, 0x21, 0x0f});
        }

        // noFlashing3:              ; CODE XREF: runLevel+F1j
        //  01ED:1F5B
        updatePlantedRedDisk();
        updateExplosionTimers();
        updateScrollOffset();

        // loc_48D59:              ; CODE XREF: runLevel+19Bj
        //                 ; runLevel+1D2j ...
        if (gIsFlashingBackgroundModeEnabled != 0)
        {
            replaceCurrentPaletteColor(0, (Color){0x3f, 0x3f, 0x3f});
        }

        // noFlashing4:              ; CODE XREF: runLevel+2D1j
        drawCurrentLevelViewport(gCurrentPanelHeight); // Added by me
        if (gFastMode != FastModeTypeUltra)
        {
            videoLoop(); // 01ED:2142
        }
        handleGameIterationFinished();

        // isFastMode2:              ; CODE XREF: runLevel+2E8j
        if (gDebugExtraRenderDelay > 1)
        {
            playBaseSound();
        }

        // loc_48DB2:              ; CODE XREF: runLevel+2F2j
        //  Extra delays in debug mode
        for (int i = 1; i < gDebugExtraRenderDelay; ++i)
        {
            // loc_48DB6:              ; CODE XREF: runLevel+310j
            if (gFastMode == FastModeTypeNone)
            {
                videoLoop(); // 01ED:2160
            }

            // isFastMode3:              ; CODE XREF: runLevel+303j
            handleGameUserInput();
        }

        // loc_48DCD:              ; CODE XREF: runLevel+2FCj
        if (gIsFlashingBackgroundModeEnabled != 0)
        {
            replaceCurrentPaletteColor(0, (Color){0, 0, 0});
        }

        // noFlashing5:              ; CODE XREF: runLevel+317j
        if (gShouldExitGame != 0)
        {
            break;
        }
        gFrameCounter++;
        if (gShouldExitLevel == 1)
        {
            break;
        }
        if (gQuitLevelCountdown == 0) // 01ED:218D
        {
            continue;
        }

        // loc_48DFA:              ; CODE XREF: runLevel+33Aj
        //  01ED:2197
        gQuitLevelCountdown--;
        if (gQuitLevelCountdown == 0)
        {
            break;
        }
    } while (1);

    // loc_48E03:              ; CODE XREF: runLevel+328j
    //                 ; runLevel+333j ...
    if (gIsRecordingDemo != 0)
    {
        stopRecordingDemo();
    }

    // loc_48E13:              ; CODE XREF: runLevel+353j
    uint8_t userDidNotCheat = (gHasUserCheated == 0);
    gHasUserCheated = 0;
    if (userDidNotCheat && gShouldUpdateTotalLevelTime != 0 && byte_5A323 == 0)
    {
        addCurrentGameTimeToPlayer();
    }

    // loc_48E30:              ; CODE XREF: runLevel+362j
    //                 ; runLevel+369j ...
    gIsMoveScrollModeEnabled = 0;
    gAdditionalScrollOffsetX = 0;
    gAdditionalScrollOffsetY = 0;
    gIsFlashingBackgroundModeEnabled = 0;
    gDebugExtraRenderDelay = 1;
    replaceCurrentPaletteColor(0, (Color){0, 0, 0});
}

void updateUserInputInScrollMovementMode() // sub_4914A   proc near       ; CODE XREF: handleGameUserInput+7p
{
    if (isLeftButtonPressed())
    {
        gAdditionalScrollOffsetX--;
        gAdditionalScrollOffsetX--;
    }

    // loc_49159:              ; CODE XREF: updateUserInputInScrollMovementMode+5j
    if (isRightButtonPressed())
    {
        gAdditionalScrollOffsetX++;
        gAdditionalScrollOffsetX++;
    }

    // loc_49168:              ; CODE XREF: updateUserInputInScrollMovementMode+14j
    if (isUpButtonPressed())
    {
        gAdditionalScrollOffsetY--;
        gAdditionalScrollOffsetY--;
    }

    // loc_49177:              ; CODE XREF: updateUserInputInScrollMovementMode+23j
    if (isDownButtonPressed())
    {
        gAdditionalScrollOffsetY++;
        gAdditionalScrollOffsetY++;
    }

    // loc_49186:              ; CODE XREF: updateUserInputInScrollMovementMode+32j
    if (gIsNumpad5Pressed != 0)
    {
        gAdditionalScrollOffsetX = 0;
        gAdditionalScrollOffsetY = 0;
    }

    // loc_49199:              ; CODE XREF: updateUserInputInScrollMovementMode+41j
    if (gIsInsertKeyPressed != 0)
    {
        gAdditionalScrollOffsetX = -gMurphyScrollOffsetX;
        gAdditionalScrollOffsetY = -gMurphyScrollOffsetY;
    }

    // loc_491B3:              ; CODE XREF: updateUserInputInScrollMovementMode+60j
    if (gIsHomeKeyPressed != 0)
    {
        gAdditionalScrollOffsetX = (kLevelBitmapWidth / 2) - gMurphyScrollOffsetX;
        gAdditionalScrollOffsetY = -gMurphyScrollOffsetY;
    }

    // loc_491C5:              ; CODE XREF: updateUserInputInScrollMovementMode+72j
    if (gIsRePagKeyPressed != 0)
    {
        gAdditionalScrollOffsetX = kLevelBitmapWidth - gMurphyScrollOffsetX;
        gAdditionalScrollOffsetY = -gMurphyScrollOffsetY;
    }

    // loc_491E8:              ; CODE XREF: updateUserInputInScrollMovementMode+99j
    if (gIsDelKeyPressed != 0)
    {
        gAdditionalScrollOffsetX = -gMurphyScrollOffsetX;
        gAdditionalScrollOffsetY = kLevelBitmapHeight - gMurphyScrollOffsetY;
    }

    // loc_491F6:              ; CODE XREF: updateUserInputInScrollMovementMode+A3j
    if (gIsEndKeyPressed != 0)
    {
        gAdditionalScrollOffsetX = (kLevelBitmapWidth / 2) - gMurphyScrollOffsetX;
        gAdditionalScrollOffsetY = kLevelBitmapHeight - gMurphyScrollOffsetY;
    }

    // loc_49208:              ; CODE XREF: updateUserInputInScrollMovementMode+B5j
    if (gIsAvPagKeyPressed != 0)
    {
        gAdditionalScrollOffsetX = kLevelBitmapWidth - gMurphyScrollOffsetX;
        gAdditionalScrollOffsetY = kLevelBitmapHeight - gMurphyScrollOffsetY;
    }
}

void simulateDemoInput() // sub_492A8   proc near       ; CODE XREF: handleGameUserInput+27p
                         // ; restartLevel+76p
{
    // 01ED:2645
    if (gDemoCurrentInputRepeatCounter > 1)
    {
        gDemoCurrentInputRepeatCounter--;
        return;
    }

    // loc_492B3:              ; CODE XREF: simulateDemoInput+5j
    uint8_t newInput = gDemos.demoData[gDemoCurrentInputIndex];

    if (newInput == 0xFF)
    {
        gQuitLevelCountdown = 0x64;
        gShouldExitLevel = 1;
    }
    else
    {
        gDemoCurrentInputIndex++;
    }

    // loc_492CA:              ; CODE XREF: simulateDemoInput+47j
    gCurrentUserInput = newInput & 0xF;
    gDemoCurrentInputRepeatCounter = (newInput >> 4) + 1;
}

void saveInputForDemo() // sub_492F1   proc near       ; CODE XREF: handleGameUserInput+1Dp
{
    gDemoCurrentInputRepeatCounter++;

    if (gDemoCurrentInputRepeatCounter == 0xFF)
    {
        gDemoCurrentInput = gCurrentUserInput;
        gDemoRecordingRandomGeneratorSeed = gRandomGeneratorSeed;
        gDemoRecordingRandomGeneratorSeedLow = (gDemoRecordingRandomGeneratorSeed >> 8);    // ah;
        gDemoRecordingRandomGeneratorSeedHigh = (gDemoRecordingRandomGeneratorSeed & 0xFF); // al;
    }

    // loc_49311:              ; CODE XREF: saveInputForDemo+Dj
    if (gDemoCurrentInput == gCurrentUserInput && gDemoCurrentInputRepeatCounter != 0xF)
    {
        return;
    }

    // loc_4931E:              ; CODE XREF: saveInputForDemo+24j
    gDemoCurrentInput = (gDemoCurrentInput | (gDemoCurrentInputRepeatCounter << 4));

    gDemoRecordingRandomGeneratorSeedHigh += gDemoCurrentInputRepeatCounter;
    gDemoRecordingRandomGeneratorSeedHigh++;

    fileWriteUInt8(gDemoCurrentInput, gCurrentRecordingDemoFile);
    gDemoCurrentInputRepeatCounter = 0xFF;
    gDemoCurrentInput = gCurrentUserInput;
}

void stopRecordingDemo() // somethingspsig  proc near       ; CODE XREF: runLevel+355p
                         // ; recordDemo+30p ...
{
    uint8_t scrambleSpeedLow = (gDemoRecordingLowestSpeed ^ gDemoRecordingRandomGeneratorSeedLow);
    uint8_t scrambleSpeedHigh = (gDemoRecordingLowestSpeed ^ gDemoRecordingRandomGeneratorSeedHigh);
    uint16_t scrambleSpeed = ((scrambleSpeedHigh << 8) | scrambleSpeedLow);

    fseek(gCurrentRecordingDemoFile, 1532, SEEK_SET);
    fileWriteUInt16(scrambleSpeed, gCurrentRecordingDemoFile);

    fileWriteUInt16(gDemoRecordingRandomGeneratorSeed, gCurrentRecordingDemoFile);

    fseek(gCurrentRecordingDemoFile, 0, SEEK_END);

    gDemoCurrentInput = 0xFF;

    fileWriteUInt8(gDemoCurrentInput, gCurrentRecordingDemoFile);
    if (byte_5A19B != 0)
    {
        FILE *sigFile = openWritableFileWithReadonlyFallback("MYSPSIG.TXT", "rb");
        if (sigFile != NULL)
        {
            if (fseek(sigFile, 0, SEEK_END) == 0)
            {
                long sigFileSize = ftell(sigFile);
                sigFileSize = MIN(sigFileSize, kMaxDemoSignatureLength);

                if (sigFileSize > 0)
                {
                    // loc_493EB:              ; CODE XREF: stopRecordingDemo+85j
                    //                 ; stopRecordingDemo+8Dj
                    if (fseek(sigFile, 0, SEEK_SET) == 0)
                    {
                        uint8_t signature[kMaxDemoSignatureLength + 1];
                        size_t bytes = fileReadBytes(signature, sigFileSize, sigFile);

                        if (bytes == sigFileSize)
                        {
                            int idx = 0;
                            for (idx = 0; idx < sigFileSize; ++idx)
                            {
                                if (signature[idx] == 0xFF)
                                {
                                    break;
                                }
                            }

                            // Make sure the signature is terminated with 0xFF
                            signature[idx] = 0xFF;
                            sigFileSize = idx;

                            // loc_4941C:              ; CODE XREF: stopRecordingDemo+BCj
                            fileWriteBytes(signature, sigFileSize + 1, gCurrentRecordingDemoFile);
                        }
                    }
                }
            }

            // loc_49430:              ; CODE XREF: stopRecordingDemo+7Ej
            //                 ; stopRecordingDemo+89j ...
            fclose(sigFile);
        }
    }
    // loc_49435:              ; CODE XREF: stopRecordingDemo+65j
    //                 ; stopRecordingDemo+6Fj
    fclose(gCurrentRecordingDemoFile);
    gIsRecordingDemo = 0;
    if (gHasUserInterruptedDemo != 0)
    {
        gIsPlayingDemo = 1;
    }

    // loc_4944F:              ; CODE XREF: stopRecordingDemo+EEj
    drawGamePanelText();
    gIsGameBusy = 1;
    gIsPlayingDemo = 0;
}

size_t writeCurrentLevelToFile(FILE *file)
{
    size_t bytes = fileWriteBytes(gCurrentLevel.tiles, sizeof(gCurrentLevel.tiles), file);

    bytes += fileWriteBytes(gCurrentLevel.unused, sizeof(gCurrentLevel.unused), file);
    bytes += fileWriteUInt8(gCurrentLevel.initialGravitation, file);
    bytes += fileWriteUInt8(gCurrentLevel.speedFixMagicNumber, file);
    bytes += fileWriteBytes(gCurrentLevel.name, sizeof(gCurrentLevel.name), file);
    bytes += fileWriteUInt8(gCurrentLevel.freezeZonks, file);
    bytes += fileWriteUInt8(gCurrentLevel.numberOfInfotrons, file);
    bytes += fileWriteUInt8(gCurrentLevel.numberOfSpecialPorts, file);
    for (int idx = 0; idx < kLevelMaxNumberOfSpecialPorts; ++idx)
    {
        bytes += fileWriteUInt16(gCurrentLevel.specialPortsInfo[idx].position, file);
        bytes += fileWriteUInt8(gCurrentLevel.specialPortsInfo[idx].gravity, file);
        bytes += fileWriteUInt8(gCurrentLevel.specialPortsInfo[idx].freezeZonks, file);
        bytes += fileWriteUInt8(gCurrentLevel.specialPortsInfo[idx].freezeEnemies, file);
        bytes += fileWriteUInt8(gCurrentLevel.specialPortsInfo[idx].unused, file);
    }
    bytes += fileWriteUInt8(gCurrentLevel.scrambledSpeed, file);
    bytes += fileWriteUInt8(gCurrentLevel.scrambledChecksum, file);
    bytes += fileWriteUInt16(gCurrentLevel.randomSeed, file);

    return bytes;
}

void recordDemo(uint16_t demoIndex) // sub_4945D   proc near       ; CODE XREF: handleGameUserInput+294p
                                    // ; handleGameUserInput+2A4p ...
{
    // 01ED:27FA
    gIsMoveScrollModeEnabled = 0;
    gAdditionalScrollOffsetX = 0;
    gAdditionalScrollOffsetY = 0;
    gIsFlashingBackgroundModeEnabled = 0;
    gDebugExtraRenderDelay = 1;

    replaceCurrentPaletteColor(0, (Color){0, 0, 0});

    if (gIsRecordingDemo != 0)
    {
        stopRecordingDemo();
    }

    // loc_49490:              ; CODE XREF: recordDemo+2Ej
    char demoIndexCharacter = '0' + demoIndex;
    gDemo0BinFilename[4] = demoIndexCharacter;

    char *filename = gDemo0BinFilename;

    if (supportsSPFileDemoPlayback() && (gShouldRecordWithOriginalDemoFilenames & 0xFF) == 0) // cmp byte ptr gShouldRecordWithOriginalDemoFilenames, 0
    {
        gSPDemoFileName[7] = demoIndexCharacter;
        filename = gSPDemoFileName;
    }

    // loc_494A6:              ; CODE XREF: recordDemo+41j
    gRecordingDemoMessage[18] = demoIndexCharacter;

    FILE *file = openWritableFile(filename, "wb");
    if (file == NULL)
    {
        return;
    }

    // loc_494B8:              ; CODE XREF: recordDemo+56j
    gCurrentRecordingDemoFile = file; // file handle
    gCurrentLevel.speedFixMagicNumber = 0x20 + kGameVersion;
    // TODO: don't know for sure but this probably is related to adjusting the demo time with the speed or something?
    // bl = speed3;
    // cl = 4;
    // bl = bl << cl;
    // bl |= gGameSpeed;
    // speed2 = bl;
    gDemoRecordingLowestSpeed = gGameSpeed;

    size_t bytes = writeCurrentLevelToFile(file);
    if (bytes != kLevelDataLength)
    {
        return;
    }

    // The original code sets index | 0x80 in demoCurrentInputRepeatCounter and then writes it to the file
    // but seems to be useless because that value is overriden later.
    //
    uint8_t levelNumber = gCurrentSelectedLevelIndex | 0x80;
    bytes = fileWriteUInt8(levelNumber, file);
    if (bytes < 1)
    {
        return;
    }
    gDemoCurrentInput = UserInputNone;
    gDemoRecordingJustStarted = 1;
    gIsPlayingDemo = 0;
    gDemoCurrentInputRepeatCounter = 0xFE; // 254
    gDebugExtraRenderDelay = 1;
    if (gIsSPDemoAvailableToRun == 0)
    {
        memcpy(gCurrentDemoLevelName, gCurrentLevelName, 3);
    }

    // loc_4952A:              ; CODE XREF: recordDemo+BCj
    gIsRecordingDemo = 1;
    if (gHasUserInterruptedDemo != 0)
    {
        gIsPlayingDemo = 1;
    }

    // loc_4953B:              ; CODE XREF: recordDemo+D7j
    fetchAndInitializeLevel();
    gIsPlayingDemo = 0;
}

void prepareDemoRecordingFilename() // sub_49544  proc near       ; CODE XREF: start+3A1p
                                    // ; handleOkButtonClick:loc_4B40Fp ...
{
    // 01ED:28E1

    char currentSuffix[3] = "AT";
    strcpy(currentSuffix, &gLevelsDatFilename[8]);

    // Checks if the last two chars are "00" like LEVELS.D00?
    if (strcmp(currentSuffix, "00") == 0)
    {
        // replaces the content with "--"
        strcpy(currentSuffix, "--");
    }

    // loc_4954F:             // ; CODE XREF: prepareDemoRecordingFilename+6j
    //  Now checks if the last two chars are "AT" like LEVELS.DAT?
    if (strcmp(currentSuffix, "AT") == 0)
    {
        // replaces the content with "00"
        strcpy(currentSuffix, "00");
    }

    // loc_49557:             // ; CODE XREF: prepareDemoRecordingFilename+Ej
    memcpy(gSPDemoFileName, currentSuffix, 2);
}

void handleGameUserInput() // sub_4955B   proc near       ; CODE XREF: runLevel:loc_48B6Bp
                           // ; runLevel+30Cp
{
    // 01ED:28F8

    if (gIsMoveScrollModeEnabled != 0)
    {
        updateUserInputInScrollMovementMode(); // 01ED:28FF
    }
    // loc_49567:              ; CODE XREF: handleGameUserInput+5j
    else if (gIsPlayingDemo == 0)
    {
        updateUserInput(); // 01ED:290B
        if (gIsRecordingDemo != 0)
        {
            saveInputForDemo(); // 01ED:2915
        }
    }

    // loc_4957B:              ; CODE XREF: handleGameUserInput+Aj
    //                 ; handleGameUserInput+11j ...
    if (gIsPlayingDemo != 0)
    {
        simulateDemoInput(); // 01ED:2929
    }

    // loc_4958F:              ; CODE XREF: handleGameUserInput+2Fj
    if (gToggleGamePanelKeyAutoRepeatCounter != 0) // cmp byte ptr word_510C1+1, 0
    {
        // 01ED:293E
        gToggleGamePanelKeyAutoRepeatCounter--;
    }

    // loc_4959A:              ; CODE XREF: handleGameUserInput+39j
    if (isToggleGamePanelButtonPressed() == 0)
    {
        gToggleGamePanelKeyAutoRepeatCounter = 0; // mov byte ptr word_510C1+1, 0
    }
    // loc_495A9:              ; CODE XREF: handleGameUserInput+44j
    else if (gToggleGamePanelKeyAutoRepeatCounter == 0) // 01ED:2946
    {
        // loc_495B3:              ; CODE XREF: handleGameUserInput+53j
        gToggleGamePanelKeyAutoRepeatCounter = 0x20; // mov byte ptr word_510C1+1, 20h ; ' '
        if (gShouldShowGamePanel != 0)
        {
            gShouldShowGamePanel = 0; // mov byte ptr word_510C1, 0
            gCurrentPanelHeight = 0;
        }
        else
        {
            // loc_495FB:              ; CODE XREF: handleGameUserInput+62j
            gShouldShowGamePanel = 1; // mov byte ptr word_510C1, 1
            gCurrentPanelHeight = kPanelBitmapHeight;
        }
    }

    // loc_49635:              ; CODE XREF: handleGameUserInput+4Bj
    //                 ; handleGameUserInput+55j ...
    if (gIsDebugModeEnabled != 1)
    {
        checkDebugKeys();
        return;
    }

    // loc_4963F:              ; CODE XREF: handleGameUserInput+DFj
    if (gIsRecordingDemo == 0) // 01ED:29DC
    {
        // loc_49649:              ; CODE XREF: handleGameUserInput+E9j
        if (gIsMKeyPressed != 0) // cmp byte ptr gIsMKeyPressed, 0
        {
            gIsMoveScrollModeEnabled = 1;
        }

        // loc_49656:              ; CODE XREF: handleGameUserInput+F3j
        if (gIsDKeyPressed != 0)
        {
            gIsFlashingBackgroundModeEnabled = 1;
        }

        // loc_49663:              ; CODE XREF: handleGameUserInput+100j
        if (gIsZKeyPressed != 0) // cmp byte ptr gIsZKeyPressed, 0
        {
            // 01ED:2A07
            removeTiles(LevelTileTypeZonk);
        }

        // loc_4966F:              ; CODE XREF: handleGameUserInput+10Dj
        if (gIsBKeyPressed != 0) // cmp byte ptr gIsBKeyPressed, 0
        {
            removeTiles(LevelTileTypeBase);
        }

        // loc_4967B:              ; CODE XREF: handleGameUserInput+119j
        if (gIsHKeyPressed != 0)
        {
            removeTiles(LevelTileTypeHardware);
        }

        // loc_49687:              ; CODE XREF: handleGameUserInput+125j
        if (gIsCKeyPressed != 0) // cmp byte ptr gIsCKeyPressed, 0
        {
            removeTiles(LevelTileTypeChip);
        }

        // loc_49693:              ; CODE XREF: handleGameUserInput+131j
        if (gIsSKeyPressed != 0)
        {
            removeTiles(LevelTileTypeSnikSnak);
        }

        // loc_4969F:              ; CODE XREF: handleGameUserInput+13Dj
        if (gIsRKeyPressed != 0)
        {
            videoLoop();
            restartLevel();
        }

        // loc_496AC:              ; CODE XREF: handleGameUserInput+149j
        if (gIsPlayingDemo == 0)
        {
            // loc_496C0:              ; CODE XREF: handleGameUserInput+160j
            if (gIs1KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 1;
            }

            // loc_496CD:              ; CODE XREF: handleGameUserInput+16Aj
            if (gIs2KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 2;
            }

            // loc_496DA:              ; CODE XREF: handleGameUserInput+177j
            if (gIs3KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 3;
            }

            // loc_496E7:              ; CODE XREF: handleGameUserInput+184j
            if (gIs4KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 4;
            }

            // loc_496F4:              ; CODE XREF: handleGameUserInput+191j
            if (gIs5KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 6;
            }

            // loc_49701:              ; CODE XREF: handleGameUserInput+19Ej
            if (gIs6KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 8;
            }

            // loc_4970E:              ; CODE XREF: handleGameUserInput+1ABj
            if (gIs7KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 0xC;
            }

            // loc_4971B:              ; CODE XREF: handleGameUserInput+1B8j
            if (gIs8KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 0x10;
            }

            // loc_49728:              ; CODE XREF: handleGameUserInput+1C5j
            if (gIs9KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 0x18;
            }

            // loc_49735:              ; CODE XREF: handleGameUserInput+1D2j
            if (gIs0KeyPressed != 0)
            {
                gDebugExtraRenderDelay = 0x20;
            }
        }
    }

    // loc_49742:              ; CODE XREF: handleGameUserInput+EBj
    //                 ; handleGameUserInput+158j ...
    //  01ED:2ADF
    if (gIsLeftControlPressed == 1)
    {
        // loc_497D1:              ; CODE XREF: handleGameUserInput+1EEj
        if (gIsPlayingDemo != 0)
        {
            loc_4988E();
            return;
        }
        // loc_497DB:              ; CODE XREF: handleGameUserInput+27Bj
        //  TODO: this if is only relevant when the game is autoadjusting the speed (speed3 != 0), right?
        //        else if (speed3 < 0)
        //        {
        //            loc_4988E();
        //            return;
        //        }
        // loc_497E5:              ; CODE XREF: handleGameUserInput+285j
        else if (gIsF1KeyPressed == 1)
        {
            // 01ED:2B89
            recordDemo(0);
        }
        // loc_497F5:              ; CODE XREF: handleGameUserInput+28Fj
        else if (gIsF2KeyPressed == 1)
        {
            // 01ED:2B99
            recordDemo(1);
        }
        // loc_49805:              ; CODE XREF: handleGameUserInput+29Fj
        else if (gIsF3KeyPressed == 1)
        {
            recordDemo(2);
        }
        // loc_49814:              ; CODE XREF: handleGameUserInput+2AFj
        else if (gIsF4KeyPressed == 1)
        {
            recordDemo(3);
        }
        // loc_49823:              ; CODE XREF: handleGameUserInput+2BEj
        else if (gIsF5KeyPressed == 1)
        {
            recordDemo(4);
        }
        // loc_49832:              ; CODE XREF: handleGameUserInput+2CDj
        else if (gIsF6KeyPressed == 1)
        {
            recordDemo(5);
        }
        // loc_49841:              ; CODE XREF: handleGameUserInput+2DCj
        else if (gIsF7KeyPressed == 1)
        {
            recordDemo(6);
        }
        // loc_49850:              ; CODE XREF: handleGameUserInput+2EBj
        else if (gIsF8KeyPressed == 1)
        {
            recordDemo(7);
        }
        // loc_4985F:              ; CODE XREF: handleGameUserInput+2FAj
        else if (gIsF9KeyPressed == 1)
        {
            recordDemo(8);
        }
        // loc_4986E:              ; CODE XREF: handleGameUserInput+309j
        else if (gIsF10KeyPressed == 1)
        {
            recordDemo(9);
        }
        // loc_4987D:              ; CODE XREF: handleGameUserInput+318j
        else if (gIsF12KeyPressed == 1 && gIsRecordingDemo != 0)
        {
            stopRecordingDemo();
        }
    }
    else
    {
        // loc_4974C:              ; CODE XREF: handleGameUserInput+1ECj
        if ((gFrameCounter & 7) == 0 && gIsRecordingDemo == 0)
        {
            // loc_49761:              ; CODE XREF: handleGameUserInput+201j
            if (gIsF1KeyPressed == 0)
            {
                gToggleGravityAutorepeatFlag = 0;
            }
            // loc_4976F:              ; CODE XREF: handleGameUserInput+20Bj
            else if (gToggleGravityAutorepeatFlag == 0)
            {
                gToggleGravityAutorepeatFlag--;
                gIsGravityEnabled &= 1;
                gIsGravityEnabled = gIsGravityEnabled ^ 1;
            }

            if (gIsF1KeyPressed == 0 || gToggleGravityAutorepeatFlag != 0)
            {
                // loc_49786:              ; CODE XREF: handleGameUserInput+212j
                //                 ; handleGameUserInput+219j
                if (gIsF2KeyPressed == 0)
                {
                    gToggleZonksFrozenAutorepeatFlag = 0;
                }
                // loc_49794:              ; CODE XREF: handleGameUserInput+230j
                else if (gToggleZonksFrozenAutorepeatFlag == 0)
                {
                    gToggleZonksFrozenAutorepeatFlag--;
                    gAreZonksFrozen &= 2;
                    gAreZonksFrozen = gAreZonksFrozen ^ 2;
                }

                if (gIsF2KeyPressed == 0 || gToggleZonksFrozenAutorepeatFlag != 0)
                {
                    // loc_497AB:              ; CODE XREF: handleGameUserInput+237j
                    //                 ; handleGameUserInput+23Ej
                    if (gIsF3KeyPressed == 0)
                    {
                        gToggleEnemiesFrozenAutorepeatFlag = 0;
                    }
                    // loc_497B9:              ; CODE XREF: handleGameUserInput+255j
                    else if (gToggleEnemiesFrozenAutorepeatFlag == 0)
                    {
                        gToggleEnemiesFrozenAutorepeatFlag--;
                        gAreEnemiesFrozen &= 1;
                        gAreEnemiesFrozen = gAreEnemiesFrozen ^ 1;
                    }
                }
            }
        }
    }

    loc_4988E();
}

void loc_4988E() // :              ; CODE XREF: handleGameUserInput+1F9j
{
    //                ; handleGameUserInput+203j ...
    if (gIsRecordingDemo != 0 || gIsPlayingDemo != 0)
    {
        checkDebugKeys();
        return;
    }

    // loc_498A2:              ; CODE XREF: handleGameUserInput+342j
    if (gIsMinusKeyPressed == 0)
    {
        gDebugSkipPreviousLevelAutorepeatFlag_1 = 0;
        gDebugSkipPreviousLevelAutorepeatFlag_2 = 5;
    }
    // loc_498B5:              ; CODE XREF: handleGameUserInput+34Cj
    else if (gDebugSkipPreviousLevelAutorepeatFlag_1 != 0)
    {
        gDebugSkipPreviousLevelAutorepeatFlag_1--;
    }
    else
    {
        // loc_498C2:              ; CODE XREF: handleGameUserInput+35Fj
        if (gDebugSkipPreviousLevelAutorepeatFlag_2 != 0)
        {
            gDebugSkipPreviousLevelAutorepeatFlag_2--;
            gDebugSkipPreviousLevelAutorepeatFlag_1 = 0x10;
        }

        // loc_498D2:              ; CODE XREF: handleGameUserInput+36Cj
        if (gCurrentSelectedLevelIndex <= 1)
        {
            gCurrentSelectedLevelIndex = 2;
        }

        // loc_498DF:              ; CODE XREF: handleGameUserInput+37Cj
        gCurrentSelectedLevelIndex--;
        if (gCurrentSelectedLevelIndex > kNumberOfLevels)
        {
            gCurrentSelectedLevelIndex = kNumberOfLevels;
        }

        // loc_498F0:              ; CODE XREF: handleGameUserInput+38Dj
        //  ax = gCurrentSelectedLevelIndex;
        convertLevelNumberTo3DigitStringWithPadding0(gCurrentSelectedLevelIndex);
        drawLevelList();
        debugSkipLevel();
    }

    // loc_498FC:              ; CODE XREF: handleGameUserInput+358j
    //                 ; handleGameUserInput+365j
    if (gIsEqualsKeyPressed == 0)
    {
        gDebugSkipNextLevelAutorepeatFlag_1 = 0;
        gDebugSkipNextLevelAutorepeatFlag_2 = 5;
    }
    // loc_4990F:              ; CODE XREF: handleGameUserInput+3A6j
    else if (gDebugSkipNextLevelAutorepeatFlag_1 != 0)
    {
        gDebugSkipNextLevelAutorepeatFlag_1--;
    }
    else
    {
        // loc_4991C:              ; CODE XREF: handleGameUserInput+3B9j
        if (gDebugSkipNextLevelAutorepeatFlag_2 != 0)
        {
            gDebugSkipNextLevelAutorepeatFlag_2--;
            gDebugSkipNextLevelAutorepeatFlag_1 = 0x10; // 16
        }

        // loc_4992C:              ; CODE XREF: handleGameUserInput+3C6j
        if (gCurrentSelectedLevelIndex >= kNumberOfLevels)
        {
            gCurrentSelectedLevelIndex = kNumberOfLevels - 1;
        }

        // loc_49939:              ; CODE XREF: handleGameUserInput+3D6j
        gCurrentSelectedLevelIndex++;
        convertLevelNumberTo3DigitStringWithPadding0(gCurrentSelectedLevelIndex); // 01ED:2CDD
        drawLevelList();
        debugSkipLevel();
    }

    checkDebugKeys();
}

void stopDemoAndPlay()
{
    gIsPlayingDemo = 0;
    gShouldUpdateTotalLevelTime = 0;
    gHasUserCheated = 1;
    gHasUserInterruptedDemo = 1;
}

void checkDebugKeys() // loc_49949:              ; CODE XREF: handleGameUserInput+E1j
//                ; handleGameUserInput+33Aj ...
{
    // 01ED:2CE6

    uint8_t shouldStartFromSavedSnapshot = (gShouldStartFromSavedSnapshot != 0);
    gShouldStartFromSavedSnapshot = 0;
    if (shouldStartFromSavedSnapshot)
    {
        loadGameSnapshot();
        return;
    }

    // loc_49958:              ; CODE XREF: handleGameUserInput+3F8j
    if (gIsLeftControlPressed != 1)
    {
        loc_49C41();
        return;
    }

    // loc_49962:              ; CODE XREF: handleGameUserInput+402j
    //  Control + F12: Interrupt demo and continue playing
    if (gIsF12KeyPressed == 1 && gIsPlayingDemo != 0)
    {
        stopDemoAndPlay();
    }

    // loc_49984:              ; CODE XREF: handleGameUserInput+40Cj
    //                 ; handleGameUserInput+413j
    if (gIsScrollLockPressed == 1)
    {
        gIsDebugModeEnabled = 1;
        drawTextWithChars8FontToGamePanel(304, 14, 6, "DB"); // Debug mode enabled
        gAdditionalInfoInGamePanelFrameCounter = 0x46;       // 70
    }

    // loc_499AA:              ; CODE XREF: handleGameUserInput+42Ej
    //                 ; handleGameUserInput+43Bj
    if (gIsWKeyPressed != 1)
    {
        // loc_49A7F:              ; CODE XREF: handleGameUserInput+456j
        if (gIsLKeyPressed != 1)
        {
            loc_49C41();
            return;
        }

        loadGameSnapshot();
        return;
    }

    saveGameSnapshot();
}

void saveGameSnapshot() // loc_499C8:              ; CODE XREF: handleGameUserInput+454j
{
    gShouldCloseAdvancedMenu = 1;

    if (saveGameState() != 0)
    {
        showSavegameOperationError();
        return;
    }

    // loc_49A78:              ; CODE XREF: handleGameUserInput+518j
    loc_49C2C("WR"); // Means snapshot saved with no issues
}

void loadGameSnapshot() // loc_49A89:              ; CODE XREF: handleGameUserInput+3FAj
{
    gShouldCloseAdvancedMenu = 1;

    if (canLoadGameState() == 0)
    {
        showSavegameOperationError();
        return;
    }

    // loc_49A96:              ; CODE XREF: handleGameUserInput+536j
    if (gIsRecordingDemo != 0)
    {
        stopRecordingDemo();
    }

    gIsRecordingDemo = 0;

    if (loadGameState() != 0)
    {
        showSavegameOperationError();
        return;
    }

    forceRestoreOriginalFancyTiles();

    // loc_49B84:              ; CODE XREF: handleGameUserInput+619j
    //                     ; handleGameUserInput+624j
    gIsPlayingDemo = 0;
    gIsRecordingDemo = 0;
    gCurrentUserInput = UserInputNone;
    gIsMoveScrollModeEnabled = 0;
    gAdditionalScrollOffsetX = 0;
    gAdditionalScrollOffsetY = 0;
    gIsFlashingBackgroundModeEnabled = 0;
    gDebugExtraRenderDelay = 1;
    replaceCurrentPaletteColor(0, (Color){0, 0, 0});
    generateRandomSeedFromClock();
    generateRandomNumber();
    // I commented out all these video transitions because they're not needed in the reimplementation. They were here
    // just to prevent graphical glitches. Also they made loading much slower.
    //
    // setPalette(gBlackPalette);
    // videoLoop();
    drawFixedLevel();
    drawGamePanel();
    convertToEasyTiles();
    scrollToMurphy();
    gLastDrawnMinutesAndSeconds = 0xFFFF;
    gLastDrawnHours = 0xFF;
    drawGameTime();
    gHasUserCheated = 1;
    gIsRecordingDemo = 0;

    drawTextWithChars8FontToGamePanel(304, 14, 6, "LD"); // Means snapshot was loaded successfully
    gAdditionalInfoInGamePanelFrameCounter = 0x46;       // 70 or '&'

    gCurrentPanelHeight = (gShouldShowGamePanel ? kPanelBitmapHeight : 0);
    drawCurrentLevelViewport(gCurrentPanelHeight);
    // videoLoop();

    // loc_49C12:              ; CODE XREF: handleGameUserInput+6A8j
    //  fadeToPalette(gGamePalette);

    // loc_49C40:              ; CODE XREF: handleGameUserInput+6BDj
    //                     ; handleGameUserInput+6D6j
    loc_49C41();
}

void showSavegameOperationError() // loc_49C28:              ; CODE XREF: handleGameUserInput+47Aj
{
    //                    ; handleGameUserInput+51Aj ...
    //    push    si
    //    mov si, 0A007h "XX"
    loc_49C2C("XX"); // Means problem writing/loading snapshot
}

void loc_49C2C(char text[3]) // :              ; CODE XREF: handleGameUserInput+521j
{
    drawTextWithChars8FontToGamePanel(304, 14, 6, text);
    gAdditionalInfoInGamePanelFrameCounter = 0x46; // 70 or '&'

    // loc_49C40:              ; CODE XREF: handleGameUserInput+6BDj
    //                     ; handleGameUserInput+6D6j
    //     pop si

    loc_49C41();
}

void loc_49C41() //              ; CODE XREF: handleGameUserInput+404j
//                    ; handleGameUserInput+52Bj
{
    if (gIsLeftAltPressed == 1 && gIsScrollLockPressed == 1)
    {
        // 01ED:2FEC
        gIsDebugModeEnabled = 0;
        gIsMoveScrollModeEnabled = 0;
        gAdditionalScrollOffsetX = 0;
        gAdditionalScrollOffsetY = 0;
        gIsFlashingBackgroundModeEnabled = 0;
        gDebugExtraRenderDelay = 1;
        replaceCurrentPaletteColor(0, (Color){0, 0, 0});

        drawTextWithChars8FontToGamePanel(304, 14, 6, "--"); // Debug mode disabled
        gAdditionalInfoInGamePanelFrameCounter = 0x46;       // 70 or '&'
    }

    // loc_49C96:              ; CODE XREF: handleGameUserInput+6EBj
    //                     ; handleGameUserInput+6F2j ...
    if (isPauseButtonPressed())
    {
        // 01ED:303A
        gIsGameRunning = 0;
        runAdvancedOptionsRootMenu();
        gIsGameRunning = 1;
    }

    // loc_49CC8:              ; CODE XREF: handleGameUserInput+740j
    if (gIsNumLockPressed != 0)
    {
        // 01ED:306C
        gIsGameRunning = 0;
        //        mov si, 6095h
        fadeToPalette(gGameDimmedPalette);

        do
        {
            // loc_49CDA:              ; CODE XREF: handleGameUserInput+784j
            int9handler(1);
        } while (gIsNumLockPressed == 1);

        // From the speed fix mod, but in uppercase so I can use characterForLastKeyPressed
        static const char kMagicDisableDebugModeCode[] = "CANT STO";
        uint8_t index = 0;

        do
        {
            // loc_49CE4:              ; CODE XREF: handleGameUserInput+7A6j
            int9handler(1);

            if (index >= strlen(kMagicDisableDebugModeCode))
            {
                gIsDebugModeEnabled = 0;
                break;
            }
            else
            {
                // loc_49CF3:              ; CODE XREF: handleGameUserInput+78Ej
                if (characterForLastKeyPressed() == kMagicDisableDebugModeCode[index])
                {
                    index++;
                }

                // loc_49CFC:              ; CODE XREF: handleGameUserInput+79Dj
                if (gIsNumLockPressed != 0)
                {
                    break;
                }
            }
        } while (1);

        do
        {
            // loc_49D03:              ; CODE XREF: handleGameUserInput+796j
            //                     ; handleGameUserInput+7ADj
            int9handler(1);
        } while (gIsNumLockPressed == 1);
        //        mov si, 6015h
        fadeToPalette(gGamePalette);
        gIsGameRunning = 1;
    }

    // loc_49D15:              ; CODE XREF: handleGameUserInput+772j
    if (isExitLevelButtonPressed() // Select/Back/- controller button -> exit game
        && gQuitLevelCountdown <= 0)
    {
        // This is called when I press ESC to exit the game, but not when I die
        gShouldKillMurphy = 1; // 01ED:30C0
    }

    // loc_49D29:              ; CODE XREF: handleGameUserInput+7BFj
    //                     ; handleGameUserInput+7C6j
    if (gIsQKeyPressed != 0 || getGameControllerCancelButton())
    {
        // 01ED:30CD
        gIsMoveScrollModeEnabled = 0;
        gAdditionalScrollOffsetX = 0;
        gAdditionalScrollOffsetY = 0;
        gIsFlashingBackgroundModeEnabled = 0;
    }

    // loc_49D48:              ; CODE XREF: handleGameUserInput+7D3j
    if (isShowNumberOfRedDisksButtonPressed())
    {
        drawNumberOfRemainingRedDisks(); // 01ED:30EC
    }
}

void forceRestoreOriginalFancyTiles() // sub_49D53   proc near       ; CODE XREF: handleGameUserInput+626p
                                      // ; removeTiles+21p
{
    // 01ED:30F0
    gIsShowingFancyTiles = 0;
    restoreOriginalFancyTiles();
}

void restoreOriginalFancyTiles() //   proc near       ; CODE XREF: runLevel+A7p
{
    // 01ED:30F5

    for (int i = 0; i < kLevelSize; ++i)
    {
        // loc_49D65:              ; CODE XREF: restoreOriginalFancyTiles+18j
        StatefulLevelTile *tile = &gCurrentLevelState[i];
        if (tile->tile == LevelTileTypeExplosion) // 31
        {
            tile->tile = 0xF1; // 241
        }
    }

    uint8_t was_gIsShowingFancyTiles_NonZero = (gIsShowingFancyTiles != 0);
    gIsShowingFancyTiles = 0;
    if (was_gIsShowingFancyTiles_NonZero)
    {
        return;
    }

    for (int i = 0; i < kLevelSize; ++i)
    {
        // loc_49D84:              ; CODE XREF: levelScanThing+4Cj
        StatefulLevelTile *tile = &gCurrentLevelState[i];
        if (tile->state != 0 || tile->tile != LevelTileTypeHardware)
        {
            continue;
        }

        LevelTileType originalTile = gCurrentLevel.tiles[i];

        if (originalTile >= LevelTileTypeHardware2      // 28
            && originalTile <= LevelTileTypeHardware11) // 37
        {
            tile->tile = originalTile;
            tile->state = 0;
        }
    }

    // loc_49DA6:              ; CODE XREF: levelScanThing+31j
    for (int i = 0; i < kLevelSize; ++i)
    {
        // loc_49DAC:              ; CODE XREF: levelScanThing+7Fj
        StatefulLevelTile *tile = &gCurrentLevelState[i];
        if (tile->state != 0 || tile->tile != LevelTileTypeChip)
        {
            continue;
        }

        LevelTileType originalTile = gCurrentLevel.tiles[i];

        if (originalTile >= LevelTileTypeHorizontalChipLeft       // 26
            && originalTile <= LevelTileTypeHorizontalChipBottom) // 39
        {
            originalTile -= LevelTileTypeHardware2;    // 28
            if (originalTile >= LevelTileTypePortDown) // 10
            {
                originalTile += LevelTileTypeHardware2; // 28
                tile->tile = originalTile;
                tile->state = 0;
            }
        }
    }

    // loc_49DD9:              ; CODE XREF: levelScanThing+59j
    gIsShowingFancyTiles = 1;
}

void updateMovingObjects() // gameloop   proc near       ; CODE XREF: runLevel:noFlashingp
{
    // 01ED:317D

    gMurphyLocation = updateMurphy(gMurphyLocation); // 01ED:318B

    if (gIsFlashingBackgroundModeEnabled != 0)
    {
        replaceCurrentPaletteColor(0, (Color){0x3f, 0x3f, 0x21});
    }

    // loc_49E14:
    if (gIsFlashingBackgroundModeEnabled != 0)
    {
        replaceCurrentPaletteColor(0, (Color){0x3f, 0x21, 0x21});
    }

    // loc_49E33:
    uint16_t numberOfMovingObjects = 0;

    typedef struct
    {
        MovingFunction function;
        uint16_t tilePosition;
    } MovingObject;

    MovingObject movingObjects[kLevelSize];

    // This loop doesn't iterate through _every tile_. Instead it only goes from the first tile in the gamefield
    // (located at levelWidth + 1) to the last tile in the gamefield (located at levelSize - levelWidth - 2).
    // From what I've seen some demos depend on this. Demo 00/01s028-1.SP is an example because there is a
    // non-hardware tile at the bottom edge that explodes, and a SnikSnak goes in there.
    //
    for (uint16_t i = kLevelWidth + 1; i < kLevelSize - kLevelWidth - 1; ++i) // starts from si, ends in si + cx
    {
        // checkCellForMovingObject:              ; CODE XREF: updateMovingObjects+84j
        LevelTileType tile = gCurrentLevelState[i].tile; //         mov bl, byte ptr leveldata[si]

        // Does this check filter out values except like 0, 2, 16 and 18??
        if ((tile & LevelTileTypeSportRight) == 0)
        {
            continue;
        }

        if (tile >= 0x20) // 32 because there are 32 moving functions, which means this is about moving objects (scissors, the infotrons, etc.)
        {
            continue;
        }

        MovingFunction function = movingFunctions[tile];

        if (function != NULL)
        {
            // There is a list of predefined functions, one per object, and this goes through
            // every tile, looking for objects with a moving function, and then putting the
            // tile and the function in a list that will be iterated later to update those objects.
            //
            MovingObject *object = &movingObjects[numberOfMovingObjects];
            object->function = function;
            object->tilePosition = i;
            numberOfMovingObjects++; // dx++;
        }
        // moveToNextCell:
    }

    if (numberOfMovingObjects != 0)
    {
        // Call all the moving functions
        for (uint16_t i = 0; i < numberOfMovingObjects; ++i)
        {
            // 01ED:3214
            movingObjects[i].function(movingObjects[i].tilePosition);
        }
    }

    // doneWithupdateMovingObjects:

    // 01ED:3227
    if (gShouldKillMurphy != 1 && gIsMurphyUpdated != 0)
    {
        return;
    }

    // loc_49E99:              ; CODE XREF: updateMovingObjects+AFj
    //                 ; updateMovingObjects+B6j
    if (gQuitLevelCountdown == 0) // 01ED:3236
    {
        // 01ED:323D
        gShouldKillMurphy = 0;
        detonateBigExplosion(gMurphyPreviousLocation);
        gQuitLevelCountdown = 0x40; // 64
    }

    return;
}

void updateScrollOffset() // sub_49EBE   proc near       ; CODE XREF: runLevel+109p
                          // ; scrollToMurphy+29p
{
    // 01ED:325B
    uint16_t randomNumber = 0;

    // This random number is used to generate the shaking effect on explosions.
    // The original game generates this random number here for _every_ explosion, even if
    // normally only Murphy's explosion will make the screen shake. However it's necessary
    // to do this here to make sure the right sequence of random numbers is generated when
    // there are explosions in the level.
    //
    if (gIsExplosionStarted == 1)
    {
        randomNumber = generateRandomNumber();
    }

    // loc_49ECC:              ; CODE XREF: updateScrollOffset+7j
    int16_t scrollX = gMurphyPositionX;
    int16_t scrollY = gMurphyPositionY;
    scrollX -= kScreenWidth / 2; // 152
    if (scrollX < 0)
    {
        scrollX = 0;
    }

    // loc_49EDF:              ; CODE XREF: updateScrollOffset+1Cj
    uint16_t maxScrollX = kLevelBitmapWidth - kScreenWidth;
    if (scrollX > maxScrollX) // 624
    {
        scrollX = maxScrollX; // 624
    }

    // loc_49EE8:              ; CODE XREF: updateScrollOffset+25j
    if (gShouldShowGamePanel == 0)
    {
        // loc_49EF4:              ; CODE XREF: updateScrollOffset+2Fj
        scrollY -= kScreenHeight / 2;
    }
    else
    {
        scrollY -= (kScreenHeight - kPanelBitmapHeight) / 2;
    }

    // loc_49EF7:              ; CODE XREF: updateScrollOffset+34j
    if (scrollY < 0)
    {
        scrollY = 0;
    }

    // loc_49EFE:              ; CODE XREF: updateScrollOffset+3Cj
    uint16_t maxScrollY = 0;

    if (gShouldShowGamePanel == 0)
    {
        // loc_49F0F:              ; CODE XREF: updateScrollOffset+45j
        maxScrollY = kLevelBitmapHeight - kScreenHeight;
        if (scrollY > maxScrollY)
        {
            scrollY = maxScrollY;
        }
    }
    else
    {
        maxScrollY = kLevelBitmapHeight - kScreenHeight + kPanelBitmapHeight;
        if (scrollY > maxScrollY)
        {
            scrollY = maxScrollY;
        }
    }

    // loc_49F17:              ; CODE XREF: updateScrollOffset:loc_49F0Dj
    //                 ; updateScrollOffset+54j
    if (gIsMoveScrollModeEnabled == 0 || gIsNumpad5Pressed != 0)
    {
        // loc_49F25:              ; CODE XREF: updateScrollOffset+5Ej
        gMurphyScrollOffsetX = scrollX;
        gMurphyScrollOffsetY = scrollY;
    }
    else
    {
        // loc_49F2E:              ; CODE XREF: updateScrollOffset+65j
        scrollX = gMurphyScrollOffsetX;
        scrollY = gMurphyScrollOffsetY;

        int16_t additionalScrollX = scrollX;
        scrollX += gAdditionalScrollOffsetX;
        if (scrollX < 0)
        {
            // 01ED:32DD
            scrollX = 0;
        }
        else
        {
            // loc_49F56:              ; CODE XREF: updateScrollOffset+80j
            if (scrollX > maxScrollX)
            {
                scrollX = maxScrollX;
            }
        }

        // loc_49F66:              ; CODE XREF: updateScrollOffset+8Cj
        //                 ; updateScrollOffset+96j
        additionalScrollX -= scrollX;
        additionalScrollX = -additionalScrollX;
        gAdditionalScrollOffsetX = additionalScrollX;

        // loc_49F6E:              ; CODE XREF: updateScrollOffset+9Dj
        //                 ; updateScrollOffset+A3j
        int16_t additionalScrollY = scrollY;
        scrollY += gAdditionalScrollOffsetY;
        if (scrollY < 0) // in asm there wasn't a explicit "cmp", just the "add" above
        {
            scrollY = 0;
        }
        else
        {
            // loc_49F99:              ; CODE XREF: updateScrollOffset+CFj
            if (scrollY > maxScrollY) // 168
            {
                scrollY = maxScrollY; // 168
            }
        }

        // loc_49FA1:              ; CODE XREF: updateScrollOffset+C1j
        //                 ; updateScrollOffset+D9j
        additionalScrollY -= scrollY;
        additionalScrollY = -additionalScrollY;
        gAdditionalScrollOffsetY = additionalScrollY;
    }

    // loc_49FA9:              ; CODE XREF: updateScrollOffset+6Ej
    //                 ; updateScrollOffset+BDj ...
    //  This makes the screen shake on an explosion
    if (gShouldShakeWithAllExplosions != 0 || (gShakeWithExplosionsDisabled == 0 && (gQuitLevelCountdown & 0xFF) != 0))
    {
        // loc_49FBE:              ; CODE XREF: updateScrollOffset+F0j
        randomNumber = randomNumber & 0x101;

        uint16_t scrollShakeYOffset = randomNumber >> 8;
        uint16_t scrollShakeXOffset = (randomNumber & 0xFF);

        scrollY += scrollShakeYOffset;
        if (scrollX > 0x13C) // 316
        {
            scrollShakeXOffset = -scrollShakeXOffset;
        }

        // loc_49FD0:              ; CODE XREF: updateScrollOffset+10Ej
        scrollX += scrollShakeXOffset;
    }

    // loc_49FD2:              ; CODE XREF: updateScrollOffset+F7j
    //                 ; updateScrollOffset+FEj
    gScrollOffsetX = scrollX;
    gScrollOffsetY = scrollY;
}

void updateBugTiles(int16_t position) // movefun7  proc near       ; DATA XREF: data:163Co
{
    // 01ED:33DA
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];
    StatefulLevelTile *aboveLeftTile = &gCurrentLevelState[position - kLevelWidth - 1];
    StatefulLevelTile *aboveRightTile = &gCurrentLevelState[position - kLevelWidth + 1];
    StatefulLevelTile *belowLeftTile = &gCurrentLevelState[position + kLevelWidth - 1];
    StatefulLevelTile *belowRightTile = &gCurrentLevelState[position + kLevelWidth + 1];

    if (currentTile->tile != LevelTileTypeBug)
    {
        return;
    }

    // loc_4A045:              ; CODE XREF: movefun7+5j
    if ((gFrameCounter & 3) != 0)
    {
        return;
    }

    // loc_4A051:              ; CODE XREF: movefun7+11j
    int8_t frameNumber = currentTile->state;
    frameNumber++;
    if (frameNumber >= 0xE)
    {
        // 01ED:33F9
        uint8_t value = generateRandomNumber() & 0xFF;
        value &= 0x3F;
        value += 0x20;
        value = -value;
        frameNumber = value;
    }

    // loc_4A067:              ; CODE XREF: movefun7+1Dj
    currentTile->state = frameNumber;
    if (frameNumber < 0)
    {
        return;
    }

    // loc_4A071:              ; CODE XREF: movefun7+31j
    if (aboveLeftTile->tile == LevelTileTypeMurphy || aboveTile->tile == LevelTileTypeMurphy || aboveRightTile->tile == LevelTileTypeMurphy || leftTile->tile == LevelTileTypeMurphy || rightTile->tile == LevelTileTypeMurphy || belowLeftTile->tile == LevelTileTypeMurphy || belowTile->tile == LevelTileTypeMurphy || belowRightTile->tile == LevelTileTypeMurphy)
    {
        // loc_4A0AB:              ; CODE XREF: movefun7+39j
        //                 ; movefun7+40j ...
        playBugSound();
    }

    // loc_4A0AE:              ; CODE XREF: movefun7+6Cj
    Point frameCoordinates = kBugFrameCoordinates[frameNumber];
    drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);
}

void updateTerminalTiles(int16_t position) // movefun5  proc near       ; DATA XREF: data:1630o
{
    // 01ED:346F
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];

    if (currentTile->tile != LevelTileTypeTerminal)
    {
        return;
    }

    // loc_4A0DA:              ; CODE XREF: updateTerminalTiles+5j
    int8_t state = currentTile->state;
    state++;
    if (state <= 0)
    {
        currentTile->state = state;
        return;
    }

    // loc_4A0EA:              ; CODE XREF: updateTerminalTiles+11j
    uint8_t value = generateRandomNumber() & 0xFF;
    value &= gTerminalMaxFramesToNextScroll;
    value = -value;
    currentTile->state = value;

    scrollTerminalScreen(position);
}

/// Updates the random seed using the clock
void generateRandomSeedFromClock() // getTime    proc near       ; CODE XREF: start:doesNotHaveCommandLinep
                                   // ; handleGameUserInput+669p ...
{
    uint32_t timeInMilliseconds = getTime();
    // In order to keep the same behavior and values, this code will convert
    // the time in milliseconds to the clock count, as described in
    // http://vitaly_filatov.tripod.com/ng/asm/asm_029.1.html
    // If 1 second is 18.2 clock counts, we need to divide the time
    // by 1000 to get the seconds, and then multiply by 18.2.
    //
    uint32_t clockCount = timeInMilliseconds * 18.2 / 1000;
    uint16_t lowValue = (clockCount & 0xFFFF);
    uint16_t highValue = ((clockCount >> 16) & 0xFFFF);
    gRandomGeneratorSeed = highValue ^ lowValue;
}

/// Generates a random number based on time?
uint16_t generateRandomNumber() // sub_4A1AE   proc near       ; CODE XREF: handleGameUserInput+66Cp
                                // ; updateScrollOffset+9p ...
{
    uint16_t someValue = gRandomGeneratorSeed;
    someValue *= 0x5E5; // 1509
    someValue += 0x31;  // '1' or 49
    gRandomGeneratorSeed = someValue;
    return someValue / 2;
}

void updateUserInput() // sub_4A1BF   proc near       ; CODE XREF: handleGameUserInput+13p
                       // ; runMainMenu+BDp ...
{
    // 01ED:355C
    uint8_t directionKeyWasPressed = 0;

    gCurrentUserInput = UserInputNone;

    if (isUpButtonPressed())
    {
        // loc_4A1CF:              ; CODE XREF: updateUserInput+7j
        gCurrentUserInput = UserInputUp;
        directionKeyWasPressed = 1;
    }

    // loc_4A1D6:              ; CODE XREF: updateUserInput+Ej
    if (isLeftButtonPressed())
    {
        // loc_4A1E4:              ; CODE XREF: updateUserInput+1Cj
        gCurrentUserInput = UserInputLeft;
        directionKeyWasPressed = 1;
    }

    // loc_4A1EB:              ; CODE XREF: updateUserInput+23j
    if (isDownButtonPressed())
    {
        // loc_4A1F9:              ; CODE XREF: updateUserInput+31j
        gCurrentUserInput = UserInputDown;
        directionKeyWasPressed = 1;
    }

    // loc_4A200:              ; CODE XREF: updateUserInput+38j
    if (isRightButtonPressed())
    {
        // loc_4A20E:              ; CODE XREF: updateUserInput+46j
        gCurrentUserInput = UserInputRight;
        directionKeyWasPressed = 1;
    }

    // loc_4A215:              ; CODE XREF: updateUserInput+4Dj
    if (isActionButtonPressed())
    {
        // loc_4A22A:              ; CODE XREF: updateUserInput+5Bj
        //                 ; updateUserInput+62j
        if (directionKeyWasPressed == 1)
        {
            gCurrentUserInput += kUserInputSpaceAndDirectionOffset;
        }
        else
        {
            // loc_4A236:              ; CODE XREF: updateUserInput+6Ej
            gCurrentUserInput = UserInputSpaceOnly;
        }
    }
}

void removeTiles(LevelTileType tileType) // sub_4A23C   proc near       ; CODE XREF: handleGameUserInput+111p
                                         // ; handleGameUserInput+11Dp ...
{
    // 01ED:35D9
    // Looks like this function goes through every tile and clears those that match the parameter
    for (uint16_t i = 0; i < kLevelSize; ++i)
    {
        // loc_4A242:              ; CODE XREF: removeTiles+1Fj
        StatefulLevelTile *tile = &gCurrentLevelState[i];
        if (tile->tile != tileType)
        {
            if (tileType != LevelTileTypeSnikSnak || tile->tile != 0xBB)
            {
                continue;
            }
        }

        // loc_4A253:              ; CODE XREF: removeTiles+Cj
        tile->state = 0;
        tile->tile = LevelTileTypeSpace;
    }
    forceRestoreOriginalFancyTiles();
    drawFixedLevel();
    convertToEasyTiles();
    gShouldUpdateTotalLevelTime = 0;
    gHasUserCheated = 1;
}

void findMurphy() //   proc near       ; CODE XREF: start+344p fetchAndInitializeLevel+22p
{
    // 01ED:360E
    for (int i = 0; i < kLevelSize; ++i)
    {
        if (gCurrentLevel.tiles[i] == LevelTileTypeMurphy)
        {
            gMurphyLocation = i;
            break;
        }
    }

    scrollToMurphy();
}

void scrollToMurphy() // sub_4A291   proc near       ; CODE XREF: handleGameUserInput+686p
{
    // 01ED:362E
    // Parameters:
    // - si: murphy location * 2
    // - al: murphy location

    gMurphyTileX = gMurphyLocation % kLevelWidth; // stores X coord
    gMurphyTileY = gMurphyLocation / kLevelWidth; // stores Y coord

    gMurphyPositionX = gMurphyTileX * kTileSize;
    gMurphyPositionY = gMurphyTileY * kTileSize;
    //    di = si[0x6155];
    //    si = kMurphyStillSpriteCoordinates;
    drawMovingFrame(304, 132, gMurphyLocation);
    updateScrollOffset();

    videoLoop();
}

uint16_t convertToEasyTiles() // sub_4A2E6   proc near       ; CODE XREF: start+33Bp runLevel+ADp ...
{
    // 01ED:3683
    uint16_t numberOfInfotrons = 0;
    uint16_t numberOfSomething = 0; // this is bx, just counts the number of tiles so technically is same as cx at this point… probably a return value but I don't see it used anywhere???

    for (int i = 0; i < kLevelSize; ++i)
    {
        // loc_4A2F0:              ; CODE XREF: convertToEasyTiles+D1j
        StatefulLevelTile *currentTile = &gCurrentLevelState[i];
        numberOfSomething++;

        if (currentTile->tile == 0xF1)
        {
            currentTile->tile = LevelTileTypeExplosion;
            continue; // jmp short loc_4A3B0
        }

        // loc_4A2FC:              ; CODE XREF: convertToEasyTiles+Ej
        if (gIsGameBusy != 1)
        {
            if (currentTile->tile == LevelTileTypeInfotron)
            {
                // loc_4A33C:              ; CODE XREF: convertToEasyTiles+20j
                numberOfInfotrons++;
                continue; // jmp short loc_4A3B0
            }
        }
        // TODO: what are these gIsGameBusy for??
        if (gIsGameBusy == 1 || currentTile->state != 0 || currentTile->tile != LevelTileTypeSnikSnak) // jz  short loc_4A34B
        {
            if (gIsGameBusy == 1 || currentTile->state != 0 || currentTile->tile != LevelTileTypeElectron) // jz  short loc_4A379
            {
                // loc_4A312:              ; CODE XREF: convertToEasyTiles+1Bj
                if ((currentTile->state == 0 && currentTile->tile == LevelTileTypeHorizontalChipLeft) || (currentTile->state == 0 && currentTile->tile == LevelTileTypeHorizontalChipRight) || (currentTile->state == 0 && currentTile->tile == LevelTileTypeHorizontalChipTop) || (currentTile->state == 0 && currentTile->tile == LevelTileTypeHorizontalChipBottom))
                {
                    // loc_4A33F:              ; CODE XREF: convertToEasyTiles+2Fj
                    //                 ; convertToEasyTiles+34j ...
                    currentTile->tile = LevelTileTypeChip; // mov word ptr [si], 5
                    currentTile->state = 0;
                    continue; // jmp short loc_4A3B0
                }
                if (currentTile->state == 0 && currentTile->tile >= LevelTileTypeHardware2 && currentTile->tile <= LevelTileTypeHardware11)
                {
                    // loc_4A345:              ; CODE XREF: convertToEasyTiles+48j
                    currentTile->tile = LevelTileTypeHardware; // mov word ptr [si], 6
                    currentTile->state = 0;
                    continue; // jmp short loc_4A3B0
                }

                // loc_4A330:              ; CODE XREF: convertToEasyTiles+43j
                if (currentTile->state == 0 && currentTile->tile >= LevelTileTypeSportRight && currentTile->tile <= LevelTileTypeSportUp)
                {
                    // loc_4A3A7:              ; CODE XREF: convertToEasyTiles+52j
                    currentTile->tile -= 4; // Converts Sport[Direction] to Port[Direction]
                    currentTile->state = 1;
                    continue;
                }

                // loc_4A33A:              ; CODE XREF: convertToEasyTiles+4Dj
                continue;
            }
        }

        StatefulLevelTile *leftTile = &gCurrentLevelState[i - 1];
        StatefulLevelTile *aboveTile = &gCurrentLevelState[i - kLevelWidth];
        StatefulLevelTile *rightTile = &gCurrentLevelState[i + 1];

        if (currentTile->state != 0 || currentTile->tile != LevelTileTypeElectron) // jz  short loc_4A379
        {
            // loc_4A34B:              ; CODE XREF: convertToEasyTiles+25j
            if (leftTile->tile == LevelTileTypeSpace && leftTile->state == 0) // cmp word ptr [si-2], 0
            {
                currentTile->state = 1;
                //                si[1] = 1; //mov byte ptr [si+1], 1
                continue; // jmp short loc_4A3B0
            }
            // loc_4A357:              ; CODE XREF: convertToEasyTiles+69j
            //  0x78 = 120
            if (aboveTile->tile == LevelTileTypeSpace && aboveTile->state == 0) // cmp word ptr [si-78h], 0
            {
                // 01ED:36FA
                // mov word ptr [si-78h], 1011h
                aboveTile->state = 0x10;
                aboveTile->tile = LevelTileTypeSnikSnak;
                // mov word ptr [si], 0FFFFh
                currentTile->state = 0xFF;
                currentTile->tile = 0xFF;
                continue; // jmp short loc_4A3B0
            }
            // loc_4A368:              ; CODE XREF: convertToEasyTiles+75j
            if (rightTile->tile == LevelTileTypeSpace && rightTile->state == 0) // cmp word ptr [si+2], 0
            {
                // 01ED:370B
                // mov word ptr [si+2], 2811h
                rightTile->state = 0x28;
                rightTile->tile = LevelTileTypeSnikSnak;
                // mov word ptr [si], 0FFFFh
                currentTile->state = 0xFF;
                currentTile->tile = 0xFF;
                continue; // jmp short loc_4A3B0
            }

            continue;
        }
        // loc_4A379:              ; CODE XREF: convertToEasyTiles+2Aj
        if (leftTile->tile == LevelTileTypeSpace && leftTile->state == 0) // cmp word ptr [si-2], 0
        {
            currentTile->state = 1; // mov byte ptr [si+1], 1
            continue;               // jmp short loc_4A3B0
        }
        // loc_4A385:              ; CODE XREF: convertToEasyTiles+97j
        if (aboveTile->tile == LevelTileTypeSpace && aboveTile->state == 0) // cmp word ptr [si-78h], 0
        {
            // mov word ptr [si-78h], 1018h
            aboveTile->state = 0x10;
            aboveTile->tile = LevelTileTypeElectron;
            // mov word ptr [si], 0FFFFh
            currentTile->state = 0xFF;
            currentTile->tile = 0xFF;
            continue; // jmp short loc_4A3B0
        }
        // loc_4A396:              ; CODE XREF: convertToEasyTiles+A3j
        if (rightTile->tile == LevelTileTypeSpace && rightTile->state == 0) // cmp word ptr [si+2], 0
        {
            // mov word ptr [si+2], 2818h
            rightTile->state = 0x28;
            rightTile->tile = LevelTileTypeElectron;
            // mov word ptr [si], 0FFFFh
            currentTile->state = 0xFF;
            currentTile->tile = 0xFF;
            continue; // jmp short loc_4A3B0
        }
    }

    return numberOfInfotrons;
}

void resetNumberOfInfotrons(uint16_t numberOfInfotronsFoundInLevel) // sub_4A3BB   proc near       ; CODE XREF: start+33Ep fetchAndInitializeLevel+17p
{
    // In the original game, the number of infotrons found in a level is stored in a 2-bytes variable,
    // however, when stored for its use in the game, it's stored in a 1-byte variable.
    //
    uint8_t numberOfInfotrons = (numberOfInfotronsFoundInLevel & 0xFF);
    if (gNumberOfInfoTrons != 0)
    {
        numberOfInfotrons = gNumberOfInfoTrons;
    }

    // loc_4A3C6:              ; CODE XREF: resetNumberOfInfotrons+5j
    gNumberOfRemainingInfotrons = numberOfInfotrons;
    gTotalNumberOfInfotrons = numberOfInfotrons;
    drawNumberOfRemainingInfotrons();
}

void debugSkipLevel() // sub_4A3D2  proc near       ; CODE XREF: handleGameUserInput+39Ep
                      // ; handleGameUserInput+3EBp
{
    gIsSPDemoAvailableToRun = 0;
    gSelectedOriginalDemoLevelNumber = 0;
    uint8_t wasNotZero = (gHasUserInterruptedDemo != 0);
    gHasUserInterruptedDemo = 0;
    gHasUserCheated = 1;
    if (wasNotZero)
    {
        restartLevelWithoutAddingCurrentGameTimeToPlayer();
    }

    restartLevel();
}

void restartLevel() // sub_4A3E9   proc near       ; CODE XREF: handleGameUserInput+14Ep
{
    if (gHasUserInterruptedDemo == 0)
    {
        addCurrentGameTimeToPlayer();
    }

    if (gIsRecordingDemo != 0)
    {
        stopRecordingDemo();
    }

    restartLevelWithoutAddingCurrentGameTimeToPlayer();
}

void restartLevelWithoutAddingCurrentGameTimeToPlayer() // loc_4A3F3:              ; CODE XREF: debugSkipLevel+15j
//                ; restartLevel+5j
{
    gIsMoveScrollModeEnabled = 0;
    gAdditionalScrollOffsetX = 0;
    gAdditionalScrollOffsetY = 0;
    gIsFlashingBackgroundModeEnabled = 0;
    gDebugExtraRenderDelay = 1;
    replaceCurrentPaletteColor(0, (Color){0, 0, 0});

    if (gHasUserInterruptedDemo != 0)
    {
        gIsPlayingDemo = 1;
    }

    // loc_4A427:              ; CODE XREF: restartLevel+37j
    gIsGameBusy = 0;
    fetchAndInitializeLevel();
    gIsGameBusy = 1;
    if (gHasUserInterruptedDemo >= 1)
    {
        gIsPlayingDemo = 0;
        if (gHasUserInterruptedDemo == 0) // WTF? this makes no sense...
        {
            gHasUserInterruptedDemo++;
        }
    }

    // loc_4A446:              ; CODE XREF: restartLevel+50j
    //                 ; restartLevel+57j
    gCurrentUserInput = UserInputNone;
    if (gIsPlayingDemo == 0)
    {
        return;
    }

    gDemoCurrentInputIndex = word_5A33C;
    gDemoCurrentInputRepeatCounter = 1;
    simulateDemoInput();
}

void fetchAndInitializeLevel() // sub_4A463   proc near       ; CODE XREF: recordDemo:loc_4953Bp
                               // ; restartLevel+43p
{
    readLevels();
    drawFixedLevel();
    drawGamePanel();
    gIsGameBusy = -gIsGameBusy;
    uint16_t numberOfInfotrons = convertToEasyTiles();
    gIsGameBusy = -gIsGameBusy;
    resetNumberOfInfotrons(numberOfInfotrons);
    gIsShowingFancyTiles = 1;
    initializeGameInfo();
    findMurphy();
}

void updateOrangeDiskTiles(int16_t position) // movefun3  proc near       ; DATA XREF: data:161Ao
{
    // 01ED:3826
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];

    if (currentTile->tile != LevelTileTypeOrangeDisk)
    {
        return;
    }

    // loc_4A491:              ; CODE XREF: movefun3+5j
    uint16_t tileValue = ((currentTile->state << 8) | currentTile->tile);

    if (tileValue >= 0x3008)
    {
        // loc_4A4D4:              ; CODE XREF: movefun3+Fj
        //     push    si
        uint8_t stateFrame = currentTile->state;
        //        bh = 0;
        //        al = state;
        //    bx *= 2;
        //    ;and bx, byte ptr 0Fh
        //    db 83h, 0E3h, 0Fh
        stateFrame *= 2;
        stateFrame &= 0xF; // 16 frames?

        //    mov di, [si+6155h]
        //    shl bx, 1
        //    add di, [bx+6C95h]
        //    mov si, 12F6h
        //    mov si, [si]
        uint16_t offset = kFallAnimationGravityOffsets[stateFrame];

        uint8_t tileX = (position % kLevelWidth);
        uint8_t tileY = (position / kLevelWidth);

        uint16_t dstX = tileX * kTileSize + (offset % 122);
        uint16_t dstY = tileY * kTileSize + (offset / 122);

        drawMovingSpriteFrameInLevel(128, 64,
                                     kTileSize,
                                     kTileSize + 2,
                                     dstX, dstY);

        uint8_t state = currentTile->state;
        state++;
        uint8_t otherMovingObject = state;
        otherMovingObject &= 7;
        if (otherMovingObject != 0)
        {
            currentTile->state = state;
            return;
        }

        // loc_4A516:              ; CODE XREF: movefun3+86j
        currentTile->state = 0;
        currentTile->tile = LevelTileTypeSpace;
        belowTile->state = 0;
        belowTile->tile = LevelTileTypeOrangeDisk;

        position += kLevelWidth;

        // Update tiles
        currentTile = &gCurrentLevelState[position];
        belowTile = &gCurrentLevelState[position + kLevelWidth];

        if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
        {
            currentTile->state = 0x30;
            belowTile->state = 8;
        }
        // loc_4A537:              ; CODE XREF: movefun3+A1j
        else if (belowTile->tile != LevelTileTypeExplosion)
        {
            // loc_4A53F:              ; CODE XREF: movefun3+B3j
            //  01ED:38DC
            detonateBigExplosion(position);
        }

        return;
    }
    else if (tileValue >= 0x2008)
    {
        // loc_4A4B4:              ; CODE XREF: movefun3+14j
        if (belowTile->state != 0 || belowTile->tile != LevelTileTypeSpace)
        {
            // loc_4A4C2:              ; CODE XREF: movefun3+30j
            uint8_t state = currentTile->state;
            state++;
            if (state == 0x22)
            {
                state = 0x30;
            }

            // loc_4A4CF:              ; CODE XREF: movefun3+42j
            currentTile->state = state;
            return;
        }
        currentTile->state = 0;
        currentTile->tile = LevelTileTypeOrangeDisk;
        return;
    }

    if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
    {
        // loc_4A4A9:              ; CODE XREF: movefun3+1Dj
        currentTile->state = 0x20;
        belowTile->state = 8;
    }
}

void updateExplosionTiles(int16_t position) // loc_4A543:              ; DATA XREF: data:1648o
{
    // 01ED:38E0
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];

    if (currentTile->tile != LevelTileTypeExplosion)
    {
        return;
    }

    // loc_4A54B:              ; CODE XREF: code:3928j
    if ((gFrameCounter & 3) != 0)
    {
        return;
    }

    // loc_4A557:              ; CODE XREF: code:3934j
    uint8_t state = currentTile->state;
    if ((state & 0x80) != 0)
    {
        // loc_4A5A0:              ; CODE XREF: code:393Ej
        state++;
        if (state != 0x89)
        {
            // loc_4A5B3:              ; CODE XREF: code:3985j
            currentTile->state = state;
            state--;
            state &= 0xF;
            // 12e6
            Point frameCoordinates = kInfotronExplosionAnimationFrameCoordinates[state];

            drawMovingFrame(frameCoordinates.x,
                            frameCoordinates.y,
                            position);
        }
        else
        {
            currentTile->state = 0;
            currentTile->tile = LevelTileTypeInfotron;
            gIsExplosionStarted = 0;
        }
    }
    else
    {
        state++;
        currentTile->state = state;
        state--;

        // 12d6
        Point frameCoordinates = kRegularExplosionAnimationFrameCoordinates[state];

        drawMovingFrame(frameCoordinates.x,
                        frameCoordinates.y,
                        position);

        // loc_4A582:              ; CODE XREF: code:396Aj
        if (currentTile->state == 8)
        {
            currentTile->state = 0;
            currentTile->tile = LevelTileTypeSpace;
            gIsExplosionStarted = 0;
        }
    }
}

void updateExplosionTimers() // sub_4A5E0   proc near       ; CODE XREF: runLevel+106p
{
    // 01ED:397D
    for (int i = 0; i < kLevelSize; ++i)
    {
        // loc_4A5E9:              ; CODE XREF: updateExplosionTimers+25j
        int8_t timer = gExplosionTimers[i];

        if (timer == 0)
        {
            continue;
        }

        if (timer < 0)
        {
            // loc_4A608:              ; CODE XREF: updateExplosionTimers+10j
            gExplosionTimers[i] = timer + 1;

            if (gExplosionTimers[i] == 0)
            {
                StatefulLevelTile *tile = &gCurrentLevelState[i];
                tile->state = 0xFF;
                tile->tile = LevelTileTypeElectron;
                detonateBigExplosion(i);
            }
        }
        else
        {
            gExplosionTimers[i] = timer - 1;

            if (gExplosionTimers[i] == 0)
            {
                detonateBigExplosion(i);
            }
        }
    }
}

void detonateBigExplosionTile(int16_t position, uint8_t newTile, uint8_t newState, uint8_t newExplosionTimer)
{
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];

    // loc_4A64C:              ; CODE XREF: detonateBigExplosion+26j
    uint8_t hasChangedCurrentTile = 0;

    if (currentTile->tile == LevelTileTypeOrangeDisk || currentTile->tile == LevelTileTypeYellowDisk || currentTile->tile == LevelTileTypeSnikSnak)
    {
        // loc_4A680:              ; CODE XREF: detonateBigExplosion+3Aj
        //                 ; detonateBigExplosion+3Ej ...
        if (currentTile->tile != LevelTileTypeHardware)
        {
            gExplosionTimers[position] = newExplosionTimer; // mov [bx+23F7h], dh
        }
    }
    else if (currentTile->tile == LevelTileTypeZonk)
    {
        // loc_4A69C:              ; CODE XREF: detonateBigExplosion+46j
        //  01ED:3A39
        detonateZonk(position, newState, newTile);
        hasChangedCurrentTile = 1; // to emulate jmp loc_4A6A6
    }
    else if (currentTile->tile == LevelTileTypeInfotron)
    {
        // loc_4A692:              ; CODE XREF: detonateBigExplosion+4Aj 01ED:3A2F
        sub_4AA34(position, newState, newTile);
        hasChangedCurrentTile = 1; // to emulate jmp loc_4A6A6
    }
    else if (currentTile->tile == LevelTileTypeElectron)
    {
        newExplosionTimer = -newExplosionTimer; // dh = -dh;
        newState = 0x80;
        newTile = LevelTileTypeExplosion;
        // loc_4A680:              ; CODE XREF: detonateBigExplosion+3Aj
        //                 ; detonateBigExplosion+3Ej ...
        if (currentTile->tile != LevelTileTypeHardware)
        {
            gExplosionTimers[position] = newExplosionTimer; // mov [bx+23F7h], dh
        }
    }
    // loc_4A676:              ; CODE XREF: detonateBigExplosion+4Ej
    else if (currentTile->tile == LevelTileTypeMurphy)
    {
        gShouldKillMurphy = 1;

        // loc_4A680:              ; CODE XREF: detonateBigExplosion+3Aj
        //                 ; detonateBigExplosion+3Ej ...
        if (currentTile->tile != LevelTileTypeHardware)
        {
            gExplosionTimers[position] = newExplosionTimer; // mov [bx+23F7h], dh
        }
    }

    if (hasChangedCurrentTile == 0)
    {
        // loc_4A688:              ; CODE XREF: detonateBigExplosion+59j
        //                 ; detonateBigExplosion+63j
        if (currentTile->tile != LevelTileTypeHardware)
        {
            // mov [si+17BAh], cx
            currentTile->state = newState;
            currentTile->tile = newTile;
        }
    }
}

// Creates an explossion of 3x3 tiles around a position
void detonateBigExplosion(int16_t position) // sub_4A61F   proc near       ; CODE XREF: movefun+271p
                                            // ; movefun2+20Fp ...
{
    // 01ED:39BC
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];

    if (currentTile->state == 0 && currentTile->tile == LevelTileTypeHardware)
    {
        return;
    }

    // These indicate the kind of the explosion created by this tile.
    // Tiles around may create a different explosion if needed (like Electrons create Infotrons).
    //
    uint8_t newState = 0;
    uint8_t newTile = 0;
    uint8_t newExplosionTimer = 0;

    // loc_4A627:              ; CODE XREF: detonateBigExplosion+5j
    gIsExplosionStarted = 1;
    if (currentTile->tile == LevelTileTypeMurphy)
    {
        gShouldKillMurphy = 1;
    }

    // loc_4A639:              ; CODE XREF: detonateBigExplosion+12j
    if (currentTile->tile == LevelTileTypeElectron)
    {
        newState = 0x80;
        newTile = LevelTileTypeExplosion;
        newExplosionTimer = -13;
    }
    else
    {
        // loc_4A647:              ; CODE XREF: detonateBigExplosion+1Fj
        //  cx = 0x1F; // 31
        newState = 0;
        newTile = LevelTileTypeExplosion;
        newExplosionTimer = 13;
    }

    detonateBigExplosionTile(position - kLevelWidth - 1, newTile, newState, newExplosionTimer);
    detonateBigExplosionTile(position - kLevelWidth, newTile, newState, newExplosionTimer);
    detonateBigExplosionTile(position - kLevelWidth + 1, newTile, newState, newExplosionTimer);
    detonateBigExplosionTile(position - 1, newTile, newState, newExplosionTimer);

    // loc_4A7AB:              ; CODE XREF: detonateBigExplosion:loc_4A795j
    //                 ; detonateBigExplosion+180j ...
    currentTile->state = newState;
    currentTile->tile = newTile;

    detonateBigExplosionTile(position + 1, newTile, newState, newExplosionTimer);
    detonateBigExplosionTile(position + kLevelWidth - 1, newTile, newState, newExplosionTimer);
    detonateBigExplosionTile(position + kLevelWidth, newTile, newState, newExplosionTimer);
    detonateBigExplosionTile(position + kLevelWidth + 1, newTile, newState, newExplosionTimer);

    // loc_4A90B:              ; CODE XREF: detonateBigExplosion:loc_4A8F5j
    //                 ; detonateBigExplosion+2E0j ...
    playExplosionSound();
    // 01ED:3CAC
}

void updatePlantedRedDisk() // sub_4A910   proc near       ; CODE XREF: runLevel:noFlashing3p
{
    // 01ED:3CAD
    if (gPlantedRedDiskCountdown <= 1)
    {
        return;
    }

    StatefulLevelTile *tile = &gCurrentLevelState[gPlantedRedDiskPosition];

    if (tile->state == 0 && tile->tile == LevelTileTypeSpace)
    {
        tile->state = 0;
        tile->tile = LevelTileTypeRedDisk;
    }

    // loc_4A932:              ; CODE XREF: updatePlantedRedDisk+1Aj
    //  si = word_5177E;
    drawMovingFrame(256, 164, gPlantedRedDiskPosition);
    gPlantedRedDiskCountdown++;
    if (gPlantedRedDiskCountdown >= 0x28)
    {
        detonateBigExplosion(gPlantedRedDiskPosition);
        gPlantedRedDiskCountdown = 0;
    }
}

void addCurrentGameTimeToPlayer() // sub_4A95F   proc near       ; CODE XREF: runLevel+372p
                                  // ; restartLevel+7p ...
{
    uint8_t seconds = gGameSeconds;
    uint8_t minutes = gGameMinutes;
    uint16_t hours = gGameHours;
    if (gIsPlayingDemo != 0)
    {
        return;
    }

    if (gIsSPDemoAvailableToRun != 0)
    {
        return;
    }

    // loc_4A980:              ; CODE XREF: addCurrentGameTimeToPlayer+1Ej
    PlayerEntry *playerEntry = &gPlayerListData[gCurrentPlayerIndex];
    seconds += playerEntry->seconds;

    do
    {
        // loc_4A994:              ; CODE XREF: addCurrentGameTimeToPlayer+3Ej
        if (seconds < 60)
        {
            break;
        }
        seconds -= 60;
        playerEntry->minutes++;
    } while (1);

    // loc_4A99F:              ; CODE XREF: addCurrentGameTimeToPlayer+37j
    playerEntry->seconds = seconds;

    minutes += playerEntry->minutes;

    do
    {
        // loc_4A9A8:              ; CODE XREF: addCurrentGameTimeToPlayer+52j
        if (minutes < 60)
        {
            break;
        }
        minutes -= 60;
        playerEntry->hours++;
    } while (1);

    // loc_4A9B3:              ; CODE XREF: addCurrentGameTimeToPlayer+4Bj
    playerEntry->minutes = minutes;

    hours += playerEntry->hours;

    if (hours > 0xFF)
    {
        hours = 0xFF;
    }

    // loc_4A9C0:              ; CODE XREF: addCurrentGameTimeToPlayer+5Dj
    playerEntry->hours = hours;
}

void detonateZonk(int16_t position, uint8_t state, uint8_t tile) // sub_4A9C4   proc near       ; CODE XREF: detonateBigExplosion+81p
                                                                 //  ; detonateBigExplosion+D8p ...
{
    // 01ED:3D61
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];

    uint8_t stateType = currentTile->state & 0xF0;

    currentTile->state = state;
    currentTile->tile = tile;

    if (stateType == 0x10 || stateType == 0x70)
    {
        // loc_4A9EF:              ; CODE XREF: detonateZonk+Aj detonateZonk+Fj
        sub_4AAB4(position - kLevelWidth);
        if (belowTile->state == 0x99 && belowTile->tile == 0x99)
        {
            sub_4AAB4(position + kLevelWidth);
        }
    }
    else if (stateType == 0x20)
    {
        // loc_4AA05:              ; CODE XREF: detonateZonk+14j
        sub_4AAB4(position + 1);
        sub_4AAB4(position + kLevelWidth);
    }
    else if (stateType == 0x30)
    {
        // loc_4AA12:              ; CODE XREF: detonateZonk+19j
        sub_4AAB4(position - 1);
        sub_4AAB4(position + kLevelWidth);
    }
    else if (stateType == 0x50)
    {
        // loc_4AA1F:              ; CODE XREF: detonateZonk+1Ej
        sub_4AAB4(position - 1);
    }
    else if (stateType == 0x60)
    {
        // loc_4AA26:              ; CODE XREF: detonateZonk+23j
        sub_4AAB4(position + 1);
    }
    else if (stateType == 0x70)
    {
        // loc_4AA2D:              ; CODE XREF: detonateZonk+28j
        sub_4AAB4(position + kLevelWidth);
    }
}

void sub_4AA34(int16_t position, uint8_t state, uint8_t tile) //   proc near       ; CODE XREF: detonateBigExplosion+77p
                                                              // ; detonateBigExplosion+CEp ...
{
    // Parameters:
    // - si: position
    // - cx: state (ch) and tile (cl)

    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];

    uint8_t stateType = currentTile->state & 0xF0;

    currentTile->state = state;
    currentTile->tile = tile;

    if (stateType == 0x10 || stateType == 0x70)
    {
        // loc_4AA5F:              ; CODE XREF: sub_4AA34+Aj sub_4AA34+Fj
        sub_4AAB4(position - kLevelWidth);
        if (belowTile->state == 0x99 && belowTile->tile == 0x99)
        {
            sub_4AAB4(position + kLevelWidth);
        }
    }
    else if (stateType == 0x20)
    {
        // loc_4AA75:              ; CODE XREF: sub_4AA34+14j
        sub_4AAB4(position + 1);
        if (belowTile->state == 0x99 && belowTile->tile == 0x99)
        {
            sub_4AAB4(position + kLevelWidth);
        }
    }
    else if (stateType == 0x30)
    {
        // loc_4AA8A:              ; CODE XREF: sub_4AA34+19j
        sub_4AAB4(position - 1);
        if (belowTile->state == 0x99 && belowTile->tile == 0x99)
        {
            sub_4AAB4(position + kLevelWidth);
        }
    }
    else if (stateType == 0x50)
    {
        // loc_4AA9F:              ; CODE XREF: sub_4AA34+1Ej
        sub_4AAB4(position - 1);
    }
    else if (stateType == 0x60)
    {
        // loc_4AAA6:              ; CODE XREF: sub_4AA34+23j
        sub_4AAB4(position + 1);
    }
    else if (stateType == 0x70)
    {
        // loc_4AAAD:              ; CODE XREF: sub_4AA34+28j
        sub_4AAB4(position + kLevelWidth);
    }
}

void sub_4AAB4(int16_t position) //   proc near       ; CODE XREF: detonateZonk+2Ep
                                 // ; detonateZonk+3Dp ...
{
    // 01ED:3DD1
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];

    if (currentTile->tile == LevelTileTypeExplosion)
    {
        return;
    }

    // loc_4AABC:              ; CODE XREF: sub_4AAB4+5j
    currentTile->state = 0;
    currentTile->tile = LevelTileTypeSpace;

    // si = word_51580;
    uint16_t dstX = (position % kLevelWidth) * kTileSize;
    uint16_t dstY = (position / kLevelWidth) * kTileSize;

    drawMovingSpriteFrameInLevel(0, 32, kTileSize, kTileSize, dstX, dstY);
}

void handleNewPlayerOptionClick() // sub_4AB1B  proc near       ; CODE XREF: runMainMenu+28Fp
//                    ; DATA XREF: data:off_50318o
{
    // 01ED:3EB8
    if (gIsForcedCheatMode != 0)
    {
        // jnz short loc_4AB4A
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 6, "PLAYER LIST FULL       ");
        return;
    }

    int newPlayerIndex = -1;

    for (int i = 0; i < kNumberOfPlayers; ++i)
    {
        PlayerEntry currentPlayerEntry = gPlayerListData[i];

        // loc_4AB2D:              ; CODE XREF: handleNewPlayerOptionClick+2Dj
        if (strcmp(currentPlayerEntry.name, "--------") == 0)
        {
            newPlayerIndex = i;
            break;
        }
        // loc_4AB42:              ; CODE XREF: handleNewPlayerOptionClick+14j
        //                 ; handleNewPlayerOptionClick+19j ...
    }

    if (newPlayerIndex == -1)
    {
        // loc_4AB4A:              ; CODE XREF: handleNewPlayerOptionClick+5j
        //         mov di, 89F7h
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 6, "PLAYER LIST FULL       ");
        return;
    }

    // loc_4AB56:              ; CODE XREF: handleNewPlayerOptionClick+25j
    gNewPlayerEntryIndex = newPlayerIndex;

    char newPlayerName[kPlayerNameLength + 1] = "        ";
    gNewPlayerNameLength = 0;
    uint16_t mouseX, mouseY;
    uint16_t mouseButtonStatus;

    restoreLastMouseAreaBitmap();

    if (supportsRealKeyboard())
    {
        // mov di, 89F7h
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 4, "YOUR NAME:             ");

        do
        {
            // loc_4AB7F:              ; CODE XREF: handleNewPlayerOptionClick+6Aj
            getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
        } while (mouseButtonStatus != 0);

        char lastPressedCharacter = '\0';

        do
        {
            // noKeyPressed:               ; CODE XREF: handleNewPlayerOptionClick+79j
            //                 ; handleNewPlayerOptionClick+8Aj ...
            videoLoop();

            int9handler(0);
            getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
            if (mouseButtonStatus != 0)
            {
                break;
            }
            if (isAnyKeyPressed() == 0)
            {
                lastPressedCharacter = '\0';
                continue;
            }

            char character = characterForLastKeyPressed();

            if (lastPressedCharacter == character)
            {
                continue;
            }

            lastPressedCharacter = character;

            if (character == 0) // For keys without a valid representation
            {
                continue;
            }
            if (character == '\n') // \n -> enter -> create player
            {
                break;
            }
            if (character == '\b') // backspace -> delete last char
            {
                // loc_4ABCC:              ; CODE XREF: handleNewPlayerOptionClick+92j
                if (gNewPlayerNameLength == 0)
                {
                    continue;
                }
                gNewPlayerNameLength--;
                newPlayerName[gNewPlayerNameLength] = ' ';
                drawTextWithChars6FontWithOpaqueBackgroundIfPossible(232, 127, 6, newPlayerName);
                continue;
            }
            if (gNewPlayerNameLength >= 8) // when more than 8 chars were entered, ignore the rest?
            {
                continue;
            }
            newPlayerName[gNewPlayerNameLength] = character; // mov [bx+si], al
            gNewPlayerNameLength++;
            drawTextWithChars6FontWithOpaqueBackgroundIfPossible(232, 127, 6, newPlayerName);
        } while (1);

        do
        {
            // loc_4ABEB:              ; CODE XREF: handleNewPlayerOptionClick+72j
            //                 ; handleNewPlayerOptionClick+8Ej ...
            getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
        } while (mouseButtonStatus != 0);
    }
    else if (supportsVirtualKeyboard())
    {
        char inputBuffer[kPlayerNameLength + 1] = "";
        uint8_t result = inputVirtualKeyboardText("Enter your name", kPlayerNameLength, inputBuffer);

        if (result == 0)
        {
            drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "                       ");
            saveLastMouseAreaBitmap();
            drawMouseCursor();
            return;
        }

        const char *allowedCharacters = "0123456789QWERTYUIOPASDFGHJKLZXCVBNM -";

        // Fill name with spaces, convert it to uppercase, and replace invalid characters with '-'
        size_t numberOfSpaces = kPlayerNameLength - strlen(inputBuffer);

        for (size_t idx = 0; idx < kPlayerNameLength; ++idx)
        {
            char inputCharacter = ' ';

            if (idx >= numberOfSpaces)
            {
                inputCharacter = toupper(inputBuffer[idx - numberOfSpaces]);

                if (strchr(allowedCharacters, inputCharacter) == NULL)
                {
                    inputCharacter = '-';
                }
            }

            newPlayerName[idx] = inputCharacter;
        }
    }
    else
    {
        do
        {
            // loc_4AB7F:              ; CODE XREF: handleNewPlayerOptionClick+6Aj
            getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
        } while (mouseButtonStatus != 0);

        // Limit the player number value to avoid -Wformat-truncation warning
        snprintf(newPlayerName, sizeof(newPlayerName), "PLAYER%2d", MIN(gNewPlayerEntryIndex + 1, kNumberOfPlayers));
        gNewPlayerNameLength = strlen(newPlayerName);
    }

    // Completely empty name: ignore
    if (strcmp(newPlayerName, "        ") == 0)
    {
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "                       ");
        saveLastMouseAreaBitmap();
        drawMouseCursor();
        return;
    }

    // loc_4AC1E:              ; CODE XREF: handleNewPlayerOptionClick+E0j
    //                 ; handleNewPlayerOptionClick+E5j ...
    //  Name with all dashes: invalid
    if (strcmp(newPlayerName, "--------") == 0)
    {
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 6, "INVALID NAME           ");
        saveLastMouseAreaBitmap();
        drawMouseCursor();
        return;
    }

    // loc_4AC46:              ; CODE XREF: handleNewPlayerOptionClick+108j
    //                 ; handleNewPlayerOptionClick+10Dj ...

    // Move spaces at the end of the name to the beginning
    const int kLastNameCharacterIndex = sizeof(newPlayerName) - 2;
    while (newPlayerName[kLastNameCharacterIndex] == ' ')
    {
        // loc_4AC4B:              ; CODE XREF: handleNewPlayerOptionClick+14Cj
        for (int i = kLastNameCharacterIndex; i >= 1; --i)
        {
            newPlayerName[i] = newPlayerName[i - 1];
        }
        newPlayerName[0] = ' ';
    }

    // loc_4AC69:              ; CODE XREF: handleNewPlayerOptionClick+137j

    for (int i = 0; i < kNumberOfPlayers; ++i)
    {
        PlayerEntry player = gPlayerListData[i];
        // loc_4AC73:              ; CODE XREF: handleNewPlayerOptionClick+18Cj
        if (strcmp(player.name, newPlayerName) == 0)
        {
            drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 6, "PLAYER EXISTS          ");
            saveLastMouseAreaBitmap();
            drawMouseCursor();
            return;
        }
        // loc_4ACA3:              ; CODE XREF: handleNewPlayerOptionClick+15Cj
        //                 ; handleNewPlayerOptionClick+164j ...
    }
    gCurrentPlayerIndex = gNewPlayerEntryIndex;
    PlayerEntry *newPlayerEntry = &gPlayerListData[gCurrentPlayerIndex];
    memcpy(newPlayerEntry->name, newPlayerName, sizeof(newPlayerName));

    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "                       ");
    savePlayerListData();
    saveHallOfFameData();
    gShouldAutoselectNextLevelToPlay = 1;
    prepareLevelDataForCurrentPlayer();
    drawPlayerList();
    drawLevelList();
    drawRankings();
    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void handleDeletePlayerOptionClick() // sub_4AD0E  proc near
{
    if (gIsForcedCheatMode != 0)
    {
        // loc_4AD3C:              ; CODE XREF: handleDeletePlayerOptionClick+5j
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "NO PLAYER SELECTED     ");
        return;
    }

    PlayerEntry *currentPlayerEntry = &gPlayerListData[gCurrentPlayerIndex];
    //    *dword_58477 = currentPlayerEntry; // mov word ptr dword_58477, si
    if (strcmp(currentPlayerEntry->name, "--------") == 0)
    {
        // loc_4AD3C:              ; CODE XREF: handleDeletePlayerOptionClick+5j
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "NO PLAYER SELECTED     ");
        return;
    }

    // loc_4AD48:              ; CODE XREF: handleDeletePlayerOptionClick+1Dj
    //                 ; handleDeletePlayerOptionClick+22j ...
    char message[24] = "";
    sprintf(message, "DELETE '%s' ???  ", currentPlayerEntry->name);

    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, message);

    uint16_t mouseX, mouseY;
    uint16_t mouseButtonStatus;

    do
    {
        // loc_4AD6C:              ; CODE XREF: handleDeletePlayerOptionClick+64j
        getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
    } while (mouseButtonStatus != 0);

    do
    {
        // loc_4AD74:              ; CODE XREF: handleDeletePlayerOptionClick+88j
        videoLoop();
        getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
        gMouseButtonStatus = mouseButtonStatus;
        gMouseX = mouseX;
        gMouseY = mouseY;
        restoreLastMouseAreaBitmap();
        saveLastMouseAreaBitmap();
        drawMouseCursor();
    } while (gMouseButtonStatus == 0);

    ButtonDescriptor okButtonDescriptor = kMainMenuButtonDescriptors[9];

    if (gMouseX >= okButtonDescriptor.startX && gMouseY >= okButtonDescriptor.startY && gMouseX <= okButtonDescriptor.endX && gMouseY <= okButtonDescriptor.endY)
    {
        //        mov di, word ptr dword_58477 // recover current player entry pointer
        memset(currentPlayerEntry, 0, sizeof(PlayerEntry));
        memset(currentPlayerEntry->name, '-', sizeof(currentPlayerEntry->name) - 1);
    }

    // loc_4ADCE:              ; CODE XREF: handleDeletePlayerOptionClick+97j
    //                 ; handleDeletePlayerOptionClick+9Cj ...
    restoreLastMouseAreaBitmap();
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "                       ");
    savePlayerListData();
    saveHallOfFameData();
    gShouldAutoselectNextLevelToPlay = 1;
    prepareLevelDataForCurrentPlayer();
    drawPlayerList();
    drawLevelList();
    drawRankings();

    do
    {
        // loc_4ADF3:              ; CODE XREF: handleDeletePlayerOptionClick+EBj
        getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
    } while (mouseButtonStatus != 0);
    saveLastMouseAreaBitmap();
}

void handleSkipLevelOptionClick() // sub_4ADFF  proc near
{
    // 01ED:419C
    PlayerEntry currentPlayerEntry = gPlayerListData[gCurrentPlayerIndex];

    if (strcmp(currentPlayerEntry.name, "--------") == 0)
    {
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "NO PLAYER SELECTED     ");
        return;
    }

    // loc_4AE2E:              ; CODE XREF: handleSkipLevelOptionClick+12j
    //                 ; handleSkipLevelOptionClick+17j ...
    int numberOfSkippedLevels = 0;

    for (int i = 0; i < kNumberOfLevels; ++i)
    {
        // loc_4AE38:              ; CODE XREF: handleSkipLevelOptionClick+40j
        if (currentPlayerEntry.levelState[i] == PlayerLevelStateSkipped)
        {
            numberOfSkippedLevels++;
        }
        // loc_4AE3E:              ; CODE XREF: handleSkipLevelOptionClick+3Bj
    }
    if (gIsDebugModeEnabled == 0)
    {
        // loc_4AE4A:              ; CODE XREF: handleSkipLevelOptionClick+47j
        if (numberOfSkippedLevels >= 3)
        {
            drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 6, "SKIP NOT POSSIBLE      ");
            return;
        }
    }

    // loc_4AE5B:              ; CODE XREF: handleSkipLevelOptionClick+49j
    //                 ; handleSkipLevelOptionClick+4Ej
    if (gCurrentPlayerLevelData[gCurrentSelectedLevelIndex - 1] != kNotCompletedLevelEntryColor)
    {
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 4, "COLORBLIND I GUESS     ");
        return;
    }

    // loc_4AE75:              ; CODE XREF: handleSkipLevelOptionClick+68j
    char levelNumber[4] = "000";
    convertNumberTo3DigitStringWithPadding0(gCurrentSelectedLevelIndex, levelNumber);

    char message[24];
    sprintf(message, "SKIP LEVEL %s ???     ", levelNumber);
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, message);

    uint16_t mouseX, mouseY;
    uint16_t mouseButtonStatus;

    do
    {
        // loc_4AE89:              ; CODE XREF: handleSkipLevelOptionClick+90j
        getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
    } while (mouseButtonStatus != 0);

    do
    {
        // loc_4AE91:              ; CODE XREF: handleSkipLevelOptionClick+B4j
        videoLoop();
        getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
        gMouseButtonStatus = mouseButtonStatus;
        gMouseX = mouseX;
        gMouseY = mouseY;
        restoreLastMouseAreaBitmap();
        saveLastMouseAreaBitmap();
        drawMouseCursor();
    } while (gMouseButtonStatus == 0);

    ButtonDescriptor okButtonDescriptor = kMainMenuButtonDescriptors[9];

    if (gMouseX >= okButtonDescriptor.startX && gMouseY >= okButtonDescriptor.startY && gMouseX <= okButtonDescriptor.endX && gMouseY <= okButtonDescriptor.endY)
    {
        gCurrentPlayerLevelState = PlayerLevelStateSkipped;
        changePlayerCurrentLevelState(); // 01ED:4275
        gShouldAutoselectNextLevelToPlay = 0;
        prepareLevelDataForCurrentPlayer();
    }

    // loc_4AEE9:              ; CODE XREF: handleSkipLevelOptionClick+C3j
    //                 ; handleSkipLevelOptionClick+C8j ...
    restoreLastMouseAreaBitmap();
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "                       ");
    drawPlayerList();
    drawLevelList();
    drawRankings();

    do
    {
        // loc_4AF00:              ; CODE XREF: handleSkipLevelOptionClick+107j
        getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
    } while (mouseButtonStatus != 0);
    saveLastMouseAreaBitmap();
}

void handleStatisticsOptionClick() // sub_4AF0C   proc near
{
    PlayerEntry currentPlayerEntry = gPlayerListData[gCurrentPlayerIndex];
    if (strcmp(currentPlayerEntry.name, "--------") == 0)
    {
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "NO PLAYER SELECTED     ");
        return;
    }

    // loc_4AFE3:              ; CODE XREF: handleStatisticsOptionClick+58j
    fadeToPalette(gBlackPalette);

    uint8_t *screenPixelsBackup = malloc(kFullScreenFramebufferLength);
    memcpy(screenPixelsBackup, gScreenPixels, kFullScreenFramebufferLength);

    drawBackBackground();

    byte_5091A = 0;
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(80, 20, 15, "SUPAPLEX  BY DREAM FACTORY");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(64, 50, 15, "(C) DIGITAL INTEGRATION LTD 1991");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(16, 60, 15, "________________________________________________");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(80, 80, 15, "SUPAPLEX PLAYER STATISTICS");

    char currentPlayerText[27] = "";
    sprintf(currentPlayerText, "CURRENT PLAYER :  %s", currentPlayerEntry.name);
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(80, 100, 15, currentPlayerText);

    if (currentPlayerEntry.nextLevelToPlay == kLastLevelIndex)
    {
        byte_5091A = 1;
    }

    // loc_4B046:              ; CODE XREF: handleStatisticsOptionClick+133j
    char levelNumberString[4] = "000";
    convertNumberTo3DigitStringWithPadding0(currentPlayerEntry.nextLevelToPlay, levelNumberString);

    char currentLevelText[27] = "";
    sprintf(currentLevelText, "CURRENT LEVEL  :       %s", levelNumberString);
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(80, 110, 15, currentLevelText);

    char secondsNumberString[4] = ":00";
    char minutesNumberString[4] = ":00";
    char hoursNumberString[4] = "  0";

    convertNumberTo3DigitStringWithPadding0(currentPlayerEntry.seconds, secondsNumberString);
    secondsNumberString[0] = ':';

    convertNumberTo3DigitStringWithPadding0(currentPlayerEntry.minutes, minutesNumberString);
    minutesNumberString[0] = ':';

    convertNumberTo3DigitPaddedString(currentPlayerEntry.hours, hoursNumberString, 1);

    char usedTimeText[27] = "";
    sprintf(usedTimeText, "USED TIME      : %s%s%s", hoursNumberString, minutesNumberString, secondsNumberString);

    drawTextWithChars6FontWithTransparentBackgroundIfPossible(80, 120, 15, usedTimeText);

    uint32_t totalMinutes = currentPlayerEntry.hours * 60 + currentPlayerEntry.minutes;

    if (currentPlayerEntry.seconds >= 30)
    {
        totalMinutes++;
    }

    // loc_4B0A1:              ; CODE XREF: handleStatisticsOptionClick+192j

    char averageTimeString[6] = "000.0";
    uint16_t averageMinutesWhole = totalMinutes / currentPlayerEntry.nextLevelToPlay;
    uint16_t averageMinutesFraction = (totalMinutes % currentPlayerEntry.nextLevelToPlay);
    averageMinutesFraction = averageMinutesFraction * 10 / currentPlayerEntry.nextLevelToPlay;
    convertNumberTo3DigitStringWithPadding0(averageMinutesFraction, &averageTimeString[2]);

    if (averageMinutesWhole == 0)
    {
        byte_5091A = 2;
    }

    // loc_4B0C2:              ; CODE XREF: handleStatisticsOptionClick+1AFj
    averageTimeString[3] = '.';

    convertNumberTo3DigitPaddedString(averageMinutesWhole, averageTimeString, 1);
    if (byte_5091A == 1)
    {
        drawTextWithChars6FontWithTransparentBackgroundIfPossible(24, 140, 15, "YOU'VE COMPLETED ALL LEVELS! CONGRATULATIONS!!!");
    }
    // loc_4B0E2:              ; CODE XREF: handleStatisticsOptionClick+1C7j
    else if (byte_5091A == 2)
    {
        drawTextWithChars6FontWithTransparentBackgroundIfPossible(40, 140, 15, "STILL UNDER ONE MINUTE (KEEP IT UP...)");
    }
    // loc_4B0F6:              ; CODE XREF: handleStatisticsOptionClick+1DBj
    else
    {
        char averageTimeMessage[44] = "";
        sprintf(averageTimeMessage, "AVERAGE TIME USED PER LEVEL  %s MINUTES", averageTimeString);
        drawTextWithChars6FontWithTransparentBackgroundIfPossible(32, 140, 15, averageTimeMessage);
    }
    // loc_4B105:              ; CODE XREF: handleStatisticsOptionClick+1D4j
    //                 ; handleStatisticsOptionClick+1E8j
    fadeToPalette(gInformationScreenPalette);
    waitForKeyMouseOrJoystick();
    fadeToPalette(gBlackPalette);
    memcpy(gScreenPixels, screenPixelsBackup, kFullScreenFramebufferLength);
    fadeToPalette(gGamePalette);

    free(screenPixelsBackup);
}

void handleGfxTutorOptionClick() // sub_4B149   proc near
{
    drawGfxTutorBackground(gScrollDestinationScreenBitmapData);
    scrollRightToNewScreen();
    waitForKeyMouseOrJoystick();
    scrollLeftToMainMenu();
    drawMenuTitleAndDemoLevelResult();
}

void handleDemoOptionClick() // sub_4B159   proc near       ; CODE XREF: runMainMenu+6Fp
{
    // 01ED:44F6
    if (readDemoFiles() == 0)
    {
        return;
    }

    // loc_4B163:              ; CODE XREF: handleDemoOptionClick+5j
    gShouldLeaveMainMenu = 1;
    gIsPlayingDemo = 1;

    uint8_t numberOfDemos = 0;

    uint8_t idx = 0;
    do
    {
        // loc_4B17A:              ; CODE XREF: handleDemoOptionClick+2Dj
        if (gDemos.demoFirstIndices[idx] == 0xFFFF)
        {
            break;
        }
        idx++;
        numberOfDemos++;
    } while (1);
    // 01ED:4525

    // loc_4B188:              ; CODE XREF: handleDemoOptionClick+2Aj
    //  This picks a random demo
    generateRandomSeedFromClock();
    uint16_t demoIndex = generateRandomNumber() % numberOfDemos;
    uint16_t demoFirstIndex = gDemos.demoFirstIndices[demoIndex];

    // This only happens if there are no demos...
    if (demoFirstIndex == 0xFFFF)
    {
        gShouldLeaveMainMenu = 0;
        gIsPlayingDemo = 0;
    }

    // loc_4B1AE:              ; CODE XREF: handleDemoOptionClick+48j
    uint8_t demoLevelNumber = gDemos.demoData[demoFirstIndex];
    uint8_t finalLevelNumber = demoIndex;

    gSelectedOriginalDemoIndex = demoIndex;
    gSelectedOriginalDemoLevelNumber = 0;

    // This checks if the level number has its MSB to 0 and is a valid level number (1-111) for the original DEMO format
    if (demoLevelNumber <= 0x6F // 111
        && demoLevelNumber != 0)
    {
        gSelectedOriginalDemoLevelNumber = (gSelectedOriginalDemoLevelNumber & 0xFF00) | demoLevelNumber; // mov byte ptr gSelectedOriginalDemoLevelNumber, al
        finalLevelNumber = demoLevelNumber;
    }

    // loc_4B1CF:              ; CODE XREF: handleDemoOptionClick+6Bj
    //                 ; handleDemoOptionClick+6Fj
    gRandomGeneratorSeed = gDemoRandomSeeds[demoIndex];
    gDemoIndexOrDemoLevelNumber = finalLevelNumber;

    demoFirstIndex++; // To skip the level number
    gDemoCurrentInputIndex = demoFirstIndex;
    word_5A33C = demoFirstIndex;
    gDemoCurrentInput = UserInputNone;
    gDemoCurrentInputRepeatCounter = 1;
}

void playDemo(uint16_t demoIndex) // demoSomething  proc near       ; CODE XREF: start+3BAp
                                  // ; runMainMenu+12Ep ...
{
    readDemoFiles();

    gRandomGeneratorSeed = gDemoRandomSeeds[demoIndex];
    gShouldLeaveMainMenu = 1;
    gIsPlayingDemo = 1;

    uint16_t demoFirstIndex = gDemos.demoFirstIndices[demoIndex];
    if (demoFirstIndex == 0xFFFF)
    {
        gShouldLeaveMainMenu = 0;
        gIsPlayingDemo = 0;
    }

    // loc_4B22F:              ; CODE XREF: playDemo+30j
    gSelectedOriginalDemoLevelNumber = 0;

    uint8_t demoLevelNumber = gDemos.demoData[demoFirstIndex];
    uint8_t finalLevelNumber = demoIndex;

    if (demoLevelNumber <= kNumberOfLevels // 111
        && demoLevelNumber != 0)
    {
        finalLevelNumber = demoLevelNumber;
        gSelectedOriginalDemoLevelNumber = (gSelectedOriginalDemoLevelNumber & 0xFF00) | finalLevelNumber; // mov byte ptr gSelectedOriginalDemoLevelNumber, al
    }

    // loc_4B248:              ; CODE XREF: playDemo+4Bj
    //                 ; playDemo+4Fj
    gDemoIndexOrDemoLevelNumber = finalLevelNumber;

    demoFirstIndex++; // To skip the level number
    gDemoCurrentInputIndex = demoFirstIndex;
    word_5A33C = demoFirstIndex;
    gDemoCurrentInput = UserInputNone;
    gDemoCurrentInputRepeatCounter = 1;
}

void handleRankingListScrollUp() // loc_4B262
{
    gRankingListButtonPressed = 1;
    gRankingListDownButtonPressed = 0;
    gRankingListUpButtonPressed = 1;

    if (gFrameCounter - gRankingListThrottleCurrentCounter < gRankingListThrottleNextCounter)
    {
        return;
    }

    // loc_4B27F:              ; CODE XREF: code:465Cj
    restoreLastMouseAreaBitmap();
    gRankingListThrottleNextCounter = gFrameCounter;
    if (gRankingListThrottleCurrentCounter > 1)
    {
        gRankingListThrottleCurrentCounter--;
    }

    // loc_4B293:              ; CODE XREF: code:466Dj
    if (gIsForcedCheatMode == 0 && byte_58D46 > 0)
    {
        byte_58D46--;
    }

    // loc_4B2A5:              ; CODE XREF: code:4678j code:467Fj
    drawRankings();
    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void handleRankingListScrollDown() // loc_4B2AF
{
    gRankingListButtonPressed = 1;
    gRankingListDownButtonPressed = 1;
    gRankingListUpButtonPressed = 0;

    if (gFrameCounter - gRankingListThrottleCurrentCounter < gRankingListThrottleNextCounter)
    {
        return;
    }

    // loc_4B2CC:              ; CODE XREF: code:46A9j
    restoreLastMouseAreaBitmap();
    gRankingListThrottleNextCounter = gFrameCounter;
    if (gRankingListThrottleCurrentCounter > 1)
    {
        gRankingListThrottleCurrentCounter--;
    }

    // loc_4B2E0:              ; CODE XREF: code:46BAj
    if (gIsForcedCheatMode == 0 && byte_58D46 < kNumberOfPlayers - 1)
    {
        byte_58D46++;
    }

    // loc_4B2F2:              ; CODE XREF: code:46C5j code:46CCj
    drawRankings();
    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void showCongratulationsScreen() // sub_4B2FC   proc near       ; CODE XREF: handleOkButtonClick+56p
{
    fadeToPalette(gBlackPalette);

    uint8_t *screenPixelsBackup = malloc(kFullScreenFramebufferLength);
    memcpy(screenPixelsBackup, gScreenPixels, kFullScreenFramebufferLength);

    drawBackBackground();
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(120, 30, 15, "CONGRATULATIONS");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(24, 70, 15, "YOU HAVE COMPLETED ALL 111 LEVELS OF SUPAPLEX");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(64, 85, 15, "YOUR BRAIN IS IN FANTASTIC SHAPE");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(40, 100, 15, "NOT MANY PEOPLE ARE ABLE TO MANAGE THIS");
    fadeToPalette(gInformationScreenPalette);
    waitForKeyMouseOrJoystick();
    fadeToPalette(gBlackPalette);
    memcpy(gScreenPixels, screenPixelsBackup, kFullScreenFramebufferLength);

    fadeToPalette(gGamePalette); // 6015h

    free(screenPixelsBackup);
}

void handleOkButtonClick() // sub_4B375  proc near       ; CODE XREF: runMainMenu+11Ep
{
    // 01ED:4712
    PlayerEntry currentPlayerEntry = gPlayerListData[gCurrentPlayerIndex];

    if (strcmp(currentPlayerEntry.name, "--------") == 0)
    {
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "NO PLAYER SELECTED     ");
        return;
    }

    // loc_4B3A4:              ; CODE XREF: handleOkButtonClick+12j
    //                 ; handleOkButtonClick+17j ...
    if (gCurrentSelectedLevelIndex == kLastLevelIndex)
    {
        // loc_4B3B4:              ; CODE XREF: handleOkButtonClick+3Cj
        uint8_t numberOfCompletedLevels = 0;

        for (int i = 0; i < kNumberOfLevels; ++i)
        {
            // loc_4B3BC:              ; CODE XREF: handleOkButtonClick+4Fj
            if (currentPlayerEntry.levelState[i] == PlayerLevelStateCompleted)
            {
                numberOfCompletedLevels++;
            }
            // loc_4B3C3:              ; CODE XREF: handleOkButtonClick+4Aj
        }
        if (numberOfCompletedLevels == kNumberOfLevels)
        {
            showCongratulationsScreen();
            return;
        }
        else
        {
            // loc_4B3CF:              ; CODE XREF: handleOkButtonClick+54j
            drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 2, "COLORBLIND I GUESS     ");
            return;
        }
    }
    else if (gCurrentSelectedLevelIndex > kNumberOfLevels)
    {
        return;
    }

    // loc_4B3DB:              ; CODE XREF: handleOkButtonClick+35j
    uint8_t currentLevelColor = gCurrentPlayerLevelData[gCurrentSelectedLevelIndex - 1];

    if (currentLevelColor == kBlockedLevelEntryColor)
    {
        // loc_4B404:              ; CODE XREF: handleOkButtonClick+70j
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 8, "COLORBLIND I GUESS     ");
        return;
    }
    gShouldLeaveMainMenu = 1;
    gIsPlayingDemo = 0;

    if (currentLevelColor == kCompletedLevelEntryColor)
    {
        gShouldUpdateTotalLevelTime = 0;
    }
    else
    {
        // loc_4B3FD:              ; CODE XREF: handleOkButtonClick+7Fj
        gShouldUpdateTotalLevelTime = 1;
    }

    // loc_4B40F:              ; CODE XREF: handleOkButtonClick+86j
    //                 ; handleOkButtonClick+8Dj
    prepareDemoRecordingFilename();                                           // 01ED:47AC
    convertLevelNumberTo3DigitStringWithPadding0(gCurrentSelectedLevelIndex); // 01ED:47B2
}

void throttledRotateLevelSet(uint8_t descending) // sub_4B419  proc near
{
    // 01ED:47B6
    // loc_4B433:              ; CODE XREF: sub_4B419+15j
    if (gFrameCounter - gLevelSetRotationThrottleCurrentCounter < gLevelSetRotationThrottleNextCounter)
    {
        return;
    }

    // loc_4B443:              ; CODE XREF: sub_4B419+25j
    gLevelSetRotationThrottleNextCounter = gFrameCounter;
    if (gLevelSetRotationThrottleCurrentCounter > 1)
    {
        gLevelSetRotationThrottleCurrentCounter--;
    }

    rotateLevelSet(descending);
}

void rotateLevelSet(uint8_t descending) // sub_4B419  proc near
{
    FILE *file = NULL;
    char currentSuffix[3] = "AT";

    do
    {
        // loc_4B454:              ; CODE XREF: sub_4B419+35j
        //                 ; sub_4B419+9Aj
        strcpy(currentSuffix, &gLevelsDatFilename[8]);

        if (descending)
        {
            // loc_4B482:              ; CODE XREF: sub_4B419+46j
            if (strcmp(currentSuffix, "AT") == 0) // "AT"
            {
                strcpy(currentSuffix, "99");
            }
            // loc_4B48C:              ; CODE XREF: sub_4B419+6Cj
            else if (strcmp(currentSuffix, "00") == 0) // "00"
            {
                strcpy(currentSuffix, "AT");
            }
            else
            {
                // loc_4B496:              ; CODE XREF: sub_4B419+76j
                currentSuffix[1]--;
                if (currentSuffix[1] < '0' && currentSuffix[0] > '0')
                {
                    currentSuffix[1] = '9'; // '9'
                    currentSuffix[0]--;
                }
            }
        }
        else
        {
            if (strcmp(currentSuffix, "AT") == 0) // "AT"
            {
                strcpy(currentSuffix, "00");
            }
            // loc_4B46B:              ; CODE XREF: sub_4B419+4Bj
            else if (strcmp(currentSuffix, "99") == 0) // "99"
            {
                strcpy(currentSuffix, "AT");
            }
            else
            {
                // loc_4B475:              ; CODE XREF: sub_4B419+55j
                currentSuffix[1]++;
                if (currentSuffix[1] > '9' && currentSuffix[0] < '9')
                {
                    currentSuffix[1] = '0'; // '0'
                    currentSuffix[0]++;
                }
            }
        }

        // loc_4B4A3:              ; CODE XREF: sub_4B419+50j
        //                 ; sub_4B419+5Aj ...
        strcpy(&gLevelsDatFilename[8], currentSuffix);

        file = openReadonlyFile(gLevelsDatFilename, "rb");
        if (file == NULL)
        {
            if (errno != ENOENT)
            {
                exitWithError("Error opening %s\n", gLevelsDatFilename);
            }
        }
    } while (file == NULL);

    // loc_4B4B8:              ; CODE XREF: sub_4B419+95j
    if (fclose(file) != 0)
    {
        exitWithError("Error closing %s\n", gLevelsDatFilename);
    }

    if (strcmp(currentSuffix, "AT") == 0)
    {
        strcpy(currentSuffix, "ST");
    }

    // loc_4B4D3:              ; CODE XREF: sub_4B419+B6j
    strcpy(&gLevelLstFilename[7], currentSuffix);
    strcpy(&gPlayerLstFilename[8], currentSuffix);
    strcpy(&gHallfameLstFilename[0xA], currentSuffix);

    if (strcmp(currentSuffix, "ST") == 0)
    {
        strcpy(currentSuffix, "IN");
    }

    // loc_4B4E4:              ; CODE XREF: sub_4B419+C6j
    strcpy(&gDemo0BinFilename[7], currentSuffix);

    if (strcmp(currentSuffix, "IN") == 0)
    {
        strcpy(currentSuffix, "AV");
    }

    // loc_4B4EF:              ; CODE XREF: sub_4B419+D1j
    if (gShouldAlwaysWriteSavegameSav == 0)
    {
        strcpy(&gSavegameSavFilename[0xA], currentSuffix);
    }

    // loc_4B504:              ; CODE XREF: sub_4B419+E6j
    readLevelsLst();
    readDemoFiles();

    // 01ED:48B2
    if (gIsForcedCheatMode != 0)
    {
        PlayerEntry *entry = &gPlayerListData[0];
        memset(entry->levelState, PlayerLevelStateSkipped, sizeof(entry->levelState));
    }
    else
    {
        // loc_4B52A:              ; CODE XREF: sub_4B419+101j
        for (int i = 0; i < kNumberOfPlayers; ++i)
        {
            PlayerEntry *entry = &gPlayerListData[i];
            // loc_4B531:              ; CODE XREF: sub_4B419+129j
            memset(entry, 0, sizeof(PlayerEntry));
            strcpy(entry->name, "--------");
        }

        for (int i = 0; i < kNumberOfHallOfFameEntries; ++i)
        {
            HallOfFameEntry *entry = &gHallOfFameData[i];

            // loc_4B54B:              ; CODE XREF: sub_4B419+143j
            memset(entry, 0, sizeof(HallOfFameEntry));
            strcpy(entry->playerName, "        ");
        }

        readHallfameLst();
        readPlayersLst();
    }

    updateMenuAfterLevelSetChanged();
}

void updateMenuAfterLevelSetChanged() // loc_4B565:              ; CODE XREF: sub_4B419+10Fj
{
    // loc_4B4C6:              ; CODE XREF: sub_4B419+A8j
    char message[] = "     LEVEL SET ??      ";
    char currentSuffix[3] = "AT";
    strcpy(currentSuffix, &gLevelsDatFilename[8]);

    memcpy(&message[0xF], currentSuffix, 2);

    // loc_4B4F9:              ; CODE XREF: sub_4B419+DBj
    if (strcmp(currentSuffix, "AT") == 0)
    {
        strcpy(message, "  SUPAPLEX LEVEL SET   ");
    }

    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 4, message);

    gShouldAutoselectNextLevelToPlay = 1;
    prepareLevelDataForCurrentPlayer();
    drawPlayerList();
    drawLevelList();
    drawHallOfFame();
    drawRankings();
    restoreLastMouseAreaBitmap();
    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void handleFloppyDiskButtonClick()
{
    // Pressing the shift key will show the level sets in descending order
    throttledRotateLevelSet(gIsRightShiftPressed || gIsLeftShiftPressed);
}

void handlePlayerListScrollDown() // sub_4B671  proc near
{
    gPlayerListButtonPressed = 1;
    gPlayerListDownButtonPressed = 1;
    gPlayerListUpButtonPressed = 0;

    if (gFrameCounter - gPlayerListThrottleCurrentCounter < gPlayerListThrottleNextCounter)
    {
        return;
    }

    // loc_4B68E:              ; CODE XREF: handlePlayerListScrollDown+1Aj
    gPlayerListThrottleNextCounter = gFrameCounter;
    if (gPlayerListThrottleCurrentCounter > 1)
    {
        gPlayerListThrottleCurrentCounter--;
    }

    // loc_4B69F:              ; CODE XREF: handlePlayerListScrollDown+28j
    if (gIsForcedCheatMode == 0 && gCurrentPlayerIndex < kNumberOfPlayers - 1)
    {
        gCurrentPlayerIndex++;
    }

    // loc_4B6B1:              ; CODE XREF: handlePlayerListScrollDown+33j
    //                 ; handlePlayerListScrollDown+3Aj
    restoreLastMouseAreaBitmap();
    gShouldAutoselectNextLevelToPlay = 1;
    prepareLevelDataForCurrentPlayer();
    drawPlayerList();
    drawLevelList();
    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void handlePlayerListScrollUp() // sub_4B6C9  proc near
{
    gPlayerListButtonPressed = 1;
    gPlayerListDownButtonPressed = 0;
    gPlayerListUpButtonPressed = 1;

    if (gFrameCounter - gPlayerListThrottleCurrentCounter < gPlayerListThrottleNextCounter)
    {
        return;
    }

    // loc_4B6E6:              ; CODE XREF: handlePlayerListScrollUp+1Aj
    gPlayerListThrottleNextCounter = gFrameCounter;
    if (gPlayerListThrottleCurrentCounter > 1)
    {
        gPlayerListThrottleCurrentCounter--;
    }

    // loc_4B6F7:              ; CODE XREF: handlePlayerListScrollUp+28j
    if (gIsForcedCheatMode == 0 && gCurrentPlayerIndex > 0)
    {
        gCurrentPlayerIndex--;
    }

    // loc_4B709:              ; CODE XREF: handlePlayerListScrollUp+33j
    //                 ; handlePlayerListScrollUp+3Aj
    restoreLastMouseAreaBitmap(); // Clears mouse trail
    gShouldAutoselectNextLevelToPlay = 1;
    prepareLevelDataForCurrentPlayer();
    drawPlayerList();
    drawLevelList();
    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void handlePlayerListClick() // sub_4B721  proc near
{
    byte_58D46 = byte_58D47;
    drawRankings();
}

void handleLevelListScrollDown() // sub_4B72B  proc near
{
    gLevelListButtonPressed = 1;
    gLevelListDownButtonPressed = 1;
    gLevelListUpButtonPressed = 0;

    if (gFrameCounter - gLevelListThrottleCurrentCounter < gLevelListThrottleNextCounter)
    {
        return;
    }

    // loc_4B748:              ; CODE XREF: handleLevelListScrollDown+1Aj
    gLevelListThrottleNextCounter = gFrameCounter;
    if (gLevelListThrottleCurrentCounter > 1)
    {
        gLevelListThrottleCurrentCounter--;
    }

    // loc_4B759:              ; CODE XREF: handleLevelListScrollDown+28j
    if (gCurrentSelectedLevelIndex >= 113)
    {
        return;
    }
    gCurrentSelectedLevelIndex++;
    restoreLastMouseAreaBitmap();
    drawLevelList();
    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void handleLevelListScrollUp() // sub_4B771  proc near
{
    gLevelListButtonPressed = 1;
    gLevelListDownButtonPressed = 0;
    gLevelListUpButtonPressed = 1;

    if (gFrameCounter - gLevelListThrottleCurrentCounter < gLevelListThrottleNextCounter)
    {
        return;
    }

    // loc_4B78E:              ; CODE XREF: handleLevelListScrollUp+1Aj
    gLevelListThrottleNextCounter = gFrameCounter;
    if (gLevelListThrottleCurrentCounter > 1)
    {
        gLevelListThrottleCurrentCounter--;
    }

    // loc_4B79F:              ; CODE XREF: handleLevelListScrollUp+28j
    if (gCurrentSelectedLevelIndex <= 1)
    {
        return;
    }
    gCurrentSelectedLevelIndex--;
    restoreLastMouseAreaBitmap();
    drawLevelList();
    saveLastMouseAreaBitmap();
    drawMouseCursor();
    // locret_4B7B6:               ; CODE XREF: handleLevelListScrollUp+33j
}

void handleLevelCreditsClick() // sub_4B7B7  proc near
{
    fadeToPalette(gBlackPalette);

    uint8_t *screenPixelsBackup = malloc(kFullScreenFramebufferLength);
    memcpy(screenPixelsBackup, gScreenPixels, kFullScreenFramebufferLength);

    drawBackBackground();

    drawTextWithChars6FontWithTransparentBackgroundIfPossible(80, 10, 15, "SUPAPLEX  BY DREAM FACTORY");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(56, 40, 15, "ORIGINAL DESIGN BY PHILIP JESPERSEN");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(88, 50, 15, "AND MICHAEL STOPP");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(56, 90, 15, "NEARLY ALL LEVELS BY MICHEAL STOPP");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(64, 100, 15, "A FEW LEVELS BY PHILIP JESPERSEN");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(56, 110, 15, "HARDLY ANY LEVELS BY BARBARA STOPP");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(64, 170, 15, "NOTE: PRESS ENTER TO REMOVE PANEL");
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(64, 190, 15, "(C) DIGITAL INTEGRATION LTD 1991");
    fadeToPalette(gInformationScreenPalette);
    waitForKeyMouseOrJoystick();
    fadeToPalette(gBlackPalette);
    memcpy(gScreenPixels, screenPixelsBackup, kFullScreenFramebufferLength);
    fadeToPalette(gGamePalette);

    free(screenPixelsBackup);
}

void drawTextWithChars6FontWithOpaqueBackgroundIfPossible(size_t destX, size_t destY, uint8_t color, const char *text) // sub_4BA5F  proc near       ; CODE XREF: handleNewPlayerOptionClick+37p
                                                                                                                       //  ; handleNewPlayerOptionClick+4Ap ...
{
    // Parameters:
    // - di is the destination surface
    // - si is the text to be rendered
    // - ah is the color index in the current palette

    // Address: 01ED:4DFC
    if (gIsGameBusy == 1)
    {
        return;
    }

    drawTextWithChars6FontWithOpaqueBackground(destX, destY, color, text);
}

void drawTextWithChars6FontWithTransparentBackgroundIfPossible(size_t destX, size_t destY, uint8_t color, const char *text) // sub_4BDF0 proc near       ; CODE XREF: recoverFilesFromFloppyDisk+2Ap
                                                                                                                            // ; handleStatisticsOptionClick+EDp ...
{
    if (gIsGameBusy == 1)
    {
        return;
    }

    drawTextWithChars6FontWithTransparentBackground(destX, destY, color, text);
}

void convertLevelNumberTo3DigitStringWithPadding0(uint8_t number) // sub_4BF4A   proc near       ; CODE XREF: start+3F7p handleGameUserInput+398p ...
{
    convertNumberTo3DigitStringWithPadding0(number, &gSPDemoFileName[3]);
}

void convertNumberTo3DigitStringWithPadding0(uint8_t number, char numberString[3]) //  proc near       ; CODE XREF: handleSkipLevelOptionClick+7Cp
                                                                                   // ; handleStatisticsOptionClick+13Dp ...
{
    convertNumberTo3DigitPaddedString(number, numberString, 0);
}

void convertNumberTo3DigitPaddedString(uint8_t number, char numberString[3], char useSpacesForPadding) // sub_4BF4F  proc near       ; CODE XREF: handleStatisticsOptionClick+16Fp
                                                                                                       // ; handleStatisticsOptionClick+1BFp ...
{
    // This function converts a number to a 3-digit string, so basically 123 to "123".
    // It also adds padding to the left, so 7 is converted to "007", with the option
    // to turn 0's in padding into spaces: 7 to "  7"
    // Parameters in the original implementation:
    // - al: number to convert
    // - ah: can be ' ' (space) or anything else. When it's ' ', the padding will be done with spaces, otherwise 0's will be used.
    // - si: the string to write the result

    numberString[0] = (number / 100) + '0';
    numberString[1] = ((number % 100) / 10) + '0';
    numberString[2] = (number % 10) + '0';

    if (useSpacesForPadding)
    {
        if (numberString[0] == '0')
        {
            numberString[0] = ' ';

            if (numberString[1] == '0')
            {
                numberString[1] = ' ';
            }
        }
    }
}

void prepareRankingTextEntries() // sub_4BF8D  proc near       ; CODE XREF: drawRankingsp
{
    // 01ED:532A
    if (gIsForcedCheatMode != 0)
    {
        return;
    }

    typedef struct
    {
        uint8_t playerIndex;
        uint8_t nextLevelToPlay;
        uint8_t hours;
        uint8_t minutes;
        uint8_t seconds;
    } RankingEntry;

    RankingEntry rankingEntries[kNumberOfPlayers];

    for (int i = 0; i < 20; ++i)
    {
        // loc_4BFA2:              ; CODE XREF: prepareRankingTextEntries+38j
        RankingEntry *rankingEntry = &rankingEntries[i];
        PlayerEntry *player = &gPlayerListData[i];

        rankingEntry->playerIndex = i;
        rankingEntry->nextLevelToPlay = player->nextLevelToPlay;
        rankingEntry->hours = player->hours;
        rankingEntry->minutes = player->minutes;
        rankingEntry->seconds = player->seconds;
    }

    // loc_4BFC7:              ; CODE XREF: prepareRankingTextEntries+B4j
    uint8_t numberOfChanges = 0;

    do
    {
        numberOfChanges = 0;

        for (int i = 0; i < kNumberOfPlayers - 1; ++i)
        {
            // loc_4BFD3:              ; CODE XREF: prepareRankingTextEntries+AFj
            RankingEntry *rankingEntry = &rankingEntries[i];
            RankingEntry *nextRankingEntry = &rankingEntries[i + 1];

            uint32_t totalSeconds = rankingEntry->hours * 3600 + rankingEntry->minutes * 60 + rankingEntry->seconds;
            uint32_t nextTotalSeconds = rankingEntry->hours * 3600 + rankingEntry->minutes * 60 + rankingEntry->seconds;

            if (nextRankingEntry->nextLevelToPlay > rankingEntry->nextLevelToPlay || (nextRankingEntry->nextLevelToPlay == rankingEntry->nextLevelToPlay && nextTotalSeconds > totalSeconds))
            {
                // loc_4BFFD:              ; CODE XREF: prepareRankingTextEntries+4Ej
                //                 ; prepareRankingTextEntries+58j ...
                RankingEntry aux = *nextRankingEntry;
                *nextRankingEntry = *rankingEntry;
                *rankingEntry = aux;
                numberOfChanges++;
            }
        }
    } while (numberOfChanges > 0);

    for (int i = 0; i < 20; ++i)
    {
        // loc_4C04B:              ; CODE XREF: prepareRankingTextEntries+CFj
        if (rankingEntries[i].playerIndex == gCurrentPlayerIndex)
        {
            byte_58D47 = i;
        }
    }

    // loc_4C061:
    //     di = 0x883C; <- entry 2 of gRankingTextEntries

    for (int i = 0; i < 20; ++i)
    {
        RankingEntry *rankingEntry = &rankingEntries[i];
        char *textEntry = gRankingTextEntries[i + 2]; // No idea why the first two are always empty
                                                      // loc_4C067:              ; CODE XREF: prepareRankingTextEntries+14Dj
        if (rankingEntry->nextLevelToPlay == 0x71)    // 113
        {
            textEntry[0] =
                textEntry[1] =
                    textEntry[2] = '9';
        }
        else
        {
            // loc_4C078:              ; CODE XREF: prepareRankingTextEntries+DFj
            convertNumberTo3DigitPaddedString(rankingEntry->nextLevelToPlay, textEntry, 0);
        }

        // loc_4C07F:              ; CODE XREF: prepareRankingTextEntries+E9j
        PlayerEntry playerEntry = gPlayerListData[rankingEntry->playerIndex];

        // loc_4C091:              ; CODE XREF: prepareRankingTextEntries+10Bj
        memcpy(&textEntry[4], playerEntry.name, sizeof(playerEntry.name) - 1);

        convertNumberTo3DigitStringWithPadding0(rankingEntry->seconds, &textEntry[19]);
        textEntry[19] = ':';

        convertNumberTo3DigitStringWithPadding0(rankingEntry->minutes, &textEntry[16]);
        textEntry[16] = ':';

        convertNumberTo3DigitStringWithPadding0(rankingEntry->hours, &textEntry[13]);
    }
}

void drawRankings() // sub_4C0DD   proc near       ; CODE XREF: handleNewPlayerOptionClick+1E9p
//                    ; handleDeletePlayerOptionClick+E2p ...
{
    // 01ED:547A
    prepareRankingTextEntries();

    const uint8_t kDistanceBetweenLines = 9;

    for (int i = 0; i < 5; ++i)
    {
        const uint8_t y = 110 + kDistanceBetweenLines * (i - 2);
        const uint8_t color = (i == 2 ? 6 : 8);
        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(8, y, color, gRankingTextEntries[byte_58D46 + i]);
    }

    char numberString[4] = "001"; // 0x8359
    convertNumberTo3DigitStringWithPadding0(byte_58D46 + 1, numberString);
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(144, 110, 6, &numberString[1]); // Remove the first (left most) digit
}

void drawLevelList() // sub_4C141  proc near       ; CODE XREF: start+41Ap handleGameUserInput+39Bp ...
{
    // 01ED:54DE
    byte_59821 = gCurrentPlayerLevelData[gCurrentSelectedLevelIndex - 2];
    byte_59822 = gCurrentPlayerLevelData[gCurrentSelectedLevelIndex - 1];
    byte_59823 = gCurrentPlayerLevelData[gCurrentSelectedLevelIndex];

    char *previousLevelName = (char *)&gLevelListData[(gCurrentSelectedLevelIndex - 2) * kListLevelNameLength];
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(144, 155, byte_59821, previousLevelName);

    char *currentLevelName = (char *)&gLevelListData[(gCurrentSelectedLevelIndex - 1) * kListLevelNameLength];
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(144, 164, byte_59822, currentLevelName);

    memcpy(gCurrentLevelName, currentLevelName, kListLevelNameLength);

    char *nextLevelName = (char *)&gLevelListData[gCurrentSelectedLevelIndex * kListLevelNameLength];
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(144, 173, byte_59823, nextLevelName);
}

void drawHallOfFame() // sub_4C1A9   proc near       ; CODE XREF: handleFloppyDiskButtonClick+15Ap
//                    ; drawMenuTitleAndDemoLevelResult+11p
{
    // 01ED:5546
    char text[19] = {'\0'};

    for (int i = 0; i < kNumberOfHallOfFameEntries; ++i)
    {
        // loc_4C1B7:              ; CODE XREF: drawHallOfFame+56j
        strcpy(text, "                  ");
        HallOfFameEntry entry = gHallOfFameData[i];

        convertNumberTo3DigitStringWithPadding0(entry.seconds, &text[15]);
        text[15] = ':';

        convertNumberTo3DigitStringWithPadding0(entry.minutes, &text[12]);
        text[12] = ':';

        convertNumberTo3DigitPaddedString(entry.hours, &text[9], 1);

        uint8_t playerNameLength = MIN(strlen(entry.playerName), sizeof(entry.playerName) - 1);
        memcpy(text, entry.playerName, playerNameLength);

        drawTextWithChars6FontWithOpaqueBackgroundIfPossible(184, 28 + i * 9, 8, text);
    }
}

void drawCurrentPlayerRanking() //   proc near       ; CODE XREF: drawPlayerList+5Bp // sub_4C224
{
    // 01ED:55C1
    PlayerEntry currentPlayerEntry = gPlayerListData[gCurrentPlayerIndex];
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 93, 8, currentPlayerEntry.name);

    char timeText[10] = "000:00:00";

    // Seconds
    convertNumberTo3DigitStringWithPadding0(currentPlayerEntry.seconds, &timeText[6]);
    timeText[6] = ':';

    // Minutes
    convertNumberTo3DigitStringWithPadding0(currentPlayerEntry.minutes, &timeText[3]);
    timeText[3] = ':';

    // Hours
    convertNumberTo3DigitStringWithPadding0(currentPlayerEntry.hours, timeText);

    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(224, 93, 8, timeText);

    char nextLevelText[4] = "000";
    convertNumberTo3DigitStringWithPadding0(currentPlayerEntry.nextLevelToPlay, nextLevelText);
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(288, 93, 8, nextLevelText);
}

void drawPlayerList() // sub_4C293  proc near       ; CODE XREF: start+32Cp start+407p ...
{
    // 01ED:5630
    PlayerEntry currentPlayer = gPlayerListData[gCurrentPlayerIndex];
    memcpy(gPlayerName, currentPlayer.name, sizeof(currentPlayer.name) - 1);
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(16, 164, 6, currentPlayer.name);

    char *prevPlayerName = "";

    if (gCurrentPlayerIndex <= 0)
    {
        prevPlayerName = "        "; // just 8 spaces :shrug:
    }
    else
    {
        prevPlayerName = gPlayerListData[gCurrentPlayerIndex - 1].name;
    }

    // loc_4C2CD:              // ; CODE XREF: drawPlayerList+35j
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(16, 155, 8, prevPlayerName);

    char *nextPlayerName = "";

    if (gCurrentPlayerIndex >= kNumberOfPlayers - 1) // 19
    {
        nextPlayerName = "        "; // just 8 spaces :shrug:
    }
    else
    {
        nextPlayerName = gPlayerListData[gCurrentPlayerIndex + 1].name;
    }

    // loc_4C2E6:              // ; CODE XREF: drawPlayerList+4Ej
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(16, 173, 8, nextPlayerName);
    drawCurrentPlayerRanking();
}

void drawMenuTitleAndDemoLevelResult() // sub_4C2F2   proc near       ; CODE XREF: handleGfxTutorOptionClick+Cp
                                       // ; sub_4C407+1Fp ...
{
    // 01ED:568F
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 4, "  WELCOME TO SUPAPLEX  ");
    drawPlayerList();
    drawLevelList();
    drawHallOfFame();
    drawRankings();
    if (byte_59B83 == 0)
    {
        return;
    }
    byte_59B83 = 0;

    char *message = "";
    if (byte_5A19B == 0)
    {
        if (gIsLevelStartedAsDemo == 0)
        {
            message = "     LEVEL FAILED      ";
        }
        else
        {
            message = "      DEMO FAILED      ";
        }
    }
    else
    {
        if (gIsLevelStartedAsDemo == 0)
        {
            message = "   LEVEL SUCCESSFUL    ";
        }
        else
        {
            message = "    DEMO SUCCESSFUL    ";
        }
    }

    // loc_4C33C:              // ; CODE XREF: drawMenuTitleAndDemoLevelResult+34j
    //  ; drawMenuTitleAndDemoLevelResult+39j ...
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(168, 127, 4, message);
    byte_5A19B = 0;
}

void prepareLevelDataForCurrentPlayer() // sub_4C34A   proc near       ; CODE XREF: start+404p handleNewPlayerOptionClick+1E0p ...
{
    // 01ED:56E7
    PlayerEntry *currentPlayerEntry = &gPlayerListData[gCurrentPlayerIndex];

    uint8_t *currentPlayerLevelState = currentPlayerEntry->levelState;

    // Sets everything to 6 which seems to mean all levels are blocked
    memset(gCurrentPlayerPaddedLevelData, kSkippedLevelEntryColor, kNumberOfLevelsWithPadding);
    memset(gCurrentPlayerLevelData, kBlockedLevelEntryColor, kCurrentPlayerkLevelDataLength);

    char isFirstUncompletedLevel = 1;

    for (int i = 0; i < kNumberOfLevels; ++i)
    {
        // loc_4C373:              // ; CODE XREF: prepareLevelDataForCurrentPlayer+53j
        if (currentPlayerLevelState[i] == PlayerLevelStateSkipped)
        {
            gCurrentPlayerLevelData[i] = kSkippedLevelEntryColor;
        }
        // loc_4C37F:              // ; CODE XREF: prepareLevelDataForCurrentPlayer+2Ej
        else if (currentPlayerLevelState[i] == PlayerLevelStateCompleted) // Completed levels
        {
            gCurrentPlayerLevelData[i] = kCompletedLevelEntryColor;
        }
        // loc_4C389:              // ; CODE XREF: prepareLevelDataForCurrentPlayer+38j
        else if (currentPlayerLevelState[i] == PlayerLevelStateNotCompleted) // Levels not completed
        {
            if (isFirstUncompletedLevel == 1)
            {
                // The first uncompleted is not blocked
                gCurrentPlayerLevelData[i] = kNotCompletedLevelEntryColor;
            }
            else
            {
                // The rest uncompleted levels are blocked
                gCurrentPlayerLevelData[i] = kBlockedLevelEntryColor;
            }
            isFirstUncompletedLevel = 0;
        }
    }

    char hasCompletedAllLevels = 1;
    uint8_t nextLevelToPlay = 1;

    // Looks for the first uncompleted level
    for (int i = 0; i < kNumberOfLevels; ++i)
    {
        // loc_4C3A7:              // ; CODE XREF: prepareLevelDataForCurrentPlayer+65j
        if (currentPlayerLevelState[i] == PlayerLevelStateNotCompleted) // not completed
        {
            hasCompletedAllLevels = 0;
            break;
        }

        nextLevelToPlay++;
    }

    if (hasCompletedAllLevels == 1)
    {
        nextLevelToPlay = 1;

        // Looks for the first completed level
        for (int i = 0; i < kNumberOfLevels; ++i)
        {
            // loc_4C3BA:              // ; CODE XREF: prepareLevelDataForCurrentPlayer+78j
            if (currentPlayerLevelState[i] == PlayerLevelStateSkipped)
            {
                hasCompletedAllLevels = 0;
                break;
            }
            nextLevelToPlay++;
        }
    }

    if (hasCompletedAllLevels == 1)
    {
        if (gShouldAutoselectNextLevelToPlay != 0)
        {
            gCurrentSelectedLevelIndex = kLastLevelIndex;
        }

        // loc_4C3D1:              // ; CODE XREF: prepareLevelDataForCurrentPlayer+7Fj
        currentPlayerEntry->nextLevelToPlay = kLastLevelIndex;
        return;
    }

    // loc_4C3D6:              // ; CODE XREF: prepareLevelDataForCurrentPlayer+61j
    //  ; prepareLevelDataForCurrentPlayer+74j
    if (gShouldAutoselectNextLevelToPlay != 0)
    {
        gCurrentSelectedLevelIndex = nextLevelToPlay;
    }

    // loc_4C3E1:              // ; CODE XREF: prepareLevelDataForCurrentPlayer+91j
    if (nextLevelToPlay == 1)
    {
        if (strcmp(currentPlayerEntry->name, "--------") == 0)
        {
            nextLevelToPlay = 0;
        }
    }

    // loc_4C403:              // ; CODE XREF: prepareLevelDataForCurrentPlayer+9Aj
    //  ; prepareLevelDataForCurrentPlayer+A0j ...
    currentPlayerEntry->nextLevelToPlay = nextLevelToPlay; // 0x7e = 126
}

void sub_4C407() //   proc near       ; CODE XREF: runMainMenu+5Dp
{
    // 01ED:57A4
    if (gLevelFailed != 0)
    {
        gLevelFailed = 0;
        drawFailedLevelResultScreen(); // 01ED:57B5
        drawMenuBackground();          // 01ED:57B8
        gShouldAutoselectNextLevelToPlay = 0;
        prepareLevelDataForCurrentPlayer();
        drawMenuTitleAndDemoLevelResult();
        //    mov si, 6015h
        fadeToPalette(gGamePalette);

        videoLoop();

        // This will prevent to leave traces of the options menu
        // area in the main menu.
        //
        saveLastMouseAreaBitmap();
    }
    else
    {
        // loc_4C449:              ; CODE XREF: sub_4C407+3Aj
        scrollLeftToMainMenu();
    }
}

void scrollLeftToMainMenu() // loc_4C44F:              ; CODE XREF: handleGfxTutorOptionClick+9p
{
    uint8_t *currentScreenPixels = malloc(kFullScreenFramebufferLength);
    memcpy(currentScreenPixels, gScreenPixels, kFullScreenFramebufferLength);

    drawMenuBackground();
    gShouldAutoselectNextLevelToPlay = 0;

    prepareLevelDataForCurrentPlayer();
    drawMenuTitleAndDemoLevelResult();

    uint8_t *menuScreenPixels = malloc(kFullScreenFramebufferLength);
    memcpy(menuScreenPixels, gScreenPixels, kFullScreenFramebufferLength);

    const int kNumberOfSteps = 80;

    const uint32_t kAnimationDuration = kNumberOfSteps * 1000 / 70; // ~571 ms
    uint32_t animationTime = 0;

    startTrackingRenderDeltaTime();

    // Draws the current scroll animation step
    while (animationTime < kAnimationDuration)
    {
        animationTime += updateRenderDeltaTime();
        animationTime = MIN(animationTime, kAnimationDuration);

        float animationFactor = (float)animationTime / kAnimationDuration;

        int limitFromLeft = animationFactor * kScreenWidth;
        int limitFromRight = kScreenWidth - limitFromLeft;

        for (int y = 0; y < kScreenHeight; ++y)
        {
            // Main menu side
            for (int x = 0; x < kScreenWidth - limitFromRight; ++x)
            {
                gScreenPixels[y * kScreenWidth + x] = menuScreenPixels[y * kScreenWidth + x + limitFromRight];
            }

            // GFX background side
            for (int x = limitFromLeft; x < kScreenWidth; ++x)
            {
                gScreenPixels[y * kScreenWidth + x] = currentScreenPixels[y * kScreenWidth + x - limitFromLeft];
            }
        }

        // loc_4C466:              ; CODE XREF: sub_4C407+90j
        videoLoop();
    }

    free(currentScreenPixels);
    free(menuScreenPixels);

    // loc_4C499:              ; CODE XREF: sub_4C407+28j
    //  This will prevent to leave traces of the options menu
    //  area in the main menu.
    //
    saveLastMouseAreaBitmap();
}

void drawFailedLevelResultScreen() // sub_4C4F9   proc near       ; CODE XREF: sub_4C407+11p
{
    setPalette(gBlackPalette);
    drawBackBackground();

    drawTextWithChars6FontWithTransparentBackgroundIfPossible(128, 60, 0xF, "HARD LUCK!");
    if (gNumberOfRemainingInfotrons == 0)
    {
        drawTextWithChars6FontWithTransparentBackgroundIfPossible(40, 80, 0xF, "YOU COMPLETED ALL THE NECESSARY INFOTRONS");
        drawTextWithChars6FontWithTransparentBackgroundIfPossible(72, 100, 0xF, "BUT FAILED TO REACH THE EXIT");
    }
    else
    {
        // loc_4C52C:              ; CODE XREF: drawFailedLevelResultScreen+19j
        char message[] = "YOU HAVE COLLECTED ??? OUT OF THE ???";

        uint8_t collectedInfotrons = gTotalNumberOfInfotrons - gNumberOfRemainingInfotrons;
        convertNumberTo3DigitPaddedString(collectedInfotrons, &message[19], 1);

        convertNumberTo3DigitPaddedString(gTotalNumberOfInfotrons, &message[34], 1);

        drawTextWithChars6FontWithTransparentBackgroundIfPossible(40, 80, 0xF, message);
        drawTextWithChars6FontWithTransparentBackgroundIfPossible(104, 100, 0xF, "INFOTRONS NEEDED");
    }

    // loc_4C55C:              ; CODE XREF: drawFailedLevelResultScreen+31j
    drawTextWithChars6FontWithTransparentBackgroundIfPossible(72, 120, 0xF, "WHY NOT GIVE IT ANOTHER TRY?");

    videoLoop();
    setPalette(gInformationScreenPalette);
    if (gShouldExitGame != 1)
    {
        waitForKeyMouseOrJoystick();
    }

    // loc_4C591:              ; CODE XREF: drawFailedLevelResultScreen+93j
    setPalette(gBlackPalette);
}

void scrollRightToNewScreen() // sub_4C5AF   proc near       ; CODE XREF: handleGfxTutorOptionClick+3p
{
    videoLoop();

    uint8_t *screenPixelsBackup = malloc(kFullScreenFramebufferLength);
    memcpy(screenPixelsBackup, gScreenPixels, kFullScreenFramebufferLength);

    const int kNumberOfSteps = 80;

    const uint32_t kAnimationDuration = kNumberOfSteps * 1000 / 70; // ~571 ms
    uint32_t animationTime = 0;

    startTrackingRenderDeltaTime();

    // Draws the current scroll animation step
    while (animationTime < kAnimationDuration)
    {
        animationTime += updateRenderDeltaTime();
        animationTime = MIN(animationTime, kAnimationDuration);

        float animationFactor = (float)animationTime / kAnimationDuration;

        int limitFromRight = animationFactor * kScreenWidth;
        int limitFromLeft = kScreenWidth - limitFromRight;

        for (int y = 0; y < kScreenHeight; ++y)
        {
            // Main menu side
            for (int x = 0; x < kScreenWidth - limitFromRight; ++x)
            {
                gScreenPixels[y * kScreenWidth + x] = screenPixelsBackup[y * kScreenWidth + x + limitFromRight];
            }

            // GFX background side
            for (int x = limitFromLeft; x < kScreenWidth; ++x)
            {
                gScreenPixels[y * kScreenWidth + x] = gScrollDestinationScreenBitmapData[y * kScreenWidth + x - limitFromLeft];
            }
        }

        // loc_4C5BA:              ; CODE XREF: scrollRightToGfxTutor+3Cj
        videoLoop();
    }

    free(screenPixelsBackup);
}

void handleOptionsStandardClick() // sub_4C705  proc near       ; CODE XREF: code:5ADBp
{
    activateInternalStandardSound();
    playExplosionSound();
    drawSoundTypeOptionsSelection(gScreenPixels);
}

void handleOptionsInternalClick() // loc_4C6FB
{
    handleOptionsStandardClick();
    playExplosionSound();
    drawSoundTypeOptionsSelection(gScreenPixels);
}

void handleOptionsSamplesClick() // sub_4C70F  proc near
{
    activateInternalSamplesSound();
    playExplosionSound();
    drawSoundTypeOptionsSelection(gScreenPixels);
}

void handleOptionsSoundBlasterClick() // sub_4C719  proc near
{
    activateSoundBlasterSound();
    playExplosionSound();
    drawSoundTypeOptionsSelection(gScreenPixels);
}

void handleOptionsAdlibClick() // sub_4C723  proc near
{
    activateAdlibSound();
    playExplosionSound();
    drawSoundTypeOptionsSelection(gScreenPixels);
}

void handleOptionsRolandClick() // sub_4C72D  proc near
{
    activateRolandSound();
    playExplosionSound();
    drawSoundTypeOptionsSelection(gScreenPixels);
}

void handleOptionsCombinedClick() // sub_4C737  proc near
{
    activateCombinedSound();
    playExplosionSound();
    drawSoundTypeOptionsSelection(gScreenPixels);
}

void handleOptionsMusicClick() // sub_4C741   proc near
{
    if (isMusicEnabled == 1)
    {
        stopMusic();
        isMusicEnabled = 0;
    }
    else
    {
        // loc_4C752:              ; CODE XREF: handleOptionsAdlibClick+5j
        isMusicEnabled = 1;
        playMusicIfNeeded();
    }

    // loc_4C75A:              ; CODE XREF: handleOptionsAdlibClick+Fj
    drawAudioOptionsSelection(gScreenPixels);
    return;
}

void handleOptionsFXClick() // loc_4C75E
{
    if (isFXEnabled == 1)
    {
        isFXEnabled = 0;
    }
    else
    {
        // loc_4C76C:              ; CODE XREF: code:5B43j
        isFXEnabled = 1;
        playExplosionSound();
    }

    // loc_4C774:              ; CODE XREF: code:5B4Aj
    drawAudioOptionsSelection(gScreenPixels);
}

void handleOptionsKeyboardClick() // loc_4C778
{
    isJoystickEnabled = 0;
    drawInputOptionsSelection(gScreenPixels);
}

void handleOptionsJoystickClick() // loc_4C781
{
    isJoystickEnabled = 1;
    // calibrateJoystick(); not needed anymore
    drawInputOptionsSelection(gScreenPixels);
}

void handleOptionsExitAreaClick() // loc_4C78D
{
    word_58463 = 1;
}

void runMainMenu() // proc near       ; CODE XREF: start+43Ap
{
    // 01ED:5B31
    gIsInMainMenu = 1;
    gHasUserInterruptedDemo = 0;
    gSelectedOriginalDemoLevelNumber = 0;
    gIsSPDemoAvailableToRun = 0;
    gAutomaticDemoPlaybackCountdown = 4200;
    if (word_58467 != 0)
    {
        drawMenuBackground(); // 01ED:5B4E
        gShouldAutoselectNextLevelToPlay = 1;
        prepareLevelDataForCurrentPlayer(); // 01ED:5B56
        drawMenuTitleAndDemoLevelResult();  // 01ED:5B59

        videoLoop();
        fadeToPalette(gGamePalette); // 6015h
        word_58467 = 0;
    }
    else
    {
        // loc_4C7EC:              // ; CODE XREF: runMainMenu+1Bj
        byte_59B83 = 1;
        sub_4C407(); // 01ED:5B8E
    }

    // loc_4C7F4:              // ; CODE XREF: runMainMenu+56j
    playMusicIfNeeded(); // 01ED:5B91
    saveLastMouseAreaBitmap();
    drawMouseCursor();

    while (1)
    {
        int9handler(0);

        // loc_4C7FD:              // ; CODE XREF: runMainMenu+121j
        //  ; runMainMenu+219j ...
        gAutomaticDemoPlaybackCountdown--;
        if (gAutomaticDemoPlaybackCountdown == 0)
        {
            handleDemoOptionClick();
        }

        // loc_4C806:              // ; CODE XREF: runMainMenu+6Dj
        if (gShouldLeaveMainMenu != 0)
        {
            gShouldLeaveMainMenu = 0;
            break;
        }

        // loc_4C81A:              // ; CODE XREF: runMainMenu+77j
        videoLoop();

        gFrameCounter++;
        uint16_t mouseX, mouseY;
        uint16_t mouseButtonStatus;
        getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
        gMouseButtonStatus = mouseButtonStatus;
        if (gMouseX != mouseX || gMouseY != mouseY)
        {
            // loc_4C834:              // ; CODE XREF: runMainMenu+98j
            gAutomaticDemoPlaybackCountdown = 4200;
        }

        // loc_4C83A:              // ; CODE XREF: runMainMenu+9Ej
        gMouseX = mouseX;
        gMouseY = mouseY;
        restoreLastMouseAreaBitmap(); // 01ED:5BDF
        saveLastMouseAreaBitmap();    // 01ED:5BE2
        drawMouseCursor();            // 01ED:5BE5 Draws mouse cursor too?
        drawMainMenuButtonBorders();  // 01ED:5BE8
        updateUserInput();
        if (gPlayerListDownButtonPressed != 0 || gPlayerListUpButtonPressed != 0)
        {
            // loc_4C862:              // ; CODE XREF: runMainMenu+C5j
            gPlayerListButtonPressed = 1;
        }

        // loc_4C867:              // ; CODE XREF: runMainMenu+CCj
        gPlayerListDownButtonPressed = 0;
        gPlayerListUpButtonPressed = 0;
        if (gRankingListDownButtonPressed != 0 || gRankingListUpButtonPressed != 0)
        {
            // loc_4C87F:              // ; CODE XREF: runMainMenu+E2j
            gRankingListButtonPressed = 1;
        }

        // loc_4C884:              // ; CODE XREF: runMainMenu+E9j
        gRankingListDownButtonPressed = 0;
        gRankingListUpButtonPressed = 0;
        if (gLevelListDownButtonPressed != 0 || gLevelListUpButtonPressed != 0)
        {
            // loc_4C89C:              // ; CODE XREF: runMainMenu+FFj
            gLevelListButtonPressed = 1;
        }

        // loc_4C8A1:              // ; CODE XREF: runMainMenu+106j
        gLevelListDownButtonPressed = 0;
        gLevelListUpButtonPressed = 0;
        if (gCurrentUserInput > kUserInputSpaceAndDirectionOffset || isStartButtonPressed())
        {
            handleOkButtonClick();
        }
        // loc_4C8B8:              // ; CODE XREF: runMainMenu+11Cj
        else if (gIsF1KeyPressed == 1)
        {
            playDemo(0);
        }
        // loc_4C8C8:              // ; CODE XREF: runMainMenu+129j
        else if (gIsF2KeyPressed == 1)
        {
            playDemo(1);
        }
        // loc_4C8D8:              // ; CODE XREF: runMainMenu+139j
        else if (gIsF3KeyPressed == 1)
        {
            playDemo(2);
        }
        // loc_4C8E8:              // ; CODE XREF: runMainMenu+149j
        else if (gIsF4KeyPressed == 1)
        {
            playDemo(3);
        }
        // loc_4C8F8:              // ; CODE XREF: runMainMenu+159j
        else if (gIsF5KeyPressed == 1)
        {
            playDemo(4);
        }
        // loc_4C908:              // ; CODE XREF: runMainMenu+169j
        else if (gIsF6KeyPressed == 1)
        {
            playDemo(5);
        }
        // loc_4C918:              // ; CODE XREF: runMainMenu+179j
        else if (gIsF7KeyPressed == 1)
        {
            playDemo(6);
        }
        // loc_4C928:              // ; CODE XREF: runMainMenu+189j
        else if (gIsF8KeyPressed == 1)
        {
            playDemo(7);
        }
        // loc_4C937:              // ; CODE XREF: runMainMenu+199j
        else if (gIsF9KeyPressed == 1)
        {
            playDemo(8);
        }
        // loc_4C946:              // ; CODE XREF: runMainMenu+1A8j
        else if (gIsF10KeyPressed == 1)
        {
            playDemo(9);
        }
        // loc_4C955:              // ; CODE XREF: runMainMenu+1B7j
        else if (gIsNumpadDividePressed == 1 && strlen(demoFileName) != 0 && fileIsDemo == 1)
        {
            gIsSPDemoAvailableToRun = 1;
            playDemo(0);
        }
        // loc_4C977:              // ; CODE XREF: runMainMenu+1C6j
        //  ; runMainMenu+1CDj ...
        else if (gIsF12KeyPressed == 1 && strlen(demoFileName) != 0)
        {
            gIsSPDemoAvailableToRun = 1;
            gShouldLeaveMainMenu = 1;
            gIsPlayingDemo = 0;
            gShouldUpdateTotalLevelTime = 0;
            gHasUserCheated = 1;
            prepareDemoRecordingFilename();
            // This adds dashes to the level name or something?
            gSPDemoFileName[3] = 0x2D; // '-' ; "001$0.SP"
            gSPDemoFileName[4] = 0x2D; // '-' ; "01$0.SP"
            gSPDemoFileName[5] = 0x2D; // '-' ; "1$0.SP"
            continue;
        }
        // loc_4C9B0:              // ; CODE XREF: runMainMenu+131j
        //  ; runMainMenu+141j ...
        // if (gMouseButtonStatus == MouseButtonRight) // Right button -> exit game
        // {
        //     gShouldExitGame = 1;
        //     break;
        // }
        else if (getGameControllerButtonBack() // Select/Back/- controller button -> exit game
                 || gIsEscapeKeyPressed == 1)
        {
            runAdvancedOptionsRootMenu();
        }
        else if (isRotateLevelSetAscendingButtonPressed())
        {
            throttledRotateLevelSet(0);
            continue; // This allows the throttling effect to act
        }
        else if (isRotateLevelSetDescendingButtonPressed())
        {
            throttledRotateLevelSet(1);
            continue; // This allows the throttling effect to act
        }
        if (gShouldExitGame == 1)
        {
            break;
        }

        if (gMouseButtonStatus == MouseButtonLeft)
        {
            // loc_4C9FF:              // ; CODE XREF: runMainMenu+236j
            gAutomaticDemoPlaybackCountdown = 4200;

            for (int i = 0; i < kNumberOfMainMenuButtons; ++i)
            {
                ButtonDescriptor buttonDescriptor = kMainMenuButtonDescriptors[i];

                // checkmousecoords:              // ; CODE XREF: runMainMenu+29Bj
                if (gMouseX >= buttonDescriptor.startX && gMouseY >= buttonDescriptor.startY && gMouseX <= buttonDescriptor.endX && gMouseY <= buttonDescriptor.endY)
                {
                    buttonDescriptor.handler(); // 01ED:5DC0
                    break;
                }
            }
        }
        else
        {
            // Reset throttle counters
            gLevelListThrottleCurrentCounter = 0x10;
            gLevelListThrottleNextCounter = 0;
            gPlayerListThrottleCurrentCounter = 0x10;
            gPlayerListThrottleNextCounter = 0;
            gRankingListThrottleCurrentCounter = 0x10;
            gRankingListThrottleNextCounter = 0;
            gLevelSetRotationThrottleCurrentCounter = 0x10;
            gLevelSetRotationThrottleNextCounter = 0;
        }
    }

    // loc_4CA34:              // ; CODE XREF: runMainMenu+223j
    //  ; runMainMenu+22Aj ...
    gIsInMainMenu = 0;
    savePlayerListData();
    saveHallOfFameData();
}

void handleControlsOptionClick() // showControls:                              ; DATA XREF: data:0044o
{
    // 01ED:5DDE;
    byte_50919 = 0xFF;
    drawOptionsBackground(gScrollDestinationScreenBitmapData);
    drawSoundTypeOptionsSelection(gScrollDestinationScreenBitmapData);
    drawAudioOptionsSelection(gScrollDestinationScreenBitmapData);
    drawInputOptionsSelection(gScrollDestinationScreenBitmapData);
    //    mov     si, 6055h
    setPalette(gControlsScreenPalette);
    scrollRightToNewScreen();
    word_58463 = 0;
    saveLastMouseAreaBitmap();
    drawMouseCursor();

    uint16_t mouseX, mouseY;
    uint16_t mouseButtonStatus;

    do
    {
        int9handler(0);

        // loc_4CA67:                              ; CODE XREF: code:5E89j
        //                             ; code:5EBFj ...
        videoLoop(); // 01ED:5E04
        updateOptionsMenuState(gScreenPixels);
        gFrameCounter++;
        getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);

        gMouseButtonStatus = mouseButtonStatus;
        gMouseX = mouseX;
        gMouseY = mouseY;

        restoreLastMouseAreaBitmap();
        saveLastMouseAreaBitmap();
        drawMouseCursor();
        if (gMouseButtonStatus == MouseButtonRight)
        {
            break;
        }
        if (isMenuBackButtonPressed()) // Select/Back/- controller button -> go back
        {
            break;
        }
        if (word_58463 == 1)
        {
            break;
        }
        if (gMouseButtonStatus == MouseButtonLeft)
        {
            // loc_4CAAB:                              ; CODE XREF: code:5E87j
            //     mov     si, offset controlsbuttons ; 0ACh // 01ED:5E54

            for (int i = 0; i < kNumberOfOptionsMenuButtons; ++i)
            {
                ButtonDescriptor buttonDescriptor = kOptionsMenuButtonDescriptors[i];
                // loc_4CABA:                              ; CODE XREF: code:5EC7j
                if (gMouseX >= buttonDescriptor.startX && gMouseY >= buttonDescriptor.startY && gMouseX <= buttonDescriptor.endX && gMouseY <= buttonDescriptor.endY)
                {
                    buttonDescriptor.handler(); // 01ED:5E6A

                    do
                    {
                        // loc_4CAD0:                              ; CODE XREF: code:5EBDj
                        videoLoop();
                        gFrameCounter++;
                        getMouseStatus(&mouseX, &mouseY, &mouseButtonStatus);
                    } while (mouseButtonStatus != 0);
                }
            }
        }
    } while (1);

    // loc_4CAEC:                              ; CODE XREF: code:5E74j
    //                             ; code:5E7Bj ...
    saveConfiguration();
    scrollLeftToMainMenu();
    drawMenuTitleAndDemoLevelResult();
    setPalette(gGamePalette);
}

void drawSoundTypeOptionsSelection(uint8_t *destBuffer) // sub_4CAFC   proc near       ; CODE XREF: code:5AE1p handleOptionsStandardClick+6p ...
{
    dimOptionsButtonText(40, 21, 40, 8, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[0], 4, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[1], 4, destBuffer);

    dimOptionsButtonText(24, 57, 72, 8, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[2], 4, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[3], 4, destBuffer);

    dimOptionsButtonText(32, 93, 56, 8, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[4], 4, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[5], 4, destBuffer);

    dimOptionsButtonText(24, 129, 64, 8, destBuffer);
    dimOptionsButtonText(136, 18, 72, 8, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[7], 4, destBuffer);

    dimOptionsButtonText(128, 46, 40, 5, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[8], 4, destBuffer);

    dimOptionsButtonText(176, 46, 40, 5, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[9], 4, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[6], 4, destBuffer);

    if (sndType == SoundTypeAdlib)
    {
        highlightOptionsButtonText(40, 21, 40, 8, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[0], 6, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[1], 6, destBuffer);
        return;
    }

    // loc_4CBC6:              ; CODE XREF: drawSoundTypeOptionsSelection+A9j
    if (sndType == SoundTypeSoundBlaster)
    {
        drawOptionsMenuLine(kOptionsMenuBorders[3], 6, destBuffer);

        if (musType == SoundTypeAdlib)
        {
            highlightOptionsButtonText(24, 57, 72, 8, destBuffer);
            drawOptionsMenuLine(kOptionsMenuBorders[2], 6, destBuffer);
            return;
        }

        // loc_4CBF3:              ; CODE XREF: drawSoundTypeOptionsSelection+DEj
        highlightOptionsButtonText(24, 129, 64, 8, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[4], 6, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[6], 6, destBuffer);
        return;
    }

    // loc_4CC11:              ; CODE XREF: drawSoundTypeOptionsSelection+CFj
    if (sndType == SoundTypeRoland)
    {
        highlightOptionsButtonText(32, 93, 56, 8, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[4], 6, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[5], 6, destBuffer);
        return;
    }

    // loc_4CC36:              ; CODE XREF: drawSoundTypeOptionsSelection+11Aj
    highlightOptionsButtonText(136, 18, 72, 8, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[7], 6, destBuffer);

    if (sndType == SoundTypeInternalStandard)
    {
        highlightOptionsButtonText(128, 46, 40, 5, destBuffer); // Standard
        drawOptionsMenuLine(kOptionsMenuBorders[8], 6, destBuffer);
        return;
    }

    // loc_4CC67:              ; CODE XREF: drawSoundTypeOptionsSelection+153j
    highlightOptionsButtonText(176, 46, 40, 5, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[9], 6, destBuffer);
}

void drawAudioOptionsSelection(uint8_t *destBuffer) // sub_4CC7C   proc near       ; CODE XREF: handleOptionsAdlibClick:loc_4C75Ap
                                                    // ; code:loc_4C774p
{
    if (isMusicEnabled == 1)
    {
        highlightOptionsButtonText(134, 99, 40, 8, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[10], 6, destBuffer);
    }
    else
    {
        // loc_4CC99:              ; CODE XREF: drawAudioOptionsSelection+5j
        dimOptionsButtonText(134, 99, 40, 8, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[10], 4, destBuffer);
    }

    // loc_4CCAD:              ; CODE XREF: drawAudioOptionsSelection+1Bj
    if (isFXEnabled == 1)
    {
        highlightOptionsButtonText(136, 138, 24, 8, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[11], 6, destBuffer);
        return;
    }

    // loc_4CCCA:              ; CODE XREF: drawAudioOptionsSelection+36j
    dimOptionsButtonText(136, 138, 24, 8, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[11], 4, destBuffer);
}

void drawInputOptionsSelection(uint8_t *destBuffer) // sub_4CCDF   proc near       ; CODE XREF: code:5B5Dp code:5B69p
{
    if (isJoystickEnabled == 0)
    {
        highlightOptionsButtonText(208, 87, 8, 62, destBuffer);
        dimOptionsButtonText(240, 88, 8, 58, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[18], 4, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[19], 6, destBuffer);
    }
    else
    {
        // loc_4CD10:              ; CODE XREF: drawInputOptionsSelection+5j
        dimOptionsButtonText(208, 87, 8, 62, destBuffer);
        highlightOptionsButtonText(240, 88, 8, 58, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[18], 6, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[19], 4, destBuffer);
    }

    // loc_4CD38:              ; CODE XREF: drawInputOptionsSelection+2Fj
    updateOptionsMenuState(destBuffer);
}

void updateOptionsMenuState(uint8_t *destBuffer) // sub_4CD3C   proc near       ; CODE XREF: drawInputOptionsSelection:loc_4CD38p
{
    // 01ED:60D9
    updateUserInput();
    if (gCurrentUserInput == byte_50919)
    {
        return;
    }

    // loc_4CD4D:              ; CODE XREF: updateOptionsMenuState+Ej
    byte_50919 = gCurrentUserInput;
    if (gCurrentUserInput == UserInputNone)
    {
        drawOptionsMenuLine(kOptionsMenuBorders[12], 6, destBuffer);
        drawOptionsMenuLine(kOptionsMenuBorders[17], 4, destBuffer);
    }
    else
    {
        // loc_4CD6A:              ; CODE XREF: updateOptionsMenuState+1Aj
        if (gCurrentUserInput <= kUserInputSpaceAndDirectionOffset)
        {
            // loc_4CD9E:              ; CODE XREF: updateOptionsMenuState+33j
            drawOptionsMenuLine(kOptionsMenuBorders[12], 4, destBuffer);
            drawOptionsMenuLine(kOptionsMenuBorders[17], 4, destBuffer);
        }
        else
        {
            drawOptionsMenuLine(kOptionsMenuBorders[17], 6, destBuffer);
            if (gCurrentUserInput != UserInputSpaceOnly)
            {
                // loc_4CD8F:              ; CODE XREF: updateOptionsMenuState+42j
                gCurrentUserInput -= kUserInputSpaceAndDirectionOffset;
                drawOptionsMenuLine(kOptionsMenuBorders[12], 4, destBuffer);
            }
            else
            {
                gCurrentUserInput = UserInputNone;
                drawOptionsMenuLine(kOptionsMenuBorders[12], 6, destBuffer);
            }
        }
    }

    // loc_4CDAE:              ; CODE XREF: updateOptionsMenuState+2Cj
    //                 ; updateOptionsMenuState+51j ...
    drawOptionsMenuLine(kOptionsMenuBorders[13], 4, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[14], 4, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[15], 4, destBuffer);
    drawOptionsMenuLine(kOptionsMenuBorders[16], 4, destBuffer);
    if (gCurrentUserInput == UserInputUp)
    {
        drawOptionsMenuLine(kOptionsMenuBorders[13], 6, destBuffer);
    }
    // loc_4CDDF:              ; CODE XREF: updateOptionsMenuState+97j
    else if (gCurrentUserInput == UserInputLeft)
    {
        drawOptionsMenuLine(kOptionsMenuBorders[14], 6, destBuffer);
    }
    // loc_4CDF0:              ; CODE XREF: updateOptionsMenuState+A8j
    else if (gCurrentUserInput == UserInputDown)
    {
        drawOptionsMenuLine(kOptionsMenuBorders[15], 6, destBuffer);
    }
    // loc_4CE01:              ; CODE XREF: updateOptionsMenuState+B9j
    else if (gCurrentUserInput == UserInputRight)
    {
        drawOptionsMenuLine(kOptionsMenuBorders[16], 6, destBuffer);
    }
}

void highlightOptionsButtonText(size_t startX, size_t startY, size_t width, size_t height, uint8_t *destBuffer) // sub_4CE11   proc near       ; CODE XREF: drawSoundTypeOptionsSelection+B4p
                                                                                                                // ; drawSoundTypeOptionsSelection+E9p ...
{
    // Copies a portion of the buffer replacing color 0xF (not selected)
    // with color 0x1 (selected).
    // Used in the options screen to highlight text from buttons.
    //
    // Parameters:
    // - si: origin coordinates
    // - cx: width / 8
    // - dx: height

    restoreLastMouseAreaBitmap();

    for (size_t y = startY; y < startY + height; ++y)
    {
        for (size_t x = startX; x < startX + width; ++x)
        {
            // loc_4CE68:              ; CODE XREF: highlightOptionsButtonText+5Aj
            //                 ; highlightOptionsButtonText+6Aj
            size_t addr = (y * kScreenWidth) + x;
            destBuffer[addr] = (destBuffer[addr] == 0xF
                                    ? 0x1
                                    : destBuffer[addr]);
        }
    }

    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void dimOptionsButtonText(size_t startX, size_t startY, size_t width, size_t height, uint8_t *destBuffer) // sub_4CE9C   proc near       ; CODE XREF: drawSoundTypeOptionsSelection+9p
                                                                                                          // ; drawSoundTypeOptionsSelection+25p ...
{
    // Copies a portion of the buffer replacing color 0x1 (selected)
    // with color 0xF (not selected).
    // Used in the options screen to dim text from buttons.
    //
    // Parameters:
    // - si: coordinates
    // - cx: width / 8
    // - dx: height

    restoreLastMouseAreaBitmap();

    for (size_t y = startY; y < startY + height; ++y)
    {
        for (size_t x = startX; x < startX + width; ++x)
        {
            // loc_4CEF3:              ; CODE XREF: dimOptionsButtonText+5Aj
            //                 ; dimOptionsButtonText+6Aj
            size_t addr = (y * kScreenWidth) + x;

            destBuffer[addr] = (destBuffer[addr] == 0x1
                                    ? 0xF
                                    : destBuffer[addr]);
        }
    }

    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void drawOptionsMenuLine(ButtonBorderDescriptor border, uint8_t color, uint8_t *destBuffer) // sub_4CF13  proc near       ; CODE XREF: drawSoundTypeOptionsSelection+11p
                                                                                            // ; drawSoundTypeOptionsSelection+19p ...
{
    // Parameters:
    // - ah: color
    // - si: pointer to ButtonBorderDescriptor item

    restoreLastMouseAreaBitmap();

    // loc_4CF38:              ; CODE XREF: drawOptionsMenuLine+96j
    for (int i = 0; i < border.numberOfLines; ++i)
    {
        ButtonBorderLineDescriptor line = border.lines[i];

        for (int j = 0; j < line.length; ++j)
        {
            size_t destAddress = 0;
            if (line.type == ButtonBorderLineTypeHorizontal)
            {
                destAddress = line.y * kScreenWidth + line.x + j;
            }
            else if (line.type == ButtonBorderLineTypeVertical)
            {
                destAddress = (line.y - j) * kScreenWidth + line.x;
            }
            else if (line.type == ButtonBorderLineTypeBottomLeftToTopRightDiagonal)
            {
                destAddress = (line.y - j) * kScreenWidth + line.x + j;
            }
            else if (line.type == ButtonBorderLineTypeTopLeftToBottomRightDiagonal)
            {
                destAddress = (line.y + j) * kScreenWidth + line.x + j;
            }

            destBuffer[destAddress] = color;

            // loc_4CFA4:              ; CODE XREF: drawOptionsMenuLine:loc_4CF7Cj
            //                 ; drawOptionsMenuLine+73j ...
        }
    }

    // loc_4CFAB:              ; CODE XREF: drawOptionsMenuLine+2Aj
    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void savePlayerListData() // sub_4CFB2   proc near       ; CODE XREF: handleNewPlayerOptionClick+1D5p
//                    ; handleDeletePlayerOptionClick+CEp ...
{
    if (gIsForcedCheatMode != 0)
    {
        return;
    }

    FILE *file = openWritableFile(gPlayerLstFilename, "wb");
    if (file == NULL)
    {
        return;
    }

    assert(sizeof(gPlayerListData) == 0xA00);

    fileWriteBytes(gPlayerListData, sizeof(gPlayerListData), file);

    fclose(file);
}

void saveHallOfFameData() //   proc near       ; CODE XREF: handleNewPlayerOptionClick+1D8p
//                    ; handleDeletePlayerOptionClick+D1p ...
{
    if (gIsForcedCheatMode != 0)
    {
        return;
    }

    FILE *file = openWritableFile(gHallfameLstFilename, "wb");
    if (file == NULL)
    {
        return;
    }

    assert(sizeof(gHallOfFameData) == 0x24);

    fileWriteBytes(gHallOfFameData, sizeof(gHallOfFameData), file);

    fclose(file);
}

void drawMainMenuButtonBorder(ButtonBorderDescriptor border, uint8_t color) // sub_4D004  proc near       ; CODE XREF: drawMainMenuButtonBorders+17p
//                    ; drawMainMenuButtonBorders+2Ap ...
{
    // 01ED:63A1
    // Parameters:
    // - si: button border descriptor
    // - ah: color??? it's either 7 or 0xD in drawMainMenuButtonBorders

    restoreLastMouseAreaBitmap();

    // loc_4D029:              ; CODE XREF: drawButtonBorder+96j
    for (int i = 0; i < border.numberOfLines; ++i)
    {
        ButtonBorderLineDescriptor line = border.lines[i];

        for (int j = 0; j < line.length; ++j)
        {
            size_t destAddress = 0;
            if (line.type == ButtonBorderLineTypeHorizontal)
            {
                destAddress = line.y * kScreenWidth + line.x + j;
            }
            else if (line.type == ButtonBorderLineTypeVertical)
            {
                destAddress = (line.y - j) * kScreenWidth + line.x;
            }
            else if (line.type == ButtonBorderLineTypeBottomLeftToTopRightDiagonal)
            {
                destAddress = (line.y - j) * kScreenWidth + line.x + j;
            }
            else if (line.type == ButtonBorderLineTypeTopLeftToBottomRightDiagonal)
            {
                destAddress = (line.y + j) * kScreenWidth + line.x + j;
            }

            gScreenPixels[destAddress] = color;

            // loc_4D095:              ; CODE XREF: drawButtonBorder:loc_4D06Dj
            //                 ; drawButtonBorder+73j ...
        }
    }

    // loc_4D09C:              ; CODE XREF: drawButtonBorder+2Aj
    saveLastMouseAreaBitmap();
    drawMouseCursor();
}

void drawMainMenuButtonBorders() // sub_4D0AD  proc near       ; CODE XREF: runMainMenu+B7p
{
    // 01ED:644A
    uint8_t color = 0;

    if (gPlayerListButtonPressed != 0)
    {
        if (gPlayerListUpButtonPressed == 0)
        {
            color = 7;
        }
        else
        {
            color = 0xD; // 13
        }

        // loc_4D0C1:              ; CODE XREF: drawMainMenuButtonBorders+10j
        drawMainMenuButtonBorder(kMainMenuButtonBorders[0], color);
        if (gPlayerListUpButtonPressed == 0)
        {
            color = 0xD; // 13
        }
        else
        {
            color = 7;
        }

        // loc_4D0D4:              ; CODE XREF: drawMainMenuButtonBorders+23j
        drawMainMenuButtonBorder(kMainMenuButtonBorders[1], color);
        if (gPlayerListDownButtonPressed == 0)
        {
            color = 7;
        }
        else
        {
            color = 0xD; // 13
        }

        // loc_4D0E7:              ; CODE XREF: drawMainMenuButtonBorders+36j
        drawMainMenuButtonBorder(kMainMenuButtonBorders[2], color);
        if (gPlayerListDownButtonPressed == 0)
        {
            color = 0xD; // 13
        }
        else
        {
            color = 7;
        }

        // loc_4D0FA:              ; CODE XREF: drawMainMenuButtonBorders+49j
        drawMainMenuButtonBorder(kMainMenuButtonBorders[3], color);
        gPlayerListButtonPressed = 0;
    }

    // loc_4D105:              ; CODE XREF: drawMainMenuButtonBorders+5j
    if (gRankingListButtonPressed != 0)
    {
        if (gRankingListUpButtonPressed == 0)
        {
            color = 7;
        }
        else
        {
            color = 0xD; // 13
        }

        // loc_4D119:              ; CODE XREF: drawMainMenuButtonBorders+68j
        //         si = 0x558; // 1368
        drawMainMenuButtonBorder(kMainMenuButtonBorders[4], color);
        if (gRankingListUpButtonPressed == 0)
        {
            color = 0xD;
        }
        else
        {
            color = 7;
        }

        // loc_4D12C:              ; CODE XREF: drawMainMenuButtonBorders+7Bj
        //         si = 0x56D;
        drawMainMenuButtonBorder(kMainMenuButtonBorders[5], color);
        if (gRankingListDownButtonPressed == 0)
        {
            color = 7;
        }
        else
        {
            color = 0xD;
        }

        // loc_4D13F:              ; CODE XREF: drawMainMenuButtonBorders+8Ej
        //         si = 0x582;
        drawMainMenuButtonBorder(kMainMenuButtonBorders[6], color);
        if (gRankingListDownButtonPressed == 0)
        {
            color = 0xD;
        }
        else
        {
            color = 7;
        }

        // loc_4D152:              ; CODE XREF: drawMainMenuButtonBorders+A1j
        //         si = 0x597;
        drawMainMenuButtonBorder(kMainMenuButtonBorders[7], color);
        gRankingListButtonPressed = 0;
    }

    // loc_4D15D:              ; CODE XREF: drawMainMenuButtonBorders+5Dj
    if (gLevelListButtonPressed == 0)
    {
        return;
    }
    if (gLevelListUpButtonPressed == 0)
    {
        color = 7;
    }
    else
    {
        color = 0xD;
    }

    // loc_4D171:              ; CODE XREF: drawMainMenuButtonBorders+C0j
    //     si = 0x5AC;
    drawMainMenuButtonBorder(kMainMenuButtonBorders[8], color);
    if (gLevelListUpButtonPressed == 0)
    {
        color = 0xD;
    }
    else
    {
        color = 7;
    }

    // loc_4D184:              ; CODE XREF: drawMainMenuButtonBorders+D3j
    //     si = 0x5C1;
    drawMainMenuButtonBorder(kMainMenuButtonBorders[9], color);
    if (gLevelListDownButtonPressed == 0)
    {
        color = 7;
    }
    else
    {
        color = 0xD;
    }

    // loc_4D197:              ; CODE XREF: drawMainMenuButtonBorders+E6j
    //     si = 0x5D6;
    drawMainMenuButtonBorder(kMainMenuButtonBorders[10], color);
    if (gLevelListDownButtonPressed == 0)
    {
        color = 0xD;
    }
    else
    {
        color = 7;
    }

    // loc_4D1AA:              ; CODE XREF: drawMainMenuButtonBorders+F9j
    //     si = 0x5EB;
    drawMainMenuButtonBorder(kMainMenuButtonBorders[11], color);
    gLevelListButtonPressed = 0;
}

void updateHallOfFameEntries() // sub_4D1B6  proc near       ; CODE XREF: changePlayerCurrentLevelState+2Ep
{
    // 01ED:6553
    if (gIsPlayingDemo != 0)
    {
        return;
    }

    // loc_4D1BE:              ; CODE XREF: updateHallOfFameEntries+5j
    PlayerEntry *currentPlayerEntry = &gPlayerListData[gCurrentPlayerIndex];
    if (currentPlayerEntry->completedAllLevels != 0)
    {
        return;
    }

    // loc_4D1D2:              ; CODE XREF: updateHallOfFameEntries+19j
    int numberOfCompletedLevels = 0;

    for (int i = 0; i < kNumberOfLevels; ++i)
    {
        // loc_4D1E2:              ; CODE XREF: updateHallOfFameEntries+33j
        if (currentPlayerEntry->levelState[i] == PlayerLevelStateCompleted)
        {
            numberOfCompletedLevels++;
        }
        // loc_4D1E8:              ; CODE XREF: updateHallOfFameEntries+2Fj
    }

    if (numberOfCompletedLevels != kNumberOfLevels)
    {
        return;
    }

    currentPlayerEntry->completedAllLevels = 1;

    int newEntryInsertIndex = -1;
    //    mov cx, 3
    //    mov di, hallFameDataBuffer
    for (int i = 0; i < kNumberOfHallOfFameEntries; ++i)
    {
        HallOfFameEntry entry = gHallOfFameData[i];

        // loc_4D1FA:              ; CODE XREF: updateHallOfFameEntries+78j
        if (entry.hours == 0 && entry.minutes == 0 && entry.seconds == 0)
        {
            newEntryInsertIndex = i;
            break;
        }

        // loc_4D20E:              ; CODE XREF: updateHallOfFameEntries+48j
        //                 ; updateHallOfFameEntries+4Ej ...
        if (currentPlayerEntry->hours < entry.hours)
        {
            newEntryInsertIndex = i;
            break;
        }
        else if (currentPlayerEntry->hours == entry.hours)
        {
            if (currentPlayerEntry->minutes < entry.minutes)
            {
                newEntryInsertIndex = i;
                break;
            }
            else if (currentPlayerEntry->minutes == entry.minutes)
            {
                if (currentPlayerEntry->seconds < entry.seconds)
                {
                    newEntryInsertIndex = i;
                    break;
                }
            }
        }

        // loc_4D22A:              ; CODE XREF: updateHallOfFameEntries+60j
        //                 ; updateHallOfFameEntries+6Aj
    }

    if (newEntryInsertIndex != -1)
    {
        // loc_4D232:              ; CODE XREF: updateHallOfFameEntries+56j
        //                 ; updateHallOfFameEntries+5Ej ...

        // Shift the list to the right to make room for the new entry
        for (int i = kNumberOfHallOfFameEntries - 1; i >= newEntryInsertIndex + 1; --i)
        {
            memcpy(&gHallOfFameData[i], &gHallOfFameData[i - 1], sizeof(HallOfFameEntry));
        }

        // Copy the player info into the new entry
        HallOfFameEntry *newEntry = &gHallOfFameData[newEntryInsertIndex];

        memcpy(newEntry->playerName,
               currentPlayerEntry->name,
               sizeof(currentPlayerEntry->name));
        newEntry->hours = currentPlayerEntry->hours;
        newEntry->minutes = currentPlayerEntry->minutes;
        newEntry->seconds = currentPlayerEntry->seconds;
    }

    // loc_4D24B:              ; CODE XREF: updateHallOfFameEntries+38j
    //                 ; updateHallOfFameEntries+7Aj
}

void changePlayerCurrentLevelState() // sub_4D24D  proc near       ; CODE XREF: handleSkipLevelOptionClick+D9p
//                    ; update?:loc_4E6A4p
{
    // 01ED:65EA
    if (gIsPlayingDemo != 0)
    {
        return;
    }
    if (gHasUserCheated != 0)
    {
        return;
    }
    uint8_t previousState = gCurrentPlayerLevelState;
    gCurrentPlayerLevelState = PlayerLevelStateNotCompleted;

    PlayerEntry *currentPlayerEntry = &gPlayerListData[gCurrentPlayerIndex];
    currentPlayerEntry->levelState[gCurrentSelectedLevelIndex - 1] = previousState;
    gCurrentSelectedLevelIndex++;
    updateHallOfFameEntries(); // 01ED:6618

    // Added by me to prevent losing progress when switching levelsets after finishing a level
    savePlayerListData();
    saveHallOfFameData();
}

void initializeFadePalette() //   proc near       ; CODE XREF: start+296p
{
    setPalette(gBlackPalette);
}

void initializeMouse() //   proc near       ; CODE XREF: start+299p
{
    gMouseX = kScreenWidth / 2;
    gMouseY = kScreenHeight / 2;
    if (getFullscreenMode())
    {
        centerMouse();
    }
    hideMouse();
    handleSystemEvents();
}

void getMouseStatus(uint16_t *mouseX, uint16_t *mouseY, uint16_t *mouseButtonStatus) //   proc near       ; CODE XREF: waitForKeyMouseOrJoystick:mouseIsClickedp
//                    ; waitForKeyMouseOrJoystick+3Ep ...
{
    // Returns coordinate X in CX (0-320) and coordinate Y in DX (0-200).
    // Also button status in BX.

    handleSystemEvents();

    int x, y;
    uint8_t leftButtonPressed, rightButtonPressed;

    getMouseState(&x, &y, &leftButtonPressed, &rightButtonPressed);

    int windowWidth, windowHeight;
    getWindowSize(&windowWidth, &windowHeight);

    float controllerX = 0, controllerY = 0;
    uint8_t controllerLeftButton = 0;
    uint8_t controllerRightButton = 0;
    gameControllerEmulateMouse(&controllerX,
                               &controllerY,
                               &controllerLeftButton,
                               &controllerRightButton);

    uint8_t shouldCorrectMousePosition = 0;

    if (controllerX != 0.0 || controllerY != 0.0)
    {
        float speed = (float)windowWidth / 1280;

        x += speed * controllerX;
        y += speed * controllerY;

        shouldCorrectMousePosition = 1;
    }

    // Read touch screen where available
    float touchScreenX, touchScreenY;
    uint8_t touchScreenPressed = readTouchScreen(&touchScreenX, &touchScreenY);
    if (touchScreenPressed)
    {
        x = touchScreenX * windowWidth;
        y = touchScreenY * windowHeight;

        shouldCorrectMousePosition = 1;
    }

    if (shouldCorrectMousePosition)
    {
        x = CLAMP(x, 0, windowWidth);
        y = CLAMP(y, 0, windowHeight);

        // Correct mouse position for future events
        moveMouse(x, y);
    }

    if (windowWidth != 0 && windowHeight != 0)
    {
        x = x * kScreenWidth / windowWidth;
        y = y * kScreenHeight / windowHeight;
    }

    leftButtonPressed = (leftButtonPressed || controllerLeftButton || touchScreenPressed);
    rightButtonPressed = (rightButtonPressed || controllerRightButton);

    // Limit coordinates as in the original game
    x = CLAMP(x, 16, 304);
    y = CLAMP(y, 8, 192);

    if (mouseX != NULL)
    {
        *mouseX = x;
    }
    if (mouseY != NULL)
    {
        *mouseY = y;
    }

    if (mouseButtonStatus != NULL)
    {
        *mouseButtonStatus = (rightButtonPressed << 1 | leftButtonPressed);
    }
}

#define COPY_LEVEL_DATA(__dest, __size)                  \
    do                                                   \
    {                                                    \
        memcpy(__dest, &levelFileData[pointer], __size); \
        pointer += __size;                               \
    } while (0)

#define COPY_LEVEL_DATA_UINT16(__dest)              \
    do                                              \
    {                                               \
        COPY_LEVEL_DATA(&__dest, sizeof(uint16_t)); \
        __dest = convert16LE(__dest);               \
    } while (0)

void mapLevelFileData(char *levelFileData, Level *level)
{
    size_t pointer = 0;

    COPY_LEVEL_DATA(level->tiles, sizeof(level->tiles));
    COPY_LEVEL_DATA(level->unused, sizeof(level->unused));
    COPY_LEVEL_DATA(&level->initialGravitation, sizeof(level->initialGravitation));
    COPY_LEVEL_DATA(&level->speedFixMagicNumber, sizeof(level->speedFixMagicNumber));
    COPY_LEVEL_DATA(level->name, sizeof(level->name));
    COPY_LEVEL_DATA(&level->freezeZonks, sizeof(level->freezeZonks));
    COPY_LEVEL_DATA(&level->numberOfInfotrons, sizeof(level->numberOfInfotrons));
    COPY_LEVEL_DATA(&level->numberOfSpecialPorts, sizeof(level->numberOfSpecialPorts));
    for (int idx = 0; idx < kLevelMaxNumberOfSpecialPorts; ++idx)
    {
        SpecialPortInfo *specialPortInfo = &level->specialPortsInfo[idx];
        COPY_LEVEL_DATA_UINT16(specialPortInfo->position);
        COPY_LEVEL_DATA(&specialPortInfo->gravity, sizeof(specialPortInfo->gravity));
        COPY_LEVEL_DATA(&specialPortInfo->freezeZonks, sizeof(specialPortInfo->freezeZonks));
        COPY_LEVEL_DATA(&specialPortInfo->freezeEnemies, sizeof(specialPortInfo->freezeEnemies));
        COPY_LEVEL_DATA(&specialPortInfo->unused, sizeof(specialPortInfo->unused));
    }
    COPY_LEVEL_DATA(&level->scrambledSpeed, sizeof(level->scrambledSpeed));
    COPY_LEVEL_DATA(&level->scrambledChecksum, sizeof(level->scrambledChecksum));
    COPY_LEVEL_DATA_UINT16(level->randomSeed);

    assert(pointer == kLevelDataLength);
}

#undef COPY_LEVEL_DATA_UINT16
#undef COPY_LEVEL_DATA

void readLevels() //  proc near       ; CODE XREF: start:loc_46F3Ep
                  // ; fetchAndInitializeLevelp
{
    // 01ED:68E5
    char *filename = "";
    FILE *file = NULL;
    char levelFileData[kLevelDataLength];

    if (gIsPlayingDemo != 0 && (gSelectedOriginalDemoLevelNumber & 0xFF) == 0 && gIsSPDemoAvailableToRun == 0)
    {
        // Demos with the new format
        Level *level = &gDemos.level[gDemoIndexOrDemoLevelNumber];

        memcpy(&levelFileData, level, kLevelDataLength);

        strcpy(gCurrentDemoLevelName, ".SP");

        memcpy(&gCurrentDemoLevelName[4], level->name, sizeof(level->name));
    }
    else
    {
        if (gIsPlayingDemo == 0 || gIsSPDemoAvailableToRun != 0)
        {
            // loc_4D59F:              ; CODE XREF: readLevels+5j
            //                 ; readLevels+13j
            filename = gLevelsDatFilename; // lea dx, aLevels_dat_0 ; "LEVELS.DAT"
        }

        if (gIsPlayingDemo != 0 && (gSelectedOriginalDemoLevelNumber & 0xFF) != 0) // cmp byte ptr gSelectedOriginalDemoLevelNumber, 0
        {
            // loc_4D599:              ; CODE XREF: readLevels+Cj
            filename = gLevelsDatFilename; // lea dx, aLevels_dat ; "LEVELS.DAT"
        }
        // loc_4D5A3:              ; CODE XREF: readLevels+55j
        else if (gIsSPDemoAvailableToRun != 0)
        {
            filename = demoFileName;
        }
        else if (gSelectedOriginalDemoFromCommandLineLevelNumber != 0)
        {
            filename = gLevelsDatFilename; // lea dx, aLevels_dat ; "LEVELS.DAT"
        }

        // loc_4D5BB:              ; CODE XREF: readLevels+63j
        file = openWritableFileWithReadonlyFallback(filename, "rb");
        if (file == NULL)
        {
            exitWithError("Error opening %s\n", filename);
        }

        uint8_t levelIndex = 0;

        // loc_4D5C2:              ; CODE XREF: readLevels+75j
        if (gIsPlayingDemo != 0)
        {
            levelIndex = gDemoIndexOrDemoLevelNumber;
        }
        else
        {
            // loc_4D5D1:              ; CODE XREF: readLevels+82j
            levelIndex = gCurrentSelectedLevelIndex;
        }

        // loc_4D5D4:              ; CODE XREF: readLevels+87j
        if (gIsSPDemoAvailableToRun != 0)
        {
            levelIndex = gSelectedOriginalDemoFromCommandLineLevelNumber;
            if (levelIndex == 0)
            {
                levelIndex++;
            }
        }

        // loc_4D5E3:              ; CODE XREF: readLevels+91j
        //                 ; readLevels+98j
        levelIndex--; // Levels anywhere else are 1-index, we need them to start from 0 here
        size_t fileOffset = levelIndex * kLevelDataLength;
        // 01ED:699A
        int result = fseek(file, fileOffset, SEEK_SET);
        if (result != 0)
        {
            exitWithError("Error seeking %s\n", filename);
        }

        // loc_4D604:              ; CODE XREF: readLevels+B7j
        //  01ED:69AE
        size_t bytes = fileReadBytes(levelFileData, kLevelDataLength, file);
        if (bytes < kLevelDataLength)
        {
            exitWithError("Error reading %s\n", filename);
        }

        Level tmpLevel;
        mapLevelFileData(levelFileData, &tmpLevel);
        gIsGravityEnabled = tmpLevel.initialGravitation;
        gAreZonksFrozen = tmpLevel.freezeZonks;
        gNumberOfInfoTrons = tmpLevel.numberOfInfotrons;
        gNumberOfSpecialPorts = tmpLevel.numberOfSpecialPorts;
        gRandomSeed = tmpLevel.randomSeed;

        // loc_4D618:              ; CODE XREF: readLevels+CBj
        if ((gSelectedOriginalDemoLevelNumber & 0xFF) != 0) // cmp byte ptr gSelectedOriginalDemoLevelNumber, 0
        {
            gSelectedOriginalDemoLevelNumber |= 0xFF00; // mov byte ptr gSelectedOriginalDemoLevelNumber+1, 0FFh

            gDemoIndexOrDemoLevelNumber = gSelectedOriginalDemoIndex;

            Level *level = &gDemos.level[gDemoIndexOrDemoLevelNumber];
            mapLevelFileData(levelFileData, level);
        }
    }

    char *levelName = NULL;

    // loc_4D64B:              ; CODE XREF: readLevels+4Ej
    //                 ; readLevels+D5j
    if (gIsPlayingDemo != 0)
    {
        gRandomGeneratorSeed = gRandomSeed;
        levelName = gCurrentDemoLevelName;
    }
    else
    {
        // loc_4D65D:              ; CODE XREF: readLevels+108j
        levelName = gCurrentLevelName;
    }

    // loc_4D660:              ; CODE XREF: readLevels+113j
    if (gSelectedOriginalDemoLevelNumber != 0 || (gIsSPDemoAvailableToRun != 0 && gSelectedOriginalDemoFromCommandLineLevelNumber != 0))
    {
        // loc_4D679:              ; CODE XREF: readLevels+121j
        strcpy(gCurrentDemoLevelName, "BIN");
        levelName += 4;
    }
    else if (gIsSPDemoAvailableToRun == 0)
    {
        // loc_4D68C:              ; CODE XREF: readLevels+128j
        levelName += 4; // Skips the number directly to the title (from pointing "005 ------- EASY DEAL -------" to pointing "------- EASY DEAL -------")
    }
    else if (gSelectedOriginalDemoFromCommandLineLevelNumber == 0)
    {
        // loc_4D682:              ; CODE XREF: readLevels+12Fj
        strcpy(gCurrentDemoLevelName, ".SP");
        levelName += 4;
    }

    // loc_4D68F:              ; CODE XREF: readLevels+142j
    mapLevelFileData(levelFileData, &gCurrentLevel);
    memcpy(levelName, gCurrentLevel.name, sizeof(gCurrentLevel.name));

    // The reason this is 1536 (level file size) and not 1440 (actual gamefield size of 60 * 24 tiles) is because
    // the game was written like this, and some levels rely on this behavior by removing the bottom border of the level
    // and using some unused bytes from those 1536 to store tile data.
    // An example of this is "HIDDEN TRACK" (07/07s062-1.sp)
    //
    for (int i = 0; i < kLevelDataLength; ++i)
    {
        // loc_4D6B8:              ; CODE XREF: readLevels+172j
        StatefulLevelTile *tile = &gCurrentLevelState[i];
        tile->tile = levelFileData[i];
        tile->state = 0;
    }

    memset(&gExplosionTimers, 0, sizeof(gExplosionTimers)); // rep stosb

    if (gIsPlayingDemo == 0 || (gSelectedOriginalDemoLevelNumber & 0xFF) != 0 || gIsSPDemoAvailableToRun != 0)
    {
        // loc_4D6DC:              ; CODE XREF: readLevels+184j
        //                 ; readLevels+18Bj
        if (fclose(file) != 0)
        {
            exitWithError("Error closing %s\n", filename);
        }
    }

    // loc_4D6EA:              ; CODE XREF: readLevels+192j
    //                 ; readLevels+19Dj
    gSelectedOriginalDemoLevelNumber &= 0xFF00; // mov byte ptr gSelectedOriginalDemoLevelNumber, 0
}

void soundShutdown() //  proc near       ; CODE XREF: start+48Ep
//                    ; loadScreen2-7DAp
{
    stopMusicAndSounds();
}

void activateInternalStandardSound() // loadBeep   proc near       ; CODE XREF: readConfig:loc_4751Ap
//                    ; readConfig:loc_47551p ...
{
    stopMusicAndSounds();
    setSoundType(SoundTypeInternalStandard, SoundTypeInternalStandard);
    playMusicIfNeeded();
    gCurrentSoundPriority = 0;
    gCurrentSoundDuration = 0;
}

void activateInternalSamplesSound() // loadBeep2  proc near       ; CODE XREF: readConfig+4Cp handleOptionsSamplesClickp
{
    stopMusicAndSounds();
    setSoundType(SoundTypeInternalStandard, SoundTypeInternalSamples);
    playMusicIfNeeded();
    gCurrentSoundPriority = 0;
    gCurrentSoundDuration = 0;
}

void activateAdlibSound() // loadAdlib  proc near       ; CODE XREF: readConfig+56p handleOptionsAdlibClickp
{
    stopMusicAndSounds(); // 01ED:6D06
    setSoundType(SoundTypeAdlib, SoundTypeAdlib);
    playMusicIfNeeded();
    gCurrentSoundPriority = 0;
    gCurrentSoundDuration = 0;
}

void activateSoundBlasterSound() // loadBlaster  proc near       ; CODE XREF: readConfig+60p handleOptionsSoundBlasterClickp
{
    // 01ED:6D39
    stopMusicAndSounds();
    setSoundType(SoundTypeAdlib, SoundTypeSoundBlaster);
    playMusicIfNeeded();
    gCurrentSoundPriority = 0;
    gCurrentSoundDuration = 0;
}

void activateRolandSound() // loadRoland  proc near       ; CODE XREF: readConfig+6Ap handleOptionsRolandClickp
{
    stopMusicAndSounds();
    setSoundType(SoundTypeRoland, SoundTypeRoland);
    playMusicIfNeeded();
    gCurrentSoundPriority = 0;
    gCurrentSoundDuration = 0;
}

void activateCombinedSound() // loadCombined proc near       ; CODE XREF: readConfig+74p handleOptionsCombinedClickp
{
    stopMusicAndSounds();
    setSoundType(SoundTypeRoland, SoundTypeSoundBlaster);
    playMusicIfNeeded();
    gCurrentSoundPriority = 0;
    gCurrentSoundDuration = 0;
}

void stopMusicAndSounds() // sound1     proc near       ; CODE XREF: soundShutdown?+5p
                          //   ; code:6CC7p ...
{
    // 01ED:6E4E
    setSoundType(SoundTypeNone, SoundTypeNone);
}

void playMusicIfNeeded() // sound2    proc near       ; CODE XREF: start+39Bp start+410p ...
{
    // 01ED:6EA8
    if (isMusicEnabled != 1)
    {
        return;
    }

    playMusic();
}

void playExplosionSound() // sound4    proc near       ; CODE XREF: detonateBigExplosion+2EDp code:5ADEp ...
{
    if (isFXEnabled != 1)
    {
        return;
    }

    // loc_4DB7F:              ; CODE XREF: playExplosionSound+5j
    if (gCurrentSoundPriority >= 5)
    {
        return;
    }

    // loc_4DB87:              ; CODE XREF: playExplosionSound+Dj
    gCurrentSoundDuration = 0xF;
    gCurrentSoundPriority = 5;

    playSoundEffect(SoundEffectExplosion);
}

void playInfotronSound() // sound5    proc near       ; CODE XREF: update?:loc_4E55Cp
                         //    ; update?:loc_4E588p ...
{
    if (isFXEnabled == 0)
    {
        return;
    }

    // loc_4DBE8:              ; CODE XREF: playInfotronSound+5j
    if (gCurrentSoundPriority >= 5)
    {
        return;
    }

    // loc_4DBF0:              ; CODE XREF: playInfotronSound+Dj
    gCurrentSoundDuration = 0xF;
    gCurrentSoundPriority = 4;

    playSoundEffect(SoundEffectInfotron);
}

void playPushSound() // sound6    proc near       ; CODE XREF: update?+B8Bp
                     //    ; update?+136Cp
{
    if (isFXEnabled == 0)
    {
        return;
    }

    // loc_4DC51:              ; CODE XREF: playPushSound+5j
    if (gCurrentSoundPriority >= 2)
    {
        return;
    }

    // loc_4DC59:              ; CODE XREF: playPushSound+Dj
    gCurrentSoundDuration = 7;
    gCurrentSoundPriority = 2;
    playSoundEffect(SoundEffectPush);
}

void playFallSound() // sound7     proc near       ; CODE XREF: movefun:loc_48125p
//                    ; movefun2:loc_48573p
{
    if (isFXEnabled == 0)
    {
        return;
    }

    // loc_4DCBA:              ; CODE XREF: playFallSound+5j
    if (gCurrentSoundPriority >= 2)
    {
        return;
    }

    // loc_4DCC2:              ; CODE XREF: playFallSound+Dj
    gCurrentSoundDuration = 7;
    gCurrentSoundPriority = 2;
    playSoundEffect(SoundEffectFall);
}

void playBugSound() // sound8     proc near       ; CODE XREF: movefun7:loc_4A0ABp
{
    if (isFXEnabled == 0)
    {
        return;
    }

    // loc_4DD23:              ; CODE XREF: playBugSound+5j
    if (gCurrentSoundPriority >= 3)
    {
        return;
    }

    // loc_4DD2B:              ; CODE XREF: playBugSound+Dj
    gCurrentSoundDuration = 3;
    gCurrentSoundPriority = 3;

    playSoundEffect(SoundEffectBug);
}

void playBaseSound() // sound9    proc near       ; CODE XREF: runLevel+2F4p
                     //    ; update?:loc_4E3E1p ...
{
    if (isFXEnabled == 0)
    {
        return;
    }

    // xxxxxxxxdcdc:               ; CODE XREF: playBaseSound+5j
    if (gCurrentSoundPriority >= 1)
    {
        return;
    }

    // loc_4DD94:              ; CODE XREF: playBaseSound+Dj
    gCurrentSoundDuration = 3;
    gCurrentSoundPriority = 1;

    playSoundEffect(SoundEffectBase);
}

void playExitSound() // sound10   proc near       ; CODE XREF: update?+7EBp
{
    if (isFXEnabled == 0)
    {
        return;
    }

    // loc_4DDF5:              ; CODE XREF: playExitSound+5j
    gCurrentSoundDuration = 0xFA;
    gCurrentSoundPriority = 0xA;
    stopMusic();

    playSoundEffect(SoundEffectExit);
}

int16_t updateMurphy(int16_t position) // update?     proc near       ; CODE XREF: updateMovingObjects+Ep
{
    // 01ED:722D

    StatefulLevelTile *murphyTile = &gCurrentLevelState[position];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];

    if (murphyTile->tile != LevelTileTypeMurphy)
    {
        gIsMurphyUpdated = 0;
        return position;
    }

    // hasValidMurphy:              ; CODE XREF: update?+5j
    gIsMurphyUpdated = 1;
    gMurphyPreviousLocation = position;
    if (murphyTile->state != 0 || murphyTile->tile != LevelTileTypeMurphy)
    {
        return updateMurphyAnimation(position);
    }

    // loc_4DEB4:              ; CODE XREF: update?+1Fj
    gScratchGravity = 0;

    if (gIsGravityEnabled != 0 && aboveTile->tile != LevelTileTypePortUp && aboveTile->tile != LevelTileTypePortVertical && aboveTile->tile != LevelTileTypePort4Way && (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace))
    {
        gScratchGravity = 1;
    }

    // loc_4DEE1:              ; CODE XREF: update?+2Ej update?+35j ...
    UserInput userInput = gCurrentUserInput;

    if (userInput == UserInputNone)
    {
        // loc_4DEED:              ; CODE XREF: update?+58j
        gPreviousUserInputWasNone = 1;
        if (gScratchGravity != 0)
        {
            MurphyAnimationDescriptor unknownMurphyData;
            // loc_4E38A:              ; CODE XREF: update?+69j update?+2FFj
            if (gIsMurphyLookingLeft != 0)
            {
                unknownMurphyData = kMurphyAnimationDescriptors[3]; // dx = 0x0E2E;
            }
            else
            {
                // loc_4E396:              ; CODE XREF: update?+4FFj
                unknownMurphyData = kMurphyAnimationDescriptors[4]; // dx = 0x0E3E;
            }

            // loc_4E399:              ; CODE XREF: update?+504j
            belowTile->state = 3;
            belowTile->tile = LevelTileTypeMurphy;
            murphyTile->state = 3;
            murphyTile->tile = LevelTileTypeSpace;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position + kLevelWidth, unknownMurphyData);
        }

        // loc_4DEFC:              ; CODE XREF: update?+67j
        if ((gFrameCounter & 3) != 0)
        {
            return position;
        }

        // loc_4DF05:              ; CODE XREF: update?+72j
        gMurphyYawnAndSleepCounter++;
        if (gMurphyYawnAndSleepCounter == 4)
        {
            // si = kMurphyStillSpriteCoordinates;
            drawMovingFrame(304, 132, position);

            return position;
        }
        // loc_4DF1E:              ; CODE XREF: update?+7Ej
        else if (gMurphyYawnAndSleepCounter <= 0x01F4)
        {
            return position;
        }
        // loc_4DF27:              ; CODE XREF: update?+94j
        else if (gMurphyYawnAndSleepCounter <= 0x020A)
        {
            // Yawning animation
            uint16_t currentFrame = gMurphyYawnAndSleepCounter - 0x1F4;
            currentFrame = currentFrame >> 1;

            AnimationFrameCoordinates animationFrameCoordinates = kMurphyAnimationFrameCoordinates[34];
            Point frameCoordinates = animationFrameCoordinates.coordinates[currentFrame];

            drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);

            return position;
        }
        // loc_4DF4A:              ; CODE XREF: update?+9Dj
        else if (gMurphyYawnAndSleepCounter <= 0x03E8)
        {
            return position;
        }
        // loc_4DF53:              ; CODE XREF: update?+C0j
        else if (gMurphyYawnAndSleepCounter <= 0x03FE)
        {
            // Yawning animation
            uint16_t currentFrame = gMurphyYawnAndSleepCounter - 0x3E8;
            currentFrame = currentFrame >> 1;

            AnimationFrameCoordinates animationFrameCoordinates = kMurphyAnimationFrameCoordinates[34];
            Point frameCoordinates = animationFrameCoordinates.coordinates[currentFrame];

            drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);

            return position;
        }
        // loc_4DF76:              ; CODE XREF: update?+C9j
        else if (gMurphyYawnAndSleepCounter <= 0x0640)
        {
            return position;
        }
        // loc_4DF7F:              ; CODE XREF: update?+ECj
        else if (gMurphyYawnAndSleepCounter <= 0x0656)
        {
            // Yawning animation
            uint16_t currentFrame = gMurphyYawnAndSleepCounter - 0x640;
            currentFrame = currentFrame >> 1;

            AnimationFrameCoordinates animationFrameCoordinates = kMurphyAnimationFrameCoordinates[34];
            Point frameCoordinates = animationFrameCoordinates.coordinates[currentFrame];

            drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);

            return position;
        }
        // loc_4DFA2:              ; CODE XREF: update?+F5j
        else if (gMurphyYawnAndSleepCounter > 0x0676)
        {
            return position;
        }
        else if (leftTile->state != 0 || leftTile->tile != LevelTileTypeSpace)
        {
            // loc_4DFBF:              ; CODE XREF: update?+11Fj
            //  Sleep to left animation
            uint16_t currentFrame = gMurphyYawnAndSleepCounter - 0x656;
            currentFrame = currentFrame >> 4;

            AnimationFrameCoordinates animationFrameCoordinates = kMurphyAnimationFrameCoordinates[35];
            Point frameCoordinates = animationFrameCoordinates.coordinates[currentFrame];

            drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);

            return position;
        }
        else if (rightTile->state != 0 || rightTile->tile != LevelTileTypeSpace)
        {
            // loc_4DFE0:              ; CODE XREF: update?+126j
            //  Sleep to right animation
            uint16_t currentFrame = gMurphyYawnAndSleepCounter - 0x656;
            currentFrame = currentFrame >> 4;

            AnimationFrameCoordinates animationFrameCoordinates = kMurphyAnimationFrameCoordinates[36];
            Point frameCoordinates = animationFrameCoordinates.coordinates[currentFrame];

            drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);

            return position;
        }
        else
        {
            gMurphyYawnAndSleepCounter = 0x24; // 36
            return position;
        }
    }

    // loc_4E001:              ; CODE XREF: update?+5Aj
    //  01ED:739E
    if (gScratchGravity != 0 && (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace))
    {
        if (userInput != UserInputUp || (aboveTile->state != 0 || aboveTile->tile != LevelTileTypeBase))
        {
            // loc_4E01B:              ; CODE XREF: update?+182j
            if (userInput != UserInputLeft || (leftTile->state != 0 || leftTile->tile != LevelTileTypeBase))
            {
                // loc_4E027:              ; CODE XREF: update?+18Ej
                if (userInput != UserInputRight || (rightTile->state != 0 || rightTile->tile != LevelTileTypeBase))
                {
                    // loc_4E033:              ; CODE XREF: update?+19Aj
                    userInput = UserInputDown;
                }
            }
        }
    }

    // loc_4E035:              ; CODE XREF: update?+176j update?+17Dj ...
    //  01ED:73D2
    if (userInput == UserInputUp)
    {
        gPreviousUserInputWasNone = 0;
        return handleMurphyDirectionUp(position);
    }
    // loc_4E041:              ; CODE XREF: update?+1A8j
    else if (userInput == UserInputLeft)
    {
        gPreviousUserInputWasNone = 0;
        return handleMurphyDirectionLeft(position);
    }
    // loc_4E04E:              ; CODE XREF: update?+1B4j
    else if (userInput == UserInputDown)
    {
        gPreviousUserInputWasNone = 0;
        return handleMurphyDirectionDown(position);
    }
    // loc_4E05B:              ; CODE XREF: update?+1C1j
    else if (userInput == UserInputRight)
    {
        gPreviousUserInputWasNone = 0;
        return handleMurphyDirectionRight(position);
    }
    // loc_4E068:              ; CODE XREF: update?+1CEj
    else if (userInput == UserInputSpaceUp)
    {
        gPreviousUserInputWasNone = 0;
        // loc_4E260:              ; CODE XREF: update?+1E2j
        //     mov ax, leveldata[si-78h]
        if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeBase)
        {
            // loc_4E4BD:              ; CODE XREF: update?+3D9j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51840;
            drawMovingFrame(160, 64, position);
            //    pop si
            playBaseSound();
            //            dx = 0x0ECE;
            murphyTile->state = 0x10;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[13]);
        }
        // loc_4E26C:              ; CODE XREF: update?+3D7j
        else if (aboveTile->tile == LevelTileTypeBug)
        {
            // loc_4E4AC:              ; CODE XREF: update?+3E0j
            if (aboveTile->state < 0x80)
            {
                detonateBigExplosion(position);
                return position;
            }

            // loc_4E4B7:              ; CODE XREF: update?+621j
            aboveTile->state = 0;
            aboveTile->tile = LevelTileTypeBase;

            // loc_4E4BD:              ; CODE XREF: update?+3D9j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51840;
            drawMovingFrame(160, 64, position);
            //    pop si
            playBaseSound();
            //            dx = 0x0ECE;
            murphyTile->state = 0x10;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[13]);
        }
        // loc_4E273:              ; CODE XREF: update?+3DEj
        else if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeInfotron)
        {
            // loc_4E5F4:              ; CODE XREF: update?+3E8j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51840;
            drawMovingFrame(160, 64, position);
            //    pop si
            playInfotronSound();
            //            dx = 0x0F6E;
            murphyTile->state = 0x14;
            aboveTile->state = 0xFF;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[23]);
        }
        // loc_4E27B:              ; CODE XREF: update?+3E6j
        else if (aboveTile->tile == LevelTileTypeTerminal)
        {
            // loc_4E712:              ; CODE XREF: update?+249j update?+3EFj
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51840;
            drawMovingFrame(160, 64, position);
            //    pop si
            if (gAreYellowDisksDetonated != 0)
            {
                gMurphyYawnAndSleepCounter = 0xA;
                return position;
            }

            // loc_4E72D:              ; CODE XREF: update?+894j
            //     push    si
            //     mov di, [si+60DDh]
            //            si = kTerminalOnSpriteCoordinates;
            drawMovingFrame(256, 388, position - kLevelWidth);
            //    pop si
            detonateYellowDisks();
            return position;
        }
        // loc_4E282:              ; CODE XREF: update?+3EDj
        else if (aboveTile->tile == LevelTileTypeRedDisk)
        {
            // loc_4E8B6:              ; CODE XREF: update?+3F6j
            //             dx = 0x106E;
            murphyTile->state = 0x20;
            aboveTile->state = 3;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[39]);
        }
        else
        {
            return position;
        }
    }
    // loc_4E075:              ; CODE XREF: update?+1DBj
    else if (userInput == UserInputSpaceLeft)
    {
        gPreviousUserInputWasNone = 0;
        // loc_4E28A:              ; CODE XREF: update?+1EFj
        gIsMurphyLookingLeft = 1;
        //    mov ax, [si+1832h]
        if (leftTile->state == 0 && leftTile->tile == LevelTileTypeBase)
        {
            // loc_4E4E9:              ; CODE XREF: update?+409j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51842;
            drawMovingFrame(208, 16, position);
            //    pop si
            playBaseSound();
            //            dx = 0x0EDE;
            murphyTile->state = 0x11;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[14]);
        }
        // loc_4E29C:              ; CODE XREF: update?+407j
        else if (leftTile->tile == LevelTileTypeBug)
        {
            // loc_4E4D8:              ; CODE XREF: update?+410j
            if (leftTile->state < 0x80)
            {
                detonateBigExplosion(position);
                return position;
            }

            // loc_4E4E3:              ; CODE XREF: update?+64Dj
            leftTile->state = 0;
            leftTile->tile = LevelTileTypeBase;

            // loc_4E4E9:              ; CODE XREF: update?+409j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51842;
            drawMovingFrame(208, 16, position);
            //    pop si
            playBaseSound();
            //            dx = 0x0EDE;
            murphyTile->state = 0x11;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[14]);
        }
        // loc_4E2A3:              ; CODE XREF: update?+40Ej
        else if (leftTile->state == 0 && leftTile->tile == LevelTileTypeInfotron)
        {
            // loc_4E614:              ; CODE XREF: update?+418j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51842;
            drawMovingFrame(208, 16, position);
            //    pop si
            playInfotronSound();
            //            dx = 0x0F7E;
            murphyTile->state = 0x15;
            leftTile->state = 0xFF;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[24]);
        }
        // loc_4E2AB:              ; CODE XREF: update?+416j
        else if (leftTile->tile == LevelTileTypeTerminal)
        {
            // loc_4E73C:              ; CODE XREF: update?+2B9j update?+41Fj
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51842;
            drawMovingFrame(208, 16, position);
            //    pop si
            if (gAreYellowDisksDetonated != 0)
            {
                gMurphyYawnAndSleepCounter = 0xA;
                return position;
            }

            // loc_4E757:              ; CODE XREF: update?+8BEj
            //     push    si
            //     mov di, [si+6153h]
            //            si = kTerminalOnSpriteCoordinates;
            drawMovingFrame(256, 388, position - 1);
            //    pop si
            detonateYellowDisks();
            return position;
        }
        // loc_4E2B2:              ; CODE XREF: update?+41Dj
        else if (leftTile->tile == LevelTileTypeRedDisk)
        {
            // loc_4E8C5:              ; CODE XREF: update?+426j
            //             dx = 0x107E;
            murphyTile->state = 0x21;
            leftTile->state = 3;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[40]);
        }
        else
        {
            return position;
        }
    }
    // loc_4E082:              ; CODE XREF: update?+1E8j
    else if (userInput == UserInputSpaceDown)
    {
        gPreviousUserInputWasNone = 0;
        // loc_4E2BA:              ; CODE XREF: update?+1FCj
        //     mov ax, [si+18ACh]
        if (belowTile->state == 0 && belowTile->tile == LevelTileTypeBase)
        {
            // loc_4E515:              ; CODE XREF: update?+433j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51844;
            drawMovingFrame(176, 64, position);
            //    pop si
            playBaseSound();
            //            dx = 0x0EEE;
            murphyTile->state = 0x12;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[15]);
        }
        // loc_4E2C6:              ; CODE XREF: update?+431j
        else if (belowTile->tile == LevelTileTypeBug)
        {
            // loc_4E504:              ; CODE XREF: update?+43Aj
            if (belowTile->state < 0x80)
            {
                detonateBigExplosion(position);
                return position;
            }

            // loc_4E50F:              ; CODE XREF: update?+679j
            belowTile->state = 0;
            belowTile->tile = LevelTileTypeBase;

            // loc_4E515:              ; CODE XREF: update?+433j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51844;
            drawMovingFrame(176, 64, position);
            //    pop si
            playBaseSound();
            //            dx = 0x0EEE;
            murphyTile->state = 0x12;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[15]);
        }
        // loc_4E2CD:              ; CODE XREF: update?+438j
        else if (belowTile->state == 0 && belowTile->tile == LevelTileTypeInfotron)
        {
            // loc_4E634:              ; CODE XREF: update?+442j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51844;
            drawMovingFrame(176, 64, position);
            //    pop si
            playInfotronSound();
            //            dx = 0x0F8E;
            murphyTile->state = 0x16;
            belowTile->state = 0xFF;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[25]);
        }
        // loc_4E2D5:              ; CODE XREF: update?+440j
        else if (belowTile->tile == LevelTileTypeTerminal)
        {
            // loc_4E766:              ; CODE XREF: update?+325j update?+449j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51844;
            drawMovingFrame(176, 64, position);
            //    pop si
            if (gAreYellowDisksDetonated != 0)
            {
                gMurphyYawnAndSleepCounter = 0xA;
                return position;
            }

            // loc_4E781:              ; CODE XREF: update?+8E8j
            //     push    si
            //     mov di, [si+61CDh]
            //            si = kTerminalOnSpriteCoordinates;
            drawMovingFrame(256, 388, position + kLevelWidth);
            //    pop si
            detonateYellowDisks();
            return position;
        }
        // loc_4E2DC:              ; CODE XREF: update?+447j
        else if (belowTile->tile == LevelTileTypeRedDisk)
        {
            // loc_4E8D4:              ; CODE XREF: update?+450j
            //             dx = 0x108E;
            murphyTile->state = 0x22;
            belowTile->state = 3;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[41]);
        }
        else
        {
            return position;
        }
    }
    // loc_4E08F:              ; CODE XREF: update?+1F5j
    else if (userInput == UserInputSpaceRight)
    {
        gPreviousUserInputWasNone = 0;
        // loc_4E2E4:              ; CODE XREF: update?+209j
        gIsMurphyLookingLeft = 0;
        //    mov ax, [si+1836h]
        if (rightTile->state == 0 && rightTile->tile == LevelTileTypeBase)
        {
            // loc_4E541:              ; CODE XREF: update?+463j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51846;
            drawMovingFrame(192, 16, position);
            //    pop si
            playBaseSound();
            //            dx = 0x0EFE;
            murphyTile->state = 0x13;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[16]);
        }
        // loc_4E2F6:              ; CODE XREF: update?+461j
        else if (rightTile->tile == LevelTileTypeBug)
        {
            // loc_4E530:              ; CODE XREF: update?+46Aj
            if (rightTile->state < 0x80)
            {
                detonateBigExplosion(position);
                return position;
            }

            // loc_4E53B:              ; CODE XREF: update?+6A5j
            rightTile->state = 0;
            rightTile->tile = LevelTileTypeBase;

            // loc_4E541:              ; CODE XREF: update?+463j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51846;
            drawMovingFrame(192, 16, position);
            //    pop si
            playBaseSound();
            //            dx = 0x0EFE;
            murphyTile->state = 0x13;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[16]);
        }
        // loc_4E2FD:              ; CODE XREF: update?+468j
        else if (rightTile->state == 0 && rightTile->tile == LevelTileTypeInfotron)
        {
            // loc_4E654:              ; CODE XREF: update?+472j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51846;
            drawMovingFrame(192, 16, position);
            //    pop si
            playInfotronSound();
            //            dx = 0x0F9E;
            murphyTile->state = 0x17;
            rightTile->state = 0xFF;
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[26]);
        }
        // loc_4E305:              ; CODE XREF: update?+470j
        else if (rightTile->tile != LevelTileTypeTerminal)
        {
            // loc_4E30C:              ; CODE XREF: update?+477j
            if (rightTile->tile != LevelTileTypeRedDisk)
            {
                return position;
            }
            // loc_4E8E3:              ; CODE XREF: update?+480j
            //             dx = 0x109E;
            murphyTile->state = 0x23;
            rightTile->state = 3;

            // loc_4E8F0:              ; CODE XREF: update?+4DAj update?+4F7j ...
            gMurphyCounterToStartPushAnimation = 0;
            gIsMurphyGoingThroughPortal = 0;
            return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[42]);
        }
        else
        {
            // loc_4E790:              ; CODE XREF: update?+395j update?+479j
            //     push    si
            //     mov di, [si+6155h]
            //            si = word_51846;
            drawMovingFrame(192, 16, position);
            //    pop si
            if (gAreYellowDisksDetonated != 0)
            {
                gMurphyYawnAndSleepCounter = 0xA;
                return position;
            }

            // loc_4E7AB:              ; CODE XREF: update?+912j
            //     push    si
            //     mov di, [si+6157h]
            //            si = kTerminalOnSpriteCoordinates;
            drawMovingFrame(256, 388, position + 1);
            //    pop si

            detonateYellowDisks();
            return position;
        }
    }
    // loc_4E09C:              ; CODE XREF: update?+202j
    else if (userInput == UserInputSpaceOnly)
    {
        // loc_4E314:              ; CODE XREF: update?+211j
        if (gNumberOfRemainingRedDisks == 0 || gPlantedRedDiskCountdown != 0 || gPreviousUserInputWasNone != 1)
        {
            return position;
        }
        murphyTile->state = 0x2A;
        gMurphyCounterToStartPushAnimation = 0x40; // 64
                                                   //        dx = 0x110E;
        gPlantedRedDiskCountdown = 1;
        gPlantedRedDiskPosition = position;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[49]);
    }
    else
    {
        // loc_4E0A4:              ; CODE XREF: update?+20Fj
        gPreviousUserInputWasNone = 0;
        return position;
    }
}

int16_t handleMurphyDirectionUp(int16_t position)
{
    // 01ED:7447
    StatefulLevelTile *murphyTile = &gCurrentLevelState[position];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *aboveAboveTile = &gCurrentLevelState[position - kLevelWidth * 2];

    // loc_4E0AA:              ; CODE XREF: update?+1AFj update?+279j
    if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
    {
        MurphyAnimationDescriptor animationDescriptor;

        // loc_4E344:              ; CODE XREF: update?+223j
        if (gIsMurphyLookingLeft != 0)
        {
            animationDescriptor = kMurphyAnimationDescriptors[0]; // dx = 0x0DFE;
        }
        else
        {
            // loc_4E350:              ; CODE XREF: update?+4B9j
            animationDescriptor = kMurphyAnimationDescriptors[1]; // dx = 0x0E0E;
        }

        // loc_4E353:              ; CODE XREF: update?+4BEj
        aboveTile->state = 1;
        aboveTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;

        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position - kLevelWidth, animationDescriptor);
    }
    // loc_4E0B6:              ; CODE XREF: update?+221j
    else if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeBase)
    {
        MurphyAnimationDescriptor unknownMurphyData;

        // loc_4E3E1:              ; CODE XREF: update?+22Bj
        playBaseSound();
        if (gIsMurphyLookingLeft != 0)
        {
            unknownMurphyData = kMurphyAnimationDescriptors[7]; // dx = 0x0E6E;
        }
        else
        {
            // loc_4E3F0:              ; CODE XREF: update?+559j
            unknownMurphyData = kMurphyAnimationDescriptors[8]; // dx = 0x0E7E;
        }

        // loc_4E3F3:              ; CODE XREF: update?+55Ej

        aboveTile->state = 5;
        aboveTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position - kLevelWidth, unknownMurphyData);
    }
    // loc_4E0BE:              ; CODE XREF: update?+229j
    else if (aboveTile->tile == LevelTileTypeBug)
    {
        // loc_4E3D0:              ; CODE XREF: update?+232j
        //     cmp byte ptr [si+17BDh], 0
        //     jl  short loc_4E3DB
        if (aboveTile->state < 0x80)
        {
            detonateBigExplosion(position);
            return position;
        }

        // loc_4E3DB:              ; CODE XREF: update?+545j
        aboveTile->state = 0;
        aboveTile->tile = LevelTileTypeBase;

        // loc_4E3E1:              ; CODE XREF: update?+22Bj
        MurphyAnimationDescriptor animationDescriptor;
        playBaseSound();
        if (gIsMurphyLookingLeft != 0)
        {
            animationDescriptor = kMurphyAnimationDescriptors[7]; // dx = 0x0E6E;
        }
        else
        {
            // loc_4E3F0:              ; CODE XREF: update?+559j
            animationDescriptor = kMurphyAnimationDescriptors[8]; // dx = 0x0E7E;
        }

        // loc_4E3F3:              ; CODE XREF: update?+55Ej

        aboveTile->state = 5;
        aboveTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position - kLevelWidth, animationDescriptor);
    }
    // loc_4E0C5:              ; CODE XREF: update?+230j
    else if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeInfotron)
    {
        MurphyAnimationDescriptor unknownMurphyData;

        // loc_4E55C:              ; CODE XREF: update?+23Aj
        playInfotronSound();
        if (gIsMurphyLookingLeft != 0)
        {
            unknownMurphyData = kMurphyAnimationDescriptors[17]; // dx = 0x0F0E;
        }
        else
        {
            // loc_4E56B:              ; CODE XREF: update?+6D4j
            unknownMurphyData = kMurphyAnimationDescriptors[18]; // dx = 0x0F1E;
        }

        // loc_4E56E:              ; CODE XREF: update?+6D9j
        aboveTile->state = 9;
        aboveTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position - kLevelWidth, unknownMurphyData);
    }
    // loc_4E0CD:              ; CODE XREF: update?+238j
    else if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeExit)
    {
        // loc_4E674:              ; CODE XREF: update?+242j update?+2AAj ...
        if (gNumberOfRemainingInfotrons != 0)
        {
            return position;
        }
        playExitSound();
        byte_5A19B = 1;
        gCurrentPlayerLevelState = PlayerLevelStateCompleted;
        gLevelFailed = 0;
        if (gHasUserCheated == 0 && gShouldUpdateTotalLevelTime != 0)
        {
            byte_5A323 = 1;
            addCurrentGameTimeToPlayer();
        }

        // loc_4E6A4:              ; CODE XREF: update?+803j update?+80Aj
        changePlayerCurrentLevelState();
        gQuitLevelCountdown = 0x40;
        //    pop si
        //        dx = 0x0E5E;
        murphyTile->state = 0xD;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[6]);
    }
    // loc_4E0D5:              ; CODE XREF: update?+240j
    else if (aboveTile->tile == LevelTileTypeTerminal)
    {
        // loc_4E712:              ; CODE XREF: update?+249j update?+3EFj
        //  01ED:7AAF
        //    push    si
        //    mov di, [si+6155h]
        //        si = word_51840;
        drawMovingFrame(160, 64, position);
        //    pop si
        if (gAreYellowDisksDetonated != 0)
        {
            gMurphyYawnAndSleepCounter = 0xA;
            return position;
        }

        // loc_4E72D:              ; CODE XREF: update?+894j
        //     push    si
        //     mov di, [si+60DDh]
        //        si = kTerminalOnSpriteCoordinates;
        drawMovingFrame(256, 388, position - kLevelWidth);
        //    pop si
        detonateYellowDisks();
        return position;
    }
    // loc_4E0DC:              ; CODE XREF: update?+247j
    else if (aboveTile->tile == LevelTileTypePortUp || aboveTile->tile == LevelTileTypePortVertical || aboveTile->tile == LevelTileTypePort4Way)
    {
        // loc_4E7DE:              ; CODE XREF: update?+250j update?+257j ...
        if (aboveAboveTile->state != 0 || aboveAboveTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E7E6:              ; CODE XREF: update?+953j
        //         dx = 0x0FCE;
        murphyTile->state = 0x18;
        aboveAboveTile->state = 3;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 1;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[29]);
    }
    // loc_4E0F1:              ; CODE XREF: update?+25Cj
    else if (aboveTile->tile == LevelTileTypeRedDisk)
    {
        MurphyAnimationDescriptor unknownMurphyData;

        // loc_4E847:              ; CODE XREF: update?+265j
        if (gIsMurphyLookingLeft != 0)
        {
            unknownMurphyData = kMurphyAnimationDescriptors[33]; // dx = 0x100E;
        }
        else
        {
            // loc_4E853:              ; CODE XREF: update?+9BCj
            unknownMurphyData = kMurphyAnimationDescriptors[34]; // dx = 0x101E;
        }

        // loc_4E856:              ; CODE XREF: update?+9C1j
        murphyTile->state = 0x1C;
        aboveTile->state = 3;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, unknownMurphyData);
    }
    // loc_4E0F8:              ; CODE XREF: update?+263j
    else if (aboveTile->tile == LevelTileTypeYellowDisk)
    {
        // loc_4E8F9:              ; CODE XREF: update?+26Cj
        if (aboveAboveTile->state != 0 || aboveAboveTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E903:              ; CODE XREF: update?+A70j
        aboveAboveTile->state = 0x12;
        //    push    si
        //    mov di, [si+6155h]
        //        si = word_5157C;
        drawMovingFrame(97, 132, position);
        //    pop si
        //        dx = 0x10AE;
        murphyTile->state = 0x24;
        gMurphyCounterToStartPushAnimation = 8;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[43]);
    }
    // loc_4E0FF:              ; CODE XREF: update?+26Aj
    else if (checkMurphyMovementToPosition(position - kLevelWidth, UserInputUp) != 1)
    {
        return handleMurphyDirectionUp(position);
    }
    else
    {
        return position;
    }
}

int16_t handleMurphyDirectionLeft(int16_t position)
{
    // 01ED:74A9
    StatefulLevelTile *murphyTile = &gCurrentLevelState[position];
    StatefulLevelTile *leftLeftTile = &gCurrentLevelState[position - 2];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];

    // loc_4E10C:              ; CODE XREF: update?+1BBj update?+2F3j
    gIsMurphyLookingLeft = 1;
    //    mov ax, [si+1832h]
    if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
    {
        // loc_4E36D:              ; CODE XREF: update?+28Bj
        //         dx = 0x0E1E;
        leftTile->state = 2;
        leftTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position - 1, kMurphyAnimationDescriptors[2]);
    }
    // loc_4E11E:              ; CODE XREF: update?+289j
    else if (leftTile->state == 0 && leftTile->tile == LevelTileTypeBase) // 01ED:7634
    {
        // loc_4E41E:              ; CODE XREF: update?+293j
        //  01ED:77BB
        playBaseSound();
        //        dx = 0x0E8E;
        leftTile->state = 2;
        leftTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position - 1, kMurphyAnimationDescriptors[9]);
    }
    // loc_4E126:              ; CODE XREF: update?+291j
    else if (leftTile->tile == LevelTileTypeBug) // 01ED:763B
    {
        // loc_4E40D:              ; CODE XREF: update?+29Aj
        if (leftTile->state < 0x80)
        {
            detonateBigExplosion(position);
            return position;
        }

        // loc_4E418:              ; CODE XREF: update?+582j
        //  01ED:77B5
        leftTile->state = 0;
        leftTile->tile = LevelTileTypeBase;

        // loc_4E41E:              ; CODE XREF: update?+293j
        playBaseSound();
        //        dx = 0x0E8E;
        leftTile->state = 2;
        leftTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position - 1, kMurphyAnimationDescriptors[9]);
    }
    // loc_4E12D:              ; CODE XREF: update?+298j
    else if (leftTile->state == 0 && leftTile->tile == LevelTileTypeInfotron)
    {
        // loc_4E588:              ; CODE XREF: update?+2A2j
        playInfotronSound();
        //        dx = 0x0F2E;
        leftTile->state = 10;
        leftTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position - 1, kMurphyAnimationDescriptors[19]);
    }
    // loc_4E135:              ; CODE XREF: update?+2A0j
    else if (leftTile->state == 0 && leftTile->tile == LevelTileTypeExit)
    {
        // loc_4E674:              ; CODE XREF: update?+242j update?+2AAj ...
        if (gNumberOfRemainingInfotrons != 0)
        {
            return position;
        }
        playExitSound();
        byte_5A19B = 1;
        gCurrentPlayerLevelState = PlayerLevelStateCompleted;
        gLevelFailed = 0;
        if (gHasUserCheated == 0 && gShouldUpdateTotalLevelTime != 0)
        {
            byte_5A323 = 1;
            addCurrentGameTimeToPlayer();
        }

        // loc_4E6A4:              ; CODE XREF: update?+803j update?+80Aj
        changePlayerCurrentLevelState();
        gQuitLevelCountdown = 0x40;
        //    pop si
        //        dx = 0x0E5E;
        murphyTile->state = 0xD;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[6]);
    }
    // loc_4E13D:              ; CODE XREF: update?+2A8j
    else if (leftTile->state == 0 && leftTile->tile == LevelTileTypeZonk)
    {
        // loc_4E6BA:              ; CODE XREF: update?+2B2j
        //     mov ax, [si+1830h]
        if (leftLeftTile->state != 0 || leftLeftTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E6C4:              ; CODE XREF: update?+831j
        leftLeftTile->state = 1;
        //    push    si
        //    mov di, [si+6155h]
        //        si = word_5157A;
        drawMovingFrame(64, 132, position);
        //    pop si
        //        dx = 0x0FAE;
        murphyTile->state = 0xE;
        gMurphyCounterToStartPushAnimation = 8;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[27]);
    }
    // loc_4E145:              ; CODE XREF: update?+2B0j
    else if (leftTile->tile == LevelTileTypeTerminal)
    {
        // loc_4E73C:              ; CODE XREF: update?+2B9j update?+41Fj
        //     push    si
        //     mov di, [si+6155h]
        //        si = word_51842;
        drawMovingFrame(208, 16, position);
        //    pop si
        if (gAreYellowDisksDetonated != 0)
        {
            gMurphyYawnAndSleepCounter = 0xA;
            return position;
        }

        // loc_4E757:              ; CODE XREF: update?+8BEj
        //     push    si
        //     mov di, [si+6153h]
        //        si = kTerminalOnSpriteCoordinates;
        drawMovingFrame(256, 388, position - 1);
        //    pop si
        detonateYellowDisks();
        return position;
    }
    // loc_4E14C:              ; CODE XREF: update?+2B7j
    else if (leftTile->tile == LevelTileTypePortLeft || leftTile->tile == LevelTileTypePortHorizontal || leftTile->tile == LevelTileTypePort4Way)
    {
        // loc_4E7F5:              ; CODE XREF: update?+2C0j update?+2C7j ...
        if (leftLeftTile->state != 0 || leftLeftTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E7FD:              ; CODE XREF: update?+96Aj
        //         dx = 0x0FDE;
        murphyTile->state = 0x19;
        leftLeftTile->state = 3;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 1;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[30]);
    }
    // loc_4E161:              ; CODE XREF: update?+2CCj
    else if (leftTile->state == 0 && leftTile->tile == LevelTileTypeRedDisk)
    {
        // loc_4E863:              ; CODE XREF: update?+2D6j
        //         dx = 0x102E;
        leftTile->state = 0x1D;
        leftTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position - 1, kMurphyAnimationDescriptors[35]);
    }
    // loc_4E169:              ; CODE XREF: update?+2D4j
    else if (leftTile->state == 0 && leftTile->tile == LevelTileTypeYellowDisk)
    {
        // loc_4E920:              ; CODE XREF: update?+2DEj
        if (leftLeftTile->state != 0 || leftLeftTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E92A:              ; CODE XREF: update?+A97j
        leftLeftTile->state = 0x12;
        //    push    si
        //    mov di, [si+6155h]
        //        si = word_5157A;
        drawMovingFrame(64, 132, position);
        //    pop si
        //        dx = 0x10BE;
        murphyTile->state = 0x25;
        gMurphyCounterToStartPushAnimation = 8;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[44]);
    }
    // loc_4E171:              ; CODE XREF: update?+2DCj
    else if (leftTile->state == 0 && leftTile->tile == LevelTileTypeOrangeDisk)
    {
        // loc_4E993:              ; CODE XREF: update?+2E6j
        if (leftLeftTile->state != 0 || leftLeftTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E99D:              ; CODE XREF: update?+B0Aj
        leftLeftTile->state = 8;
        //    push    si
        //    mov di, [si+6155h]
        //        si = word_5157A;
        drawMovingFrame(64, 132, position);
        //    pop si
        //        dx = 0x10EE;
        murphyTile->state = 0x28;
        gMurphyCounterToStartPushAnimation = 8;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[47]);
    }
    // loc_4E179:              ; CODE XREF: update?+2E4j
    else if (checkMurphyMovementToPosition(position - 1, UserInputLeft) != 1)
    {
        return handleMurphyDirectionLeft(position);
    }
    else
    {
        return position;
    }
}

int16_t handleMurphyDirectionDown(int16_t position)
{
    StatefulLevelTile *murphyTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *belowBelowTile = &gCurrentLevelState[position + kLevelWidth * 2];

    // loc_4E186:              ; CODE XREF: update?+1C8j update?+355j
    //     mov ax, leveldata[si+78h]
    if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
    {
        MurphyAnimationDescriptor unknownMurphyData;

        // loc_4E38A:              ; CODE XREF: update?+69j update?+2FFj
        if (gIsMurphyLookingLeft != 0)
        {
            unknownMurphyData = kMurphyAnimationDescriptors[3]; // dx = 0x0E2E;
        }
        else
        {
            // loc_4E396:              ; CODE XREF: update?+4FFj
            unknownMurphyData = kMurphyAnimationDescriptors[4]; // dx = 0x0E3E;
        }

        // loc_4E399:              ; CODE XREF: update?+504j
        belowTile->state = 3;
        belowTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position + kLevelWidth, unknownMurphyData);
    }
    // loc_4E192:              ; CODE XREF: update?+2FDj
    else if (belowTile->state == 0 && belowTile->tile == LevelTileTypeBase)
    {
        MurphyAnimationDescriptor unknownMurphyData;

        // loc_4E44F:              ; CODE XREF: update?+307j
        playBaseSound();
        if (gIsMurphyLookingLeft != 0)
        {
            unknownMurphyData = kMurphyAnimationDescriptors[10]; // dx = 0x0E9E;
        }
        else
        {
            // loc_4E45E:              ; CODE XREF: update?+5C7j
            unknownMurphyData = kMurphyAnimationDescriptors[11]; // dx = 0x0EAE;
        }

        // loc_4E461:              ; CODE XREF: update?+5CCj
        belowTile->state = 7;
        belowTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position + kLevelWidth, unknownMurphyData);
    }
    // loc_4E19A:              ; CODE XREF: update?+305j
    else if (belowTile->tile == LevelTileTypeBug)
    {
        // loc_4E43E:              ; CODE XREF: update?+30Ej
        if (belowTile->state < 0x80)
        {
            detonateBigExplosion(position);
            return position;
        }

        // loc_4E449:              ; CODE XREF: update?+5B3j
        belowTile->state = 0;
        belowTile->tile = LevelTileTypeBase;

        MurphyAnimationDescriptor unknownMurphyData;

        // loc_4E44F:              ; CODE XREF: update?+307j
        playBaseSound();
        if (gIsMurphyLookingLeft != 0)
        {
            unknownMurphyData = kMurphyAnimationDescriptors[10]; // dx = 0x0E9E;
        }
        else
        {
            // loc_4E45E:              ; CODE XREF: update?+5C7j
            unknownMurphyData = kMurphyAnimationDescriptors[11]; // dx = 0x0EAE;
        }

        // loc_4E461:              ; CODE XREF: update?+5CCj
        belowTile->state = 7;
        belowTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position + kLevelWidth, unknownMurphyData);
    }
    // loc_4E1A1:              ; CODE XREF: update?+30Cj
    else if (belowTile->tile == LevelTileTypeInfotron)
    {
        MurphyAnimationDescriptor unknownMurphyData;

        // loc_4E5A8:              ; CODE XREF: update?+316j
        playInfotronSound();
        if (gIsMurphyLookingLeft != 0)
        {
            unknownMurphyData = kMurphyAnimationDescriptors[20]; // dx = 0x0F3E;
        }
        else
        {
            // loc_4E5B7:              ; CODE XREF: update?+720j
            unknownMurphyData = kMurphyAnimationDescriptors[21]; // dx = 0x0F4E;
        }

        // loc_4E5BA:              ; CODE XREF: update?+725j
        belowTile->state = 11;
        belowTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeBase;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position + kLevelWidth, unknownMurphyData);
    }
    // loc_4E1A9:              ; CODE XREF: update?+314j
    else if (belowTile->tile == LevelTileTypeExit)
    {
        // loc_4E674:              ; CODE XREF: update?+242j update?+2AAj ...
        if (gNumberOfRemainingInfotrons != 0)
        {
            return position;
        }
        playExitSound();
        byte_5A19B = 1;
        gCurrentPlayerLevelState = PlayerLevelStateCompleted;
        gLevelFailed = 0;
        if (gHasUserCheated == 0 && gShouldUpdateTotalLevelTime != 0)
        {
            byte_5A323 = 1;
            addCurrentGameTimeToPlayer();
        }

        // loc_4E6A4:              ; CODE XREF: update?+803j update?+80Aj
        changePlayerCurrentLevelState();
        gQuitLevelCountdown = 0x40;
        //    pop si
        //        dx = 0x0E5E;
        murphyTile->state = 0xD;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[6]);
    }
    // loc_4E1B1:              ; CODE XREF: update?+31Cj
    else if (belowTile->tile == LevelTileTypeTerminal)
    {
        // loc_4E766:              ; CODE XREF: update?+325j update?+449j
        //     push    si
        //     mov di, [si+6155h]
        //        si = word_51844;
        drawMovingFrame(176, 64, position);
        //    pop si
        if (gAreYellowDisksDetonated != 0)
        {
            gMurphyYawnAndSleepCounter = 0xA;
            return position;
        }

        // loc_4E781:              ; CODE XREF: update?+8E8j
        //     push    si
        //     mov di, [si+61CDh]
        //        si = kTerminalOnSpriteCoordinates;
        drawMovingFrame(256, 388, position + kLevelWidth);
        //    pop si
        detonateYellowDisks();
        return position;
    }
    // loc_4E1B8:              ; CODE XREF: update?+323j
    else if (belowTile->tile == LevelTileTypePortDown || belowTile->tile == LevelTileTypePortVertical || belowTile->tile == LevelTileTypePort4Way)
    {
        // loc_4E80C:              ; CODE XREF: update?+32Cj update?+333j ...
        if (belowBelowTile->state != 0 || belowBelowTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E814:              ; CODE XREF: update?+981j
        //         dx = 0x0FEE;
        murphyTile->state = 0x1A;
        belowBelowTile->state = 3;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 1;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[31]);
    }
    // loc_4E1CD:              ; CODE XREF: update?+338j
    else if (belowTile->tile == LevelTileTypeRedDisk)
    {
        MurphyAnimationDescriptor unknownMurphyData;

        // loc_4E87F:              ; CODE XREF: update?+341j
        if (gIsMurphyLookingLeft != 0)
        {
            unknownMurphyData = kMurphyAnimationDescriptors[36]; // dx = 0x103E;
        }
        else
        {
            // loc_4E88B:              ; CODE XREF: update?+9F4j
            unknownMurphyData = kMurphyAnimationDescriptors[37]; // dx = 0x104E;
        }

        // loc_4E88E:              ; CODE XREF: update?+9F9j
        murphyTile->state = 0x1E;
        belowTile->state = 3;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, unknownMurphyData);
    }
    // loc_4E1D4:              ; CODE XREF: update?+33Fj
    else if (belowTile->tile == LevelTileTypeYellowDisk)
    {
        // loc_4E947:              ; CODE XREF: update?+348j
        if (belowBelowTile->state != 0 || belowBelowTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E951:              ; CODE XREF: update?+ABEj
        belowBelowTile->state = 0x12;
        //    push    si
        //    mov di, [si+6155h]
        //        si = word_5157C;
        drawMovingFrame(97, 132, position);
        //    pop si
        //        dx = 0x10CE;
        murphyTile->state = 0x27;
        gMurphyCounterToStartPushAnimation = 8;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[45]);
    }
    // loc_4E1DB:              ; CODE XREF: update?+346j
    else if (checkMurphyMovementToPosition(position + kLevelWidth, UserInputDown) != 1)
    {
        return handleMurphyDirectionDown(position);
    }
    else
    {
        return position;
    }
}

int16_t handleMurphyDirectionRight(int16_t position)
{
    StatefulLevelTile *murphyTile = &gCurrentLevelState[position];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];
    StatefulLevelTile *rightRightTile = &gCurrentLevelState[position + 2];
    StatefulLevelTile *belowRightTile = &gCurrentLevelState[position + kLevelWidth + 1];

    // loc_4E1E8:              ; CODE XREF: update?+1D5j update?+3CDj
    gIsMurphyLookingLeft = 0;
    //    mov ax, leveldata[si+2]
    if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
    {
        // loc_4E3B3:              ; CODE XREF: update?+367j
        //         dx = 0x0E4E;
        rightTile->state = 4;
        rightTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position + 1, kMurphyAnimationDescriptors[5]);
    }
    // loc_4E1FA:              ; CODE XREF: update?+365j
    else if (rightTile->state == 0 && rightTile->tile == LevelTileTypeBase)
    {
        // loc_4E48C:              ; CODE XREF: update?+36Fj
        playBaseSound();
        //        dx = 0x0EBE;
        rightTile->state = 8;
        rightTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position + 1, kMurphyAnimationDescriptors[12]);
    }
    // loc_4E202:              ; CODE XREF: update?+36Dj
    else if (rightTile->tile == LevelTileTypeBug)
    {
        // loc_4E47B:              ; CODE XREF: update?+376j
        if (rightTile->state < 0x80)
        {
            detonateBigExplosion(position);
            return position;
        }

        // loc_4E486:              ; CODE XREF: update?+5F0j
        rightTile->state = 0;
        rightTile->tile = LevelTileTypeBase;

        // loc_4E48C:              ; CODE XREF: update?+36Fj
        playBaseSound();
        //        dx = 0x0EBE;
        rightTile->state = 8;
        rightTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position + 1, kMurphyAnimationDescriptors[12]);
    }
    // loc_4E209:              ; CODE XREF: update?+374j
    else if (rightTile->state == 0 && rightTile->tile == LevelTileTypeInfotron)
    {
        // loc_4E5D4:              ; CODE XREF: update?+37Ej
        playInfotronSound();
        //        dx = 0x0F5E;
        rightTile->state = 12;
        rightTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position + 1, kMurphyAnimationDescriptors[22]);
    }
    // loc_4E211:              ; CODE XREF: update?+37Cj
    else if (rightTile->state == 0 && rightTile->tile == LevelTileTypeExit)
    {
        // loc_4E674:              ; CODE XREF: update?+242j update?+2AAj ...
        if (gNumberOfRemainingInfotrons != 0)
        {
            return position;
        }
        playExitSound();
        byte_5A19B = 1;
        gCurrentPlayerLevelState = PlayerLevelStateCompleted;
        gLevelFailed = 0;
        if (gHasUserCheated == 0 && gShouldUpdateTotalLevelTime != 0)
        {
            byte_5A323 = 1;
            addCurrentGameTimeToPlayer();
        }

        // loc_4E6A4:              ; CODE XREF: update?+803j update?+80Aj
        changePlayerCurrentLevelState();
        gQuitLevelCountdown = 0x40;
        murphyTile->state = 0xD;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[6]);
    }
    // loc_4E219:              ; CODE XREF: update?+384j
    else if (rightTile->state == 0 && rightTile->tile == LevelTileTypeZonk)
    {
        // loc_4E6E1:              ; CODE XREF: update?+38Ej
        //     mov ax, [si+1838h]
        if (rightRightTile->state != 0 || rightRightTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E6EB:              ; CODE XREF: update?+858j
        //     mov ax, [si+18AEh]
        if (belowRightTile->state == 0 && belowRightTile->tile == LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E6F5:              ; CODE XREF: update?+862j
        rightRightTile->state = 1;
        //    push    si
        //    mov di, [si+6155h]
        //        si = word_5157C;
        drawMovingFrame(97, 132, position);
        //    pop si
        //        dx = 0x0FBE;
        murphyTile->state = 0xF;
        gMurphyCounterToStartPushAnimation = 8;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[28]);
    }
    // loc_4E221:              ; CODE XREF: update?+38Cj
    else if (rightTile->tile == LevelTileTypeTerminal)
    {
        // loc_4E790:              ; CODE XREF: update?+395j update?+479j
        //     push    si
        //     mov di, [si+6155h]
        //        si = word_51846;
        drawMovingFrame(192, 16, position);
        //    pop si
        if (gAreYellowDisksDetonated != 0)
        {
            gMurphyYawnAndSleepCounter = 0xA;
            return position;
        }

        // loc_4E7AB:              ; CODE XREF: update?+912j
        //     push    si
        //     mov di, [si+6157h]
        //        si = kTerminalOnSpriteCoordinates;
        drawMovingFrame(256, 388, position + 1);
        //    pop si

        detonateYellowDisks();
        return position;
    }
    // loc_4E228:              ; CODE XREF: update?+393j
    else if (rightTile->tile == LevelTileTypePortRight || rightTile->tile == LevelTileTypePortHorizontal || rightTile->tile == LevelTileTypePort4Way)
    {
        // loc_4E823:              ; CODE XREF: update?+39Cj update?+3A3j ...
        if (rightRightTile->state != 0 || rightRightTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E82B:              ; CODE XREF: update?+998j
        //         dx = 0x0FFE;
        murphyTile->state = 0x1B;
        rightRightTile->state = 3;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 1;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[32]);
    }
    // loc_4E23D:              ; CODE XREF: update?+3A8j
    else if (rightTile->tile == LevelTileTypeRedDisk)
    {
        // loc_4E89A:              ; CODE XREF: update?+3B1j
        //         dx = 0x105E;
        rightTile->state = 0x1F;
        rightTile->tile = LevelTileTypeMurphy;
        murphyTile->state = 3;
        murphyTile->tile = LevelTileTypeSpace;
        gMurphyCounterToStartPushAnimation = 0;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position + 1, kMurphyAnimationDescriptors[38]);
    }
    // loc_4E244:              ; CODE XREF: update?+3AFj
    else if (rightTile->tile == LevelTileTypeYellowDisk)
    {
        // loc_4E96D:              ; CODE XREF: update?+3B8j
        if (rightRightTile->state != 0 || rightRightTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E977:              ; CODE XREF: update?+AE4j
        rightRightTile->state = 0x12;
        //    push    si
        //    mov di, [si+6155h]
        //        si = word_5157C;
        drawMovingFrame(97, 132, position);
        //    pop si
        //        dx = 0x10DE;
        murphyTile->state = 0x26;
        gMurphyCounterToStartPushAnimation = 8;
        gIsMurphyGoingThroughPortal = 0;
        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[46]);
    }
    // loc_4E24B:              ; CODE XREF: update?+3B6j
    else if (rightTile->state == 0 && rightTile->tile == LevelTileTypeOrangeDisk)
    {
        // loc_4E9B9:              ; CODE XREF: update?+3C0j
        if (rightRightTile->state != 0 || rightRightTile->tile != LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E9C3:              ; CODE XREF: update?+B30j
        if (belowRightTile->state == 0 && belowRightTile->tile == LevelTileTypeSpace)
        {
            return position;
        }

        // loc_4E9CD:              ; CODE XREF: update?+B3Aj
        rightRightTile->state = 1;
        //    push    si
        //    mov di, [si+6155h]
        //        si = word_5157C;
        drawMovingFrame(97, 132, position);
        //    pop si
        //        dx = 0x10FE;
        murphyTile->state = 0x29;

        // loc_4E9E7:              ; CODE XREF: update?+84Ej update?+87Fj ...
        gMurphyCounterToStartPushAnimation = 8;

        // loc_4E9ED:              ; CODE XREF: update?+A66j
        gIsMurphyGoingThroughPortal = 0;

        return updateMurphyAnimationInfo(position, kMurphyAnimationDescriptors[48]);
    }
    // loc_4E253:              ; CODE XREF: update?+3BEj
    else if (checkMurphyMovementToPosition(position + 1, UserInputRight) != 1)
    {
        return handleMurphyDirectionRight(position);
    }
    else
    {
        return position;
    }
}

int16_t updateMurphyAnimationInfo(int16_t position, MurphyAnimationDescriptor unknownMurphyData)
{
    // 01ED:7D9F

    // loc_4E9F3:              ; CODE XREF: update?+4B0j update?+9B4j
    //     di = 0x0DE0;
    //     memcpy(di, si, 7 * 2); // rep movsw

    gCurrentMurphyAnimation = unknownMurphyData;

    return updateMurphyAnimation(position);
}

int16_t updateMurphyAnimation(int16_t position)
{
    // 01ED:7DA4

    StatefulLevelTile *murphyTile = &gCurrentLevelState[position];
    StatefulLevelTile *leftLeftTile = &gCurrentLevelState[position - 2];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];
    StatefulLevelTile *rightRightTile = &gCurrentLevelState[position + 2];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *belowBelowTile = &gCurrentLevelState[position + kLevelWidth * 2];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *aboveAboveTile = &gCurrentLevelState[position - kLevelWidth * 2];
    StatefulLevelTile *belowRightRightTile = &gCurrentLevelState[position + kLevelWidth + 2];

    // loc_4EA07:              ; CODE XREF: update?+21j
    gMurphyYawnAndSleepCounter = 0;

    if (gMurphyCounterToStartPushAnimation == 0)
    {
        // 01ED:7E08
        uint8_t currentFrame = gCurrentMurphyAnimation.currentFrame;
        AnimationFrameCoordinates animationFrameCoordinates = kMurphyAnimationFrameCoordinates[gCurrentMurphyAnimation.animationIndex];
        Point frameCoordinates = animationFrameCoordinates.coordinates[currentFrame];

        // loc_4EA6B:              ; CODE XREF: update?+B83j
        gMurphyPositionX += gCurrentMurphyAnimation.speedX;
        gMurphyPositionY += gCurrentMurphyAnimation.speedY;
        gCurrentMurphyAnimation.currentFrame++;

        uint16_t dstX = (position % kLevelWidth) * kTileSize;
        uint16_t dstY = (position / kLevelWidth) * kTileSize;

        int16_t offsetX = (gCurrentMurphyAnimation.animationCoordinatesOffset % 122) * 8;
        int16_t offsetY = (gCurrentMurphyAnimation.animationCoordinatesOffset / 122);

        // loc_4EA9F:              ; CODE XREF: update?+C28j
        drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                     frameCoordinates.y,
                                     gCurrentMurphyAnimation.width * 8,
                                     gCurrentMurphyAnimation.height,
                                     dstX + offsetX,
                                     dstY + offsetY);

        if (gIsMurphyGoingThroughPortal != 0)
        {
            // This +1 is because the "opposite" portal animation is always the next one
            AnimationFrameCoordinates animationFrameCoordinates = kMurphyAnimationFrameCoordinates[gCurrentMurphyAnimation.animationIndex + 1];
            Point frameCoordinates = animationFrameCoordinates.coordinates[currentFrame];

            int16_t offsetX = (gCurrentMurphyAnimation.animationCoordinatesOffsetIncrement % 122) * 8;
            int16_t offsetY = (gCurrentMurphyAnimation.animationCoordinatesOffsetIncrement / 122);

            drawMovingSpriteFrameInLevel(frameCoordinates.x,
                                         frameCoordinates.y,
                                         gCurrentMurphyAnimation.width * 8,
                                         gCurrentMurphyAnimation.height,
                                         dstX + offsetX,
                                         dstY + offsetY);
        }
        else
        {
            // loc_4EAFA:              ; CODE XREF: update?+C32j
            gCurrentMurphyAnimation.animationCoordinatesOffset += gCurrentMurphyAnimation.animationCoordinatesOffsetIncrement;
        }

        // loc_4EB04:              ; CODE XREF: update?+C68j
        if (gCurrentMurphyAnimation.currentFrame < animationFrameCoordinates.numberOfCoordinates)
        {
            return position;
        }

        // loc_4EB10:              ; CODE XREF: update?+C7Bj
        //  01ED:7EAD
        gMurphyTileX += gCurrentMurphyAnimation.speedX / 2;
        gMurphyTileY += gCurrentMurphyAnimation.speedY / 2;
        uint8_t previousMurphyMovingObject = murphyTile->state;
        murphyTile->state = 0;
        if (previousMurphyMovingObject == 1)
        {
            // loc_4EC93:              ; CODE XREF: update?+CA3j update?+CC3j
            murphyTile->tile = LevelTileTypeMurphy;
            handleMurphyCollisionAfterMovement(position + kLevelWidth);
            return position;
        }
        // loc_4EB36:              ; CODE XREF: update?+CA1j
        else if (previousMurphyMovingObject == 2)
        {
            // loc_4ECB1:              ; CODE XREF: update?+CABj update?+CCBj
            murphyTile->tile = LevelTileTypeMurphy;
            handleMurphyCollisionAfterMovement(position + 1);
            return position;
        }
        // loc_4EB3E:              ; CODE XREF: update?+CA9j
        else if (previousMurphyMovingObject == 3)
        {
            // loc_4ECCF:              ; CODE XREF: update?+CB3j update?+CD3j
            if (aboveTile->tile != LevelTileTypeExplosion)
            {
                aboveTile->state = 0;
                aboveTile->tile = LevelTileTypeSpace;
            }

            // loc_4ECDC:              ; CODE XREF: update?+E44j
            murphyTile->state = 0;
            murphyTile->tile = LevelTileTypeMurphy;
            return position;
        }
        // loc_4EB46:              ; CODE XREF: update?+CB1j
        else if (previousMurphyMovingObject == 4)
        {
            // loc_4EF53:              ; CODE XREF: update?+CBBj update?+CDBj
            handleMurphyCollisionAfterMovement(position - 1);
            murphyTile->state = 0;
            murphyTile->tile = LevelTileTypeMurphy;
            return position;
        }
        // loc_4EB4E:              ; CODE XREF: update?+CB9j
        else if (previousMurphyMovingObject == 5)
        {
            // loc_4EC93:              ; CODE XREF: update?+CA3j update?+CC3j
            murphyTile->tile = LevelTileTypeMurphy;
            handleMurphyCollisionAfterMovement(position + kLevelWidth);
            return position;
        }
        // loc_4EB56:              ; CODE XREF: update?+CC1j
        else if (previousMurphyMovingObject == 6)
        {
            // loc_4ECB1:              ; CODE XREF: update?+CABj update?+CCBj
            murphyTile->tile = LevelTileTypeMurphy;
            handleMurphyCollisionAfterMovement(position + 1);
            return position;
        }
        // loc_4EB5E:              ; CODE XREF: update?+CC9j
        else if (previousMurphyMovingObject == 7)
        {
            // loc_4ECCF:              ; CODE XREF: update?+CB3j update?+CD3j
            if (aboveTile->tile != LevelTileTypeExplosion)
            {
                aboveTile->state = 0;
                aboveTile->tile = LevelTileTypeSpace;
            }

            // loc_4ECDC:              ; CODE XREF: update?+E44j
            murphyTile->state = 0;
            murphyTile->tile = LevelTileTypeMurphy;
            return position;
        }
        // loc_4EB66:              ; CODE XREF: update?+CD1j
        else if (previousMurphyMovingObject == 8)
        {
            // loc_4EF53:              ; CODE XREF: update?+CBBj update?+CDBj
            //  01ED:82F0
            handleMurphyCollisionAfterMovement(position - 1);
            murphyTile->state = 0;
            murphyTile->tile = LevelTileTypeMurphy;
            return position;
        }
        // loc_4EB6E:              ; CODE XREF: update?+CD9j
        else if (previousMurphyMovingObject == 9)
        {
            // loc_4EC85:              ; CODE XREF: update?+CE3j
            if (gNumberOfRemainingInfotrons > 0)
            {
                gNumberOfRemainingInfotrons--;
            }

            // loc_4EC90:              ; CODE XREF: update?+DFAj
            drawNumberOfRemainingInfotrons();

            // loc_4EC93:              ; CODE XREF: update?+CA3j update?+CC3j
            murphyTile->tile = LevelTileTypeMurphy;
            handleMurphyCollisionAfterMovement(position + kLevelWidth);
            return position;
        }
        // loc_4EB76:              ; CODE XREF: update?+CE1j
        else if (previousMurphyMovingObject == 10)
        {
            // loc_4ECA3:              ; CODE XREF: update?+CEBj
            if (gNumberOfRemainingInfotrons > 0)
            {
                gNumberOfRemainingInfotrons--;
            }

            // loc_4ECAE:              ; CODE XREF: update?+E18j
            drawNumberOfRemainingInfotrons();

            // loc_4ECB1:              ; CODE XREF: update?+CABj update?+CCBj
            murphyTile->tile = LevelTileTypeMurphy;
            handleMurphyCollisionAfterMovement(position + 1);
            return position;
        }
        // loc_4EB7E:              ; CODE XREF: update?+CE9j
        else if (previousMurphyMovingObject == 11)
        {
            // loc_4ECC1:              ; CODE XREF: update?+CF3j
            if (gNumberOfRemainingInfotrons > 0)
            {
                gNumberOfRemainingInfotrons--;
            }

            // loc_4ECCC:              ; CODE XREF: update?+E36j
            drawNumberOfRemainingInfotrons();

            // loc_4ECCF:              ; CODE XREF: update?+CB3j update?+CD3j
            if (aboveTile->tile != LevelTileTypeExplosion)
            {
                aboveTile->state = 0;
                aboveTile->tile = LevelTileTypeSpace;
            }

            // loc_4ECDC:              ; CODE XREF: update?+E44j
            murphyTile->state = 0;
            murphyTile->tile = LevelTileTypeMurphy;
            return position;
        }
        // loc_4EB86:              ; CODE XREF: update?+CF1j
        else if (previousMurphyMovingObject == 12)
        {
            // loc_4EF45:              ; CODE XREF: update?+CFBj
            if (gNumberOfRemainingInfotrons > 0)
            {
                gNumberOfRemainingInfotrons--;
            }

            // loc_4EF50:              ; CODE XREF: update?+10BAj
            drawNumberOfRemainingInfotrons();

            // loc_4EF53:              ; CODE XREF: update?+CBBj update?+CDBj
            handleMurphyCollisionAfterMovement(position - 1);
            murphyTile->state = 0;
            murphyTile->tile = LevelTileTypeMurphy;
            return position;
        }
        // loc_4EB8E:              ; CODE XREF: update?+CF9j
        else if (previousMurphyMovingObject == 14)
        {
            // loc_4ECE3:              ; CODE XREF: update?+D03j
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4ECF0:              ; CODE XREF: update?+E58j
            leftTile->state = 0;
            leftTile->tile = LevelTileTypeMurphy;
            leftLeftTile->state = 0;
            leftLeftTile->tile = LevelTileTypeZonk;
            handleZonkPushedByMurphy(position - 2);
            return position - 1;
        }
        // loc_4EB96:              ; CODE XREF: update?+D01j
        else if (previousMurphyMovingObject == 15)
        {
            // loc_4ED06:              ; CODE XREF: update?+D0Bj
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4ED13:              ; CODE XREF: update?+E7Bj
            rightTile->state = 0;
            rightTile->tile = LevelTileTypeMurphy;
            rightRightTile->state = 0;
            rightRightTile->tile = LevelTileTypeZonk;
            handleZonkPushedByMurphy(position + 2);
            return position + 1;
        }
        // loc_4EB9E:              ; CODE XREF: update?+D09j
        else if (previousMurphyMovingObject == 16)
        {
            // loc_4EF71:              ; CODE XREF: update?+D13j
            if (aboveTile->tile != LevelTileTypeExplosion)
            {
                aboveTile->state = 0;
                aboveTile->tile = LevelTileTypeSpace;
            }

            return position;
        }
        // loc_4EBA6:              ; CODE XREF: update?+D11j
        else if (previousMurphyMovingObject == 17)
        {
            // loc_4EF8D:              ; CODE XREF: update?+D1Bj
            if (leftTile->tile != LevelTileTypeExplosion)
            {
                leftTile->state = 0;
                leftTile->tile = LevelTileTypeSpace;
            }

            return position;
        }
        // loc_4EBAE:              ; CODE XREF: update?+D19j
        else if (previousMurphyMovingObject == 19)
        {
            // loc_4EFC5:              ; CODE XREF: update?+D23j
            if (rightTile->tile != LevelTileTypeExplosion)
            {
                rightTile->state = 0;
                rightTile->tile = LevelTileTypeSpace;
            }

            return position;
        }
        // loc_4EBB6:              ; CODE XREF: update?+D21j
        else if (previousMurphyMovingObject == 18)
        {
            // loc_4EFA9:              ; CODE XREF: update?+D2Bj
            if (belowTile->tile != LevelTileTypeExplosion)
            {
                belowTile->state = 0;
                belowTile->tile = LevelTileTypeSpace;
            }

            return position;
        }
        // loc_4EBBE:              ; CODE XREF: update?+D29j
        else if (previousMurphyMovingObject == 20)
        {
            // loc_4EF63:              ; CODE XREF: update?+D33j
            if (gNumberOfRemainingInfotrons > 0)
            {
                gNumberOfRemainingInfotrons--;
            }

            // loc_4EF6E:              ; CODE XREF: update?+10D8j
            drawNumberOfRemainingInfotrons();

            // loc_4EF71:              ; CODE XREF: update?+D13j
            if (aboveTile->tile != LevelTileTypeExplosion)
            {
                aboveTile->state = 0;
                aboveTile->tile = LevelTileTypeSpace;
            }

            return position;
        }
        // loc_4EBC6:              ; CODE XREF: update?+D31j
        else if (previousMurphyMovingObject == 21)
        {
            // loc_4EF7F:              ; CODE XREF: update?+D3Bj
            if (gNumberOfRemainingInfotrons > 0)
            {
                gNumberOfRemainingInfotrons--;
            }

            // loc_4EF8A:              ; CODE XREF: update?+10F4j
            drawNumberOfRemainingInfotrons();

            // loc_4EF8D:              ; CODE XREF: update?+D1Bj
            if (leftTile->tile != LevelTileTypeExplosion)
            {
                leftTile->state = 0;
                leftTile->tile = LevelTileTypeSpace;
            }

            return position;
        }
        // loc_4EBCE:              ; CODE XREF: update?+D39j
        else if (previousMurphyMovingObject == 23)
        {
            // loc_4EFB7:              ; CODE XREF: update?+D43j
            if (gNumberOfRemainingInfotrons > 0)
            {
                gNumberOfRemainingInfotrons--;
            }

            // loc_4EFC2:              ; CODE XREF: update?+112Cj
            drawNumberOfRemainingInfotrons();

            // loc_4EFC5:              ; CODE XREF: update?+D23j
            if (rightTile->tile != LevelTileTypeExplosion)
            {
                rightTile->state = 0;
                rightTile->tile = LevelTileTypeSpace;
            }

            return position;
        }
        // loc_4EBD6:              ; CODE XREF: update?+D41j
        else if (previousMurphyMovingObject == 22)
        {
            // loc_4EF9B:              ; CODE XREF: update?+D4Bj
            if (gNumberOfRemainingInfotrons > 0)
            {
                gNumberOfRemainingInfotrons--;
            }

            // loc_4EFA6:              ; CODE XREF: update?+1110j
            drawNumberOfRemainingInfotrons();

            // loc_4EFA9:              ; CODE XREF: update?+D2Bj
            if (belowTile->tile != LevelTileTypeExplosion)
            {
                belowTile->state = 0;
                belowTile->tile = LevelTileTypeSpace;
            }

            return position;
        }
        // loc_4EBDE:              ; CODE XREF: update?+D49j
        else if (previousMurphyMovingObject == 13)
        {
            // loc_4ED42:              ; CODE XREF: update?+D53j
            gShouldExitLevel = 1;
            return position;
        }
        // loc_4EBE6:              ; CODE XREF: update?+D51j
        else if (previousMurphyMovingObject == 24)
        {
            // loc_4EFD3:              ; CODE XREF: update?+D5Bj
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4EFE0:              ; CODE XREF: update?+1148j
            aboveAboveTile->state = 0;
            aboveAboveTile->tile = LevelTileTypeMurphy;
            gIsMurphyGoingThroughPortal = 0;
            position -= kLevelWidth * 2;
            if (aboveTile->state == 1)
            {
                updateSpecialPort(position + kLevelWidth);
            }

            return position;
        }
        // loc_4EBEE:              ; CODE XREF: update?+D59j
        else if (previousMurphyMovingObject == 25)
        {
            // loc_4F001:              ; CODE XREF: update?+D63j
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F00E:              ; CODE XREF: update?+1176j
            leftLeftTile->state = 0;
            leftLeftTile->tile = LevelTileTypeMurphy;
            gIsMurphyGoingThroughPortal = 0;
            position -= 2;
            if (leftTile->state == 1)
            {
                updateSpecialPort(position + 1);
            }

            return position;
        }
        // loc_4EBF6:              ; CODE XREF: update?+D61j
        else if (previousMurphyMovingObject == 26)
        {
            // loc_4F02E:              ; CODE XREF: update?+D6Bj
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F03B:              ; CODE XREF: update?+11A3j
            belowBelowTile->state = 0;
            belowBelowTile->tile = LevelTileTypeMurphy;
            gIsMurphyGoingThroughPortal = 0;
            position += kLevelWidth * 2;
            if (belowTile->state == 1)
            {
                updateSpecialPort(position - kLevelWidth);
            }

            return position;
        }
        // loc_4EBFE:              ; CODE XREF: update?+D69j
        else if (previousMurphyMovingObject == 27)
        {
            // loc_4F05C:              ; CODE XREF: update?+D73j
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F069:              ; CODE XREF: update?+11D1j
            rightRightTile->state = 0;
            rightRightTile->tile = LevelTileTypeMurphy;
            gIsMurphyGoingThroughPortal = 0;
            position += 2;
            if (rightTile->state == 1)
            {
                updateSpecialPort(position - 1);
            }

            return position;
        }
        // loc_4EC06:              ; CODE XREF: update?+D71j
        else if (previousMurphyMovingObject == 28)
        {
            // loc_4F089:              ; CODE XREF: update?+D7Bj
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F096:              ; CODE XREF: update?+11FEj
            position -= kLevelWidth;

            // loc_4FDAF:              ; CODE XREF: update?+1209j
            //             ; update?:loc_4F0A9j ...
            aboveTile->state = 0;
            aboveTile->tile = LevelTileTypeMurphy;
            decreaseRemainingRedDisksIfNeeded(position);
            return position;
        }
        // loc_4EC0E:              ; CODE XREF: update?+D79j
        else if (previousMurphyMovingObject == 29)
        {
            // loc_4F09C:              ; CODE XREF: update?+D83j
            if (rightTile->tile != LevelTileTypeExplosion)
            {
                rightTile->state = 0;
                rightTile->tile = LevelTileTypeSpace;
            }

            // loc_4FDAF:              ; CODE XREF: update?+1209j
            //             ; update?:loc_4F0A9j ...
            murphyTile->state = 0;
            murphyTile->tile = LevelTileTypeMurphy;
            decreaseRemainingRedDisksIfNeeded(position);
            return position;
        }
        // loc_4EC16:              ; CODE XREF: update?+D81j
        else if (previousMurphyMovingObject == 30)
        {
            // loc_4F0AC:              ; CODE XREF: update?+D8Bj
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F0B9:              ; CODE XREF: update?+1221j
            position += kLevelWidth;

            // loc_4FDAF:              ; CODE XREF: update?+1209j
            //             ; update?:loc_4F0A9j ...
            belowTile->state = 0;
            belowTile->tile = LevelTileTypeMurphy;
            decreaseRemainingRedDisksIfNeeded(position);
            return position;
        }
        // loc_4EC1E:              ; CODE XREF: update?+D89j
        else if (previousMurphyMovingObject == 31)
        {
            // loc_4F0BF:              ; CODE XREF: update?+D93j
            if (leftTile->tile != LevelTileTypeExplosion)
            {
                leftTile->state = 0;
                leftTile->tile = LevelTileTypeSpace;
            }

            // loc_4FDAF:              ; CODE XREF: update?+1209j
            //             ; update?:loc_4F0A9j ...
            murphyTile->state = 0;
            murphyTile->tile = LevelTileTypeMurphy;
            decreaseRemainingRedDisksIfNeeded(position);
            return position;
        }
        // loc_4EC26:              ; CODE XREF: update?+D91j
        else if (previousMurphyMovingObject == 32)
        {
            // loc_4F0CF:              ; CODE XREF: update?+D9Bj
            if (aboveTile->tile != LevelTileTypeExplosion)
            {
                aboveTile->state = 0;
                aboveTile->tile = LevelTileTypeSpace;
            }

            // loc_4F0DC:              ; CODE XREF: update?+1244j
            decreaseRemainingRedDisksIfNeeded(position - kLevelWidth);
            return position;
        }
        // loc_4EC2E:              ; CODE XREF: update?+D99j
        else if (previousMurphyMovingObject == 33)
        {
            // loc_4F0E6:              ; CODE XREF: update?+DA3j
            if (leftTile->tile != LevelTileTypeExplosion)
            {
                leftTile->state = 0;
                leftTile->tile = LevelTileTypeSpace;
            }

            // loc_4F0F3:              ; CODE XREF: update?+125Bj
            decreaseRemainingRedDisksIfNeeded(position - 1);
            return position;
        }
        // loc_4EC36:              ; CODE XREF: update?+DA1j
        else if (previousMurphyMovingObject == 34)
        {
            // loc_4F0FD:              ; CODE XREF: update?+DABj
            if (belowTile->tile != LevelTileTypeExplosion)
            {
                belowTile->state = 0;
                belowTile->tile = LevelTileTypeSpace;
            }

            // loc_4F10A:              ; CODE XREF: update?+1272j
            decreaseRemainingRedDisksIfNeeded(position + kLevelWidth);
            return position;
        }
        // loc_4EC3E:              ; CODE XREF: update?+DA9j
        else if (previousMurphyMovingObject == 35)
        {
            // loc_4F114:              ; CODE XREF: update?+DB3j
            if (rightTile->tile != LevelTileTypeExplosion)
            {
                rightTile->state = 0;
                rightTile->tile = LevelTileTypeSpace;
            }

            // loc_4F121:              ; CODE XREF: update?+1289j
            decreaseRemainingRedDisksIfNeeded(position + 1);
            return position;
        }
        // loc_4EC46:              ; CODE XREF: update?+DB1j
        else if (previousMurphyMovingObject == 36)
        {
            // loc_4F12B:              ; CODE XREF: update?+DBBj
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F138:              ; CODE XREF: update?+12A0j
            aboveTile->state = 0;
            aboveTile->tile = LevelTileTypeMurphy;
            aboveAboveTile->state = 0;
            aboveAboveTile->tile = LevelTileTypeYellowDisk;
            return position - kLevelWidth;
        }
        // loc_4EC4E:              ; CODE XREF: update?+DB9j
        else if (previousMurphyMovingObject == 37)
        {
            // loc_4F148:              ; CODE XREF: update?+DC3j
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F155:              ; CODE XREF: update?+12BDj
            leftTile->state = 0;
            leftTile->tile = LevelTileTypeMurphy;
            leftLeftTile->state = 0;
            leftLeftTile->tile = LevelTileTypeYellowDisk;
            return position - 1;
        }
        // loc_4EC56:              ; CODE XREF: update?+DC1j
        else if (previousMurphyMovingObject == 39)
        {
            // loc_4F165:              ; CODE XREF: update?+DCBj
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F172:              ; CODE XREF: update?+12DAj
            belowTile->state = 0;
            belowTile->tile = LevelTileTypeMurphy;
            belowBelowTile->state = 0;
            belowBelowTile->tile = LevelTileTypeYellowDisk;
            return position + kLevelWidth;
        }
        // loc_4EC5E:              ; CODE XREF: update?+DC9j
        else if (previousMurphyMovingObject == 38)
        {
            // loc_4F182:              ; CODE XREF: update?+DD3j
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F18F:              ; CODE XREF: update?+12F7j
            rightTile->state = 0;
            rightTile->tile = LevelTileTypeMurphy;
            rightRightTile->state = 0;
            rightRightTile->tile = LevelTileTypeYellowDisk;
            return position + 1;
        }
        // loc_4EC66:              ; CODE XREF: update?+DD1j
        else if (previousMurphyMovingObject == 40)
        {
            // loc_4F19F:              ; CODE XREF: update?+DDBj
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F1AC:              ; CODE XREF: update?+1314j
            leftTile->state = 0;
            leftTile->tile = LevelTileTypeMurphy;
            leftLeftTile->state = 0;
            leftLeftTile->tile = LevelTileTypeOrangeDisk;
            return position - 1;
        }
        // loc_4EC6E:              ; CODE XREF: update?+DD9j
        else if (previousMurphyMovingObject == 41)
        {
            // loc_4F1BC:              ; CODE XREF: update?+DE3j
            if (murphyTile->tile != LevelTileTypeExplosion)
            {
                murphyTile->state = 0;
                murphyTile->tile = LevelTileTypeSpace;
            }

            // loc_4F1C9:              ; CODE XREF: update?+1331j
            rightTile->state = 0;
            rightTile->tile = LevelTileTypeMurphy;
            rightRightTile->state = 0;
            rightRightTile->tile = LevelTileTypeOrangeDisk;
            if (belowRightRightTile->state == 0 && belowRightRightTile->tile == LevelTileTypeSpace)
            {
                rightRightTile->state = 0x20;
                belowRightRightTile->state = 8;
            }

            // loc_4F1E6:              ; CODE XREF: update?+134Aj
            return position + 1;
        }
        // loc_4EC76:              ; CODE XREF: update?+DE1j
        else if (previousMurphyMovingObject == 42)
        {
            // loc_4F1EA:              ; CODE XREF: update?+DEBj
            murphyTile->state = 0;
            murphyTile->tile = LevelTileTypeMurphy;
            gPlantedRedDiskCountdown = 2;
            gNumberOfRemainingRedDisks--;
            drawNumberOfRemainingRedDisks();
            playPushSound();
            return position;
        }
        else
        {
            // loc_4EC7E:              ; CODE XREF: update?+DE9j
            gShouldExitLevel = 1;
            return position;
        }
    }

    gMurphyCounterToStartPushAnimation--;
    if (gMurphyCounterToStartPushAnimation == 0)
    {
        playPushSound();
    }

    // loc_4EA1E:              ; CODE XREF: update?+B89j
    if (murphyTile->state == 0xE)
    {
        // loc_4ED49:              ; CODE XREF: update?+B97j
        if (gCurrentUserInput == UserInputLeft && (leftTile->state == 0 && leftTile->tile == LevelTileTypeZonk))
        {
            return position;
        }

        // loc_4ED5A:              ; CODE XREF: update?+EC0j update?+EC7j
        murphyTile->state = 0;
        murphyTile->tile = LevelTileTypeMurphy;
        leftTile->state = 0;
        leftTile->tile = LevelTileTypeZonk;
        if (leftLeftTile->tile != LevelTileTypeExplosion)
        {
            leftLeftTile->state = 0;
            leftLeftTile->tile = LevelTileTypeSpace;
        }

        // loc_4ED73:              ; CODE XREF: update?+EDBj
        //  si = kMurphyStillSpriteCoordinates;
        drawMovingFrame(304, 132, position);

        return position;
    }
    // loc_4EA2A:              ; CODE XREF: update?+B95j
    else if (murphyTile->state == 0xF)
    {
        // loc_4ED81:              ; CODE XREF: update?+B9Fj
        if (gCurrentUserInput == UserInputRight && (rightTile->state == 0 && rightTile->tile == LevelTileTypeZonk))
        {
            return position;
        }

        // loc_4ED92:              ; CODE XREF: update?+EF8j update?+EFFj
        murphyTile->state = 0;
        murphyTile->tile = LevelTileTypeMurphy;
        rightTile->state = 0;
        rightTile->tile = LevelTileTypeZonk;
        if (rightRightTile->tile != LevelTileTypeExplosion)
        {
            rightRightTile->state = 0;
            rightRightTile->tile = LevelTileTypeSpace;
        }

        // loc_4EDAB:              ; CODE XREF: update?+F13j
        //  si = kMurphyStillSpriteCoordinates;
        drawMovingFrame(304, 132, position);

        return position;
    }
    // loc_4EA32:              ; CODE XREF: update?+B9Dj
    else if (murphyTile->state == 0x28)
    {
        // loc_4EDB9:              ; CODE XREF: update?+BA7j
        if (gCurrentUserInput == UserInputLeft && (leftTile->state == 0 && leftTile->tile == LevelTileTypeOrangeDisk))
        {
            return position;
        }

        // loc_4EDCA:              ; CODE XREF: update?+F30j update?+F37j
        murphyTile->state = 0;
        murphyTile->tile = LevelTileTypeMurphy;
        leftTile->state = 0;
        leftTile->tile = LevelTileTypeOrangeDisk;
        if (leftLeftTile->tile != LevelTileTypeExplosion)
        {
            leftLeftTile->state = 0;
            leftLeftTile->tile = LevelTileTypeSpace;
        }

        // loc_4EDE3:              ; CODE XREF: update?+F4Bj
        //  si = kMurphyStillSpriteCoordinates;
        drawMovingFrame(304, 132, position);

        return position;
    }
    // loc_4EA3A:              ; CODE XREF: update?+BA5j
    else if (murphyTile->state == 0x29)
    {
        // loc_4EDF1:              ; CODE XREF: update?+BAFj
        if (gCurrentUserInput == UserInputRight && (rightTile->state == 0 && rightTile->tile == LevelTileTypeOrangeDisk))
        {
            return position;
        }

        // loc_4EE02:              ; CODE XREF: update?+F68j update?+F6Fj
        murphyTile->state = 0;
        murphyTile->tile = LevelTileTypeMurphy;
        rightTile->state = 0;
        rightTile->tile = LevelTileTypeOrangeDisk;
        if (rightRightTile->tile != LevelTileTypeExplosion)
        {
            rightRightTile->state = 0;
            rightRightTile->tile = LevelTileTypeSpace;
        }

        // loc_4EE1B:              ; CODE XREF: update?+F83j
        //  si = kMurphyStillSpriteCoordinates;
        drawMovingFrame(304, 132, position);

        return position;
    }
    // loc_4EA42:              ; CODE XREF: update?+BADj
    else if (murphyTile->state == 0x24)
    {
        // loc_4EE29:              ; CODE XREF: update?+BB7j
        if (gCurrentUserInput == UserInputUp && (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeYellowDisk))
        {
            return position;
        }

        // loc_4EE3A:              ; CODE XREF: update?+FA0j update?+FA7j
        murphyTile->state = 0;
        murphyTile->tile = LevelTileTypeMurphy;
        aboveTile->state = 0;
        aboveTile->tile = LevelTileTypeYellowDisk;
        if (aboveAboveTile->tile != LevelTileTypeExplosion)
        {
            aboveAboveTile->state = 0;
            aboveAboveTile->tile = LevelTileTypeSpace;
        }

        // loc_4EE53:              ; CODE XREF: update?+FBBj
        //  si = kMurphyStillSpriteCoordinates;
        drawMovingFrame(304, 132, position);

        return position;
    }
    // loc_4EA4A:              ; CODE XREF: update?+BB5j
    else if (murphyTile->state == 0x25)
    {
        // loc_4EE61:              ; CODE XREF: update?+BBFj
        if (gCurrentUserInput == UserInputLeft && (leftTile->state == 0 && leftTile->tile == LevelTileTypeYellowDisk))
        {
            return position;
        }

        // loc_4EE72:              ; CODE XREF: update?+FD8j update?+FDFj
        murphyTile->state = 0;
        murphyTile->tile = LevelTileTypeMurphy;
        leftTile->state = 0;
        leftTile->tile = LevelTileTypeYellowDisk;
        if (leftLeftTile->tile != LevelTileTypeExplosion)
        {
            leftLeftTile->state = 0;
            leftLeftTile->tile = LevelTileTypeSpace;
        }

        // loc_4EE8B:              ; CODE XREF: update?+FF3j
        //  si = kMurphyStillSpriteCoordinates;
        drawMovingFrame(304, 132, position);

        return position;
    }
    // loc_4EA52:              ; CODE XREF: update?+BBDj
    else if (murphyTile->state == 0x27)
    {
        // loc_4EE99:              ; CODE XREF: update?+BC7j
        if (gCurrentUserInput == UserInputDown && (belowTile->state == 0 && belowTile->tile == LevelTileTypeYellowDisk))
        {
            return position;
        }

        // loc_4EEAA:              ; CODE XREF: update?+1010j
        //                 ; update?+1017j
        murphyTile->state = 0;
        murphyTile->tile = LevelTileTypeMurphy;
        belowTile->state = 0;
        belowTile->tile = LevelTileTypeYellowDisk;
        if (belowBelowTile->tile != LevelTileTypeExplosion)
        {
            belowBelowTile->state = 0;
            belowBelowTile->tile = LevelTileTypeSpace;
        }

        // loc_4EEC3:              ; CODE XREF: update?+102Bj
        //  si = kMurphyStillSpriteCoordinates;
        drawMovingFrame(304, 132, position);

        return position;
    }
    // loc_4EA5A:              ; CODE XREF: update?+BC5j
    else if (murphyTile->state == 0x26)
    {
        // loc_4EED1:              ; CODE XREF: update?+BCFj
        if (gCurrentUserInput == UserInputRight && (rightTile->state == 0 && rightTile->tile == LevelTileTypeYellowDisk))
        {
            return position;
        }

        // loc_4EEE2:              ; CODE XREF: update?+1048j
        //                 ; update?+104Fj
        murphyTile->state = 0;
        murphyTile->tile = LevelTileTypeMurphy;
        rightTile->state = 0;
        rightTile->tile = LevelTileTypeYellowDisk;
        if (rightRightTile->tile != LevelTileTypeExplosion)
        {
            rightRightTile->state = 0;
            rightRightTile->tile = LevelTileTypeSpace;
        }

        // loc_4EEFB:              ; CODE XREF: update?+1063j
        //  si = kMurphyStillSpriteCoordinates;
        drawMovingFrame(304, 132, position);

        return position;
    }
    // loc_4EA62:              ; CODE XREF: update?+BCDj
    else if (murphyTile->state == 0x2A)
    {
        // loc_4EF09:              ; CODE XREF: update?+BD7j
        if (gCurrentUserInput == UserInputSpaceOnly)
        {
            if (gMurphyCounterToStartPushAnimation > 0x20)
            {
                return position;
            }

            // si = word_51790;
            drawMovingFrame(288, 132, position);
            gPlantedRedDiskCountdown = 1;

            return position;
        }

        // loc_4EF2C:              ; CODE XREF: update?+1080j
        murphyTile->state = 0;
        murphyTile->tile = LevelTileTypeMurphy;

        // si = kMurphyStillSpriteCoordinates;
        drawMovingFrame(304, 132, position);
        gPlantedRedDiskCountdown = 0;

        return position;
    }
    else
    {
        return position;
    }
}

void detonateYellowDisks()
{
    // loc_4E7B8:              ; CODE XREF: update?+8AAj update?+8D4j ...
    gTerminalMaxFramesToNextScroll = 7;
    gAreYellowDisksDetonated = 1;

    for (int i = 0; i < kLevelSize; ++i)
    {
        // loc_4E7C9:              ; CODE XREF: update?+94Aj
        StatefulLevelTile *tile = &gCurrentLevelState[i];
        if (tile->state == 0 && tile->tile == LevelTileTypeYellowDisk)
        {
            detonateBigExplosion(i);
        }
    }
}

void handleZonkPushedByMurphy(int16_t position) // sub_4ED29   proc near       ; CODE XREF: update?+E6Fp update?+E92p
{
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];

    if (belowTile->tile == LevelTileTypeSnikSnak || belowTile->tile == 0xBB)
    {
        // loc_4ED38:              ; CODE XREF: handleZonkPushedByMurphy+5j handleZonkPushedByMurphy+Cj
        detonateBigExplosion(position + kLevelWidth);
    }
}

uint8_t checkMurphyMovementToPosition(int16_t position, UserInput userInput) // sub_4F21F   proc near       ; CODE XREF: update?+273p update?+2EDp ...
{
    // 01ED:85BC
    // Parameters:
    // - si: position
    // - ax: value of that position (state + tile)
    // - bl: user input to process
    StatefulLevelTile *tile = &gCurrentLevelState[position];

    if ((tile->state == 0xFF && tile->tile == 0xFF) || (tile->state == 0xAA && tile->tile == 0xAA) || (tile->state == 0))
    {
        // loc_4F296:              ; CODE XREF: checkMurphyMovementToPosition+3j checkMurphyMovementToPosition+8j ...
        return 1;
    }
    else if (tile->tile == LevelTileTypeZonk)
    {
        // loc_4F24F:              ; CODE XREF: checkMurphyMovementToPosition+11j
        if (userInput == UserInputLeft)
        {
            // loc_4F25E:              ; CODE XREF: checkMurphyMovementToPosition+33j
            uint8_t stateType = (tile->state & 0xF0);
            if (stateType == 0x20 || stateType == 0x40 || stateType == 0x50 || stateType == 0x70)
            {
                // loc_4F278:              ; CODE XREF: checkMurphyMovementToPosition+45j
                //                 ; checkMurphyMovementToPosition+4Aj ...
                return 1;
            }
            detonateBigExplosion(position);

            // loc_4F278:              ; CODE XREF: checkMurphyMovementToPosition+45j
            //                 ; checkMurphyMovementToPosition+4Aj ...
            return 1;
        }
        else if (userInput != UserInputRight)
        {
            detonateBigExplosion(position);
            return 1;
        }

        // loc_4F27A:              ; CODE XREF: checkMurphyMovementToPosition+38j
        uint8_t stateType = (tile->state & 0xF0);
        if (stateType == 0x30 || stateType == 0x40 || stateType == 0x60 || stateType == 0x70)
        {
            // loc_4F294:              ; CODE XREF: checkMurphyMovementToPosition+61j
            //                 ; checkMurphyMovementToPosition+66j ...
            return 1;
        }
        detonateBigExplosion(position);

        // loc_4F294:              ; CODE XREF: checkMurphyMovementToPosition+61j
        //                 ; checkMurphyMovementToPosition+66j ...
        return 1;
    }
    else if (tile->tile == LevelTileTypeExplosion)
    {
        // loc_4F298:              ; CODE XREF: checkMurphyMovementToPosition+15j
        //  01ED:8635
        if ((tile->state & 0x80) != 0 || tile->state < 4)
        {
            // loc_4F2A2:              ; CODE XREF: checkMurphyMovementToPosition+7Cj
            detonateBigExplosion(position);
            return 1;
        }
        else
        {
            // loc_4F2A7:              ; CODE XREF: checkMurphyMovementToPosition+81j
            tile->state = 0;
            tile->tile = LevelTileTypeSpace;
            return 0;
        }
    }
    else if (tile->tile == LevelTileTypeOrangeDisk || tile->tile == LevelTileTypePortRight || tile->tile == LevelTileTypePortDown || tile->tile == LevelTileTypePortLeft || tile->tile == LevelTileTypePortUp)
    {
        // loc_4F296:              ; CODE XREF: checkMurphyMovementToPosition+3j checkMurphyMovementToPosition+8j ...
        return 1;
    }
    else
    {
        detonateBigExplosion(position);
        return 1;
    }
}

void updateSpecialPort(int16_t position) // sub_4F2AF   proc near       ; CODE XREF: update?+116Ap
                                         // ; update?+1197p ...
{
    // 01ED:864C
    if (gNumberOfSpecialPorts == 0)
    {
        return;
    }

    uint8_t isPortInPosition = 0;
    uint8_t portIndex = 0;

    for (uint8_t i = 0; i < gNumberOfSpecialPorts; ++i)
    {
        // loc_4F2BD:              ; CODE XREF: updateSpecialPort+19j
        SpecialPortInfo portInfo = gCurrentLevel.specialPortsInfo[i];
        // For _reasons_ the port position has its bytes inverted (first high, then low), so we must reverse them
        uint16_t portPosition = swap16(portInfo.position);
        portPosition /= 2; // We must divide by 2 because the level format works with words

        if (portPosition == position)
        {
            isPortInPosition = 1;
            portIndex = i;
            break;
        }
    }

    if (isPortInPosition == 0)
    {
        return;
    }

    // loc_4F2CB:              ; CODE XREF: updateSpecialPort+14j
    SpecialPortInfo portInfo = gCurrentLevel.specialPortsInfo[portIndex];
    gIsGravityEnabled = portInfo.gravity;
    gAreZonksFrozen = portInfo.freezeZonks;
    gAreEnemiesFrozen = portInfo.freezeEnemies;
    // I still don't know where word_510AC is read :fearful:
    // I tried with a breakpoint on memory read and it was never accessed :shrug:
    // I can probably forget about it...
    // word_510AC = word_510AC ^ gRandomGeneratorSeed;
}

void updateSnikSnakTiles(int16_t position) // movefun4  proc near       ; DATA XREF: data:162Co
{
    // 01ED:868D
    if (gAreEnemiesFrozen == 1)
    {
        return;
    }

    StatefulLevelTile *currentTile = &gCurrentLevelState[position];

    if (currentTile->tile != LevelTileTypeSnikSnak)
    {
        return;
    }

    uint8_t frame = currentTile->state;

    FrameBasedMovingFunction function = kSnikSnakMovingFunctions[frame];

    if (function != NULL)
    {
        // 01ED:86AC
        function(position, frame);
    }
}

void updateSnikSnakTurnLeft(int16_t position, uint8_t frame) // sub_4F312   proc near       ; DATA XREF: data:movingFunctions3o
{
    // 01ED:86AF
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    uint16_t value = gFrameCounter & 3;

    if (value == 0)
    {
        // loc_4F320:              ; CODE XREF: updateSnikSnakTurnLeft+6j
        Point frameCoordinates = kSnikSnakAnimationFrameCoordinates[frame];
        drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);
        frame++;
        frame &= 7;
        currentTile->state = frame;
        return;
    }
    else if (value != 3)
    {
        return;
    }

    // loc_4F362:              ; CODE XREF: updateSnikSnakTurnLeft+Bj
    uint8_t state = currentTile->state;
    uint8_t nextMovingObject = 0;

    if (state == 0)
    {
        // loc_4F37B:              ; CODE XREF: updateSnikSnakTurnLeft+57j
        if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
        {
            // loc_4F38E:              ; CODE XREF: updateSnikSnakTurnLeft+6Ej
            currentTile->state = 0x1;
            currentTile->tile = 0xBB;
            aboveTile->state = 0x10;
            aboveTile->tile = LevelTileTypeSnikSnak;
            return;
        }
        nextMovingObject = aboveTile->state;
        if (aboveTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 2)
    {
        // loc_4F39E:              ; CODE XREF: updateSnikSnakTurnLeft+5Cj
        if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
        {
            // loc_4F3B1:              ; CODE XREF: updateSnikSnakTurnLeft+91j
            currentTile->state = 0x2;
            currentTile->tile = 0xBB;
            leftTile->state = 0x18;
            leftTile->tile = LevelTileTypeSnikSnak;
            return;
        }
        nextMovingObject = leftTile->state;
        if (leftTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 4)
    {
        // loc_4F3C1:              ; CODE XREF: updateSnikSnakTurnLeft+61j
        if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
        {
            // loc_4F3D7:              ; CODE XREF: updateSnikSnakTurnLeft+B4j
            currentTile->state = 0x3;
            currentTile->tile = 0xBB;
            belowTile->state = 0x20;
            belowTile->tile = LevelTileTypeSnikSnak;
            return;
        }
        nextMovingObject = belowTile->state;
        if (belowTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 6)
    {
        // loc_4F3E7:              ; CODE XREF: updateSnikSnakTurnLeft+66j
        if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
        {
            // loc_4F3FD:              ; CODE XREF: updateSnikSnakTurnLeft+DAj
            currentTile->state = 0x4;
            currentTile->tile = 0xBB;
            rightTile->state = 0x28;
            rightTile->tile = LevelTileTypeSnikSnak;
            return;
        }
        nextMovingObject = rightTile->state;
        if (rightTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else
    {
        return;
    }

    // loc_4F34A:              ; CODE XREF: updateSnikSnakTurnLeft+79j
    //                 ; updateSnikSnakTurnLeft+9Cj ...
    if (nextMovingObject != 0x1B && nextMovingObject != 0x19 && nextMovingObject != 0x18 && nextMovingObject != 0x1A)
    {
        detonateBigExplosion(position);
    }
}

void updateSnikSnakTurnRight(int16_t position, uint8_t frame) // sub_4F40D   proc near       ; DATA XREF: data:155Ao
{
    // 01ED:87AA
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];

    uint16_t value = gFrameCounter & 3;

    if (value == 0)
    {
        // loc_4F41B:              ; CODE XREF: updateSnikSnakTurnRight+6j
        Point frameCoordinates = kSnikSnakAnimationFrameCoordinates[frame];
        drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);
        frame++;
        frame &= 7;
        frame |= 8;
        currentTile->state = frame;
        return;
    }
    else if (value != 3)
    {
        return;
    }

    // loc_4F45F:              ; CODE XREF: updateSnikSnakTurnRight+Bj
    uint8_t state = currentTile->state;
    uint8_t nextMovingObject = 0;
    if (state == 8)
    {
        // loc_4F478:              ; CODE XREF: updateSnikSnakTurnRight+59j
        if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
        {
            // loc_4F48B:              ; CODE XREF: updateSnikSnakTurnRight+70j
            currentTile->state = 0x1;
            currentTile->tile = 0xBB;
            aboveTile->state = 0x10;
            aboveTile->tile = LevelTileTypeSnikSnak;
            return;
        }
        nextMovingObject = aboveTile->state;
        if (aboveTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 0xA)
    {
        // loc_4F4E4:              ; CODE XREF: updateSnikSnakTurnRight+5Ej
        if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
        {
            // loc_4F4FA:              ; CODE XREF: updateSnikSnakTurnLeft+DAj
            currentTile->state = 0x4;
            currentTile->tile = 0xBB;
            rightTile->state = 0x28;
            rightTile->tile = LevelTileTypeSnikSnak;
            return;
        }
        nextMovingObject = rightTile->state;
        if (rightTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 0xC)
    {
        // loc_4F4BE:              ; CODE XREF: updateSnikSnakTurnRight+63j
        if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
        {
            // loc_4F4D4:              ; CODE XREF: updateSnikSnakTurnRight+B6j
            currentTile->state = 0x3;
            currentTile->tile = 0xBB;
            belowTile->state = 0x20;
            belowTile->tile = LevelTileTypeSnikSnak;
            return;
        }
        nextMovingObject = belowTile->state;
        if (belowTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 0xE)
    {
        // loc_4F49B:              ; CODE XREF: updateSnikSnakTurnRight+68j
        if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
        {
            // loc_4F4AE:              ; CODE XREF: updateSnikSnakTurnRight+93j
            currentTile->state = 0x2;
            currentTile->tile = 0xBB;
            leftTile->state = 0x18;
            leftTile->tile = LevelTileTypeSnikSnak;
            return;
        }
        nextMovingObject = leftTile->state;
        if (leftTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else
    {
        return;
    }

    // loc_4F447:              ; CODE XREF: updateSnikSnakTurnRight+7Bj
    //                 ; updateSnikSnakTurnRight+9Ej ...
    if (nextMovingObject != 0x1B && nextMovingObject != 0x19 && nextMovingObject != 0x18 && nextMovingObject != 0x1A)
    {
        detonateBigExplosion(position);
    }
}

void updateSnikSnakMovementUp(int16_t position, uint8_t frame) // sub_4F50A    proc near       ; DATA XREF: data:156Ao
{
    // 01ED:88A7
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    uint16_t finalPosition = position + kLevelWidth;
    Point frameCoordinates = kSnikSnakAnimationFrameCoordinates[frame];
    // sub bx, 1Eh
    frame -= 15; // 0x1E / 2
    uint16_t offset = kFallAnimationGravityOffsets[frame];

    uint8_t tileX = (finalPosition % kLevelWidth);
    uint8_t tileY = (finalPosition / kLevelWidth);

    uint16_t dstX = tileX * kTileSize - (offset % 122) * 2;
    uint16_t dstY = tileY * kTileSize - (offset / 122) * 2;

    drawMovingSpriteFrameInLevel(frameCoordinates.x, frameCoordinates.y,
                                 kTileSize,
                                 kTileSize + 2,
                                 dstX, dstY);
    if (frame == 7)
    {
        if (belowTile->tile != LevelTileTypeExplosion)
        {
            belowTile->state = 0;
            belowTile->tile = LevelTileTypeSpace;
        }
    }
    // loc_4F546:              ; CODE XREF: updateSnikSnakMovementUp+2Dj
    //                 ; updateSnikSnakMovementUp+34j
    if (frame < 8)
    {
        frame += 0x10;
        currentTile->state = frame;
        return;
    }

    // loc_4F553:              ; CODE XREF: updateSnikSnakMovementUp+3Fj
    currentTile->state = 0;
    currentTile->tile = LevelTileTypeSnikSnak;

    if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 1;
        return;
    }

    // loc_4F566:              ; CODE XREF: updateSnikSnakMovementUp+54j
    if (leftTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 1;
        return;
    }

    // loc_4F573:              ; CODE XREF: updateSnikSnakMovementUp+61j
    if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 1;
        currentTile->tile = 0xBB;
        aboveTile->state = 0x10;
        aboveTile->tile = LevelTileTypeSnikSnak;
        return;
    }

    // loc_4F58A:              ; CODE XREF: updateSnikSnakMovementUp+6Ej
    if (aboveTile->tile == LevelTileTypeMurphy)
    {
        detonateBigExplosion(position);
        return;
    }

    // loc_4F595:              ; CODE XREF: updateSnikSnakMovementUp+85j
    if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
    {
        // 01ED:8939
        currentTile->state = 9;
        return;
    }

    // loc_4F5A2:              ; CODE XREF: updateSnikSnakMovementUp+90j
    if (rightTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 9;
        return;
    }

    // loc_4F5AF:              ; CODE XREF: updateSnikSnakMovementUp+9Dj
    currentTile->state = 1;
}

void updateSnikSnakMovementLeft(int16_t position, uint8_t frame) // sub_4F5B5   proc near       ; DATA XREF: data:157Ao
{
    // 01ED:8952
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    Point frameCoordinates = kSnikSnakAnimationFrameCoordinates[frame];

    uint8_t tileX = (position % kLevelWidth);
    uint8_t tileY = (position / kLevelWidth);

    uint16_t dstX = tileX * kTileSize;
    uint16_t dstY = tileY * kTileSize;

    drawMovingSpriteFrameInLevel(frameCoordinates.x, frameCoordinates.y,
                                 kTileSize * 2,
                                 kTileSize,
                                 dstX, dstY);

    frame &= 7;
    frame++;
    if (frame == 7)
    {
        if (rightTile->tile != LevelTileTypeExplosion)
        {
            rightTile->state = 0;
            rightTile->tile = LevelTileTypeSpace;
        }
    }
    // loc_4F5EC:              ; CODE XREF: updateSnikSnakMovementLeft+28j
    //                 ; updateSnikSnakMovementLeft+2Fj
    if (frame < 8)
    {
        frame += 0x18;
        currentTile->state = frame;
        return;
    }

    // loc_4F5F9:              ; CODE XREF: updateSnikSnakMovementLeft+3Aj
    currentTile->state = 0;
    currentTile->tile = LevelTileTypeSnikSnak;

    if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 3;
        return;
    }

    // loc_4F60C:              ; CODE XREF: updateSnikSnakMovementLeft+4Fj
    if (belowTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 3;
        return;
    }

    // loc_4F619:              ; CODE XREF: updateSnikSnakMovementLeft+5Cj
    if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 2;
        currentTile->tile = 0xBB;
        leftTile->state = 0x18;
        leftTile->tile = LevelTileTypeSnikSnak;
        return;
    }

    // loc_4F630:              ; CODE XREF: updateSnikSnakMovementLeft+69j
    if (leftTile->tile == LevelTileTypeMurphy)
    {
        detonateBigExplosion(position);
        return;
    }

    // loc_4F63B:              ; CODE XREF: updateSnikSnakMovementLeft+80j
    if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 0xF;
        return;
    }

    // loc_4F648:              ; CODE XREF: updateSnikSnakMovementLeft+8Bj
    if (aboveTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 0xF;
        return;
    }

    // loc_4F655:              ; CODE XREF: updateSnikSnakMovementLeft+98j
    currentTile->state = 3;
}

void updateSnikSnakMovementDown(int16_t position, uint8_t frame) // sub_4F65B   proc near       ; DATA XREF: data:158Ao
{
    // 01ED:89F8
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    uint16_t finalPosition = position - kLevelWidth;
    Point frameCoordinates = kSnikSnakAnimationFrameCoordinates[frame];
    // sub bx, 40h ; '@'
    frame -= 0x20; // 0x40 / 2

    uint16_t offset = kFallAnimationGravityOffsets[frame];

    uint8_t tileX = (finalPosition % kLevelWidth);
    uint8_t tileY = (finalPosition / kLevelWidth);

    uint16_t dstX = tileX * kTileSize + (offset % 122) * 2;
    uint16_t dstY = tileY * kTileSize + (offset / 122) * 2;

    drawMovingSpriteFrameInLevel(frameCoordinates.x, frameCoordinates.y,
                                 kTileSize,
                                 kTileSize + 2,
                                 dstX, dstY);

    frame++;
    if (frame == 7)
    {
        if (aboveTile->tile != LevelTileTypeExplosion)
        {
            aboveTile->state = 0;
            aboveTile->tile = LevelTileTypeSpace;
        }
    }
    // loc_4F699:              ; CODE XREF: sub_4F66B+1Fj
    //                 ; sub_4F66B+26j
    if (frame < 8)
    {
        frame += 0x20;
        currentTile->state = frame;
        return;
    }

    // loc_4F6A6:              ; CODE XREF: sub_4F66B+31j
    currentTile->state = 0;
    currentTile->tile = LevelTileTypeSnikSnak;

    if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 5;
        return;
    }

    // loc_4F6B9:              ; CODE XREF: sub_4F66B+46j
    if (rightTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 5;
        return;
    }

    // loc_4F6C6:              ; CODE XREF: sub_4F66B+53j
    if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 3;
        currentTile->tile = 0xBB;
        belowTile->state = 0x20;
        belowTile->tile = LevelTileTypeSnikSnak;
        return;
    }

    // loc_4F6DD:              ; CODE XREF: sub_4F66B+60j
    if (belowTile->tile == LevelTileTypeMurphy)
    {
        detonateBigExplosion(position);
        return;
    }

    // loc_4F6E8:              ; CODE XREF: sub_4F66B+77j
    if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
    {
        // 01ED:8A8C
        currentTile->state = 0xD;
        return;
    }

    // loc_4F6F5:              ; CODE XREF: sub_4F66B+82j
    if (leftTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 0xD;
        return;
    }

    // loc_4F702:              ; CODE XREF: sub_4F66B+8Fj
    currentTile->state = 5;
}

void updateSnikSnakMovementRight(int16_t position, uint8_t frame) // sub_4F708   proc near       ; DATA XREF: data:159Ao
{
    // 01ED:8AA5
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    Point frameCoordinates = kSnikSnakAnimationFrameCoordinates[frame];

    uint16_t finalPosition = position - 1;

    uint8_t tileX = (finalPosition % kLevelWidth);
    uint8_t tileY = (finalPosition / kLevelWidth);

    uint16_t dstX = tileX * kTileSize;
    uint16_t dstY = tileY * kTileSize;

    drawMovingSpriteFrameInLevel(frameCoordinates.x, frameCoordinates.y,
                                 kTileSize * 2,
                                 kTileSize,
                                 dstX, dstY);
    frame &= 7;
    frame++;
    if (frame == 7)
    {
        if (leftTile->tile != LevelTileTypeExplosion)
        {
            leftTile->state = 0;
            leftTile->tile = LevelTileTypeSpace;
        }
    }
    // loc_4F740:              ; CODE XREF: updateSnikSnakMovementRight+29j
    //                 ; updateSnikSnakMovementRight+30j
    if (frame < 8)
    {
        frame += 0x28;
        currentTile->state = frame;
        return;
    }

    // loc_4F74D:              ; CODE XREF: updateSnikSnakMovementRight+3Bj
    currentTile->state = 0;
    currentTile->tile = LevelTileTypeSnikSnak;

    if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 7;
        return;
    }

    // loc_4F760:              ; CODE XREF: updateSnikSnakMovementRight+50j
    if (aboveTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 7;
        return;
    }

    // loc_4F76D:              ; CODE XREF: updateSnikSnakMovementRight+5Dj
    if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 4;
        currentTile->tile = 0xBB;
        rightTile->state = 0x28;
        rightTile->tile = LevelTileTypeSnikSnak;
        return;
    }

    // loc_4F784:              ; CODE XREF: updateSnikSnakMovementRight+6Aj
    if (rightTile->tile == LevelTileTypeMurphy)
    {
        detonateBigExplosion(position);
        return;
    }

    // loc_4F78F:              ; CODE XREF: updateSnikSnakMovementRight+81j
    if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 0xB;
        return;
    }

    // loc_4F79C:              ; CODE XREF: updateSnikSnakMovementRight+8Cj
    if (belowTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 0xB;
        return;
    }

    // loc_4F7A9:              ; CODE XREF: updateSnikSnakMovementRight+99j
    currentTile->state = 7;
}

void updateElectronTiles(int16_t position) // movefun6  proc near       ; DATA XREF: data:163Ao
{
    // 01ED:8B4C
    if (gAreEnemiesFrozen == 1)
    {
        return;
    }
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];

    if (currentTile->tile != LevelTileTypeElectron)
    {
        return;
    }

    uint8_t frame = currentTile->state;

    FrameBasedMovingFunction function = kElectronMovingFunctions[frame];

    if (function != NULL)
    {
        // 01ED:8B6B
        function(position, frame);
    }
}

void updateElectronTurnLeft(int16_t position, uint8_t frame) // sub_4F7D1   proc near       ; DATA XREF: data:movingFunctions2o
{
    // 01ED:8B6E
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    uint16_t value = gFrameCounter & 3;

    if (value == 0)
    {
        // loc_4F7DF:              ; CODE XREF: updateElectronTurnLeft+6j
        Point frameCoordinates = kElectronAnimationFrameCoordinates[frame];
        drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);
        frame++;
        frame &= 7;
        currentTile->state = frame;
        return;
    }
    else if (value != 3)
    {
        return;
    }

    // loc_4F80D:              ; CODE XREF: updateElectronTurnLeft+Bj
    uint8_t state = currentTile->state;
    if (state == 0)
    {
        // loc_4F826:              ; CODE XREF: updateElectronTurnLeft+43j
        if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
        {
            // loc_4F835:              ; CODE XREF: updateElectronTurnLeft+5Aj
            currentTile->state = 0x1;
            currentTile->tile = 0xBB;
            aboveTile->state = 0x10;
            aboveTile->tile = LevelTileTypeElectron;
            return;
        }
        else if (aboveTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 2)
    {
        // loc_4F845:              ; CODE XREF: updateElectronTurnLeft+48j
        if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
        {
            // loc_4F854:              ; CODE XREF: updateElectronTurnLeft+79j
            currentTile->state = 0x2;
            currentTile->tile = 0xBB;
            leftTile->state = 0x18;
            leftTile->tile = LevelTileTypeElectron;
            return;
        }
        else if (leftTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 4)
    {
        // loc_4F864:              ; CODE XREF: updateElectronTurnLeft+4Dj
        if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
        {
            // loc_4F873:              ; CODE XREF: updateElectronTurnLeft+98j
            currentTile->state = 0x3;
            currentTile->tile = 0xBB;
            belowTile->state = 0x20;
            belowTile->tile = LevelTileTypeElectron;
            return;
        }
        else if (belowTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 6)
    {
        // loc_4F883:              ; CODE XREF: updateElectronTurnLeft+52j
        if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
        {
            // loc_4F895:              ; CODE XREF: updateElectronTurnLeft+B7j
            currentTile->state = 0x4;
            currentTile->tile = 0xBB;
            rightTile->state = 0x28;
            rightTile->tile = LevelTileTypeElectron;
            return;
        }
        else if (rightTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else
    {
        return;
    }

    // loc_4F809:              ; CODE XREF: updateElectronTurnLeft+61j
    //                 ; updateElectronTurnLeft+80j ...
    detonateBigExplosion(position);
}

void updateElectronTurnRight(int16_t position, uint8_t frame) // sub_4F8A5   proc near       ; DATA XREF: data:15BAo
{
    // 01ED:8C42
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    uint16_t value = gFrameCounter & 3;

    if (value == 0)
    {
        // loc_4F8B3:              ; CODE XREF: updateElectronTurnRight+6j
        Point frameCoordinates = kElectronAnimationFrameCoordinates[frame];
        drawMovingFrame(frameCoordinates.x, frameCoordinates.y, position);
        frame++;
        frame &= 7;
        frame |= 8;
        currentTile->state = frame;
        return;
    }
    else if (value != 3)
    {
        return;
    }

    // loc_4F8E3:              ; CODE XREF: updateElectronTurnRight+Bj
    uint8_t state = currentTile->state;
    if (state == 8)
    {
        // loc_4F8FC:              ; CODE XREF: updateElectronTurnRight+45j
        if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
        {
            // loc_4F90B:              ; CODE XREF: updateElectronTurnRight+5Cj
            currentTile->state = 0x1;
            currentTile->tile = 0xBB;
            aboveTile->state = 0x10;
            aboveTile->tile = LevelTileTypeElectron;
            return;
        }
        else if (aboveTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 0xA)
    {
        // loc_4F959:              ; CODE XREF: updateElectronTurnRight+4Aj
        if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
        {
            // loc_4F96B:              ; CODE XREF: updateElectronTurnRight+B9j
            currentTile->state = 0x4;
            currentTile->tile = 0xBB;
            rightTile->state = 0x28;
            rightTile->tile = LevelTileTypeElectron;
            return;
        }
        else if (rightTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 0xC)
    {
        // loc_4F93A:              ; CODE XREF: updateElectronTurnRight+4Fj
        if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
        {
            // loc_4F949:              ; CODE XREF: updateElectronTurnRight+9Aj
            currentTile->state = 0x3;
            currentTile->tile = 0xBB;
            belowTile->state = 0x20;
            belowTile->tile = LevelTileTypeElectron;
            return;
        }
        else if (belowTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else if (state == 0xE)
    {
        // loc_4F91B:              ; CODE XREF: updateElectronTurnRight+54j
        if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
        {
            // loc_4F92A:              ; CODE XREF: updateElectronTurnRight+7Bj
            currentTile->state = 0x2;
            currentTile->tile = 0xBB;
            leftTile->state = 0x18;
            leftTile->tile = LevelTileTypeElectron;
            return;
        }
        else if (leftTile->tile != LevelTileTypeMurphy)
        {
            return;
        }
    }
    else
    {
        return;
    }

    // loc_4F8DF:              ; CODE XREF: updateElectronTurnRight+63j
    //                 ; updateElectronTurnRight+82j ...
    detonateBigExplosion(position);
}

void updateElectronMovementUp(int16_t position, uint8_t frame) // sub_4F97B   proc near       ; DATA XREF: data:15CAo
{
    // 01ED:8D18
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    uint16_t finalPosition = position + kLevelWidth;
    Point frameCoordinates = kElectronAnimationFrameCoordinates[frame];
    // sub bx, 1Eh
    frame -= 15; // 0x1E / 2
    uint16_t offset = kFallAnimationGravityOffsets[frame];

    uint8_t tileX = (finalPosition % kLevelWidth);
    uint8_t tileY = (finalPosition / kLevelWidth);

    uint16_t dstX = tileX * kTileSize - (offset % 122) * 2;
    uint16_t dstY = tileY * kTileSize - (offset / 122) * 2;

    drawMovingSpriteFrameInLevel(frameCoordinates.x, frameCoordinates.y,
                                 kTileSize,
                                 kTileSize + 2,
                                 dstX, dstY);

    if (frame == 7)
    {
        if (belowTile->tile != LevelTileTypeExplosion)
        {
            belowTile->state = 0;
            belowTile->tile = LevelTileTypeSpace;
        }
    }

    // loc_4F9B7:              ; CODE XREF: updateElectronMovementUp+2Dj
    //                 ; updateElectronMovementUp+34j
    if (frame < 8)
    {
        frame += 0x10;
        currentTile->state = frame;
        return;
    }

    // loc_4F9C4:              ; CODE XREF: updateElectronMovementUp+3Fj
    currentTile->state = 0;
    currentTile->tile = LevelTileTypeElectron;

    if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 1;
        return;
    }

    // loc_4F9D7:              ; CODE XREF: updateElectronMovementUp+54j
    if (leftTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 1;
        return;
    }

    // loc_4F9E4:              ; CODE XREF: updateElectronMovementUp+61j
    if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 1;
        currentTile->tile = 0xBB;
        aboveTile->state = 0x10;
        aboveTile->tile = LevelTileTypeElectron;
        return;
    }

    // loc_4F9FB:              ; CODE XREF: updateElectronMovementUp+6Ej
    if (aboveTile->tile == LevelTileTypeMurphy)
    {
        detonateBigExplosion(position);
        return;
    }

    // loc_4FA06:              ; CODE XREF: updateElectronMovementUp+85j
    if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 9;
        return;
    }

    // loc_4FA13:              ; CODE XREF: updateElectronMovementUp+90j
    if (rightTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 9;
        return;
    }

    // loc_4FA20:              ; CODE XREF: updateElectronMovementUp+9Dj
    currentTile->state = 1;
}

void updateElectronMovementDown(int16_t position, uint8_t frame) // sub_4FA26   proc near       ; DATA XREF: data:15DAo
{
    // 01ED:8DC3
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    Point frameCoordinates = kElectronAnimationFrameCoordinates[frame];

    uint8_t tileX = (position % kLevelWidth);
    uint8_t tileY = (position / kLevelWidth);

    uint16_t dstX = tileX * kTileSize;
    uint16_t dstY = tileY * kTileSize;

    drawMovingSpriteFrameInLevel(frameCoordinates.x, frameCoordinates.y,
                                 kTileSize * 2,
                                 kTileSize,
                                 dstX, dstY);

    frame &= 7;
    frame++;
    if (frame == 7)
    {
        if (rightTile->tile != LevelTileTypeExplosion)
        {
            rightTile->state = 0;
            rightTile->tile = LevelTileTypeSpace;
        }
    }

    // loc_4FA5D:              ; CODE XREF: updateElectronMovementDown+28j
    //                 ; updateElectronMovementDown+2Fj
    if (frame < 8)
    {
        frame += 0x18;
        currentTile->state = frame;
        return;
    }

    // loc_4FA6A:              ; CODE XREF: updateElectronMovementDown+3Aj
    currentTile->state = 0;
    currentTile->tile = LevelTileTypeElectron;

    if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 3;
        return;
    }

    // loc_4FA7D:              ; CODE XREF: updateElectronMovementDown+4Fj
    if (belowTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 3;
        return;
    }

    // loc_4FA8A:              ; CODE XREF: updateElectronMovementDown+5Cj
    if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 2;
        currentTile->tile = 0xBB;
        leftTile->state = 0x18;
        leftTile->tile = LevelTileTypeElectron;
        return;
    }

    // loc_4FAA1:              ; CODE XREF: updateElectronMovementDown+69j
    if (leftTile->tile == LevelTileTypeMurphy)
    {
        detonateBigExplosion(position);
        return;
    }

    // loc_4FAAC:              ; CODE XREF: updateElectronMovementDown+80j
    if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 0xF;
        return;
    }

    // loc_4FAB9:              ; CODE XREF: updateElectronMovementDown+8Bj
    if (aboveTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 0xF;
        return;
    }

    // loc_4FAC6:              ; CODE XREF: updateElectronMovementDown+98j
    currentTile->state = 3;
}

void updateElectronMovementRight(int16_t position, uint8_t frame) // sub_4FACC   proc near       ; DATA XREF: data:15EAo
{
    // 01ED:8E69
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    uint16_t finalPosition = position - kLevelWidth;
    Point frameCoordinates = kElectronAnimationFrameCoordinates[frame];
    // sub bx, 40h ; '@'
    frame -= 0x20; // 0x40 / 2

    uint16_t offset = kFallAnimationGravityOffsets[frame];

    uint8_t tileX = (finalPosition % kLevelWidth);
    uint8_t tileY = (finalPosition / kLevelWidth);

    uint16_t dstX = tileX * kTileSize + (offset % 122) * 2;
    uint16_t dstY = tileY * kTileSize + (offset / 122) * 2;

    drawMovingSpriteFrameInLevel(frameCoordinates.x, frameCoordinates.y,
                                 kTileSize,
                                 kTileSize + 2,
                                 dstX, dstY);

    frame++;
    if (frame == 7)
    {
        if (aboveTile->tile != LevelTileTypeExplosion)
        {
            aboveTile->state = 0;
            aboveTile->tile = LevelTileTypeSpace;
        }
    }

    // loc_4FB0A:              ; CODE XREF: updateElectronMovementRight+2Fj
    //                 ; updateElectronMovementRight+36j
    if (frame < 8)
    {
        frame += 0x20;
        currentTile->state = frame;
        return;
    }

    // loc_4FB17:              ; CODE XREF: updateElectronMovementRight+41j
    currentTile->state = 0;
    currentTile->tile = LevelTileTypeElectron;

    if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 5;
        return;
    }

    // loc_4FB2A:              ; CODE XREF: updateElectronMovementRight+56j
    if (rightTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 5;
        return;
    }

    // loc_4FB37:              ; CODE XREF: updateElectronMovementRight+63j
    if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 3;
        currentTile->tile = 0xBB;
        belowTile->state = 0x20;
        belowTile->tile = LevelTileTypeElectron;
        return;
    }

    // loc_4FB4E:              ; CODE XREF: updateElectronMovementRight+70j
    if (belowTile->tile == LevelTileTypeMurphy)
    {
        detonateBigExplosion(position);
        return;
    }

    // loc_4FB59:              ; CODE XREF: updateElectronMovementRight+87j
    if (leftTile->state == 0 && leftTile->tile == LevelTileTypeSpace)
    {
        // 01ED:8A8C
        currentTile->state = 0xD;
        return;
    }

    // loc_4FB66:              ; CODE XREF: updateElectronMovementRight+92j
    if (leftTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 0xD;
        return;
    }

    // loc_4FB73:              ; CODE XREF: updateElectronMovementRight+9Fj
    currentTile->state = 5;
}

void updateElectronMovementLeft(int16_t position, uint8_t frame) // sub_4FB79   proc near       ; DATA XREF: data:15FAo
{
    // 01ED:8F16
    StatefulLevelTile *currentTile = &gCurrentLevelState[position];
    StatefulLevelTile *belowTile = &gCurrentLevelState[position + kLevelWidth];
    StatefulLevelTile *leftTile = &gCurrentLevelState[position - 1];
    StatefulLevelTile *aboveTile = &gCurrentLevelState[position - kLevelWidth];
    StatefulLevelTile *rightTile = &gCurrentLevelState[position + 1];

    Point frameCoordinates = kElectronAnimationFrameCoordinates[frame];

    uint16_t finalPosition = position - 1;

    uint8_t tileX = (finalPosition % kLevelWidth);
    uint8_t tileY = (finalPosition / kLevelWidth);

    uint16_t dstX = tileX * kTileSize;
    uint16_t dstY = tileY * kTileSize;

    drawMovingSpriteFrameInLevel(frameCoordinates.x, frameCoordinates.y,
                                 kTileSize * 2,
                                 kTileSize,
                                 dstX, dstY);
    frame &= 7;
    frame++;
    if (frame == 7)
    {
        if (leftTile->tile != LevelTileTypeExplosion)
        {
            leftTile->state = 0;
            leftTile->tile = LevelTileTypeSpace;
        }
    }

    // loc_4FBB1:              ; CODE XREF: updateElectronMovementLeft+29j
    //                 ; updateElectronMovementLeft+30j
    if (frame < 8)
    {
        frame += 0x28;
        currentTile->state = frame;
        return;
    }

    // loc_4FBBE:              ; CODE XREF: updateElectronMovementLeft+3Bj
    currentTile->state = 0;
    currentTile->tile = LevelTileTypeElectron;

    if (aboveTile->state == 0 && aboveTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 7;
        return;
    }

    // loc_4FBD1:              ; CODE XREF: updateElectronMovementLeft+50j
    if (aboveTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 7;
        return;
    }

    // loc_4FBDE:              ; CODE XREF: updateElectronMovementLeft+5Dj
    if (rightTile->state == 0 && rightTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 4;
        currentTile->tile = 0xBB;
        rightTile->state = 0x28;
        rightTile->tile = LevelTileTypeElectron;
        return;
    }

    // loc_4FBF5:              ; CODE XREF: updateElectronMovementLeft+6Aj
    if (rightTile->tile == LevelTileTypeMurphy)
    {
        detonateBigExplosion(position);
        return;
    }

    // loc_4FC00:              ; CODE XREF: updateElectronMovementLeft+81j
    if (belowTile->state == 0 && belowTile->tile == LevelTileTypeSpace)
    {
        currentTile->state = 0xB;
        return;
    }

    // loc_4FC0D:              ; CODE XREF: updateElectronMovementLeft+8Cj
    if (belowTile->tile == LevelTileTypeMurphy)
    {
        currentTile->state = 0xB;
        return;
    }

    // loc_4FC1A:              ; CODE XREF: updateElectronMovementLeft+99j
    currentTile->state = 7;
}

void drawGamePanelText() // sub_4FC20  proc near       ; CODE XREF: stopRecordingDemo:loc_4944Fp
                         // ; drawGamePanel+22p ...
{
    if (gFastMode == FastModeTypeUltra)
    {
        return;
    }

    // 01ED:8FBD
    if (gIsRecordingDemo != 0) // Recording demo?
    {
        //    mov si, 87D1h // "  DEMO  "
        drawTextWithChars8FontToGamePanel(72, 3, 8, "  DEMO  ");
        //    mov si, 87DAh // "000" -> this address is the ".SP" text
        drawTextWithChars8FontToGamePanel(16, 14, 8, gCurrentDemoLevelName);
        //        mov si, 87F6h // "--- RECORDING DEMO0 ---"
        drawTextWithChars8FontToGamePanel(64, 14, 8, gRecordingDemoMessage);
    }
    // loc_4FC6F:              ; CODE XREF: drawGamePanelText+5j
    else if (gIsPlayingDemo != 0) // Playing demo?
    {
        drawTextWithChars8FontToGamePanel(72, 3, 8, "  DEMO  ");
        //      mov si, 87DAh // "000" -> this address is the ".SP" text
        drawTextWithChars8FontToGamePanel(16, 14, 8, gCurrentDemoLevelName);
        //      mov si, 87DEh // "----- DEMO LEVEL! -----"
        drawTextWithChars8FontToGamePanel(64, 14, 8, &gCurrentDemoLevelName[4]);
    }
    else
    {
        // loc_4FCD6:              ; CODE XREF: drawGamePanelText+B1j
        drawTextWithChars8FontToGamePanel(72, 3, 6, gPlayerName);
        char levelNumber[4] = "000";
        memcpy(levelNumber, gCurrentLevelName, 3);
        drawTextWithChars8FontToGamePanel(16, 14, 8, levelNumber);
        drawTextWithChars8FontToGamePanel(64, 14, 8, &gCurrentLevelName[4]);
    }

    // loc_4FD1A:              ; CODE XREF: drawGamePanelText+4Cj
    //                 ; drawGamePanelText+A0j ...
    drawNumberOfRemainingInfotrons();
    drawGameTime();
}

void drawNumberOfRemainingInfotrons() // sub_4FD21   proc near       ; CODE XREF: resetNumberOfInfotrons+13p
                                      // ; update?:loc_4EC90p ...
{
    if (gFastMode == FastModeTypeUltra)
    {
        return;
    }

    if (gNumberOfRemainingInfotrons < 1)
    {
        gNumberOfRemainingInfotrons = 0; // WTF? Can this be negative? In theory not...
    }

    // loc_4FD2E:              ; CODE XREF: drawNumberOfRemainingInfotrons+6j
    char number[4] = "000";
    convertNumberTo3DigitStringWithPadding0(gNumberOfRemainingInfotrons, number);

    // loc_4FD46:              ; CODE XREF: drawNumberOfRemainingInfotrons+20j
    uint8_t color = (gNumberOfRemainingInfotrons == 0
                         ? 6
                         : 8);

    // loc_4FD56:              ; CODE XREF: drawNumberOfRemainingInfotrons+31j
    drawTextWithChars8FontToGamePanel(272, 14, color, number);
}

void clearAdditionalInfoInGamePanelIfNeeded() // sub_4FD65   proc near       ; CODE XREF: runLevel+E9p
{
    // loc_4FD6D:              ; CODE XREF: clearAdditionalInfoInGamePanelIfNeeded+5j
    if (gAdditionalInfoInGamePanelFrameCounter == 0)
    {
        return;
    }

    // loc_4FD75:              ; CODE XREF: clearAdditionalInfoInGamePanelIfNeeded+Dj
    gAdditionalInfoInGamePanelFrameCounter--;
    if (gAdditionalInfoInGamePanelFrameCounter != 0)
    {
        return;
    }

    clearAdditionalInfoInGamePanel();
}

void decreaseRemainingRedDisksIfNeeded(int16_t position) // sub_4FDB5   proc near       ; CODE XREF: update?+124Fp
                                                         // ; update?+1266p ...
{
    if (word_59B73 == 0 && gPlantedRedDiskCountdown != 0 && gPlantedRedDiskPosition == position)
    {
        return;
    }

    // loc_4FDCA:              ; CODE XREF: decreaseRemainingRedDisksIfNeeded+5j decreaseRemainingRedDisksIfNeeded+Cj ...
    gNumberOfRemainingRedDisks++;
    drawNumberOfRemainingRedDisks();
}

void drawNumberOfRemainingRedDisks() // sub_4FDCE   proc near       ; CODE XREF: handleGameUserInput+7F4p
                                     // ; update?+1369p
{
    // loc_4FDD6:              ; CODE XREF: drawNumberOfRemainingRedDisks+5j
    char numberString[4] = "000";
    convertNumberTo3DigitStringWithPadding0(gNumberOfRemainingRedDisks, numberString);
    //    mov di, 6D2h
    //    mov si, 87CAh
    uint8_t color = 0;
    if (gNumberOfRemainingRedDisks != 0)
    {
        // loc_4FDF1:              ; CODE XREF: drawNumberOfRemainingRedDisks+1Dj
        color = 6;
    }
    else
    {
        color = 8;
    }

    // loc_4FDF3:              ; CODE XREF: drawNumberOfRemainingRedDisks+21j
    drawTextWithChars8FontToGamePanel(304, 14, color, &numberString[1]);
    gAdditionalInfoInGamePanelFrameCounter = 0x46; // 70
}

void drawGameTime() // sub_4FDFD   proc near       ; CODE XREF: runLevel+29p
                    // ; runLevel:noFlashing2p ...
{
    if (gFastMode == FastModeTypeUltra)
    {
        return;
    }

    // Only the 2 last digits will be printed, hence why it will be used with &number[1] everywhere
    char number[4] = "000";

    if ((gLastDrawnMinutesAndSeconds & 0xFF) != gGameSeconds) // byte
    {
        gLastDrawnMinutesAndSeconds = (gLastDrawnMinutesAndSeconds & 0xFF00) + gGameSeconds; // byte
        convertNumberTo3DigitStringWithPadding0(gGameSeconds, number);
        // loc_4FE2C:              ; CODE XREF: drawGameTime+2Aj
        drawTextWithChars8FontToGamePanel(208, 3, 6, &number[1]); // seconds
    }

    // loc_4FE36:              ; CODE XREF: drawGameTime+12j
    if ((gLastDrawnMinutesAndSeconds >> 8) != gGameMinutes) // byte
    {
        gLastDrawnMinutesAndSeconds = (gGameMinutes << 8) + (gLastDrawnMinutesAndSeconds & 0x00FF); // byte
        convertNumberTo3DigitStringWithPadding0(gGameMinutes, number);
        drawTextWithChars8FontToGamePanel(184, 3, 6, &number[1]); // minutes
    }

    // loc_4FE5F:              ; CODE XREF: drawGameTime+40j
    if (gLastDrawnHours != gGameHours)
    {
        gLastDrawnHours = gGameHours;
        convertNumberTo3DigitStringWithPadding0(gGameHours, number);
        drawTextWithChars8FontToGamePanel(160, 3, 6, &number[1]); // hours
    }
}

void drawGamePanel() // sub_501C0   proc near       ; CODE XREF: start+338p handleGameUserInput+678p ...
{
    clearGamePanel();
    drawGamePanelText();
}

void drawSpeedFixTitleAndVersion() //   proc near       ; CODE XREF: start+2E6p
{
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(102, 11, 1, "SUPAPLEX VERSION " VERSION_STRING);
}

void drawSpeedFixCredits() // showNewCredits  proc near       ; CODE XREF: start+2ECp
{
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(60, 168, 0xE, "VERSIONS 1-4 + 6.X BY HERMAN PERK");
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(60, 176, 0xE, "VERSIONS 5.X BY ELMER PRODUCTIONS");
    drawTextWithChars6FontWithOpaqueBackgroundIfPossible(60, 184, 0xE, "  VERSION 7.X BY SERGIO PADRINO  ");

    videoLoop();

    do
    {
        // loc_502F1:             // ; CODE XREF: drawSpeedFixCredits+28j
        int9handler(1);

        if (gIsScrollLockPressed == 1)
        {
            gIsDebugModeEnabled = 1;
        }
        // loc_50301:             // ; CODE XREF: drawSpeedFixCredits+1Ej
    } while (isAnyKeyPressed() == 0 && isAnyGameControllerButtonPressed() == 0);
}
