I'm working on simple evolutionary AI. I need to generate an anonymous function dynamically. For it I have a list of conditions and actions:
var conditions = [
function () { return enemyNear(), },
function () { return mySpeed() > 5; },
function () { return 1 === 1; }];
var actions = [
function () { return alert('walk'); },
function () { return alert('jump'); }
function () { return alert('attack'); } ]
The code chooses one of each to generate a new function:
condition = conditions [Math.floor(Math.random()*conditions .length)];
actions = conditions [Math.floor(Math.random()*actions .length)];
Provided it the chosen condition is enemyNear() and the chosen action is walk(), how can I generate the simple anonymous function?
behavior = function() {
if(enemyNear()) {
walk();
}
}
I can change the way the arrays are saved if needed. How can this be done?
All characters behaviors are called inside a loop like this:
for(i=0,i<chars.length,i++) {
chars[i].behavior.call();
}
The simplest way would be to only put functions inside the arrays:
var conditions = [
enemyNear,
function () { return mySpeed() > 5; },
function () { return 1 === 1;
}];
var actions = [walk, attack, jump];
Then you could define behave as something like:
var behave = function(condition, action) {
if(condition()) {
action();
}
}
And use it like for example:
behave(conditions[2], actions[1]);
Here, 2 and 1 could be a randomly generated number like this:
var getRandomInt = function (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var actRandomly = function (conditions, actions) {
behave(
conditions[getRandomInt(0, conditions.length -1)],
actions[getRandomInt(0, actions.length -1)]
);
};
Call it like:
actRandomly(conditions, actions);
Of course this only presents the idea, and is not neatly organized.
For the fun of it, I created a Basic jsFiddle Demo Version.
Note: Always use var when you are defining a variable. Don't pollute the global scope.
First, inside of the arrays you will need to have a reference to the condition and action function. Right now, you are calling them, so they basically are the same as a useless array like:
conditions = [true, false, true];
actions = [undefined, undefined, undefined];
I would correct it to something like this:
var conditions = [enemyNear,
function() { return mySpeed() > 5 },
function(){ return true; }];
var actions = [walk, attack, jump];
And then you can create a function that generates a behavior:
function generateBehavior(){
var condition = conditions[Math.floor(Math.random() * conditions.length)];
var action = actions[Math.floor(Math.random() * actions.length)];
return function() {
if(condition()) {
action();
}
}
}
You can see it in action on this JSFiddle demo.
Change your arrays from this:
conditions = [enemyNear(), mySpeed()>5, 1=1];
To this:
conditions = [
function() { return enemyNear() }, // or just enemyNear
function() { return mySpeed() > 5 },
function() { return 1 == 1 } // 1 = 1?
];
With your current code, your functions are being called and conditions becomes an array of the outputs of those functions.
condition = [
enemyNear,
function() { return mySpeed() > 5; },
function() { return 1 == 1; }
];
You need to store your functions in the array, not call them, otherwise you are storing the result of the functions in the array.
function enemyNear() {}
function walk() {}
conditions = [enemyNear]
actions = [walk]
behaviour = function() {
if(conditions[randomNumber]()) {
actions[randomNumber]();
}
}
Simple. Use eval()
var behavior;
eval("behavior = function() { if (enemyNear()) walk(); }");
behavior();
Related
I'm working on a pet project, a little front-end library for students. It reads variables/code in a JS file and tests it, outputting some panels. The code itself roughly follows the Jest framework.
My problem is that I'm trying to create a function that watches the execution of other functions, counts them, and lets me access the count.
function watchFunction(funcName){
let originalFunction = window[funcName];
let counter = 0;
// Wrap the function, counts when called
window[funcName] = function(...args){
console.log("watching");
counter++;
return originalFunction(...args);
}
return {
getCount: () => {return counter},
reset: () => {
// Unwrap the function
window[funcName] = originalFunction
}
}
}
This seems to work for methods like Number() or parseInt(), but I don't know how I would go about accessing methods like Math.floor(), or prototype methods like Array.prototype.map().
I've tried passing in the function reference instead of using window["funcNameString"], but that doesn't seem to work.
Does anyone have suggestions or tips for wrapping functions or watching functions like this?
EDIT:
It appears a solution was found!
function watchFunction(obj, fName) {
let counter = 0;
const originalFunction = obj[fName];
obj[fName] = (...args) => {
counter++;
return originalFunction.bind(obj)(...args);
};
return {
removeWatcher: () => (obj[fName] = originalFunction),
resetCount: () => (counter = 0),
getCount: () => counter,
};
}
Example of use:
// Array.prototype.push
const arrayPushWatcher = watchFunction(Array.prototype, "push");
let arr = [];
// 0
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arr.push(1);
// 1
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arr.push(1);
// 2
console.log("Array.prototype.push", arrayPushWatcher.getCount());
arrayPushWatcher.removeWatcher();
arr.push(1);
// 2 (stopped counting)
console.log("Array.prototype.push", arrayPushWatcher.getCount());
How to watch for any function call
Is that what you want? I can also write a block for this function so that it determines whether an object has been passed in or a string. If string -> run this function on window as a property "objectThatStoresFunction".
I've tried playing around with the Function.prototype, but it doesn't really work. So the function turned out a bit more complex.
This code below works both with functions / objects on window Array.prototype.map (Prototype / Class functions)
function watchFunction(objectThatStoresFunction, functionName) {
let counter = 0;
const originalFunction = objectThatStoresFunction[functionName];
objectThatStoresFunction[functionName] = (...args) => {
counter += 1;
return originalFunction(...args);
}
return {
getCount: () => {
return counter
}
}
}
const mathRoundWatcher = watchFunction(Math, 'round');
// 0
console.log(mathRoundWatcher.getCount());
// 1
Math.round(99666.9999999);
console.log(mathRoundWatcher.getCount());
// 2
Math.round(999999999.99);
console.log(mathRoundWatcher.getCount());
function watchFunction(objectThatStoresFunction, functionName, optionalOriginalFunction) {
const self = this;
if (optionalOriginalFunction) {
objectThatStoresFunction = this.window;
functionName = optionalOriginalFunction.name;
}
let counter = 0;
const originalFunction = objectThatStoresFunction[functionName] || optionalOriginalFunction;
objectThatStoresFunction[functionName] = (...args) => {
counter += 1;
return originalFunction.bind(self)(...args);
}
return {
// should it remove the watcher or reset the count?
reset: () => objectThatStoresFunction[functionName] = originalFunction,
getCount: () => {
return counter;
}
}
}
const arrayMapWatcher = watchFunction(Array.prototype, 'map');
// 0
console.log('Array.prototype.map', arrayMapWatcher.getCount());
[-99].map(() => {});
// 1
console.log('Array.prototype.map', arrayMapWatcher.getCount());
const mathRoundWatcher = watchFunction(Math, 'round');
// 0
console.log('Math.round', mathRoundWatcher.getCount());
// 1
Math.round(99666.9999999);
console.log('Math.round', mathRoundWatcher.getCount());
// 2
Math.round(999999999.99);
console.log('Math.round', mathRoundWatcher.getCount());
const alertWatcher = watchFunction(null, null, window.alert);
// 0
console.log('window.alert', alertWatcher.getCount());
// 1
window.alert('1');
console.log('window.alert', alertWatcher.getCount());
// 2
alert('2')
console.log('window.alert', alertWatcher.getCount());
// reset the alertWatcher counter
alertWatcher.reset();
This code above breaks the stacksnippets.com when used with Array.prototype.map for some reason, please see this JsFiddle link:
https://jsfiddle.net/ctbjnawz/3/
Do you mean a method of an instance or object? One way is to create a new function. e.g
function WatchInstanceMethods(instance, functionName){
let originalFunction = window[instance][funcName];
let counter = 0;
window[instance][functionName] = function(...args){
console.log("watching");
counter++;
return originalFunction(...args);
}
return {
getCount: () => {return counter},
reset: () => {
// Unwrap the function
window[funcName] = originalFunction
}
}
}
although adding support for chaining methods will get difficult with more nested methods but you can pass a string for functionName name and split it to have each layer of calling instance for function and repeat the logic above.
I have the following app function that stores data. Users can add new items and it returns the length of the data array.
I am trying to understand why returning the data in a function returns the data including the pushed items, but returning the data.length does not and neither does applying a filter. If I request the data length outside of the app function scope, it does return it including the pushed items.
In my example, getfn , app.data.length and app.data.filter returns the added items, but getcntfn and filterinitialfn does not. Why is this?
var app = (function () {
var data = ["initial"];
function add(x) { data.push(x) }
function getfn() { return data };
function getcntfn() { return data.length };
function filterinitialfn(filter) { return data.filter(x => x == filter) }
return {
add: function (x) { add(x) },
data:data,
getfn: getfn(),
getcntfn: getcntfn(),
filterinitialfn: function(filter) {filterinitialfn(filter)}
}
}());
app.add("added")
console.log("app.getfn", app.getfn) //["initial", "added"]
console.log("app.getcntfn", app.getcntfn) //1 ???
console.log("app.data.length", app.data.length) //2
console.log("app.filterinitialfn", app.filterinitialfn("added")) //[] ???
console.log("app.filterinitial=>", app.data.filter(x => x == "added")) //["added"]
because you are calling the function immediately when returning it so it can't be changed from the initial state, so change it to:
return {
add: function (x) { add(x) },
data:data,
getfn: getfn,
getcntfn: getcntfn,
filterinitialfn: filterinitialfn,
}
of course, when using then you should include parentheses.
For example, the result of getfn: getfn() is evaluated when being initialised, it wont' change the value when calling it.
You'll need to pass the function itself, without calling it so it's evaluated on the call:
getfn: getfn,
getcntfn: getcntfn,
...
Since all your functions have the same name as the return object key, we can use the shorthand to create the object:
var app = (function () {
var data = ["initial"];
function add(x) { data.push(x) }
function getfn() { return data };
function getcntfn() { return data.length };
function filterinitialfn(filterBy) { return data.filter(x => x == "added") }
function filterBy(filterBy) { return data.filter(x => x == filterBy); }
return {
add,
data,
getfn,
getcntfn,
filterinitialfn,
filterBy
}
}());
app.add("added")
console.log("app.getfn", app.getfn())
console.log("app.getcntfn", app.getcntfn())
console.log("app.data.length", app.data.length)
console.log("app.filterinitialfn", app.filterinitialfn())
console.log("app.filterinitial=>", app.data.filter(x => x == "added"))
console.log("app.filterBy", app.filterBy('initial'))
I create some kind of "routing system" for RestfulAPI in function runUrl - on input I put url which contains some parameters (ID values) and I want to find this url in routes array, execute function given for that route with this parameters, and return that result as runUrl result.
function runUrl(url) {
return projects('f505ecfb74','5e735f505c'); // hardcoded mockup
// Question: how this function should look like ?
}
let routes = [
['/cars', cars ],
['/companies/:companyId/cars/:carId/projects', projects ],
['/companies/:companyId/room/:roomId', rooms ],
//...
];
// list of funtions to execute for given url
function cars() { return ["car1","car2"]; }
function projects(companyId,carId) { return [`proj-${companyId}`,`proj-${carId}`]; }
function rooms(companyId,roomId) { return `room-${companyId}-room-${roomId}` }
// ... (more functions)
// TEST
console.log(runUrl('/companies/f505ecfb74/cars/5e735f505c/projects'));
So far I write below function - but I have headache and it doesn't work
function runUrl(url) {
let route = routes.find( r=> url.match(r[0]) );
if(route) {
return route[1](url.match(route[0]));
}
return null;
}
The parameters values are alpha-numeric strings, parameters names in routes array start with : and then are alpha-numeric strings too. The number of parameters is arbitrary.
How function runUrl should look like?
I don't think you can do it with the match method because it's not meant to be equal between two strings, but I think I found an elegant solution to your problem, maybe it will help you :
let routes = [
['/cars', cars ],
['/companies/:companyId/cars/:carId/projects', projects ],
['/companies/:companyId/room/:roomId', rooms ],
];
function runUrl(url) {
let arrUrl = url.split('/') // it will make an array with any value after /
let route = routes.find( r=> arrUrl.length === r[0].split('/').length ); // it supposed to be equal by length
if(route) {
let params = arrUrl.filter(p => p && p.match(/\d+/)) // it will cut only the ids (string with number)
return route[1](params);
}
return null;
}
// list of funtions to execute for given url
function cars() {
return ["car1","car2"];
}
function projects(array) {
return [`proj-${array[0]}`,`proj-${array[1]}`];
}
function rooms(array) {
return `company-${array[0]}-room-${array[1]}`;
}
// ... (more functions)
// TEST
console.log(runUrl('/cars'))
console.log(runUrl('/companies/f505ecfb74/cars/5e735f505c/projects'))
console.log(runUrl('/companies/f505ecfb74/room/5e735f505c'))
Here is my proposition, similar to tomer raitz idea but use regexp to detect parameters
function runUrl(url) {
let result = undefined;
let u = url.split("/");
routes.find( ([route,func]) => {
let r = route.split("/");
if(r.length==u.length && r.every( (el,i) => /^:/.test(el) || el===u[i] ) ){
let params = u.filter((el,i)=> /^:/.test(r[i]));
result = func.call(this,...params);
return true;
}
return false;
})
return result;
}
// -----------
// TEST
// -----------
let routes = [
['/cars', cars ],
['/companies/:companyId/cars/:carId/projects', projects ],
['/companies/:companyId/room/:roomId', rooms ],
//...
];
function cars() { return ["car1","car2"]; }
function projects(companyId,carId) { return [`proj-${companyId}`,`proj-${carId}`]; }
function rooms(companyId,roomId) { return `room-${companyId}-room-${roomId}`; }
// TEST
console.log(runUrl('/companies/f505ecfb74/cars/5e735f505c/projects'));
console.log(runUrl('/cars'))
console.log(runUrl('/companies/ABC123/room/DEF5678'))
This code reacts on variable my_var changes in scope:
$scope.$watch('my_var', function () {
//do somethins
});
Is there a way to check whether my_var is being watched or not? E.g. have somebody already called $scope.$watch function on my_var or not?
You can simply list all of your watchers and check if your variable has a watcher. I don't think there are any other ways to check if $scope.$watch already applied on it.
Here is the code that you can paste even in your console to check your watchers:
function getWatchers(root) {
root = angular.element(root || document.documentElement);
var watcherCount = 0;
function getElemWatchers(element) {
var isolateWatchers = getWatchersFromScope(element.data().$isolateScope);
var scopeWatchers = getWatchersFromScope(element.data().$scope);
var watchers = scopeWatchers.concat(isolateWatchers);
angular.forEach(element.children(), function(childElement) {
watchers = watchers.concat(getElemWatchers(angular.element(childElement)));
});
return watchers;
}
function getWatchersFromScope(scope) {
if (scope) {
return scope.$$watchers || [];
} else {
return [];
}
}
return getElemWatchers(root);
}
console.log(getWatchers())
// this prints the array of watchers, e.g:
// [ {fn: ƒ, last: "my_var", get: ƒ, exp: ƒ, eq: false} ]
The code is taken from "Watching your AngularJS Watchers" by Kent C. Dodds
(The code was modified to print watchers, instead of just displaying how many are used (array length), so be prepared if you have more that 1000+ watchers)
This may help.
let myVar = $scope.my_var;
Object.defineProperty($scope, 'my_var', {
get: function() {
console.log('Someone called me');
return myVar;
},
set: function(val) {
myVar = val;
}
});
$scope.$apply();
If this variable is being watched - you will see something in log. (Of course you may have some access from your js code, but you can debug ... Also this should be $scope, where variable is actuall defined)
You can use something like this:
function isWatched($scope, ref) {
for(var i = 0; i < $scope.$$watchers.length; i++) {
const w = $scope.$$watchers[i];
if (w && w.exp === ref)
return true;
}
return false;
}
$scope.$watch('my_var', function () {
//do somethins
});
var result = isWatched($scope, 'my_var');
I'm creating a module that accepts a data set and an integer n and recursively fills that dataset with n products at a time, after the first call, the function loses its scope and errors out. Why, And what's the best practice for fixing this?
Code:
function ProductFactory(){
var bigArr = [0,1,2,3,4,5,6,7,8,9];
var smallArr = [1,2,3,4];
return {
getProductList: getProductList,
getAllProducts: getAllProducts
};
function getProductList(start, size){ return start < 5 ? bigArr : smallArr }
function getAllProducts(batchSizeRequested, dataSet) {
var startPage = dataSet.length / batchSizeRequested;
var productBatch = this.getProductList(startPage, batchSizeRequested);
dataSet = dataSet.concat(productBatch);
if (productBatch.length === batchSizeRequested)
getAllProducts(batchSizeRequested, dataSet);
}
}
var productGetter = new ProductFactory();
productGetter.getAllProducts(10, []);
1) First of all you shouldn't call getProductList using this, in this case you can just call it as it is, because getProductList is not a function that was assigned directly to this object. It is just a closure that uses local variables in it's code. If you want to call function using this, you should assign it using this, for example this.getProductList = function() {}
2) I don't think there are other scoping problems except redundant this, but I found another issue, though.
You are not actually return anything from your function, plus recursive call does not have an exit point.
Fixed code looks like this.
function ProductFactory(){
var bigArr = [0,1,2,3,4,5,6,7,8,9];
var smallArr = [1,2,3,4];
return {
getProductList: getProductList,
getAllProducts: getAllProducts
};
function getProductList(start, size){ return start < 5 ? bigArr : smallArr }
function getAllProducts(batchSizeRequested, dataSet) {
var startPage = dataSet.length / batchSizeRequested;
var productBatch = getProductList(startPage, batchSizeRequested);
dataSet = dataSet.concat(productBatch);
if (productBatch.length === batchSizeRequested) {
return getAllProducts(batchSizeRequested, dataSet);
} else {
return dataSet;
}
}
}
var productGetter = ProductFactory();
var products = productGetter.getAllProducts(10, []);
console.log(products)
The typical approach to a function call like this is to assign an external value to this (typically called self):
function ProductFactory(){
...
var self = this;
function getAllProducts(batchSizeRequested, dataSet) {
...
getAllProducts.apply(self, [batchSizeRequested, dataSet]);
}
}
In this case, however, please try to remember that you have defined a closure function getAllProducts that is only privately accessible internal to the constructor. Instead you should probably do:
function ProductFactory(){
...
var self = this;
this.getAllProducts = function(batchSizeRequested, dataSet) {
...
self.getAllProducts(batchSizeRequested, dataSet);
}
}