Created
December 20, 2024 00:25
-
-
Save gerred/ae5074a928f5ef29265dc468093b0a64 to your computer and use it in GitHub Desktop.
gemini-2.0-flash-thinking-experimental doom ascii raytracer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Write a Doom style FPS in TS |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment