Package Exports
- @shaisrc/tty
- @shaisrc/tty/dist/index.es.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@shaisrc/tty) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Status Badges
@shaisrc/tty
A minimalist, high-performance ASCII rendering library for game developers
KISS Philosophy • Simple drawing functions, no framework overhead • Output-agnostic design • TypeScript-first with excellent DX
✨ Features
- 🎨 Rich Drawing API - Box drawing, text alignment, shapes, and more
- 🎮 Game-Ready - Built-in game loop, keyboard/gamepad/pointer input, camera system
- ✨ Animation System - Flash, pulse, and custom animations with easing functions
- 🧱 Layering System - Multiple render layers with z-ordering 🎯 Output Agnostic - Render to Canvas (DOM target planned)
- ⚡ High Performance - Double-buffering, dirty rectangle optimization
- 🔗 Chainable API - Fluent method chaining for better DX
- 📦 Zero Dependencies - Lightweight and self-contained
- 💪 TypeScript First - Full type safety and IntelliSense support
🚧 Release Status
Current builds are beta. A stable 0.1.0 release is blocked by:
- DOMTarget (browser DOM render target for accessibility)
📦 Installation
npm install @shaisrc/tty🚀 Quick Start
30-Second Example
import { Renderer, CanvasTarget } from "@shaisrc/tty";
// Create a canvas target
const canvas = document.getElementById("game") as HTMLCanvasElement;
const target = new CanvasTarget(canvas, { width: 80, height: 24 });
// Create renderer
const renderer = new Renderer(target);
// Draw something!
renderer
.clear()
.box(10, 5, 30, 10, { style: "double", fill: true })
.centerText(9, "Hello, ASCII World!", { fg: "brightCyan" })
.render();Complete Game Loop Example
import {
Renderer,
CanvasTarget,
GameLoop,
KeyboardManager,
} from "@shaisrc/tty";
const canvas = document.getElementById("game") as HTMLCanvasElement;
const target = new CanvasTarget(canvas, { width: 80, height: 24 });
const renderer = new Renderer(target);
const keyboard = new KeyboardManager();
let playerX = 40;
let playerY = 12;
// Handle input
keyboard.onKeyDown("ArrowUp", () => playerY--);
keyboard.onKeyDown("ArrowDown", () => playerY++);
keyboard.onKeyDown("ArrowLeft", () => playerX--);
keyboard.onKeyDown("ArrowRight", () => playerX++);
// Game loop
const game = new GameLoop(
(dt) => {
// Game logic here
},
() => {
renderer
.clear()
.box(0, 0, 80, 24, { style: "single" })
.setChar(playerX, playerY, "@", "yellow")
.render();
},
);
game.start();📚 Core API
Drawing Primitives
// Characters and text
renderer.setChar(x, y, "A", "red", "black");
renderer.drawText(x, y, "Hello", { fg: "green" });
// Shapes
renderer.box(x, y, width, height, { style: "double", fill: true });
renderer.border(x, y, width, height, { style: "rounded" });
renderer.rect(x, y, width, height, "█", "blue");
renderer.fill(x, y, width, height, " ");Text Alignment
renderer.centerText(y, "Centered!", { fg: "cyan" });
renderer.rightAlign(y, "Right aligned", { fg: "yellow" });
renderer.leftAlign(y, "Left aligned", { fg: "white" });
renderer.alignText(y, "Auto align", { align: "center" });UI Helpers
// Menus
renderer.menu(x, y, ["New Game", "Continue", "Options", "Quit"], {
selected: 0,
indicator: ">",
border: true,
title: "Main Menu",
});
// Progress bars
renderer.progressBar(x, y, 20, 0.75, {
style: "blocks",
showPercent: true,
border: true,
});
// Panels
renderer.panel(x, y, 40, 15, {
title: "Inventory",
content: ["Sword", "Shield", "Potion x3"],
style: "double",
});Layers
// Draw on different layers
renderer
.layer("background")
.fill(0, 0, 80, 24, ".", "gray")
.layer("entities")
.setChar(playerX, playerY, "@", "yellow")
.layer("ui")
.box(0, 0, 20, 5, { style: "single" })
.render(); // Composites all layers
// Control layer visibility
renderer.hideLayer("ui");
renderer.showLayer("ui");
renderer.layerOrder(["background", "entities", "ui"]);Camera System
// Follow a player entity
renderer.follow(playerX, playerY);
renderer.setChar(playerX, playerY, "@", "yellow");
renderer.render();
// Manual camera control
renderer.setCamera(100, 50);
renderer.moveCamera(10, 0);
renderer.resetCamera();
// Coordinate conversion
const screen = renderer.worldToScreen(worldX, worldY);
const world = renderer.screenToWorld(screenX, screenY);Animations
// Custom animations
renderer.animate({
duration: 1000,
easing: "easeInOut",
onUpdate: (progress) => {
const x = Math.floor(10 + progress * 50);
renderer.setChar(x, 10, "@", "cyan");
},
onComplete: () => console.log("Done!"),
});
// Flash effect
renderer.flash(10, 5, {
char: "*",
fg: "red",
count: 3,
duration: 500,
});
// Pulsing animation
renderer.pulse(20, 10, {
duration: 1000,
minIntensity: 0.3,
maxIntensity: 1.0,
loop: true,
fg: "brightYellow", // Named colors, hex, and RGB are supported
});
// Update animations in game loop
gameLoop.update(() => renderer.updateAnimations());Input Management
// Keyboard
const keyboard = new KeyboardManager();
keyboard.onKeyDown("w", () => player.moveUp());
// Key aliases (normalized): "Space"/"Spacebar" -> " ", "Esc" -> "Escape"
keyboard.onKeyDown("Space", () => player.attack());
// Simple direction input (WASD/Arrows)
const dir = keyboard.getDirection();
player.x += dir.x * speed;
player.y += dir.y * speed;
// Async key waiting
const answer = await keyboard.waitForKey(["y", "n"]);
// Gamepad/Controller
const gamepad = new GamepadManager();
gamepad.update(); // Call once per frame
const leftStick = gamepad.getLeftStick();
player.x += leftStick.x * 5;
player.y += leftStick.y * 5;
if (gamepad.justPressed(0)) {
// A button
player.jump();
}
// Vibration/rumble
await gamepad.vibrate(200, 0.5, 0.5);
// Pointer (Mouse/Touch/Pen)
const pointer = new PointerManager(canvas, 80, 24, 8, 16);
pointer.onClick(({ grid }) => {
console.log(`Clicked at grid: ${grid.x}, ${grid.y}`);
});
pointer.onHover(({ grid }) => {
// Handle hover
});Game Loop
const loop = new GameLoop(
(deltaTime) => {
// Update game state (60 FPS)
},
() => {
// Render current state
renderer.clear().render();
},
{ fps: 60 },
);
loop.start();
loop.pause();
loop.resume();
loop.stop();🎨 Color Support
// Named ANSI colors
renderer.setChar(x, y, "A", "red");
renderer.setChar(x, y, "B", "brightCyan");
// Hex colors
renderer.setChar(x, y, "C", "#ff00ff");
// RGB objects
renderer.setChar(x, y, "D", { r: 255, g: 128, b: 0 });
// Color utilities
import { brighten, darken, lerp, parseColor } from "@shaisrc/tty";
const brighter = brighten("red", 0.2);
const darker = darken("#ff0000", 0.3);
const blended = lerp("red", "blue", 0.5);🎯 Box Styles
// Built-in styles
renderer.box(x, y, w, h, { style: "single" }); // ┌─┐│└┘
renderer.box(x, y, w, h, { style: "double" }); // ╔═╗║╚╝
renderer.box(x, y, w, h, { style: "rounded" }); // ╭─╮│╰╯
renderer.box(x, y, w, h, { style: "heavy" }); // ┏━┓┃┗┛
renderer.box(x, y, w, h, { style: "ascii" }); // +-+|+-+
// Custom border characters
renderer.box(x, y, w, h, {
style: {
topLeft: "╒",
topRight: "╕",
bottomLeft: "╘",
bottomRight: "╛",
horizontal: "═",
vertical: "│",
},
});📖 Documentation
Full documentation available at tty.shaitern.dev
- Quick Start Guide - Step-by-step tutorial
- Core Types - Type definitions reference
- Renderer API - Complete API documentation
- Box Drawing - Box and border styles
- Color Utilities - Color manipulation
- Canvas Target - Browser rendering
- Layer System - Multi-layer rendering
- Camera System - Viewport and scrolling
- Keyboard Input - Keyboard handling
- Gamepad Input - Controller/gamepad support
- Pointer Input - Mouse/touch/pen handling
- Game Loop - Game loop utilities
- Menu Helper - Menu rendering
- Progress Bar - Progress indicators
- Panel Helper - Panel/window rendering
- Alignment - Text alignment helpers
💡 Examples
Check out live interactive demos or the examples/ folder for complete working examples:
- basic-game.ts - Simple player movement
- menu-demo.ts - Interactive menu system
- rpg-ui.ts - RPG-style interface
- snake-game.ts - Complete snake game
- space-invaders.ts - Space Invaders with keyboard & gamepad support
🏗️ Architecture
┌─────────────────┐
│ Your Game │
└────────┬────────┘
│
┌────────▼────────┐
│ Renderer │ ← Drawing API, Layers, Camera
└────────┬────────┘
│
┌────────▼────────┐
│ RenderTarget │ ← Abstract interface
└────────┬────────┘
│
┌────┴────┐
│ │
┌───▼──┐
│Canvas│ ← Implementation
└──────┘🎮 Real-World Use Cases
- Roguelike Games - Perfect for traditional ASCII roguelikes
- Game Prototyping - Quick iteration with simple graphics
- Retro Games - Snake, Tetris, Breakout
- Data Visualization - ASCII charts and graphs
- Dev Tools - Create text-based development tools
⚙️ Configuration
Renderer Options
const renderer = new Renderer(target, {
defaultFg: "white",
defaultBg: "black",
});
// Runtime configuration
renderer.setSafeMode(true); // Throw on out-of-bounds
renderer.setClipMode(true); // Clip instead of errorCanvas Target Options
const target = new CanvasTarget(canvas, {
width: 80,
height: 24,
charWidth: 12,
charHeight: 20,
fontFamily: "monospace",
fontSize: 16,
});🧪 Testing
# Run tests
npm test
# Watch mode
npm test -- --watch
# Coverage report
npm run test:coverage
# UI mode
npm run test:ui🛠️ Development
# Install dependencies
npm install
# Run type checking
npm run type-check
# Lint code
npm run lint
npm run lint:fix
# Format code
npm run format
npm run format:check
# Build
npm run build
# Generate API docs
npm run docs📝 Philosophy
This library follows the KISS (Keep It Simple, Stupid) principle:
- ✅ Simple, composable functions over complex frameworks
- ✅ Explicit over implicit behavior
- ✅ Reusable helpers over monolithic components
- ✅ Performance by design, not as an afterthought
- ✅ Developer experience is a first-class concern
🤝 Contributing
Contributions are welcome! Please follow the TDD approach:
- Write tests first (Red)
- Implement the feature (Green)
- Refactor for quality (Refactor)
Ensure >90% test coverage for new features.
📄 License
MIT © Shai
🙏 Acknowledgments
Inspired by classic ASCII games and modern game development libraries.
Made with ❤️ for the ASCII art and roguelike community