1.0.1 • Published 11 months ago

termengine.js v1.0.1

Weekly downloads
-
License
-
Repository
github
Last release
11 months ago

Terminal Game Engine

Terminal Game Engine is an optimized, easy-to-use game engine for developing terminal-based games. This engine allows you to quickly create simple 2D games and develop high-performance terminal applications using the terminal-kit package.

Features

  • 2D terminal graphics support
  • Game loop and rendering processes
  • Event handler for user input
  • Easy collision detection
  • Pathfinding algorithms
  • CLI tools for project management

Installation

To start your project, simply install the termengine.js package.

npm install termengine.js

Usage

CLI Commands

Initialize a Project

To start a new game project:

npx termengine.js init my-game
cd my-game
npm install

Compile the Project

To compile the project and generate optimized code:

npx termengine.js compile

Build the Project

To build the project without compiling:

npx termengine.js build

Run the Project

To run the project:

npx termengine.js run

Game Engine Usage

To develop a game using the game engine, follow these steps.

index.js

const { terminal } = require('terminal-kit');
const ScreenBuffer = require('terminal-kit').ScreenBuffer;
const { EventHandler } = require('termengine.js');

const screen = new ScreenBuffer({ dst: terminal, width: terminal.width, height: terminal.height });
const eventHandler = new EventHandler();

let gameState = {
    player: {
        x: 10,
        y: 10,
        char: '@',
        color: 'green'
    },
    enemies: []
};

function draw() {
    screen.clear();
    screen.put({ x: gameState.player.x, y: gameState.player.y, attr: { color: gameState.player.color } }, gameState.player.char);
    screen.draw();
}

function update() {
    // Update game state
    // For example, move enemies, check collisions, etc.
}

function gameLoop() {
    setInterval(() => {
        update();
        draw();
    }, 1000 / 30); // 30 FPS
}

function handleInput() {
    terminal.grabInput();
    terminal.on('key', (name) => {
        if (name === 'CTRL_C') {
            process.exit();
        } else if (name === 'UP') {
            gameState.player.y--;
        } else if (name === 'DOWN') {
            gameState.player.y++;
        } else if (name === 'LEFT') {
            gameState.player.x--;
        } else if (name === 'RIGHT') {
            gameState.player.x++;
        }
    });
}

function startGame() {
    draw();
    handleInput();
    gameLoop();
}

startGame();

Game Engine Structure

src/engine/index.js

The entry point and main game loop of the game engine:

class GameEngine {
    constructor() {
        this.isRunning = false;
    }

    start() {
        this.isRunning = true;
        this.gameLoop();
    }

    stop() {
        this.isRunning = false;
    }

    gameLoop() {
        if (!this.isRunning) return;

        this.update();
        this.render();

        setTimeout(() => this.gameLoop(), 1000 / 30); // 30 FPS
    }

    update() {
        // Update game state
    }

    render() {
        // Render game screen
    }
}

module.exports = GameEngine;

src/engine/render.js

Module managing render operations:

const { terminal } = require('terminal-kit');
const ScreenBuffer = require('terminal-kit').ScreenBuffer;

class Renderer {
    constructor() {
        this.screenBuffer = new ScreenBuffer({ dst: terminal, width: terminal.width, height: terminal.height });
    }

    draw(gameState) {
        this.screenBuffer.clear();
        gameState.entities.forEach(entity => {
            this.screenBuffer.put({ x: entity.x, y: entity.y, attr: { color: entity.color } }, entity.char);
        });
        this.screenBuffer.draw();
    }
}

module.exports = Renderer;

src/engine/input.js

Module managing user inputs:

const { terminal } = require('terminal-kit');

class InputHandler {
    constructor() {
        this.keys = {};
        terminal.grabInput();

        terminal.on('key', (name) => {
            this.keys[name] = true;
            if (name === 'CTRL_C') {
                process.exit();
            }
        });
    }

    isKeyPressed(key) {
        return !!this.keys[key];
    }

    clear() {
        this.keys = {};
    }
}

module.exports = InputHandler;

src/engine/gameState.js

Module managing game state:

class GameState {
    constructor() {
        this.entities = [];
    }

    addEntity(entity) {
        this.entities.push(entity);
    }

    removeEntity(entity) {
        this.entities = this.entities.filter(e => e !== entity);
    }
}

module.exports = GameState;

