I just want some feedback to improve readability and structure for this implementation of Hangman. The goal was to write it in a purely functional way. However, coming from an imperative background, I found it difficult to organize functional code.
function Hangman(word, revealed = word.replace(/\w/g, '_'), errors = 0) {
const hasLost = errors > 5;
const hasWon = !hasLost && word === revealed;
const tryGuess = (letter, character, guess) =>
new RegExp(letter, "i").test(character) ? character : '_';
const revealCharacter = (letter, character, guess) => /_/.test(guess) ?
tryGuess(letter, character, guess) : guess;
const revealLetters = (letter, [head, ...rest], [guess, ...guessed]) => {
const character = / /.test(head) ? ' ' : revealCharacter(letter, head, guess);
return rest.length ?
character + revealLetters(letter, rest, guessed) : character;
}
const getErrorCount = (guess, word, count) =>
new RegExp(guess, "i").test(word) ? count : count + 1;
const getResult = () => hasLost ? 'You lose' : 'You win';
if (!hasWon && !hasLost) {
this.guess = letter => new Hangman(word,
revealLetters(letter, word, revealed),
getErrorCount(letter, word, errors));
console.log(revealed, errors);
} else {
console.log(getResult());
}
}
const game = new Hangman('Agile Development');
game.guess('e').guess('a').guess('t').guess('z');
UPDATE: Below is the revised edition of the code with the advised changes. The code base looks much cleaner and I decided to try to stick to only using primitives and functions. Having each function accomplish a single task increased the code size, but definitely improved the readability and maintainability of the code.
function Hangman(text, revealed = null, errors = 0) {
{ // constructor
if (revealed === null) {
return Hangman(text, revealMatch(/\s/g, text), 0);
}
if (isDone()) {
console.info(getGameStatus());
} else {
console.info(arguments);
return makeGuess;
}
}
function createMatchedCharacterList(matcher) {
return (revealed, actual, index) => {
if (matcher.test(actual)) {
revealed[index] = actual;
}
return revealed;
}
}
function revealMatch(matcher, text) {
return text.split('').reduce(createMatchedCharacterList(matcher), [])
}
function revealCharacter(guess) {
return Object.assign([], revealed, revealMatch(new RegExp(guess, 'i'), text));
}
function makeGuess(guess) {
return (hasCharacter(guess) && !hasGuessed(guess)) ?
Hangman(text, revealCharacter(guess), errors) :
Hangman(text, revealed, errors + 1);
}
function hasCharacter(character) {
return text.search(character) > -1;
}
function hasGuessed(guess) {
return revealed.join('').search(guess) > -1;
}
function hasLost() {
return errors > 5;
}
function hasWon() {
return text === revealed.join('');
}
function isDone() {
return hasLost() || hasWon();
}
function getGameStatus() {
return isDone() ? hasLost() ? 'You lose' : 'You win' : 'Incomplete';
}
}
console.log(Hangman('eatz')('e')('a')('t')('z'));