Heavily packed javascript, multiple questions from 219-byte (canvas, bitwise,...) - javascript

I came across a website today. It was a challenge of some developers about making the smallest game possible with certain requirements, and they released the final code of unbelievable size of 219 bytes and runnable on Chrome 17 (one of the requirements). I tried to explore the code (with the explanation provided from the site) and do researches on codes I didn't comprehend. However, the code was packed too heavily to my knowledge, so I am seeking for some helps.
Here is the code, after beautified:
<body id=b onkeyup=e=event onload=
z=c.getContext('2d');
z.fillRect(s=0,0,n=150,x=11325);
setInterval("
0<x%n
&x<n*n
&(z[x+=[1,-n,-1,n][e.which&3]]^=1)?
z.clearRect(x%n,x/n,1,1,s++)
:b.innerHTML='GameOver:'+s
",9)>
<canvas id=c>
The game is named "Tron", and just like classic snake game (without apple).
Anyway, here are my questions:
1) How can they select element with id 'c' without using getElementById() rather than a simple c
Look at the bottom of the code, there is a canvas with <canvas id=c>. I understand that advanced browsers will automatically fix the quotes "" for the id="c". However, when a function is called in onload event, it assigns z = c.getContent('2d'); and directly refers to the canvas without even applying anything such as document.getElementById(). Also the same when they referred to <body id=b>. How is that possible? Under which circumstances am I able to do similarly?
2) Will replacing parameters of a functions by quick assigning variables affect the function at all? If it does, how will it be calculated?
Particularly, take a look of the third line:
z.fillRect(s = 0, 0, n = 150, x = 11325);
I understand to the most basic level, fillRect() requires 4 parameters fillRect(x-cor, y-cor, width, height). However, the code above produces a rectangle 150x150. First of all, I read the description and they claimed that by default, the code would produce a rectangle 300x150, so I assumed that the assigning short functions would actually assign nothing to the parameters of the parent function. I did a test, and replaced n = 150 by n = 200. Weirdly enough, it produces a rectangle 200x150, so once again I agree that n = 150 did assign 150 to that slot. Hence, the code above can be written as:
z.fillRect(0, 0, 150, 11325);
However, another problem comes. Why isn't its height 11325px but 150px instead? I thought it was reset to default because 11325 excessed the handling of browsers, so I changed it to 500 instead; and it produced the same problem.
So generally, when you do short assigning inside a function (for instance: someCustomFunction.call(s = 1)), what really happens there? If nothing happens, why did the rectangle in the example above changed its size when replacing n = 200 but not when x = 200?
Additional question: this question is not the question I am really into, because it is too personal, but I would love to know. The source states that "x is going to be the tron's position, and is set to 11325 (11325 = 75 x 75 + 75 = center of the grid)", how does this kind of one-number-represents-position work?
3) What in the world does it mean?
This is the most headache part to me, because the author packed it too smartly.
&(z[x+=[1,-n,-1,n][e.which&3]]^=1)?
I broke it up, and figured that it was actually z[]^=1 in order to check the condition for the later ? : operators. But first:
What does ^=1 do?
Some people commented on the project and said it could be replaced by --. I think of it as a bitwise AND operator, but what does it have to do with --?
And next:
How did they use [e.which&3] together with the preset array to filter keystroke "i", "j", "k", "l" too effectively?
I notice the array has the length of 4, which is the length of the keys needs filtering. Also, pressing another key rather than "i", "j", "k", "l" also works. It leads me to believe that the &3 does something in filtering the 4 keys, but I don't know how.
Those are all I have to ask. The short but complicated code really excites me, and I really appreciate any help in understanding the code further.

