Problem with undefined class parameters

The system is being created using Tweego, with the latest version of sugarcube 2.
Please specify version and format if asking for help, or apply optional tags above: I’m creating a poker game based on a previous model, but a problem has been hindering my project. The class parameters always return undefined, starting with this._names and I’m out of ideas for the problem.

setup.poker_data = {
	cards : [ 'Ace', 'King', 'Queen', 'Jack', 'Joker', 10, 9, 8, 7, 6, 5, 4, 3, 2 ],
	suits : ['Hearts', 'Diamonds', 'Spades', 'Clubs']
};

setup.Decktype = Object.freeze({
    STANDARD: "standard",
    UNO: "uno"
});

setup.ruleSet = Object.freeze({
    BLACKJACK: "blackjack"
});

class Deck {
	constructor(deckType, ruleSet, newGame) {
		switch(deckType) {
			case setup.Decktype.STANDARD:
				this._names = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
				this._suits = ['Hearts', 'Diamonds', 'Spades', 'Clubs'];
				break;
			case setup.Decktype.UNO:
				this._names = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'Draw Two', 'Skip', 'Reverse'];
				this._suits = ['Red', 'Green', 'Yellow', 'Blue'];
				break;
			default:
				throw new Error("Unknown deck type \"" + deckType + "\" requested.");
		}

		this._cards = [];
		this._discard = [];
		this._totalCards = 0;

		if (newGame) {
			this._discard.length = 0;
			this._suits.forEach(function(element) {
				for (let i = 0; i < this._names.length; i++) {
					this._cards.push(
						new Card(
							findValue(ruleSet, i), 
							this._names[i], 
							element
						)
					);
					this._totalCards += 1;
				}
			})
			specialCards(ruleSet);
			shuffleDeck();
			return true;
		}
		else if (this._discard.length > 0) {
			this._cards = [...this._cards, ...this._discard];
			this._discard.length = 0;
			shuffleDeck();
			return true;
		}
		else {
			if (this._cards.length > 0) { 
				shuffleDeck();
			} else {
				return false;
			}
		}
	}

	//Get/Set functions and an additional function to draw more than one card.
	get totalCards() {
		return this._totalCards;
	}

	//Returns an array of cards from the deck. Same warning as above.
	cards(numOfCards) {
		//Check to see if the deck is empty. If so, try and replenish it from discard if the rules allow you to do this.
		if (!emptyCheck()) {
			//There was a successful rebuild using the discard stack. That doesn't mean there's enough cards, however.
			//Draw cards until you reach the number specified or stack runs out. Whatever comes first.
			let cardsOut = []
			for (let i = 0; i < numOfCards || i < this._cards.length; i++) {
				cardsOut.push(this._cards.length[i]);
			}
			return cardsOut;
		}
		else {
			//There weren't enough cards to draw from and you couldn't restock the deck with discard.
			return null;
		}
	}

	//Checks to see if the input is a card. If so, unshift it into the discard pile.
	cardCheckUnShift(cardIn) {
		if (cardIn instanceof Card) {
			this._discard.unshift(CardIn)
			return true
		}
		return false;
	}

	//Sets the values of cards.
	findValue (ruleset, iterator) {
		if (setup.ruleSet.BLACKJACK) {
			return iterator > 9 ? 10 : iterator + 1;
		}
		else {
			return iterator + 1;
		}
	}

	specialCards(ruleset){
		//Establish special cards found in more than one unique case.
		if (ruleset.toUpperCase() === "TBD") {
			//Most decks come with 2 jokers, so I'll just throw this here.
			for(let i = 0; i < 2; i++){
				let joker = 
				this._cards.push(new Card(0, 'Joker', null))
			}
		}
		if (ruleset.toUpperCase() === "UNO") {
			for(let i = 0; i < 5; i++){
				this._cards.push(new Card(-4, 'Wild', null), new Card(-5, 'Wild Draw Four', 'X'));
			}
		}
	}

	//Shuffles the deck. I was going to use ES6 for this, but it appears destructuring assignment causes performance hits.
	//Since shuffling might be frequent, I'm not using it here.
	shuffleDeck() {
		for (let i = this._cards.length - 1; i > 0; i--) {
			const j = Math.floor(math.random() * (i + 1));
			let x = this._cards[i];
			this._cards[i] = this._cards[j];
			this._cards[j] = x;
		}
	}

	//Tests to see if the deck is empty and needs to be restocked.
	emptyCheck(ruleset){	
		if (this._cards.length <= 0) {
			//If the game requires the main deck never be rebuilt in a single game. Return false to indicate nothing was rebuilt.
			if (ruleset === 'TBD') {
				return true;
			}
			else {
				//The rules call for the deck to be rebuilt if empty from discard. Arguments can be left empty as we aren't building from scratch.
				//If build is successful, false is returned and the deck is no longer empty. Else, the deck is still is empty and false is returned.
				return !buildDeck();
			}
			//There's cards to go around.
			return false;
		}
	}

	clone() {
		return new Potion(clone(this));
	}

	toJSON() {
		return JSON.reviveWrapper('new deck card', clone(this));
	}
};

