Which is better for massive amounts of Variables - javascript

So after reading massively into JS the past few days (I'm just starting with it) I have come upon 3 viable options for my project, which is a game btw. There will be about 200 items in my game, with a total of about 30 stats/properties. But since the program has to know that a Sword doesn't give Spell damage than I have to give each item all those 30 properties even if 90% will be 0. So here is what I came up with, any advice is greatly appreciated:
var prop, obj = { Item1: "Boots", Item2: "Gloves", Item3: "Sword", Item4: "Cape" };
// Pretty sure I'd have to remake this somehow to put properties in there, this might not be as useable
function Item(hp, mp, dmg, spd, luk) {
this.hp = hp;
this.mp = mp;
this.dmg = dmg;
this.spd = spd;
this.luk = luk;
}
var BigSword = new Item(0, 0, 50, 20, 10)
//I think this might be my best option so there are as few variables as possible? or does that not matter as much? I figured with this option whenever I update totalHp and totalMp and totalDmg etc in the updating function I can create a local variable called theItem = BigSword and then just do theItem.hp, theItem.mp and that way it doesn't have to search much for the variable and should run relatively faster? Sorry for the newbie question but does JS even have local variables in functions? As in if I do var theItem = whatever, then theItem will be null outside the function?
itemNames = [ "Boots", "Gloves", "Sword", "Cape" ];
itemHp = [ 0, 0, 0, 50 ];
itemMp = [ 0, 30, 0, 0 ];
itemDmg = [ 0, 0, 50, 0 ];
itemSpd = [ 50, 0, 0, 0];
//This is the other option that I came up with, a little uglier but if it's faster or better to just use arrays I don't mind that much.
But please keep in mind, like I said, 200~ish total items, each item will have around 30 properties. What do you think?
Thanks a lot for reading and for any information provided, it's greatly appreciated! Thanks in advance

Your best bet is to have a constructor, as you have in your first example. However, I'd change a few things. First of all, for the constructor, you're simply passing in a lot of numbers. This is bad, because it's very easy to forget the order in which your numbers go. A better way to go would be to pass in an object to your constructor, so you can label each property:
function Item(props) {
this.hp = props.hp;
this.mp = props.mp;
this.dmg = props.dmg;
this.spd = props.spd;
this.luk = props.luk;
}
var BigSword = new Item({
hp: 0,
mp: 0, 5
dmg: 0,
spd: 20,
luk: 10
});
This is nice, but still a pain, because you have all sorts of different items, and as you said, you'd have to define 30 properties for each item, which gets both messy and time-consuming. At this point you get into the realm of more complex object oriented stuff. From here you have basically two options: inheritance and construction.
Inheritance
Using object inheritance to properly define objects is one probably the more common of the two, and is fairly straightforward. In essence, you have a "class" of object, and then sub-classes of each class. So you might have Magical Items and Non-Magical Items as two classes. If you had a magic belt, perhaps, you'd want to make it inherit the properties of the Magical Item class. On the other hand, if you had a basic un-charmed sword, you'd want to make it inherit from the Non-Magical Item class.
But say you have magical scrolls and magical potions. Those are both magical items, right? So you'd want to make a class called Potions and a class called Scrolls, which both inherit from the Magical Item class. Then you might have certain types of potions. Healing potions, maybe--"Magical Potion of Heal Allies" and "Magical Potion Of Heal Self". So you'd need different classes for those--and so on and so forth.
Inheritance in Javascript can be done a number of different ways and it's possible to get very in-depth, so I'm not going to discuss it in my post. Here are a few useful links to get you started with inheritance, though, if you want to go that route:
David Crockford on Inheritance
MDN on Inheritance and MDN Inheritance Revisted
Alex Sexton on Inheritance
Composition
Composition is a concept that borrows heavily from other loosely-typed languages, such as Python, which employs it quite extensively. The basic idea behind composition is that you have a base object class, and whenever you need a certain specific type of object, you add behaviors, or members, to that base class. This isn't quite as well-known as inheritance, but it's definitely the one I prefer.
In Javascript, it works by having a base constructor, Item, and then having other constructors which add the necessary behaviors to your Item. Let's take a look at the example I used for composition--creating a Magical Potion of Heal Allies. You have several distinct properties here: Magical Item, Potion, Healing. With inheritance you'd do something like Item -> Magical -> Potion -> Healing Potions -> Allies. However, with composition, you can add several behaviors to a base Item class, without having to set up a huge prototype chain: Affect Allies, Potion, Healing.
Since composition is a bit simpler than inheritance (and, I'll admit it, I'm biased) I'll set up a quick example in psuedo-code:
// base item class
function Item(props) {
this.someProperty = props.someProperty;
this.someOtherProperty = props.someOtherProperty;
}
// potion behavior
function Potion(props) {
this.amount = props.amount; // IDK what amount means, just some random potion property
this.color = "green";
}
// healing behavior
function Healing(props) {
this.amountToHeal = props.amountToHeal;
this.cooldown = props.cooldown;
}
// behavior to affect allies
function AffectAllies(props) {
this.allies = props.allies;
for(ally in this.allies) {
this.allies[ally].affect(props.affact);
}
}
// at this point we have the various behaviors set up and we can create the Magical Potion of Heal Allies constructor:
function MassHealingPotion(props) {
var self = this;
this.amount = props.amount;
this.potion = new Potion(props);
this.effect = new Healing(props);
this.AffectAllies = new AffectAllies({
affect: function(ally) {
self.potion.affect;
},
allies: player.allies;
});
}
// you can now create a mass healing potion:
var potion = new MassHealingPotion({
weight: 50,
someProp: "someValue",
someOtherProp: "someOtherValue"
// and so on and so forth with whatever properties you want the potion to have
});
Well, that turned out a bit longer than I expected and probably contains proportionally more errors than I'd like, but I hope that conveys the basic idea of composition.
Now, simpler object creation isn't even the best part of composition, which is that you can re-use different behaviors. Consider you have a magical sword that whenever you make an attack it heals your allies. All you have to do is give it the Healing member and the AffectAllies member and boom--magical sword of heal buddies. With inheritance, you'd have to create a Swords class in the Magical Items class (and then you'd have two different Swords classes-one for Non Magical Swords and the other for Magical Swords! Ew!), then extend that with Healing Swords, and so on and so forth.
You can go either way, inheritance or object composition. Both are equally effective for what you want to achieve.
But what about performance?
200 items with 30 properties a piece? Not a problem. Stick them all in one giant array/object, boil 'em, mash 'em, stick 'em in a stew. The browser don't care. Your best bet is probably an object, though:
var itemList = {
bigSword: new BigSword(...);
smallSword: new SmallSword(...);
}

I would prefer Objects. I would start with a simple, generic "Item" and then build specific items by "inherit" from the generic one with prototyping.
That probably may reduce code redundancy and increase maintainability dramatically.
Speedwise I don't have a clue but I think iterating through 200*30 array values isn't also that fast.

You need to do a serious design effort first. If you just start coding (as many developers do), you will only get so far and realise your design has a fundamental flaw and the application needs to be rewritten. That doesn't matter in the early stages, but once you've written a few hundred lines of code it gets quite tedious.
By all means write code to prototype things, but all you should expect to get from it are design hints, the code should be thrown away.
Your items list (or catalogue) should be an object with methods to add, remove and update items. Each player (character, avatar, whatever) then just needs a list of their items and their state. There should be a global update damage or health or whatever function that gets items of a specific type and applies them to players of a specific type (e.g. weapons damage enemies, food revives the player and friends, etc.).
Only once you know all the players and items and their attributes and actions, you can start to design the interfaces and methods, and finally write some code.
Keep your eye on the objective, which should be to develop an engaging game, not to just write a few thousand lines of code.

Related

How do you access a body by its label in MatterJS?

This question was asked here but no answer was given.
To clarify the question, once a body is created, it is stored in the World/Composite.
The question is, given a body created like so:
Bodies.rectangle(0, 1000, 0, 100, {
isStatic: true,
label: "floor",
friction: 0,
render: {
fillStyle: 'light blue'
},
})
How do you access the body using the label? (Assuming the body is added to the world)
The simple answer is no, there is no built in function that allows you to retrieve a body by its label. The reason is because labels aren't unique and retrieving a body by label can take really long. Imagine if there were thousands of bodies...
In any case, if you still want to retrieve a body by its label you can do this to search for the body in linear time:
// retrieve all bodies in the world and filter on label
// returns an array containing all bodies that have that label
function getBodiesByLabel(label, world) {
return Composite.allBodies(world).filter(body => body.label === label)
}
const floorBodies = getBodiesByLabel('floor')
floorBodies.forEach(floorBody => console.log(floorBody))
If you only have a couple of bodies to look through, it's not that bad.
Source: MatterJS GitHub Question
Credit: grantjenkins on GitHub
The answer by gfdb works, but it involves a linear search over all bodies for each label lookup, O(n). As I mentioned in a couple of comments, MJS does offer a label property for convenience, but doesn't purport to be a holistic entity management solution; it's just a physics engine library. There doesn't seem to be any backing data structure for labels, and that's probably a good thing. Leaning heavily on this single property seems to be an antipattern, expecting MJS to handle entity management when it's not intended to.
So, the general approach when using MJS standalone is to roll your own application-specific entity management solution that meets your needs, or use an opinionated framework like Phaser that offers an off-the-shelf solution.
A couple of common approaches are:
Use a composition pattern: write your own classes and keep fields for MJS bodies as implementation details (probably OK to be tightly-coupled for most use cases), along with whatever other data you need for your app. Group in data structures as needed and optionally inherit from your base classes as in normal OOP.
class Enemy {
constructor(x, y, width, height, opts) {
this.body = Matter.Bodies.rectangle(x, y, width, height, opts);
this.kills = 0;
this.cooldown = 30;
// ... other important data that isn't necessarily MJS-related
}
update() {...}
draw() {...}
...
}
const entities = {
enemies: [new Enemy(...), ...],
walls: [...],
...
};
Use the bodies directly, but put them into an object of arrays organized by label:
const bodiesByType = {
walls: [Matter.Bodies.rectangle(), ...],
enemies: [Matter.Bodies.rectangle(), ...],
players: [Matter.Bodies.rectangle(), ...],
...
};
... or even skip the object and look them up by loose variable names player, walls, etc.
Use gfdb's approach for simple use cases where the above options might be premature optimization (although I don't think option 2 is much work).

Approach to modifying mutable objects?

Given that functional programming is best when sticking to immutable variables as much as possible, and that Ramda always makes shallow copies, how are objects that must be mutable dealt with in a mostly-purely functional framework?
For example, consider PIXI.Sprite (in pixi.js). The display system has an inherent hierarchy that is linked together and has its own way of tracking objects so it can re-use textures, etc. Garbage collecting them could be a real problem.
What sort of approaches can be taken to deal with this (alongside a robust frp system like sodium-typescript)?
Specifically, can this approach be improved:
Having some sort of unsafePerformIO() function that will modify the heavy objects. All modification of those objects is only done through that function.
Lightweight meta-information objects carry all the logic around.
Those lightweight information objects reference the heavy objects directly
Here is a full code sample in Typescript to demonstrate it in action (just need to import PIXI and Ramda). The key lines are in gameLoop():
let app = new PIXI.Application(window.innerWidth, window.innerHeight);
document.body.appendChild(app.view);
let ball = new PIXI.Graphics();
ball.beginFill(0xFF0000);
ball.drawCircle(0,0,40);
ball.endFill();
ball.y = app.stage.height/2;
app.stage.addChild(ball); //app
let ticker = new PIXI.ticker.Ticker();
ticker.add(gameLoop);
ticker.start();
let ballReference = {
ptr: ball,
x: 0
}
function performUnsafeIO(reference) {
reference.ptr.x = reference.x;
}
function gameLoop(deltaTime:number) {
//this version breaks!
//ball = R.assoc('x', ball.position.x + deltaTime * 1, ball);
//This works fine... but are there any gotchas?
ballReference = R.assoc('x', ballReference.x + deltaTime * 1, ballReference);
performUnsafeIO(ballReference);
}
(note, this conversation found via Google is a good reference point: https://web.archive.org/web/20100410001213/http://itmmetelko.com/blog/2008/02/23/functional-programming-immutable-objects-explained-irc-style/)

render a tile map using javascript

I'm looking for a logical understanding with sample implementation ideas on taking a tilemap such as this:
http://thorsummoner.github.io/old-html-tabletop-test/pallete/tilesets/fullmap/scbw_tiles.png
And rendering in a logical way such as this:
http://thorsummoner.github.io/old-html-tabletop-test/
I see all of the tiles are there, but I don't understand how they are placed in a way that forms shapes.
My understanding of rendering tiles so far is simple, and very manual. Loop through map array, where there are numbers (1, 2, 3, whatever), render that specified tile.
var mapArray = [
[0, 0, 0, 0 ,0],
[0, 1, 0, 0 ,0],
[0, 0, 0, 0 ,0],
[0, 0, 0, 0 ,0],
[0, 0, 1, 1 ,0]
];
function drawMap() {
background = new createjs.Container();
for (var y = 0; y < mapArray.length; y++) {
for (var x = 0; x < mapArray[y].length; x++) {
if (parseInt(mapArray[y][x]) == 0) {
var tile = new createjs.Bitmap('images/tile.png');
}
if (parseInt(mapArray[y][x]) == 1) {
var tile = new createjs.Bitmap('images/tile2.png');
}
tile.x = x * 28;
tile.y = y * 28;
background.addChild(tile);
}
}
stage.addChild(background);
}
Gets me:
But this means I have to manually figure out where each tile goes in the array so that logical shapes are made (rock formations, grass patches, etc)
Clearly, the guy who made the github code above used a different method. Any guidance on understanding the logic (with simply pseudo code) would be very helpful
There isn't any logic there.
If you inspect the page's source, you'll see that the last script tag, in the body, has a huge array of tile coordinates.
There is no magic in that example which demonstrates an "intelligent" system for figuring out how to form shapes.
Now, that said, there are such things... ...but they're not remotely simple.
What is more simple, and more manageable, is a map-editor.
Tile Editors
out of the box:
There are lots of ways of doing this... There are free or cheap programs which will allow you to paint tiles, and will then spit out XML or JSON or CSV or whatever the given program supports/exports.
Tiled ( http://mapeditor.org ) is one such example.
There are others, but Tiled is the first I could think of, is free, and is actually quite decent.
pros:
The immediate upside is that you get an app that lets you load image tiles, and paint them into maps.
These apps might even support adding collision-layers and entity-layers (put an enemy at [2,1], a power-up at [3,5] and a "hurt-player" trigger, over the lava).
cons:
...the downside is that you need to know exactly how these files are formatted, so that you can read them into your game engines.
Now, the outputs of these systems are relatively-standardized... so that you can plug that map data into different game engines (what's the point, otherwise?), and while game-engines don't all use tile files that are exactly the same, most good tile-editors allow for export into several formats (some will let you define your own format).
...so that said, the alternative (or really, the same solution, just hand-crafted), would be to create your own tile-editor.
DIY
You could create it in Canvas, just as easily as creating the engine to paint the tiles.
The key difference is that you have your map of tiles (like the tilemap .png from StarCr... erm... the "found-art" from the example, there).
Instead of looping through an array, finding the coordinates of the tile and painting them at the world-coordinates which match that index, what you would do is choose a tile from the map (like choosing a colour in MS Paint), and then wherever you click (or drag), figure out which array point that relates to, and set that index to be equal to that tile.
pros:
The sky is the limit; you can make whatever you want, make it fit any file-format you want to use, and make it handle any crazy stuff you want to throw at it...
cons:
...this of course, means you have to make it, yourself, and define the file-format you want to use, and write the logic to handle all of those zany ideas...
basic implementation
While I'd normally try to make this tidy, and JS-paradigm friendly, that would result in a LOT of code, here.
So I'll try to denote where it should probably be broken up into separate modules.
// assuming images are already loaded properly
// and have fired onload events, which you've listened for
// so that there are no surprises, when your engine tries to
// paint something that isn't there, yet
// this should all be wrapped in a module that deals with
// loading tile-maps, selecting the tile to "paint" with,
// and generating the data-format for the tile, for you to put into the array
// (or accepting plug-in data-formatters, to do so)
var selected_tile = null,
selected_tile_map = get_tile_map(), // this would be an image with your tiles
tile_width = 64, // in image-pixels, not canvas/screen-pixels
tile_height = 64, // in image-pixels, not canvas/screen-pixels
num_tiles_x = selected_tile_map.width / tile_width,
num_tiles_y = selected_tile_map.height / tile_height,
select_tile_num_from_map = function (map_px_X, map_px_Y) {
// there are *lots* of ways to do this, but keeping it simple
var tile_y = Math.floor(map_px_Y / tile_height), // 4 = floor(280/64)
tile_x = Math.floor(map_px_X / tile_width ),
tile_num = tile_y * num_tiles_x + tile_x;
// 23 = 4 down * 5 per row + 3 over
return tile_num;
};
// won't go into event-handling and coordinate-normalization
selected_tile_map.onclick = function (evt) {
// these are the coordinates of the click,
//as they relate to the actual image at full scale
map_x, map_y;
selected_tile = select_tile_num_from_map(map_x, map_y);
};
Now you have a simple system for figuring out which tile was clicked.
Again, there are lots of ways of building this, and you can make it more OO,
and make a proper "tile" data-structure, that you expect to read and use throughout your engine.
Right now, I'm just returning the zero-based number of the tile, reading left to right, top to bottom.
If there are 5 tiles per row, and someone picks the first tile of the second row, that's tile #5.
Then, for "painting", you just need to listen to a canvas click, figure out what the X and Y were,
figure out where in the world that is, and what array spot that's equal to.
From there, you just dump in the value of selected_tile, and that's about it.
// this might be one long array, like I did with the tile-map and the number of the tile
// or it might be an array of arrays: each inner-array would be a "row",
// and the outer array would keep track of how many rows down you are,
// from the top of the world
var world_map = [],
selected_coordinate = 0,
world_tile_width = 64, // these might be in *canvas* pixels, or "world" pixels
world_tile_height = 64, // this is so you can scale the size of tiles,
// or zoom in and out of the map, etc
world_width = 320,
world_height = 320,
num_world_tiles_x = world_width / world_tile_width,
num_world_tiles_y = world_height / world_tile_height,
get_map_coordinates_from_click = function (world_x, world_y) {
var coord_x = Math.floor(world_px_x / num_world_tiles_x),
coord_y = Math.floor(world_px_y / num_world_tiles_y),
array_coord = coord_y * num_world_tiles_x + coord_x;
return array_coord;
},
set_map_tile = function (index, tile) {
world_map[index] = tile;
};
canvas.onclick = function (evt) {
// convert screen x/y to canvas, and canvas to world
world_px_x, world_px_y;
selected_coordinate = get_map_coordinates_from_click(world_px_x, world_px_y);
set_map_tile(selected_coordinate, selected_tile);
};
As you can see, the procedure for doing one is pretty much the same as the procedure for doing the other (because it is -- given an x and y in one coordinate-set, convert it to another scale/set).
The procedure for drawing the tiles, then, is nearly the exact opposite.
Given the world-index and tile-number, work in reverse to find the world-x/y and tilemap-x/y.
You can see that part in your example code, as well.
This tile-painting is the traditional way of making 2d maps, whether we're talking about StarCraft, Zelda, or Mario Bros.
Not all of them had the luxury of having a "paint with tiles" editor (some were by hand in text-files, or even spreadsheets, to get the spacing right), but if you load up StarCraft or even WarCraft III (which is 3D), and go into their editors, a tile-painter is exactly what you get, and is exactly how Blizzard made those maps.
additions
With the basic premise out of the way, you now have other "maps" which are also required:
you'd need a collision-map to know which of those tiles you could/couldn't walk on, an entity-map, to show where there are doors, or power-ups or minerals, or enemy-spawns, or event-triggers for cutscenes...
Not all of these need to operate in the same coordinate-space as the world map, but it might help.
Also, you might want a more intelligent "world".
The ability to use multiple tile-maps in one level, for instance...
And a drop-down in a tile-editor to swap tile-maps.
...a way to save out both tile-information (not just X/Y, but also other info about a tile), and to save out the finished "map" array, filled with tiles.
Even just copying JSON, and pasting it into its own file...
Procedural Generation
The other way of doing this, the way you suggested earlier ("knowing how to connect rocks, grass, etc") is called Procedural Generation.
This is a LOT harder and a LOT more involved.
Games like Diablo use this, so that you're in a different randomly-generated environment, every time you play. Warframe is an FPS which uses procedural generation to do the same thing.
premise:
Basically, you start with tiles, and instead of just a tile being an image, a tile has to be an object that has an image and a position, but ALSO has a list of things that are likely to be around it.
When you put down a patch of grass, that grass will then have a likelihood of generating more grass beside it.
The grass might say that there's a 10% chance of water, a 20% chance of rocks, a 30% chance of dirt, and a 40% chance of more grass, in any of the four directions around it.
Of course, it's really not that simple (or it could be, if you're wrong).
While that's the idea, the tricky part of procedural generation is actually in making sure everything works without breaking.
constraints
You couldn't, for example have the cliff wall, in that example, appear on the inside of the high-ground. It can only appear where there's high ground above and to the right, and low-ground below and to the left (and the StarCraft editor did this automatically, as you painted). Ramps can only connect tiles that make sense. You can't wall off doors, or wrap the world in a river/lake that prevents you from moving (or worse, prevents you from finishing a level).
pros
Really great for longevity, if you can get all of your pathfinding and constraints to work -- not only for pseudo-randomly generating the terrain and layout, but also enemy-placement, loot-placement, et cetera.
People are still playing Diablo II, nearly 14 years later.
cons
Really difficult to get right, when you're a one-man team (who doesn't happen to be a mathematician/data-scientist in their spare time).
Really bad for guaranteeing that maps are fun/balanced/competitive...
StarCraft could never have used 100% random-generation for fair gameplay.
Procedural-generation can be used as a "seed".
You can hit the "randomize" button, see what you get, and then tweak and fix from there, but there'll be so much fixing for "balance", or so many game-rules written to constrain the propagation, that you'll end up spending more time fixing the generator than just painting a map, yourself.
There are some tutorials out there, and learning genetic-algorithms, pathfinding, et cetera, are all great skills to have... ...buuuut, for purposes of learning to make 2D top-down tile-games, are way-overkill, and rather, are something to look into after you get a game/engine or two under your belt.

How can I reliably subsort arrays using DOM methods?

UP-FRONT NOTE: I am not using jQuery or another library here because I want to understand what I’ve written and why it works (or doesn’t), so please don’t answer this with libraries or plugins for libraries. I have nothing against libraries, but for this project they’re inimical to my programming goals.
That said…
Over at http://meyerweb.com/eric/css/colors/ I added some column sorting using DOM functions I wrote myself. The problem is that while it works great for, say, the simple case of alphabetizing strings, the results are inconsistent across browsers when I try to sort on multiple numeric terms—in effect, when I try to do a sort with two subsorts.
For example, if you click “Decimal RGB” a few times in Safari or Firefox on OS X, you get the results I intended. Do the same in Chrome or Opera (again, OS X) and you get very different results. Yes, Safari and Chrome diverge here.
Here’s a snippet of the JS I’m using for the RGB sort:
sorter.sort(function(a,b){
return a.blue - b.blue;
});
sorter.sort(function(a,b){
return a.green - b.green;
});
sorter.sort(function(a,b){
return a.red - b.red;
});
(sorter being the array I’m trying to sort.)
The sort is done in the tradition of another StackOverflow question “How does one sort a multi dimensional array by multiple columns in JavaScript?” and its top answer. Yet the results are not what I expected in two of the four browsers I initially tried out.
I sort (ha!) of get that this has to do with array sorts being “unstable”—no argument here!—but what I don’t know is how to overcome it in a consistent, reliable manner. I could really use some help both understanding the problem and seeing the solution, or at least a generic description of the solution.
I realize there are probably six million ways to optimize the rest of the JS (yes, I used a global). I’m still a JS novice and trying to correct that through practice. Right now, it’s array sorting that’s got me confused, and I could use some help with that piece of the script before moving on to cleaning up the code elsewhere. Thanks in advance!
UPDATE
In addition to the great explanations and suggestions below, I got a line on an even more compact solution:
function rgbSort(a,b) {
return (a.red - b.red || a.green - b.green || a.blue - b.blue);
}
Even though I don’t quite understand it yet, I think I’m beginning to grasp its outlines and it’s what I’m using now. Thanks to everyone for your help!
OK. So, as you've discovered, your problem is that the default JavaScript sort is not guaranteed to be stable. Specifically, I think that in your mind it works like this: I'll sort by blueness, and then when I sort by greenness the sorter will just move entries in my array up and down but keep them ordered by blueness. Sadly, the universe is not so conveniently arranged; the built-in JS sort is allowed to do the sort how it likes. In particular, it's allowed to just throw the contents of the array into a big bucket and then pull them out sorted by what you asked for, completely ignoring how it was arranged before, and it looks like at least some browsers do precisely that.
There are a couple of ways around this, for your particular example. Firstly, you could still do the sort in three separate calls, but make sure those calls do the sort stably: this would mean that after sorting by blueness, you'd stably sort by greenness and that would give you an array sorted by greenness and in blueness order within that (i.e., precisely what you're looking for). My sorttable library does this by implementing a "shaker sort" or "cocktail sort" method (http://en.wikipedia.org/wiki/Cocktail_sort); essentially, this style of sorting walks through the list a lot and moves items up and down. (In particular, what it does not do is just throw all the list items into a bucket and pull them back out in order.) There's a nice little graphic on the Wikipedia article. This means that "subsorts" stay sorted -- i.e., that the sort is stable, and that will give you what you want.
However, for this use case, I wouldn't worry about doing the sort in three different calls and ensuring that they're stable and all that; instead, I'd do all the sorting in one go. We can think of an rgb colour indicator (255, 192, 80) as actually being a big number in some strange base: to avoid too much math, imagine it's in base 1000 (if that phrase makes no sense, ignore it; just think of this as converting the whole rgb attribute into one number encompassing all of it, a bit like how CSS computes precedences in the cascade). So that number could be thought of as actually 255,192,080. If you compute this number for each of your rows and then sort by this number, it'll all work out, and you'll only have to do the sort once: so instead of doing three sorts, you could do one: sorter.sort(function(a,b) { return (a.red*1000000 + a.green*1000 + a.blue) - (b.red*1000000 + b.green*1000 + b.blue) } and it'll all work out.
Technically, this is slightly inefficient, because you have to compute that "base 1000 number" every time that your sort function is called, which may be (is very likely to be) more than once per row. If that's a big problem (which you can work out by benchmarking it), then you can use a Schwartzian transform (sorry for all the buzzwords here): basically, you work out the base-1000-number for each row once, put them all in a list, sort the list, and then go through the sorted list. So, create a list which looks like [ [255192080, <table row 1>], [255255255, <table row 2>], [192000000, <table row 3>] ], sort that list (with a function like mylist.sort(function(a,b) { return a[0]-b[0]; })), and then walk through that list and appendChild each of the s onto the table, which will sort the whole table in order. You probably don't need this last paragraph for the table you've got, but it may be useful and it certainly doesn't hurt to know about this trick, which sorttable.js also uses.
I would approach this problem in a different manner. It appears you're trying to reconstruct all the data by extracting it from the markup, which can be a perilous task; a more straightforward approach would be to represent all the data you want to render out to the page in a format your programs can understand from the start, and then simply regenerate the markup first on page load and then on each subsequent sort.
For instance:
var colorsData = [
{
keyword: 'mediumspringgreen',
decimalrgb: {
r: 0,
g: 250,
b: 154
},
percentrgb: {
r: 0,
g: 98,
b: 60.4
},
hsl: {
h: 157,
s: 100,
l: 49
}
hex: '00FA9A',
shorthex: undefined
},
{
//next color...
}
];
That way, you can run sorts on this array in whatever way you'd like, and you're not trying to rip data out from markup and split it and reassign it and all that.
But really, it seems you're maybe hung up on the sort functions. Running multiple sorts one after the other will get unintended results; you have to run a single sort function that compares the next 'column' in the case the previous one is found to be equal. An RGB sort could look like:
var decimalRgbForwards = function(a,b) {
var a = a.decimalrgb,
b = b.decimalrgb;
if ( a.r === b.r ) {
if ( a.g === b.g ) {
return a.b - b.b;
} else {
return a.g - b.g;
}
} else {
return a.r - b.r;
}
};
So two colors with matching r and g values would return for equality on the b value, which is just what you're looking for.
Then, you can apply the sort:
colorsData.sort(decimalRgbForwards);
..and finally iterate through that array to rebuild the markup inside the table.
Hope it helps, sir-

Javascript: What's the algorithmic performance of 'splice'?

That is, would I be better suited to use some kind of tree or skip list data structure if I need to be calling this function a lot for individual array insertions?
You might consider whether you want to use an object instead; all JavaScript objects (including Array instances) are (highly-optimized) sets of key/value pairs with an optional prototype An implementation should (note I don't say "does") have a reasonable performance hashing algorithm. (Update: That was in 2010. Here in 2018, objects are highly optimized on all significant JavaScript engines.)
Aside from that, the performance of splice is going to vary a lot between implementations (e.g., vendors). This is one reason why "don't optimize prematurely" is even more appropriate advice for JavaScript applications that will run in multiple vendor implementations (web apps, for instance) than it is even for normal programming. Keep your code well modularized and address performance issues if and when they occur.
Here's a good rule of thumb, based on tests done in Chrome, Safari and Firefox: Splicing a single value into the middle of an array is roughly half as fast as pushing/shifting a value to one end of the array. (Note: Only tested on an array of size 10,000.)
http://jsperf.com/splicing-a-single-value
That's pretty fast. So, it's unlikely that you need to go so far as to implement another data structure in order to squeeze more performance out.
Update: As eBusiness points out in the comments below, the test performs an expensive copy operation along with each splice, push, and shift, which means that it understates the difference in performance. Here's a revised test that avoids the array copying, so it should be much more accurate: http://jsperf.com/splicing-a-single-value/19
Move single value
// tmp = arr[1][i];
// arr[1].splice(i, 1); // splice is slow in FF
// arr[1].splice(end0_1, 0, tmp);
tmp = arr[1][i];
ii = i;
while (ii<end0_1)
{
arr[1][ii] = arr[1][++ii];
cycles++;
}
arr[1][end0_1] = tmp;

Categories