Phaser event issue. Why did my change resolve it? - phaser-framework

Apologies for the title but I'm hoping somebody could explain why I was getting the below error and how come what I changed fixed it.
Overview:
I'm creating a game where you have to shoot 3 targets to win. If you hit all 3 a HIT state is loaded (Consequently if you don't hit all 3 a MISS state is loaded). Once in the HIT state you click the screen which then triggers some game logic that determines if you have won a prize. The game logic is a php script that runs and returns a win or lose variable via ajax. Once returned a state change is triggered to direct you to the WIN state or LOSE state depending on the result.
All 4 state code:
Hit State
MyGame.Hit = function () {};
MyGame.Hit.prototype = {
create: function () {
this.bgd = this.add.image( 0, 0, 'hitBgd');
this.bgd.inputEnabled = true;
this.bgd.events.onInputDown.add( this.gameLogic, this);
},
gameLogic: function () {
var username = 'user';
if (typeof username !== 'undefined') {
var request = new XMLHttpRequest();
request.open('GET', '_/api/logic.php?user=' + username, true);
request.onreadystatechange = function () {
if ((request.readyState === 4) && (request.status === 200)) {
var returnedData = JSON.parse(request.responseText);
if (returnedData.outcome === true) { // Winner
MyGame.game.state.start('Won');
} else { // Loser
MyGame.game.state.start('Lose');
}
}
}
request.send();
} else {
MyGame.game.state.start('Lose');
}
}
};
Miss State:
MyGame.Miss = function () {};
MyGame.Miss.prototype = {
create: function (){
this.bgd = this.add.image( 0, 0, 'missBgd');
this.playAgainBtn = this.add.button((this.game.width / 2) - 145, 235, 'playAgainBtn', this.playAgain, this);
this.exitBtn = this.add.button((this.game.width / 2) + 145, 235, 'exitGameBtn', this.exitGame, this);
},
playAgain: function () {
this.state.start('MainMenu');
},
exitGame: function () {
alert('AWAITING LINK FROM CLIENT');
},
};
Won State
MyGame.Won = function () {};
MyGame.Won.prototype = {
create: function () {
this.bgd = this.add.image( 0, 0, 'wonBgd');
this.playAgain = this.add.button(130, 510, 'wonPlayAgainBtn', this.playAgain, this);
this.exitBtn = this.add.button(395, 510, 'wonExitGameBtn', this.exitGame, this);
},
playAgain: function () {
this.state.start('MainMenu');
},
exitGame: function () {
alert('AWAITING LINK FROM CLIENT');
}
};
Lose State
MyGame.Lose = function () {};
MyGame.Lose.prototype = {
create: function (){
this.bgd = this.add.image( 0, 0, 'loseBgd');
this.playAgainBtn = this.add.button((this.game.width / 2) - 145, 235, 'playAgainBtn', this.playAgain, this);
this.exitBtn = this.add.button((this.game.width / 2) + 145, 235, 'exitGameBtn', this.exitGame, this);
},
playAgain: function () {
this.state.start('MainMenu');
},
exitGame: function () {
alert('AWAITING LINK FROM CLIENT');
}
};
As you can see the MISS, WON and LOSE states are all the same apart from in the WON state my button is called this.playAgain instead of this.playAgainBtn.
The first time you win is fine but if you play again without refreshing the page and get to the HIT state, trigger the game logic and win, you get the below error
Uncaught Error: Phaser.Signal: listener is a required param of add() and should be a Function.
It then points out this.playAgain in my WON state.
I want to know why changing this to this.playAgainBtn removes the error. Should I be destroying events/signals? should I be naming my functions differently even though they are in different states/objects?
also why do I need to call MyGame.game.state.start('Won'); in the ajax request instead of this.state.start('Won'); to switch states?
When this goes live and the odds are adjusted the chance of this happening will be ridiculously slim but at the moment for development everyone is a winner.
Any advice is greatly appreciated.

You add a button called playAgain but the callback method is also called playAgain, they have the same name and that is what is causing the error.
When MyGame.Won.create() is called for the first time, and you add the MyGame.Won.playAgain button then the reference to your original callback function MyGame.Won.playAgain() is retained by that button. But the second time you start the Won-state MyGame.Won.create() is also called, and then the reference to your original callback function was lost because it was effectively overwritten by the button.

Related

Chrome Extension API Calls order and DOM Information

