namastejs
Version:
A spiritual greeting from your JavaScript code. Because every function deserves a 'Namaste š'
150 lines (122 loc) ⢠3.68 kB
JavaScript
const readline = require("readline");
const {
initScreen,
updateHeader,
renderBody,
renderInput,
parkCursor,
BODY_HEIGHT,
BODY_WIDTH,
} = require("./renderer");
const { initInput } = require("./input");
const { WORDS, WORD_COLORS } = require("../data/words");
const { style } = require("../../../theme");
// š Hide cursor during game
process.stdout.write("\x1B[?25l");
let words = [];
let score = 0;
let dropped = 0;
let speed = 1000; // slow start
let backspaceDisabled = false;
let gameRunning = true;
let isPaused = false;
// āāā HELPERS āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
function randomWord() {
return WORDS[Math.floor(Math.random() * WORDS.length)];
}
function randomColor() {
return WORD_COLORS[Math.floor(Math.random() * WORD_COLORS.length)];
}
function spawnWord() {
const text = randomWord();
const x = Math.floor(Math.random() * (BODY_WIDTH - text.length - 2));
words.push({ text, x, y: 0, color: randomColor() });
}
function restoreTerminal() {
process.stdout.write("\x1B[?25h"); // show cursor back
process.stdin.setRawMode(false);
process.stdin.pause();
}
function gameOver() {
gameRunning = false;
restoreTerminal();
process.stdout.write("\x1Bc");
console.log(style.red("\nš GAME OVER š"));
console.log(style.gold(`š Final Score: ${score}`));
console.log(style.cyan("š Thanks for playing Typing Rain š\n"));
process.exit();
}
function togglePause() {
isPaused = !isPaused;
updateHeader({ score, lives: Math.max(0, 3 - dropped), dropped, isPaused });
}
// āāā MAIN GAME LOOP (SMOOTH & DYNAMIC) āāāāāā
function gameLoop() {
if (!gameRunning || isPaused) {
setTimeout(gameLoop, speed);
return;
}
// move words down
words.forEach((w) => w.y++);
// drop detection
words = words.filter((w) => {
if (w.y >= BODY_HEIGHT) {
dropped++;
if (dropped >= 3) gameOver();
return false;
}
return true;
});
renderBody(words);
updateHeader({
score,
lives: Math.max(0, 3 - dropped),
dropped,
isPaused,
});
parkCursor();
setTimeout(gameLoop, speed);
}
// āāā INPUT HANDLING (EXTERNALIZED) āāāāāāāāāā
initInput({
onSubmit: (typedWord) => {
const index = words.findIndex((w) => w.text === typedWord);
if (index !== -1) {
words.splice(index, 1);
score++;
// š„ Level-up rule
if (score === 10 && !backspaceDisabled) {
backspaceDisabled = true;
speed -= 100;
readline.cursorTo(process.stdout, 2, BODY_HEIGHT + 6);
console.log("ā ļø Level Up! Backspace disabled");
parkCursor();
}
// increase difficulty
if (score === 15) speed -= 100;
if (score === 25) speed -= 100;
if (score === 30) speed -= 100;
if (score === 35) speed -= 100;
if (score === 40) speed -= 100;
if (score === 45) speed -= 100;
}
},
onExit: gameOver,
onInputChange: (input) => {
renderInput(input);
parkCursor();
},
onPauseToggle: togglePause,
isBackspaceDisabled: () => backspaceDisabled,
});
// āāā START GAME āāāāāāāāāāāāāāāāāāāāāāāāāāāāā
initScreen();
updateHeader({ score, lives: 3, dropped, isPaused });
renderInput("");
parkCursor();
// spawn words independently
spawnInterval = setInterval(() => {
if (!isPaused) spawnWord();
}, 2000);
// start falling loop
gameLoop();