Refactoring class component to React functional component

I am a beginner in javascript and react. I’m trying to refactor the class component into a functional one (Rules for Yahtzee game scoring).

This is class component:

/** Rule for Yahtzee scoring.
 *
 * This is an "abstract class"; the real rules are subclasses of these.
 * This stores all parameters passed into it as properties on the instance
 * (to simplify child classes so they don't need constructors of their own).
 *
 * It contains useful functions for summing, counting values, and counting
 * frequencies of dice. These are used by subclassed rules.
 */

 class Rule {
  constructor(params) {
    // put all properties in params on instance
    Object.assign(this, params);
  }
 
 sum(dice) {
    // sum of all dice
    return dice.reduce((prev, curr) => prev + curr);
  }
 
 freq(dice) {
    // frequencies of dice values
    const freqs = new Map();
    for (let d of dice) freqs.set(d, (freqs.get(d) || 0) + 1);
    return Array.from(freqs.values());
  }
 
 count(dice, val) {
    // # times val appears in dice
    return dice.filter(d => d === val).length;
  }
}

/** Given a sought-for val, return sum of dice of that val.
  * Used for rules like "sum of all ones"*/

class TotalOneNumber extends Rule {
  evalRoll = dice => {
    return this.val * this.count(dice, this.val);
  };
}

/** Given a required # of same dice, return sum of all dice.
 * Used for rules like "sum of all dice when there is a 3-of-kind"*/
class SumDistro extends Rule {
  evalRoll = dice => {
    // do any of the counts meet of exceed this distro?
    return this.freq(dice).some(c => c >= this.count) ? this.sum(dice) : 0;
  };
}

/** Check if full house (3-of-kind and 2-of-kind) */
class FullHouse extends Rule {
  evalRoll = dice => {
    const freqs = this.freq(dice);
    return freqs.includes(2) && freqs.includes(3) ? this.score : 0;
  };
}

/** Check for small straights. */
class SmallStraight extends Rule {
  evalRoll = dice => {
    const d = new Set(dice);
    // straight can be 234 + either 1 or 5
    if (
      (d.has(2) && d.has(3) && d.has(4) && d.has(5) && d.has(2)) ||
      d.has(5)
    ) {
      return this.score;
    }
    // or 345 + either 2 or 6
    if ((d.has(3) && d.has(4) && d.has(5) && d.has(2)) || d.has(6)) {
      return this.score;
    }
    return 0;
  };
}

/** Check for large straights. */
class LargeStraight extends Rule {
  evalRoll = dice => {
    const d = new Set(dice);
    // large straight must be 5 different dice & only one can be a 1 or a 6
    return d.size === 5 && (!d.has(1) || !d.has(6)) ? this.score : 0;
  };
}

/** Check if all dice are same. */
class Yahtzee extends Rule {
  evalRoll = dice => {
    // all dice must be the same
    return this.freq(dice)[0] === 5 ? this.score : 0;
  };
}

// ones, twos, etc score as sum of that value
const ones = new TotalOneNumber({ val: 1, description: '1 point per 1' });
const twos = new TotalOneNumber({ val: 2, description: '2 points per 2' });
const threes = new TotalOneNumber({ val: 3, description: '3 points per 3' });
const fours = new TotalOneNumber({ val: 4, description: '4 points per 4' });
const fives = new TotalOneNumber({ val: 5, description: '6 points per 5' });
const sixes = new TotalOneNumber({ val: 6, description: '6 points per 6' });

// three/four of kind score as sum of all dice
const threeOfKind = new SumDistro({
  count: 3,
  description: 'Sum all dice if 3 are the same'
});

const fourOfKind = new SumDistro({
  count: 4,
  description: 'Sum all dice if 4 are the same'
});

// full house scores as flat 25
const fullHouse = new FullHouse({
  score: 25,
  description: '25 points for full house'

});

// small/large straights score as 30/40
const smallStraight = new SmallStraight({
  score: 30,
  description: '30 points for small straight'
});

const largeStraight = new LargeStraight({
  score: 40,
  description: '40 points for large straight'
});

// yahtzee scores as 50
const yahtzee = new Yahtzee({
  score: 50,
  description: '30 points for yahtzee'

});

