Search code examples
javascriptlodashimmutabilitydeep-copylookup-tables

Lodash _.cloneDeep() is mutating an object property derived from a hashTable lookup during an iteration (Poker - Playing Cards)


I am running into a truly perplexing bug that I've been unsuccessfully trying to squash for the past several hours. I am working on a Poker implementation. Initially, I generate the cards with an iterative loop.

const suits = ['Heart', 'Spade', 'Club', 'Diamond'];
const cards = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'];
const VALUE_MAP = {
    2:1,
    3:2,
    4:3,
    5:4,
    6:5,
    7:6,
    8:7,
    9:8,
    10:9,
    J:10,
    Q:11,
    K:12,
    A:13,
};
const generateDeckOfCards = () => {
    const deck = [];

    for (let suit of suits) {
        for (let card of cards) {
            deck.push({
                cardFace: card,
                suit: suit,
                value: VALUE_MAP[card]
            })
        }
    }
        return deck
}

const fullDeck = generateDeckOfCards()

When the round is completed, the player's 2 Private Cards and 5 Community Cards are concatenated into an array and sorted by value (descending):

player.showDownHand.hand = player.cards.concat(state.communityCards);
hand = player.showDownHand.hand.sort((a,b) => b.value - a.value);

console.log(hand)
// Output:
0: Object { cardFace: "J", suit: "Heart", value: 10, … }
1: Object { cardFace: "9", suit: "Heart", value: 8, … }
2: Object { cardFace: "9", suit: "Club", value: 8, … }
3: Object { cardFace: "6", suit: "Heart", value: 5, … }
4: Object { cardFace: "5", suit: "Diamond", value: 4, … }
5: Object { cardFace: "4", suit: "Diamond", value: 3, … }
6: Object { cardFace: "3", suit: "Club", value: 2, … }
length: 7

Now, the bug occurs when I initiate my method to build the best possible hand of 5 cards depending on hand rank. I need to mess around with this array and possibly mutate it, filter out cards that I've picked out, etc. - So I create a deep clone of the object.

There is a huge problem - the values for the cards CHANGE for some reason! I do not mutate anything in between - The value should be static and derived from the current card's cardFace property.

import { cloneDeep } from 'lodash';

let mutableHand = cloneDeep(hand);
console.log(mutableHand)
// Output
0: Object { cardFace: "J", suit: "Heart", value: 13, … }
1: Object { cardFace: "9", suit: "Heart", value: 8, … }
2: Object { cardFace: "9", suit: "Club", value: 8, … }
3: Object { cardFace: "6", suit: "Heart", value: 6, … }
4: Object { cardFace: "5", suit: "Diamond", value: 4, … }
5: Object { cardFace: "4", suit: "Diamond", value: 3, … }
6: Object { cardFace: "3", suit: "Club", value: 2, … }

Card 0 and 3 have had their values totally changed! Why? I have no idea - Has the original context of the lookup table changed? If anyone has any hints to how I can fix this, I would be most appreciative.

An additional note - Creating a Shallow Copy with let mutableHand = [...hand]; does NOT initially exhibit this behavior in console log - If I do nothing to it.. However, after I run the shallow copied array through the function, even the original descending deck state has many of its values mutated. Again, I'm not sure why :\

Full code can be viewed on codesandbox.io/s/oqx8ooyv29 - The problem stems from the buildBestHand() function in src/utils/card.js

Here is the problem code:

const bestHand = [];
let mutableHand = cloneDeep(hand);
    for (let i = 0; i < 2; i++) {
        const indexOfPair = mutableHand.findIndex(card => card.cardFace === frequencyHistogramMetaData.pairs[0].face);
            bestHand.push(mutableHand[indexOfPair])
                mutableHand = mutableHand.filter((card, index) => index !== indexOfPair)
    }
        return bestHand.concat(mutableHand.slice(0, 3))

I am trying to find the index of card which matches "Pair" (Player has 2 cards with that face), Push to an array of best cards, filter out that index in between iterations, and fill the rest of the best-cards array with their next 3 highest cards.

Edit: If I do hand = cloneDeep(player.showDownHand.hand).sort((a,b) => b.value - a.value);, the issue is heavily exacerbated, and almost all the values are corrupt for final comparisons


Solution

  • Looking through your example code, you are mutating the value in cards.js:787 (which looks accidental).

     } else if ((cur.card.value = highValue)) {
        acc.push(cur.name);
        console.log(
          "Adding player at comparatorindex ",
          index,
          "To Winners Array"
        );
        console.log(acc);
        return acc;
      }
    

    I would recommend using something like eslint, which should warn you against this particular kind of accident.