Chapter 8: Don't Repeat Yourself

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), 15

It 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 SUB

Now 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)

PAUSE

Five 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

PAUSE

Each 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")

PAUSE

The 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

PAUSE

See 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

PAUSE

Functions 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

PAUSE

Notice 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 player
  • PlaySound(soundName$) — Plays a sound
  • MoveEnemy(enemyID) — Updates enemy position

FUNCTION examples:

  • Distance(x1, y1, x2, y2) — Returns the distance between points
  • IsColliding(obj1, obj2) — Returns 1 if objects collide, 0 if not
  • GetScore() — 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

PAUSE

If 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 SUB

This 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

PAUSE

The 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)
PAUSE

This 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 SUB

DECLARE 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 SUB

Now 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

PAUSE

IMPORT 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 FUNCTION

Look how clean that main loop is! Just four calls:

  1. HandleInput() — Check what the player is doing
  2. UpdateGame() — Update all the game logic
  3. DrawGame() — Draw everything
  4. 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:

  1. Don't Repeat Yourself (DRY) — Write code once, use it many times
  2. Organization — Break complex problems into simple pieces
  3. Readability — CALL DrawEnemy(x, y) is clearer than 20 lines of drawing code
  4. Debugging — If enemies look wrong, you know exactly where to look
  5. Teamwork — Different people can work on different SUBs

Try It Yourself!

  1. Create a DrawButton(x, y, text$) SUB that draws a nice button with text
  2. Write a Max(a, b) FUNCTION that returns the larger of two numbers
  3. Make a DrawStarfield() SUB that draws random stars, call it each frame
  4. 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!