// for chance, can view as some of all dice, requiring at least 0 of a kind
const chance = new SumDistro({ count: 0, description: 'Sum of all dice' });

export {
  ones,
  twos,
  threes,
  fours,
  fives,
  sixes,
  threeOfKind,
  fourOfKind,
  fullHouse,
  smallStraight,
  largeStraight,
  yahtzee,
  chance
};

This is my non working refactored code:

    // put all properties in params on instance
    Object.assign(params);
 
    function sum(dice) {
        // sum of all dice
        return dice.reduce((prev, curr) => prev + curr);
      }
  
    
    function freq(dice) {
    // frequencies of dice values
       const freqs = new Map();
       for (let d of dice) freqs.set(d, (freqs.get(d) || 0) + 1);
       return Array.from(freqs.values());
      }
              
    
    function count(dice, val) {
    // # times val appears in dice
       
    return dice.filter(d => d === val).length;
      }

    
    /** Given a sought-for val, return sum of dice of that val.
     *
     * Used for rules like "sum of all ones"
     */
    
    const TotalOneNumber = {
        evalRoll: (dice, val) => {
        return val * count(dice, val);
        }
    }
  
  /** Given a required # of same dice, return sum of all dice.
   *
   * Used for rules like "sum of all dice when there is a 3-of-kind"
   */
  
    const sumDistro = {
    evalRoll: dice => {
      // do any of the counts meet of exceed this distro?
      return freq(dice).some(c => c >= count) ? sum(dice) : 0;
    }
  }
  
    /** Check if full house (3-of-kind and 2-of-kind) */
  
   const fullHouse = {
    evalRoll: (dice, score) => {
      const freqs = freq(dice);
      return freqs.includes(2) && freqs.includes(3) ? score : 0;
    }
  }
  
  /** Check for small straights. */
  
    const smallStraight = {
    evalRoll: (dice, score) => {
      const d = new Set(dice);
      // straight can be 234 + either 1 or 5
      if (
        (d.has(2) && d.has(3) && d.has(4) && d.has(5) && d.has(2)) ||
        d.has(5)
      ) {
        return score;
      }
  
      // or 345 + either 2 or 6
      if ((d.has(3) && d.has(4) && d.has(5) && d.has(2)) || d.has(6)) {
        return score;
      }
  
      return 0;
    }
  }
  
  /** Check for large straights. */
  
    const largeStraight = {
    evalRoll: (dice, score) => {
      const d = new Set(dice);
  
      // large straight must be 5 different dice & only one can be a 1 or a 6
      return d.size === 5 && (!d.has(1) || !d.has(6)) ? score : 0;
    }
  }
  
  /** Check if all dice are same. */
  
    const yahtzee = {
    evalRoll: (dice, score) => {
      // all dice must be the same
      return freq(dice)[0] === 5 ? score : 0;
    }
  }
  
    // ones, twos, etc score as sum of that value
    const ones = new TotalOneNumber({ val: 1, description: '1 point per 1' });
    const twos = new TotalOneNumber({ val: 2, description: '2 points per 2' });
    const threes = new TotalOneNumber({ val: 3, description: '3 points per 3' });
    const fours = new TotalOneNumber({ val: 4, description: '4 points per 4' });
    const fives = new TotalOneNumber({ val: 5, description: '6 points per 5' });
    const sixes = new TotalOneNumber({ val: 6, description: '6 points per 6' });
  
    // three/four of kind score as sum of all dice
    const threeOfKind = new sumDistro({
        count: 3,
        description: 'Sum all dice if 3 are the same'
    });
    const fourOfKind = new sumDistro({
        count: 4,
        description: 'Sum all dice if 4 are the same'
    });
    
    // full house scores as flat 25
    const FullHouse = new FullHouse({
        score: 25,
        description: '25 points for full house'
    });
    
    // small/large straights score as 30/40
    const SmallStraight = new SmallStraight({
        score: 30,
        description: '30 points for small straight'
    });
    const LargeStraight = new LargeStraight({
        score: 40,
        description: '40 points for large straight'
    });
    
    // yahtzee scores as 50
    const Yahtzee = new Yahtzee({
        score: 50,
        description: '30 points for yahtzee'
    });
    
    // for chance, can view as some of all dice, requiring at least 0 of a kind
    const chance = new sumDistro({ count: 0, description: 'Sum of all dice' });
    
   export {
        ones,
        twos,
        threes,
        fours,
        fives,
        sixes,
        threeOfKind,
        fourOfKind,
        fullHouse,
        smallStraight,
        largeStraight,
        yahtzee,
        chance,
    };