setup.Deck = Deck;

I am declaring the class as follows… <<set $newDeck = new setup.Deck(setup.Decktype.STANDARD, 'BLACKJACK', true)>> But _names always returns undefined, I believe that the other parameters also have the same problem.

Twine Version: Twine 2
Story Format: Sugarcube

notes:
. For debugging purposes I will assume that your unspecified Card class looks something like the following…

class Card {
	constructor(value, name, suit) {
		this.value = value;
		this.name = name;
		this.suit = suit;
	}
}

. There are quite a few JavaScript syntax issues with your example, like: misnamed Modules/variables; referencing variables/functions that weren’t in Scope; etc… So I ran your code through my text editor’s ES Lint extension before debugging it, this produced the following result…
(warning: the below includes a dummy Card class declaration.)

setup.poker_data = {
	cards: [ 'Ace', 'King', 'Queen', 'Jack', 'Joker', 10, 9, 8, 7, 6, 5, 4, 3, 2 ],
	suits: ['Hearts', 'Diamonds', 'Spades', 'Clubs']
};

setup.Decktype = Object.freeze({
	STANDARD: 'standard',
	UNO: 'uno'
});

setup.ruleSet = Object.freeze({
	BLACKJACK: 'blackjack'
});

class Card {
	constructor(value, name, suit) {
		this.value = value;
		this.name = name;
		this.suit = suit;
	}
}