Utility Functions

src/utils/eventHandler.js

Using the event handler:

class EventHandler {
    constructor() {
        this.events = {};
    }

    on(event, listener) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(listener);
    }

    off(event, listener) {
        if (!this.events[event]) return;

        this.events[event] = this.events[event].filter(l => l !== listener);
    }

    emit(event, ...args) {
        if (!this.events[event]) return;

        this.events[event].forEach(listener => listener(...args));
    }
}

module.exports = { EventHandler };

src/utils/collision.js

Collision detection:

function checkCollision(obj1, obj2) {
    return obj1.x < obj2.x + obj2.width &&
           obj1.x + obj1.width > obj2.x &&
           obj1.y < obj2.y + obj2.height &&
           obj1.y + obj1.height > obj2.y;
}

module.exports = { checkCollision };

src/utils/pathfinding.js

Pathfinding algorithm:

class Node {
    constructor(x, y, parent = null) {
        this.x = x;
        this.y = y;
        this.parent = parent;
        this.g = 0;
        this.h = 0;
        this.f = 0;
    }
}

function aStar(grid, start, end) {
    let openList = [];
    let closedList = [];
    let startNode = new Node(start.x, start.y);
    let endNode = new Node(end.x, end.y);

    openList.push(startNode);

    while (openList.length > 0) {
        let currentNode = openList.reduce((prev, curr) => (prev.f < curr.f ? prev : curr));
        if (currentNode.x === endNode.x && currentNode.y === endNode.y) {
            let path = [];
            let current = currentNode;
            while (current) {
                path.push({ x: current.x, y: current.y });
                current = current.parent;
            }
            return path.reverse();
        }

        openList = openList.filter(node => node !== currentNode);
        closedList.push(currentNode);

        let neighbors = getNeighbors(grid, currentNode);
        for (let neighbor of neighbors) {
            if (closedList.find(node => node.x === neighbor.x && node.y === neighbor.y)) {
                continue;
            }

            neighbor.g = currentNode.g + 1;
            neighbor.h = Math.abs(neighbor.x - endNode.x) + Math.abs(neighbor.y - endNode.y);
            neighbor.f = neighbor.g + neighbor.h;

            if (openList.find(node => node.x === neighbor.x && node.y === neighbor.y && node.g <= neighbor.g)) {
                continue;
            }

            openList.push(neighbor);
        }
    }

    return [];
}

function getNeighbors(grid, node) {
    let neighbors = [];
    let directions = [
        { x: -1, y: 0 },
        { x: 1, y: 0 },
        { x: 0, y: -1 },
        { x: 0, y: 1 },
    ];

    for (let dir of directions) {
        let x = node.x + dir.x;
        let y = node.y + dir.y;
        if (grid[y] && grid[y][x] !== undefined && grid[y][x] !== 1) {
            neighbors.push(new Node(x, y, node));
        }
    }

    return neighbors;
}

module.exports = { aStar };

Changelog

Version 1.0.1

  • Improved package.json with additional keywords for better discoverability
  • Added delay functions using delay package
  • Optimized existing code for better performance
  • Fixed missing or non-functional files to ensure full functionality
  • Enhanced flexibility and user control within the game engine

Updated package.json

Keywords changed, version updated.

Added delay functions

src/utils/delay.js

const delay = require('delay');

async function wait(ms) {
    await delay(ms);
}

module.exports = { wait };

Integrated delay functions into the engine

src/engine/index.js

const { wait } = require('../utils/delay');

class GameEngine {
    constructor() {
        this.isRunning = false;
    }

    async start() {
        this.isRunning = true;
        await this.gameLoop();
    }

    stop() {
        this.isRunning = false;
    }

    async gameLoop() {
        while (this.isRunning) {
            this.update();
            this.render();
            await wait(1000 / 30); // 30 FPS
        }
    }

    update() {
        // Update game state
    }

    render() {
        // Render game screen
    }
}

module.exports = GameEngine;

Enhanced flexibility and user control

You can add more utility functions and modules to provide additional features like custom events, improved rendering, more sophisticated input handling, etc. For example:

src/utils/customEvent.js

class CustomEvent {
    constructor(type, detail = {}) {
        this.type = type;
        this.detail = detail;
        this.timeStamp = Date.now();
    }
}

module.exports = { CustomEvent };

Thanks for using termengine.js