Initial push; huzzah.

This commit is contained in:
Ryan McGrath 2011-07-10 03:15:27 -04:00
commit 0b1c7ff386
12 changed files with 1188 additions and 0 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2009 - 2011 Ryan McGrath
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

64
build.py Normal file
View file

@ -0,0 +1,64 @@
#!/usr/bin/python
"""
Build-script that brings together all the JS files for wii.js and
takes care of minifying it down to a decent size.
Before you even ask, no, we're not using uglify.js or Closure. Both
of them are simply too aggressive for their own good; while normally this
is an excellent feature to have, the JS engine that the Opera version on the Wii
uses has a really odd set of quirks when you start trying to get smart with code,
and YUI is the only minifier that doesn't appear to totally destroy it all through
obfuscation. We'll forego a few bytes to have the thing working. ;P
Uglify also requires Node.js. While I *love* Node, I'll wait until it's more readily
available on Windows - there are a large amount of Windows-based programmers that I'm
not keen on keeping out of development.
"""
import os
from subprocess import call
currdir = os.getcwd()
# The names of our JS files (minus the .js) that we want to build into the
# final distribution.
DEPENDENCIES = [
'wii',
'util',
'remote',
]
# What we're going to end up injecting as our core build, into core.js.
inject_build = ''
def minify_code(filename, final_filename):
"""
Dips out to Java/YUI compressor and runs the minifier on the specified file, dumping the
output into the specified final_filename.
"""
#cmd = 'java -jar %s/utilities/yuicompressor-2.4.2.jar %s -o %s' % (currdir, filename, final_filename)
call(['java', '-jar', '%s/utilities/yuicompressor-2.4.2.jar' % currdir, filename, '-o', final_filename])
# Run through each dependency, read it into "inject build", then do a simple split on the
# contents of core.js and wrap it all up.
for dependency in DEPENDENCIES:
f = open('%s/js/src/%s.js' % (currdir, dependency), 'r')
inject_build += f.read()
f.close()
# Open core.js, split it on our build spot, wrap junks.
f = open('%s/js/src/core.js' % currdir, 'r')
core = f.read()
f.close()
core = core.split('/*{{inject_build}}*/')
# Write out a non-minified build.
f = open('%s/js/wii.js' % currdir, 'w')
f.write(core[0])
f.write(inject_build)
f.write(core[1])
f.close()
# Write out a minified build.
minify_code('%s/js/wii.js' % currdir, '%s/js/wii.min.js' % currdir)

25
css/demo.css Normal file
View file

@ -0,0 +1,25 @@
/**
* Wii Demo CSS
*
* Uses a basic CSS reset, and then some very rudimentary styles to make stuff easy
* on the eyes with the Wii console.
*/
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, font, img, kbd, q, s, samp,
small, strike, sub, sup, tt, var, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {
margin: 0; padding: 0; border: 0; outline: 0; font-weight: inherit; font-style: inherit; font-size: 100%; font-family: inherit; vertical-align: baseline; }
:focus { outline: 0; }
body { line-height: 1; }
ol, ul { list-style: none; }
table { border-collapse: separate; border-spacing: 0; }
caption, th, td { text-align: left; font-weight: normal; }
blockquote:before, blockquote:after, q:before, q:after { content: ""; }
blockquote, q { quotes: "" ""; }
html {
color: #333;
font: normal 30px/34px helvetica, arial, sans-serif;
padding: 0px;
text-align: center;
background: #f9f9f9;
}

99
index.html Normal file
View file

