Sunday, October 7, 2012

Forcing the Editor to Draw, Dammit

‹prev | My Chain | next›

Of the half dozen or so remaining issues in the code editor for Gaming JavaScript, only two of them are significant bugs. The first is that the programmer needs to click on the document after hiding the code so that events are properly recorded. The second is that code seems to disappear sometimes. The latter problem seems the more difficult of the two, so I start with that one.

The code does not actually disappear, but it sure looks as though it is gone:


It is not easy to get into this situation, but it is also not too difficult. The easiest way to reproduce is to highlight some of the code, scroll, then click somewhere in the middle of the highlighted code. At some point, a good portion of the code will vanish.

If I move the cursor up a little bit, the code is eventually revealed:


But that is clearly not a winning usability scenario—especially for kids just learning to program.

Interestingly, this seems to be a Chrome-only issue as I am unable to get Firefox to fail in the same way no matter what I try. Since I plan to require Chrome (for the built-in JavaScript console) in Gaming JavaScript, I need to solve this.

This may end being one of those weird browser edge cases that I have to solve by forcing redraw. Hopefully, I can come up with something a little more robust. Before digging in, I note that the line numbers on the side do not suffer from the bug.

Inspecting the page elements, I see that the display, the code, and the toolbar are three separate, absolutely positioned elements. The first two have the exact dimensions of the browser viewport. If I hide the code <div>, then both the code and the line numbers are hidden:


So the problem lies somewhere in that absolutely positioned <div>. Digging down a bit further, I find that, after recreating the bug, elements that Chrome thinks it is displaying, are not, in fact, displayed:


The computed style for that <span> is "inline", not "none", so it should be visible. Add the fact that Firefox does not see this problem and this begins to look very much like a Chrome bug, albeit a very hard to reproduce one.

While messing about in the JavaScript console, I notice that, if I disappear all of my code <pre> tags:
function hidePres() {
  var pres = document.getElementsByTagName('pre');
  for (var i=0; i<pres.length; i++) {
    pres[i].style.visibility = 'hidden';
  }
}
And then restore them:
function showPres() {
  var pres = document.getElementsByTagName('pre');
  for (var i=0; i<pres.length; i++) {
    pres[i].style.visibility = 'visible';
  }
}
Then my disappeared code is restored.

Since this situation seems to arise on click events, I try a document click handler to call these two in succession:
document.addEventListener( 'click', function(event) {
  console.log('Trying workardoun');
  disappearingCodeFix();
});

function disappearingCodeFix() {
  hidePres();
  showPres();
}
That kind of works. When the click event fires, it seems to do the trick, but it rarely fires. It seems that the codemirror editor is capturing click events and not allowing them to propagate. Tsk.

Instead, I switch to a simply calling my fix every 5 seconds. But that never works. It seems that I need an interval—even if only a millisecond—in between the two functions so that Chrome has time to recognize that it needs to repaint those invisible-now-visible elements:
function disappearingCodeFix() {
  hidePres();
  setTimeout(showPres, 1);
}

setInterval(disappearingCodeFix, 5*1000);
That works... except that there is a perceptible flash every 5 seconds. I will file that away as plan Z.

After much fiddling and googling, I stumble across this solution (based on a StackOverflow solution):
function disappearingCodeFix() {
  var pres = document.getElementsByTagName('pre');
  for (var i=0; i<pres.length; i++) {
    pres[i].style.display = 'none';
    pres[i].offsetHeight;
    pres[i].style.display = 'block';
  }
}

setInterval(disappearingCodeFix, 1000);
And yes, the offsetHeight reference does appear to be necessary. So in the end I am forced to use a manual redraw solution. I am still not quite satisfied, but it works. I may file this away as plan B and pursue something more robust or I may pick up with other code-editor issues tomorrow.


Day #532

Saturday, October 6, 2012

Reducing the localStorage in Code Editor

‹prev | My Chain | next›

Today, I continue my efforts to adopt Mr Doob's code editor for Gaming JavaScript. As of yesterday, I think I have a decent local development environment setup with express.js. It requires me to modify my appcache settings when doing local development, but it works.

One other thing that I need to do when developing locally, especially when I am developing the first-time user experience, is to delete the localStorage contents. The code editor stores its work in localStorage (though it can also archive to local disk). To delete localStorage while trying the first time experience, I open the JavaScript console, switch to the Resources tab and right-click delete the localStorage:


