Jump to content

Changing a passed variable


MrOrphanage
 Share

Recommended Posts

Hi all,

It's your friendly neighborhood noob here again. I have a problem that is stumping me and I can't for the life of me figure out how to fix it. What I'm trying to do, is use a function to update a text variable and an integer that I pass to it. It needs to work on multiple separate text/integer variables, so I can't just outright say "this.variable = 5" because sometimes it needs to change this.variable1 and sometimes it needs to change this.variable2. Below is my test code to try to create a function that alters the variables I pass it:

var mainState = {
	preload: function() {
		game.load.spritesheet('button', 'assets/whiteDice.png', 200, 200);
	},
	
	create: function() {
		// Set background color
		game.stage.backgroundColor = '#3498db';
		
		// Variables
		this.status1 = 'TEST1';
		this.status2 = 'TEST2';
		this.value1 = 0;
		this.value2 = 5;
		
		// Text to display
		this.status1Text = game.add.text(0, 0, this.status1, 
			{ font: '20px Arial', fill: '#ffffff' });
		
		this.status2Text = game.add.text(game.world.width, 0, this.status2, 
			{ font: '20px Arial', fill: '#ffffff' });
		this.status2Text.anchor.setTo(1, 0);
		
		this.value1Text = game.add.text(0, 40, this.value1,
			{ font: '20px Arial', fill: '#ffffff' });
		
		this.value2Text = game.add.text(game.world.width, 40, this.value2,
			{ font: '20px Arial', fill: '#ffffff' });
		this.value2Text.anchor.setTo(1, 0);
		
		// Button to click
		this.changeButton = game.add.sprite(game.world.width/2, game.world.height/2, 'button');
		this.changeButton.anchor.setTo(0.5);
		this.changeButton.scale.setTo(.25);
		this.changeButton.inputEnabled = true;
		this.changeButton.events.onInputDown.add(this.click, this);
	},
	
	update: function() {
		
	},
	
	click: function() {
		this.change(this.status1Text, this.value1);
		this.change(this.status2Text, this.value2);
	},
	
	// Function to change text and values
	change: function(status, value) {
		status.text = 'THIS TEXT CHANGED!';
		value = 3;
	},
}
// Create a 500 px by 340 px game in the 'gameDiv' of the index.html
var game = new Phaser.Game(500, 340, Phaser.AUTO, 'gameDiv');

// Add the 'mainState' to Phaser and call it 'main'
game.state.add('main', mainState);

// Start 'mainState'
game.state.start('main');

Please someone, tell me what I'm doing wrong. If I'm passing this.value1 and this.value2 to the change function, why is it not updating those values to 3 like I'm expecting it to. NOTE: I had a similar issue when I was trying to update the text of status1Text and status2Text objects. I was initially holding the text for them to display in separate text variables and then attempting to pass those variables in to change them. When it didn't work, the above was my work-around. Now I'm stuck trying to update the values as well.

Link to comment
Share on other sites

JS's lovely rules on pass by reference vs pass by value strike again!

You can dynamically access properties of an object using square bracket notation i.e.

var obj = {
  value1: 20,
  value2: 30,
  value3: 40
}

console.log(obj)
// {value1: 20, value2: 30, value3: 40}

var num = 1
obj['value' + num] = 'there are'
obj['value' + (num + 1)] = 'no stupid'
obj['value' + (num + 2)] = 'questions'

console.log(obj)
// {value1: 'there are', value2: 'no stupid', value3: 'questions'}

In your specific case I agree with using a helper (this.change) to do this and keep things a little DRYer. You just probably need to pass the id and then the value to change it to, something like:

change (id, value) {
  this['status' + id + 'Text'] = value
}

// Usage
this.change(1, 'hello text')

Note that I'm not suggesting that this is a particularly good pattern, but if it solves a problem and you're ok with it, then go for it!

Link to comment
Share on other sites

Ah, I think I understand a bit about the "pass by reference" vs. "pass by value" issue - thanks for the info! I'm still a bit confused on how exactly to circumvent the problem here though. I see you mentioned passing the "id", but I haven't been successful in getting that to work yet.

EDIT: My new example code (that contains objects to sort out pass-by-value issues) that still isn't behaving as I'd expect:

