UNPKG

math-game-cli

Version:

Some math questions to solve in the cli

273 lines (270 loc) 8.29 kB
#!/usr/bin/env node 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]);