I have been developing the new-project UI, which creates new projects from templates. For the most part, those templates will be useful starting points for Gaming JavaScript projects. In addition to new projects, it also seems useful to have a demo app pre-loaded for the first-time user. Mr Doob has a spinning icosahedron:


I leave this for the time being. In the near future, I will likely replace it with something a little more Gaming JavaScript-specific. Regardless, it seems reasonable to treat this as a template as well. That way, if a reader messes it up, which seems likely for new coders, there is always a way to retrieve it.

So I add it the list of templates:
var templates = [ {
 filename: '3D starter project',
 filetype: 'text/plain',
 autoupdate: true,
  code: /* ... */
}, {
 filename: 'Empty project',
 filetype: 'text/plain',
 autoupdate: true,
 code: /* ... */
}, {
 filename: 'Spinning Multi-Sided Thingy',
 filetype: 'text/plain',
 autoupdate: true,
 code: /* ... */
} ];
If the localStorage is empty, I then add the last template (the icosahedron) to the initial list of coding projects:
var documents = ( localStorage.codeeditor !== undefined ) ?
 JSON.parse( localStorage.codeeditor ) :
  [templates[templates.length-1]];
That seems a reasonable way to create the first project, but leave it around for future use without duplication.

With that, I am nearly done with the new-project UI. The last thing that I need is to use the template name in the dialog:


I am probably being to clever here, but I use reduce() to find the correct code for this:
var createProject = function(name, template_name) {
  // ...
  var code = templates.
    reduce(function(code, template) {
      if (template.filename == template_name) return template.code;
      return code;
    }, undefined);

 documents[0].code = code;
  documents[0].filename = name;

 localStorage.codeeditor = JSON.stringify(documents);

  changeProject(name);
};
Were the templates list longer, I might like a means to short circuit the iteration. Since there are only three templates, and I am unlikely to add many more, this will work.

That ought to do it for the new-project dialog. At this point, I can tell readers to create a new project with a name like "My Player" and get started with the lesson. I commit my work, including an update to the appcache manfiest:
CACHE MANIFEST
# 2012-10-06

CACHE:
/favicon.ico
/code-editor/
# ...
And call it a night here. Up tomorrow: fixes to the save dialog.



Day #531

Friday, October 5, 2012

Local Appcache Dev with Express.js

‹prev | My Chain | next›

