Chapter 14: Keeping Records

Pop quiz: What's the most frustrating thing in gaming?

If you said "losing all your progress when you close the game," congratulations—you understand why saving and loading matters!

Nobody wants to beat level 50, close the browser, come back tomorrow, and start at level 1 again. Your players will REVOLT.

Good news: Crash BASIC makes it easy to save data that persists between play sessions. High scores? Saved. Player progress? Saved. Settings and preferences? You guessed it—SAVED.

Let's learn how to keep records!


The Simplest Save: SAVETEXT and LOADTEXT

The most basic way to save data is as text. SAVETEXT writes a string to storage, and LOADTEXT reads it back:

SCREEN 13

' Save some text
playerName$ = "Crash-tan"
SAVETEXT "player_name.txt", playerName$

PRINT "Name saved!"
PRINT "Press any key to load it back..."
PAUSE

' Load it back
loadedName$ = LOADTEXT("player_name.txt")
PRINT "Loaded name: "; loadedName$

PAUSE

That's it! The data is stored in your browser's local storage and will be there even after you close and reopen the game.

Saving Numbers as Text

SAVETEXT only saves strings, so you need to convert numbers:

SCREEN 13

highScore = 9999

' Convert number to string with STR$
SAVETEXT "highscore.txt", STR$(highScore)

PRINT "High score saved: "; highScore

' Load it back and convert to number with VAL
loaded$ = LOADTEXT("highscore.txt")
loadedScore = VAL(loaded$)

PRINT "Loaded score: "; loadedScore

PAUSE

Saving Multiple Values

What if you need to save multiple things? You can combine them into one string:

SCREEN 13

' Game state
playerName$ = "Hero"
level = 5
score = 12500
lives = 3

' Combine into one string with a separator
saveData$ = playerName$ + "," + STR$(level) + "," + STR$(score) + "," + STR$(lives)
SAVETEXT "savegame.txt", saveData$

PRINT "Game saved!"
PRINT "Save data: "; saveData$

PAUSE

To load it back, you'd split the string apart. But there's a MUCH easier way...


SAVEJSON and LOADJSON: The Easy Way

JSON (JavaScript Object Notation) is a standard format for storing structured data. Crash BASIC can save and load JSON automatically!

SCREEN 13

' Create a player object
DIM player AS OBJECT
player.name = "Crash-tan"
player.level = 5
player.score = 12500
player.lives = 3

' Save the whole object as JSON - so easy!
SAVEJSON "player.json", player

PRINT "Player saved!"
PRINT "Name: "; player.name
PRINT "Level: "; player.level

PAUSE

' Load it back
DIM loadedPlayer AS OBJECT
loadedPlayer = LOADJSON("player.json")

PRINT ""
PRINT "Player loaded!"
PRINT "Name: "; loadedPlayer.name
PRINT "Level: "; loadedPlayer.level
PRINT "Score: "; loadedPlayer.score

PAUSE

SAVEJSON automatically converts your objects, arrays, and custom types into JSON format. LOADJSON reads it back and reconstructs everything. No manual string manipulation needed!

Saving Arrays

JSON handles arrays too:

SCREEN 13

' High scores array (5 scores, indices 0-4)
DIM highScores(5) AS INTEGER
highScores(0) = 10000
highScores(1) = 8500
highScores(2) = 7200
highScores(3) = 5000
highScores(4) = 3500

SAVEJSON "highscores.json", highScores

PRINT "High scores saved!"

' Load them back
DIM loaded(5) AS INTEGER
loaded = LOADJSON("highscores.json")

PRINT "Top 3 scores:"
FOR i = 0 TO 2
    PRINT i + 1; ". "; loaded(i)
NEXT i

PAUSE

Saving Custom Types

Remember our custom types from Chapter 11? They work with JSON too:

SCREEN 13

TYPE PlayerData
    name AS STRING
    x AS INTEGER
    y AS INTEGER
    health AS INTEGER
    level AS INTEGER