Sidenote: I didn't actually look at the website, so if I'm covering anything they already have, I'm sorry. I realised there was a link to it halfway through my post.
Here is the code, unminified, with some adjustments.
HTML:
<body id=b>
<canvas id=c></canvas>
</body>
Javascript:
document.body.onkeyup = handleOnKeyUp;
document.body.onload = handleOnLoad;
function handleOnKeyUp(event) {
e = event;
}
function handleOnLoad(event) {
score = 0, n = 150, x = 11325;
context = c.getContext('2d');
context.fillRect(0, 0, n, n);
end = setInterval(function () {
if (typeof e === "undefined") return; // This isn't part of the original code - removes errors before you press a button
// Map key that was pressed to a "direction"
// l(76) i (73) j(74) k(75).
// If you AND those values with 3, you'd get
// l(0), i(1), j(2), k(3)
var oneDimDirs = [1, -n, -1, n];
var dirChoice = oneDimDirs[e.which & 3];
// Now add the chosen "direction" to our position
x += dirChoice;
if (x % n <= 0 || n * n <= x || // out of bounds
!(context[x] ^= 1) // already passed through here
) {
b.innerHTML = "GameOver:" + score;
clearInterval(end);
}
else {
score++;
context.clearRect(x % n,x / n, 1 , 1)
}
}, 9);
}
Generally, the code makes heavy use of a couple of hacks:
It makes heavy use of global variables and the fact that assigning to an undefined variable creates a global.
It also makes use of irrelevant function parameters to set or modify variables(this shortens the code length)
The code uses a one-dimensional representation of a two-dimensional space. Instead of moving you in two directions, x and y, it "flattens" the representation of the two-dimensional space(the gameboard) into a one-dimensional array. x represents your position on the overall game board. If you add 1 to x, you will move along the current "line"(until you get to its end). Adding n moves you a whole line forward, so it's similar to moving once down.
Your questions, one by one:
1) How can they select element with id 'c' without using getElementById() rather than a simple c
Kooilnc's answer already covers this. This is called "named access":
http://www.whatwg.org/specs/web-apps/current-work/#named-access-on-the-window-object
2) Will replacing parameters of a functions by quick assigning variables affect the function at all? If it does, how will it be calculated?
You misunderstood what is happening here. First, functions accept values as their parameters, and expressions produce values when they are evaluated. An assignment expression, n = 150, produces the value 150 when evaluated. As a side effect, it also assigns that value to the variable n. So calling a function func(n = 150) is almost equivalent to func(150), with the exception of that side effect. That side effect is used in the code to save space, instead of having to assign the variables on separate lines.
Now, for the canvas WTF - As far as I can tell, a canvas element's default width and height happen to be 300px and 150px. You cannot draw past those limits, so trying to execute z.fillRect(0, 0, 150, 11325); will not draw past the 150 height limit. The code authors use the fact that 11325 is bigger than 150 and is thus safe to pass as a parameter(the rectangle will still be drawn as 150x150). 11325 happens to be the one-dimensional coordinate of the starting position.
3) What in the world does it mean?
I hope I mostly answered it within the code. The inner part is unpacked and commented, which only leaves this part unexplained:
context[x] ^= 1.
(Note, ^ === XOR)
The idea is that the authors are using the context as an array to store which positions they've already visited. There are three reasons for doing it this way:
One, they want to assign some value to mark that they've passed through here. context[x] is usually undefined, undefined ^ 1 === 1, so they're assigning ones to the positions they pass through.
Next, they want to be able to check if they've passed through there. Coincidentally, 1 ^ 1 === 0, which makes it easy to check. Note what I mentioned about assignment expressions - the value of this assignment expression will be either 1, if the position has not been visited before, or 0, if it has. This allows the authors to use it as a check.
Since the check they use is something like expr & expr & expr, expressions which yield true/false or 1/0 values will work the same way as if it was expr && expr && expr(true and false are converted to the numbers 1 and 0 when used in &)
How did they use [e.which&3] together with the preset array to filter keystroke "i", "j", "k", "l" too effectively?
I hope I answered this sufficiently with comments in the code. Basically, using the onkeyup handler, they store the event which has the key that was pressed. On the next interval tick, they check e.which to determine which direction to go in.

About
1) How can they select element with id 'c' without using getElementById() rather than a simple c
In most browsers you can access an element using its ID as a variable in the global namespace (i.e. window). In other words, to retrieve <div id="c"> you can also use c, or window.c.
See also
The other questions I leave to smarter people ;)