After some thought (and last night's pain), I plan to perform local development of the Gaming JavaScript the code editor under a simple express.js (v2.x) server.

I strip out nearly everything from the standard, generated express code in my local repository, leaving behind only what is necessary to serve up static resources from the current directory. The contents of app.js are then:
var express = require('express')
  , app = express.createServer();

app.configure(function(){
  app.use(express.static(__dirname));
});

app.listen(3000, function(){
  console.log(
    "Express server listening on port %d in %s mode",
    app.address().port,
    app.settings.env
  );
});
I do not really need that console.log(), but I am used to seeing it when I spin a server:
➜  code-editor git:(gamingjs) ✗ node app
Express server listening on port 3000 in development mode
With that, and yesterday's removal of the manifest="editor.appcache" attribute from the HTML tag of code-editor's index.html, I am ready to code locally:


And that seems to do the trick. Eventually, I am able to build a list of templates that will be used for new projects (boy this is hard without jQuery):
var openNewDialog = function() {
  // ...
  var templateField = document.createElement( 'select' );
  templateLabel.appendChild(templateField);
  templates.forEach(function(template) {
    var optionField = document.createElement( 'option' );
    optionField.textContent = template.filename;
    templateField.appendChild(optionField);
  });
  // ...
}
Which results in:


That will do for a stopping point today. I can live with this as a way for Gaming JavaScript kids to start new work. Up tomorrow: more fixes for the code editor.

Day #530

Thursday, October 4, 2012

Appcache and Local Development

‹prev | My Chain | next›

With the Three.js retrograde motion simulation (which is amazing) complete, I believe that I am feature complete for Gaming JavaScript. That is, I think that I have enough games and simulations to produce a pretty cool book. So tonight, I take a step back to focus on an equally important aspect of the book: the code editor.

I am committed to using Mr Doob's incredible code editor. It has the right combination of code editor, syntax highlighting, and immediate feedback that will be perfect for kids learning to code:


Mr Doob is completely up front that this code editor is in a proof of concept state—not nearly ready for pull requests. But I need more than a few changes for use in Gaming JavaScript. So tonight I get started with the most pressing issue: a "New" dialog. This is pressing because I've got several chapters that tell kids to open an old project, then immediately save it, to create a new project. I even have screenshots of doing silly things like this.

But of course there is a better way.

First up, I need to figure out a workflow for editing the code editor. Currently, my code editor fork is a git submodule on the Gaming JavaScript site. Further complicating things is that the code editor is an appcache application. So I need a way to develop locally and to easily see changes immediately.

I stick with a jekyll for running the Gaming JavaScript locally (it's a GitHub pages site). It seems reasonable to stick close to the production environment. To see changes immediately, I add a symlink to my local repository:
➜  gamingjs git:(gh-pages) ln -s ../code-editor ice.local
➜  gamingjs git:(gh-pages) ✗ echo ice.local >> .gitignore
I am not going to use this ice.local path anywhere else, so I dot-git-ignore it. I am going to have to remember to update my submodule reference every time I update my code-editor fork, but such is the price of submodules.

To see immediate changes, I update the index.html page of code-editor, removing the manifest attribute:
<!DOCTYPE html>
<html manifest="editor.appcache">
 <head>
  <title>code editor</title>
...
Without the manifest attribute, I am just loading a normal page without have to worry about expiring the appcache:
<!DOCTYPE html>
<html>
 <head>
  <title>code editor</title>
...
I am going to have to make darn sure that I never commit changes to that index.html file that include the removal of that attribute, but I think I can do that.

With that, I am ready to start editing the code editor (that's going to be fun to say). I have most of the editing code in the editor.js file. To add a "New" menu option, I add a function to projectMenu():
var projectMenu = function() {
  menu(
    menuNew(),
    menuOpen(),
    menuSave(),
    menuShare(),
    menuDownload(),
    menuInfo()
  );
};
Where menuNew() is similar to the other menu functions, changed only for the label and the function that opens the corresponding dialog:
var menuNew = function() {
  var el = document.createElement( 'li' );
  el.textContent = 'new';
  el.addEventListener( 'click', function ( event ) {

    openNewDialog();

  }, false );
  return el;
};
It is certainly an interesting challenge doing this without jQuery.

Anyhow, I reload the page to find... a file not found. Ugh.

It seems that jekyll does not care for symbolic links. When it regenerates the site, it ignores symlinks (even if they are explicitly listed in the include directive of the configuration). If I manually restore that symlink, I can reload and see my "New" menu item:


It does not seem as though using a symlink to local development code is going to work. I will have to rethink this, but my best option may be to drop jekyll for local development and instead switch to a simple express.js server directly in the code-editor repository. I could still use jekyll for smoke tests -- especially right before I update the editor on Gaming JavaScript.

Day #528

Wednesday, October 3, 2012

Three.js Retrograde Motion

‹prev | My Chain | next›

Up today I am going to make my Three.js space simulation a bit more space-like. I would also to add a few different cameras to the simulation, time permitting.

First up, I make my background color black by setting the clear color on the renderer:
  renderer.setClearColorHex(0x000000);
This makes the dark side of planets blend completely into the blackness of space. Although this is more realistic, I add a bit of ambient color (it had been 0x000000):
  var surface = new THREE.MeshPhongMaterial({ambient: 0x1a1a1a, color: 0x0000cd});
  var planet = new THREE.SphereGeometry(20);
  var earth = new THREE.Mesh(planet, surface);
  earth.position.set(250, 0, 0);
  scene.add(earth);
That works fairly well:


Next, I add some stars. I figured out how do do that with Three.js particle systems a few days ago. This time, I need to distribute the stars all around the Solar System. A plane far away in the Z direction will not suffice because I need to move the camera around and still see stars. I am unsure what the conceptually simplest way to accomplish this is going to be. For now, I go with the easiest way that I know to do it—trigonometry:
  var stars = new THREE.Geometry();
  while (stars.vertices.length < 1e5) {
    var lat = Math.PI * Math.random() - Math.PI/2;
    var lon = 2*Math.PI * Math.random();
    
    stars.vertices.push(new THREE.Vector3(
      1e5 * Math.cos(lon) * Math.cos(lat),
      1e5 * Math.sin(lon) * Math.cos(lat),
      1e5 * Math.sin(lat)
    ));
  }
  var star_stuff = new THREE.ParticleBasicMaterial({size: 1});
  var star_system = new THREE.ParticleSystem(stars, star_stuff);
  scene.add(star_system);
That gives me nice stars from "above" the Solar System:


And if I spin the camera around, I still see nicely distributed stars.

Figuring out how to position the "earth-cam" that always faces Mars (so that we can witness retrograde motion) is a solved problem. I re-implement in Three.js as:
    var y_diff = mars.position.y - earth.position.y
      , x_diff = mars.position.x - earth.position.x
      , angle = Math.atan2(x_diff, y_diff);
    
    earth_cam.rotation.set(Math.PI/2, -angle, 0)
    earth_cam.position.set(earth.position.x, earth.position.y, 22);
I end by adding a simple document listener that can switch between above-the-solar-system-cam and earth-cam by pressing "1" or "2":
  document.addEventListener("keydown", function(event) {
    var code = event.which || event.keyCode;

    if (code == 49) {
      camera = above_cam;
    }
    if (code == 50) {
      camera = earth_cam; 
    }
    else console.log(code);
    
    event.preventDefault();
    return false;
  });
With that, I can easily switch back and forth between earth-cam:


And above-cam:


That is a fine stopping point for tonight. At this point, I think I need to rework a few things in Mr Doob's code editor to make it more suited to kids working through Gaming JavaScript, so I will pick up with that tomorrow.

Editable / runnable code


Day #528

Tuesday, October 2, 2012

Three.js Planets

‹prev | My Chain | next›

I just can't help myself. One of my favorite things to do with 3D is make astronomical simulations. I find the topic fun and 3D suits it. So I return to my retrograde motion simulator, but this time with Three.js.

For the Sun, I create an ambient light throughout the scene. This ambient light will give the Sun its color, which I specify with the ambient attribute for the Phong material that will play the role of the Sun's surface:
  var ambient = new THREE.AmbientLight(0xffffff); 
  scene.add(ambient);
  
  var surface = new THREE.MeshPhongMaterial({ambient: 0xFFD700});
  var star = new THREE.SphereGeometry(50, 10, 10);
  var sun = new THREE.Mesh(star, surface);
  scene.add(sun);
  
  var sunlight = new THREE.PointLight(0xffffff, 5, 1000);
  sun.add(sunlight)
That works to give the Sun a nice yellow, but to make it behave as a light source for the rest of the Solar System, I add a point light to the sun, making it fairly bright and extending well out into space (1000).

To get the Phong materials to work, I need to use the WebGL renderer instead of the canvas renderer that I default most of my work to (for greater compatibility). I suppose in Gaming JavaScript that I will have to start with MeshBasicMaterial, which ought to be fine for the Sun. Not so much for planets.

Anyhow, with that, I have my Sun:


Next up, I add my planets:
var surface = new THREE.MeshPhongMaterial({ambient: 0x000000, color: 0x0000cd});
  var planet = new THREE.SphereGeometry(20);
  var earth = new THREE.Mesh(planet, surface);
  earth.position.set(250, 0, 0);
  scene.add(earth);
Since I am using Phong materials, I get a nice dark-side of the Earth effect:


When I do the same for Mars, however, I notice that it is looking... stretched:


At first, I think that I will be forced to switch to an orthographic camera to deal with this. That is not necessarily a bad thing as it probably makes a little more sense for astronomy simulations. After all, the vast distances that are usually involved make "perspective" a quaint concept. But in this case, I don't have to introduce the difference between cameras. I had been setting the field of view for the camera to a rather wide 75°:
  var camera = new THREE.PerspectiveCamera(75, aspect_ratio, 1, 10000);
  camera.position.z = 500;
  scene.add(camera);
Cutting that down (and moving back a bit), does the trick:


I still may switch to the orthographic camera, but at least now I am not forced to.

Last up, is movement of the planets. Back when I was first doing these simulations, I would add the planet to a frame of reference and rotate the frame of reference. This time, however, I stick with simple geometry:
  clock = new THREE.Clock();

  function animate() {
    requestAnimationFrame(animate);
    
    var t = clock.getElapsedTime();
    
    var e_angle = 3 * t * 0.1;
    earth.position.set(250* Math.cos(e_angle), 250* Math.sin(e_angle), 0);

    // Now, show what the camera sees on the screen:
    renderer.render(scene, camera);
  }
  
  animate();
If memory serves, knowing the X-Y position in scene coordinate space will come in handy.

And that does the trick:


That is a good stopping point for tonight. Up tomorrow, I will add cameras on the two planets to witness retrograde motion. And maybe some background stars.

Editable source code

Day #527

Monday, October 1, 2012

Iterating JavaScript for Kids

‹prev | My Chain | next›

After some messing about, I think the follow code ought to suffice to remove letter "meteors" from my learn-to-type Three.js game:
  function removeMeteor(number) {
    var head = meteors.slice(0, number)
      , tail = meteors.slice(number + 1);

    scene.remove(meteors[number].sprite);
    meteors = head.concat(tail);
  }
Given an index of the array, I slice from the beginning of the array up to, but not including the element at the given index (this is how slice() works), assigning the result to the "head" of the array. I then slice from the element after the supplied index to the end of the array, assigning the result to the "tail" of the array. The array with the indicated element removed is then the concatenation of the head and tail. I update the global variable accordingly and remove the corresponding Three.js Sprite from the scene.

I am trying to not care too much about global variables in Gaming JavaScript. I do not think the discussion really adds to kids' understanding of things. If I was concerned, I could have made this a method of Array. Then again, the necessity to remove the sprite as well would likely force me to create a Meteors class as a subclass of Array that could perform both operations. Yuck. Especially since I am trying my best to keep this conceptually easy for kids.

To decide which meteor needs removing, I make another compromise:
  document.addEventListener('keypress', function(event) {
    var letter_pressed = String.fromCharCode(event.keyCode);
    
    for (var i=0; i<meteors.length; i++) {
      if (letter_pressed != meteors[i].letter) continue;
      removeMeteor(i);
      break;
    };
  });
The compromise here is the use of a for-loop. Call me crazy, but I had hoped to stick with array methods in Gaming JavaScript. Callbacks in JavaScript are a way of life. Even in this example I am already using an event callback, so an iterator callback hardly constitutes added complexity. And for-loops are just ew.

Unfortunately, I cannot get away with a forEach() in this case:
  document.addEventListener('keypress', function(event) {
    var letter_pressed = String.fromCharCode(event.keyCode);
    
    meteors.forEach(function(meteor, number) {
      if (letter_pressed != meteor.letter) return false;
      removeMeteor(number);
      return true;
    });
  });
The problem is that this code will not stop after the first meteor is removed. If there are multiple "f" meteors falling, I will end up destroying multiple meteors with a single shot. Worse yet is that the the meteors array shrinks with a hit, meaning that a subsequent removal could be for an out-of-bounds entry in the array.

I could use some():
    meteors.some(function(meteor, number) {
      if (letter_pressed != meteor.letter) return false;
      removeMeteor(number);
      return true;
    });
The some() method will stop after the first true return value from the callback. But of course, some() makes no semantic sense in this instance. It is meant to be used in a boolean context and I am completely ignoring the return value.

The filter() method almost works, but I would have to add a tracking variable to get the original indexes so that I could remove the first hit:
    var hits = meteors.
          filter(function(meteor) {
            return (letter_pressed == meteor.letter);
          });
I might just as well stick with a for-loop, which is what I do. Bother.

With that more or less settled, all that remains in the game is to keep score and to increase the difficulty level. Scoring is a simple matter of incrementing a score integer whenever the correct letter is typed:
  document.addEventListener('keypress', function(event) {
    var letter_pressed = String.fromCharCode(event.keyCode);
    
    for (var i=0; i<meteors.length; i++) {
      if (letter_pressed != meteors[i].letter) continue;
      score++;
      removeMeteor(i);
      break;
    };
  });
To keep things simple, increasing the difficulty level will only involve new letters:
  function nextLetter() {
    var pool = ['f', 'j'];
    if (score > 10) pool = pool.concat(['d', 'k']);
    if (score > 20) pool = pool.concat(['s', 'l']);
    if (score > 30) pool = pool.concat(['a', 'r', 'u']);
    
    return pool[Math.floor(Math.random() * pool.length)];
  }
After the player has mastered the touch-typing "home keys", new keys are added. I can leave all letters as an exercise for the kids following along. That seems like a decent stopping point for this game. I have one more game / simulation that I would like to include in the book. I will pick up with that tomorrow.

Editable code


Day #527