984 lines
16 KiB
Markdown
984 lines
16 KiB
Markdown
# Programming in c65gm
|
|
|
|
c65gm is a high-level language for 6502 assembly programming that combines modern programming constructs with direct hardware access. Originally designed for Commodore 64 development, it compiles to efficient 6502 assembly code.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Getting Started](#getting-started)
|
|
2. [Variables and Types](#variables-and-types)
|
|
3. [Expressions and Operators](#expressions-and-operators)
|
|
4. [Control Flow](#control-flow)
|
|
5. [Functions](#functions)
|
|
6. [Memory Operations](#memory-operations)
|
|
7. [Code Blocks](#code-blocks)
|
|
8. [Preprocessor](#preprocessor)
|
|
9. [Writing Libraries](#writing-libraries)
|
|
10. [Common Patterns](#common-patterns)
|
|
|
|
---
|
|
|
|
## Getting Started
|
|
|
|
### Your First C64 Program
|
|
|
|
Here's a simple complete program that changes the screen border color:
|
|
|
|
```c65
|
|
#INCLUDE <c64start.c65>
|
|
#INCLUDE <c64defs.c65>
|
|
|
|
GOTO start
|
|
|
|
FUNC main
|
|
BYTE borderColor @ $D020
|
|
borderColor = color_green
|
|
FEND
|
|
|
|
LABEL start
|
|
main()
|
|
```
|
|
|
|
### Creating a C64 Executable
|
|
|
|
To create a runnable C64 program, always start with:
|
|
|
|
```c65
|
|
#INCLUDE <c64start.c65>
|
|
```
|
|
|
|
This creates a valid C64 executable with a BASIC loader (`0 SYS 2064`) and sets up the proper memory layout.
|
|
|
|
### Program Structure
|
|
|
|
A typical c65gm program for C64 follows this pattern:
|
|
|
|
```c65
|
|
#INCLUDE <c64start.c65>
|
|
#INCLUDE <c64defs.c65> // Optional: standard C64 definitions
|
|
|
|
GOTO start // Jump over your function definitions
|
|
|
|
// Variables
|
|
BYTE counter = 0
|
|
WORD screen = $0400
|
|
|
|
// Functions - must be declared before we use them.
|
|
FUNC processData
|
|
// Do something
|
|
FEND
|
|
|
|
FUNC initialize
|
|
counter = 10
|
|
processData()
|
|
FEND
|
|
|
|
// Entry point - execution starts here
|
|
LABEL start
|
|
initialize()
|
|
```
|
|
|
|
**Important:** The `GOTO start` jumps over your function definitions. Without it, the CPU would try to execute directly into your function code, causing a crash. Always put `GOTO start` before your functions, and `LABEL start` before your actual program entry point.
|
|
|
|
---
|
|
|
|
## Variables and Types
|
|
|
|
### BYTE Variables
|
|
|
|
BYTE variables store 8-bit values (0-255).
|
|
|
|
```c65
|
|
BYTE count // Uninitialized
|
|
BYTE speed = 5 // Initialized to 5
|
|
BYTE screen @ $D020 // Memory-mapped to specific address
|
|
BYTE CONST MAX_SPEED = 10 // Constant (recommended over #DEFINE)
|
|
```
|
|
|
|
### WORD Variables
|
|
|
|
WORD variables store 16-bit values (0-65535).
|
|
|
|
```c65
|
|
WORD counter // Uninitialized
|
|
WORD address = $C000 // Initialized to hex value
|
|
WORD message = "Hello" // String literal (pointer to text)
|
|
WORD screenPtr @ $FB // Zero-page pointer
|
|
WORD CONST SCREEN_RAM = $0400 // Constant
|
|
```
|
|
|
|
### Memory-Mapped Variables
|
|
|
|
Variables can be placed at specific addresses using `@`:
|
|
|
|
```c65
|
|
BYTE borderColor @ $D020 // VIC-II border color
|
|
WORD irqVector @ $0314 // IRQ vector
|
|
WORD zpPointer @ $FB // Zero-page variable (fast access)
|
|
```
|
|
|
|
### Constants
|
|
|
|
Use `CONST` keyword for named constants (preferred over preprocessor defines):
|
|
|
|
```c65
|
|
BYTE CONST MAX_ITEMS = 100
|
|
WORD CONST SCREEN_START = $0400
|
|
WORD CONST CHAR_ROM = $D000
|
|
|
|
// Usage
|
|
BYTE itemCount = MAX_ITEMS
|
|
screen = SCREEN_START
|
|
```
|
|
|
|
---
|
|
|
|
## Expressions and Operators
|
|
|
|
### Number Formats
|
|
|
|
```c65
|
|
value = 123 // Decimal
|
|
value = $FF // Hexadecimal ($ prefix)
|
|
value = %11111111 // Binary (% prefix)
|
|
```
|
|
|
|
### Arithmetic Operators
|
|
|
|
```c65
|
|
result = 10 + 5 // Addition
|
|
result = 100 - 5 // Subtraction
|
|
```
|
|
|
|
### Bitwise Operators
|
|
|
|
```c65
|
|
mask = value & $FF // Bitwise AND
|
|
flags = flags | $01 // Bitwise OR
|
|
toggle = value ^ $FF // Bitwise XOR
|
|
```
|
|
|
|
### Increment and Decrement
|
|
|
|
```c65
|
|
counter++ // Increment by 1
|
|
counter-- // Decrement by 1
|
|
index++
|
|
lives--
|
|
```
|
|
|
|
### Critical: No Operator Precedence
|
|
|
|
**Expressions evaluate strictly left to right!** There is no operator precedence.
|
|
|
|
```c65
|
|
result = 2+3*4 // Evaluates as (2+3)*4 = 20, NOT 2+(3*4) = 14
|
|
value = 100-20+5 // Evaluates as (100-20)+5 = 85
|
|
```
|
|
|
|
For complex expressions, use temporary variables:
|
|
|
|
```c65
|
|
// Instead of: result = (b - c) + a
|
|
temp = b - c
|
|
result = a + temp
|
|
```
|
|
|
|
---
|
|
|
|
## Control Flow
|
|
|
|
### IF Statements
|
|
|
|
Basic conditional execution:
|
|
|
|
```c65
|
|
IF count == 10
|
|
result = 1
|
|
ENDIF
|
|
|
|
IF value > threshold
|
|
process()
|
|
ELSE
|
|
skip()
|
|
ENDIF
|
|
```
|
|
|
|
Supported comparison operators: `=` `==` `<>` `!=` `>` `<` `>=` `<=`
|
|
|
|
Single-parameter IF treats 0 as false, non-zero as true:
|
|
|
|
```c65
|
|
IF running
|
|
// Executes if running != 0
|
|
update()
|
|
ENDIF
|
|
```
|
|
|
|
### WHILE Loops
|
|
|
|
Loop while condition is true:
|
|
|
|
```c65
|
|
WHILE counter < 100
|
|
counter++
|
|
process(counter)
|
|
WEND
|
|
|
|
WHILE running
|
|
gameLoop()
|
|
WEND
|
|
|
|
WHILE x != y
|
|
x++
|
|
WEND
|
|
```
|
|
|
|
### FOR Loops
|
|
|
|
Loop with automatic counter:
|
|
|
|
```c65
|
|
FOR i = 0 TO 10
|
|
screen = i
|
|
NEXT
|
|
|
|
FOR x = 0 TO 255 STEP 2
|
|
result = result + x
|
|
NEXT
|
|
|
|
FOR counter = start TO finish
|
|
process(counter)
|
|
NEXT
|
|
```
|
|
|
|
### BREAK
|
|
|
|
Exit a loop early:
|
|
|
|
```c65
|
|
FOR i = 0 TO 100
|
|
IF i == 50
|
|
BREAK
|
|
ENDIF
|
|
process(i)
|
|
NEXT
|
|
|
|
WHILE running
|
|
IF error
|
|
BREAK
|
|
ENDIF
|
|
update()
|
|
WEND
|
|
```
|
|
|
|
---
|
|
|
|
## Functions
|
|
|
|
### Declaring Functions
|
|
|
|
```c65
|
|
FUNC initialize
|
|
BYTE temp = 0
|
|
screen = temp
|
|
FEND
|
|
|
|
FUNC setColor(color, brightness)
|
|
borderColor = color
|
|
backgroundColor = brightness
|
|
FEND
|
|
```
|
|
|
|
### Calling Functions
|
|
|
|
```c65
|
|
initialize()
|
|
setColor(1, 14)
|
|
process(xpos, ypos, spriteData)
|
|
```
|
|
|
|
### Parameter Passing Modes
|
|
|
|
Parameters can have different access modes:
|
|
|
|
- `in:` - Read-only (default if not specified)
|
|
- `out:` - Write-only (for returning values)
|
|
- `io:` - Read-write
|
|
|
|
```c65
|
|
FUNC add(in:a, in:b, out:result)
|
|
result = a + b
|
|
FEND
|
|
|
|
FUNC swap(io:x, io:y)
|
|
BYTE temp = x
|
|
x = y
|
|
y = temp
|
|
FEND
|
|
```
|
|
|
|
### Local Variables
|
|
|
|
Variables declared inside functions are local:
|
|
|
|
```c65
|
|
FUNC calculate(value)
|
|
BYTE local = 10 // Only exists inside this function
|
|
WORD temp // Local temporary
|
|
temp = value + local
|
|
FEND
|
|
```
|
|
|
|
### Curly Brace Syntax
|
|
|
|
Alternative parameter syntax using curly braces:
|
|
|
|
```c65
|
|
FUNC process({BYTE value} out:{BYTE result})
|
|
result = value + 1
|
|
FEND
|
|
```
|
|
|
|
### Returning from Functions
|
|
|
|
```c65
|
|
FUNC checkValue(value)
|
|
IF value == 0
|
|
EXIT // Return early
|
|
ENDIF
|
|
process(value)
|
|
FEND
|
|
```
|
|
|
|
---
|
|
|
|
## Memory Operations
|
|
|
|
### PEEK - Reading Memory
|
|
|
|
Read a byte from memory:
|
|
|
|
```c65
|
|
value = PEEK $D020 // Read from absolute address
|
|
char = PEEK screenPtr[index] // Read with offset
|
|
byte = PEEK pointer // Read from pointer
|
|
```
|
|
|
|
**Important:** For indexed access, the address must be a WORD variable in zero page.
|
|
|
|
```c65
|
|
WORD buffer @ $FB // Zero-page pointer
|
|
value = PEEK buffer[10] // Read buffer+10
|
|
```
|
|
|
|
### POKE - Writing Memory
|
|
|
|
Write a byte to memory:
|
|
|
|
```c65
|
|
POKE $D020 WITH 0 // Write to absolute address
|
|
POKE screenPtr[index] WITH char // Write with offset
|
|
POKE pointer WITH value // Write to pointer
|
|
```
|
|
|
|
### PEEKW - Reading 16-bit Words
|
|
|
|
Read a 16-bit value from memory:
|
|
|
|
```c65
|
|
WORD address
|
|
address = PEEKW $FFFC // Read reset vector
|
|
|
|
WORD buffer @ $FB
|
|
value = PEEKW buffer[10] // Read word at buffer+10
|
|
```
|
|
|
|
### POKEW - Writing 16-bit Words
|
|
|
|
Write a 16-bit value to memory:
|
|
|
|
```c65
|
|
POKEW $0314 WITH irqHandler // Set IRQ vector
|
|
POKEW dataPtr[0] WITH address // Write word with offset
|
|
```
|
|
|
|
### POINTER - Setting Pointers
|
|
|
|
Set a pointer to an address:
|
|
|
|
```c65
|
|
POINTER screenPtr TO $0400
|
|
POINTER bufferPtr TO dataBuffer
|
|
POINTER funcPtr TO myFunction
|
|
```
|
|
|
|
---
|
|
|
|
## Code Blocks
|
|
|
|
### ASM Blocks
|
|
|
|
Inline assembly for direct hardware control:
|
|
|
|
```c65
|
|
ASM
|
|
lda #$00
|
|
sta $d020
|
|
jsr $ffd2
|
|
ENDASM
|
|
```
|
|
|
|
#### Referencing Variables in ASM
|
|
|
|
Global variables can be referenced directly:
|
|
|
|
```c65
|
|
BYTE temp = 5
|
|
|
|
ASM
|
|
lda temp
|
|
clc
|
|
adc #10
|
|
sta temp
|
|
ENDASM
|
|
```
|
|
|
|
Local variables (inside FUNC) need pipe delimiters `|varname|`:
|
|
|
|
```c65
|
|
FUNC calculate(value)
|
|
BYTE local = 10
|
|
ASM
|
|
lda |local| // Reference local variable
|
|
clc
|
|
adc value // Global parameter
|
|
sta |local|
|
|
ENDASM
|
|
FEND
|
|
```
|
|
|
|
### SCRIPT Blocks
|
|
|
|
Generate assembly code at compile time using Starlark (Python-like):
|
|
|
|
```c65
|
|
// Generate a table of squares
|
|
SCRIPT
|
|
print("squares:")
|
|
for i in range(256):
|
|
print(" !8 %d" % (i * i % 256))
|
|
ENDSCRIPT
|
|
```
|
|
|
|
#### Sine Table Example
|
|
|
|
```c65
|
|
SCRIPT
|
|
import math
|
|
print("sintable:")
|
|
for i in range(256):
|
|
angle = (i * 2.0 * math.pi) / 256.0
|
|
sine = math.sin(angle)
|
|
value = int((sine + 1.0) * 127.5)
|
|
print(" !8 %d" % value)
|
|
ENDSCRIPT
|
|
```
|
|
|
|
#### Referencing Variables
|
|
|
|
Use `|varname|` syntax in generated assembly:
|
|
|
|
```c65
|
|
BYTE tableSize = 64
|
|
|
|
SCRIPT
|
|
print("lookup:")
|
|
for i in range(64):
|
|
print(" !8 %d" % (i * 2))
|
|
print(" // Size stored in |tableSize|")
|
|
ENDSCRIPT
|
|
```
|
|
|
|
---
|
|
|
|
## Preprocessor
|
|
|
|
### Comments
|
|
|
|
```c65
|
|
// Single-line comment
|
|
BYTE counter = 0 // End-of-line comment
|
|
```
|
|
|
|
In ASM blocks, use assembly syntax:
|
|
```c65
|
|
ASM
|
|
lda #$00 ; Assembly comment
|
|
ENDASM
|
|
```
|
|
|
|
In SCRIPT blocks, use Python syntax:
|
|
```c65
|
|
SCRIPT
|
|
# Python-style comment
|
|
print("hello")
|
|
ENDSCRIPT
|
|
```
|
|
|
|
### Include Files
|
|
|
|
```c65
|
|
#INCLUDE mylib.c65 // Relative to current file
|
|
#INCLUDE lib/string.c65 // Subdirectory
|
|
#INCLUDE <stdlib.c65> // Search in C65LIBPATH
|
|
```
|
|
|
|
Include guards prevent multiple inclusion:
|
|
|
|
```c65
|
|
#IFNDEF __MY_LIBRARY
|
|
#DEFINE __MY_LIBRARY = 1
|
|
|
|
// Library code here
|
|
|
|
#IFEND
|
|
```
|
|
|
|
### Define Macros
|
|
|
|
Text substitution macros:
|
|
|
|
```c65
|
|
#DEFINE MAX_SPEED = 10
|
|
#DEFINE SCREEN = $$0400 // $$ escapes to literal $
|
|
|
|
speed = MAX_SPEED // Replaced with 10
|
|
```
|
|
|
|
Special characters in defines:
|
|
|
|
```c65
|
|
#DEFINE SPACE = $20 // Space character (hex 20)
|
|
#DEFINE NEWLINE = $0D // Carriage return
|
|
#DEFINE HEXADDR = $$D020 // Literal "$D020" text
|
|
```
|
|
|
|
**Note:** Prefer `BYTE CONST` and `WORD CONST` over `#DEFINE` for constants.
|
|
|
|
### Conditional Compilation
|
|
|
|
```c65
|
|
#IFDEF DEBUG
|
|
BYTE debugFlag = 1
|
|
#IFEND
|
|
|
|
#IFNDEF __LIB_INCLUDED
|
|
#DEFINE __LIB_INCLUDED = 1
|
|
// Include library code
|
|
#IFEND
|
|
```
|
|
|
|
### Pragma Directives
|
|
|
|
Control compiler behavior:
|
|
|
|
```c65
|
|
#PRAGMA _P_USE_LONG_JUMP 1 // Use JMP instead of branches
|
|
#PRAGMA _P_USE_IMMUTABLE_CODE 1 // No self-modifying code (for ROM)
|
|
#PRAGMA _P_USE_CBM_STRINGS 1 // Use PETSCII encoding
|
|
```
|
|
|
|
### Debug Directives
|
|
|
|
```c65
|
|
#PRINT Compiling main module // Print during compilation
|
|
#HALT // Stop compilation
|
|
```
|
|
|
|
---
|
|
|
|
## Writing Libraries
|
|
|
|
When creating a library file to be included by other programs, use this structure:
|
|
|
|
```c65
|
|
#IFNDEF __MY_LIBRARY
|
|
#DEFINE __MY_LIBRARY = 1
|
|
|
|
GOTO lib_mylib_skip // Jump over library code
|
|
|
|
// Library variables
|
|
WORD lib_mylib_buffer
|
|
|
|
// Library functions
|
|
FUNC lib_mylib_initialize
|
|
lib_mylib_buffer = $C000
|
|
FEND
|
|
|
|
FUNC lib_mylib_process(value)
|
|
// Do something
|
|
FEND
|
|
|
|
// Skip label - execution continues here after GOTO
|
|
LABEL lib_mylib_skip
|
|
|
|
#IFEND
|
|
```
|
|
|
|
**Key points for libraries:**
|
|
|
|
1. **Include guard** - Use `#IFNDEF` to prevent multiple inclusion
|
|
2. **GOTO skip** - Jump over all library code immediately
|
|
3. **LABEL skip** - Place at the end so GOTO jumps past everything
|
|
4. **Naming convention** - Prefix all names with `lib_yourlib_` to avoid conflicts
|
|
|
|
This ensures when someone does `#INCLUDE <mylib.c65>`, the library functions are defined but not executed.
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Complete Working Example
|
|
|
|
Here's a complete C64 program showing all the pieces together:
|
|
|
|
```c65
|
|
#INCLUDE <c64start.c65>
|
|
#INCLUDE <c64defs.c65>
|
|
|
|
GOTO start
|
|
|
|
// Variables
|
|
BYTE frameCount = 0
|
|
WORD screenPtr @ $FB
|
|
|
|
// Functions
|
|
FUNC initialize
|
|
BYTE borderColor @ $D020
|
|
borderColor = color_black
|
|
POINTER screenPtr TO $0400
|
|
FEND
|
|
|
|
FUNC updateScreen
|
|
BYTE color
|
|
color = frameCount & $0F
|
|
POKE screenPtr[0] WITH color
|
|
frameCount++
|
|
FEND
|
|
|
|
// Entry point
|
|
LABEL start
|
|
initialize()
|
|
|
|
WHILE 1
|
|
updateScreen()
|
|
WEND
|
|
```
|
|
|
|
### Screen Manipulation
|
|
|
|
```c65
|
|
WORD CONST SCREEN = $0400
|
|
WORD CONST COLOR_RAM = $D800
|
|
BYTE CONST SCREEN_WIDTH = 40
|
|
BYTE CONST SCREEN_HEIGHT = 25
|
|
|
|
FUNC clearScreen
|
|
WORD screenPtr @ $FB
|
|
POINTER screenPtr TO SCREEN
|
|
|
|
WORD remaining = 1000
|
|
WHILE remaining > 0
|
|
POKE screenPtr[0] WITH 32 // Space character
|
|
screenPtr++
|
|
remaining--
|
|
WEND
|
|
FEND
|
|
```
|
|
|
|
### String Handling
|
|
|
|
```c65
|
|
// Print null-terminated string
|
|
FUNC printString(textPtr)
|
|
BYTE char
|
|
char = PEEK textPtr[0]
|
|
|
|
WHILE char != 0
|
|
ASM
|
|
lda |char|
|
|
jsr $FFD2 // CHROUT
|
|
ENDASM
|
|
textPtr++
|
|
char = PEEK textPtr[0]
|
|
WEND
|
|
FEND
|
|
```
|
|
|
|
### Sprite Manipulation
|
|
|
|
```c65
|
|
WORD CONST VIC2 = $D000
|
|
BYTE spriteX @ VIC2+0
|
|
BYTE spriteY @ VIC2+1
|
|
BYTE spriteEnable @ VIC2+21
|
|
|
|
FUNC enableSprite(spriteNum)
|
|
BYTE mask
|
|
mask = 1
|
|
|
|
FOR i = 0 TO spriteNum
|
|
mask = mask * 2
|
|
NEXT
|
|
|
|
spriteEnable = spriteEnable | mask
|
|
FEND
|
|
```
|
|
|
|
### Zero Page Optimization
|
|
|
|
For frequently accessed pointers, use zero page:
|
|
|
|
```c65
|
|
WORD fastPtr @ $FB // Zero page = fast indexed access
|
|
|
|
FUNC processBuffer(buffer, size)
|
|
POINTER fastPtr TO buffer
|
|
|
|
WHILE size > 0
|
|
BYTE value
|
|
value = PEEK fastPtr[0]
|
|
// Process value
|
|
fastPtr++
|
|
size--
|
|
WEND
|
|
FEND
|
|
```
|
|
|
|
### Interrupt Handlers
|
|
|
|
```c65
|
|
WORD CONST IRQ_VECTOR = $0314
|
|
WORD oldIRQ
|
|
|
|
FUNC installIRQ
|
|
ASM
|
|
sei // Disable interrupts
|
|
ENDASM
|
|
|
|
oldIRQ = PEEKW IRQ_VECTOR
|
|
POKEW IRQ_VECTOR WITH myIRQ
|
|
|
|
ASM
|
|
cli // Enable interrupts
|
|
ENDASM
|
|
FEND
|
|
|
|
LABEL myIRQ
|
|
// IRQ handler code
|
|
ASM
|
|
// Save registers
|
|
pha
|
|
txa
|
|
pha
|
|
tya
|
|
pha
|
|
|
|
// Do IRQ work
|
|
inc $d020
|
|
|
|
// Restore and return
|
|
pla
|
|
tay
|
|
pla
|
|
tax
|
|
pla
|
|
rti
|
|
ENDASM
|
|
```
|
|
|
|
### Lookup Tables
|
|
|
|
Generate tables at compile time:
|
|
|
|
```c65
|
|
ASM
|
|
colorTable:
|
|
ENDASM
|
|
|
|
SCRIPT
|
|
colors = [0, 1, 15, 12, 11, 9, 2, 8]
|
|
for c in colors:
|
|
print(" !8 %d" % c)
|
|
ENDSCRIPT
|
|
```
|
|
|
|
### Delay Loops
|
|
|
|
```c65
|
|
FUNC delay(frames)
|
|
BYTE raster @ $D012
|
|
BYTE oldRaster
|
|
|
|
WHILE frames > 0
|
|
oldRaster = raster
|
|
WHILE raster == oldRaster
|
|
// Wait for raster to change
|
|
WEND
|
|
frames--
|
|
WEND
|
|
FEND
|
|
```
|
|
|
|
### Bit Manipulation
|
|
|
|
```c65
|
|
// Set bit
|
|
flags = flags | %00000001 // Set bit 0
|
|
|
|
// Clear bit
|
|
flags = flags & %11111110 // Clear bit 0
|
|
|
|
// Toggle bit
|
|
flags = flags ^ %00000001 // Toggle bit 0
|
|
|
|
// Test bit
|
|
IF flags & %00000001
|
|
// Bit 0 is set
|
|
ENDIF
|
|
```
|
|
|
|
---
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Constants for Magic Numbers
|
|
|
|
```c65
|
|
// Bad
|
|
POKE $D020 WITH 5
|
|
|
|
// Good
|
|
BYTE CONST COLOR_GREEN = 5
|
|
BYTE borderColor @ $D020
|
|
borderColor = COLOR_GREEN
|
|
```
|
|
|
|
### 2. Zero Page for Performance
|
|
|
|
Place frequently accessed pointers in zero page ($00-$FF):
|
|
|
|
```c65
|
|
WORD screenPtr @ $FB // Fast indexed access
|
|
WORD tempPtr @ $FD
|
|
```
|
|
|
|
### 3. Watch Expression Evaluation Order
|
|
|
|
Remember: left-to-right evaluation, no precedence!
|
|
|
|
```c65
|
|
// Be careful with expressions
|
|
result = 2 + 3 * 4 // = 20, not 14
|
|
|
|
// Use temps for clarity
|
|
temp = 3 * 4
|
|
result = 2 + temp // Now = 14
|
|
```
|
|
|
|
### 4. Include Guards
|
|
|
|
Always use include guards in library files:
|
|
|
|
```c65
|
|
#IFNDEF __MY_LIB
|
|
#DEFINE __MY_LIB = 1
|
|
|
|
// Library code
|
|
|
|
#IFEND
|
|
```
|
|
|
|
### 5. Comment Your Code
|
|
|
|
```c65
|
|
// Explain what and why, not how
|
|
FUNC calculateTrajectory(velocity, angle)
|
|
// Use fixed-point math (8.8 format)
|
|
// because we don't have floating point
|
|
WORD xVel
|
|
WORD yVel
|
|
// ...
|
|
FEND
|
|
```
|
|
|
|
### 6. Use Meaningful Names
|
|
|
|
```c65
|
|
// Bad
|
|
BYTE x
|
|
BYTE y
|
|
|
|
// Good
|
|
BYTE playerXPos
|
|
BYTE playerYPos
|
|
```
|
|
|
|
---
|
|
|
|
## Further Resources
|
|
|
|
- See `commands.md` for complete command reference
|
|
- See `syntax.md` for detailed syntax rules
|
|
- Check the `lib/` directory for example library code
|
|
- Set `C65LIBPATH` environment variable to specify library search paths
|
|
|
|
---
|
|
|
|
## Quick Reference Card
|
|
|
|
```c65
|
|
// Variables
|
|
BYTE varName = 10
|
|
WORD address = $C000
|
|
BYTE CONST MAX = 100
|
|
|
|
// Control Flow
|
|
IF x == 10
|
|
// code
|
|
ENDIF
|
|
|
|
WHILE x < 100
|
|
x++
|
|
WEND
|
|
|
|
FOR i = 0 TO 10
|
|
// code
|
|
NEXT
|
|
|
|
// Functions
|
|
FUNC myFunc(in:param1, out:result)
|
|
result = param1 + 1
|
|
FEND
|
|
|
|
// Memory
|
|
value = PEEK $D020
|
|
POKE $D020 WITH 5
|
|
address = PEEKW $FFFC
|
|
POKEW $0314 WITH handler
|
|
|
|
// Operators
|
|
+ - * / // Arithmetic
|
|
& | ^ // Bitwise
|
|
++ -- // Increment/Decrement
|
|
== != < > <= >= // Comparison
|
|
|
|
// Blocks
|
|
ASM
|
|
// assembly code
|
|
ENDASM
|
|
|
|
SCRIPT
|
|
# Python code
|
|
ENDSCRIPT
|
|
```
|