Search code examples
javascriptjavascript-objects

JS: new-state object created; prevent "pop" from affecting old-state object


In the game code below: even though the javascript new keyword was used to make a new object to hold the next-state object, methods applied to the next-state object still refer to first-state object for "naked" properties passed to the constructor when making the next-state object.

When a transformation is applied to a property before using it as a parameter for the next-object constructor, preserving the first-state property is easy (e.g. with timeOfDay it is easy to avoid overwriting the first state).

I've resorted to a "map" workaround to avoid referencing the first-state object when modifying the list of mafia, but it is probably not the cleanest or easiest to understand.

A few other previously answered questions suggest using something like Object.assign({},this.mafia) but this does not allow usage of the "pop method" in the code below.

Please suggest an alternative to avoid overwriting the first-state object as shown below.

class mafiaVillageState{

    constructor(timeOfDay, villagers, mafia){
        this.timeOfDay = timeOfDay;
        this.villagers = villagers;
        this.mafia = mafia;
    }    

    killPlayer(){
        let gameStep = new mafiaVillageState(1-this.timeOfDay,
            this.villagers.map(v => {return v}),

            // this.mafia
            this.mafia.map(v => {return v})      
            // Object.assign({},this.mafia)

        );
        if (this.timeOfDay == 1){
            gameStep.mafia.pop();
        } else {
            gameStep.villagers.pop();
        }
        return gameStep 
    }
}

let first = new mafiaVillageState(
    1,
    ["Sana","Dahyun"], //the villagers
    ["Chaeyoung","Tzuyu"] //the mafia
)

"Playing the game" is done by getting the next game state:

let next = first.killPlayer();

console.log(first.timeOfDay);
console.log(first.mafia);
console.log(next.timeOfDay);
console.log(next.mafia);
  • output without any workaround to avoid referencing the first object modifies the first state (not desired as I do not want to overwrite the initial state, to help debug when the selection of the player to kill is much more complicated)
1
Array(1) ["Chaeyoung"]
0
Array(1) ["Chaeyoung"]
  • output with the "map workaround": this is the desired output; one mafia is killed and the first state is not changed
1
Array(2) ["Chaeyoung", "Tzuyu"]
0
Array(1) ["Chaeyoung"]

-using Object.assign({},this.mafia) results in error below

' TypeError: gameStep.mafia.pop is not a function '

Solution

  • You need to create a new object for the new state if you want to preserve the old one for debugging reasons, there is no way around that. I don't think that using .map is a bad solution but if you are looking for something cleaner you can use the es6 array spread operator or Array.prototype.splice.

    killPlayer(){
        let gameStep = new mafiaVillageState(
            1 - this.timeOfDay,
            [...this.villagers], // alternatively: this.villagers.splice(),
            [...this.mafia],
        );
    
        ...
    }
    

    The spread operator dumps the items in the array being spread into the new one (you can also do things like [...arr1, ...arr2] to combine arrays). You should note that the items are not copied, so if you mutate something in either array the mutation will affect all states that contain that object.