Kean Walmsley


  • About the Author
    Kean on Google+

April 2014

Sun Mon Tue Wed Thu Fri Sat
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      







« Upgraded Leap Motion controller | Main | Taking a break »

February 22, 2013

A gamification experiment: Apollonian Obliteration

Gamification is happening all over the place. In case you’ve missed what it’s all about, this short video should help. To get even more background, here’s another from the same source on augmented reality games, which is a follow-on from this video on alternate reality games.

After having managed to get Apollonian Packings brought into the very cool PointCloud Browser, I thought it’d be fun to integrate some of the code from another of the PointCloud Browser sample apps, creating a little augmented reality game.

Here’s the basic idea: each level consists of a packing brought into PointCloud Browser from the Apollonian web-service, and you have twice as many bullets as there are spheres to pop them all (which you do by aiming the cross-hairs and tapping the screen). The good news is that each time you pop a sphere you get two more bullets (an idea copied directly from the original Ball Invasion sample, along with much of its code).

What you end up with is something that is actually pretty fun – and surprisingly therapeutic… it’s a bit like popping 3D bubble-wrap – and is an interesting way to experience the internal structure of a 3D fractal. My 6-year old is now regularly pestering me to let him play it, which I take as a good sign. :-)

Here are some screenshots:

The main menu

First level

Starting to shoot


Second level

Third level

Getting shot away


Fourth level

Seeing the insides

Fifth level


Nicely exposed

Not much left

Sixth level

 

Here’s a video of me playing the game. It was a bit tricky capturing what was being shown on the iPad and playing the game at the same time, but it settles down after about a minute (please don’t give up on it straight away).



The game has a few quirks that I haven’t yet ironed out: some spheres just end up being impossible to shoot, which I’m guessing is due to the (usually invisible) base plane stopping the bullets from reaching them. It should be simple enough to fix, but please don’t get too frustrated if this happens to you.

I’ve also left the game open-ended: the levels just keep on going. The Apollonian web-service will only serve up packings for up to level 10, but I’ve in any case found that my iPad 2 slows down in a big way when it gets to level 5 (with 989 spheres) and is basically unusable at level 6 (with 2,837). It may be that more recent iPad devices manage later levels very well, so I haven’t capped them at all.

I do wonder whether gamifiying design work is an interesting concept or not (I mostly suspect not, but I could well be wrong). Very interestingly two of the entrants of the APPHACK at AU 2012 were focused on the idea of gamifying AutoCAD usage, providing awards, etc. when you use certain commands. Perhaps gamifying augmented reality clash detection in BIM 360 or in-place analysis via Simulation 360 would be fun and compelling (and yes, I’m pulling these examples out of thin air, so there’s really no need to tell if you find them ridiculous).

Anyway, here’s the code. I’ve avoided a separate JavaScript file (which I’d probably have used under normal circumstances for the bulk of this implementation) to make it simpler to post here.

You can load this page directly in the PointCloud Browser by entering the following in the application’s address-bar: http://autode.sk/apparg.

<!DOCTYPE html>

