math-game-cli
Version:
Some math questions to solve in the cli
273 lines (270 loc) • 8.29 kB
JavaScript
import terminalSize from 'terminal-size';
import { drawEqual, drawPlus, drawDivide, drawMultiply, drawMinus, drawSuccess, drawError, drawZero, drawOne, drawTwo, drawThree, drawFour, drawFive, drawSix, drawSeven, drawEight, drawNine, clearTerminal } from 'math-figures';
import ansiEscapes from 'ansi-escapes';
import chalk from 'chalk';
import * as R from 'remeda';
import readline from 'readline';
import delay from 'delay';
const multiplyOperationsSuite = R.map(
[
[8, 5, 40],
[6, 8, 48],
[5, 9, 45],
[4, 7, 28],
[2, 4, 8],
[3, 6, 18],
[6, 9, 54],
[8, 8, 64]
],
(item) => {
return {
first: item[0],
second: item[1],
operation: "multiply",
result: item[2]
};
}
);
const addOperationsSuite = R.map(
[
[17, 15, 32],
[11, 8, 19],
[18, 23, 41],
[13, 24, 37],
[26, 38, 64]
],
(item) => {
return {
first: item[0],
second: item[1],
operation: "add",
result: item[2]
};
}
);
const suites = [
multiplyOperationsSuite,
addOperationsSuite
];
readline.emitKeypressEvents(process.stdin);
if (process.stdin.isTTY) process.stdin.setRawMode(true);
const getShuffledSuites = (suites2) => R.take(R.shuffle(R.flat(suites2)), 15);
let shuffledSuites = getShuffledSuites(suites);
let score = 0;
const leftPadding = 2;
let userStr = "";
let lastOccupied;
const updateScore = (newValue) => {
process.stdout.write(ansiEscapes.cursorSavePosition);
process.stdout.write(ansiEscapes.cursorTo(0, 0));
process.stdout.write(ansiEscapes.cursorRestorePosition);
};
let roundIndex = 0;
const showResults = () => {
process.stdout.write(ansiEscapes.cursorMove(1, 1));
process.stdout.write(`Score: ${score}`);
process.stdout.write(ansiEscapes.cursorNextLine);
process.stdout.write(ansiEscapes.cursorNextLine);
process.stdout.write(ansiEscapes.cursorForward(1));
process.stdout.write(`${chalk.bold.cyan("n")} - Start new round`);
process.stdout.write(ansiEscapes.cursorForward(3));
process.stdout.write(
`${chalk.bold.cyan("n")} or ${chalk.bold.cyan("CTRL")}+${chalk.cyan("C")}- Quit`
);
};
const handleMenu = () => {
process.stdin.on("keypress", menuListener);
};
const menuListener = (ch, key) => {
if (key && key.name === "y") {
shuffledSuites = getShuffledSuites(suites);
startGame();
runRound(shuffledSuites[0]);
} else if (key && key.name === "q") {
process.stdin.pause();
} else if (key && key.ctrl && key.name === "c") {
process.stdin.pause();
}
};
const userAnswerListener = async (ch, key) => {
if (["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].includes(key.name)) {
userStr += key.name;
const occupied = drawDigit(Number(key.name));
lastOccupied = occupied;
process.stdout.write(ansiEscapes.cursorMove(occupied[0] + 2, 0));
} else if (key && key.name === "return") {
if (shuffledSuites[roundIndex].result === Number(userStr)) {
score++;
process.stdout.write(ansiEscapes.cursorMove(2, 0));
drawSuccess();
} else {
process.stdout.write(ansiEscapes.cursorMove(2, 0));
drawError();
}
roundIndex++;
await delay(1400);
if (roundIndex >= shuffledSuites.length) {
stopGame();
} else {
runRound(shuffledSuites[roundIndex]);
}
} else if (key && key.name === "backspace") {
R.times(lastOccupied[1] + 1, (i) => {
process.stdout.write(ansiEscapes.cursorMove(-lastOccupied[0] - 2, 0));
R.times(lastOccupied[0] + 2, () => {
process.stdout.write(` `);
});
process.stdout.write(ansiEscapes.cursorMove(0, 1));
});
process.stdout.write(ansiEscapes.cursorMove(-1, -lastOccupied[1] - 1));
process.stdout.write(ansiEscapes.cursorMove(-lastOccupied[0] - 1, 0));
userStr = userStr.slice(0, userStr.length - 1);
} else if (key.name === "y") {
roundIndex++;
score++;
if (roundIndex >= suites.length) {
stopGame();
} else {
runRound(shuffledSuites[roundIndex]);
}
} else if (key.name === "n") {
roundIndex++;
if (roundIndex >= suites.length) {
stopGame();
} else {
runRound(shuffledSuites[roundIndex]);
}
} else if (key && key.ctrl && key.name == "c") {
process.stdout.write(ansiEscapes.clearTerminal);
process.stdin.pause();
}
};
const handleUserAnswer = () => {
process.stdin.on("keypress", userAnswerListener);
};
const startGame = () => {
process.stdin.removeListener("keypress", menuListener);
handleUserAnswer();
roundIndex = 0;
};
const stopGame = () => {
clearTerminal();
process.stdin.removeListener("keypress", userAnswerListener);
showResults();
handleMenu();
};
const runRound = (expression) => {
process.stdout.write(ansiEscapes.clearTerminal);
let topOffset = 1;
updateScore();
userStr = "";
process.stdout.write(ansiEscapes.cursorMove(0, topOffset));
const occupyingList = [];
let occupied = drawNumber(expression.first);
occupyingList.push(occupied);
process.stdout.write(ansiEscapes.cursorMove(occupied[0] + leftPadding, 0));
process.stdout.write(ansiEscapes.cursorSavePosition);
let operOccupied = drawOper(expression.operation);
occupyingList.push(operOccupied);
process.stdout.write(ansiEscapes.cursorRestorePosition);
process.stdout.write(
ansiEscapes.cursorMove(operOccupied[0] + leftPadding, 0)
);
let occupied2 = drawNumber(expression.second);
occupyingList.push(occupied2);
process.stdout.write(ansiEscapes.cursorMove(occupied2[0] + leftPadding, 0));
process.stdout.write(ansiEscapes.cursorSavePosition);
const equalOccupied = drawEqual();
occupyingList.push(equalOccupied);
process.stdout.write(ansiEscapes.cursorRestorePosition);
process.stdout.write(
ansiEscapes.cursorMove(equalOccupied[0] + leftPadding, 0)
);
let horizontalTaken = 0;
horizontalTaken = R.reduce(
occupyingList,
(prev, curr) => {
return prev + curr[0];
},
0
);
process.stdout.write(ansiEscapes.cursorSavePosition);
process.stdout.write(ansiEscapes.cursorNextLine);
const { columns, rows } = terminalSize();
process.stdout.write(
ansiEscapes.cursorMove(horizontalTaken > rows - 3 ? 4 : 3, 8)
);
process.stdout.write(`${chalk.bold("SCORE")}: ${chalk.cyan(score)}`);
process.stdout.write(ansiEscapes.cursorForward(3));
process.stdout.write(`${chalk.bold("ENTER")} - submit`);
process.stdout.write(ansiEscapes.cursorRestorePosition);
};
const drawDigit = (number) => {
let result = [0, 0];
process.stdout.write(ansiEscapes.cursorSavePosition);
if (number === 0) {
result = drawZero();
} else if (number === 1) {
result = drawOne();
} else if (number === 2) {
result = drawTwo();
} else if (number === 3) {
result = drawThree();
} else if (number === 4) {
result = drawFour();
} else if (number === 5) {
result = drawFive();
} else if (number === 6) {
result = drawSix();
} else if (number === 7) {
result = drawSeven();
} else if (number === 8) {
result = drawEight();
} else if (number === 9) {
result = drawNine();
}
process.stdout.write(ansiEscapes.cursorRestorePosition);
return result;
};
const drawNumber = (value) => {
if (value.toString().length === 1) {
return drawDigit(value);
} else {
const digitSymbols = value.toString().split("");
const accum = [];
R.map(digitSymbols, (digitStr, index) => {
const currOccupying = drawDigit(Number(digitStr));
if (index < digitSymbols.length - 1) {
process.stdout.write(ansiEscapes.cursorMove(currOccupying[0], 0));
}
accum.push(currOccupying);
if (index < digitSymbols.length - 1) {
process.stdout.write(ansiEscapes.cursorMove(leftPadding, 0));
accum.push([leftPadding, 0]);
}
});
R.reduce(
accum,
(prev, curr) => {
return [prev[0] + curr[0], prev[1] + curr[1]];
},
[0, 0]
);
return accum[accum.length - 1];
}
};
const drawOper = (oper) => {
if (oper === "add") {
return drawPlus();
} else if (oper === "divide") {
return drawDivide();
} else if (oper === "multiply") {
return drawMultiply();
} else if (oper === "subtract") {
return drawMinus();
}
return [0, 0];
};
startGame();
runRound(shuffledSuites[0]);