1) How can they select element with id 'c' without using getElementById() rather than a simple c
I believe it was an old version of IE that originally allowed access of DOM elements in JS directly by their id, but a number of other browsers followed for compatibility with websites that rely on this. In general it's not a great thing because you can get weird bugs if you create variables with the same names as the element ids (not that that matters for this example).
2) Will replacing parameters of a functions by quick assigning variables affect the function at all? If it does, how will it be calculated?
You said within your point (2) that z.fillRect(s = 0, 0, n = 150, x = 11325); can be written instead as z.fillRect(0, 0, 150, 11325);, but that is true only in as far as having the function call to fillRect() work the same way. This change would break the overall program because the assignment statements are needed to create and set the n and x global variables.
In a general sense in JS the assignment operator = not only assigns the value from the right-hand operand to the variable on the left, but the whole expression gives the value of the right-hand operand. So someFunc(x = 10) sets the variable x to 10 and passes the value 10 to someFunc().
In the case of that fillRect(), the n and x variables have not previously been declared, so assigning a value to them creates them as globals.
3) What in the world does it mean? &(z[x+=[1,-n,-1,n][e.which&3]]^=1)?
Well, it's (obviously) kind of complicated. To answer the specific questions you mentioned about parts of it:
How did they use [e.which&3] together with the preset array to filter keystroke "i", "j", "k", "l" too effectively?
The keys "i", "j", "k", and "l" are a good choice for up/left/down/right controls in a game because their physical layout on a standard keyboard corresponds to up/left/down/right but also because those letters belong next to each other in the alphabet so they have contiguous keycodes too: 73, 74, 75, 76. So when you take those keycodes and do a bitwise AND with &3 you get the values 1, 2, 3, 0 which then act as appropriate indices into the [1,-n,-1,n] array.
What does ^=1 do? ... I think of it as a bitwise AND operator ...
It's not a bitwise AND operator. ^ is a bitwise XOR, and ^= is the bitwise XOR assignment operator. That is, x ^= 1 is equivalent to x = x ^ 1.

Related

how to use the 'map' function using Brackets and p5.js?