@ -0,0 +1,99 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Wii.js Demo</title>
<link rel="stylesheet" type="text/css" media="screen" href="css/demo.css">
</head>
<body>
<h1>Wii.js Demo</h1>
<p>
This is a demo page meant to show how easy it is to interact with the Nintendo Wii remotes using Javascript. It's
powered by <a href="http://github.com/ryanmcgrath/wii-js/">wii-js</a>, a Javascript library that abstracts the differences
and pain points associated with using the Wii remotes.
</p>
<p>
This demo is really simple, and works best with two Wii remotes (Wiimotes). Hold either Wiimote #1 or #2 horizontal (sideways), and use
the directional pad to move the box floating in the bottom right corner around. Wiimote #1 will turn the box red and fill it with a "1",
and Wiimote #2 will turn the box green and fill it with a "2". Have fun!
</p>
<p>
If you find bugs or want to get in touch, come find me at the project home on <a href="http://github.com/ryanmcgrath/wii-js/">GitHub</a>! You
can also find me on Twitter (<a href="http://twitter.com/ryanmcgrath">@ryanmcgrath</a>), or my <a href="http://venodesigns.net/">personal website</a>.
I'm always interested in what others are building!
</p>
<p>
- Ryan McGrath
</p>
<div id="x" style="border: 5px solid #010101; position: absolute; top: 300px; left: 300px; width: 100px; height: 100px; padding: 10px; color: #010101;">x</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script src="js/wii.js"></script>
<script>
var wiimote = new Wii.Remote(1, {horizontal: true}),
wiimote2 = new Wii.Remote(2, {horizontal: true}),
x = $("#x");
wiimote2.when('pressed_up', function() {
x.html('2').css({
'background-color': 'green',
'top': parseInt(x[0].style.top) - 50
});
});
wiimote2.when('pressed_down', function() {
x.html('2').css({
'background-color': 'green',
'top': parseInt(x[0].style.top) + 50
});
});
wiimote2.when('pressed_right', function() {
x.html('2').css({
'background-color': 'green',
'left': parseInt(x[0].style.left) + 50
});
wiimote2.when('pressed_left', function() {
x.html('2').css({
'background-color': 'green',
'left': parseInt(x[0].style.left) - 50
});
});
wiimote.when('pressed_up', function() {
x.html('1').css({
'background-color': 'red',
'top': parseInt(x[0].style.top) - 50
});
});
wiimote.when('pressed_down', function() {
x.html('1').css({
'background-color': 'red',
'top': parseInt(x[0].style.top) + 50
});
});
wiimote.when('pressed_right', function() {
x.html('1').css({
'background-color': 'red',
'left': parseInt(x[0].style.left) + 50
});
});
wiimote.when('pressed_left', function() {
x.html('1').css({
'background-color': 'red',
'left': parseInt(x[0].style.left) - 50
});
});
Wii.listen();
</script>
</body>
</html>

27
js/src/core.js Normal file
View file

@ -0,0 +1,27 @@
/**
* core.js
*
* This is the main template file for bringing together
* the various libraries that power this entire little eco-system.
*
* Building the releases requires Python (2.5+); simply run...
*
* python build.py
*
* ...from the /js/ directory.
*
* @Author: Ryan McGrath <ryan@venodesigns.net>
* @Requires: Nothing, top-level file.
*/
;(function(running_inside_wii_browser) {
/**
* If we're not running inside the Nintendo Wii browser, bail out.
* In the future, branch here for touch-enabled devices...?
*/
if(!running_inside_wii_browser) return false;
/*{{inject_build}}*/
window.Wii = Wii;
})(window.opera && opera.wiiremote);

120
js/src/remote.js Normal file
View file

@ -0,0 +1,120 @@
/**
* remote.js
*
* Handles the subscribing to events portion of a Wii remote. It's best to think of this
* as a "request" object; it asks to be notified of events, and the actual events are
* dispatched from the main wii.js file.
*
* @Author: Ryan McGrath <ryan@venodesigns.net>
* @Requires: wii.js, util.js
*/
/**
* var wii_remote = new Wii.Remote(1, {...});
*
* Instantiates a Wii Remote object. Events can be set on each of these objects,
* and the internal game loop will fire events based on the properties subscribed
* to here.
*
* @param remote_id - Number, required. 1 - 4, dictates which Wiimote this object
* relates to.
* @param opts - Object, optional. Allows you to override internal settings and such,
* should you want different behavior.
* @returns Wii.Remote instance.
*/
Wii.Remote = function(remote_id, opts) {
this.remote_id = remote_id;
this.opts = opts;
/**
* If this is the "main" wii_remote, then the bitwise checks will fail
* because it's treated more as a "browsing" device. For these events,
* we'll just store the current wii_remote that's denoted as the "browsing"
* device and let the normal event/key delegation take care of things.
*
* The rest of the wii_remotes will go through the DISPATCHER checks that
* they've subscribed to.
*/
var startupStatus = this.isEnabled();
if(startupStatus) {
if(!startupStatus.isBrowsing) {
Wii.extraRemotes.push(this);
} else {
Wii.currentBrowsingRemote = this;
}
}
};
Wii.Remote.prototype = {
opts: {
/**
* We default the controller to be in the vertical orientation; if
* it's overridden as "horizontal" (false), we'll catch it for the different key
* events and fire accordingly (e.g, the "up" button is different depending on
* how the player is holding the controller).
*/
horizontal: false
},
/**
* A hash of events that this Wii remote is interested in. Each
* entry should be a key (evtName) with a value (response).
* "evtName" is the event name that corresponds with boolean functions
* in the DISPATCHER above, and the response is a remote-bound function
* to call on that event.
*/
evtsInterestedIn: undefined,
/**
* Wiimote.isEnabled()
*
* Determines the status of the wii_remote this object is curious about. Will return
* an updated kPadStatus object if so, false if it's not responding or the data
* is sent back as "invalid". This makes it so we don't bother sending events
* where they're not applicable.
*
* @returns object or boolean.
*/
isEnabled: function() {
var remote = opera.wiiremote.update(this.remote_id - 1);
return (remote.isEnabled && remote.isDataValid ? remote : false);
},
/**
* Wiimote.on(evtName, fn)
*
* Allows you to listen for an event on a given Wiimote. Call this on an instantiated
* Wiimote; "this" will be scoped to the Wiimote object. ;)
*
* @param evtName - String, a supported wii.js (DISPATCHER) event name.
* @param fn - Function, callback function to be executed on the event firing. Will be scoped to the Wiimote.
* @returns object or undefined - instantiated object this was called on to begin with, undefined if evtName is not supported.
*/
when: function(evtName, fn) {
if(typeof Wii.DISPATCHER[evtName] !== 'undefined') {
/**
* THIS IS INCREDIBLY IMPORTANT, DO NOT REMOVE THIS!.
*
* The Wii's browser has an (odd...?) bug wherein if you have a prototype chain
* set up for a given object, and you default keys on the prototype chain to a blank object ("{}", for instance),
* it will _NOT_ make this a unique object, it keeps pointers to the original object that was created by the system
* for the first instantiated object.
*
* This is, needless to say, unlike just about any other JS environment and threw me for a loop for a good 6 hours.
* This line ensures that the first time this property is ever referenced, we get a fresh _CORRECTLY ALLOCATED_ chunk
* of memory to play with and store things in.
*
* Note that this also happens with Array structures, and conceivably anything else that would be using a copy-by-reference
* technique instead of a full clone. We want an object for this case, though, so we're not doing iterations on event dispatch,
* just a 1:1 lookup instead.
*/
if(this.evtsInterestedIn === undefined) this.evtsInterestedIn = {};
this.evtsInterestedIn[evtName] = Wii.util.bind(this, fn);
return this;
}
return undefined;
}
};

66
js/src/util.js Normal file
View file

@ -0,0 +1,66 @@
/**
* util.js
*
* A basic utility wrapper; anything extra that's often re-used should
* find its way here (e.g, debuggerDiv, bind, etc).
*
* @Author: Ryan McGrath <ryan@venodesigns.net>
* @Requires: wii.js
*/
Wii.util = {
/**
* Wii.util.debug(err);
*
* The Wii has... such little options for debugging, but we can try and make this a bit nicer.
* This accepts a stack trace (see example code below) and then outputs it to the screen.
*
* try { ... } catch(e) { Wii.util.debug(e); }
*/
debug: function(err) {
if(Wii.debuggerDiv === null) {
Wii.debuggerDiv = document.createElement('div');
Wii.debuggerDiv.style.cssText = [
'width: 90%;',
'height: 90%;',
'padding: 10px;',
'font-size: 26px;',
'font-family: monospace;',
'overflow: scroll',
'position: absolute;',
'top: 10px;',
'left: 10px;',
'color: #f9f9f9;',
'background-color: #010101;'
].join('');
document.body.appendChild(Wii.debuggerDiv);
}
if(typeof err === 'string') {
Wii.debuggerDiv.innerHTML = err;
} else {
var msg = '';
for(var e in err) { msg += e + '=' + err[e] + '<br>'; }
Wii.debuggerDiv.innerHTML = msg;
}
Wii.debuggerDiv.style.display = 'block';
},
/**
* Wiimote.util.bind(bindReference, fn)
*
* Takes a reference (an object to scope to "this" at a later runtime) and binds it to a function (fn).
*
* @param bindReference - An object to set as the "this" reference for a later function call.
* @param fn - A function to bind the "this" object for.
* @returns fn - A new function to pass around, wherein it's all scoped as you want it.
*/
bind: function(bindReference, fn) {
return function() {
return fn.apply(bindReference, arguments);
};
}
};

224
js/src/wii.js Normal file
View file

@ -0,0 +1,224 @@
/**
* wii.js
*
* Provides a sane, event-based documented wrapper around the Wiimote controls
* and API inside the Opera-based Nintendo Wii web browser.
*
* This is not produced by nor endorsed by Nintendo or Opera. I've written it
* on my own because I see a device that's sitting in millions of living rooms
* but being sorely neglected because a company couldn't get their act together
* in regards to third party development. ;)
*
* @Author: Ryan McGrath <ryan@venodesigns.net>
* @Requires: Nothing.
*/
/* Wii
*
* Top level namespace. Contains information about the main event loop, etc.
*/
var Wii = {
/**
* A "global" reference to the Wiimotes we're currently watching. Lets us run
* through on each loop/iteration and poll for a new status on it.
*/
extraRemotes: [],
currentBrowsingRemote: null,
setKeyListeners: false,
debuggerDiv: null
};
/**
* Install some basic low-level event listeners to monitor how
* the primary wii_remote is interacting with the browser; it's treated
* differently than the other wii_remotes, more as a "browsing" tool than
* a controller. Doesn't mean we can't try and mend the gap...
*/
Wii.installKeyListeners = function() {
document.addEventListener('mouseup', Wii.parsePrimaryWiimote, false);
document.addEventListener('keydown', Wii.parsePrimaryWiimote, false);
document.addEventListener('mousedown', Wii.parsePrimaryWiimote, false);
document.addEventListener('keyup', Wii.parsePrimaryWiimote, false);
/**
* Some keys, like the directional ones, get... multiple events?
* In this case, just shut. down. everything.
*
* ...and let the programmer deal with it.
*/
document.addEventListener('keypress', Wii.parsePrimaryWiimote, false);
return true;
};
/**
* Wii.listen()
*
* The main game loop. This must stay very performant; try to keep things as absolutely
* low-level as possible here.
*/
Wii.listen = function() {
if(!Wii.setKeyListeners) Wii.setKeyListeners = Wii.installKeyListeners();
var i = Wii.extraRemotes.length;
while(i--) {
/* Check if it's enabled; returns a kPadStatus object if so. */
var wii_remote = Wii.extraRemotes[i],
wii_remoteCurrStatus = wii_remote.isEnabled();
/* If it's enabled, huzzah, do some checks and fire appropriate events. */
if(wii_remoteCurrStatus) {
/**
* Do this check here as well, as the primary wiimote might've changed...
* Note that we don't remove it from the internal remote tracking Array; this is because
* if the remote that _was_ the primary one comes back online, it'll take over as the
* primary one again as it's the lowest ID in terms of all remotes. This check here will
* ensure that whatever remote is the current primary one will default to using other
* dispatched events instead of bitwise checks, but should all default back if another one
* comes online.
*/
if(wii_remoteCurrStatus.isBrowsing()) {
Wii.currentBrowsingRemote = wii_remote;
} else {
for(var evt in wii_remote.evtsInterestedIn) {
var evtHappened = Wii.DISPATCHER[evt](wii_remote, wii_remoteCurrStatus);
if(evtHappened) wii_remote.evtsInterestedIn[evt](wii_remote, wii_remoteCurrStatus);
}
}
}
}
/**
* This is a better choice than working with intervals; it keeps the amount of "spasm" responses
* that one would normally get on the Wii to a bare minimum. This is due to how the two types of
* timers in JS work - intervals will queue up no matter what, and if there's a backlog, rapidly
* fire through all of them. Timeouts are guaranteed to always have their delay, even though at points
* it may end up being more (or less) than 100ms.
*
* Note that this is set to 100ms - any lower, and the Wii becomes very unresponsive for some reason. The
* web browser is... odd, not sure what the deal is. 100ms should be enough for most cases though...
*/
return setTimeout(Wii.listen, 100);
};
/**
* Wii.parsePrimaryWiimote(e)
*
* The Wii browser environment is... quite oddball at times. You see, the
* primary Wii remote is treated differently than the other Wiimotes; it uses
* browser-based events (keydown, mouseup, etc) to communicate which buttons have
* been pressed.
*
* The "primary" Wiimote can also change at any given time (loss of battery in the main, etc).
*
* Luckily, it's not -impossible- to catch this internally. Our main library event loop catches
* if a given Wiimote is marked as the primary one, and will not attempt bitwise operations on it,
* merely wait for standard DOM events to trickle up and handle firing them appropriately.
*
* ...ugh.
*
* This method is a callback for any DOM-event listeners; accepts an event as its argument.
*/
Wii.parsePrimaryWiimote = function(e) {
/* Cancel whatever the default was, because we're going to try and normalize everything. */
e.preventDefault();
var wii_remote = Wii.currentBrowsingRemote,
wii_remoteCurrStatus = wii_remote.isEnabled(),
buttonPressed = Wii.PRIMARY_CONTROLLER_DISPATCHER[wii_remote.opts.horizontal ? 'horizontal' : 'vertical'][e.keyCode];
/**
* Filter down and figure out which "event" we're really looking at based on code
* matchups; this gets messy pretty quickly...
*/
if(typeof buttonPressed !== 'undefined' && wii_remote.evtsInterestedIn[buttonPressed] !== 'undefined') {
wii_remote.evtsInterestedIn[buttonPressed](wii_remote, wii_remoteCurrStatus);
}
/* Doing this in conjunction with preventDefault() halts an odd clicking bug or two. */
return false;
};
/**
* Wii.PRIMARY_CONTROLLER_DISPATCHER
*
* In order to keep things as performant as possible, we want DOM events (for the primary controller)
* to also be a 1:1 hash map lookup. This is PRIMARILY for the primary ("browsing") controller; all other
* controllers get their operations routed through the DISPATCHER below.
*/
Wii.PRIMARY_CONTROLLER_DISPATCHER = {
vertical: {
0: 'pressed_a',
13: 'pressed_a', /* Older versions of the Wii Browser...? */
170: 'pressed_minus',
171: 'pressed_b',
172: 'pressed_1',
173: 'pressed_2',
174: 'pressed_plus',
175: 'pressed_up',
176: 'pressed_down',
177: 'pressed_right',
178: 'pressed_left'
},
horizontal: {
0: 'pressed_a',
13: 'pressed_a', /* Older versions of the Wii Browser...? */
170: 'pressed_minus',
171: 'pressed_b',
172: 'pressed_1',
173: 'pressed_2',
174: 'pressed_plus',
175: 'pressed_left',
176: 'pressed_right',
177: 'pressed_up',
178: 'pressed_down'
}
};
/**
* Wii.DISPATCHER
*
* A table of the supported events that we watch for in our game loop, then fire off for respective
* Wiimotes. Each index is a function that does a check and returns true or false.
*
* Many of these functions use bitwise comparisons. Read up on it if you're not familiar. Note that
* we also take into account the orientation of the device here!
*/
Wii.DISPATCHER = {
/**
* These functions depend on whether or not the controller is meant to be in horizontal mode
* or not. Quite... different.
*/
'pressed_up': function(wii_remote, wii_remoteStatus) {
if(wii_remote.opts.horizontal) return wii_remoteStatus.hold & 2;
return wii_remoteStatus.hold & 8;
},
'pressed_right': function(wii_remote, wii_remoteStatus) {
if(wii_remote.opts.horizontal) return wii_remoteStatus.hold & 4;
return wii_remoteStatus.hold & 2;
},
'pressed_down': function(wii_remote, wii_remoteStatus) {
if(wii_remote.opts.horizontal) return wii_remoteStatus.hold & 1;
return wii_remoteStatus.hold & 4;
},
'pressed_left': function(wii_remote, wii_remoteStatus) {
if(wii_remote.opts.horizontal) return wii_remoteStatus.hold & 8;
return wii_remoteStatus.hold & 1;
},
'pressed_plus': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 16; },
'pressed_minus': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 4096; },
'pressed_2': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 256; },
'pressed_1': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 512; },
'pressed_b': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 1024; },
'pressed_a': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 2048; },
/**
* I'm keeping these noted here for legacy reasons, but by and large it's just not even
* worth trying to use the Nunchuk with anything in the browser; the primary controller
* can never read them, so there's a large chunk of functionality missing...
*/
'pressed_z': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 8192; },
'pressed_c': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 16384; }
};