END TYPE

' Create and populate player
DIM player AS PlayerData
player.name = "Hero"
player.x = 160
player.y = 100
player.health = 85
player.level = 7

' Save the custom type
SAVEJSON "player_data.json", player

PRINT "Player data saved!"

' Load it back
DIM loaded AS PlayerData
loaded = LOADJSON("player_data.json")

PRINT "Loaded: "; loaded.name
PRINT "Position: "; loaded.x; ", "; loaded.y
PRINT "Health: "; loaded.health
PRINT "Level: "; loaded.level

PAUSE

SERIALIZE$ and DESERIALIZE$: For Complex Data

Sometimes you need more control over how data is converted. SERIALIZE$ converts ANY data to a string, and DESERIALIZE$ converts it back:

SCREEN 13

TYPE GameState
    level AS INTEGER
    score AS INTEGER
    lives AS INTEGER
END TYPE

DIM state AS GameState
state.level = 10
state.score = 50000
state.lives = 2

' Convert to string
saveString$ = SERIALIZE$(state)
PRINT "Serialized: "; saveString$

' Save the string
SAVETEXT "gamestate.dat", saveString$

' Later, load and deserialize
loaded$ = LOADTEXT("gamestate.dat")
DIM loadedState AS GameState
loadedState = DESERIALIZE$(loaded$)

PRINT ""
PRINT "Loaded level: "; loadedState.level
PRINT "Loaded score: "; loadedState.score

PAUSE

When to use SERIALIZE$/DESERIALIZE$ vs SAVEJSON/LOADJSON:

  • SAVEJSON/LOADJSON — Quick and easy for most cases
  • SERIALIZE$/DESERIALIZE$ — When you need the string for other purposes (like sending over a network)

Checking If Save Data Exists

What if the player is starting fresh and there's no save file yet?

SCREEN 13

' Try to load saved data
savedData$ = LOADTEXT("mysave.txt")

IF LEN(savedData$) = 0 THEN
    PRINT "No save data found - starting new game!"
    score = 0
    level = 1
ELSE
    PRINT "Save data found - loading..."
    ' Parse the saved data
    ' (In a real game, you'd use LOADJSON here)
END IF

PAUSE

If a file doesn't exist or is empty, LOADTEXT returns an empty string. Check the length to see if there's actually data!


DATA Blocks: Pre-Loaded Game Data

Sometimes you want to include data IN your program—like level layouts, enemy stats, or dialog text. That's what DATA blocks are for:

SCREEN 13

' Define enemy data right in your code
DATA "enemies"
    "Slime", 10, 1
    "Goblin", 25, 2
    "Orc", 50, 5
    "Dragon", 200, 20
END DATA

' Open the DATA block like a file
OPEN DATA "enemies" AS #1

' Read until we hit the end
DIM name$ AS STRING
DIM health AS INTEGER
DIM damage AS INTEGER

PRINT "ENEMY BESTIARY"
PRINT "=============="

WHILE NOT EOF(1)
    READ #1, name$, health, damage
    PRINT name$; " - HP: "; health; " DMG: "; damage
WEND

CLOSE #1

PAUSE

DATA blocks are great for:

  • Enemy statistics
  • Level layouts
  • Item definitions
  • Dialog trees
  • Any game data that doesn't change

Opening DATA Blocks Like Files

DATA blocks work just like files! You open them, read from them, check for the end, and close them. This file-like approach gives you powerful flexibility.

The Basic Pattern

' 1. Define the DATA block (can be anywhere in your program)
DATA "blockname"
    value1, value2, value3
    value4, value5, value6
END DATA

' 2. Open it with a file number
OPEN DATA "blockname" AS #1

' 3. Read values
READ #1, var1, var2, var3

' 4. Close when done
CLOSE #1

Using EOF() to Read All Data

EOF stands for "End Of File." Use it to loop through ALL the data without knowing how many rows there are:

SCREEN 13

DATA "scores"
    "Alice", 9500
    "Bob", 8200
    "Charlie", 7800
    "Diana", 6500
    "Eve", 5000
END DATA

OPEN DATA "scores" AS #1

DIM playerName$ AS STRING
DIM playerScore AS INTEGER
DIM rank AS INTEGER

rank = 0

PRINT "HIGH SCORES"
PRINT "==========="

WHILE NOT EOF(1)
    READ #1, playerName$, playerScore
    rank = rank + 1
    PRINT rank; ". "; playerName$; " - "; playerScore
WEND

CLOSE #1

PAUSE

Multiple DATA Blocks

You can have as many DATA blocks as you need, each with its own name:

SCREEN 13

' Enemy data
DATA "enemies"
    "Slime", 10, 1, 2
    "Goblin", 25, 3, 4
    "Dragon", 100, 10, 8
END DATA

' Item data
DATA "items"
    "Potion", 50, "heal"
    "Bomb", 0, "damage"
    "Key", 0, "unlock"
END DATA

' Weapon data
DATA "weapons"
    "Sword", 10, 100
    "Axe", 15, 80
    "Staff", 8, 50
END DATA

' Read enemies
OPEN DATA "enemies" AS #1
PRINT "ENEMIES:"
WHILE NOT EOF(1)
    DIM eName$ AS STRING, eHP AS INTEGER
    READ #1, eName$, eHP
    PRINT "  "; eName$; " (HP: "; eHP; ")"
WEND
CLOSE #1

PRINT ""

' Read items
OPEN DATA "items" AS #2
PRINT "ITEMS:"
WHILE NOT EOF(2)
    DIM iName$ AS STRING, iValue AS INTEGER, iType$ AS STRING
    READ #2, iName$, iValue, iType$
    PRINT "  "; iName$; " - "; iType$
WEND
CLOSE #2

PAUSE

Loading Into Arrays

DATA blocks are perfect for initializing arrays:

SCREEN 13

TYPE Enemy
    name AS STRING
    health AS INTEGER
    damage AS INTEGER
    speed AS INTEGER
END TYPE

DATA "enemy_stats"
    "Slime", 10, 2, 1
    "Bat", 8, 3, 5
    "Skeleton", 20, 5, 2
    "Ghost", 15, 4, 3
    "Ogre", 50, 10, 1
END DATA

' Count enemies first (optional - or just use a big enough array)
DIM enemies(10) AS Enemy
DIM enemyCount AS INTEGER

OPEN DATA "enemy_stats" AS #1

enemyCount = 0
WHILE NOT EOF(1)
    enemyCount = enemyCount + 1
    READ #1, enemies(enemyCount).name, enemies(enemyCount).health, enemies(enemyCount).damage, enemies(enemyCount).speed
WEND

CLOSE #1

' Now use the array!
PRINT "Loaded "; enemyCount; " enemies:"
PRINT ""

FOR i = 1 TO enemyCount
    PRINT enemies(i).name
    PRINT "  HP: "; enemies(i).health; "  DMG: "; enemies(i).damage; "  SPD: "; enemies(i).speed
    PRINT ""
NEXT i

PAUSE

Level Data Example

DATA blocks are perfect for simple level layouts:

SCREEN 13

' A simple 10x5 level map
' 0 = empty, 1 = wall, 2 = coin, 3 = enemy
DATA "level1"
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1
    1, 0, 0, 2, 0, 0, 0, 3, 0, 1
    1, 0, 1, 1, 1, 0, 1, 1, 0, 1
    1, 0, 0, 0, 2, 0, 0, 0, 0, 1
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1
END DATA

DIM levelMap(10, 5) AS INTEGER

OPEN DATA "level1" AS #1

FOR y = 1 TO 5
    FOR x = 1 TO 10
        READ #1, levelMap(x, y)
    NEXT x
NEXT y

CLOSE #1

