patternjavascriptMinor
Hangman in stateless JavaScript
Viewed 0 times
javascriptstatelesshangman
Problem
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.
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.in
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.in
Solution
Such a great question,
I found an interesting guide: https://drboolean.gitbooks.io/mostly-adequate-guide/ which states that
Okay, 2nd version with far less
hasLostandhasWonshould be functions
- You should write functions with the keyword
function, otherwise you get a mess of anonymous functions in stack-traces. The fat arrow syntax was only meant for inline functions.
headis not a great variable name, I don't know what it is supposed to be from the name
- On the whole, the challenge in pure fp is to never assign, which you do a few times, read this for good measure: https://stackoverflow.com/a/33114654/7602
- I am not sure I like that the
guessfunction creates a newHangmanobject every single time, there has to be a cleaner approach.
I found an interesting guide: https://drboolean.gitbooks.io/mostly-adequate-guide/ which states that
this should be avoided.. Meanwhile, this is my this littered approach. It follows all my recommendations, and shows where I feel fat arrows have a place. I will try to rewrite again once I finish the guide ;)function Hangman(word, copy, mistakes){
//Initialize again, no assignments here ;)
if(!copy){
return Hangman(word, '_'.repeat(word.length), 0 );
}
//Non Exposed functions requiring context
const won = (() => {
return function won(){
return word == copy;
};
})();
const lost = (() => {
return function lost(){
return mistakes > 5;
}
})();
const updateMistakes = (() => {
return function updateMistakes(oldCopy , newCopy){
return mistakes += oldCopy == newCopy ? 1 : 0;
}
})();
const applyGuess = (() => {
return function applyGuess(letter){
return copy = word.split('').map( (c,i) => letter == c ? c : copy[i] ).join('');
}
})();
//Exposed Functions
function guess(){
return function guess(letter){
if(!done()){
updateMistakes( copy , applyGuess(letter) );
}
this.display();
return this;
};
}
function display(){
return function display(){
console.log( won()? 'You won' : lost() ? 'You lost' : copy + ' ' + mistakes );
};
}
//Return object
return {
guess : guess(),
display: display(),
}
//Pure function?
function done(){
return won() || lost();
}
}
const game = Hangman('Agile Development');
game.guess('e').guess('a').guess('t').guess('z');Okay, 2nd version with far less
this, many more clean functions:function Hangman(word, copy, mistakes){
//Initialize again, no assignments here ;)
if(!copy){
return Hangman(word, '_'.repeat(word.length), 0 );
}
//Exposed Functions
function guess(){
return function guess(letter){
if( !done( won(word,copy), lost(mistakes) ) ){
//An assignment, it was unavoidable I think
mistakes = updateMistakes( mistakes, copy , copy = applyGuess(word,copy,letter) );
}
display(word, copy, mistakes, letter );
return this;
};
}
//Return object, kind pointless now that there is only 1 function :/
return {
guess : guess()
}
//Not entirely pure function, not cacheable
function display(word, copy, mistakes, letter){
console.log( won(word,copy) ? 'You won' :
lost(mistakes) ? 'You lost' :
copy + ' ' + mistakes + ' You tried (' + letter + ')' );
}
//Pure functions!!
function applyGuess(word, copy, letter){
return word.split('').map( (c,i) => letter == c ? c : copy[i] ).join('');
}
function updateMistakes(mistakes, oldCopy , newCopy){
return mistakes + (oldCopy == newCopy ? 1 : 0 );
}
function done(won, lost){
return won || lost;
}
function won(word, copy){
return word == copy;
}
function lost(mistakes){
return mistakes > 5;
}
}
const game = Hangman('Agile Development');
game.guess('e').guess('a').guess('t').guess('z');Code Snippets
function Hangman(word, copy, mistakes){
//Initialize again, no assignments here ;)
if(!copy){
return Hangman(word, '_'.repeat(word.length), 0 );
}
//Non Exposed functions requiring context
const won = (() => {
return function won(){
return word == copy;
};
})();
const lost = (() => {
return function lost(){
return mistakes > 5;
}
})();
const updateMistakes = (() => {
return function updateMistakes(oldCopy , newCopy){
return mistakes += oldCopy == newCopy ? 1 : 0;
}
})();
const applyGuess = (() => {
return function applyGuess(letter){
return copy = word.split('').map( (c,i) => letter == c ? c : copy[i] ).join('');
}
})();
//Exposed Functions
function guess(){
return function guess(letter){
if(!done()){
updateMistakes( copy , applyGuess(letter) );
}
this.display();
return this;
};
}
function display(){
return function display(){
console.log( won()? 'You won' : lost() ? 'You lost' : copy + ' ' + mistakes );
};
}
//Return object
return {
guess : guess(),
display: display(),
}
//Pure function?
function done(){
return won() || lost();
}
}
const game = Hangman('Agile Development');
game.guess('e').guess('a').guess('t').guess('z');function Hangman(word, copy, mistakes){
//Initialize again, no assignments here ;)
if(!copy){
return Hangman(word, '_'.repeat(word.length), 0 );
}
//Exposed Functions
function guess(){
return function guess(letter){
if( !done( won(word,copy), lost(mistakes) ) ){
//An assignment, it was unavoidable I think
mistakes = updateMistakes( mistakes, copy , copy = applyGuess(word,copy,letter) );
}
display(word, copy, mistakes, letter );
return this;
};
}
//Return object, kind pointless now that there is only 1 function :/
return {
guess : guess()
}
//Not entirely pure function, not cacheable
function display(word, copy, mistakes, letter){
console.log( won(word,copy) ? 'You won' :
lost(mistakes) ? 'You lost' :
copy + ' ' + mistakes + ' You tried (' + letter + ')' );
}
//Pure functions!!
function applyGuess(word, copy, letter){
return word.split('').map( (c,i) => letter == c ? c : copy[i] ).join('');
}
function updateMistakes(mistakes, oldCopy , newCopy){
return mistakes + (oldCopy == newCopy ? 1 : 0 );
}
function done(won, lost){
return won || lost;
}
function won(word, copy){
return word == copy;
}
function lost(mistakes){
return mistakes > 5;
}
}
const game = Hangman('Agile Development');
game.guess('e').guess('a').guess('t').guess('z');Context
StackExchange Code Review Q#140812, answer score: 3
Revisions (0)
No revisions yet.