Skip to content

Instantly share code, notes, and snippets.

@gerred
Created December 20, 2024 00:25
Show Gist options
  • Select an option

  • Save gerred/ae5074a928f5ef29265dc468093b0a64 to your computer and use it in GitHub Desktop.

Select an option

Save gerred/ae5074a928f5ef29265dc468093b0a64 to your computer and use it in GitHub Desktop.

Revisions

  1. gerred created this gist Dec 20, 2024.
    173 changes: 173 additions & 0 deletions doom.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,173 @@
    import * as readline from 'node:readline/promises';
    import { stdin as input, stdout as output } from 'node:process';

    // --- Game Constants ---
    const MAP_WIDTH = 32;
    const MAP_HEIGHT = 32;
    const SCREEN_WIDTH = 80;
    const SCREEN_HEIGHT = 30;
    const FOV = Math.PI / 3; // Field of View
    const MAX_DEPTH = 20;

    // --- Game World ---
    const map = [
    "################################",
    "#..............#...............#",
    "#..............#...............#",
    "#..............#...............#",
    "#.......########.......########.#",
    "#.......#..............#.......#",
    "#.......#..............#.......#",
    "#####...#.......##.......#...####",
    "#.......#.......##.......#.......#",
    "#.......#..............#.......#",
    "#.......########.......########.#",
    "#..............#...............#",
    "#..............#...............#",
    "#..............#...............#",
    "################################",
    "#..............#...............#",
    "#..............#...............#",
    "#..............#...............#",
    "#.......########.......########.#",
    "#.......#..............#.......#",
    "#.......#..............#.......#",
    "#####...#.......##.......#...####",
    "#.......#.......##.......#.......#",
    "#.......#..............#.......#",
    "#.......########.......########.#",
    "#..............#...............#",
    "#..............#...............#",
    "#..............#...............#",
    "################################",
    "################################",
    "################################",
    "################################",
    ];

    // --- Game State ---
    let playerX = 2;
    let playerY = 2;
    let playerAngle = 0;

    // --- Helper Functions ---
    function getMap(x: number, y: number): string {
    if (x < 0 || x >= MAP_WIDTH || y < 0 || y >= MAP_HEIGHT) {
    return "#"; // Treat out of bounds as a wall
    }
    return map[Math.floor(y)][Math.floor(x)];
    }

    function distance(x1: number, y1: number, x2: number, y2: number): number {
    return Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
    }

    // --- Ray Casting ---
    function castRay(angle: number): number {
    let rayX = playerX;
    let rayY = playerY;
    const sinA = Math.sin(angle);
    const cosA = Math.cos(angle);

    for (let depth = 0; depth < MAX_DEPTH; depth++) {
    rayX += cosA * 0.1;
    rayY += sinA * 0.1;

    if (getMap(rayX, rayY) === '#') {
    return depth * 0.1;
    }
    }
    return MAX_DEPTH;
    }

    // --- Rendering ---
    function renderScreen(): string {
    let screenBuffer = "";

    for (let y = 0; y < SCREEN_HEIGHT; y++) {
    for (let x = 0; x < SCREEN_WIDTH; x++) {
    const rayAngle = playerAngle - FOV / 2 + (x / SCREEN_WIDTH) * FOV;
    const dist = castRay(rayAngle);

    // Ceiling and Floor
    if (y < SCREEN_HEIGHT / 2) {
    screenBuffer += ' '; // Ceiling
    } else {
    screenBuffer += '.'; // Floor
    }

    // Walls
    const wallHeight = Math.floor(SCREEN_HEIGHT / dist);
    const wallTop = Math.floor((SCREEN_HEIGHT - wallHeight) / 2);

    if (y >= wallTop && y < wallTop + wallHeight) {
    if (dist < 5) {
    screenBuffer = screenBuffer.slice(0, -1) + '#'; // Closer wall
    } else if (dist < 10) {
    screenBuffer = screenBuffer.slice(0, -1) + '=';
    } else {
    screenBuffer = screenBuffer.slice(0, -1) + '-'; // Farther wall
    }
    }
    }
    screenBuffer += '\n';
    }
    return screenBuffer;
    }

    // --- Input Handling ---
    async function handleInput(rl: readline.Interface): Promise<void> {
    const answer = await rl.question('> ');
    switch (answer.toLowerCase()) {
    case 'w':
    const moveX = Math.cos(playerAngle) * 0.5;
    const moveY = Math.sin(playerAngle) * 0.5;
    if (getMap(playerX + moveX, playerY) !== '#') {
    playerX += moveX;
    }
    if (getMap(playerX, playerY + moveY) !== '#') {
    playerY += moveY;
    }
    break;
    case 's':
    const moveBackX = Math.cos(playerAngle) * 0.5;
    const moveBackY = Math.sin(playerAngle) * 0.5;
    if (getMap(playerX - moveBackX, playerY) !== '#') {
    playerX -= moveBackX;
    }
    if (getMap(playerX, playerY - moveBackY) !== '#') {
    playerY -= moveBackY;
    }
    break;
    case 'a':
    playerAngle -= 0.1;
    break;
    case 'd':
    playerAngle += 0.1;
    break;
    case 'q':
    console.log('Exiting...');
    process.exit(0);
    default:
    console.log('Invalid input.');
    }
    }

    // --- Game Loop ---
    async function gameLoop(rl: readline.Interface): Promise<void> {
    console.clear();
    console.log(renderScreen());
    await handleInput(rl);
    gameLoop(rl);
    }

    // --- Main ---
    async function main(): Promise<void> {
    const rl = readline.createInterface({ input, output });
    console.log('Welcome to the Doom-like FPS!');
    console.log('Controls: W (forward), S (backward), A (turn left), D (turn right), Q (quit)');
    gameLoop(rl);
    }

    main();

    1 change: 1 addition & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    Write a Doom style FPS in TS