<html>

  <head>

    <title>Apollonian Obliteration</title>

    <meta

      name="viewport"

      content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"/>

    <meta name="viper-init-options" content="manual"/>

 

    <link

      rel="viper-app-icon" type="images/png"

      href="resources/images/appicon2.png"/>

    <link

      rel="stylesheet" href="css/common.css" type="text/css"

      charset="utf-8"/>

    <link

      rel="stylesheet" href="resources/css/ballinvasion.css"

      type="text/css" charset="utf-8"/>

 

    <script

      type="text/javascript"

      src="http://code.jquery.com/jquery-1.7.1.min.js">

    </script>

    <script type="text/javascript" src="js/common.js"></script>

 

    <script type="text/xml" id="library">

      <library>

        <sound

          id="fire" pitch="1.0" volume="0.8"

          src="resources/sounds/fire.mp3"/>

        <sound

          id="explosion" prototype="fire"

          src="resources/sounds/explosion.mp3"/>

        <sound

          id="bounce" pitch="1.0" volume="0.5"

          src="resources/sounds/bounce.mp3"/>

        <sound

          id="reset" pitch="1.0" volume="0.5"

          src="resources/sounds/reset.mp3"/>

 

        <mesh

          id="cameracube_mesh" primitive="cube" width="0.05"

          height="0.05" len="0.05"

          texture_src="resources/images/explosion.png"/>

 

        <mesh

          id="bullet_mesh" primitive="sphere" radius="0.01"

          details="2" color="0.3,0.3,0.3,1.0"/>

 

        <mesh

          id="base_plane_mesh" primitive="plane" width="1.0"

          height="1.0"

          texture_src="resources/images/init_texture.png"/>

 

        <mesh

          id="sphere_mesh" radius="0.1" primitive="sphere"

          details="2" />

 

        <node

          id="sphere_node" static="true"

          notify_collisions_with_classes="bullet"

          bounce_factor="0" friction_factor="0.99">

          <model

            texture_src="resources/images/white-4x4.png"

            id="sphere_model" mesh="sphere_mesh"/>

        </node>

 

        <node

          id="bullet" class="bullet" mass="1.0" bounce_factor="0.3"

          friction_factor="0.90">

          <model mesh="bullet_mesh"/>

          <animation

            class="fadeout" duration="1.5s" delay="1.5s"

            autostart="true">

            <property name="alpha" from="1.0" to="0.0"

              function="linear"/>

          </animation>

        </node>

 

        <node id="explosion_node" scale="0.1" alpha="1.0">

          <model

            id="explosion_model" blending="luminous"

            texture_src="resources/images/explosion.png"/>

          <animation

            class="explosion" duration="0.4" autostart="true">

            <property

              name="scale" from="0.1" to="2.0" function="ease-out"/>

            <property

              name="alpha" from="1" to="0" function="ease-out"/>

          </animation>

        </node>

      </library>

    </script>

 

    <script type="text/xml" id="scene">

      <scene

        base="relative-center" gravity_constant="0.5"

        notify_collisions_with_classes="bullet">

        <light id="main_light"

          intensity="1.0"

          fade="constant"

          ambient="0.2, 0.2, 0.2, 0.2"

          diffuse="1.0, 1.0, 1.0, 1.0"

          specular="1.0, 1.2, 1.2, 1.0"

          position="3, 0.5, 2, 0"/>

 

        <node id="base_plane" position="0,0,0" alpha="0.0">

          <model mesh="base_plane_mesh" blending="no_shadows"/>

          <animation

            class="color_animation" id="show" duration="0.3">

            <property

              name="alpha" from="0.0" to="1.0" function="ease-out"/>

          </animation>

          <animation

            class="color_animation" id="hide" duration="0.3">

            <property

              name="alpha" from="1.0" to="0.0" function="ease-out"/>

          </animation>

        </node>

 

      </scene>

    </script>

 

    <script type="text/javascript">

 

      var appobliteration = {

        STATE_MENU : 0,

        STATE_GAME_STARTING : 1,

        STATE_GAME_ONGOING : 2,

        state : -1,

 

        init : function() {

          appobliteration.game.init();

          appobliteration.menu.init();

        },

 

        onStartGameRequested : function(course) {

          if (!viper.isCameraEnabled()) {

            viper.setCameraEnabled();

          }

 

          // Hide menu regardless...

 

          appobliteration.menu.fadeOut();

 

          // ...then determine whether we need to initialize a map,

          // or whether we can start the game right away

 

          if (viper.hasTracking()) {

            appobliteration.startGame();

          }

          else {

            appobliteration.state =

              appobliteration.STATE_GAME_STARTING;

            var resetExistingMap = false;

            viper.requireRealityMap(resetExistingMap);

          }

        },

 

        startGame : function() {

          appobliteration.state =

            appobliteration.STATE_GAME_ONGOING;

          appobliteration.game.resetGame();

          appobliteration.game.fadeIn(); 

        },

 

        endGame : function() {

          appobliteration.state = appobliteration.STATE_MENU;

          appobliteration.showMenu();

        },

 

        showMenu : function() {

          appobliteration.game.fadeOut();

          appobliteration.menu.fadeIn();

        },

 

        onMapCreated : function() {

          if (

            appobliteration.state ==

              appobliteration.STATE_GAME_STARTING

          ) {

            // Start the game when map has been created

            // (called as a result of viper.requireRealityMap();

            // above)

 

            appobliteration.startGame();

          }

          else if (

            appobliteration.state == appobliteration.STATE_MENU

          ) {

            // May happen if we load a map from the browser UI

            // while in the game menu

 

            appobliteration.menu.fadeIn();

            appobliteration.game.fadeIn(true); 

          }

          else {

            // Do nothing if we're already playing a game

          }

        },

      }

 

      appobliteration.menu = {

 

        menuView : null,

 

        init : function() {

          this.menuView = $("#menu");

          this.menuView.bind("webkitTransitionEnd", function() {

            if ($(this).css("opacity") == 0.0) {

              $(this).css("display", "none");

            }

          }); 

        },

 

        fadeIn : function() {

          this.menuView.css("display", "block");

          this.menuView.css("opacity", 1.0);

        },

 

        fadeOut : function() {

          this.menuView.css("opacity", 0.0);

        },

 

        pushView : function(view) {

          $("#menutitle").html(view.attr('title'));

          $("#content").html(view.html());

 

          var $panelbuttons = $("#content").find(".panelbutton");

          appobliteration.ui.prepareButtons(

            $panelbuttons,

            appobliteration.menu.onMenuButtonClick

          );

        },

 

        onMenuButtonClick : function($elem) {

          viper.log("on menu button click: " + $elem.attr('id'));

          if ($elem.attr('id') == 'play') {

            appobliteration.onStartGameRequested();

          }

          else if ($elem.attr('id') == 'action') {

            viper.goHome();

          }

          else if ($elem.attr('id') == 'more') {

            viper.launchSystemURI("http://autode.sk/ttif");

          }

        },

      }

 

      appobliteration.ui = {

 

        sightSource : "",

 

        showSplash : function() {

          var sight = $("#sight")[0];

          appobliteration.ui.sightSource =

            sight.style.backgroundImage;

          sight.style.backgroundImage =

            "url(resources/images/splash.png)";

        },

 

        hideSplash : function() {

          $("#sight")[0].style.backgroundImage =

            appobliteration.ui.sightSource;

        },

 

        prepareButtons : function($elem, fnc) {

          viper.log("preparing button");

          var maxMove = 16;

 

          $elem.unbind();

          $elem.bind("touchstart", function(e) {

            viper.log("touched button");

            var touch =

              e.originalEvent.touches[0] ||

              e.originalEvent.changedTouches[0];

            $(this).data(

              'touchdown',

              {x: touch.pageX, y: touch.pageY}

            );

          });

 

          $elem.bind("touchend", function(e) {

            var touch =

              e.originalEvent.touches[0] ||

              e.originalEvent.changedTouches[0];

            var touchDown = $(this).data('touchdown');

            var maxMoveSq =

              Math.pow((touchDown.x - touch.pageX),2) +

              Math.pow((touchDown.y - touch.pageY), 2);

 

            if (maxMoveSq < Math.pow(maxMove,2)) {

              viper.log("executing function");

              fnc($(this));

            }

          });

        },

      }

 

      appobliteration.game = {

 

        score : 0,

        level : 1,

        spheres : [],

        bulletCount : 0,

        sphereCount : 0,

        roundsFired : 0,

        cameraTransform : 0,

        isRunning : false,

 

        init : function() {

          this.gameView = $("#game");

          this.gameView.bind("webkitTransitionEnd",

            function() {

              if ($(this).css("opacity") == 0.0) {

                $(this).css("display", "none");

              }

            }

          );

 

          this.gameNode = viper.getScene();

 

          this.spheresForLevel(this.level);

 

          var $buttons = $("#game").find(".clickable");

 

          appobliteration.ui.prepareButtons(

            $buttons, appobliteration.game.onGameButtonClick

          );

        },

 

        onGameButtonClick : function($elem) {

          if ($elem.attr('id') == 'recharge') {

            appobliteration.game.populateWithSpheres(

              appobliteration.game.spheres

            );

            appobliteration.game.updateDashboard();

 

            viper.find("#reset").play();

          }

          else if ($elem.attr('id') == 'toggle_baseplane') {

            viper.log('toggle baseplane');

            appobliteration.game.toggleBasePlane($elem);

          }

          else if ($elem.attr('id') == 'toggle_points') {

            appobliteration.game.togglePoints($elem);

          }

        },

 

        togglePoints : function($elem) {

          if (!$elem.data('on')) {

            $elem.data('on', true);

            $elem.addClass('selected');

            viper.showTrackedPoints(true);

          }

          else {

            $elem.data('on', false);

            $elem.removeClass('selected');

            viper.showTrackedPoints(false);

          }

        },

 

        toggleBasePlane : function($elem) {

          if (!$elem.data('on')) {

            $elem.data('on', true);

            $elem.addClass('selected');

            var anim = viper.find("#base_plane").find("#show");

 

            anim.start();

          }

          else {

            $elem.data('on', false);

            $elem.removeClass('selected');

            var anim = viper.find("#base_plane").find("#hide");

            anim.start();

          }

        },

 

        populateWithSpheres : function(spheres) {

 

          viper.log(

            "Populate with spheres: " + spheres.length

          );

 

          // Hard-code the colour for each level in an array

 

          var colors =

            [ "0,0,0,1", "1,0,0,1", "1,1,0,1", "0,1,0,1",

              "0,1,1,1", "0,0,1,1", "1,0,1,1", "0.9,0.9,0.9,1",

              "0.6,0.6,0.6,1", "0.3,0.3,0.3,1", "1,1,1,1",

              "1,1,1,1" ]

 

          // Process each sphere, adding it to the scene

 

          $.each(

            spheres,

            function (i, item) {

 

              // Get shortcuts to our JSON data

 

              //viper.log("Processing item " + i + ": " + item);

 

              var x = item.X, y = item.Y, z = item.Z,

                  rad = item.R, level = item.L;

 

              // Create a spherical node

 

              var nodeID = "sphere_" + i;

              var position = new viper.math.Vector(x, y + 1, z);

              var sphere = new viper.Node(nodeID, position);

              sphere.setPrototype("sphere_node");

              sphere.setScale(rad * 10);

              sphere.setTint(colors[parseInt(level)]);

 

              appobliteration.game.gameNode.addChild(sphere);

            }

          );

 

          appobliteration.game.sphereCount = spheres.length;

          appobliteration.game.bulletCount = spheres.length * 2;

 

          appobliteration.game.updateDashboard();

        },

 

        spheresForLevel : function(level) {

 

          viper.log("Spheres for level: " + level);

 

          appobliteration.ui.showSplash();

 

          // Make sure CORS is enabled

 

          jQuery.support.cors = true;

 

          // Call our web-service with the appropriate level

 

          $.ajax({

            url:

              'http://apollonian.cloudapp.net/api/spheres/0.3/' +

              level,

            crossDomain: true,

            data: {},

            dataType: "json"

          }).done(function(results) {

 

            viper.log("Successfully called web service.");

 

            appobliteration.game.spheres = results;

            appobliteration.game.populateWithSpheres(results);

 

            appobliteration.ui.hideSplash();

          });

        },

 

        resetGame : function() {

          viper.find("#reset").play();

          appobliteration.game.start();

        },

 

        createBullet : function(position, referenceNode) {

          try {

            var nodeID = "bullet_" + this.roundsFired;

            var bulletNode = new viper.Node(nodeID);

 

            // Get all properties from the prototype

 

            bulletNode.setPrototype("bullet");

            bulletNode.setPosition(position, referenceNode);

 

            return bulletNode;

          }

          catch (ex) {

            alert(ex);

          }

        },

 

        fire : function() {   

          if (appobliteration.game.bulletCount <= 0) { return; }

 

          var camera = viper.getCamera();

          var bullet =

            this.createBullet(

              new viper.math.Vector(0,(0.2+0.01),0), camera

            );

 

          appobliteration.game.gameNode.addChild(bullet);

 

          try {

            var forceVector = null;

 

            if (appobliteration.deviceAngle == 0) {

              forceVector =

                viper.Math.unit(new viper.math.Vector(0, 1.0, 0.2));

            }

            else if (appobliteration.deviceAngle == 90) {

              forceVector =

                viper.Math.unit(new viper.math.Vector(0.2, 1.0, 0));

            }

            else if (appobliteration.deviceAngle == 180) {

              forceVector =

                viper.Math.unit(new viper.math.Vector(0, 1.0, -0.2));

            }

            else if (appobliteration.deviceAngle == 270) {

              forceVector =

                viper.Math.unit(new viper.math.Vector(-0.2, 1.0, 0));

            }

 

            var innerPosition = viper.math.Vector(0,0,0);

            bullet.push(1.1, forceVector, innerPosition, camera);

            viper.log(

              "Fired bullet in (local) direction " +

              forceVector.toArray()

            );

 

            appobliteration.game.bulletCount--;

            appobliteration.game.updateDashboard();

          }

          catch (ex) {

            alert(ex);

          }

 

          viper.find("#fire").play();

 

          this.roundsFired++;

        },

 

        updateDashboard : function() {

          try {

            $("#disp_current_score").html(

              appobliteration.game.score

            );

            $("#disp_level").html(appobliteration.game.level);

            $("#disp_bulletcount").html(

              appobliteration.game.bulletCount

            );

            $("#disp_spherecount").html(

              appobliteration.game.sphereCount

            );

          }

          catch (ex) {

            alert("error updating dash: " + ex);

          }

        },

 

        onBounce : function(event) {

          var bullet = event.element;

          var bounceCount = bullet.getMeta('bounce_count', 0);

          bounceCount++;

          bullet.setMeta('bounce_count', bounceCount);

 

          var pitch = 1.0;

          var volume = 1 / (bounceCount*2);

 

          viper.find("#bounce").play(pitch, volume);

        },

 

        onCollision : function(event1, event2) {

          try {

            // Determine which event belongs to which class

            // (sphere vs bullet)

 

            var bullet_collision = null;

            var sphere_collision = null;

 

            if (event1.element.getClass() == "bullet") {

              bullet_collision = event1;

              sphere_collision = event2;

            }

            else if (event2.element.getClass() == "bullet") {

              sphere_collision = event1;

              bullet_collision = event2;

            }

            else {

              return;

            }

 

            // Bullet explosion

 

            var bullet_explosion =

              new viper.Node(

                bullet_collision.element.id + "_explosion",

                bullet_collision.position

              );

            bullet_explosion.setPrototype("explosion_node");

            bullet_explosion.find("#explosion_model").

              setMesh("bullet_mesh"); // Replace the mesh

 

            // Sphere explosion

 

            var sphere_explosion =

              new viper.Node(

                sphere_collision.element.id + "_explosion",

                sphere_collision.position

              );

            sphere_explosion.setPrototype("explosion_node");

            sphere_explosion.find("#explosion_model").

              setMesh("sphere_mesh"); // Replace the mesh

 

            viper.log(

              'Collision position: ' +

              sphere_collision.position.toArray()

            );

 

            viper.find("#explosion").play();

 

            // Remove the colliding objects

 

            bullet_collision.element.remove();

 

            var sphereSuperNode =

              sphere_collision.element.getOwnerNode();

            if (

              sphereSuperNode &&

              sphereSuperNode.getClass() == "supernode"

            ) {

              // Remove the sphere superclass, if any

 

              viper.log("Removing sphere supernode");

              sphereSuperNode.remove();

            }

            else {

              // Remove the sphere directly

 

              viper.log("Removing sphere directly");

              sphere_collision.element.remove();

            }

 

            // Add the explosion objects

 

            viper.getScene().addChild(sphere_explosion);

            viper.getScene().addChild(bullet_explosion);

 

            // Update numbers

 

            appobliteration.game.score += 200;     // A good score

            appobliteration.game.sphereCount--;    // 1 less sphere

            appobliteration.game.bulletCount += 2; // 2 for each hit

 

            // Update dashboard

 

            appobliteration.game.updateDashboard();

 

            if (appobliteration.game.sphereCount <= 0)

            {

              appobliteration.game.spheresForLevel(

                ++appobliteration.game.level

              );

            }

          }

          catch (ex) {

            viper.log("Error: " + ex);

          }

        },

 

        onAnimationFinished : function(animationElement) {

          var animClass = animationElement.getClass();

          if (

            animClass != "color_animation" && animClass != null

          ) {

            var node = animationElement.getOwnerNode();

            if (node) {

              node.remove();

            }

          }

        },

 

        fadeIn : function() {

          var $fireFrame = $("#fireframe");

          $fireFrame.css("display", "block").bind(

            "touchstart",

            function() {

              if (

                appobliteration.state ==

                  appobliteration.STATE_GAME_ONGOING

              ) {

                appobliteration.game.fire();

              }

            }

          );

 

          this.gameView.css("opacity", 1.0);

        },

 

        fadeOut : function() {

          this.gameView.css("opacity", 0.0);

          $("#fireframe").unbind().css("display", "none");

        },

 

        pause : function() {

          this.isRunning = false;

        },

 

        start : function() {

          this.isRunning = true;

        }

      }

 

      function onAppLoaded() {

      }

 

      function onViperReady() {

 

        viper.setLoggingEnabled(false); // Logging is slow in IOS

        viper.setBrowserBounce(false);  // No browser window bounce

        viper.resetRealityMap();        // Reset any existing map

        viper.showTrackedPoints(false); // Default is points off

 

        appobliteration.init();

 

        var observer = {

          onTrackingLost: function () {

          },

 

          onTrackingFound: function () {

            viper.find("#base_plane").setPosition(

              new viper.math.Vector(0, 0, 0), viper.getBasePlane()

            );

          },

 

          onMapCreated: function () {

            viper.find("#base_plane").setPosition(

              new viper.math.Vector(0, 0, 0), viper.getBasePlane()

            );

            appobliteration.onMapCreated();

          },

 

          onMapCreationCancelled: function () {

            appobliteration.showMenu();

          },

 

          onCollision: function (element1, element2) {

            appobliteration.game.onCollision(element1, element2);

          },

 

          onBounce: function (element1) {

            appobliteration.game.onBounce(element1);

          },

 

          onAnimationFinished: function (animationElement) {

            appobliteration.game.onAnimationFinished(

              animationElement

            );

          },

 

          onDeviceOrientationChanged: function (angle) {

            appobliteration.deviceAngle = angle;

            var $screen = $("#screen");

            var classname = "screen_orientation_" + angle;

            $screen.removeClass().addClass(classname);

          },

 

          handleTouchStart: function (x, y) {

            viper.log("GAME: Touch start at " + x + ", " + y);

            return false;

          },

 

          handleTouchMove: function (x, y, dX, dY) {

            return false;

          },

 

          handleTouchEnd: function (x, y) {

            return false;

          }

        }

 

        // Attach the observer to viper

 

        viper.setObserver(observer);

 

        appobliteration.menu.pushView($("#mainmenu"));

        appobliteration.menu.fadeIn();

      }

    </script>

  </head>

  <body>

    <div id="screen">

      <div id="menu">

        <div id="header">

          <div class="inner"><div class="logo"></div></div>

        </div>

        <div class="headershade"></div>       

        <div class="menupanel">

          <div class="stripes">

            <div class="header">

              <div id="menutitle">Title</div>

            </div>

            <div class="content_outer">

              <div class="headershade"></div>

              <div id="content"></div>

              <div class="footershade"></div>

            </div>

            <div class="footer"></div>

          </div>

        </div>

        <div id="mainmenu" class="menuview" title="Main Menu">

          <div class="mainmenu_content">

            <div id="play" class="panelbutton">

              <div class="icon"></div>

              <div class="title">Play the Game</div>

              <div class="subtext">

              Blast some Apollonian Packings

              </div>

              <div style="clear:both;"></div>

            </div>

            <div id="action" class="panelbutton">

              <div class="icon"></div>

              <div class="title">Go Home and Relax</div>

              <div class="subtext">

              Go back to the home page

              </div>

              <div style="clear:both;"></div>

            </div>

            <div id="more" class="panelbutton">

              <div class="icon"></div>

              <div class="title">Visit Kean's blog</div>

              <div class="subtext">

              Find out more about all this

              </div>

              <div style="clear:both;"></div>

            </div>

          </div>

        </div>

      </div>     

      <div id="game">

        <div id="fireframe"></div>

        <div id="header">

          <div class="inner"><div class="logo"></div></div>

        </div> 

        <div id="dashboard">

          <div class="inner">

            <div class="score_column">

              <div class="caption">Current Score</div>

              <div id="disp_current_score">0</div>

            </div>

            <div class="score_column">

              <div class="caption">Level</div>

              <div id="disp_level">0</div>

            </div>

            <div class="score_column">

              <div class="caption">Bullets</div>

              <div id="disp_bulletcount">0</div>

            </div>

            <div class="score_column">

              <div class="caption">Enemies</div>

              <div id="disp_spherecount">0</div>

            </div>

            <div style="clear:both;"></div>

          </div>

        </div>

        <div class="headershade"></div>       

        <div id="sight"></div>

        <div id="toggle_buttons">

          <div id="toggle_baseplane" class="clickable"></div>

          <div id="toggle_points" class="clickable"></div>

        </div>

        <div id="recharge" class="button clickable">Reset</div>

      </div>     

      <div id="log"></div>

    </div>

  </body>

</html>

blog comments powered by Disqus

10 Random Posts