I'm currently taking a course on intro to computer programming. It's an online course and doesn't have much help when you're stuck.
I'm using Brackets and p5.js.
I unfortunately don't know how to use the map function, I've tried different possibilities, but so far I haven't been able to solve the question below:
When the mouse button is pressed:
- Use the 'random' function to produce random values ranging from 2 to 14.
- Assign the output to Secure_vault_key0
When the mouse button is released:
- Use the 'random' function to produce random values ranging from 2 to 8.
- Assign the output to Secure_vault_key1
When any key is pressed:
- Make Secure_vault_key2 equal to the value of 'key'
When the mouse button is pressed:
- Use the 'map' function to scale mouseX to values ranging from 14 to 77.
- Assign the output to Secure_vault_key3
When the mouse button is pressed:
- Use the 'map' function to scale mouseY to values ranging from 22 to 76.
- Assign the output to Secure_vault_key4
Whilst the mouse is being dragged:
- Use the 'map' function to scale mouseX to values ranging from 14 to 80.
- Assign the output to Secure_vault_key5
This time you'll need to create the relevant event handlers yourself.
There are many possible ways of investigating this case, but you
should use ONLY the following commands:
- The assignment operator aka. the equals sign !
- mouseX, mouseY
- key, keyCode
- random
- map
*/
//declare the variables
var Secure_vault_key0;
var Secure_vault_key1;
var Secure_vault_key2;
var Secure_vault_key3;
var Secure_vault_key4;
var Secure_vault_key5;
function preload()
{
//IMAGES WILL BE LOADED HERE
}
function setup()
{
createCanvas(512,512);
//initialise the variables
Secure_vault_key0 = 0;
Secure_vault_key1 = "";
Secure_vault_key2 = "";
Secure_vault_key3 = 0;
Secure_vault_key4 = 0;
Secure_vault_key5 = 0;
}
///////////////////EVENT HANDLERS///////////////////
//Create event handlers here to open the safe ...
function mouseDragged()
{
console.log("mouseDragged", mouseX, mouseY);
Secure_vault_key5 = map(mouseX, 14, 80);
}
function mousePressed()
{
console.log("mousePressed");
Secure_vault_key0 = random(2,14);
Secure_vault_key3 = map(mouseX, 14, 76);
}
function keyPressed()
{
console.log("keyPressed");
Secure_vault_key2 = key;
}
function mouseRealesed()
{
console.log("mouseReleased");
Secure_vault_key1 = random(2,8);
}
///////////////DO NOT CHANGE CODE BELOW THIS POINT///////////////////
Check out the p5.js reference page for map() : https://p5js.org/reference/#/p5/map
It looks like you're only using three parameters in your map functions, and it requires five (with an optional sixth parameter). The five necessary parameters are as follows (in this order):
variable you want to map the value of (in your case mouseX)
the current minimum of that value (if you're working with mouseX, you most likely want to use zero)
the current maximum of that value (again, if you're working with mouseX, you probably want to use the width of your canvas here, which you can do using the built in width variable or hardcoding in the value)
the new minimum value you want to be stored in one of your Secure_vault variables
the new maximum value you want
It might seem silly or redundant that you need to specify the current maximum and minimum of a built-in variable like mouseX, but it's just one of those programming things where the computer demands as much explicit instruction as possible in order to properly execute your instructions.
Look at the second example on the reference page linked above for a good example of using the map() function with mouseX. And if you're going to be working in p5.js beyond this project these reference pages are a really great place to look for answers to all kinds of questions!
update!! I just corrected in below code. use height and width
/*oh I also met such issues, I know I have to define five arguments, I just can't understand what the second and third arguments should be, I wrote 0,mouseY/X, but it's not correct.
only SecretStoreCombination0 is correct, I've used up the chances today, I will try tomorrow!*/
function keyReleased(){
SecretStoreCombination0 = random()*10+1;
SecretStoreCombination2 = key;
}
function mouseReleased(){
SecretStoreCombination1 = map(mouseY,0,height,3,13);
SecretStoreCombination4 = map(mouseY,0,height,20,79);
}
function mousePressed(){
SecretStoreCombination3= map(mouseX,0,width,6,69);
SecretStoreCombination5= map(mouseY,0,height,16,73);
}

Javascript find intersections given two functions

I am trying today to solve for the coordinates where functions intersect. I am using the nerdarmer library right now, but it only returns only one solution out of all the possible solutions. For example, I want the code below to print -1, 0, 1 but it only outputs 0. Another example is if I want to find intersections between y = 0 and sin(x), I want the output to be ..., (-2pi, 0), (-pi, 0), (pi, 0), (2pi, 0), (3pi, 0), ...
intersect("x^3", "x")
function intersect(f1, f2){
var x = nerdamer.solve('f1', 'f2');
console.log(x.toString());
}
Is there any way to get all the possible solutions?
You've misunderstood the syntax of nerdamer.solve
The first argument is the formula or equation.
The second argument is the variable to solve for.
If the first argument is not an equation, it is assumed to be equal 0.
In your case x^3=0. which only has the solution 0.
If you want to intersect the equations you will need to set them equal to each other in the first argument. And in the second argument just specify x. (or change it to suit your needs if required).
intersect("x^3", "x")
function intersect(f1, f2){
var x = nerdamer.solve(f1+"="+f2, "x");
console.log(x.toString()); //outputs [0,1,-1]
}
Edit:
In your example you also directly put the strings "f1" and "f2" into the solve function which seems to just solve f=0;

Reverse array looping - Going back to the last index when index 0 is surpassed [duplicate]

This question already has answers here:
JavaScript % (modulo) gives a negative result for negative numbers
(13 answers)
Closed 4 years ago.
I'm currently extending the Array.prototype to include a simple 'pointer' system, as I can move through there on several different spots without looping.
Array.prototype.current = 0;
This pointer can move both forwards and backwards through the array, using the following method to go forwards:
Array.prototype.next = function () {
return this[(this.current + 1) % this.length];
};
This line of code allows the pointer to be increased by one, unless it reaches the final index, in which case if it is increased again, it will return to index 0 instead of going out of bounds (because of the modulo).
However, now I want the exact same, but when decreasing the pointer instead. This would create something similar, i.e.:
Array.prototype.prev = function () {
return this[(this.current - 1) /* similar to modulo here */];
};
What I want here is that the pointer gets decreased by 1 under any normal circumstance. However, if the pointer was 0, it needs to go back to the last index of the array instead, so effectively reverse looping around. But, I want this with as minimal code as possible, like the modulo solution for increasing the pointer but looping it back to start if it reaches the end.
Is there a short-hand solution like the modulo solution possible for this?
EDIT
In response to the duplication flag. The referenced question and answers there indeed do apply to my question, so I understand the marking. However, it does not reference such a case of looping a list backwards and looping from front to back like this case (without branches, that is). After a considerable amount of time I hadn't run into this solution, so I'd think this question would result in positive impact for others running into a similar issue like mine.
you can try this
Array.prototype.prev = function () {
return this[(this.current - 1 + this.length) % this.length]
};

JsPerf: ParseInt vs Plus conversion

I've try to probe that plus (+) conversion is faster than parseInt with the following jsperf, and the results surprised me:
Parse vs Plus
Preparation code
<script>
Benchmark.prototype.setup = function() {
var x = "5555";
};
</script>
Parse Sample
var y = parseInt(x); //<---80 million loops
Plus Sample
var y = +x; //<--- 33 million loops
The reason is because I'm using "Benchmark.prototype.setup" in order to declare my variable, but I don't understand why
See the second example:
Parse vs Plus (local variable)
<script>
Benchmark.prototype.setup = function() {
x = "5555";
};
</script>
Parse Sample
var y = parseInt(x); //<---89 million loops
Plus Sample
var y = +x; //<--- 633 million loops
Can someone explain the results?
Thanks
In the second case + is faster because in that case V8 actually moves it out of the benchmarking loop - making benchmarking loop empty.
This happens due to certain peculiarities of the current optimization pipeline. But before we get to the gory details I would like to remind how Benchmark.js works.
To measure the test case you wrote it takes Benchmark.prototype.setup that you also provided and the test case itself and dynamically generates a function that looks approximately like this (I am skipping some irrelevant details):
function (n) {
var start = Date.now();
/* Benchmark.prototype.setup body here */
while (n--) {
/* test body here */
}
return Date.now() - start;
}
Once the function is created Benchmark.js calls it to measure your op for a certain number of iterations n. This process is repeated several times: generate a new function, call it to collect a measurement sample. Number of iterations is adjusted between samples to ensure that function runs long enough to give meaningful measurement.
Important things to notice here is that
both your case and Benchmark.prototype.setup are the textually inlined;
there is a loop around the operation you want to measure;
Essentially we discussing why the code below with a local variable x
function f(n) {
var start = Date.now();
var x = "5555"
while (n--) {
var y = +x
}
return Date.now() - start;
}
runs slower than the code with global variable x
function g(n) {
var start = Date.now();
x = "5555"
while (n--) {
var y = +x
}
return Date.now() - start;
}
(Note: this case is called local variable in the question itself, but this is not the case, x is global)
What happens when you execute these functions with a large enough values of n, for example f(1e6)?
Current optimization pipeline implements OSR in a peculiar fashion. Instead of generating an OSR specific version of the optimized code and discarding it later, it generates a version that can be used for both OSR and normal entry and can even be reused if we need to perform OSR at the same loop. This is done by injecting a special OSR entry block into the right spot in the control flow graph.
OSR entry block is injected while SSA IR for the function is built and it eagerly copies all local variables out of the incoming OSR state. As a result V8 fails to see that local x is actually a constant and even looses any information about its type. For subsequent optimization passes x2 looks like it can be anything.
As x2 can be anything expression +x2 can also have arbitrary side-effects (e.g. it can be an object with valueOf attached to it). This prevents loop-invariant code motion pass from moving +x2 out of the loop.
Why is g faster than? V8 pulls a trick here. It tracks global variables that contain constants: e.g. in this benchmark global x always contains "5555" so V8 just replaces x access with its value and marks this optimized code as dependent on the value of x. If somebody replaces x value with something different than all dependent code will be deoptimized. Global variables are also not part of the OSR state and do not participate in SSA renaming so V8 is not confused by "spurious" φ-functions merging OSR and normal entry states. That's why when V8 optimizes g it ends up generating the following IR in the loop body (red stripe on the left shows the loop):
Note: +x is compiled to x * 1, but this is just an implementation detail.
Later LICM would just take this operation and move it out of the loop leaving nothing of interest in the loop itself. This becomes possible because now V8 knows that both operands of the * are primitives - so there can be no side-effects.
And that's why g is faster, because empty loop is quite obviously faster than a non-empty one.
This also means that the second version of benchmark does not actually measure what you would like it to measure, and while the first version did actually grasp some of the differences between parseInt(x) and +x performance that was more by luck: you hit a limitation in V8's current optimization pipeline (Crankshaft) that prevented it from eating the whole microbenchmark away.
I believe the reason is because parseInt looks for more than just a conversion to an integer. It also strips any remaining text off of the string like when parsing a pixel value:
var width = parseInt(element.style.width);//return width as integer
whereas the plus sign could not handle this case:
var width = +element.style.width;//returns NaN
The plus sign does an implicit conversion from string to number and only that conversion. parseInt tries to make sense out of the string first (like with integers tagged with a measurement).

Javascript bit map for simple collision detection

I need help/advice for improving/commenting my current design please :)
This relates to collision detection in a simple game: Dynamic bodies (moving ones) might collide with static bodies (i.e. ground, walls). I'm porting my Obj-C model to Javascript and am facing memory/performance questions as to my way of implementing this.
I'm using a very basic approach: An array of arrays represents my level in terms of physic opacity.
bit set to 0: Transparent area, bodies can go through
bit set to 1: Opaque area, bodies collide
Testing the transparency/opacity of a pixel simply goes as follows:
if (grid[x][y]) {
// collide!
}
My knowledge of JS is pretty limited in termes of performance/memory and can't evaluate how good this approach is :) No idea of the efficiency of using arrays that being said.
Just imagine a 1000-pixel wide level that's 600px high. It's a small level but this already means an array containing 1000 arrays each containing up to 600 entries. Besides, I've not found a way to ensure I create a 1bit-sized element like low-level languages have.
Using the following, can I be sure an entry isn't something "else" than a bit?
grid[x][y] = true;
grid[x][y] = false;
Thanks for your time and comments/advices!
J.
If you have an 1000x600 grid, you can guarantee you have at least 601 arrays in memory (1001 if you do it the other way round).
Rather than doing this, I would consider using either 1 array, or (preferrably) one object with a mapping scheme.
var map = {};
map["1x1"] = 1;
map["1x3"] = 1;
// assume no-hits are empty and free to move through
function canGoIn(x, y) {
return map.hasOwnProperty(x + "x" + y);
};
Alternately;
var map = [];
var width = 600;
map.push(0);
map.push(1);
// etc
function canGoIn(x, y) {
return map[(x * width) + y] == 1;
}
a boolean value won't be stored as just one bit, and that is also true for any other language I know (C included).
If you are having memory issues, you should consider implementing a bitarray like this one: https://github.com/bramstein/bit-array/blob/master/lib/bit-array.js
You will have to make your 2d array into a simple vector and converting your x, y coordinates like this: offset = x + (y * width);
Browsing an array will still lead to a multiplication to evaluate the offset so using a vector is equivalent to arrays.
But I suspect that calling a function (in case your using a bit-array) and doing some evaluations inside will lead to poorer performances.
I don't think you can gain performances and save memory at the same time.

Categories

Resources