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$
PAUSEThat'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
PAUSESaving 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$
PAUSETo 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
PAUSESAVEJSON 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
PAUSESaving 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
PAUSESERIALIZE$ 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
PAUSEWhen 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
PAUSEIf 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
PAUSEDATA 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 #1Using 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
PAUSEMultiple 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
PAUSELoading 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
PAUSELevel 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!"
PAUSEOpening 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, #3DATA 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 SUBThis 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 SUB2. 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", gameProgress3. 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", things4. 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 IF5. 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", progressQuick 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!
Create a settings menu that saves player preferences (sound volume, difficulty, control scheme) and loads them on startup.
Build a high score table that stores the top 10 scores with player names and dates.
Make a simple level-based game that remembers which levels the player has completed.
Create an "achievements" system that tracks and saves player accomplishments.
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!