437
js/wii.js Normal file
View file

@ -0,0 +1,437 @@
/**
* core.js
*
* This is the main template file for bringing together
* the various libraries that power this entire little eco-system.
*
* Building the releases requires Python (2.5+); simply run...
*
* python build.py
*
* ...from the /js/ directory.
*
* @Author: Ryan McGrath <ryan@venodesigns.net>
* @Requires: Nothing, top-level file.
*/
;(function(running_inside_wii_browser) {
/**
* If we're not running inside the Nintendo Wii browser, bail out.
* In the future, branch here for touch-enabled devices...?
*/
if(!running_inside_wii_browser) return false;
/**
* wii.js
*
* Provides a sane, event-based documented wrapper around the Wiimote controls
* and API inside the Opera-based Nintendo Wii web browser.
*
* This is not produced by nor endorsed by Nintendo or Opera. I've written it
* on my own because I see a device that's sitting in millions of living rooms
* but being sorely neglected because a company couldn't get their act together
* in regards to third party development. ;)
*
* @Author: Ryan McGrath <ryan@venodesigns.net>
* @Requires: Nothing.
*/
/* Wii
*
* Top level namespace. Contains information about the main event loop, etc.
*/
var Wii = {
/**
* A "global" reference to the Wiimotes we're currently watching. Lets us run
* through on each loop/iteration and poll for a new status on it.
*/
extraRemotes: [],
currentBrowsingRemote: null,
setKeyListeners: false,
debuggerDiv: null
};
/**
* Install some basic low-level event listeners to monitor how
* the primary wii_remote is interacting with the browser; it's treated
* differently than the other wii_remotes, more as a "browsing" tool than
* a controller. Doesn't mean we can't try and mend the gap...
*/
Wii.installKeyListeners = function() {
document.addEventListener('mouseup', Wii.parsePrimaryWiimote, false);
document.addEventListener('keydown', Wii.parsePrimaryWiimote, false);
document.addEventListener('mousedown', Wii.parsePrimaryWiimote, false);
document.addEventListener('keyup', Wii.parsePrimaryWiimote, false);
/**
* Some keys, like the directional ones, get... multiple events?
* In this case, just shut. down. everything.
*
* ...and let the programmer deal with it.
*/
document.addEventListener('keypress', Wii.parsePrimaryWiimote, false);
return true;
};
/**
* Wii.listen()
*
* The main game loop. This must stay very performant; try to keep things as absolutely
* low-level as possible here.
*/
Wii.listen = function() {
if(!Wii.setKeyListeners) Wii.setKeyListeners = Wii.installKeyListeners();
var i = Wii.extraRemotes.length;
while(i--) {
/* Check if it's enabled; returns a kPadStatus object if so. */
var wii_remote = Wii.extraRemotes[i],
wii_remoteCurrStatus = wii_remote.isEnabled();
/* If it's enabled, huzzah, do some checks and fire appropriate events. */
if(wii_remoteCurrStatus) {
/**
* Do this check here as well, as the primary wiimote might've changed...
* Note that we don't remove it from the internal remote tracking Array; this is because
* if the remote that _was_ the primary one comes back online, it'll take over as the
* primary one again as it's the lowest ID in terms of all remotes. This check here will
* ensure that whatever remote is the current primary one will default to using other
* dispatched events instead of bitwise checks, but should all default back if another one
* comes online.
*/
if(wii_remoteCurrStatus.isBrowsing()) {
Wii.currentBrowsingRemote = wii_remote;
} else {
for(var evt in wii_remote.evtsInterestedIn) {
var evtHappened = Wii.DISPATCHER[evt](wii_remote, wii_remoteCurrStatus);
if(evtHappened) wii_remote.evtsInterestedIn[evt](wii_remote, wii_remoteCurrStatus);
}
}
}
}
/**
* This is a better choice than working with intervals; it keeps the amount of "spasm" responses
* that one would normally get on the Wii to a bare minimum. This is due to how the two types of
* timers in JS work - intervals will queue up no matter what, and if there's a backlog, rapidly
* fire through all of them. Timeouts are guaranteed to always have their delay, even though at points
* it may end up being more (or less) than 100ms.
*
* Note that this is set to 100ms - any lower, and the Wii becomes very unresponsive for some reason. The
* web browser is... odd, not sure what the deal is. 100ms should be enough for most cases though...
*/
return setTimeout(Wii.listen, 100);
};
/**
* Wii.parsePrimaryWiimote(e)
*
* The Wii browser environment is... quite oddball at times. You see, the
* primary Wii remote is treated differently than the other Wiimotes; it uses
* browser-based events (keydown, mouseup, etc) to communicate which buttons have
* been pressed.
*
* The "primary" Wiimote can also change at any given time (loss of battery in the main, etc).
*
* Luckily, it's not -impossible- to catch this internally. Our main library event loop catches
* if a given Wiimote is marked as the primary one, and will not attempt bitwise operations on it,
* merely wait for standard DOM events to trickle up and handle firing them appropriately.
*
* ...ugh.
*
* This method is a callback for any DOM-event listeners; accepts an event as its argument.
*/
Wii.parsePrimaryWiimote = function(e) {
/* Cancel whatever the default was, because we're going to try and normalize everything. */
e.preventDefault();
var wii_remote = Wii.currentBrowsingRemote,
wii_remoteCurrStatus = wii_remote.isEnabled(),
buttonPressed = Wii.PRIMARY_CONTROLLER_DISPATCHER[wii_remote.opts.horizontal ? 'horizontal' : 'vertical'][e.keyCode];
/**
* Filter down and figure out which "event" we're really looking at based on code
* matchups; this gets messy pretty quickly...
*/
if(typeof buttonPressed !== 'undefined' && wii_remote.evtsInterestedIn[buttonPressed] !== 'undefined') {
wii_remote.evtsInterestedIn[buttonPressed](wii_remote, wii_remoteCurrStatus);
}
/* Doing this in conjunction with preventDefault() halts an odd clicking bug or two. */
return false;
};
/**
* Wii.PRIMARY_CONTROLLER_DISPATCHER
*
* In order to keep things as performant as possible, we want DOM events (for the primary controller)
* to also be a 1:1 hash map lookup. This is PRIMARILY for the primary ("browsing") controller; all other
* controllers get their operations routed through the DISPATCHER below.
*/
Wii.PRIMARY_CONTROLLER_DISPATCHER = {
vertical: {
0: 'pressed_a',
13: 'pressed_a', /* Older versions of the Wii Browser...? */
170: 'pressed_minus',
171: 'pressed_b',
172: 'pressed_1',
173: 'pressed_2',
174: 'pressed_plus',
175: 'pressed_up',
176: 'pressed_down',
177: 'pressed_right',
178: 'pressed_left'
},
horizontal: {
0: 'pressed_a',
13: 'pressed_a', /* Older versions of the Wii Browser...? */
170: 'pressed_minus',
171: 'pressed_b',
172: 'pressed_1',
173: 'pressed_2',
174: 'pressed_plus',
175: 'pressed_left',
176: 'pressed_right',
177: 'pressed_up',
178: 'pressed_down'
}
};
/**
* Wii.DISPATCHER
*
* A table of the supported events that we watch for in our game loop, then fire off for respective
* Wiimotes. Each index is a function that does a check and returns true or false.
*
* Many of these functions use bitwise comparisons. Read up on it if you're not familiar. Note that
* we also take into account the orientation of the device here!
*/
Wii.DISPATCHER = {
/**
* These functions depend on whether or not the controller is meant to be in horizontal mode
* or not. Quite... different.
*/
'pressed_up': function(wii_remote, wii_remoteStatus) {
if(wii_remote.opts.horizontal) return wii_remoteStatus.hold & 2;
return wii_remoteStatus.hold & 8;
},
'pressed_right': function(wii_remote, wii_remoteStatus) {
if(wii_remote.opts.horizontal) return wii_remoteStatus.hold & 4;
return wii_remoteStatus.hold & 2;
},
'pressed_down': function(wii_remote, wii_remoteStatus) {
if(wii_remote.opts.horizontal) return wii_remoteStatus.hold & 1;
return wii_remoteStatus.hold & 4;
},
'pressed_left': function(wii_remote, wii_remoteStatus) {
if(wii_remote.opts.horizontal) return wii_remoteStatus.hold & 8;
return wii_remoteStatus.hold & 1;
},
'pressed_plus': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 16; },
'pressed_minus': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 4096; },
'pressed_2': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 256; },
'pressed_1': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 512; },
'pressed_b': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 1024; },
'pressed_a': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 2048; },
/**
* I'm keeping these noted here for legacy reasons, but by and large it's just not even
* worth trying to use the Nunchuk with anything in the browser; the primary controller
* can never read them, so there's a large chunk of functionality missing...
*/
'pressed_z': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 8192; },
'pressed_c': function(wii_remote, wii_remoteStatus) { return wii_remoteStatus.hold & 16384; }
};
/**
* util.js
*
* A basic utility wrapper; anything extra that's often re-used should
* find its way here (e.g, debuggerDiv, bind, etc).
*
* @Author: Ryan McGrath <ryan@venodesigns.net>
* @Requires: wii.js
*/
Wii.util = {
/**
* Wii.util.debug(err);
*
* The Wii has... such little options for debugging, but we can try and make this a bit nicer.
* This accepts a stack trace (see example code below) and then outputs it to the screen.
*
* try { ... } catch(e) { Wii.util.debug(e); }
*/
debug: function(err) {
if(Wii.debuggerDiv === null) {
Wii.debuggerDiv = document.createElement('div');
Wii.debuggerDiv.style.cssText = [
'width: 90%;',
'height: 90%;',
'padding: 10px;',
'font-size: 26px;',
'font-family: monospace;',
'overflow: scroll',
'position: absolute;',
'top: 10px;',
'left: 10px;',
'color: #f9f9f9;',
'background-color: #010101;'
].join('');
document.body.appendChild(Wii.debuggerDiv);
}
if(typeof err === 'string') {
Wii.debuggerDiv.innerHTML = err;
} else {
var msg = '';
for(var e in err) { msg += e + '=' + err[e] + '<br>'; }
Wii.debuggerDiv.innerHTML = msg;
}
Wii.debuggerDiv.style.display = 'block';
},
/**
* Wiimote.util.bind(bindReference, fn)
*
* Takes a reference (an object to scope to "this" at a later runtime) and binds it to a function (fn).
*
* @param bindReference - An object to set as the "this" reference for a later function call.
* @param fn - A function to bind the "this" object for.
* @returns fn - A new function to pass around, wherein it's all scoped as you want it.
*/
bind: function(bindReference, fn) {
return function() {
return fn.apply(bindReference, arguments);
};
}
};
/**
* remote.js
*
* Handles the subscribing to events portion of a Wii remote. It's best to think of this
* as a "request" object; it asks to be notified of events, and the actual events are
* dispatched from the main wii.js file.
*
* @Author: Ryan McGrath <ryan@venodesigns.net>
* @Requires: wii.js, util.js
*/
/**
* var wii_remote = new Wii.Remote(1, {...});
*
* Instantiates a Wii Remote object. Events can be set on each of these objects,
* and the internal game loop will fire events based on the properties subscribed
* to here.
*
* @param remote_id - Number, required. 1 - 4, dictates which Wiimote this object
* relates to.
* @param opts - Object, optional. Allows you to override internal settings and such,
* should you want different behavior.
* @returns Wii.Remote instance.
*/
Wii.Remote = function(remote_id, opts) {
this.remote_id = remote_id;
this.opts = opts;
/**
* If this is the "main" wii_remote, then the bitwise checks will fail
* because it's treated more as a "browsing" device. For these events,
* we'll just store the current wii_remote that's denoted as the "browsing"
* device and let the normal event/key delegation take care of things.
*
* The rest of the wii_remotes will go through the DISPATCHER checks that
* they've subscribed to.
*/
var startupStatus = this.isEnabled();
if(startupStatus) {
if(!startupStatus.isBrowsing) {
Wii.extraRemotes.push(this);
} else {
Wii.currentBrowsingRemote = this;
}
}
};
Wii.Remote.prototype = {
opts: {
/**
* We default the controller to be in the vertical orientation; if
* it's overridden as "horizontal" (false), we'll catch it for the different key
* events and fire accordingly (e.g, the "up" button is different depending on
* how the player is holding the controller).
*/
horizontal: false
},
/**
* A hash of events that this Wii remote is interested in. Each
* entry should be a key (evtName) with a value (response).
* "evtName" is the event name that corresponds with boolean functions
* in the DISPATCHER above, and the response is a remote-bound function
* to call on that event.
*/
evtsInterestedIn: undefined,
/**
* Wiimote.isEnabled()
*
* Determines the status of the wii_remote this object is curious about. Will return
* an updated kPadStatus object if so, false if it's not responding or the data
* is sent back as "invalid". This makes it so we don't bother sending events
* where they're not applicable.
*
* @returns object or boolean.
*/
isEnabled: function() {
var remote = opera.wiiremote.update(this.remote_id - 1);
return (remote.isEnabled && remote.isDataValid ? remote : false);
},
/**
* Wiimote.on(evtName, fn)
*
* Allows you to listen for an event on a given Wiimote. Call this on an instantiated
* Wiimote; "this" will be scoped to the Wiimote object. ;)
*
* @param evtName - String, a supported wii.js (DISPATCHER) event name.
* @param fn - Function, callback function to be executed on the event firing. Will be scoped to the Wiimote.
* @returns object or undefined - instantiated object this was called on to begin with, undefined if evtName is not supported.
*/
when: function(evtName, fn) {
if(typeof Wii.DISPATCHER[evtName] !== 'undefined') {
/**
* THIS IS INCREDIBLY IMPORTANT, DO NOT REMOVE THIS!.
*
* The Wii's browser has an (odd...?) bug wherein if you have a prototype chain
* set up for a given object, and you default keys on the prototype chain to a blank object ("{}", for instance),
* it will _NOT_ make this a unique object, it keeps pointers to the original object that was created by the system
* for the first instantiated object.
*
* This is, needless to say, unlike just about any other JS environment and threw me for a loop for a good 6 hours.
* This line ensures that the first time this property is ever referenced, we get a fresh _CORRECTLY ALLOCATED_ chunk
* of memory to play with and store things in.
*
* Note that this also happens with Array structures, and conceivably anything else that would be using a copy-by-reference
* technique instead of a full clone. We want an object for this case, though, so we're not doing iterations on event dispatch,
* just a 1:1 lookup instead.
*/
if(this.evtsInterestedIn === undefined) this.evtsInterestedIn = {};
this.evtsInterestedIn[evtName] = Wii.util.bind(this, fn);
return this;
}
return undefined;
}
};
window.Wii = Wii;
})(window.opera && opera.wiiremote);

