Program library
+Choose a program
+Programs are read directly from the public repository each time this page loads.
+Loading programs…
+ + +Now running
+ +Click inside the emulator to enable keyboard input and sound.
+ + +diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..25ea755 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.env.example +node_modules +npm-debug.log + diff --git a/.env b/.env new file mode 100644 index 0000000..0423648 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +HOST_PORT=8134 +PROGRAM_REPOSITORY=https://git.p20.dev/christian/c64-basic +PROGRAM_BRANCH=main + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cb691fa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM node:22-alpine + +WORKDIR /app +COPY server.mjs ./server.mjs +COPY public ./public + +ENV PORT=8080 +EXPOSE 8080 + +USER node +CMD ["node", "server.mjs"] + diff --git a/README.md b/README.md index ef074b4..cf829c0 100644 --- a/README.md +++ b/README.md @@ -7,26 +7,36 @@ machine-language payload is required. ## Programs -| Program | Type | Controls | Description | Play online | +| Program | Type | Controls | Description | Run locally | | --- | --- | --- | --- | --- | -| [Meteor Dodge](meteor-dodge/meteor-dodge.bas) | Arcade | `A`, `D`, `Q` | Dodge falling meteors and build your score. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#meteor-dodge) | -| [Code Breaker](code-breaker/code-breaker.bas) | Logic | Digits, `Q` | Deduce a four-digit secret in ten guesses. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#code-breaker) | -| [Submarine Hunt](submarine-hunt/submarine-hunt.bas) | Strategy | Coordinates, `Q` | Find three hidden submarines with limited sonar shots. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#submarine-hunt) | -| [Lunar Lander](lunar-lander/lunar-lander.bas) | Simulation | `0`-`9`, `Q` | Choose engine burns and touch down gently. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#lunar-lander) | -| [Memory Matrix](memory-matrix/memory-matrix.bas) | Memory | `1`-`4`, `Q` | Repeat a growing sequence of flashing quadrants. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#memory-matrix) | -| [Robot Maze](robot-maze/robot-maze.bas) | Puzzle | `W`, `A`, `S`, `D`, `Q` | Navigate a freshly generated maze to its exit. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#robot-maze) | -| [Music Box](music-box/music-box.bas) | Music | `A`-`K`, `1`-`3`, `Q` | Play the SID chip as a small computer keyboard. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#music-box) | -| [Pixel Painter](pixel-painter/pixel-painter.bas) | Creative | Cursor keys, Space, `C`, `Q` | Draw character-cell pictures on the screen. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#pixel-painter) | -| [Stock Trader](stock-trader/stock-trader.bas) | Simulation | `B`, `S`, `W`, `Q` | Trade a volatile stock over twenty market days. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#stock-trader) | -| [Starfield](starfield/starfield.bas) | Demo | `1`-`3`, `Q` | Watch and control a layered animated starfield. | [Run program](https://htmlpreview.github.io/?https://git.p20.dev/christian/c64-basic/raw/branch/main/runner.html#starfield) | +| [Meteor Dodge](meteor-dodge/meteor-dodge.bas) | Arcade | `A`, `D`, `Q` | Dodge falling meteors and build your score. | [Run program](http://localhost:8134/?program=meteor-dodge%2Fmeteor-dodge.prg) | +| [Code Breaker](code-breaker/code-breaker.bas) | Logic | Digits, `Q` | Deduce a four-digit secret in ten guesses. | [Run program](http://localhost:8134/?program=code-breaker%2Fcode-breaker.prg) | +| [Submarine Hunt](submarine-hunt/submarine-hunt.bas) | Strategy | Coordinates, `Q` | Find three hidden submarines with limited sonar shots. | [Run program](http://localhost:8134/?program=submarine-hunt%2Fsubmarine-hunt.prg) | +| [Lunar Lander](lunar-lander/lunar-lander.bas) | Simulation | `0`-`9`, `Q` | Choose engine burns and touch down gently. | [Run program](http://localhost:8134/?program=lunar-lander%2Flunar-lander.prg) | +| [Memory Matrix](memory-matrix/memory-matrix.bas) | Memory | `1`-`4`, `Q` | Repeat a growing sequence of flashing quadrants. | [Run program](http://localhost:8134/?program=memory-matrix%2Fmemory-matrix.prg) | +| [Robot Maze](robot-maze/robot-maze.bas) | Puzzle | `W`, `A`, `S`, `D`, `Q` | Navigate a freshly generated maze to its exit. | [Run program](http://localhost:8134/?program=robot-maze%2Frobot-maze.prg) | +| [Music Box](music-box/music-box.bas) | Music | `A`-`K`, `1`-`3`, `Q` | Play the SID chip as a small computer keyboard. | [Run program](http://localhost:8134/?program=music-box%2Fmusic-box.prg) | +| [Pixel Painter](pixel-painter/pixel-painter.bas) | Creative | Cursor keys, Space, `C`, `Q` | Draw character-cell pictures on the screen. | [Run program](http://localhost:8134/?program=pixel-painter%2Fpixel-painter.prg) | +| [Stock Trader](stock-trader/stock-trader.bas) | Simulation | `B`, `S`, `W`, `Q` | Trade a volatile stock over twenty market days. | [Run program](http://localhost:8134/?program=stock-trader%2Fstock-trader.prg) | +| [Starfield](starfield/starfield.bas) | Demo | `1`-`3`, `Q` | Watch and control a layered animated starfield. | [Run program](http://localhost:8134/?program=starfield%2Fstarfield.prg) | ## Running the programs -Click a **Run program** link in the table to launch that program directly in a -browser-based VICE x64sc emulator powered by WebAssembly. The runner downloads -the emulator from the EmulatorJS CDN; the program itself is embedded in the -repository's runner, so no file picker or manual loading is required. Click the -emulator once to give it keyboard focus and enable audio. +Start the local browser app with Docker Compose: + +```text +docker compose up --build -d +``` + +Open [http://localhost:8134](http://localhost:8134), or click a **Run program** +link in the table. The app runs the VICE x64sc core through WebAssembly and +downloads the selected PRG through its own backend, avoiding browser CORS +restrictions. Click inside the emulator to enable keyboard input and sound. + +The app reads the public repository tree at runtime. New `.prg` files pushed to +the configured branch appear after clicking **Refresh list**; rebuilding the +Docker image is not required. Repository, branch, and host port settings live in +`.env`, where the default host port is `8134`. Each directory contains an editable, line-numbered `.bas` listing and a matching tokenized `.prg` file. The PRGs use the normal C64 BASIC load address, `$0801`. diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..f41691b --- /dev/null +++ b/compose.yaml @@ -0,0 +1,10 @@ +services: + c64-basic: + build: . + ports: + - "${HOST_PORT:-8134}:8080" + environment: + PROGRAM_REPOSITORY: "${PROGRAM_REPOSITORY:-https://git.p20.dev/christian/c64-basic}" + PROGRAM_BRANCH: "${PROGRAM_BRANCH:-main}" + restart: unless-stopped + diff --git a/public/app.js b/public/app.js new file mode 100644 index 0000000..c0afa5b --- /dev/null +++ b/public/app.js @@ -0,0 +1,71 @@ +const library = document.querySelector('#library'); +const player = document.querySelector('#player'); +const status = document.querySelector('#status'); +const programsElement = document.querySelector('#programs'); +const refreshButton = document.querySelector('#refresh'); + +function titleFromPath(path) { + const filename = path.split('/').at(-1).replace(/\.prg$/i, ''); + return filename.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' '); +} + +async function loadPrograms() { + status.hidden = false; + status.className = 'status'; + status.textContent = 'Loading programs…'; + programsElement.replaceChildren(); + refreshButton.disabled = true; + try { + const response = await fetch('/api/programs', { cache: 'no-store' }); + if (!response.ok) throw new Error('The program list could not be loaded.'); + const { programs } = await response.json(); + for (const program of programs) { + const link = document.createElement('a'); + link.className = 'program-card'; + link.href = `/?program=${encodeURIComponent(program.path)}`; + const title = document.createElement('strong'); + title.textContent = titleFromPath(program.path); + const action = document.createElement('span'); + action.textContent = 'Run program →'; + link.append(title, action); + programsElement.append(link); + } + status.textContent = programs.length ? `${programs.length} programs available` : 'No PRG files found.'; + } catch (error) { + status.className = 'status error'; + status.textContent = error.message; + } finally { + refreshButton.disabled = false; + } +} + +function startEmulator(programPath) { + library.hidden = true; + player.hidden = false; + const title = titleFromPath(programPath); + document.title = `${title} — C64 BASIC Collection`; + document.querySelector('#program-title').textContent = title; + + window.EJS_player = '#game'; + window.EJS_core = 'vice_x64sc'; + window.EJS_gameUrl = `/api/program?path=${encodeURIComponent(programPath)}`; + window.EJS_gameName = programPath.split('/').at(-1); + window.EJS_pathtodata = 'https://cdn.emulatorjs.org/stable/data/'; + window.EJS_startOnLoaded = true; + window.EJS_language = 'en-US'; + + const loader = document.createElement('script'); + loader.src = `${window.EJS_pathtodata}loader.js`; + loader.onerror = () => { + const message = document.querySelector('#emulator-error'); + message.hidden = false; + message.textContent = 'The WASM VICE emulator could not be downloaded. Check your connection and try again.'; + }; + document.body.append(loader); +} + +refreshButton.addEventListener('click', loadPrograms); +const programPath = new URLSearchParams(location.search).get('program'); +if (programPath) startEmulator(programPath); +else loadPrograms(); + diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..3d46884 --- /dev/null +++ b/public/index.html @@ -0,0 +1,40 @@ + + +
+ + + +Program library
+Programs are read directly from the public repository each time this page loads.
+Loading programs…
+ + +Now running
+ +Click inside the emulator to enable keyboard input and sound.
+ + +Click the emulator to enable keyboard input and sound.
-