1
0
Fork 0
tsgames/AGENTS.md

9.3 KiB

AI Agent Instructions for TS-Games

This document provides guidelines for AI agents working with the TS-Games project.

Project Overview

TS-Games is a custom framework/build system for creating simple single-file TypeScript games. All games compile into one standalone HTML file with no external dependencies—all assets are inlined as data URLs.

Key Concepts

Two Types of Applications

  1. Games - Interactive applications with game loops

    • Located in src/games/<name>/
    • Entry point: index.ts or index.tsx
    • Default export: runGame() function
    • Use gameLoop() from @common/game for structured game development
  2. Apps - More complex applications (like AI-Story)

    • Also located in src/games/<name>/
    • Entry point: index.tsx
    • Default export: main() function
    • May use Preact components and complex state management

Single HTML File Architecture

  • All games build into a single .html file in dist/
  • No external dependencies at runtime
  • All assets (images, audio, fonts, WASM) are inlined as base64 data URLs
  • Build process uses Bun's plugin system to transform assets

Creating a New Game

Step 1: Create Directory Structure

mkdir -p src/games/<yourgame>/assets

Step 2: Create Entry Point

Create src/games/<yourgame>/index.ts or index.tsx:

// Simple game example (index.ts)
import { gameLoop } from "@common/game";
import Input from "@common/input";

const setup = () => {
    // Initialize game state
    return { x: 0, y: 0 };
};

const frame = (dt: number, state: ReturnType<typeof setup>) => {
    // Update game state
    state.x += Input.getHorizontal() * dt;
    state.y += Input.getVertical() * dt;
};

export default gameLoop(setup, frame);
// Preact component example (index.tsx)
import { render } from "preact";
import App from "./components/app";

export default function main() {
    render(<App />, document.body);
}

Step 3: Add Assets (Optional)

Place assets in src/games/<yourgame>/assets/:

  • Images: .png, .jpg, .jpeg → Import as HTMLImageElement
  • Audio: .wav, .mp3, .ogg → Import as HTMLAudioElement
  • CSS: .css, .module.css → Import as styles
  • Fonts: .font.css → Import font faces
  • WASM: .c, .cpp, .wasm → Import as WASM module
  • Icons: favicon.ico → Auto-used as page icon
  • PWA: pwa_icon.png → Used for PWA manifest

Step 4: Import Assets

// Import image
import spritesheet from './assets/spritesheet.png';
console.log(spritesheet); // <img src="data:image/png;base64,..." />

// Import audio
import heal from './assets/heal.ogg';
heal.play();

// Import CSS
import './assets/styles.css';

// Import CSS modules
import styles from './assets/styles.module.css';
<div className={styles.root} />

// Import WASM (C/C++)
import wasmModule from './assets/game.c';
// Access exports: wasmModule.memory, wasmModule.functionName()

// Import GLSL shader
import shader from './assets/shader.glsl';

Development Workflow

Running Development Server

bun start

Navigate to http://localhost:3000 to see the game list. Games rebuild on each page reload.

To run a specific game:

bun start --game=<yourgame>

Building for Production

Build a specific game:

bun run build <yourgame>

Or select from interactive list:

bun run build

Output: dist/<yourgame>.html

Building with Options

  • --local: Build for local testing (no PWA, different index)
  • --production: Minify JS/CSS/HTML (default for build)

Project Structure

tsgames/
├── src/
│   ├── games/           # All games/apps live here
│   │   ├── <game1>/     # Individual game directory
│   │   │   ├── index.ts # Entry point (required)
│   │   │   └── assets/  # Game assets
│   │   └── index/       # Special: game list page
│   ├── common/          # Shared utilities
│   │   ├── game.ts      # Game loop helper
│   │   ├── input.ts     # Input handling
│   │   ├── dom.ts       # DOM utilities
│   │   ├── utils.ts     # General utilities
│   │   ├── errors.ts    # Error formatting
│   │   ├── components/  # Shared Preact components
│   │   ├── hooks/       # React-like hooks
│   │   ├── display/     # Canvas/graphics utilities
│   │   └── physics/     # Physics utilities
│   └── types.d.ts       # Global type definitions
├── build/
│   ├── build.ts         # Build script
│   ├── server.ts        # Dev server
│   ├── html.ts          # HTML generation
│   ├── isGame.ts        # Game detection
│   ├── imagePlugin.ts   # Image bundler plugin
│   ├── audioPlugin.ts   # Audio bundler plugin
│   ├── fontPlugin.ts    # Font bundler plugin
│   ├── wasmPlugin.ts    # WASM bundler plugin
│   └── filePlugin.ts    # Generic file plugin
├── dist/                # Built HTML files
└── test/                # Tests

