meyerweb.com

Skip to: site navigation/presentation
Skip to: Thoughts From Eric

Archive: 'JavaScript' Category

Gravity Wars Redux

Over the past few weeks, I’ve been writing a remake of/homage to a game I last played somewhere around 1990: Gravity Wars.  I gave this personal project a few hours here and there each day, or more usually each evening, slowly resurrecting a little piece of my past and putting my own spin on it.  You can check it out for yourself, if you like: Gravity Wars Redux.

There are a few things I changed from the version I played all those years ago, besides the change of colors.  First of all, instead of taking turns, in my version the ships fire simultaneously, meaning a no-survivors tie is possible.  Second, the shot preview paths weren’t part of the original game.  Third, the “Gravimetric display” wasn’t a thing in the original game either.  That and the shot previews are leftovers from my development testing that I decided to keep around, either because I thought they added something to the game or because I just liked them too much to dump.

In fact, most of the code in there is accreted leftovers from the experimentation and development process.  Looking at it now, I can see all the things I should have done.  It just got to a point, as with most coding projects, where I could add another scoop or two of spaghetti to the existing mound and ship it, or I could start over and try to make the lasagna properly this time.  I decided to ship it.

I have to admit the point of all this wasn’t actually to recreate Gravity Wars, as much as I like the game and am glad to have a working copy of it.  It was instead to teach myself about the canvas API, and also to try to get a handle on some JS features that have never quite made sense to me.  I think I did okay on the both fronts, in my own idiosyncratic way.

At this point in a JS-related post, I usually throw in a disclaimer about being a JS newb whose code should never be inspected by anybody.  That’s probably still true, but I know I’ve advanced quite a bit from where I was, which pleases me.  I can see that not just in that the code I’m publishing today, which is convoluted and clumsy but still better than what I’ve written in the past.  I can also tell I’ve gotten better simply because I can see better approaches, as a result of what I learned along the way.

This may be where I end the project, or I may go back and take another crack at making lasagna.  We’ll see.  The carrot for me in doing that is it would let me add some other fun features and useful improvements pretty cheaply, not to mention I could fix some things that aren’t quite what they should be (like the placement of planets and ships, which should be a little less random).  The stick is of course having to rewrite code I already wrote.  I mean, lasagna is nice and all, but I like spaghetti too.

Anyway, I enjoyed writing it and learning as I coded, so if slinging missiles around planetary limbs sounds like your idea of a good time, please enjoy.


I’m indebted to Sohrab Ismail-Beigi, whose 1989 PASCAL source code I studied and in some cases directly ported; and to pascal-source.ru, for making that code available for download.  My thanks to both.

Turning Web Video On Its Head

Here’s some fun.  (For a sufficiently nerdy definition of “fun”.)

  1. Launch Safari 4 or Chrome 4.

  2. Drag Videotate to the bookmarks bar.

  3. Go opt into the YouTube HTML5 beta.

  4. Find your favorite YouTube video.  Or maybe your least favorite.  Here’s one of my favorites: Walk Don’t Run.  Here’s another that’s not necessarily a favorite, but it seems like a fairly appropriate choice.

    Note: not all videos are available via HTML5, even when you’re opted in.  If you get a Flash video, the bookmarklet won’t work.

  5. Once the video has started playing, activate the “Videotate” bookmarklet.

  6. Enjoy.

Thanks to Simon WIllison for tweeting the JS I modified, and Jeremy Keith for helping me realize it would be easy to do during the HTML5 portion of A Day Apart.

Seeking JavaScript Help

Even though it turned out that there is no simple solution for the math problem I posted, I learned a fair amount from the fantastic responses—thanks, everyone!—and eventually came up with a solution that worked for me.  (I’d like to say it was one of the iterative approaches posted, but none of them worked for me.  In the end, I brute-forced it.)  I’m hoping for a different outcome with my next question, which is about JavaScript.

