This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript dynamic variable name
I have variables being passed from an onClick event to a JavaScript function. There are four variables in total: two that tell the direction and two that tell the speed change. I want the function to evaluate which direction was chosen (either h_ or v_, for horizontal and vertical) and then apply the necessary speed (either faster or slower).
Right now, I do this successfully by first evaluating the direction and calling a different changeSpeed function depending on the direction which was chosen.
What I would like to do is combine these functions. In the example, $(direction + "speed") is meant to become either h_speed or v_speed.
Is JavaScript equipped to do this? (sincerely, miguel)
var h_speed = 10;
var v_speed = 10;
function changeSpeed(speed, direction){
var direction = direction;
switch (speed)
{
case 'slower':
$($direction + "speed") = $($direction + "speed")*2;
break;
case 'faster':
$($direction + "speed") = $($direction + "speed")/2;
break;
}
}
Here are two versions of my working code:
VERSION 1
var h_speed = 10;
var v_speed = 10;
function identifyDirection(speed, direction){
switch (direction)
{
case 'vertical':
v_changeSpeed(speed);
break;
case 'horizontal':
h_changeSpeed(speed);
break;
}
}
function h_changeSpeed(speed){
switch (speed)
{
case 'slower':
h_speed = h_speed*2;
break;
case 'faster':
h_speed = h_speed/2;
break;
}
}
function v_changeSpeed(speed){
switch (speed)
{
case 'slower':
v_speed = v_speed*2;
break;
case 'faster':
v_speed = v_speed/2;
break;
}
}
VERSION 2
/**
* the changeSpeed functions' arguments
* are placed directly in the function that
* determines whether horizontal or vertical
* speed is changing.
*
*/
function changeSpeed(speed, direction){
switch (direction)
{
case 'vertical':
switch (speed)
{
case 'slower':
v_speed = v_speed*2;
break;
case 'faster':
v_speed = v_speed/2;
break;
}
break;
case 'horizontal':
switch (speed)
{
case 'slower':
h_speed = h_speed*2;
break;
case 'faster':
h_speed = h_speed/2;
break;
}
break;
}
}
Variables are made properties of a variable object. The only variable object you can access by name is the global variable object (this in a global context or window in a browser). So for global variables you could do:
function hSpeed() {...}
function vSpeed(){...}
// Set direction
var direction = 'h';
// Call related function
window[direction + 'Speed']();
However, you can't do that in a function execution context (because ECMA-262 explicitly denies access to function execution and variable objects), you need to make the "variable" a property of an object that you access the same way (i.e. using square bracket notation):
var lib = {};
var lib.hSpeed = function(){...};
var lib.vSpeed = function(){...};
// Set direction
var direction = 'h';
// Call related function
lib[direction + 'Speed']();
Put the 2 variables in a single object like:
var directions = {
horizontal: 1,
vertical: 1
}
Then you'd be able to take the direction out of the arguments and match the child of the object:
function changeSpeed(speed, direction) {
//operate on diections[direction]
}
As far as changing the speed you could do a similar thing with functions in an object, but in your case I'd just suggest using another data structure since the logic doesn't change, only the parameter:
var speedFactor = {
faster: 2,
slower: .5
}
then you'd be able to do everything with:
function changeSpeed(speed, direction) {
directions[direction] = directions[direction] * speedFactor[speed]
}
There are certainly better ways to do what you wanna achieve, but if you wanna have the same thing (note that you shouldn't use global variables, you can use function scoping to make them private, but that's another topic).
var speed = {
h: 10,
v: 10
};
function changeSpeed(speedChange, direction) {
switch (speedChange) {
case 'slower':
speed[direction] *= 2;
break;
case 'faster':
speed[direction] /= 2;
break;
}
}
Now you can change the speed by calling, for example:
changeSpeed("slower", "h");
and access that speed by speed.h or speed.v
Okay...
Tricky but:
//Global namespace
var speeds = {};
speeds['h_speed'] = 10;
speeds['v_speed'] = 10;
function changeSpeed(speed, direction){
var dir = direction.substring(0,1);
var sp = (speed === 'slower') ? 0.5 : 2;
//Still accessible from inside your function
speeds[dir + '_speed'] = speeds[dir + '_speed'] * sp;
}
Will do the work.
Related
I'm not sure if this has been asked already, but my code is a bit different than the usual snake code. I basically have some parts working already, which are:
Generate and render the board
Snake movement (with no eating and dying)
Generate a fruit randomly inside the board
Generate a fruit randomly again after being eaten
My issue now is to make the snake update and re-render itself inside the tick() every time it eats a fruit.
/**
* #returns {TickReturn}
*/
tick() {
// very simple movement code which assumes a single length snake
// no eating or dieing implemented yet.
let oldPosition = { ...this._snake[0]
};
switch (this._direction) {
case Direction.Up:
this._snake[0].y -= 1;
break;
case Direction.Down:
this._snake[0].y += 1;
break;
case Direction.Left:
this._snake[0].x -= 1;
break;
case Direction.Right:
this._snake[0].x += 1;
break;
}
if (this.eating(this._fruit)) {
this._fruit = this.nextFruitFn(); // update the position of the fruit
this.update(); // nothing's being done yet
}
return {
gameOver: this._isGameOver,
eating: false, // currently static, not doing anything yet
changes: [{
position: oldPosition,
tileValue: Tiles.Empty
},
{
position: this._snake[0],
tileValue: Tiles.Snake
},
{
position: this._fruit,
tileValue: Tiles.Fruit
}
]
};
}
eating(pos) {
let hasEaten = false;
this._snake.map((s, idx) => {
// if snake and position merged indices,
// then fruit has been eaten
hasEaten = (s.y === pos.y && s.x === pos.x) ? true : false;
if (hasEaten) {
this._board[pos.x][pos.y] = Tiles.Empty;
// make sure to clear the tile after eating
}
});
return hasEaten;
}
I am lost with two things here:
How do you update the snake array? Do you simply get the current x,y index and reduce both of them by 1? { y: y-1, x: x-1 }?
Once I successfully added the new array inside the snake, how do I properly render it in the TickReturn object? Currently, it is simply updating this._snake[0] at a position.
How do I update the switch case that will include the succeeding parts of the snake? At the moment, it can only move itself fixed at index 0, like so:
switch (this._direction) {
case Direction.Up:
this._snake[0].y -= 1;
break;
case Direction.Down:
this._snake[0].y += 1;
break;
case Direction.Left:
this._snake[0].x -= 1;
break;
case Direction.Right:
this._snake[0].x += 1;
break;
}
Is this also a similar approach if I start considering the walls? Here's a screenshot of what I have so far:
Can I reduce multiple variables in JavaScript?
For example if I have 3 variables:
var foo = 10;
var boo = 15;
var lol = 15;
//Normal syntax:
foo -=1; // -> foo will be 9
boo -=1; // -> boo will be 14
lol -=1; // -> lol will be 14
Is there an option to do it with one line? or better syntax?
For example: foo ,boo ,lol -=1;
No, you cannot. Standard JavaScript does not give this opportunity. The worse thing that you nearly cannot do it functionally as well. lol -=1; or lol--; is just a shortcut for lol = lol - 1;. So if you will try to write a function, which does that for you e.g.:
function reduce() {
for (var i = 0; i < arguments.length; i++) {
arguments[i] = arguments[i] - 1;
}
}
and then call it like
reduce(foo, bar, lol);
it just won't work because you pass primitive numbers (not references). Every time you change the number inside of the function it won't change the number itself but it will return a new number instead.
This could be solved by using some object to store all the variables, e.g.:
var vars = {
foo: 12,
bar: 13,
lol: 14
};
function reduce(variables) {
for (variable in variables) {
if (variables.hasOwnProperty(variable)) {
variables[variable] -= 1;
}
}
return variables;
}
reduce(vars);
But this is not a list of 3 variables, this is kind of a context you attach them to.
If you do the stuff in a global scope (e.g. in a window without wrapping the stuff in a function), you can combine both ways above into one (Window stores all var-declared variables inside):
function reduce(vars) {
var varslist = vars.split(',');
for (var i = 0; i < varslist.length; i++) {
window[varslist[i]] -= 1;
}
}
reduce('foo,boo,lol');
but as soon as you move it to some subcontext it won't work any longer. Also it looks very nasty. I would rather prefer the second solution with vars object representing your variables context.
You can do it with one line, but you still have to repeat the operation:
foo--, bar--, lol--;
Read about the comma operator. It can be useful, but it's usually not very readable.
I wouldn't even combine var statements into 1:
var foo = 1, bar = 2, lol = 3;
because if a var changes, the entire line changes.
I used to do this:
var foo = 1,
bar = 2,
lol = 3;
but that's bad practice too, because deleting foo or lol will change more than just 1 line (because the var prefix or ; suffix).
Sometimes verbosity is good:
var foo = 10;
var boo = 15;
var lol = 15;
foo -= 1;
boo -= 1;
lol -= 1;
I'm trying to figure out why my Google Chrome console is giving me the error "undefined is not a function." I have a hunch, but maybe I'm on the wrong track. My function boxCollision(...) is defined at the bottom of my class. Nearer to the top I have a statement
if (this.boxCollision(this.food.getBBox(), this.body[0].getBBox()))
this.food.translate(this.linkSize, 0);
the first line of which is causing the error I mentioned. I think that's maybe because I haven't yet defined boxCollision, so it's essentially nonexistent. Is that right? The getBBox() functions are recognized because they're from an external JavaScript file.
function snakegame(C, C_w, C_h, spd)
{
/* NOTE TO SELF: C is a Raphel object. Can't find a method to return the height
and width of a Raphael object in the documentation:
http://raphaeljs.com/reference.html#Raphael.
Using C_h and C_w for now, but should probably change it later.
*/
this.linkSize = 50; /* size of a snake unit, in pixels; must divide C_h and C_w */
this.link = C.rect(C_h/2, C_w/2, this.linkSize, this.linkSize);
this.link.attr("fill", "#E9E581");
this.body = [this.link];
this.food = C.rect(randInt(0,C_w/this.linkSize-1) * this.linkSize, randInt(0,C_h/this.linkSize-1) * this.linkSize, this.linkSize, this.linkSize);
if (this.boxCollision(this.food.getBBox(), this.body[0].getBBox()))
this.food.translate(this.linkSize, 0);
this.food.attr("fill","#B43535");
this.maxSnakeSize = C_h * C_w / (this.linkSize * this.linkSize);
/* On instantiation, the snake direction is down and has 1 link */
this.dy = 0;
this.dx = 0;
this.score = 0;
/* Event listener for changing the direction of the
snake with arroy keys on the keyboard
*/
this.redirect = function(dirnum)
{
switch (dirnum)
{
/*
dirnum corresponds to
1 ---> right
2 ---> down
3 ---> left
4 ---> up
*/
case 1:
this.dx = this.linkSize;
this.dy = 0;
break;
case 2:
this.dx = 0;
this.dy = this.linkSize;
break;
case 3:
this.dx = -this.linkSize;
this.dy = 0;
break;
case 4:
this.dx = 0;
this.dy = -this.linkSize;
break;
default: /* never happens */
break;
}
}
this.move = function()
{
if (this.body.length == this.maxSnakeSize)
{
this.destruct();
return;
}
var addLink = false;
var BBhead = this.body[0].getBBox();
if (this.hitWall(BBhead) || this.hitSnake(BBhead))
{
document.getElementById("snakescorediv").innerHTML = "<p>GAME OVER!</p><p>Score: "+ this.score +"</p>";
this.destruct();
return;
}
var BBfood = this.food.getBBox();
if (this.boxCollision(BBhead, BBfood))
{
this.moveFood();
this.score += 10;
document.getElementById("snakescorediv").innerHTML = this.score.toString();
addLink = true;
}
if (addLink)
this.body.push(this.body[this.body.length - 1].clone());
for (var i = this.body.length - 1; i > 0; --i)
{
var prevBB = this.body[i-1].getBBox();
var thisBB = this.body[i].getBBox();
this.body[i].translate(prevBB.x-thisBB.x, prevBB.y-thisBB.y)
}
this.body[0].translate(this.dx, this.dy);
}
this.mover = setInterval(this.move.bind(this), spd);
this.hitWall = function(bb)
{
return bb.x < 0 || bb.x2 > C_w || bb.y < 0 || bb.y2 > C_h;
}
this.hitSnake = function(bb)
{
var retval = false;
for (var i = 1, j = this.body.length; i < j; ++i)
{
var thisbb = this.body[i].getBBox();
if (this.boxCollision(bb, thisbb))
{
retval = true;
break;
}
}
return retval;
}
this.moveFood = function()
{
var bbf = this.food.getBBox(); // bounding box for food
do {
/* tx, ty: random translation units */
tx = randInt(0, C_w / this.linkSize - 1) * this.linkSize - bbf.x;
ty = randInt(0, C_h / this.linkSize - 1) * this.linkSize - bbf.y;
// translate copy of food
this.food.translate(tx, ty);
bbf = this.food.getBBox(); // update bbf
} while (this.hitSnake(bbf));
}
this.boxCollision = function(A, B)
{
return A.x == B.x && A.y == B.y;
}
this.destruct = function()
{
clearInterval(this.mover);
for (var i = 0, j = this.body.length; i < j; ++i)
{
this.body[i].removeData();
this.body[i].remove();
}
this.food.removeData();
this.food.remove();
this.score = 0;
}
}
Put the methods on the prototype to avoid this issue.
This won't work:
function Ctor() {
this.init()
this.init = function() {
console.log('init')
}
}
var inst = new Ctor // Error: undefined is not a function
But this will:
function Ctor() {
this.init()
}
Ctor.prototype.init = function() {
console.log('init')
}
var inst = new Ctor // init
Javascript parses code in two steps: compilation and evaluation.
The first step is compilation. In this step all definitions are compiled but no statement or expressions are evaluated. What this means is that definitions such as:
function a () {}
and:
var x
gets compiled into memory.
In the evaluation phase the javascript interpreter finally starts executing. This allows it to process operators which makes it possible to execute statements and expressions. It is in this step that variables get their values:
var x = 10;
^ ^
| |______ this part now gets assigned to `x` in the evaluation phase
|
this part was processed in the compilation phase
What this means is that for function expressions:
var x = function () {}
while both the variable and function body are compiled in the compilation phase, the anonymous function is not assigned to the variable until the evaluation phase. That's because the = operator is only executed in the evaluation phase (during the compilation phase all variables are allocated memory and assigned the value undefined).
Both the compilation phase and evaluation phase happen strictly top-down.
What some call "hoisting" is simply the fact that the compilation phase happen before the evaluation phase.
One work-around is to simply use a function definition instead of a function expression. Javascript support inner functions so a function defined in another function doesn't exist in the global scope:
function boxCollision (A, B) {
return A.x == B.x && A.y == B.y;
}
this.boxCollision = boxCollision;
Then you can use it at the top of your constructor:
if (boxCollision(this.food.getBBox(), this.body[0].getBBox()))
this.food.translate(this.linkSize, 0);
Note that you can't use this.boxCollision because it's still undefined when you call it.
Another obvious work-around is to of course assign this.boxCollision = function (){} at the top before using it.
Or you could even assign it to the constructor's prototype. Or you can have an init function that gets called at the top (note: function, not method - again the use of a definition instead of a function expression make use of "hoisting").
There are many ways to get around this. But it's useful to know why it's happening to understand what works and what doesn't.
See my answer to this related question for more examples of this behavior: JavaScript function declaration and evaluation order
This question already has answers here:
Prevent JavaScript keydown event from being handled multiple times while held down
(8 answers)
Closed 6 years ago.
I have a same problem like this guy How to disable repetitive keydown in jQuery , it's just that I'm not using jQuery so I'm having hard time translating it to "pure" JavaScript, anyways I have set switch-case of some keys and when I press and hold right arrow key my div is flying. Also if it's not a problem can you tell me what would be the easiest way to stop div movement when I let the right arrow key go, do I have to make a new switch case with clearInterval or?
switch (keyPressed) {
case 39:
setInterval(movFwr, 50);
break;
}
function movFwr() {
if (currPos == 1000) {
a.style.left = 1000 + "px";
} else currPos += 10;
a.style.left = trenutnaPozicija + "px";
}
I'm sorry guys, I've been busy a bit, I'm testing all possibilities and so far I have seen some interesting suggestions. I'll test em all these days and then rate of what ever is that you do on this site. Great community I must say. Thank you all for your help :)
Something like this should do the trick;
var down = false;
document.addEventListener('keydown', function () {
if(down) return;
down = true;
// your magic code here
}, false);
document.addEventListener('keyup', function () {
down = false;
}, false);
I would record the time and prevent action unless enough time has passed, for example using Date.now
var lastPress = 0;
function myListener() {
var now = Date.now();
if (now - lastPress < 1000) return; // less than a second ago, stop
lastPress = now;
// continue..
}
This can be made into a more generic function
function restrict(func, minDuration) {
var lastPress = 0;
return function () {
var now = Date.now();
if (now - lastPress < minDuration) return; // or `throw`
lastPress = now;
return func.apply(this, arguments);
};
}
// now, for example
foo = function () {console.log('foo');};
bar = restrict(foo, 200); // only permit up to once every 200ms
bar(); // logs "foo"
bar(); // void as less than 200ms passed
To ignore key events caused by key repeats, keep track of which keys are pressed during the keydown and keyup events.
var pressedKeys = [];
function keydownHandler(e) {
var isRepeating = !!pressedKeys[e.keyCode];
pressedKeys[e.keyCode] = true;
switch (e.keyCode) {
case !isRepeating && 39:
// do something when the right arrow key is pressed, but not repeating
break;
}
}
function keyupHandler(e) {
pressedkeys[e.keyCode] = false;
}
To stop your div moving, you could keep track of the interval ids in an array, and clear the interval during the keyup event with something like clearInterval(intervalIds[e.keyCode]), but I'd probably switch to using setTimeout() and checking whether they key is down instead. That way, you don't have to keep track of another variable.
var pressedKeys = [];
function keydownHandler(e) {
var isRepeating = !!pressedKeys[e.keyCode];
pressedKeys[e.keyCode] = true;
switch (e.keyCode) {
case !isRepeating && 39:
movFwr();
break;
}
}
function keyupHandler(e) {
pressedkeys[e.keyCode] = false;
}
function movFwr() {
if (pressedKeys[39] && currPos < 1000) {
currPos += 10;
a.style.left = currPos + "px";
setTimeout(movFwr, 50);
}
}
This way, you also automatically stop repeating the function as soon as the div reaches the right edge, instead of waiting for the user to release the arrow key.
Track the last key pressed, if its the same as current ignore it by returning, and use clearInterval to stop the intervals
//global variables
var lastKey = 0;
var moveTimer = [];
//in keydown function
if(lastKey == keyPressed)
return;
switch (keyPressed) {
case 39:
lastKey = keyPressed
moveTimer[keyPressed] = setInterval(movFwr, 50);
break;
}
//in a onkey up function
lastKey = null;
if(typeof(moveTimer[keyPressed]) != "undefined")
clearInterval(moveTimer[keyPressed]);
I'm building an animated scene with HTML, CSS, and Javascript. Currently I have 2 functions for each fish that I want to animate. The first to send it across the screen and the second to reset its position once its off the screen.
Here is what the 2 functions look like...
function fish1Swim1() {
var ranNum = Math.round(Math.random() * 2.5);
var speed = 6000 * ranNum;
var screenW = screenWidth+350;
$('#fish1').animate({
left: -screenW,
}, speed, function () {
fish1Swim2();
});
}
function fish1Swim2() {
var ranNum = Math.round(Math.random() * 7);
var top = ranNum * 100;
var screenW = screenWidth+350;
$('#fish1').css("left", screenW);
$('#fish1').css("top", top);
fish1Swim1();
}
I'm using very similar functions for all the other fish in the scene as well, but I'd like to create 2 arrays at the beginning of the script, one for the IDs and one for speed like so...
var fish=["#fish1","#fish2","#oldBoot"];
var speeds=["6000","7000","5000"];
Then have the function I wrote early run but replacing the fish and speed with the items from the arrays.
How can I do this?
How's this? This provides a generalalised function for all animation/CSS movement. Where animation is concerned, the speed is read from an array, as you wanted.
The function expects two arguments - the first, the ID of the element (minus #); the second, the phase (like in your original code - you had a phase 1 function for fish 1, and a phase 2 function for fish 1).
To make the function work for the other animatory elements, just extend the switch statements.
//element data and corresponding anim speeds
var speeds = {fish1: 6000, fish2: 7000, oldBoot: 5000};
//main animation/movement func
function do_action(id, phase) {
var el, jq_method, method_data, after;
//work out what it is we're doing, and to what, and set some details accordingly
switch (id) {
case 'fish1':
el = $('#fish1');
switch (phase) {
case 1:
jq_method = 'animate';
method_data = [
{left: -screenWidth+350},
Math.round(Math.random() * 2.5) * speeds[id],
function() { do_action('fish1', 2); }
];
break;
case 2:
jq_method = 'css';
method_data = [
{top: Math.round(Math.random() * 700), left: screenWidth+350}
];
after = function() { do_action('fish1', 1); };
break;
}
break;
break;
}
//do actions
el[jq_method].apply(el, method_data);
if (after) after();
}
//and off we go
do_action('fish1', 1);