This document describes the adapter system in Triangle.js, which provides a standardized interface for integrating external engines and bots with the Triangle.js game engine. Adapters abstract away the communication layer, allowing developers to focus on game logic rather than protocol implementation.
Transparency note: this document was AI-generated based on the Triangle.js adapter source code.
The adapter system consists of:
Adapter<T>
provides the core interface and helper methodsAdapterIO
The Adapter<T>
abstract class defines the core interface that all adapters must implement:
import { adapters } from "@haelp/teto/utils";
abstract class Adapter<T extends adapters.Types.CustomMessageData> {
// Must be implemented by subclasses
abstract initialize(): Promise<adapters.Types.Incoming.Info<T>>;
abstract config(engine: Engine, data?: T["config"]): void;
abstract update(engine: Engine, data?: T["state"]): void;
abstract play(
engine: Engine,
data?: T["play"]
): Promise<adapters.Types.Incoming.Move<T>>;
abstract stop(): void;
// Helper methods provided by base class
protected configFromEngine(
engine: Engine
): Omit<adapters.Types.Outgoing.Config<T>, "type" | "data">;
protected stateFromEngine(
engine: Engine
): Omit<adapters.Types.Outgoing.State<T>, "type" | "data">;
protected playFromEngine(
engine: Engine
): Omit<adapters.Types.Outgoing.Play<T>, "type" | "data">;
}
The base class provides three helper methods that extract relevant data from the Triangle.js Engine
:
configFromEngine(engine)
- Extracts game configuration:
boardWidth
, boardHeight
)kicks
, spins
, comboTable
)b2bCharing
, b2bChargeAt
, etc.)garbageMultiplier
, garbageCap
, etc.)pcB2b
, pcGarbage
)stateFromEngine(engine)
- Extracts current game state:
playFromEngine(engine)
- Extracts play-specific data:
The AdapterIO
class communicates with external processes via standard input/output using JSON messages. It's available through the adapters.IO
export.
import { adapters } from "@haelp/teto/utils";
const adapter = new adapters.IO({
path: "./my-bot", // Required: path to executable
name: "MyBot", // Optional: display name (default: "AdapterIO")
verbose: true, // Optional: enable debug logging (default: false)
env: { RUST_BACKTRACE: "1" }, // Optional: environment variables
args: ["--mode", "fast"] // Optional: command line arguments
});
// Initialize and use
const info = await adapter.initialize();
console.log(`Connected to ${info.name} v${info.version} by ${info.author}`);
// Configure the external process
adapter.config(engine);
// Send state updates
adapter.update(engine);
// Request moves (used during `tick`)
const move = await adapter.play(engine);
console.log("Received keys:", move.keys);
// Clean shutdown
adapter.stop();
interface AdapterIOConfig {
name: string; // Adapter name for logging
verbose: boolean; // Enable detailed logging
path: string; // Path to executable (required)
env: NodeJS.ProcessEnv; // Environment variables
args: string[]; // Command line arguments
}
When verbose: true
, AdapterIO logs:
Extend the CustomMessageData
interface to add adapter-specific data to messages:
interface MyBotData extends adapters.Types.CustomMessageData {
info: {
capabilities: string[];
supportedModes: string[];
};
move: {
confidence: number;
evaluationTime: number;
nodesSearched: number;
};
config: {
difficulty: "easy" | "medium" | "hard";
searchDepth: number;
};
state: {
boardEvaluation: number;
threatLevel: number;
};
play: {
timeLimit: number;
allowHold: boolean;
};
}
// Use with adapter
const adapter = new adapters.IO<MyBotData>({
path: "./my-advanced-bot"
});
// Send config with custom data
adapter.config(engine, {
difficulty: "hard",
searchDepth: 7
});
// Send state with custom data
adapter.update(engine, {
boardEvaluation: 0.75,
threatLevel: 2
});
// Request move with custom data
const move = await adapter.play(engine, {
timeLimit: 1000,
allowHold: true
});
// Access custom move data
console.log(`Move confidence: ${move.data.confidence}%`);
console.log(`Nodes searched: ${move.data.nodesSearched}`);