Unfortunately, I am struggling to make this work, can someone help me, what am I doing wrong here?

What is the error you are seeing in the console?

You can see what this is all about here at this link: Yahtzee - Codesandbox

I see this error in the console:

Uncaught TypeError: TotalOneNumber is not a constructor
    at Module.<anonymous> (Rules.js:100)
    at Module../src/components/Rules.js (Rules.js:328)
    at __webpack_require__ (bootstrap:851)
    at fn (bootstrap:150)
    at Module.<anonymous> (Rules.js:328)
    at Module../src/components/ScoreTable.js (ScoreTable.js:117)
    at __webpack_require__ (bootstrap:851)
    at fn (bootstrap:150)
    at Module.<anonymous> (Die.js:27)
    at Module../src/components/Game.js (Game.js:133)
    at __webpack_require__ (bootstrap:851)
    at fn (bootstrap:150)
    at Module.<anonymous> (App.css?dde5:82)
    at Module../src/App.js (App.js:14)
    at __webpack_require__ (bootstrap:851)
    at fn (bootstrap:150)
    at Module.<anonymous> (index.css?bb0a:82)
    at Module../src/index.js (index.js:10)
    at __webpack_require__ (bootstrap:851)
    at fn (bootstrap:150)
    at Object.1 (index.js:10)
    at __webpack_require__ (bootstrap:851)
    at checkDeferredModules (bootstrap:45)
    at Array.webpackJsonpCallback [as push] (bootstrap:32)
    at main.chunk.js:1
webpackHotDevClient.js:138 src\components\Game.js
  Line 37:6:  React Hook useEffect has a missing dependency: 'animateRoll'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

src\components\Rules.js
  Line 118:23:  'FullHouse' was used before it was defined      no-use-before-define
  Line 124:27:  'SmallStraight' was used before it was defined  no-use-before-define
  Line 128:27:  'LargeStraight' was used before it was defined  no-use-before-define
  Line 134:21:  'Yahtzee' was used before it was defined        no-use-before-define

There are no react components in your code. These are all just normal JavaScript classes. You can’t convert them to react functional components.

I will reformulate the question since I did not express myself well. I want to rewrite the code from above using traditional function-based syntax. Something like this: Sub classing with extends

When you say “something like this” do you mean like that but not using classes (because that link uses classes)?

And are you trying to be functional or just not use classes? What exactly are you after in attempting this conversion?

I’m trying that Rules be functional not class.

