Pop quiz! What's wrong with this code?
' Draw enemy 1
LINE (enemy1X, enemy1Y)-(enemy1X + 20, enemy1Y + 20), 4, BF
CIRCLE (enemy1X + 5, enemy1Y + 5), 3, 15
CIRCLE (enemy1X + 15, enemy1Y + 5), 3, 15
LINE (enemy1X + 5, enemy1Y + 15)-(enemy1X + 15, enemy1Y + 15), 15
' Draw enemy 2
LINE (enemy2X, enemy2Y)-(enemy2X + 20, enemy2Y + 20), 4, BF
CIRCLE (enemy2X + 5, enemy2Y + 5), 3, 15
CIRCLE (enemy2X + 15, enemy2Y + 5), 3, 15
LINE (enemy2X + 5, enemy2Y + 15)-(enemy2X + 15, enemy2Y + 15), 15
' Draw enemy 3
LINE (enemy3X, enemy3Y)-(enemy3X + 20, enemy3Y + 20), 4, BF
CIRCLE (enemy3X + 5, enemy3Y + 5), 3, 15
CIRCLE (enemy3X + 15, enemy3Y + 5), 3, 15
LINE (enemy3X + 5, enemy3Y + 15)-(enemy3X + 15, enemy3Y + 15), 15It WORKS, but... ugh. The same four lines copied three times! What if you have 10 enemies? 50? What if you want to change how enemies look? You'd have to change it EVERYWHERE.
This is where subroutines and functions save the day. Write the code once, use it anywhere!
SUB: Do This Thing
A SUB (short for subroutine) is a named block of code that you can run whenever you want by CALLing it.
SUB DrawEnemy(x, y)
LINE (x, y)-(x + 20, y + 20), 4, BF
CIRCLE (x + 5, y + 5), 3, 15
CIRCLE (x + 15, y + 5), 3, 15
LINE (x + 5, y + 15)-(x + 15, y + 15), 15
END SUBNow drawing an enemy is just ONE line:
SCREEN 13
SUB DrawEnemy(x, y)
LINE (x, y)-(x + 20, y + 20), 4, BF
CIRCLE (x + 5, y + 5), 3, 15
CIRCLE (x + 15, y + 5), 3, 15
LINE (x + 5, y + 15)-(x + 15, y + 15), 15
END SUB
CALL DrawEnemy(50, 50)
CALL DrawEnemy(100, 80)
CALL DrawEnemy(150, 50)
CALL DrawEnemy(200, 100)
CALL DrawEnemy(250, 60)
PAUSEFive enemies with five simple CALLs! And if you want to change how enemies look? Change it ONCE in the SUB, and ALL enemies update automatically.
The Anatomy of a SUB
SUB SubroutineName(parameter1, parameter2, ...)
' Your code here
END SUB- SUB — Starts the subroutine definition
- SubroutineName — Whatever you want to call it (descriptive names are best!)
- Parameters — Values you pass in (like x and y coordinates)
- END SUB — Marks the end of the subroutine
To run the subroutine:
CALL SubroutineName(value1, value2, ...)The values you pass in replace the parameters inside the SUB.
Parameters: Passing Information In
Parameters let you customize what the SUB does each time you call it:
SCREEN 13
SUB DrawCircleAt(x, y, radius, color)
CIRCLE (x, y), radius, color
END SUB
CALL DrawCircleAt(50, 100, 10, 14) ' Small yellow
CALL DrawCircleAt(160, 100, 30, 12) ' Medium red
CALL DrawCircleAt(270, 100, 20, 11) ' Medium cyan
PAUSEEach CALL passes different values, so you get different circles—all from the same SUB!
Parameters with Types
You can specify what type of data a parameter should be:
SUB Greet(BYVAL name AS STRING)
PRINT "Hello, "; name; "!"
END SUB
CALL Greet("Crash-tan")
CALL Greet("Player")
PAUSEThe AS STRING tells Crash BASIC this parameter is text. The BYVAL means "pass by value"—we'll explain that soon!
FUNCTION: Do This Thing and Tell Me the Answer
A SUB does something, but what if you need it to give you a result back? That's what FUNCTIONs are for!
FUNCTION Add(a, b)
Add = a + b
END FUNCTION
result = Add(5, 3)
PRINT result ' Prints: 8
PAUSESee the magic? Inside the function, you assign the result to the function's name (Add = a + b). That value comes back to wherever you called the function.
Functions Are Expressions
Because functions return values, you can use them anywhere you'd use a value:
FUNCTION Double(n)
Double = n * 2
END FUNCTION
PRINT Double(5) ' Prints: 10
PRINT Double(Double(3)) ' Prints: 12 (Double(3)=6, Double(6)=12)
x = Double(10) + Double(20) ' x = 20 + 40 = 60
PAUSEFunctions with Return Types
You can specify what type of value a function returns:
FUNCTION FullName$(first AS STRING, last AS STRING) AS STRING
FullName$ = first + " " + last
END FUNCTION
name$ = FullName$("Crash", "Tan")
PRINT name$ ' Prints: Crash Tan
PAUSENotice the $ on the function name—that's how Crash BASIC knows it returns a string!
SUB vs FUNCTION: When to Use Which?
| Use a SUB when... | Use a FUNCTION when... |
|---|---|
| You want to DO something | You want to CALCULATE something |
| Drawing graphics | Math calculations |
| Playing sounds | Getting a value back |
| Updating game state | Checking conditions |
| No return value needed | Return value needed |
SUB examples:
DrawPlayer(x, y)— Draws the playerPlaySound(soundName$)— Plays a soundMoveEnemy(enemyID)— Updates enemy position
FUNCTION examples:
Distance(x1, y1, x2, y2)— Returns the distance between pointsIsColliding(obj1, obj2)— Returns 1 if objects collide, 0 if notGetScore()— Returns the current score
BYVAL: Protecting Your Variables
Here's something tricky. What does this code print?
SUB AddTen(x)
x = x + 10
PRINT "Inside SUB: "; x
END SUB
myNumber = 5
CALL AddTen(myNumber)
PRINT "After CALL: "; myNumber
PAUSEIf you guessed "Inside SUB: 15" and "After CALL: 5", you're right!
By default, parameters in Crash BASIC are passed "by value" (BYVAL). This means the SUB gets a copy of the value. Changing the copy doesn't affect the original.
You can make this explicit:
SUB AddTen(BYVAL x AS INTEGER)
x = x + 10 ' Only changes the local copy
END SUBThis is usually what you want—it prevents SUBs from accidentally messing up your variables!
Global Variables: Sharing Data
Sometimes you WANT a SUB to access and modify variables from your main program. Variables defined outside of any SUB or FUNCTION are global—they can be accessed anywhere:
SCREEN 13
score = 0 ' Global variable
SUB AddPoints(points)
score = score + points ' Accesses the global
PRINT "Score: "; score
END SUB
CALL AddPoints(100)
CALL AddPoints(50)
CALL AddPoints(25)
PRINT "Final score: "; score
PAUSEThe score variable is defined in the main program, but the SUB can read and modify it!
Where Do SUBs and FUNCTIONs Go?
Here's an important rule: you can only call a SUB or FUNCTION after it's been defined. This means without any special tricks, your SUBs and FUNCTIONs have to go at the TOP of your file:
' SUBs must come first...
SUB DrawEnemy(x, y)
CIRCLE (x, y), 10, 4, 4
END SUB
' ...then your main code can use them
SCREEN 13
CALL DrawEnemy(100, 100)
CALL DrawEnemy(200, 100)
PAUSEThis works fine, but it means you have to scroll past all your SUBs to see your main game code. For small programs, no big deal. For bigger programs? Annoying!
DECLARE: Flip the Script
DECLARE lets you put your main code first and your SUBs at the bottom. It tells Crash BASIC "trust me, this SUB exists—I'll define it later."
SCREEN 13
' Declare functions first
DECLARE FUNCTION Distance(x1, y1, x2, y2)
DECLARE SUB DrawPlayer(x, y)
' Main code can now use them!
playerX = 160
playerY = 100
enemyX = 200
enemyY = 120
d = Distance(playerX, playerY, enemyX, enemyY)
PRINT "Distance to enemy: "; d
CALL DrawPlayer(playerX, playerY)
PAUSE
' Actual definitions at the bottom
FUNCTION Distance(x1, y1, x2, y2)
dx = x2 - x1
dy = y2 - y1
Distance = SQR(dx * dx + dy * dy)
END FUNCTION
SUB DrawPlayer(x, y)
CIRCLE (x, y), 10, 14
END SUBDECLARE lets you organize your code however you like!
IMPORT: Code in Separate Files
As your game grows, you'll want to split code into multiple files. That's what IMPORT is for!
Create a file called utils.bas:
' utils.bas - Utility functions
FUNCTION Clamp(value, minVal, maxVal)
IF value < minVal THEN
Clamp = minVal
ELSEIF value > maxVal THEN
Clamp = maxVal
ELSE
Clamp = value
END IF
END FUNCTION
SUB DrawBox(x, y, w, h, color)
LINE (x, y)-(x + w, y + h), color, BF
END SUBNow in your main file:
' main.bas
IMPORT "utils.bas"
SCREEN 13
' Use the imported functions!
x = Clamp(500, 0, 319) ' Returns 319 (clamped to max)
CALL DrawBox(50, 50, 100, 60, 12)
PRINT "Clamped value: "; x
PAUSEIMPORT must be at the TOP of your file, before any other code. The imported file must be in your project's Resources.
This is how you build libraries of reusable code!
Real Example: A Complete Game Loop
Let's put it all together with a proper game structure:
SCREEN 13
' ============================================
' DECLARE all our functions and subs
' ============================================
DECLARE SUB InitGame()
DECLARE SUB HandleInput()
DECLARE SUB UpdateGame()
DECLARE SUB DrawGame()
DECLARE FUNCTION CheckCollision(x1, y1, r1, x2, y2, r2)
' ============================================
' Global game state
' ============================================
playerX = 160
playerY = 180
bulletX = 0
bulletY = 0
bulletActive = 0
enemyX = 160
enemyY = 20
enemyDir = 1
score = 0
gameOver = 0
' ============================================
' Main game loop
' ============================================
CALL InitGame()
DO UNTIL gameOver
CALL HandleInput()
CALL UpdateGame()
CALL DrawGame()
DELAY 16
LOOP
PRINT "GAME OVER! Final Score: "; score
PAUSE
' ============================================
' Subroutine and Function definitions
' ============================================
SUB InitGame()
score = 0
gameOver = 0
playerX = 160
enemyX = 160
enemyY = 20
bulletActive = 0
END SUB
SUB HandleInput()
IF KEYDOWN("LEFT") AND playerX > 10 THEN playerX = playerX - 4
IF KEYDOWN("RIGHT") AND playerX < 310 THEN playerX = playerX + 4
IF KEYDOWN("SPACE") AND bulletActive = 0 THEN
bulletX = playerX
bulletY = playerY - 10
bulletActive = 1
END IF
IF KEYDOWN("ESC") THEN gameOver = 1
END SUB
SUB UpdateGame()
' Move enemy
enemyX = enemyX + enemyDir * 2
IF enemyX > 300 OR enemyX < 20 THEN
enemyDir = -enemyDir
enemyY = enemyY + 10
END IF
' Enemy reached bottom?
IF enemyY > 170 THEN gameOver = 1
' Move bullet
IF bulletActive THEN
bulletY = bulletY - 8
IF bulletY < 0 THEN bulletActive = 0
END IF
' Check bullet-enemy collision
IF bulletActive THEN
IF CheckCollision(bulletX, bulletY, 3, enemyX, enemyY, 10) THEN
score = score + 100
bulletActive = 0
enemyX = INT(RND * 280) + 20
enemyY = 20
END IF
END IF
END SUB
SUB DrawGame()
BEGIN FRAME
CLS
' Draw player
LINE (playerX - 10, playerY)-(playerX + 10, playerY + 10), 14, BF
LINE (playerX - 5, playerY - 5)-(playerX + 5, playerY), 14, BF
' Draw enemy
CIRCLE (enemyX, enemyY), 10, 4, 4
' Draw bullet
IF bulletActive THEN
CIRCLE (bulletX, bulletY), 3, 15, 15
END IF
' Draw score
LOCATE 1, 1
PRINT "Score: "; score
END FRAME
END SUB
FUNCTION CheckCollision(x1, y1, r1, x2, y2, r2)
dx = x2 - x1
dy = y2 - y1
dist = SQR(dx * dx + dy * dy)
IF dist < r1 + r2 THEN
CheckCollision = 1
ELSE
CheckCollision = 0
END IF
END FUNCTIONLook how clean that main loop is! Just four calls:
HandleInput()— Check what the player is doingUpdateGame()— Update all the game logicDrawGame()— Draw everything- Repeat!
Each piece of the game is organized into its own SUB. Easy to read, easy to modify, easy to debug.
Quick Reference
| Syntax | Purpose |
|---|---|
SUB Name(params)...END SUB |
Define a subroutine |
FUNCTION Name(params)...END FUNCTION |
Define a function |
CALL SubName(args) |
Run a subroutine |
result = FuncName(args) |
Run a function, get result |
BYVAL param AS TYPE |
Pass parameter by value (copy) |
FuncName = value |
Return a value from function |
DECLARE SUB/FUNCTION... |
Forward declaration |
IMPORT "file.bas" |
Import code from another file |
Why This Matters
Subroutines and functions are THE key to writing bigger programs:
- Don't Repeat Yourself (DRY) — Write code once, use it many times
- Organization — Break complex problems into simple pieces
- Readability —
CALL DrawEnemy(x, y)is clearer than 20 lines of drawing code - Debugging — If enemies look wrong, you know exactly where to look
- Teamwork — Different people can work on different SUBs
Try It Yourself!
- Create a
DrawButton(x, y, text$)SUB that draws a nice button with text - Write a
Max(a, b)FUNCTION that returns the larger of two numbers - Make a
DrawStarfield()SUB that draws random stars, call it each frame - Refactor the dodge game from Chapter 2 to use SUBs for input, update, and draw
The more you use SUBs and FUNCTIONs, the more natural it becomes. Soon you'll be organizing code like a pro!
See you in Chapter 9!
Next Chapter: We'll explore parallel universes—running multiple things at once with threads!