var mainState = {
	preload: function() {
		game.load.spritesheet('button', 'assets/whiteDice.png', 200, 200);
	},
	
	create: function() {
		// Set background color
		game.stage.backgroundColor = '#3498db';
		
		// Variables
		this.object1 = {
			status: 'TEST',
			value: 0
		};
		this.object2 = {
			status: 'TEST2',
			value: 5
		};
		
		// Text to display
		this.status1Text = game.add.text(0, 0, this.object1.status, 
			{ font: '20px Arial', fill: '#ffffff' });
		
		this.status2Text = game.add.text(game.world.width, 0, this.object2.status, 
			{ font: '20px Arial', fill: '#ffffff' });
		this.status2Text.anchor.setTo(1, 0);
		
		this.value1Text = game.add.text(0, 40, this.object1.value,
			{ font: '20px Arial', fill: '#ffffff' });
		
		this.value2Text = game.add.text(game.world.width, 40, this.object2.value,
			{ font: '20px Arial', fill: '#ffffff' });
		this.value2Text.anchor.setTo(1, 0);
		
		// Button to click
		this.changeButton = game.add.sprite(game.world.width/2, game.world.height/2, 'button');
		this.changeButton.anchor.setTo(0.5);
		this.changeButton.scale.setTo(.25);
		this.changeButton.inputEnabled = true;
		this.changeButton.events.onInputDown.add(this.click, this);
	},
	
	update: function() {
		
	},
	
	click: function() {
		this.change(this.object1);
		this.change(this.object2);
	},
	
	// Function to change text and values
	change: function(object) {
		object.status = 'THIS TEXT CHANGED!';
		object.value = 3;
	},
}
// Create a 500 px by 340 px game in the 'gameDiv' of the index.html
var game = new Phaser.Game(500, 340, Phaser.AUTO, 'gameDiv');

// Add the 'mainState' to Phaser and call it 'main'
game.state.add('main', mainState);

// Start 'mainState'
game.state.start('main');

 

Link to comment
Share on other sites

Yeah you're still passing by value, due to way JS works you're no longer mutating the variable referenced by `this.objectX`, you're just mutating the local variable.

Here's a simpler test you can fire into jsbin (or just use node to run it)

class Foo {
  constructor () {
    this.name1 = 'jon'
    this.name2 = 'dave'
  }

  click () {
    this.change(this.name1)
  }

  change (name) {
    name = 'XXX'
    console.log(name)
  }
}

var f = new Foo()
f.click()
// XXX

console.log(f)
// Foo {name1: 'jon', name2: 'dave'}

I've tried to name the variables the same, so when `click` gets executed it calls `this.change` but passes the value of `this.name1` to it, not a reference, and there is no way to pass this by reference (as you might do with a pointer in some other languages).

To make things even more confusing for you, get a load of what happens when you try and do this:

class Foo {
  constructor () {
    this.names = [
      'jon',
      'dave'
    ]
  }

  update () {
    this.change(this.names)
  }

  change (name) {
    name.push('XXX')
    console.log(name)
  }
}

var f = new Foo()
f.update()
// ['jon', 'dave', 'XXX']

console.log(f)
// Foo {names: ['jon', 'dave', 'XXX']}

