Daniel Covington преди 1 седмица
родител
ревизия
0c23ee2b15
променени са 12 файла, в които са добавени 786 реда и са изтрити 157 реда
  1. +21
    -0
      .claude/settings.json
  2. +5
    -1
      .claude/settings.local.json
  3. +34
    -0
      AGENTS.md
  4. +1
    -155
      CLAUDE.md
  5. +21
    -0
      examples/linked-list-test-build.bat
  6. +199
    -0
      examples/linked-list-test.c
  7. Двоични данни
      examples/linked-list-test.prg
  8. +21
    -0
      examples/moonlander-build.bat
  9. +465
    -0
      examples/moonlander.c
  10. Двоични данни
      examples/moonlander.prg
  11. +18
    -0
      examples/run.bat
  12. +1
    -1
      examples/sprite-music-demo.bas

+ 21
- 0
.claude/settings.json Целия файл

@@ -0,0 +1,21 @@
{
"permissions": {
"allow": [
"Bash(where cl65 *)",
"Bash(where x64sc *)",
"Bash(cl65 --version)",
"Bash(command -v cl65)",
"Bash(command -v x64sc)",
"Bash(command -v x64)",
"Bash(ls \"/c/Program Files/\" 2>/dev/null | grep -i vice; ls \"/c/Program Files \\(x86\\)/\" 2>/dev/null | grep -i vice; find /d/Development -iname \"x64sc*\" 2>/dev/null | head -20)",
"Read(//c/Program Files \\\\\\(x86\\\\\\)//**)",
"Bash(cl65 -t c64 -O -o linked-list-test.prg linked-list-test.c)",
"Bash(\"/c/Program Files/GTK3VICE-3.10-win64/bin/x64sc.exe\" -autostart linked-list-test.prg -autostart-warp -warp -remotemonitor -remotemonitoraddress ip4://127.0.0.1:6510)",
"Bash(echo \"launched pid $!\")",
"Bash(ps -W)",
"Bash(powershell -NoProfile -Command \"Get-Process x64sc -ErrorAction SilentlyContinue | Select-Object Id,ProcessName\")",
"Bash(powershell -NoProfile -Command \"Get-NetTCPConnection -LocalPort 6510 -ErrorAction SilentlyContinue\")",
"Bash(tasklist //FI \"IMAGENAME eq x64sc.exe\")"
]
}
}

+ 5
- 1
.claude/settings.local.json Целия файл

@@ -2,7 +2,11 @@
"permissions": {
"allow": [
"Bash(build.bat)",
"PowerShell(Copy-Item \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mousedemo.c\" \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mouse_driver_demo.c\" -Force)"
"PowerShell(Copy-Item \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mousedemo.c\" \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\samples\\\\cc65-mouse-driver-demo\\\\mouse_driver_demo.c\" -Force)",
"Bash(mkdir -p \"C:\\\\Users\\\\danielc.NTP\\\\.claude\\\\projects\\\\d--Development-Commodore-C64-dev\\\\memory\")"
],
"additionalDirectories": [
"C:\\Users\\danielc.NTP\\.claude\\projects\\d--Development-Commodore-C64-dev"
]
}
}

+ 34
- 0
AGENTS.md Целия файл

@@ -211,6 +211,32 @@ When an AI agent receives a C64 development question:

---

## Development Conventions

- Write machine addresses in hexadecimal with a `$` prefix (for example, `$D000` and `$FFD2`). Include decimal equivalents when an address is used with BASIC `PEEK` or `POKE` (for example, `$D000` = `53248`).
- Check memory banking before accessing `$A000–$FFFF`; the CPU may see ROM, RAM, I/O, or character ROM depending on the 6510 port at `$0001`.
- Prefer Kernal routines for standard I/O and use zero page for variables that benefit from faster access. Keep BASIC's memory ceiling below machine code that BASIC might otherwise overwrite.
- State whether timing-sensitive code assumes PAL or NTSC, especially for raster timing and sound frequencies.

### Hardware Limits to Check

| Resource | Limit or behavior |
|----------|-------------------|
| Default BASIC program area | `$0801–$9FFF` (38,911 addressable bytes; usable free space is lower) |
| 6510 hardware stack | `$0100–$01FF` (256 bytes) |
| Default screen matrix | `$0400–$07E7` (1,000 visible character cells) |
| Color RAM | `$D800–$DBE7` (1,000 visible 4-bit color cells when I/O is mapped in) |
| VIC-II address space | One 16KB bank at a time, selected by CIA #2 at `$DD00`, bits 0–1 |
| Sprite data | 64 bytes per block; at most 256 block positions in a 16KB VIC bank |
| SID reads | `$D400–$D418` are write-only; `$D419–$D41C` provide paddle, oscillator 3, and envelope 3 readings |
| Character ROM | CPU-readable at `$D000–$DFFF` only under the appropriate `$0001` banking configuration |

