Friday, September 28, 2007

Smooth Move, NBC

So, my wife and I have been buying The Office (among other shows) on iTunes for the past two seasons. With a toddler, it was impossible for us to catch prime time television, so we had resorted to getting it online. It cost money, but we were willing to pay it to be able to watch the shows we were trying to follow on our own schedule in a convenient format.

Unfortunately, NBC recently threw a hissy fit with Apple about not wanting to, oh, have a reasonable, consistent price for digital media at the Apple Store. Apparently they think that their content should cost more than everybody else's content and that Apple should pay them more money for the right to deliver it. They took their ball and went home.

So, looking on the Apple Store for the new season, it's not there. I can't pay $2 an episode to Apple and NBC because NBC are being a bunch of petulant children. Instead, we go to NBC.com to watch it fer free!!! That'll show Apple, right?

Wrong. A show like The Office is all about comedic timing, and since you can't actually download the episode, out of some paranoid fear of who-knows-what, you have to watch it streaming. Except that their media player is crap, so it cuts out, stutters, and even fails, which makes the comedy fail. And their chapter browser doesn't even work - when we selected chapter 5 for the fifth time because the video cut out, it kept starting back at chapter 1. It stops being funny if you have to watch it several times in a row.

Indeed, I ended up thinking, while watching a frozen frame of two characters in mid-sentence talking to each other for several minutes, "You know what? This show isn't really worth the effort, even if it's free now." The extra hassle made what was once an enjoyable treat after the kid goes to bed into a frustrating experience which I eventually abandoned. I'm not going to follow The Office any more.

And do you know something else? The Daily Show actually IS on iTunes. And they let us download it (granted, with DRM, but that's never going to change so long as the content owners continue living in the fantasy land where they can hate and love their customers at the same time). So NBC just lost a paying customer. If we hadn't been forced to watch the painful first episode of The Office separately on NBC.com, we probably would have bought the season pass. But now, that cash is going to go to a different network. Apple's still getting their cut, NBC's not.

Bye, bye, NBC. Call me if you ever pull your head out of your ass.

Tuesday, September 11, 2007

9 JavaScript Tips You May Not Know | Ayman Hourieh's Blog

Ayman Hourieh has provided a nice overview of 9 JavaScript Tips You May Not Know. I knew almost all of these, but there were a few nuggets in there I thought were clever, such as the way to model linked lists with arithmetic, and the static local variables trick.

Preloading Both Images AND Scripts in Javascript

We've lately begun developing content for the iPhone and other mobile devices, so I'm starting to get into Javascript programming more.

For an educational game I'm building, I wanted to show a loading screen while the device is downloading all the script information over the slow EDGE network. Unfortunately, while there is a lot of information on the web about preloading images for faster response time in Javascript applications, there's not much about preloading Javascript scripts. In fact, I saw several "it can't be done" type responses.

It turns out it can be done, and quite easily, by leveraging the power of Prototype's AJAX calls. Because Prototype will eval() any AJAX response that comes back with a Javascript MIME type, you can essentially use it to deliver your scripts, rather than using the typical <script> tag, and provide a pretty download bar as it goes.

Here's some code:

//   asset_loader.js
// This object handles all the assets for the page.

// First, the master list of application assets to preload.
// All images are considered to be optional unless required is set to 1.
// All scripts are considered to be required unless optional is set to 1.

var assets = {

// List images here.
// Each image is an associative array with:
// id: an identifier
// url: a url to load the image from (can be relative)
// required: (optional) if '1', calls failure callback
// if there is an error loading this image
// (Thus, all images default to being optional.)

images: [
{ id: 'an_image', url: 'images/an_image.png' },
{ id: 'another_image', url: 'images/another_image.png' },
{ id: 'required_image', url: 'images/important_image.png', required: 1 }
],

// List scripts here.
// Each script is an associative array with:
// id: an identifier
// url: a url to load the script from (can be relative)
// optional: (optional) if '1', does not call failure callback
// if there is an error loading this script
// (Thus, all scripts default to being required.)

scripts: [
{ id: 'required_javascript_thing', url: 'javascripts/important.js' },
{ id: 'optional_thing', url: 'javascripts/optional.js', optional: 1 }
]

};

// The preloader object.
// This object handles the job of preloading everything.
// Usage:
//
// preLoader.startLoading( preloadSuccess, preloadFailure, preloadStatus );
//
// Where:
// preloadSuccess: the function to call when preloading was successful
// preloadFailure: the function to call when preloading failed
// preloadStatus: the function to call to report status of preloading in progress
//
// Scripts grabbed in this manner are eval()'d by Prototype.

var preLoader = {
errors: { images: 0, scripts: 0 },
errortext: '',
progress: 0,
startLoading: function( completeCallback, errorCallback, statusCallback ) {
this.success = completeCallback;
this.failure = errorCallback;
this.status = statusCallback;
this.loadNextImage();
},
loadNextImage: function() {
if (this.progress >= assets.images.length) {
this.progress = 0;
this.loadNextScript();
return;
}
imageObject = new Image();
imageObject.onload = function() { preLoader.imageLoaded(); }
imageObject.onerror = function() { preLoader.imageError(); }
imageObject.src = assets.images[ this.progress ].url;
assets.images[ this.progress ].image = imageObject;
},
imageLoaded: function() {
var perc = Math.round((this.progress + 1) * 100 / assets.images.length);
assets.images[ this.progress ].success = 1;
this.preloaderMessage( perc, 'Loading Images', assets.images[ this.progress ].url );
this.progress++;
this.loadNextImage();
},
imageError: function() {
var perc = Math.round((this.progress + 1) * 100 / assets.images.length);
assets.images[ this.progress ].success = 0;
this.errors.images++;
this.errortext += '<div>Error loading ' + assets.images[ this.progress ].url + '</div>';
this.preloaderMessage( perc, 'Loading Images', assets.images[ this.progress ].url );
if (assets.images[ this.progress ].required == 1) return this.failure();
this.progress++;
this.loadNextImage();
},
loadNextScript: function() {
if (this.progress >= assets.scripts.length) {
this.progress = 0;
this.success();
return;
}
this.ajax = new Ajax.Request( assets.scripts[ this.progress ].url, {
method: 'get',
onSuccess: function(transport) {
preLoader.scriptLoaded();
},
onFailure: function(transport) {
preLoader.scriptError();
}
});
},
scriptLoaded: function() {
var perc = Math.round((this.progress + 1) * 100 / assets.scripts.length);
assets.scripts[ this.progress ].success = 1;
this.preloaderMessage( perc, 'Loading Scripts', assets.scripts[ this.progress ].url );
this.progress++;
this.loadNextScript();
},
scriptError: function() {
var perc = Math.round((this.progress + 1) * 100 / assets.scripts.length);
assets.scripts[ this.progress ].success = 0;
this.errors.scripts++;
this.errortext += '<div>Error loading ' + assets.scripts[ this.progress ].url + '</div>';
this.preloaderMessage( perc, 'Loading Scripts', assets.scripts[ this.progress ].url );
if (assets.scripts[ this.progress ].optional != 1) return this.failure();
this.progress++;
this.loadNextScript();
},
preloaderMessage: function( perc, header, msg ) {

this.status( perc, header, msg, this.errortext );

}

};


...and here's a sample usage which tries preloading the given elements, and displays a "Failure" or "Success" message depending on whether the items were successfully loaded. (In practice, you'd probably not burn out all that innerHTML each frame, but this works as a demo.)