If you’re trying to be functional then you want to think in terms of functions. You kind of started off that way (function sum(dice)...) but then you went into objects (const TotalOneNumber = {…) and then continued to try to new things which is going back to instantiating classes - classes which you should no longer have, and actually don’t, hence the errors.

The somewhat tricky part here is dealing with your parameter state, namely val, description, count, and score. count, by the way, is a bad parameter name because it conflicts with the count method. Luckily, this state never changes so it should be pretty straightforward to make function versions of these classes/methods since its all just inputs producing outputs with no side effects. What was state can now be just another input.

… actually it looks like steve.mills is replying so I’m going to see what happens there before I continue (I have some errands to run now anyway :upside_down_face: )

I love FP and you can learn a lot from functional programming.
Function composition, generic reusable functions, clean code, provable functions ect.

The basic premise of functional programming is:

  • copy data without mutating
  • pipe data through a series of single purpose pure functions by copying and not mutating
  • don’t save store any actions as variables (not even an iteration)
  • output the data as a response or to an interface using an “impure” IO function

This is great for back end data base type operations where performance is not critical but correctness is.

Even Rust a systems level language is immutable by default and supports higher order functions.

However for 95% of all front end JS, functional programming is unnecessary and bad for performance.

FP is great to learn and makes you a better programmer in 2 key areas:

  • Clean code: Pure single purpose functions are nice
  • Higher order functions: understanding passing functions as arguments, piping composition ect is really usefull

If you really want to learn Functional Programming in JS Brian Lonsdorf has a course on Front End Masters, Egghead IO and a book thats free. Be prepared to go down the rabbit hole. :grin:

I personally wouldn’t try to change existing front end JS to an FP style unless you were trying to clean up really bad code and make it more readable.

Also:

  • Loops are fine, you don’t need recursion (you can also write an immutable map function with a loop)
  • Mapping repeatedly throughout a pipe or compose is a waste of memory
  • You don’t need monads/ monoids. Promise is like a monad and async functions/ await is more performant and easier to read.

To be honest if you really want to get into FP…

  • front end: Elm is great (its a compile to JS language and a framework).
  • back end: Elixer is pretty kick ass but hard to learn (runs on Erlang)
  • up and coming: Roc (from the creators /primary users of Elm, in development)

Just use whatever style of programming that is best suited to your project/ framework. Take the good parts of FP, OO ect and write easy to understand/ test and debug code.

2 Likes

Looking at the original, it seems the Rules class doesn’t do much more than save the params its given (via assign) and set up a few helper functions, none of which actually refer to those params. This is good because they can be easily ripped out and made into simple, standalone functions - something you’ve already done, so off to a good start.

After that there are some other classes that all extend Rules, but all of these only ever define a single evalRoll method. So really, these can effectively just be that method as a function. You’re putting them in objects in your refactored code which isn’t necessary since they all simply wrap one function. Instead just make them the function. So

class TotalOneNumber extends Rule {
  evalRoll = dice => {
    return this.val * this.count(dice, this.val);
  };
}

would loosely become

function totalOneNumber(dice) {
  return this.val * this.count(dice, this.val);
}

The problem now is getting rid of this. this would represent the current instance and the members within that instance but we’re not dealing with classes an instances anymore, just functions with simple inputs and outputs.

There’s two uses of this, one for val and one for count. count is easy, that’s a function we ripped out earlier so that can just drop the this and simply work. val is something that would be coming from the params passed into the constructor during instantiation of the class. Its another kind of input, one that would be coming from the instance rather than the arguments, but we can simply move it to be an argument input instead because we no longer have an instance. That gives us

function totalOneNumber(val, dice) {
  return val * count(dice, val);
}

You can then repeat this process for the other classes as well.

After the classes, we set the individual numbers being instantiated from the TotalOneNumber class. Each of these instances contain the functionality of the TotalOneNumber class but each having a different val property (there’s a description too, but we can ignore that for now since I don’t know how it’s being used).

Since the refactor doesn’t have instances, these would instead have to be represented as functions… or do they? Wouldn’t each of these instead just calling the new totalOneNumber(val, dice) function with a different val argument each time? Well, yes, but we can also make new function versions of totalOneNumber(val, dice) with those values baked in using partial application. JavaScript can do this with the native bind() method. That would look something like this:

const ones = totalOneNumber.bind(null, 1);
const twos = totalOneNumber.bind(null, 2);
const threes = totalOneNumber.bind(null, 3);
const fours = totalOneNumber.bind(null, 4);
const fives = totalOneNumber.bind(null, 5);
const sixes = totalOneNumber.bind(null, 6);

Now, each of these are new functions which calls totalOneNumber(val, dice) with the val pre-defined. ones, for example, would have the signature of ones(dice) and would use 1 as the value of val in its function body which is that of totalOneNumber, or more specifically something like:

function ones(dice) {
  return 1 * count(dice, 1);
}

Lets test it out (full script with all necessary dependencies so you can copy and paste and run somewhere):

function count(dice, val) {
  return dice.filter(d => d === val).length;
}

function totalOneNumber(val, dice) {
    return val * count(dice, val);
}

const ones = totalOneNumber.bind(null, 1);
const twos = totalOneNumber.bind(null, 2);
const threes = totalOneNumber.bind(null, 3);

const dice = [1,1,2,2,3];
console.log('ones:', ones(dice)); // 2 (1x2)
console.log('twos:', twos(dice)); // 4 (2x2)
console.log('threes:', threes(dice)); // 3 (3x1)

The other classes aren’t very different and would need a similar treatment. You think you could do the rest yourself? Just be sure to not confuse the count parameter and the count function. When you’re done there should be no references to this or new. What you’ll be exporting will be a list of functions, not a list of object instances, and these functions would be used in place of instance.evalRoll() (e.g. what was ones.evalRoll(dice) would instead become ones(dice)).

3 Likes

Been having a play with it and did up a function that returns a score from a five number array.

Loops are more efficient than Map because they don’t create a function object every iteration.
You can also return the main function and break from a loop.

It tally’s the array for each number on the dice 0-6 into a frequency array (disregard 0).
Then loops and checks for Full House (2 + 3 of a kind), 3/4/5 of a kind.
If that doesn’t return it checks for Low and High Straight .
Failing that returns 0;

const check = function(arr){
  // efficient reduce function
  let count = function(ar){ let acc = 0; for(let i of ar){acc += i} return acc }
  
  let two = null, thr = null, frq = [0,0,0,0,0,0,0];  
  // sets the frequency of roll numbers to frq array
  for(let i of arr){frq[i] ++}
  
  for(let i of frq){
    // 2 and 3  of a kind  
    i == 2 ? two = 1 : i == 3 ? thr = 1 : 0;
    // Four of a kind
    if(i == 4) return count(arr)
    //yahtzee
    if(i == 5) return 50;
  }
  // Full house
  if(two && thr) return 25
  // Three of a kind // wait to check for full house
  if(thr) return count(arr)
  
  let cnt = 0, low = 0, val = NaN;
  for(let x of arr) {
    // checks for Low straight before setting count to 0
    cnt == 3 ? low = 1 : 0;
    // counts each sequential incremental number
    val == (x -1) ? cnt ++ : cnt = 0;
    //set val to current iteration for next loop to compare
    val = x;
  }
    // High straight
    if(cnt == 4) return 40; 
    // Low Straight
    if(low == 1 || cnt == 3) return 30;
    return 0;
}
// Nothing Roll
console.log(check([1,3,1,2,5])) // returns 0

// Full House
console.log(check([1,1,1,2,2])) // returns 25

// 3 of a kind
console.log(check([1,6,1,2,1])) // returns 11

// Four of a kind
console.log(check([5,5,5,5,1])) // returns 21

// Low straight
console.log(check([1,2,3,4,6])) // returns 30

// High straight
console.log(check([2,3,4,5,6])) // returns 40

// Yahtzee
console.log(check([6,6,6,6,6])) // returns 50

without comments

const check = function(arr){
  let count = function(ar){ let acc = 0; for(let i of ar){acc += i} return acc }
  let two = null, thr = null, frq = [0,0,0,0,0,0,0];  
  for(let i of arr){frq[i] ++}
  
  for(let i of frq){ 
    i == 2 ? two = 1 : i == 3 ? thr = 1 : 0;
    if(i == 4) return count(arr)
    if(i == 5) return 50;
  }
  if(two && thr) return 25
  if(thr) return count(arr)
  
  let cnt = 0, low = 0, val = NaN;
  for(let x of arr) {
    cnt == 3 ? low = 1 : 0;
    val == (x -1) ? cnt ++ : cnt = 0;
    val = x;
  }
    if(cnt == 4) return 40; 
    if(low == 1 || cnt == 3) return 30;
    else return 0;
}
1 Like

@ senocular I don’t know how to thank you for such an exhaustive and good explanation. I did a refactoring according to your suggestions and the part related to results from one to six is working properly.
This is how the code looks like now:

/** Functions for summing, counting values, and counting frequencies of dice.
*/

function sum(dice) {
// sum of all dice
return dice.reduce((prev, curr) => prev + curr);
}

function freq(dice) {
// frequencies of dice values
const freqs = new Map();
for (let d of dice) freqs.set(d, (freqs.get(d) || 0) + 1);
return Array.from(freqs.values());
}

function count(dice, val) {
// # times val appears in dice
return dice.filter(d => d === val).length;
}

/** Given a sought-for val, return sum of dice of that val.
Used for rules like “sum of all ones”
*/

function totalOneNumber(val, dice) {
return val * count(dice, val);
}

/** Given a required # of same dice, return sum of all dice.
Used for rules like “sum of all dice when there is a 3-of-kind”
*/

function sumDistro(dice) {
// do any of the counts meet of exceed this distro?
return freq(dice).some(c => c >= count) ? sum(dice) : 0;
}

/** Check if full house (3-of-kind and 2-of-kind) */

function fullHouseFn(dice, score) {
const freqs = freq(dice);
return freqs.includes(2) && freqs.includes(3) ? score : 0;
}

/** Check for small straights. */

function smallStraightFn(dice, score) {
const d = new Set(dice);
// straight can be 234 + either 1 or 5
if (
(d.has(2) && d.has(3) && d.has(4) && d.has(5) && d.has(2)) ||
d.has(5)
) {
return score;
}
// or 345 + either 2 or 6
if ((d.has(3) && d.has(4) && d.has(5) && d.has(2)) || d.has(6)) {
return score;
}
return 0;
}

/** Check for large straights. */

function largeStraightFn(dice, score) {
const d = new Set(dice);

// large straight must be 5 different dice & only one can be a 1 or a 6
return d.size === 5 && (!d.has(1) || !d.has(6)) ? score : 0;
}

/** Check if all dice are same. */

function yahtzeeFn(dice, score) {
// all dice must be the same
return freq(dice)[0] === 5 ? score : 0;
}

// ones, twos, etc score as sum of that value

const ones = totalOneNumber.bind(null, 1);
const twos = totalOneNumber.bind(null, 2);
const threes = totalOneNumber.bind(null, 3);
const fours = totalOneNumber.bind(null, 4);
const fives = totalOneNumber.bind(null, 5);
const sixes = totalOneNumber.bind(null, 6)

// three/four of kind score as sum of all dice
const threeOfKind = sumDistro({
count: 3,
description: ‘Sum all dice if 3 are the same’
});

const fourOfKind = sumDistro({
count: 4,
description: ‘Sum all dice if 4 are the same’
});

// full house scores as flat 25
const fullHouse = fullHouseFn({
score: 25,
description: ‘25 points for full house’
});

// small/large straights score as 30/40
const smallStraight = smallStraightFn({
score: 30,
description: ‘30 points for small straight’
});

const largeStraight = largeStraightFn({
score: 40,
description: ‘40 points for large straight’
});

// yahtzee scores as 50
const yahtzee = yahtzeeFn({
score: 50,
description: ‘30 points for yahtzee’
});

// for chance, can view as some of all dice, requiring at least 0 of a kind
const chance = sumDistro({ count: 0, description: ‘Sum of all dice’ });

export {
ones,
twos,
threes,
fours,
fives,
sixes,
threeOfKind,
fourOfKind,
fullHouse,
smallStraight,
largeStraight,
yahtzee,
chance,
};

Now, I have a problem with functions for threeOfKind, fourOfKind, fullHouse, etc …
I got an error that occurs in the function freq.
This is the error in the console:

Uncaught TypeError: dice is not iterable!
at freq (Rules1.js:14)
at sumDistro (Rules1.js:45)
at Module. (Rules1.js:114)
at Module…/src/components/Rules1.js (Rules1.js:158)
at webpack_require (bootstrap:851)
at fn (bootstrap:150)
at Module. (Rules1.js:158)
at Module…/src/components/ScoreTable.js (ScoreTable.js:285)
at webpack_require (bootstrap:851)
at fn (bootstrap:150)
at Module. (Die.js:27)
at Module…/src/components/Game.js (Game.js:226)
at webpack_require (bootstrap:851)
at fn (bootstrap:150)
at Module. (App.css?dde5:82)
at Module…/src/App.js (App.js:14)
at webpack_require (bootstrap:851)
at fn (bootstrap:150)
at Module. (index.css?bb0a:82)
at Module…/src/index.js (index.js:10)
at webpack_require (bootstrap:851)
at fn (bootstrap:150)
at Object.1 (index.js:10)
at webpack_require (bootstrap:851)
at checkDeferredModules (bootstrap:45)
at Array.webpackJsonpCallback [as push] (bootstrap:32)
at main.chunk.js:1

I personally find Object Orientated programming is confusing and mostly unnecessary much like about half of Functional programming (algebraic notation, point free, natural tx ect).

I tend to use a mix of procedural and basic functional programming however, I do like classes for UI components/ CSS (custom elements).

An interesting take on class inheritance:
Object-Oriented Programming is Bad - YouTube

The problem appears to be these constants e.g

const smallStraight = smallStraightFn({
score: 30,
description: ‘30 points for small straight’
});

are calling these functions e.g.

function smallStraightFn(dice, score) {
const d = new Set(dice);
// straight can be 234 + either 1 or 5
if (
(d.has(2) && d.has(3) && d.has(4) && d.has(5) && d.has(2)) ||
d.has(5)
) {
return score;
}
// or 345 + either 2 or 6
if ((d.has(3) && d.has(4) && d.has(5) && d.has(2)) || d.has(6)) {
return score;
}
return 0;
}

with the wrong arguments.

smallStraightFn(dice, score) expects an array as the first argument e.g. [1,2,3,4,1] and a score as the second argument e.g. 30
so smallStraightFn([1,2,3,4,1], 25) should return 30

BUT it’s being called with an object smallStraightFn({score : 30})

There is another problem:

In theory the functionsmallStraightFn() should return the constant const smallStraight = { score: 30, description: ‘30 points for small straight’}

After you call smallStraightFn() with an array [1,2,3,4,1] so the function verifies that the array is a “small straight”…

1 Like

Assuming an input an array of five (dice numbers) and output an object with the frequencies, total and description e.g

{ sixes: 5,
  score: '50',
  description: '50 points for Yahtzee'
}

The following block does this:

// more efficient freq function
const freq = function(arr){
  let frq = [0,0,0,0,0,0,0]
  for(let i of arr){frq[i] ++}
  return frq
}
// more efficient sum function
const sum = function(arr){
  let acc = 0; 
  for(let i of arr){acc += i} 
  return acc 
}
// replaces all those const = function()
const score = function({score, type}){
  return { score: `${score}`, description: `${score} points for ${type}`}
}

// checks for X of a kind incl Yahtzee and Full House
const checkKind = (frq,arr) => {
  let two = null, thr = null; 
  for(let i of frq){ 
    if(i == 2) two = 1;
    if (i == 3) thr = 1;
    if(i == 4) return {score: sum(arr), type: 'Four of a Kind'}
    if(i == 5) return {score: 50, type: 'Yahtzee'}
  }
  if(two && thr) return {score: 25, type: 'Full House'}
  if(thr) return {score: sum(arr), type: 'Three of a Kind'}
  else return 0;
}

// checks for Low / High Straight
const checkStraight = arr => {
  let count = 0, low = 0, val = NaN;
  for(let x of arr) {
    if(val == (x -1)) count ++;
    else {  if(count == 3) low = 1;  count = 0;}
    val = x;
  }
    if(count == 4) return {score: 40, type: 'Hight Straight'} 
    if(low == 1 || count == 3) return {score: 30, type: 'Low Straight'}
    return {score: 0, type: 'No Dice'}
}
// replaces all the bind functions
const frqObj = function([zero, ones, twos, threes, fours, fives, sixes]){ 
  let obj = {}
  if(ones !== 0) obj.ones = ones;
  if(twos !== 0) obj.twos = twos;
  if(threes !== 0) obj.threes = threes;
  if(fours !== 0) obj.fours = fours;
  if(fives !== 0) obj.fives = fives;
  if(sixes !==0) obj.sixes = sixes;
  return obj;  
}
// runs all the functions and returns a merged freq and score object
const check = function(arr){
  let frq = freq(arr);
  let vals = frqObj(frq);
  let kind = checkKind(frq, arr);
  let straight;
  let total;
  if(kind == 0){
    straight = checkStraight(arr);
    total = score(straight);
    return {...vals, ...total}
  }
  else {total = score(kind);
       return {...vals, ...total}
       }
}

// Nothing Roll
console.log(check([1,3,1,3,5])) 
// returns { ones: 2, threes: 2, fives: 1, score: '0',description: '0 points for No Dice'}

// Full House
console.log(check([1,1,1,2,2])) 
// returns { ones: 3, twos: 2, score: '25', description: '25 points for Full House'}

// 3 of a kind
console.log(check([1,6,1,2,1])) 


// Four of a kind
console.log(check([5,5,5,5,1])) 

// Low straight
console.log(check([1,2,3,4,6])) 

// High straight
console.log(check([2,3,4,5,6])) 

// Yahtzee
console.log(check([6,6,6,6,6]))

Have a play, see if you can work out what all the functions do in detail :slightly_smiling_face:

1 Like