1
js/wii.min.js vendored Normal file
View file

@ -0,0 +1 @@
(function(a){if(!a){return false}var b={extraRemotes:[],currentBrowsingRemote:null,setKeyListeners:false,debuggerDiv:null};b.installKeyListeners=function(){document.addEventListener("mouseup",b.parsePrimaryWiimote,false);document.addEventListener("keydown",b.parsePrimaryWiimote,false);document.addEventListener("mousedown",b.parsePrimaryWiimote,false);document.addEventListener("keyup",b.parsePrimaryWiimote,false);document.addEventListener("keypress",b.parsePrimaryWiimote,false);return true};b.listen=function(){if(!b.setKeyListeners){b.setKeyListeners=b.installKeyListeners()}var e=b.extraRemotes.length;while(e--){var g=b.extraRemotes[e],f=g.isEnabled();if(f){if(f.isBrowsing()){b.currentBrowsingRemote=g}else{for(var c in g.evtsInterestedIn){var d=b.DISPATCHER[c](g,f);if(d){g.evtsInterestedIn[c](g,f)}}}}}return setTimeout(b.listen,100)};b.parsePrimaryWiimote=function(g){g.preventDefault();var f=b.currentBrowsingRemote,d=f.isEnabled(),c=b.PRIMARY_CONTROLLER_DISPATCHER[f.opts.horizontal?"horizontal":"vertical"][g.keyCode];if(typeof c!=="undefined"&&f.evtsInterestedIn[c]!=="undefined"){f.evtsInterestedIn[c](f,d)}return false};b.PRIMARY_CONTROLLER_DISPATCHER={vertical:{0:"pressed_a",13:"pressed_a",170:"pressed_minus",171:"pressed_b",172:"pressed_1",173:"pressed_2",174:"pressed_plus",175:"pressed_up",176:"pressed_down",177:"pressed_right",178:"pressed_left"},horizontal:{0:"pressed_a",13:"pressed_a",170:"pressed_minus",171:"pressed_b",172:"pressed_1",173:"pressed_2",174:"pressed_plus",175:"pressed_left",176:"pressed_right",177:"pressed_up",178:"pressed_down"}};b.DISPATCHER={pressed_up:function(d,c){if(d.opts.horizontal){return c.hold&2}return c.hold&8},pressed_right:function(d,c){if(d.opts.horizontal){return c.hold&4}return c.hold&2},pressed_down:function(d,c){if(d.opts.horizontal){return c.hold&1}return c.hold&4},pressed_left:function(d,c){if(d.opts.horizontal){return c.hold&8}return c.hold&1},pressed_plus:function(d,c){return c.hold&16},pressed_minus:function(d,c){return c.hold&4096},pressed_2:function(d,c){return c.hold&256},pressed_1:function(d,c){return c.hold&512},pressed_b:function(d,c){return c.hold&1024},pressed_a:function(d,c){return c.hold&2048},pressed_z:function(d,c){return c.hold&8192},pressed_c:function(d,c){return c.hold&16384}};b.util={debug:function(c){if(b.debuggerDiv===null){b.debuggerDiv=document.createElement("div");b.debuggerDiv.style.cssText=["width: 90%;","height: 90%;","padding: 10px;","font-size: 26px;","font-family: monospace;","overflow: scroll","position: absolute;","top: 10px;","left: 10px;","color: #f9f9f9;","background-color: #010101;"].join("");document.body.appendChild(b.debuggerDiv)}if(typeof c==="string"){b.debuggerDiv.innerHTML=c}else{var f="";for(var d in c){f+=d+"="+c[d]+"<br>"}b.debuggerDiv.innerHTML=f}b.debuggerDiv.style.display="block"},bind:function(c,d){return function(){return d.apply(c,arguments)}}};b.Remote=function(e,d){this.remote_id=e;this.opts=d;var c=this.isEnabled();if(c){if(!c.isBrowsing){b.extraRemotes.push(this)}else{b.currentBrowsingRemote=this}}};b.Remote.prototype={opts:{horizontal:false},evtsInterestedIn:undefined,isEnabled:function(){var c=opera.wiiremote.update(this.remote_id-1);return(c.isEnabled&&c.isDataValid?c:false)},when:function(d,c){if(typeof b.DISPATCHER[d]!=="undefined"){if(this.evtsInterestedIn===undefined){this.evtsInterestedIn={}}this.evtsInterestedIn[d]=b.util.bind(this,c);return this}return undefined}};window.Wii=b})(window.opera&&opera.wiiremote);