Technical Details

Bun Configuration

  • Use bun for all package management and script running
  • .env files are automatically loaded (no dotenv needed)
  • Use Bun.serve() for servers (not express)
  • Use Bun.file() for file operations (not fs)
  • Use Bun.$ for shell commands (not execa)

TypeScript Configuration

  • JSX: react-jsx with Preact
  • Module resolution: bundler
  • Path aliases: @common/*./src/common/*
  • Strict mode enabled

Asset Processing

All asset processing happens at build time via Bun plugins:

  1. Images (.png, .jpg, .jpeg):

    • Converted to base64 data URLs
    • Return HTMLImageElement that's ready to use
  2. Audio (.wav, .mp3, .ogg):

    • Converted to base64 data URLs
    • Return HTMLAudioElement with loaded source
  3. CSS:

    • Bundled and inlined in <style> tag
    • CSS modules supported (.module.css)
    • Modern CSS features transpiled (nesting, vendor prefixes)
  4. Fonts:

    • Import via .font.css files
    • Base64 inlined
  5. WASM:

    • .c/.cpp: Compiled to WASM at build time (requires clang)
    • .wasm: Direct inclusion
    • All inlined as base64 data URLs

WASM Development

For C/C++ files:

// Example: src/games/life/life.c
EXPORT(init) void init();
EXPORT(step) void step();
EXPORT(getCell) int getCell(int x, int y);

// Must export memory

Requirements:

  • Install: sudo apt install clang lld wabt
  • Only function exports and memory supported

PWA Support

Production builds include PWA manifest with:

  • App name from game directory name (capitalized)
  • Icon from favicon.ico or pwa_icon.png
  • Fullscreen display mode

Publishing

Configure .env:

PUBLISH_LOCATION=ssh.example.com:/var/www/games/
PUBLISH_URL=https://example.com/

Build and publish:

bun run build <yourgame>

Common Patterns

Game Loop Pattern

import { gameLoop } from "@common/game";
import Input from "@common/input";

interface State {
    playerX: number;
    playerY: number;
    score: number;
}

const setup = (): State => {
    return {
        playerX: 100,
        playerY: 100,
        score: 0,
    };
};

const frame = (dt: number, state: State): State => {
    // Update based on input
    state.playerX += Input.getHorizontal() * 200 * dt;
    state.playerY += Input.getVertical() * 200 * dt;

    // Update score
    state.score += dt;

    // Return new state (optional)
    return state;
};

export default gameLoop(setup, frame);

Preact Component Pattern

import { useState, useEffect } from "preact/hooks";
import styles from './app.module.css';

export function App() {
    const [count, setCount] = useState(0);

    return (
        <div className={styles.container}>
            <h1>Count: {count}</h1>
            <button onClick={() => setCount(c => c + 1)}>
                Increment
            </button>
        </div>
    );
}

Testing

Run tests:

bun test

Tests use Bun's built-in test framework:

import { test, expect } from "bun:test";

test("example", () => {
    expect(1 + 1).toBe(2);
});

Important Notes

  1. No external runtime dependencies - Everything must be bundled
  2. Single entry point - Each game has one default export
  3. Game name from directory - Directory name becomes game name and URL path
  4. Hot reload - Dev server rebuilds on each page reload
  5. Mobile support - Use ?mobile=true to enable Eruda debugger
  6. Production debugging - Use ?production=true to test production builds in dev

Examples

Study existing games for patterns:

  • Simple: playground/ - Basic game loop example
  • Canvas: binario/ - Canvas-based puzzle game
  • Preact: ai-story/ - Complex app with components and state
  • WASM: life/ - C code compiled to WASM
  • Text-based: text-dungeon/ - Text adventure game

Troubleshooting

Build fails with "No entry point found"

  • Ensure your game has index.ts or index.tsx with default export

Assets not loading

  • Check file extensions are supported
  • Verify assets are in src/games/<name>/assets/

WASM compilation fails

  • Install clang and WASM toolchain
  • Check C/C++ code compiles with -Werror

TypeScript errors

  • Run bunx tsc --noEmit --skipLibCheck to check types
  • Use @common/* path alias for common imports