I'm working on an extension that is supposed to extract information from the DOM based specific classes/tags,etc, then allow the user to save the information as a CSV file.
I'm getting stuck on a couple of places and haven't been able to find answers to questions similar enough.
Where I am tripped up at is:
1) Making sure that the page has completely loaded so the chrome.tabs.query doesn't return null a couple of times before the promise actually succeeds and allows the blocksF to successfully inject. I have tried placing it within a settimeout function but the chrome api doesn't seem to work within such the function.
2) Saving the extracted information so when the user moves onto a new page, the information is still there. I'm not sure if I should use the chrome.storage api call or simply save the information as an array and keep passing it through. It's just text, so I don't believe that it should take up too much space.
Then main function of the background.js is below.
let mainfunc = chrome.tabs.onUpdated.addListener(
async(id, tab) => {
if (buttonOn == true) {
let actTab = await chrome.tabs.query({
active: true,
currentWindow: true,
status: "complete"
}).catch(console.log(console.error()));
if (!actTab) {
console.log("Could not get URL. Turn extension off and on again.");
} else {
console.log("Tab information recieved.")
};
console.log(actTab);
let blocksF = chrome.scripting.executeScript({
target: { tabId: actTab[0]['id'] },
func: createBlocks
})
.catch(console.error)
if (!blocksF) {
console.log("Something went wrong.")
} else {
console.log("Buttons have been created.")
};
/*
Adds listeners and should return value of the works array if the user chose to get the information
*/
let listenersF = chrome.scripting.executeScript({
target: { tabId: actTab[0]['id'] },
func: loadListeners
})
.catch(console.error)
if (!listenersF) {
console.log("Listeners failed to load.")
} else {
console.log("Listeners loaded successfully.")
};
console.log(listenersF)
};
});
Information from the DOM is extracted through an event listener on a div/button that is added. The event listener is added within the loadListeners function.
let workArr = document.getElementById("getInfo").addEventListener("click", () => {
let domAr = Array.from(
document.querySelectorAll(<class 1>, <class 2>),
el => {
return el.textContent
}
);
let newAr = []
for (let i = 0; i < domAr.length; i++) {
if (i % 2 == 0) {
newAr.push([domAr[i], domAr[i + 1]])
}
}
newAr.forEach((work, i) => {
let table = document.getElementById('extTable');
let row = document.createElement("tr");
row.appendChild(document.createElement("td")).textContent = work[0];
row.appendChild(document.createElement("td")).textContent = work[1];
table.appendChild(row);
});
return newAr
I've been stuck on this for a couple of weeks now. Any help would be appreciated. Thank you!
There are several issues.
chrome methods return a Promise in MV3 so you need to await it or chain on it via then.
tabs.onUpdated listener's parameters are different. The second one is a change info which you can check for status instead of polling the active tab, moreover the update may happen while the tab is inactive.
catch(console.log(console.error())) doesn't do anything useful because it immediately calls these two functions so it's equivalent to catch(undefined)
Using return newArr inside a DOM event listener doesn't do anything useful because the caller of this listener is the internal DOM event dispatcher which doesn't use the returned value. Instead, your injected func should return a Promise and call resolve inside the listener when done. This requires Chrome 98 which added support for resolving Promise returned by the injected function.
chrome.tabs.onUpdated.addListener(onTabUpdated);
async function onTabUpdated(tabId, info, tab) {
if (info.status === 'complete' &&
/^https?:\/\/(www\.)?example\.com\//.test(tab.url) &&
await exec(tabId, createBlocks)) {
const [{result}] = await exec(tabId, loadListeners);
console.log(result);
// here you can save it in chrome.storage if necessary
}
}
function exec(tabId, func) {
// console.error returns `undefined` so we don't need try/catch,
// because executeScript is always an array of objects on success
return chrome.scripting.executeScript({target: {tabId}, func})
.catch(console.error);
}
function loadListeners() {
return new Promise(resolve => {
document.getElementById('getInfo').addEventListener('click', () => {
const result = [];
// ...add items to result
resolve(result);
});
});
}

Timeout change args without re setTimeout

const timer = setTimeout(({a, b}) => {
console.log(a + b)
}, 3000, {a:1, b:2});
setTimeout(() => {
Object.assign(timer._timerArgs,[{a:2, b:2}])
}, 1000)
// Output: 4
Please have a look at this. What I'm going to do is, going to change the timer args if needed before it's called.
I don't want to use clearTimeout and setTimeout again for this process.
But not sure this is the right way. And plus how can I set the priority per each timer in case the timeout will be the same.
I don't know where you got ._timerArgs from. I've never seen that. Without dipping into undocumented properties (that are only present in node.js), you can do it like this:
const objA = {a:1, b:2};
const timer = setTimeout(({a, b}) => {
console.log(a + b);
}, 500, objA);
objA.a = 2;
objA.b = 3;
This will output 5 which reflects that you changed the property values before the timer callback fired.
Since objects in Javascript are passed by pointer (not copied), you can still modify the object that objA points at any time before the timer fires and see the effect inside the timer callback.
But, then you don't even have to pass the arguments into the setTimeout(). You can just reference a parent scoped variable in the callback:
const objA = {a:1, b:2};
const timer = setTimeout(() => {
console.log(objA.a + objA.b);
}, 500);
objA.a = 2;
objA.b = 3;
This will also output 5.
If you want arbitrary argument modification (not properties embedded in an object), and you want it to be only using supported, standard tools that work in all implementations of Javascript, then you can't do that with just setTimeout(). You could make your own timer wrapper though:
class MyTimer {
constructor(fn, t, ...args) {
this.args = args;
this.fired = false;
this.timer = setTimeout(() => {
this.fired = true;
fn.apply(null, this.args);
}, t);
}
cancel() {
clearTimeout(this.timer);
}
hasFired() {
return this.fired;
}
}
const timer = new MyTimer((...args) => {
console.log("timer callback arguments:", args);
}, 500, "hello", "goodbye");
timer.args = ["ola", "adios", "amor", "amigo"];
You can then modify the array in the .args property at any time before the timer fires and they will be passed to the timer callback.

Call a generator function inside setInterval()

I am trying to call a generator function inside setInterval() method. The objective of this code is it will query a particular server for some data periodically, until it gets a non zero response. Upon getting the response it will call storeAddress() which is a generator function defined in the same file.
The below code is giving me an error like this:
SyntaxError: yield is a reserved word (248:6)
NOTE: I am using react-boilerplate to build my app. The above error is thrown by babel, as far as I can tell through searching internet.
I have tried const query = yeild call (setInterval, function(){magic code}, 10000). This does not give the error, but magic code never gets executed.
I have tried const query = setInterval(function* () {magic code}, 10000) with the same effect as above.
I have tried const query = setInterval(yield call(function(){magic code}, 10000) with same effect as above.
I have tried const query = yield call (setInterval, function(){magic code}, 10000) with same effect as above.
I have tried storeAddress(action.payload, balance).next() inside setInterval(). The control does flow inside storeAddress(), but that function also have generator calls inside, which never gets invoked. In fact nothing after the first generator call inside storeAddress() gets executed in this case.
function* callSaveNewAddress(action){
const selectedNetwork = yield select(selectNetworkId());
let count = 1;
let balance = 0;
const query = setInterval(function () {
getAddressBalance(action.payload, selectedNetwork).then(res =>
{return balance = res.data.mempool_balance});
if(balance > 0) {
yield call (storeAddress, action.payload, balance);
clearInterval(query);
} else if(count == 90) {
clearInterval(query);
console.log("Nothing received in 15 minutes");
}
}, 10000);
}
So how am I suppose to call storeAddress(), which is a generator function, inside a normal function like setInterval()?
const query= function * () {
const runner = yield call(setInterval, () => {
getAddressBalance(action.payload, selectedNetwork).then(res =>
{return balance = res.data.mempool_balance});
if(balance > 0) {
yield call (storeAddress, action.payload, balance);
clearInterval(query);
} else if(count == 90) {
clearInterval(query);
console.log("Nothing received in 15 minutes");
}
}, 1000);
}
try to use the setInterval within a call, passing through parameters the function you want to execute within it.

Error Handling in a Recursive setTimeout Function in Node.js

I'm building my first node.js application on my Raspberry Pi which I am using to control an air conditioner via LIRC. The following code is called when you want to increase the temperature of the AC unit. It sends a LIRC command every 250 milliseconds depending on how many degrees you want to increase it by. This code works as expected.
var iDegrees = 5;
var i = 0;
var delay = 250 // The delay in milliseconds
function increaseTemperatureLoop(){
i++;
//lirc_node.irsend.send_once("ac", "INCREASE", function() {});
console.log(i);
// Call the fucntion/loop again after the delay if we still need to increase the temperature
if (i <= iDegrees){
timer = setTimeout(increaseTemperatureLoop, delay);
}
else {
res.json({"message": "Success"});
}
}
// Start the timer to call the recursive function for the first time
var timer = setTimeout(increaseTemperatureLoop, delay);
I'm having a hard time working with the asynchronous nature of node.js. Once my recursive function is done, I return my json to the browser as shown in the code above. By habit, I feel like I should return the json in a line of code after my initial function call like below but obviously that wouldn't wait for all of the LIRC calls to be successful - it seems silly to have it inside of the function:
var timer = setTimeout(increaseTemperatureLoop, delay);
res.json({"message": "Success"});
What if I have a bunch of other stuff to do after my LIRC sends are done but before I want to send my json back to the browser? Or what if that block of code throws an error...
My second question is, how do I properly wrap the LIRC call in a try/catch and then if there is an error, stop the recursive calls, pass the error back up, and then pass this back to the browser along with the actual error message:
res.json({"message": "Failed"});
For track end of the cycle execution task, you can use a callback.
In order to know whether completed all routine tasks, you can use the task queue.
Monitor and report bugs to the top - it is possible with the help of
three of the same callback.
In general, it is desirable to wrap everything into a single object.
Some example for reflection:
var lircTasks = function __self (){
if (typeof __self.tasks === "undefined") __self.tasks = 0;
__self.func = {
increaseTemperature: function() {
// lirc_node.irsend.send_once("ac", "INCREASE_TEMPERATURE", function() {});
},
increaseFanPower: function() {
// lirc_node.irsend.send_once("ac", "INCREASE_FANPOWER", function() {});
}
}
var fab = function () {
__self.tasks++;
this.i = 0;
this.args = arguments[0];
this.callback = arguments[1];
this.run = function __ref(taskName) {
if (taskName) this.taskName = taskName;
if (this.i<this.args.deg) {
try {
__self.func[this.taskName]();
} catch(e) {
__self.tasks--;
this.callback( {message: "error", error: e, taskName: this.taskName, task: this.args, tasks: __self.tasks} );
}
this.i++;
setTimeout( __ref.bind(this), this.args.delay );
} else {
__self.tasks--;
this.callback({message:"complete", taskName: this.taskName, task: this.args, tasks: __self.tasks});
}
}
}
if ((arguments.length === 2) && (typeof arguments[1] === "function") && arguments[0].deg>0 && arguments[0].delay>=0) {
return new fab(arguments[0], arguments[1]);
}
}
function complete(e) {
console.log(e);
if (e.tasks === 0) console.log({message: "Success"});
}
lircTasks( {deg: 10, delay:100, device: "d1" }, complete ).run("increaseTemperature");
lircTasks( {deg: 20, delay:150, device: "d2" }, complete ).run("increaseTemperature");
lircTasks( {deg: 5, delay:100, device: "d3" }, complete ).run("increaseFanPower");

SP.Ribbon.WebPartComponent.getWebPartAdder() returns undefined

I am using the source at http://blog.symprogress.com/2010/11/ribbon-insert-any-web-part-using-javascript/ to handle user web part button click event.
The function 'addWebPart()' calls a function 'SP.Ribbon.WebPartComponent.getWebPartAdder()' which is supposed to return adder instance but sometimes it returns undefined.
If I add a while loop to wait for the instance value to return correctly, the browser in my VM stalls for some time. When an instance is returned, the browser becomes responsive again. This only happens in some instances.
I am using SharePoint 2013 and the section of code I am referring to is:
addWebPart = function (wpCategory, wpTitle) {
var webPartAdder = SP.Ribbon.WebPartComponent.getWebPartAdder();
while (webPartAdder == undefined)
webPartAdder = SP.Ribbon.WebPartComponent.getWebPartAdder();
// ... Other stuff ...
}
How can this issue be resolved?
For anyone looking for an answer to this question, turns out you have to call 'LoadWPAdderOnDemand()' function then wait for the event '_spEventWebPartAdderReady'. Then query for 'window.WPAdder':
addWebPartDelayed = function (webPartAdder, wpCategory, wpTitle) {
var webPart = findWebPart(webPartAdder, wpCategory, wpTitle);
if (webPart) {
var zone = WPAdder._zones[0];
var wpid = WPAdder._createWebpartPlaceholderInRte();
WPAdder.addItemToPageByItemIdAndZoneId(webPart.id, zone.id, 0, wpid);
}
else
alert('ERROR: Web part not found! Please try again after sometime.');
},
addWebPart = function (wpCategory, wpTitle) {
var webPartAdder = window.WPAdder;
if (webPartAdder == undefined) {
LoadWPAdderOnDemand();
ExecuteOrDelayUntilEventNotified(
function () {
var webPartAdder = window.WPAdder;
addWebPartDelayed(webPartAdder, wpCategory, wpTitle);
},
"_spEventWebPartAdderReady");
}
else
addWebPartDelayed(webPartAdder, wpCategory, wpTitle);
};

Resources