So thats fun! yay JS! :( You can do the same if you pass an object and assign to its members, but be careful doing this, be very careful:


// WARNING: ANTI-PATTERN AHEAD
// HERE BE DRAGONS

class Foo {
  constructor () {
    this.user1 = {
      name: 'jon',
      score: 40
    }
  }

  click () {
    this.change(this.user1)
  }

  change (user) {
    user.name = 'XXX'
    user.score = 50
  }
}

var f = new Foo()
f.click()

console.log(f)
// Foo {user1:{name: 'jon', score: 50} }

To solve your specific problem I'd pass an id so you can reference the 'index' of the variables you want to change, I'd additionally allow the function to accept the value you're changing which adds some degree of pureness and transparency back to your function. I'd also use an array to hold that data rather than named variables but I'm guessing you've simplified something else for this question, so, to change my first example (which is a simplified version of yours):

class Foo {
  constructor () {
    this.user1 = {
      name: 'jon',
      score: 40
    }
    
    this.user2 = {
      name: 'dave',
      score: 10
    }
  }

  click () {
    this.change('1', {
      name: 'jon',
      score: 60
    })
  }

  change (id, value) {
    this['user' + id] = value
  }
}

var f = new Foo()
f.click()

console.log(f)
// Foo {
//  user1: { name: 'jon', score: 60 },
//  user2: { name: 'dave', score: 10 } 
// }

Of course you'd probably want to be a little smarter about what happens in your update, if you're using objects then maybe a better way than stamping over variables would be to do a change on just a set of values, Object.assign (or object spread if you're transpiling from ES2017):

click () {
  this.change('1', {
    score: 100
  })
}

change (id, value) {
  Object.assign(this['user' + id], value)
}

f.click()

console.log(f)
// Foo {
//   user1: { name: 'jon', score: 100 },
//   user2: { name: 'dave', score: 10 } 
// }

All of these methods I'd consider a little dangerous.

Although, if you're going to have some logic for mutating some of your fields then I'd prefer to extract that totally and make the function pure, whilst it's easy to see what is going on at the moment as your app grows the logic there might become more complex, for example, if it wasn't a change but if it updated values:

const updateScore = (record, value) => {
  record.score += value
  return record
}

class Foo {
  constructor () {
    this.user1 = {
      name: 'jon',
      score: 40
    }
  }

  click () {
    this.user1 = updateScore(this.user1, 20)
  }
}

var f = new Foo()
f.click()

console.log(f)
// Foo { user1: { name: 'jon', score: 60 } }

This has some advantages in that the updateScore method is now pure, i.e. given the same inputs it'll always return the same output (this is NOT true of our earlier fixes) which means its testable and I can reuse that function anywhere else in my codebase I want to change a record (in this case I've called them users), this is also NOT true of tying the implementation to the class (its an object, but it attempts to mimic class behaviour by mucking with `this`) we've created.

But it's up to you to implement the patterns that make most sense to you, even the example I've listed as an anti-pattern ahead is alarmingly prevalent in codebases, but, I'd say use that method at your own peril.

Does the pass by value vs pass by reference thing make sense to you? Check out the square bracket notation too, you'll need to grok that if you're going to use named keys to hold your variables in your object.

Link to comment
Share on other sites

Yeah, you've done a great job explaining the pass-by-reference mistakes I've made. I see why neither of my attempts above would work in JS. However, I'm still unable to cobble together anything that Phaser can properly interpret. It runs without errors but doesn't update the proper variables. Even when I made changes to the change function like so:

this.status1 = 'TEST1';
this.status2 = 'TEST2';

this.change(1);
this.change(2);

change (id) {
    this['status' + id] = "THIS TEXT CHANGED!";
}

Phaser doesn't like the bracket notation there for me. I'm not sure what "grok"ing is/means (you mentioned it above when discussing bracket notation) but maybe that's the issue?

Perhaps I'm just approaching this from the wrong way in general. What I'm trying to achieve can't be this difficult. There must be a better way. Essentially I have a game that's like poker only the player can see the computer's hand at all times. Each time new card(s) are added, the strength of what the current hand "type" is should update. There are 2 text objects displayed on screen that display the hand type. I have a global variable for the player's current hand strength (pHandStatus) and another variable for the computer's hand strength (cHandStatus) and I just want the function that analyzes what type of hand it is to update the text so that it displays the new hand type. So, I already have conditionals that check what hand type it is (Full House, Straight, etc.) but I need to update those variables so the text changes from "Pair" to "Three of a Kind" or whatever the case may be. Unfortunately, I just can't hard-code that if X conditions are met, then the global variable "pHandStatus = 'FULL HOUSE'" because sometimes the function will be analyzing to changes pHandStatus and sometimes it will be analyzing to change cHandStatus.

So, I'm not sure if I'm making all this more difficult on myself because there is a simpler method or not. I definitely get that pass-by-reference is not a thing in JS, but I don't think I'm implementing the workarounds you've suggested correctly because they're just not working in my Phaser code.

EDIT: For the record, I should also mention that I considered having the function simply return the text values and updating the global variables by assigning a new value wherein I call the function (IE. pHandStatus = change()) but that's not a possibility either because the function I'm calling needs to not only update the displayed hand type text, but it simultaneously needs to update a separate integer value that indicates the particular hand's strength.

Link to comment
Share on other sites

Okay, I think I actually just found one of my issues with the bracket notation. I missed a pair of single quotes in one of the brackets. I actually got your suggestions to work I think. Will test further but at least my trial run seemed to work once, so that's progress!

EDIT: Alright, I've got it working now. It appears I've been properly passing the object's reference (via passing the value) for a little while now. I just wasn't seeing the results updated in my game because I was properly updating the global variables, but the text that gets displayed based on the variables' value wasn't properly updating. Once I added some lines in the update statement that continuously updates the text based on those changing values, it now displays things properly. I guess this is a lesson in just debugging using the console rather than trying to get "fancy" and displaying values on screen. Thanks so much for taking the time out to help me understand this matt!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...