class Deck {
	constructor(deckType, ruleSet, newGame) {
		console.log('Deck: deckType: ' + deckType + ' ruleSet: ' + ruleSet + ' newGame: ' + newGame);

		switch (deckType) {
		case setup.Decktype.STANDARD:
			this._names = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];
			this._suits = ['Hearts', 'Diamonds', 'Spades', 'Clubs'];
			break;
		case setup.Decktype.UNO:
			this._names = ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'Draw Two', 'Skip', 'Reverse'];
			this._suits = ['Red', 'Green', 'Yellow', 'Blue'];
			break;
		default:
			throw new Error('Unknown deck type "' + deckType + '" requested.');
		}
		console.log('Deck: names: ' + this._names + ' suits: ' + this._suits);

		this._cards = [];
		this._discard = [];
		this._totalCards = 0;

		if (newGame) {
			this._discard.length = 0;

			for (let i = 0; i < this._suits.length; i++) {
				for (let j = 0; j < this._names.length; j++) {
					this._cards.push(
						new Card(
							this.findValue(ruleSet, j),
							this._names[j],
							this._suits[i]
						)
					);
					this._totalCards += 1;
				}
			}

			this.specialCards(ruleSet);
			this.shuffleDeck();
		}
		else if (this._discard.length > 0) {
			this._cards = [...this._cards, ...this._discard];
			this._discard.length = 0;
			this.shuffleDeck();
		}
		else if (this._cards.length > 0) {
			this.shuffleDeck();
		}

	}

	// Get/Set functions and an additional function to draw more than one card.
	get totalCards() {
		return this._totalCards;
	}

	// Returns an array of cards from the deck. Same warning as above.
	cards(numOfCards) {
		// Check to see if the deck is empty. If so, try and replenish it from discard if the rules allow you to do this.
		if (!this.emptyCheck()) {
			// There was a successful rebuild using the discard stack. That doesn't mean there's enough cards, however.
			// Draw cards until you reach the number specified or stack runs out. Whatever comes first.
			let cardsOut = [];
			for (let i = 0; i < numOfCards || i < this._cards.length; i++) {
				cardsOut.push(this._cards.length[i]);
			}
			return cardsOut;
		}
		// There weren't enough cards to draw from and you couldn't restock the deck with discard.
		return null;
	}

	// Checks to see if the input is a card. If so, unshift it into the discard pile.
	cardCheckUnShift(cardIn) {
		if (cardIn instanceof Card) {
			this._discard.unshift(cardIn);
			return true;
		}
		return false;
	}

	// Sets the values of cards.
	findValue(ruleset, iterator) {
		if (setup.ruleSet.BLACKJACK) {
			return iterator > 9 ? 10 : iterator + 1;
		}
		return iterator + 1;
	}

	specialCards(ruleset) {
		// Establish special cards found in more than one unique case.
		if (ruleset.toUpperCase() === 'TBD') {
			// Most decks come with 2 jokers, so I'll just throw this here.
			for (let i = 0; i < 2; i++) {
				this._cards.push(new Card(0, 'Joker', null));
			}
		}
		if (ruleset.toUpperCase() === 'UNO') {
			for (let i = 0; i < 5; i++) {
				this._cards.push(new Card(-4, 'Wild', null), new Card(-5, 'Wild Draw Four', 'X'));
			}
		}
	}

	// Shuffles the deck. I was going to use ES6 for this, but it appears destructuring assignment causes performance hits.
	// Since shuffling might be frequent, I'm not using it here.
	shuffleDeck() {
		for (let i = this._cards.length - 1; i > 0; i--) {
			const j = Math.floor(Math.random() * (i + 1));
			let x = this._cards[i];
			this._cards[i] = this._cards[j];
			this._cards[j] = x;
		}
	}

	// Tests to see if the deck is empty and needs to be restocked.
	emptyCheck(ruleset) {
		if (this._cards.length <= 0) {
			// If the game requires the main deck never be rebuilt in a single game. Return false to indicate nothing was rebuilt.
			if (ruleset === 'TBD') {
				return true;
			}
			// The rules call for the deck to be rebuilt if empty from discard. Arguments can be left empty as we aren't building from scratch.
			// If build is successful, false is returned and the deck is no longer empty. Else, the deck is still is empty and false is returned.
			return !this.buildDeck();
		}
	}

	clone() {
		return new Deck(clone(this));
	}

	toJSON() {
		return JSON.reviveWrapper('new deck card', clone(this));
	}
};

setup.Deck = Deck;

Besides the syntax / misnaming errors the main issue with your original code was the calling of the <array>.forEach() function, the anonymous inner function was unable to reference the class’s instance variables / functions (because they aren’t in Scope). And while you could resolve this issue using the .call() function it is simpler to just replace the forEach() call with a standard for statement.

warning: As I didn’t know exactly what each piece of functionality was meant to achieve I only debugged the above to the point where it would successfully created an instance of the Deck class using your Passage code example.

<<set $newDeck = new setup.Deck(setup.Decktype.STANDARD, 'BLACKJACK', true)>>
1 Like

The .clone() and .toJSON() methods will need more work. The constructor isn’t setup to be able to properly restore an in-progress card game, so either additional work will need to be done after creating new instances to restore the internal state or, preferably, the constructor and properties need to be reworked a bit.

Beyond that, the call to JSON.reviveWrapper() needs to be attempting to instantiate (a) the Deck class and (b) must use it’s external handle setup.Deck, since the restoration process doesn’t have access to the module’s scope. E.g. return JSON.reviveWrapper('new setup.Deck(…));. You’re not going to be able to use the revival parameter easily with the constructor and properties as-is—though, as noted, they should probably be reworked to be able to.

Finally, I know what this is for and it will be a ES Module. Thus, setup shouldn’t be used internally at all, save for within the code string passed to JSON.reviveWrapper(). Rather, use either export const/export class for each or just const/class and a single export { PokerData, DeckType, RuleSet, Card, Deck } at the bottom—though, frankly, those should probably be reworked.