Design a simple card game in JavaScript
Your constructor has

constructor({ play1, play2, rules, messages }) {
  this.play1 = play1;
  this.play2 = play2;
  this.rules = rules;
  this.messages = messages;
}


You may as well Object.assign the parameter to the instance instead:

constructor(config) {
  Object.assign(this, config);
}


pattern is a slightly odd name for what it represents here - the usual English word for one of the clubs, diamonds, etc, is suit. rule is a bit strange as well - a rule usually refers to the process by which a game is played (eg "Hands consist of 6 cards" or "The player with the best hand wins"). To describe the different possible winning combinations and their ranks, I'd use the word handRanks or something similar. play1 and play2 aren't great descriptors either - these represent the cards held in each player's hand, so maybe use player1Cards or player1Hand.

With regards to the play() method, when you want to find an item in an array which fulfills a condition, it would be more appropriate to use .find, rather than a for loop - find more clearly indicates what the intention of the loop is, and is more concise. You also need to set the rank to Infinity if no handRanks pass - why not integrate this Infinity into the handRanks array itself? You're also writing the looping code twice - you can make it more DRY by putting it into a function, and calling that function twice instead.

new Card({ suit: "spade", number: 1 }),  // &lt;-- Suit


new HandRank({  // &lt;-- HandRank
  description: "Six Cards of the same suit",  // &lt;-- Suit
  rank: 1,
  validator: cards =&gt; {
    return hasSameSuit(cards, 6);  // &lt;-- hasSameSuit, not hasSamePattern
  }
}),
new HandRank({  // &lt;-- HandRank
  description: "Nothing special",
  rank: Infinity,                 // &lt;-- add this whole new HandRank
  validator: cards =&gt; true,
}),


getRank(cards) {
  return this.handRanks.find(({ validator }) =&gt; validator(cards)).rank; // &lt;-- this.handRanks
}
play() {
  const rankOfP1 = this.getRank(this.player1Cards); //  &lt;-- player1Cards
  const rankOfP2 = this.getRank(this.player2Cards); //  &lt;-- player2Cards
  return rankOfP1 === rankOfP2
    ? this.messages.tie
    : rankOfP1 &lt; rankOfP2
      ? this.messages.win.player1
      : this.messages.win.player2;
}


One of the benefits of using arrow functions is that if the function contains only a single expression which is immediately returned, you can omit the { } brackets and the return keyword, if you want to make things concise, eg for the hasSameSuit test above:

validator: cards =&gt; hasSameSuit(cards, 6),


If you want to find if any item in an array passes a test, but you don't care about which item passes the test, you should use .some, not .find. (.some returns a boolean indicating whether any passed, .find returns the found item) For the hasSamePattern (or hasSameSuit) method, use:

return Object.values(patterns).some(num =&gt; num &gt;= threshold);


Your hasConsecutiveNums method has the bug mentioned in the comments previously - a hand of [1, 2, 2, 3] will not pass a 3-consecutive-number test because the sorted array will contain 2 twice, failing if (prevNum + 1 === num) {. De-duplicate the numbers with a Set first.

const nums = [...new Set(cards.map(card =&gt; card.number).sort((a, b) =&gt; a - b))];



  I wonder if anyone knows how to rewrite the game in functional programming style.


Javascript isn't entirely suited to completely functional programming either, though it can get most of the way there. To start with, make your functions pure, and avoid side-effects and mutations. For example, assigning to a property of the instance with this.play1 = play1; (or this.player1Cards = player1Cards;) is a mutation. None of your code fundamentally requires anything non-functional (except the console.log at the very end, which is unavoidable), so it should be pretty easy to convert - rather than assigning to properties, just keep the variables in a closure, and return a function for the play method, eg:

const makeGame = ({ player1Cards, player2Cards, handRanks, messages }) =&gt; () =&gt; {
  // getRank is now a standalone function which takes a handRanks parameter
  const rankOfP1 = getRank(player1Cards, handRanks);
  const rankOfP2 = getRank(player2Cards, handRanks);
  return rankOfP1 === rankOfP2
    ? messages.tie
    : rankOfP1 &lt; rankOfP2
      ? messages.win.player1
      : messages.win.player2;
};


const play = makeGame({ ... });
console.log(play());