104
readme.md Normal file
View file

@ -0,0 +1,104 @@
wii-js
==============================================================================================
The Nintendo Wii is an entertainment system with an utterly _massive_ install base, and when
you couple it with the fact that it's got a web browser (mostly) built in, there's a lot of
potential for third party development. Sadly, few have opted to do any sort of development for
it. While it doesn't help that Nintendo pretty much dropped the ball on this opportunity, the
experience of browsing the web on the Wii isn't actually that compelling to begin with.
That said, I think this can serve one other purpose: it's an ideal environment to teach children
how to program! I created this library to sanitize Wii interaction with webpages in the browser,
as it's notoriously crippled. It aims to offer a solid, documented, performant API that's easy to
understand and pick up. With this library, you can have up to 4 Wii-motes interacting with your
webpage at once, a dynamic not found in other web browsing mediums.
Questions, comments, criticism and praise can be directed to me at the following outlets:
- You can email me at **ryan [at] venodesigns (dot) net**.
- You can hit me up on Twitter: [@ryanmcgrath](http://twitter.com/ryanmcgrath/)
- Contact me through **[my website](http://venodesigns.net)**
- **Technical issues can be filed on the [wii-js GitHub Issues Tracker](https://github.com/ryanmcgrath/wii-js/issues)**
Example Usage
----------------------------------------------------------------------------------------------
``` javascript
var wiimote = new Wii.Remote(1, {horizontal: true}),
wiimote2 = new Wii.Remote(2, {horizontal: true});
wiimote.when('pressed_a', function() {
alert('Wiimote #1 pressed the A Button!');
});
wiimote2.when('pressed_a', function() {
alert('Wiimote #2 pressed the A Button!');
});
```
Technical Documentation
----------------------------------------------------------------------------------------------
The largest issue with making interactive pages that work with the Wii has been that the API has
been nothing short of a black hole. When you actually begin to dig in and figure out what's going on,
it gets even uglier to see - the primary wiimote, for instance, has a totally different set of signals
than the other three.
wii-js abstracts away most of these differences and/or problems, and works on a very simple event-dispatch
system. What this means is that you create an instance of a Wii Remote, subscribe to events, and provide a
function to get called when that event has occurred. The following syntax should explain this:
``` javascript
wiimote.when('event_name_here', function() { /* My callback function */ });
```
When instantiating a Wii Remote instance, you can choose to have the library interpret directional pad controls
in horizontal or vertical mode. You can change this at any point, if you want, by simply swapping the property.
``` javascript
var wiimote = new Wii.Remote(1, {horizontal: true}); // Horizontal controls
var wiimote = new Wii.Remote(1, {horizontal: false}); // Vertical controls
wiimote.opts.horizontal = true; // Change to horizontal scheme.
```
You can listen for the following events on all controllers:
- **pressed_up** - The up button was pressed.
- **pressed_down** - The down button was pressed.
- **pressed_left** - The left button was pressed.
- **pressed_right** - The right button was pressed.
- **pressed_a** - The A button was pressed.
- **pressed_1** - The 1 button was pressed. (_Note: On controller 1, this triggers a menu - work in progress..._)
- **pressed_2** - The 2 button was pressed.
- **pressed_plus** - The plus (+) button was pressed.
- **pressed_minus** - The minus (-) button was pressed.
- **roll_change** - The roll of the controller (balance) changed. You can get the current roll in radians with _"this.roll"_; positive is upright, negative is the other.
- **distance_change** - The distance of the wiimote (in meters) from the TV/sensor bar has changed. This event isn't totally reliable, but should work for most cases.
You can listen for the following events on _extra controllers_, but not the primary controller.
- **pressed_b** - The B button was pressed.
- **pressed_c** - The C button (on the Nunchuk) was pressed.
- **pressed_z** - The Z button (on the Nunchuk) was pressed.
You can also get the following properties from any Wii Remote instance; they will return "undefined" if the remote
isn't able to see the TV/sensor bar, so be sure to check this!
- **x** - The x coordinate where the Wii Remote is pointing to on the screen. Generally between 0 and 800, but can be more on wide pages.
- **y** - The y coordinate where the Wii Remote is pointing to on the screen. Odd one; can be found as low as -48, as high as the height
of the current webpage + toolbar height, if enabled. Tinker with this one for your purposes.
Why the button limitations?
------------------------------------------------------------------------------------------------------------------
The Nintendo Wii treats the primary controller differently than the other ones, and doesn't report any action
from the Nunchuk, nor does it report when someone has pressed the "B" button on the primary controller (as it's used
for scrolling a page).
The Wii Browser also doesn't report data for Gamecube controllers, the Classic controller, or any other accessories.
It's a work in progress to see what can be done about these, but it's impossible to guarantee anything will come out
of it unless Nintendo and/or Opera can come out with something new.
Licensing, etc
-------------------------------------------------------------------------------------------------------------------
wii-js is released under an MIT license. Just provide credit where need be if you choose to use this, it's taken quite
a bit of my time to decipher the utterly random pieces and intricacies of this Javascript engine. ;)

BIN
utilities/yuicompressor-2.4.2.jar Executable file

Binary file not shown.