' Draw the level
FOR y = 1 TO 5
    FOR x = 1 TO 10
        px = (x - 1) * 20 + 60
        py = (y - 1) * 20 + 50

        SELECT CASE levelMap(x, y)
            CASE 0    ' Empty
                ' Draw nothing
            CASE 1    ' Wall
                LINE (px, py)-(px + 18, py + 18), 8, BF
            CASE 2    ' Coin
                CIRCLE (px + 9, py + 9), 6, 14, 14
            CASE 3    ' Enemy
                LINE (px + 2, py + 2)-(px + 16, py + 16), 4, BF
        END SELECT
    NEXT x
NEXT y

LOCATE 1, 1
PRINT "Level 1 loaded from DATA block!"

PAUSE

Opening Multiple DATA Blocks Simultaneously

You can have multiple DATA blocks open at once using different file numbers:

OPEN DATA "enemies" AS #1
OPEN DATA "items" AS #2
OPEN DATA "levels" AS #3

' Read from any of them in any order
READ #1, enemyName$
READ #2, itemName$
READ #3, levelTile

' Close them all when done
CLOSE #1, #2, #3

DATA Blocks vs JSON Files

DATA Blocks JSON Files
Built into your program Separate files
Always available Must be loaded
Read-only (can't save to them) Can save AND load
Fast access Slightly slower
Good for static game data Good for player data

Use DATA blocks for things that never change (enemy stats, level layouts, dialog). Use JSON for things that DO change (save games, high scores, settings)


Real Example: A Complete Save System

Let's build a real save/load system for a game:

SCREEN 13

' ================================================
' TYPE DEFINITIONS
' ================================================

TYPE SaveData
    playerName AS STRING
    level AS INTEGER
    score AS INTEGER
    highScore AS INTEGER
    posX AS INTEGER
    posY AS INTEGER
    health AS INTEGER
    playTime AS INTEGER
END TYPE

' ================================================
' DECLARATIONS
' ================================================

DECLARE SUB SaveGame()
DECLARE SUB LoadGame()
DECLARE SUB NewGame()

' ================================================
' GLOBAL GAME STATE
' ================================================

DIM game AS SaveData
DIM gameLoaded AS INTEGER

' Try to load existing save
CALL LoadGame()

IF NOT gameLoaded THEN
    CALL NewGame()
END IF

' ================================================
' MAIN GAME LOOP
' ================================================

startTime = TIMER

DO
    ' Simple controls
    IF KEYDOWN("LEFT") AND game.posX > 10 THEN game.posX = game.posX - 3
    IF KEYDOWN("RIGHT") AND game.posX < 310 THEN game.posX = game.posX + 3
    IF KEYDOWN("UP") AND game.posY > 10 THEN game.posY = game.posY - 3
    IF KEYDOWN("DOWN") AND game.posY < 190 THEN game.posY = game.posY + 3

    ' Collect points (simplified)
    game.score = game.score + 1
    IF game.score > game.highScore THEN
        game.highScore = game.score
    END IF

    ' Update play time
    game.playTime = game.playTime + 1

    ' Press S to save
    IF KEYDOWN("S") THEN
        CALL SaveGame()
    END IF

    ' Press N for new game
    IF KEYDOWN("N") THEN
        CALL NewGame()
    END IF

    ' Render
    BEGIN FRAME
    CLS

    ' Draw player
    CIRCLE (game.posX, game.posY), 10, 14, 14

    ' Draw UI
    LOCATE 1, 1
    PRINT "Player: "; game.playerName
    LOCATE 2, 1
    PRINT "Level: "; game.level; "  Score: "; game.score
    LOCATE 3, 1
    PRINT "High Score: "; game.highScore
    LOCATE 4, 1
    PRINT "Health: "; game.health

    LOCATE 23, 1
    PRINT "[S] Save  [N] New Game  [ESC] Quit"

    END FRAME
    DELAY 16

LOOP UNTIL KEYDOWN("ESCAPE")

' Auto-save on exit!
CALL SaveGame()
PRINT "Game saved automatically!"
DELAY 1000

END

' ================================================
' SUBROUTINES
' ================================================

SUB SaveGame()
    SAVEJSON "crashgame_save.json", game
    LOCATE 12, 10
    PRINT "*** GAME SAVED! ***"
END SUB

SUB LoadGame()
    ' Check if save exists
    testLoad$ = LOADTEXT("crashgame_save.json")

    IF LEN(testLoad$) > 0 THEN
        game = LOADJSON("crashgame_save.json")
        gameLoaded = TRUE
        PRINT "Save data loaded!"
        DELAY 500
    ELSE
        gameLoaded = FALSE
    END IF
END SUB

SUB NewGame()
    game.playerName = "Hero"
    game.level = 1
    game.score = 0
    ' Keep the high score!
    game.posX = 160
    game.posY = 100
    game.health = 100
    game.playTime = 0

    PRINT "Starting new game!"
    DELAY 500
END SUB

This save system:

  • Loads automatically when the game starts
  • Saves on demand when the player presses S
  • Auto-saves on exit so progress is never lost
  • Preserves high score even when starting a new game
  • Uses a custom type to keep all save data organized

Save System Best Practices

1. Auto-Save Regularly

Don't rely on players remembering to save:

' Save every 30 seconds using a timer
DECLARE SUB AutoSave()
CALL TIMER AutoSave() EVERY 30000 MILLISECONDS AS "autosave"

SUB AutoSave()
    SAVEJSON "autosave.json", gameState
END SUB

2. Keep High Scores Separate

High scores should survive even if the player starts a new game:

' Save high scores separately
SAVEJSON "highscores.json", highScoreList

' Save regular progress separately
SAVEJSON "progress.json", gameProgress

3. Use Meaningful Filenames

' GOOD: Clear, descriptive names
SAVEJSON "player_progress.json", progress
SAVEJSON "game_settings.json", settings
SAVEJSON "high_scores.json", scores

' BAD: Cryptic names
SAVEJSON "data1.json", stuff
SAVEJSON "x.json", things

4. Validate Loaded Data

' Check that loaded data makes sense
loaded = LOADJSON("save.json")

IF loaded.health < 0 OR loaded.health > 100 THEN
    PRINT "Save data corrupted! Starting fresh."
    CALL NewGame()
END IF

5. Save Settings Separately from Progress

' Settings (volume, controls, etc.)
DIM settings AS OBJECT
settings.musicVolume = 80
settings.sfxVolume = 100
settings.difficulty = "normal"
SAVEJSON "settings.json", settings

' Game progress (separate file)
DIM progress AS OBJECT
progress.level = 5
progress.score = 12000
SAVEJSON "progress.json", progress

Quick Reference

Command What It Does
SAVETEXT "file", text$ Save a string to storage
text$ = LOADTEXT("file") Load a string from storage
SAVEJSON "file", data Save any data as JSON
data = LOADJSON("file") Load JSON data
str$ = SERIALIZE$(data) Convert data to string
data = DESERIALIZE$(str$) Convert string back to data
DATA "name" ... END DATA Define a data block
OPEN DATA "name" AS #n Open a data block for reading
READ #n, var1, var2 Read values from data block
EOF(n) Check if at end of data block
CLOSE #n Close a data block

Try It Yourself!

  1. Create a settings menu that saves player preferences (sound volume, difficulty, control scheme) and loads them on startup.

  2. Build a high score table that stores the top 10 scores with player names and dates.

  3. Make a simple level-based game that remembers which levels the player has completed.

  4. Create an "achievements" system that tracks and saves player accomplishments.

  5. Build a character customization screen that saves and loads the player's choices.

Your games now have MEMORY! Players can come back tomorrow and pick up right where they left off.

See you in Chapter 15!


Next Chapter: We'll put your game on autopilot—behaviors, patterns, collisions, and more!