<html>
<head>
<title>Javascript Preloading Demo</title>
<meta name="viewport" content="width=320, user-scalable=no" />
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen, tv" />
<script src="javascripts/prototype.js" type="text/javascript"></script>
<script src="javascripts/game_loader.js" type="text/javascript"></script>
<script>
// <![CDATA[
// Sample preloading code.

function initialize() {
preLoader.startLoading( preloadSuccess, preloadFailure, preloadStatus );
}

function preloadStatus( perc, header, msg, errors ) {
$('preloader-message').innerHTML = '\
<div style="text-align: center; font-weight: bold; color: #999; margin-top: 80px;">' + header + '</div> \
<div style="margin: 0px 20px; border: 2px solid #00f; position: relative; height: 16px;"> \
<div style="position: absolute; left: 0px; top: 0px; width: ' + perc + '%; height: 16px; \
background-color: #008;"></div> \
<div style="position: relative; text-align: center; color: #999;">' + msg + '</div> \
</div>';
$('preloader-error').innerHTML = errors;
}

function preloadFailure() {
$('preloader').innerHTML = '<div style="text-align: center;"><h1>Failed to Load!</h1></div>';
}

function preloadSuccess() {
$('preloader').innerHTML = '<div style="text-align: center;"><h1>Success!</h1></div>';
}
// ]]>
</script>
</head>
<body onLoad="initialize();" style="border: 1px solid #666;">
<div id="preloader">
<div id="preloader-message"></div>
<div id="preloader-error"></div>
</div>
</body>
</html>


If you're not using Prototype, this same principle could be applied using a standard XMLHttpRequest.

Update

Some notes on the usage of the above code. As is typical with eval()'ed code, objects that you create in the global namespace using the above method will not persist. Depending on how you've architected your code, you may not be able to just "drop in" your Javascript and have it work.

However, this is easily remedied. All you need to do is create an object that will persist, and then attach your objects and methods to that. (This is generally a good idea anyway, rather than cluttering up the global namespace.)

For instance, you might add, to the loading script code above, a global object called "app". Then, if you wanted to add a "mainMenu" object, do "app.mainMenu = { whatever };" Since app persists, so too would app.mainMenu.



Update#2

See Andrew's comment below for an alternate method for preloading scripts. (Thanks, Andrew!)

Monday, September 10, 2007

Update to Gordo

I've released a new build of our "Gordo" digital puppet which includes a new feature suggested by one of our users.