### Development Tools

VICE is the emulator used by the examples in this repository. Common modern assemblers include KickAssembler, ACME, ca65, and 64tass; CBM .prg Studio is a Windows IDE, C64 debugger provides visual debugging with VICE integration, and Exomizer and Pucrunch are executable crunchers. Period tools include SUPERMON, Mikro Assembler, PAL, and Simon's BASIC.

---

## VICE Debug Workflow

When debugging a C64 program in VICE, agents should prefer this flow:
@@ -260,3 +286,11 @@ LDA #>my_irq ; high byte
STA $0315
CLI ; enable interrupts
```

### Assembly program skeleton
```asm
*=$C000 ; assemble at $C000 (49152)
START JSR $FF81 ; CINT: initialize the screen editor
; program code goes here
RTS ; return to BASIC when entered with SYS 49152
```

+ 1
- 155
CLAUDE.md Целия файл

@@ -1,155 +1 @@
# CLAUDE.md — Commodore 64 Development Agent Configuration

> This file configures AI agents (Claude and others) to assist with Commodore 64 application development.
> **Start here.** Then follow pointers to AGENTS.md and the skill files.

---

## Purpose

This repository is a structured knowledge base for developing software on the **Commodore 64**, compiled from 11 canonical C64 reference books by a collective of 150 expert C64 developers. It also includes supplemental VICE 3.10 emulator workflow guidance for modern testing and debugging.

## Primary Reference

→ **Read [`AGENTS.md`](./AGENTS.md) first.** It contains:
- Complete system overview and memory map
- Skill directory index
- Kernal jump table quick reference
- Zero-page location table
- Common development patterns
- Source book citations

## Repository Structure

```
c64-dev-knowledge/
├── CLAUDE.md ← You are here
├── AGENTS.md ← Primary AI reference (read this first)
├── skills/
│ ├── 6510-assembly/SKILL.md ← 6510 CPU, opcodes, addressing
│ ├── memory-map/SKILL.md ← Memory layout, banking, zero page
│ ├── graphics-vic2/SKILL.md ← VIC-II chip, sprites, modes
│ ├── sound-sid/SKILL.md ← SID 6581 sound chip
│ ├── disk-io-1541/SKILL.md ← 1541 disk drive programming
│ ├── kernal-routines/SKILL.md ← OS routines and vectors
│ ├── basic-programming/SKILL.md ← BASIC 2.0 and extensions
│ ├── interrupts/SKILL.md ← IRQ, NMI, CIA timers
│ ├── compiler-design/SKILL.md ← Assemblers, compilers for C64
│ └── vice-emulator/SKILL.md ← VICE launch, autostart, monitor, automation
├── reference/
│ ├── memory-map-complete.md ← Full address space reference
│ ├── 6510-opcodes.md ← Complete opcode table
│ ├── kernal-jumplist.md ← Kernal routine details
│ ├── vic-registers.md ← VIC-II register map
│ ├── sid-registers.md ← SID register map
│ ├── cia-registers.md ← CIA 6526 register map
│ ├── disk-dos-commands.md ← 1541 DOS commands
│ └── vice-usage.md ← VICE 3.10 quick reference
└── examples/
├── hello-world.asm ← Assembly hello world
├── sprite-demo.bas ← Sprite programming in BASIC
├── sound-demo.bas ← SID sound in BASIC
├── disk-access.bas ← File I/O examples
├── irq-handler.asm ← Custom IRQ handler
├── kernal-io.asm ← Kernal I/O routines
├── vice-autostart-c64os.bat ← Launch c64os.prg in x64sc
├── vice-debug-c64os.bat ← Launch c64os.prg with native monitor
├── vice-monitor-c64os.txt ← VICE monitor playback commands
└── vice-monitor-c64os-debug.txt ← Repeatable VICE debug session
```

## Agent Instructions

### When a user asks a C64 development question:

1. **Consult `AGENTS.md`** for the system overview and to identify which skill applies
2. **Load the relevant `SKILL.md`** from `skills/<topic>/SKILL.md`
3. **Reference specific values** from files in `reference/`
4. **Use examples** from `examples/` as starting points for code
5. **If the task involves emulator execution or debugging**, load `skills/vice-emulator/SKILL.md`

When the task is specifically about debugging a program in VICE, prefer:

- `x64sc` over the older `x64`
- `-nativemonitor -keepmonopen -refreshonbreak`
- `sidefx off` before inspecting hardware-mapped locations
- watchpoints on `$0001`, `$0314/$0315`, and other critical machine state
- repeatable playback files from `examples/vice-monitor-c64os-debug.txt`

### Topic Routing

| User asks about... | → Load skill |
|--------------------|-------------|
| 6502/6510 assembly, opcodes, registers, addressing | `skills/6510-assembly/SKILL.md` |
| Memory layout, PEEK/POKE locations, banking | `skills/memory-map/SKILL.md` |
| Sprites, text modes, bitmap, colors, VIC chip | `skills/graphics-vic2/SKILL.md` |
| Music, sound effects, SID chip | `skills/sound-sid/SKILL.md` |
| Disk operations, loading, saving, file types | `skills/disk-io-1541/SKILL.md` |
| OS calls, LOAD/SAVE from ML, I/O from assembly | `skills/kernal-routines/SKILL.md` |
| BASIC programming, PEEK/POKE tricks, adding commands | `skills/basic-programming/SKILL.md` |
| IRQ handlers, NMI, CIA timers, raster bars | `skills/interrupts/SKILL.md` |
| Writing assemblers, compilers, parsers | `skills/compiler-design/SKILL.md` |
| Running, testing, or automating code in VICE | `skills/vice-emulator/SKILL.md` |

### Expert Conventions (apply to all answers)

- **Always use hex addresses** with `$` prefix (e.g., `$D000`, `$FFD2`)
- **Always note decimal equivalents** for BASIC PEEK/POKE (e.g., `53248`)
- **Always check memory banking** before giving addresses in the $A000–$FFFF range
- **Prefer Kernal routines** over custom code for standard I/O operations
- **Use zero page** for frequently-accessed variables (faster indexed addressing)
- **Protect ML code** from BASIC with `POKE 56,<high-byte> : CLR`
- **Note NTSC/PAL differences** for timing-sensitive code (raster, sound frequencies)

## Hardware Constraints

Always keep these limits in mind when generating code or advice:

- **RAM available to BASIC**: ~38KB ($0801–$9FFF = 38911 bytes)
- **Free RAM under I/O**: $C000–$CFFF (4KB, always RAM)
- **6510 stack**: $0100–$01FF (256 bytes, hardware fixed)
- **Screen RAM**: $0400–$07E7 (default, 1000 bytes)
- **Color RAM**: $D800–$DBE7 (always at this address, 1000 nybbles)
- **VIC-II sees**: One 16KB bank at a time (bank selected via CIA #2 $DD00 bits 0-1)
- **SID registers**: Write-only except $D419–$D41C (paddles/misc)
- **Character ROM**: Mapped at $D000 when I/O is banked out (not normally accessible via CPU)
- **Max sprite data blocks**: 256 blocks × 64 bytes = 16KB within VIC bank

## Quick-Start Code Templates

### BASIC Program with ML Routine
```basic
1 REM C64 ML TEMPLATE
10 ML=49152 : REM $C000 - safe ML area
20 POKE 56,192 : CLR : REM PROTECT ML AREA
30 FOR I=ML TO ML+N-1 : READ D : POKE I,D : NEXT
40 SYS ML : REM RUN THE ROUTINE
50 REM ... your BASIC code ...
60000 DATA 96 : REM RTS (minimal routine placeholder)
```

### Assembly Program Skeleton
```asm
; C64 Assembly Program Template
; Assemble to $C000 (49152)
*=$C000
START JSR $FF81 ; CINT - init screen
; ... your code ...
RTS ; return to BASIC (if SYS'd)
; or JMP $FCE2 to warm-start BASIC
```

## Emulator/Development Tool Notes

Modern C64 development typically uses:
- **VICE** (emulator) — accurate cycle-level emulation
- **KickAssembler**, **ACME**, **CA65**, **64tass** — modern assemblers
- **CBM .prg Studio** — Windows IDE
- **C64 debugger** — visual debugger with VICE integration
- **Exomizer**, **Pucrunch** — executable crunchers

For period-accurate development (on hardware or accurate emulation):
- **SUPERMON** — machine language monitor (from ROM books)
- **Mikro Assembler**, **PAL** — period assemblers
- **Simon's BASIC** — extended BASIC with ML-callable routines
@AGENTS.md

+ 21
- 0
examples/linked-list-test-build.bat Целия файл

@@ -0,0 +1,21 @@
@echo off
REM Build linked-list-test.c with cc65 and optionally launch in VICE x64sc
REM Requirements: cc65 on PATH, VICE x64sc on PATH or in GTK3VICE install dir

set SRC=linked-list-test.c
set PRG=linked-list-test.prg
set VICE=x64sc

echo Compiling %SRC% ...
cl65 -t c64 -O -o %PRG% %SRC%
if errorlevel 1 (
echo BUILD FAILED
exit /b 1
)

echo Build OK: %PRG%

if "%1"=="-run" (
echo Launching in VICE ...
"%VICE%" -autostart %PRG% -warp -autostartprgmode 1
)

+ 199
- 0
examples/linked-list-test.c Целия файл

@@ -0,0 +1,199 @@
/* linked-list-test.c - singly linked list on the C64, built with cc65
*
* Build:
* cl65 -t c64 -O -o linked-list-test.prg linked-list-test.c
*
* Exercises malloc()/free()-backed linked list operations (append,
* count, sum, search, delete, reverse, free) at sizes from 1000 to
* 2000 nodes, then runs an allocator capacity test to show how many
* 4-byte nodes actually fit in real C64 RAM under this cc65 memory
* config (program loads at $0801, heap tops out at $D000).
*/

#include <stdlib.h>
#include <conio.h>
#include <c64.h>

typedef struct Node {
int value;
struct Node *next;
} Node;

/* ---- basic list operations ------------------------------------- */

/* Appends 1..count to a new list. Returns NULL (and frees any partial
list) if malloc() runs out of memory partway through. */
static Node *build_list(unsigned int count)
{
Node *head = NULL, *tail = NULL, *n;
unsigned int i;

for (i = 0; i < count; i++) {
n = malloc(sizeof(Node));
if (!n) {
while (head) { n = head; head = head->next; free(n); }
return NULL;
}
n->value = (int)(i + 1);
n->next = NULL;
if (head) tail->next = n; else head = n;
tail = n;
}
return head;
}

static unsigned int count_list(Node *head)
{
unsigned int c = 0;
while (head) { c++; head = head->next; }
return c;
}

static long sum_list(Node *head)
{
long s = 0;
while (head) { s += head->value; head = head->next; }
return s;
}

static Node *list_find(Node *head, int value)
{
while (head) {
if (head->value == value) return head;
head = head->next;
}
return NULL;
}

/* Removes the first node matching value. Returns 1 if removed. */
static unsigned char list_delete(Node **headp, int value)
{
Node *prev = NULL, *cur = *headp;
while (cur) {
if (cur->value == value) {
if (prev) prev->next = cur->next; else *headp = cur->next;
free(cur);
return 1;
}
prev = cur;
cur = cur->next;
}
return 0;
}

static void list_reverse(Node **headp)
{
Node *prev = NULL, *cur = *headp, *next;
while (cur) {
next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
*headp = prev;
}

static void free_list(Node *head)
{
Node *n;
while (head) { n = head; head = head->next; free(n); }
}

/* ---- one pass of tests at a given size --------------------------- */

static unsigned char test_size(unsigned int n)
{
Node *head;
unsigned int c;
long s, expected;
int mid;
unsigned char ok = 1;

head = build_list(n);
if (!head) {
textcolor(COLOR_RED);
cprintf("N=%4u ALLOC FAIL\n\r", n);
return 0;
}

c = count_list(head);
s = sum_list(head);
expected = ((long)n * (n + 1)) / 2;
if (c != n || s != expected) ok = 0;

mid = (int)(n / 2);
if (!list_find(head, mid)) ok = 0;
if (list_find(head, (int)(n + 500))) ok = 0;

if (!list_delete(&head, mid)) ok = 0;
if (list_find(head, mid)) ok = 0;
if (count_list(head) != n - 1) ok = 0;

list_reverse(&head);
if (!head || head->value != (int)n) ok = 0;

free_list(head);

textcolor(ok ? COLOR_GREEN : COLOR_RED);
cprintf("N=%4u %s\n\r", n, ok ? "PASS" : "FAIL");
return ok;
}

/* ---- how many nodes really fit in RAM? --------------------------- */

static void capacity_test(void)
{
Node *head = NULL, *tail = NULL, *n;
unsigned int count = 0;

textcolor(COLOR_WHITE);
cprintf("\n\rCAPACITY TEST (malloc until NULL)...\n\r");

for (;;) {
n = malloc(sizeof(Node));
if (!n) break;
n->value = (int)count;
n->next = NULL;
if (head) tail->next = n; else head = n;
tail = n;
count++;
}

textcolor(COLOR_YELLOW);
cprintf("MAX NODES ALLOCATED: %u\n\r", count);
free_list(head);
}

/* ---- driver -------------------------------------------------------- */

int main(void)
{
static const unsigned int sizes[5] = { 1000, 1250, 1500, 1750, 2000 };
unsigned char i, passed = 0;

clrscr();
bgcolor(COLOR_BLACK);
bordercolor(COLOR_BLACK);
textcolor(COLOR_CYAN);
cprintf("C64 LINKED LIST TEST (cc65)\n\r");
cprintf("===========================\n\r");
textcolor(COLOR_WHITE);
cprintf("append/count/sum/find/delete/reverse\n\r\n\r");

for (i = 0; i < 5; i++) {
passed += test_size(sizes[i]);
}

textcolor(COLOR_WHITE);
cprintf("\n\r%u/5 SIZE TESTS PASSED\n\r", passed);

capacity_test();

textcolor(COLOR_CYAN);
cprintf("\n\rDONE - PRESS ANY KEY\n\r");
cgetc();

bgcolor(COLOR_BLUE);
bordercolor(COLOR_LIGHTBLUE);
return 0;
}

Двоични данни
examples/linked-list-test.prg Целия файл


+ 21
- 0
examples/moonlander-build.bat Целия файл

@@ -0,0 +1,21 @@
@echo off
REM Build moonlander.c with cc65 and optionally launch in VICE x64sc
REM Requirements: cc65 on PATH, VICE x64sc on PATH or in GTK3VICE install dir

set SRC=moonlander.c
set PRG=moonlander.prg
set VICE=x64sc

echo Compiling %SRC% ...
cl65 -t c64 -O -o %PRG% %SRC%
if errorlevel 1 (
echo BUILD FAILED
exit /b 1
)

echo Build OK: %PRG%

if "%1"=="-run" (
echo Launching in VICE ...
"%VICE%" -autostart %PRG% -warp -autostartprgmode 1
)

+ 465
- 0
examples/moonlander.c Целия файл

@@ -0,0 +1,465 @@
/* moonlander.c – Lunar Lander for Commodore 64
*
* Build:
* cl65 -t c64 -O -o moonlander.prg moonlander.c
*
* Controls (joystick port 2):
* FIRE = upward thrust
* LEFT/RIGHT = horizontal thrust
*
* Land gently on the green pad to score points.
* Hard landing or missing the pad = CRASH.
*/

#include <c64.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>

/* ----------------------------------------------------------------
Hardware register macros
---------------------------------------------------------------- */
#define SP0X (*(unsigned char*)0xD000)
#define SP0Y (*(unsigned char*)0xD001)
#define MSIGX (*(unsigned char*)0xD010) /* MSB of sprite X coords */
#define SPENA (*(unsigned char*)0xD015) /* sprite enable bits */
#define SP0COL (*(unsigned char*)0xD027) /* sprite 0 color */
#define BGCOL (*(unsigned char*)0xD021) /* background color */
#define BORDER (*(unsigned char*)0xD020) /* border color */
#define RASTER (*(unsigned char*)0xD012) /* current raster line */

#define SCREEN ((unsigned char*)0x0400) /* screen RAM */
#define COLRAM ((unsigned char*)0xD800) /* color RAM */
#define SPR0PTR (*(unsigned char*)0x07F8) /* sprite 0 data pointer */

/* CIA #1 $DC00 – joystick port 2 (active LOW) */
#define JOY2 (*(unsigned char*)0xDC00)
#define JOY_PORT2_UP 0x01
#define JOY_PORT2_DOWN 0x02
#define JOY_PORT2_LEFT 0x04
#define JOY_PORT2_RIGHT 0x08
#define JOY_PORT2_FIRE 0x10

/* SID voice 1 */
#define SID_FLO (*(unsigned char*)0xD400)
#define SID_FHI (*(unsigned char*)0xD401)
#define SID_CT (*(unsigned char*)0xD404)
#define SID_AD (*(unsigned char*)0xD405)
#define SID_SR (*(unsigned char*)0xD406)
#define SID_VOL (*(unsigned char*)0xD418)

/* ----------------------------------------------------------------
Layout constants
---------------------------------------------------------------- */
#define COLS 40
#define ROWS 25
#define TERRAIN_ROW 22 /* text row: top of terrain surface */
#define PAD_COL 18 /* landing pad left column */
#define PAD_W 4 /* landing pad width in characters */

/* Sprite block 13 sits at $0340 (tape buffer area, safe to reuse) */
#define SPR_BLOCK 13
#define SPR_MEM ((unsigned char*)0x0340)

/* Sprite Y where terrain surface begins (NTSC top ≈ 50, row*8 px) */
#define GROUND_Y (50 + TERRAIN_ROW * 8) /* = 226 */

/* Landing pad pixel X bounds (left border ≈ 24px for NTSC) */
#define PAD_PIX_L (24 + PAD_COL * 8) /* 168 */
#define PAD_PIX_R (24 + (PAD_COL + PAD_W) * 8) /* 200 */

/* Fixed-point scale: multiply all positions/velocities by SCALE */
#define SCALE 4

/* ----------------------------------------------------------------
Lander sprite: 24×21 hi-res, 63 bytes + 1 padding
----------------------------------------------------------------
Looks like a rocket/lander body with two splayed landing legs.
---------------------------------------------------------------- */
static const unsigned char spr_lander[64] = {
0x00, 0x18, 0x00, /* row 0 top nozzle */
0x00, 0x3C, 0x00, /* row 1 */
0x00, 0x7E, 0x00, /* row 2 */
0x01, 0xFF, 0x80, /* row 3 */
0x03, 0xFF, 0xC0, /* row 4 */
0x07, 0xFF, 0xE0, /* row 5 wide body */
0x03, 0xFF, 0xC0, /* row 6 */
0x01, 0xFF, 0x80, /* row 7 */
0x00, 0xFF, 0x00, /* row 8 */
0x00, 0x7E, 0x00, /* row 9 */
0x00, 0x3C, 0x00, /* row 10 */
0x00, 0x3C, 0x00, /* row 11 */
0x00, 0x3C, 0x00, /* row 12 */
0x01, 0xFF, 0x80, /* row 13 lower body */
0x03, 0xFF, 0xC0, /* row 14 */
0x07, 0xFF, 0xE0, /* row 15 */
0x06, 0x00, 0x60, /* row 16 leg struts */
0x0C, 0x00, 0x30, /* row 17 */
0x18, 0x00, 0x18, /* row 18 */
0x30, 0x00, 0x0C, /* row 19 */
0x60, 0x00, 0x06, /* row 20 footpads */
0x00 /* padding byte */
};

/* ----------------------------------------------------------------
Thrust exhaust sprite: 24×21, placed below lander when firing
---------------------------------------------------------------- */
static const unsigned char spr_exhaust[64] = {
0x00, 0x18, 0x00,
0x00, 0x3C, 0x00,
0x00, 0x7E, 0x00,
0x01, 0xFF, 0x80,
0x01, 0xE7, 0x80,
0x00, 0xC3, 0x00,
0x00, 0x42, 0x00,
0x00, 0x3C, 0x00,
0x00, 0x18, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00, 0x00, 0x00,
0x00
};

/* ----------------------------------------------------------------
Game state
---------------------------------------------------------------- */
static int px, py; /* lander sprite position × SCALE */
static int vx, vy; /* velocity × SCALE */
static int fuel; /* remaining fuel × SCALE */
static int score;
static unsigned char crashed;
static unsigned char landed;

/* ----------------------------------------------------------------
Helpers
---------------------------------------------------------------- */

/* Sync to raster line 250 – gives one tick per TV frame (≈50/60 Hz) */
static void wait_frame(void)
{
while (RASTER != 250) {}
while (RASTER == 250) {}
}

static void sid_thrust_on(void)
{
SID_VOL = 0x0F;
SID_FLO = 0x60;
SID_FHI = 0x00;
SID_CT = 0x81; /* noise waveform + gate */
SID_AD = 0x00;
SID_SR = 0xF0;
}

static void sid_off(void)
{
SID_CT = 0x00; /* release gate */
}

static void sid_silence_all(void)
{
(*(unsigned char*)0xD404) = 0x00;
(*(unsigned char*)0xD40B) = 0x00;
(*(unsigned char*)0xD412) = 0x00;
SID_VOL = 0x0F;
}

/* Draw two rows of terrain: solid blocks with a flat pad gap */
static void draw_terrain(void)
{
unsigned char c;
for (c = 0; c < COLS; c++) {
unsigned int r1 = (unsigned int)TERRAIN_ROW * COLS + c;
unsigned int r2 = (unsigned int)(TERRAIN_ROW + 1) * COLS + c;

if (c >= PAD_COL && c < PAD_COL + PAD_W) {
SCREEN[r1] = 0x2D; /* '-' in screen code = pad marker */
COLRAM[r1] = 5; /* green */
} else {
SCREEN[r1] = 0xA0; /* solid block */
COLRAM[r1] = 11; /* dark grey */
}
SCREEN[r2] = 0xA0;
COLRAM[r2] = 11;
}
}

static void draw_stars(void)
{
static const unsigned char sr[] = { 2, 4, 7, 10, 13, 3, 8, 15, 19, 6 };
static const unsigned char sc[] = { 5, 15, 30, 8, 35, 22, 3, 18, 10, 37 };
unsigned char i;
for (i = 0; i < 10; i++) {
unsigned int p = (unsigned int)sr[i] * COLS + sc[i];
SCREEN[p] = 0x2E; /* '.' */
COLRAM[p] = 1; /* white */
}
}

/* Write sprite 0 position registers from internal fixed-point state */
static void update_sprite(void)
{
unsigned int sx = (unsigned int)(px / SCALE);
unsigned int sy = (unsigned int)(py / SCALE);

if (sx > 255) {
SP0X = (unsigned char)(sx - 256);
MSIGX = MSIGX | 0x01;
} else {
SP0X = (unsigned char)sx;
MSIGX = MSIGX & 0xFE;
}
SP0Y = (unsigned char)(sy > 247 ? 247 : sy);
}

/* Update exhaust sprite 1 (shown only while thrusting) */
static void update_exhaust(unsigned char thrusting)
{
if (thrusting) {
unsigned int sx = (unsigned int)(px / SCALE);
unsigned int sy = (unsigned int)(py / SCALE) + 18;
if (sx > 255) {
(*(unsigned char*)0xD002) = (unsigned char)(sx - 256);
MSIGX = MSIGX | 0x02;
} else {
(*(unsigned char*)0xD002) = (unsigned char)sx;
MSIGX = MSIGX & 0xFD;
}
(*(unsigned char*)0xD003) = (unsigned char)(sy > 247 ? 247 : sy);
SPENA = SPENA | 0x02;
} else {
SPENA = SPENA & 0xFD; /* hide exhaust sprite */
}
}

static void draw_hud(void)
{
gotoxy(0, 0);
cprintf("FUEL:%4d VY:%3d VX:%3d SC:%5d",
fuel / SCALE, vy / SCALE, vx / SCALE, score);
}

static void center_msg(const char *msg, unsigned char row)
{
unsigned char len = (unsigned char)strlen(msg);
unsigned char col = (COLS - len) / 2;
gotoxy(col, row);
textcolor(COLOR_WHITE);
cputs(msg);
}

/* ----------------------------------------------------------------
main
---------------------------------------------------------------- */
int main(void)
{
unsigned char joy;
unsigned char thrusting;
unsigned char gravity_tick;
unsigned char play_again;
int lsx, lsy; /* lander sprite X/Y (not scaled) for collision */

/* ---- Title screen ------------------------------------------ */
clrscr();
bgcolor(COLOR_BLACK);
bordercolor(COLOR_BLACK);
textcolor(COLOR_CYAN);
center_msg("* MOONLANDER *", 4);
textcolor(COLOR_YELLOW);
center_msg("GUIDE YOUR SHIP TO THE GREEN PAD", 7);
textcolor(COLOR_WHITE);
center_msg("FIRE = UPWARD THRUST", 9);
center_msg("LEFT/RIGHT = HORIZONTAL THRUST", 10);
center_msg("LAND GENTLY TO SCORE POINTS", 11);
center_msg("HARD LANDING OR MISSING PAD", 12);
center_msg("MEANS CERTAIN DOOM!", 13);
textcolor(COLOR_GREEN);
center_msg("PRESS FIRE TO BEGIN", 17);

/* Park lander off-screen during title */
memcpy(SPR_MEM, spr_lander, 64);
memcpy((unsigned char*)0x0380, spr_exhaust, 64);
SPR0PTR = SPR_BLOCK; /* block 13 = lander */
(*(unsigned char*)0x07F9) = 14; /* block 14 = exhaust */
SP0COL = 7; /* yellow lander */
(*(unsigned char*)0xD028) = 8; /* orange exhaust */
SP0X = 0; SP0Y = 251;
SPENA = 0x01;
sid_silence_all();

while (JOY2 & JOY_PORT2_FIRE) {} /* wait for fire press */
while (!(JOY2 & JOY_PORT2_FIRE)) {} /* wait for release */

play_again = 1;
do {
/* ---- Game setup -------------------------------------------- */
clrscr();
bgcolor(COLOR_BLACK);
bordercolor(COLOR_BLACK);
textcolor(COLOR_WHITE);

draw_terrain();
draw_stars();

px = 160 * SCALE;
py = 55 * SCALE;
vx = 0;
vy = 1; /* start with gentle downward drift */
fuel = 200 * SCALE;
score = 0;
crashed = 0;
landed = 0;
gravity_tick = 0;

SPENA = 0x01;
update_sprite();
update_exhaust(0);

/* ---- Main game loop ---------------------------------------- */
while (!crashed && !landed) {
wait_frame();

joy = JOY2;
thrusting = 0;

/* Upward thrust from fire button */
if (!(joy & JOY_PORT2_FIRE) && fuel > 0) {
vy -= 5;
fuel -= SCALE;
thrusting = 1;
sid_thrust_on();
} else {
sid_off();
}

/* Horizontal thrust */
if (!(joy & JOY_PORT2_LEFT) && fuel > 0) {
vx -= 2;
fuel -= 1;
}
if (!(joy & JOY_PORT2_RIGHT) && fuel > 0) {
vx += 2;
fuel -= 1;
}
if (fuel < 0) fuel = 0;

/* Very gentle gravity: +1 sub-pixel every fourth frame */
gravity_tick++;
if (gravity_tick >= 4) {
gravity_tick = 0;
vy += 1;
}

/* Light horizontal damping (simulate RCS / air drag) */
if (vx > 0) vx--;
else if (vx < 0) vx++;

/* Clamp velocities */
if (vy > 32) vy = 32;
if (vy < -24) vy = -24;
if (vx > 16) vx = 16;
if (vx < -16) vx = -16;

/* Integrate position */
px += vx;
py += vy;

/* Clamp to visible playfield (X: ~24..295 sprite pixels) */
if (px < 24 * SCALE) { px = 24 * SCALE; vx = -vx / 2; }
if (px > 295 * SCALE) { px = 295 * SCALE; vx = -vx / 2; }

/* ---- Ground collision ---------------------------------- */
lsx = (int)(px / SCALE);
lsy = (int)(py / SCALE);

/* Sprite is 21 px tall; check if bottom row reaches terrain */
if (lsy + 21 >= GROUND_Y) {
py = (GROUND_Y - 21) * SCALE;
lsy = GROUND_Y - 21;

/* Is lander center (X+12) over the pad? */
if ((lsx + 12) >= PAD_PIX_L && (lsx + 12) <= PAD_PIX_R) {
/* Safe if slow enough: vy <= 8, |vx| <= 6 */
if (vy <= 8 && vx >= -6 && vx <= 6) {
score = 1000 - (vy / SCALE) * 40 + (fuel / SCALE);
if (score < 0) score = 0;
landed = 1;
} else {
crashed = 1;
}
} else {
crashed = 1; /* missed the pad */
}
}

/* Don't let lander go above row 1 (HUD row) */
if (py < 58 * SCALE) { py = 58 * SCALE; vy = 1; }

update_sprite();
update_exhaust(thrusting);
draw_hud();
}

/* ---- Game over --------------------------------------------- */
sid_off();

if (landed) {
SPENA = 0x01; /* keep lander visible */
textcolor(COLOR_GREEN);
center_msg("** SAFE LANDING! **", 9);
{
char buf[24];
sprintf(buf, "YOUR SCORE: %d", score);
textcolor(COLOR_YELLOW);
center_msg(buf, 11);
}
textcolor(COLOR_WHITE);
center_msg("NICE FLYING, PILOT!", 13);
} else {
SPENA = 0x00; /* hide sprite – it exploded */
/* Draw explosion at impact site */
{
unsigned char ex_col = (unsigned char)((px / SCALE - 24) / 8);
unsigned int ep;
if (ex_col >= COLS) ex_col = COLS - 1;
ep = (unsigned int)(TERRAIN_ROW - 1) * COLS + ex_col;
SCREEN[ep] = 0x51; /* '*' in PETSCII screen code */
COLRAM[ep] = 2; /* red */
}
textcolor(COLOR_RED);
center_msg("** CRASH! **", 9);
textcolor(COLOR_WHITE);
center_msg("NO SCORE FOR YOU.", 11);
}

textcolor(COLOR_GREEN);
center_msg("FIRE = PLAY AGAIN", 16);
textcolor(COLOR_WHITE);
center_msg("LEFT = QUIT", 17);

while (!(JOY2 & JOY_PORT2_FIRE) || !(JOY2 & JOY_PORT2_LEFT)) {}
do {
joy = JOY2;
} while ((joy & JOY_PORT2_FIRE) && (joy & JOY_PORT2_LEFT));
play_again = !(joy & JOY_PORT2_FIRE);
while (!(JOY2 & JOY_PORT2_FIRE) || !(JOY2 & JOY_PORT2_LEFT)) {}
} while (play_again);

/* ---- Return to BASIC --------------------------------------- */
SPENA = 0x00;
sid_silence_all();
clrscr();
bgcolor(6); /* restore C64 default blue */
bordercolor(14);

return 0;
}

Двоични данни
examples/moonlander.prg Целия файл


+ 18
- 0
examples/run.bat Целия файл

@@ -0,0 +1,18 @@
@echo off
setlocal

set "VICE_EXE=C:\Program Files\GTK3VICE-3.10-win64\bin\x64sc.exe"
set "PRG=%~dp0moonlander.prg"

if not exist "%VICE_EXE%" (
echo VICE x64sc was not found at:
echo %VICE_EXE%
exit /b 1
)

if not exist "%PRG%" (
call "%~dp0moonlander-build.bat"
if errorlevel 1 exit /b 1
)

"%VICE_EXE%" -autostart "%PRG%" -autostart-warp -autostartprgmode 1

+ 1
- 1
examples/sprite-music-demo.bas Целия файл

@@ -24,7 +24,7 @@
1000 REM ===== INIT SID =====
1010 FOR A=54272 TO 54296:POKE A,0:NEXT A : REM SILENCE ALL
1020 POKE 54296,15 : REM MAX VOLUME
1030 POKE 54272,0:POKE 54273,40 : REM VOICE 1 FREQ ~440Hz (A4)
1030 POKE 54272,0:POKE 54273,40
1040 POKE 54277,9 : REM ATTACK=0 DECAY=9
1050 POKE 54278,240 : REM SUSTAIN=15 RELEASE=0
1060 RETURN


Loading…
Отказ
Запис

Powered by TurnKey Linux.