Consider the following structure, which is a much-edited-down version of part of the HYDEsim code:

function Detonation(name,lat,lon,yield) {
    var scale = Math.pow(yield,1/3);
    var gz = new GLatLng(lat,lon);
    this.name = name;
    this.lon = lon;
    this.lat = lat;
    this.gz = gz;
    this.yield = yield;
    this.overpressure = {
        p30 : 0.108 * scale,
        p15 : 0.153 * scale,
        p10 : 0.185 * scale,
         p5 : 0.281 * scale,
         p1 : 0.725 * scale
    };
    this.psi30 = {
        radius: 0.108 * scale,
        overlay : {
            points: makePoints(this.gz,0.108 * scale)
        }
    };
    this.psi15 = {
        radius: 0.153 * scale,
        overlay : {
            points: makePoints(this.gz, 0.153 * scale)
        }
    };
    this.therm20 = {
        radius: thermDist(20,this.yield,0.33,conditions.visibility),
        overlay : {
            points: makePoints(
                this.gz,
                thermDist(20,this.yield,0.33,conditions.visibility)
            )
    };
    // ...and so on...
}

There are two things I’ve tried and failed to do.  And tried, and tried, and tried; and failed, and failed, and failed.

  1. Eliminate the redundant calculations of radii.  Note how I define a radius property in each case?  And then have to not use it when I create the overlay?  It seems like there must be a way to just define the value once for each subsection and then use it as many times as needed within that context.  How?
  2. How do I make it so that all those properties and overlays and such automatically recalculate any time one of the “upper-level” terms changes?  As in, once I’ve created a new Detonation object det, how can I set things up so declaring det.yield = 250; will trigger recalculation of all the other pieces of the object?  At present, I’m just blowing away the existing det and creating a whole new one, which just seems clumsy and silly.  I have to believe there’s a better way.  I just can’t find it.

Please note: tossing off comments like “oh, just instantiate a mixin constructor with an extra closure” will be of no help at all, because I don’t understand what those terms mean.  Hell, I’m not even sure I used the words “object” and “property” correctly in my explanation above.  Similarly, pointing me to tutorials that use those terms liberally is unlikely to be of help, since the text will just confuse me.  Sample code (whether posted or in a tutorial) will help a great deal, because it will give me something to poke and prod and dissect.  That’s how I’ve always learned to program.  Actually, it’s how I’ve always learned anything.

As well, I’m absolutely willing to believe that there are much, much better ways to structure the object, but right now I really need to learn how those two things are accomplished in the context of what I already have.  Once I get familiar with those and finish up some other work, I can start thinking about more fundamental overhauls of the code (which needs it, but not now).

I really appreciate any concrete help you can give me.

Addendum: if you leave code in a comment, please just wrap it in a code element and use whatever indentation you like.  The indentation won’t show up when the post goes up, but I’ll go in after and wrap the code in a pre and then everything will be fine.  Sorry to those who’ve already gone to the effort of posting with indents or nbsp entities to try to preserve indentation!  As soon as I can dig up the right preference panel or plugin to allow pre in comments, I’ll do that, but for now I’ll manually edit in the needed pres as comments are added.  THanks, and again, apologies to those who posted before I made this clear!

JavaScript Will Save Us All

A while back, I woke up one morning thinking, John Resig’s got some great CSS3 support in jQuery but it’s all forced into JS statements.  I should ask him if he could set things up like Dean EdwardsIE7 script so that the JS scans the author’s CSS, finds the advanced selectors, does any necessary backend juggling, and makes CSS3 selector support Transparently Just Work.  And then he could put that back into jQuery.

And then, after breakfast, I fired up my feed reader and saw Simon Willison‘s link to John Resig’s nascent Sizzle project.

I swear to Ged this is how it happened.

Personally, I can’t wait for Sizzle to be finished, because I’m absolutely going to use it and recommend its use far and wide.  As far as I’m concerned, though, it’s a first step into a larger world.

Think about it: most of the browser development work these days seems to be going into JavaScript performance.  Those engines are being overhauled and souped up and tuned and re-tuned to the point that performance is improving by orders of magnitude.  Scanning the DOM tree and doing things to it, which used to be slow and difficult, is becoming lightning-fast and easy.

So why not write JS to implement multiple background-image support in all browsers?  All that’s needed is to scan the CSS, find instances of multiple-image backgrounds, and then dynamically add divs, one per extra background image, to get the intended effect.

Just like that, you’ve used the browser’s JS to extend its CSS support.  This approach advances standards support in browsers from the ground up, instead of waiting for the browser teams to do it for us.

I suspect that not quite everything in CSS3 will be amenable to this approach, but you might be surprised.  Seems to me that you could do background sizing with some div-and-positioning tricks, and text-shadow could be supportable using a sIFR-like technique, though line breaks would be a bear to handle.  RGBa and HSLa colors could be simulated with creative element reworking and opacity, and HSL itself could be (mostly?) supported in IE with HSL-to-RGB calculations.  And so on.

There are two primary benefits here.  The first is obvious: we can stop waiting around for browser makers to give us what we want, thanks to their efforts on JS engines, and start using the advanced CSS we’ve been hearing about for years.  The second is that the process of finding out which parts of the spec work in the real world, and which fall down, will be greatly accelerated.  If it turns out nobody uses (say) background-clip, even given its availability via a CSS/JS library, then that’s worth knowing.

What I wonder is whether the W3C could be convinced that two JavaScript libraries supporting a given CSS module would constitute “interoperable implementations”, and thus allow the specification to move forward on the process track.  Or heck, what about considering a single library getting consistent support in two or more browsers as interoperable?  There’s a chance here to jump-start the entire process, front to back.

It is true that browsers without JavaScript will not get the advanced CSS effects, but older browsers don’t get our current CSS, and we use it anyway.  (Still older browsers don’t understand any CSS at all.)  It’s the same problem we’ve always faced, and everyone will face it differently.

We don’t have to restrict this to CSS, either.  As I showed with my href-anywhere demo, it’s possible to extend markup using JS.  (No, not without breaking validation: you’d need a custom DTD for that.  Hmmm.)  So it would be possible to use JS to, say, add audio and video support to currently-available browsers, and even older browsers.  All you’d have to do is convert the HTML5 element into HTML4 elements, dynamically writing out the needed attributes and so forth.  It might not be a perfect 1:1 translation, but it would likely be serviceable—and would tear down some of the highest barriers to adoption.

There’s more to consider, as well: the ability to create our very own “standards”.  Maybe you’ve always wanted a text-shake property, which jiggles the letters up and down randomly to look like the element got shaken up a bit.  Call it -myCSS-text-shake or something else with a proper “vendor” prefix—we’re all vendors now, baby!—and go to town.  Who knows?  If a property or markup element or attribute suddenly takes off like wildfire, it might well make it into a specification.  After all, the HTML 5 Working Group is now explicitly set up to prefer things which are implemented over things that are not.  Perhaps the CSS Working Group would move in a similar direction, given a world where we were all experimenting with our own ideas and seeing the best ideas gain widespread adoption.

In the end, as I said in Chicago last week, the triumph of standards (specifically, the DOM standard) will permit us to push standards support forward now, and save some standards that are currently dying on the vine.  All we have to do now is start pushing.  Sizzle is a start.  Who will take the next step, and the step after that?

Any-Element Linking Demo

In support of the still-to-be-finished proposal for allowing most HTML 5 elements to become hyperlinks, I’ve written a quick proof-of-concept demo for your perusal.  Basically, it’s a page with some JavaScript that captures the whole document tree, looks for any elements with an href attribute, and then sprinkles some events on those elements in order to make them act like hyperlinks.  There’s also some CSS that applies old-school link presentation to said elements (blue and underlined, baby!).  I’m using href because it was the easiest thing to do.

I’m sure I could have written a more elegant script (and yes, I know, your favorite JS framework would done it in half the lines and seventeen times the page weight) and I suspect there are some things I’m missing.  I’ll be interested to hear what those may be.  Meanwhile, if you want to try out your own arbitrary-element linking, grab a copy of the demo and edit the markup to your heart’s content.  Or you could suck out the JS and apply it to your own test pages.  Your call.

The demo works fine in Firefox 2, Camino 1.5, Safari 2, and Opera 9.2.  I didn’t test it in anything else.  It may well fail spectacularly in every other browser known to man and dog.  That’s not really an issue, though.  The goal here is to have a working demonstration, not a universal solution.  (The latter may come later.)  It’s a handy way to show people how browsers should behave in an arbitrary-link world.

The one thing that didn’t go right is the status-bar URL handling when hovering over a linked element (other than an a element) that descends from another linked element.  For some reason the descendant’s URL never shows up in the status bar.  I’m sure there’s an easy fix.  I regard this as a minor issue.  [Update 7/23: this has been fixed thanks to Allwyn Fernandez.]

The biggest thing that’s missing is simulating “visited” styles on non-a elements; in this case, turning them purple.  That would require mining the history and dynamically adding classes and, well, all kinds of stuff.  I’m sure it’s possible.  I’m also sure that I don’t have the time right now to figure out how to do it well.  Besides, ship early, ship often, right?

As I said before, I’m very interested to know what people think of the demonstrated behavior and how it might be improved.  And hey, if anyone wants to contribute improvements to the JS, I’ll do my best to keep up.

One more step toward a concrete proposal…

Need Help With Table Row Events

Here’s a late-week call for assistance in the JavaScript realm, specifically in making IE do what I need and can make happen in other browsers.  I’d call this a LazyWeb request except I’ve been trying to figure out how to do it all [censored] afternoon, and it doesn’t [censored] work no matter how many [censored] semi-related examples I find online that work just [censored] fine, but still don’t [censored] help me [censored] fix this [censored] problem.  [doubly censored]!

I have a table.  (Yes, for data.)  In the table are rows, of course, and each row has a number of cells.  I want to walk through the rows and dynamically add an ‘onclick’ event to every row.  The actual event is slightly different for each row, but in every case, it’s supposed to call a function and pass some parameters (which are the things that change).  Here’s how I’m doing it:

var event = '5'; // in the real code this is passed into the surrounding function
var mapStates = getElementsByClassName('map','tr');
for (x = 0; x < mapStates.length; x++) {
	var el = mapStates[x];
	var id = el.getAttribute('id');
	var val = "goto('" + id + "','" + event + "');";
	el.setAttribute('onclick',val);
}

Okay, so that works fine in Gecko.  It doesn't work at all in IE.  I changed el.setAttribute('onclick',val); to el.onclick = val; per some advice I found online and that completely failed in everything.  Firebug told me "el.onclick is not a function".  Explorer just silently did nothing, like always.

So how am I supposed to make this work in IE, let alone in IE and Gecko-based and WebKit-based and all other modern browsers?

Oh, and do not tell me that framework X or library Q does this so easily, because I'm trying to learn here, not have someone else's code hand-wave the problem away.  Pointing me directly to the actual code block inside a framework or library that makes this sort of thing possible:  that's totally fine.  I may not understand it, but at least there will be JS for me to study and ask questions about.  Ditto for pointing me to online examples of doing the exact same thing, which I tried to find in Google but could not: much appreciated.

Help, please?

Update: many, many commenters helped me see what I was missing and therefore doing wrong---thank you all!  For those wondering what I was wondering, check out the comments.  There are a lot of good examples and quick explanations there.

Help Wanted: IE/Win Script Weirdness

Okay, JavaScript / DOM scripting gurus, I need your help.  Explorer for Windows is completely baffling me and hopefully one of you out there can determine what’s making it act so peculiar.  To see the problems, go to the debug version of HYDEsim in IE/Win and compare the the results to those in Firefox.

Problem #1 is that the rounding function I wrote doesn’t seem to be working right (though this could be an effect of VirtualPC).  Here’s the function:

function round(x,dp) {
	var rt = Math.pow(10,dp);
	return Math.round(x*rt) / rt;
}

That’s it.  All it’s supposed to do is round off a number to the given number of decimal points.  Thus, var test = round(3.1415926539,3) should return 3.142.  In IE/Win, instead of the expected 0.71, I’m getting results like 0.7100000000000001.  Huh?  Where the heck did that come from?  Is this fallout from that Intel rounding bug everyone was smirking about five years ago, or what?

Problem #2 is perhaps more obvious: the Great Big Circles on the map.  I’m creating the GIcon objects correctly, passing in the height and width of each one.  The top left corner placement is correct for each marker.  They just haven’t been resized in any way at all, and so are being drawn at their inherent 1000-by-1000 size.  Is this a breakdown in IE/Win, the Google Maps API, or something else?  I tried passing pre-rounded values and it didn’t seem to help.  Am I stuck here, or is there a solution?

Many thanks for any help you can provide.

Working With Google Maps

As I worked on HYDEsim, I discovered some interesting things about the Google Maps API.  Well, let’s call them what they are: limitations.

(And let me say right up front that if I missed ways to get around these limitations, then I’ll happily be corrected.  Either way, these are the impressions of someone whose first project in the API took about two days, and was in the end basically a success, which speaks volumes to the quality of the API.)

The first and most important limitation was that the Google Maps API permits the creation of two types of objects.  The first type is icons, the most obvious example of which are those little push-pin symbols in Google Maps that mark locations.  The second is polylines, which are how Google Maps draws the “get from here to there” routes on the maps.  You get both any time you ask for driving directions, like these from Norwalk, CT to New York City, NY.

Note that they’re polylines, not polygons.  You could certainly draw a polygonal shape using a polyline, but you couldn’t easily fill it with a color, let alone a translucent color.  And as for a circle… well, if you want to draw enough line segments so that you approximate the roundness of a circle, you certainly can.  It just doesn’t seem like a great idea.  Plus there’s no simple way to fill it in.

So in order to draw the overpressure rings, I created a 1000×1000 pixel 24-bit PNG of a circle.  To create a ring, I first used the Google Maps API to figure out the latitude and longitude boundaries of the map.  From that, I determined the number of miles per degree based on the latitude, and then calculated the number of miles per pixel (mpp) within the view.  From there, I determined how wide a ring needed to be to be the right size, created an icon at that size using the big PNG, and added it as an icon.

Whew.

Okay, so that solved the problem of putting the rings on the map.  What it didn’t solve was what happened if the zoom level changed, because icons (being raster images) don’t zoom with the map.  By default, you wouldn’t want them to: if the pushpin kept growing with the map as you zoomed in, eventually it would get huge and blocky and obscure half the view.

Therefore, the upshot was that every time the zoom level changed, I had to rip away the rings and rebuild them entirely, based on the new mpp value.  It was easy to trigger the process:

GEvent.addListener(map, "zoom", zoomLimit);

That’s the API at work for me.  I just tack a listener onto the zoom event, and I’m ready to go.  Cool.  Rebuilding the icons—well, not so cool, although it doesn’t seem to kill the tool’s performance.

All this zoom handling was necessary because icons, as you might expect, are given dimensions in pixels.  Polylines, on the other hand, have each point defined with longitude/latitude coordinates.  That’s why polylines do scale with change in zoom level—as, again, you’d want them to do by default.  If I could have defined my icons’ sizes using longitude/latitude measures instead of pixels, I could have avoided the whole “recalculate the ring sizes every time the zoom level changes” bit and shaved two or three hours off of my development time.  (Which was, in total, 12 hours or less.)

Of course, if the API provided polygonal primitives, I’d have avoided even more hackery.  If I could have just drawn the circles as circles, using longitudinal degrees as the unit of sizing, then there’d have been even less work and a shorter development time.  Something like this:

var base = new GShape();
base.type = circle;
base.anchor = new GPoint(-73.9971, 40.7223);  // longitude,latitude
base.radius = 0.0273548;  // degrees of latitude

…or something to that effect, with properties for the color and thickness of the outline, and also for the color and transparency of the interior.  And so on.

By doing it this way, the shapes (and there could easily be many other types) would be like filled polylines, and would scale in size along with the map.  That would have made HYDESim a whole lot easier to create.

You might say, “That’s all well and good, Eric, but how many reasons are there to draw circles on a map besides charting widespread destruction?”  I thought of a few possibilities:

  • Explicitly showing the scope of a “show me hotels within this many miles of the specified address” type of request
  • Someone looking to recreate the WIMPUR map in Google Maps
  • Plotting Iridium flare intensities

I’m sure there are countless more.  As well, allowing for actual filled polygons would add extra possibilities to applications like chicagocrime.org, which uses polylines to draw ZIP code boundaries.  With filled polygons, they could shade the ZIP code in question… or shade all other ZIP codes while leaving the current one unshaded, in order to give it some extra visual pop.

There was one other thing I encountered that’s either a limitation, or I just couldn’t figure out how to deal with it.  If you click on a detonation point in HYDESim, it pops up a “blowup” window (their term, not mine!) that shows a zoomed-in view of that point on the map.  The overpressure ring overlays are faithfully reproduced on that map, but they aren’t scaled to its zoom level.  They exactly match the overlays on the main map, and zooming in and out in that window has no scaling effects.

Ideally, I’d just remove the overlays from the zoom window while leaving them in place on the larger map.  I couldn’t find a way to do it.  Failing that, I wanted to have the overlays correctly scaled.  No dice there either.  If there is a way to do either of these and I missed it, hopefully someone will let me know.  If not, it’s something I hope the API adds in the future.

The final observation has to do with the icons and interactivity.  I wanted to set the overpressure rings to be event-transparent.  In other words, I wanted to make it so that the rings didn’t exist as far as the event model was concerned.  That way, you could click-and-drag the map even if there’s a ring underneath the mouse pointer.  This didn’t appear to be possible, although again, maybe I just couldn’t figure out how.  I did play around with the imageMap property, but it didn’t seem to have much effect.  Figuring that out would be nice, though.  I could leave the detonation point active for popping open the blowup window, and make the rest inert.

Other than that, things went as smoothly as you might expect they would for someone with limited JavaScript skills and no prior experience with the Google Maps API.  The examples provided by Google on the documentation page helped immensely, actually, especially the AJAX example.  That let me split the city list into a separate file, thus making it much easier to maintain, and get my first hands-on experience with AJAX programming.  (I’ve seen AJAX applications before—as long as three years ago, actually—but never written any code along those lines.)

Oh, and one more thing—the fact that the Google Maps API key only works for a specific directory, and not any of its subdirectories, drove me up the wall.  Instead of generating a key for meyerweb.com that would cover anything I might do on the site, I’ll have to generate a new key for every new directory.  This is why I set up the directory /eric/tools/gmap/, but that just seems so… confining.  Similarly, it was annoying that the key was completely bound to the full address.  I generated the key for meyerweb.com/eric/tools/gmap/, so if anyone types in www.meyerweb.com/eric/tools/gmap/ they’ll get a key error.  It would be nice if at some future time the keys were a little more flexible than they are now.

December 2014
SMTWTFS
November  
 123456
78910111213
14151617181920
21222324252627
28293031  

Archives

Feeds

Extras