Basically, it extends the "keypress performance playback" control mode by letting you control Gordo using the keyboard (not the microphone) when a performance is not playing.

This way, you can have a prerecorded performance with a soundtrack that can be triggered with a keypress (say, for the introduction to a show), but you can still have Gordo interacting with your visitors while they are getting themselves seated.

If you try it out, let me know whether it works for you, or if you have any other troubles. Thanks!

Friday, September 07, 2007

Frosty: a New Digital Puppet in the Works

Here's a teaser for the new digital puppet I'm working on for ImaginEERIEing:
Frosty the Snowman
I hope I didn't scare off any of my fellow home haunters, but my wife thought that people would be interested in these puppets for the holiday season, too. As you can see, I've been working on a singing snowman with a top hat. I will probably name him "Frosty" for obvious reasons.

Question: What would you use a Christmas-themed digital puppet for? I know what Halloween-themed digital puppets are used for, so I can customize the controls and other features to the use that they'll be put to, but I'm less sure about what a Christmas-themed digital puppet would be used for. If I had an idea of how Frosty might be used, I could probably have a better feature set.

Tuesday, September 04, 2007

Gordo Available For Purchase

Well, the Gordo demo has been out for quite a while, and I haven't received any problem reports, so I've released Gordo for sale.
The Many Moods of Gordo
Gordo is our new digital puppet for 2007, a new addition to our existing line-up of Yorick and Mirror digital puppets. It's a haunted pumpkin that has a friendly, tot-friendly side and a corrupted, angst-ridden-teen-friendly side. You can spook or delight your visitors, or do both - Gordo can switch between modes instantly to accommodate all your visitors.

There is a free demo you can download to try it out. The full version is $15.

If you like Gordo, or Yorick, or any of our offerings, I'd appreciate it if you spread the word a bit to your fellow home haunter friends. We're not getting rich off this; it's just helping to offset the costs of our own home haunt each year.

And, as always, if you use Gordo in your home haunt, please send us a link where we can check out videos or photos of it in action - we're always amazed at the creative uses people put our products to.

Happy Haunting in 2007!

Monday, September 03, 2007

The Perils of Virtual Playtesting

I thought I was so clever.

For the Carnival of Souls Board Game I released yesterday, I had done a fair amount of playtesting, so I was pretty confident that the game balance was pretty good.

However, the playtesting was done by myself using a small Javascript application that modeled the game, and allowed me to run through many playtests quickly.

We played the physical game "for real" for the first time tonight, and one thing is clear: virtual playtesting doesn't get you all the way there. While the game balance was right on, with the game coming right down to the wire, it was the physical game components that bogged the game down and needed work.

It's a good object lesson that the "twiddly bits" of a game are not merely conceptual, but need to be experienced to understand their impact on the gameplay. You just can't tell what the tactile experience of the game will be until you play it.

In the case of Carnival of Souls, I have a lot of little chips representing different resources, which you earn every turn. Then, you spend them every turn to take on different challenges. This means that whoever is banker ends up shuffling these little chips around incessantly, which became rather annoying to that player.

The fix, I think, is to introduce character cards, a'la Arkham Horror, which have little sliders for each resource type, so that each player can keep track of his or her own resources, and eliminates the need for a banker. (In the interim, until I release a fix, you may want to track your resources on paper if you decide to play the game.)

I also learned some other things about the physical game, such as the need to print out and assemble more than one of each die. They were being passed around too much. (If you decide to play, print out the die sheet two or three times and assemble more dice.)

On the flip side, there were some pleasant surprises. It was the first time the boards had been assembled together, and it made for a nice little layout. The crypt doors and the gravestones worked better than I thought they would, because they had a nice tactile feel to them; I might change the others to be similar, so that you're flipping things over in all cases.

On the whole, the game worked pretty well. I still need to come up with a mechanic that makes the end of the game not end up having so many turns of just trading in resources for other resources. I have a few ideas about that, but the game is still pretty well balanced, so it still works as a good diversion, I think. I enjoyed it.

If you play the game, let me know what you think.

Saturday, September 01, 2007

Carnival of Souls Board Game

Carnival of Souls Board Game Logo
Continuing the tradition of releasing other fun things for our Halloween visitors besides candy, we've made a fun board game about the Carnival of Souls that you can print out and play at home.

It's a cooperative, rather than competitive, board game, where the players take on the roles of the spirit guardians who defend the mortal world from the evil ghosts of the vile Blackwood family, who seek to slip out of the spirit world and plunge us into a nightmare world of darkness. With the help of Madame Sarita, the players work as a team to defeat the forces of evil, taking on challenges like fighting werewolves, befriending the Magic Mirror, and exploring the Blackwood Family mausoleum.

We're releasing a draft of it now in hopes of some people trying it out and letting us know how it plays. We're going to be handing out URL's at Halloween where people can come and get all sorts of information about the Carnival of Souls, and the board game will be one of the site's offerings. If you try it out, please let us know how it works, whether good or bad.

Enjoy!