DAN ZEN EXPO - CODE EXHIBIT -
NANORA
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Nanora - Sneaking Puzzle Game in Nano Land</title>
<link rel="shortcut icon" type="image/ico" href="favicon.ico" />
<!-- for Google -->
<meta name="description" content="Nanora - Sneak Game Puzzle to get Meta Monks past the Nano Bots to the Lens of the Lost - with original Moog backing" />
<meta name="keywords" content="Nanora, nano, nanobots, game, puzzle, sneak, artificial intelligence" />
<meta name="author" content="Dan Zen" />
<meta name="copyright" content="Dan Zen" />
<!-- for Facebook -->
<meta property="og:title" content="Nanora - Sneaking Puzzle Game in Nano Land" />
<meta property="og:type" content="website" />
<meta property="og:image" content="http://danzen.com/nanora/og.jpg" />
<meta property="og:url" content="http://danzen.com/nanora/index.html" />
<meta property="og:description" content="Nanora - Sneak Game Puzzle to get Meta Monks past the Nano Bots to the Lens of the Lost - with original Moog backing. You try!" />
<!-- for Twitter -->
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="Nanora - Sneaking Puzzle Game in Nano Land" />
<meta name="twitter:description" content="Nanora - Sneak Game Puzzle to get Meta Monks past the Nano Bots to the Lens of the Lost - with original Moog backing. You try!" />
<meta name="twitter:image" content="http://danzen.com/nanora/og.jpg" />
<!-- for Apple -->
<meta name="viewport" content="width=device-width; initial-scale=.5; target-densitydpi=device-dpi; initial-scale=1; maximum-scale=1; minimal-ui" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<link rel="apple-touch-icon-precomposed" href="http://danzen.com/nanora/apple-icon.png" />
<script>var zon = true; // true for comments from zim code</script>
<script src="../code/live/zim_1.4.3.js"></script><!-- take off _min to see code -->
<script src="http://code.createjs.com/createjs-2014.12.12.min.js"></script>
<!-- or can go to zimjs.com and createjs.com for individual modules -->
<style>
body {margin:0px; padding:0px; background-color:#333;}
#myCanvas {position:absolute; background-color:#333;}
@font-face {
font-family: hoverage;
src: url(hoverage.ttf);
}
@font-face {
font-family: hoveragevintage;
src: url(hoveragevintage.ttf);
}
</style>
<script>
// SCALING OPTIONS
// "none" sets canvas and stage to dimensions and does not scale if window changes
// "fit" sets canvas and stage to dimensions and scales to fit inside window size
// "outside" sets canvas and stage to dimensions and scales to fit outside window size
// "full" sets canvas and stage to window size (canvas is actually set to screen size)
var scaling = "fit"; // full automatically sets width and height to window size
var width = 960;
var height = 640;
var frame = new zim.Frame(scaling, width, height);
frame.on("ready", function() {
zog("ready from ZIM Frame - Load");
var stage = frame.stage;
var stageW = frame.width;
var stageH = frame.height;
// PRELOADING
// handle asset loading and call main app function when complete
// time to bring in the pictures and sound with preloadjs
// could just type out all the content in the form:
// {src:"some.mp3", id:"someid"}
// but we can factor the data to remove duplication
var prefix = "nanora";
var sounds = ["across", "back", "backing1", "backing2", "cycle",
"logo", "new", "quiet", "win", "wrong", "move"];
var manifest = [];
for (var i=0; i<sounds.length; i++) {
manifest.push({src:prefix+sounds[i]+".mp3", id:sounds[i]});
}
var contentPath = "http://danzen.com/nanora/content/";
var preload = new createjs.LoadQueue(false, contentPath); // use true if on the same server
preload.installPlugin(createjs.Sound); // sound requires this plugin call (comment out if no sound)
preload.on("progress", animateProgress); // this will update the progress (optional)
preload.on("complete", nanora); // call the main code when ready
// MAKE PROGRESS / PRELOADER
// make the progress screen relevant to the application
// here we are in nano land so use the screen to help people experience this
// so we will have concentric rings representing being shrunk
// as the files load the rings will disappear from the outside to the inside
var progress = new createjs.Container();
stage.addChild(progress);
var backing = new zim.Rectangle(stageW, stageH, "#333");
progress.addChild(backing);
// draw rings
var rings = new createjs.Container();
rings.x = stageW/2;
rings.y = stageH/2;
progress.addChild(rings);
progress.rings = rings;
var ring;
var ringNum = 5;
for (var i=1; i<=ringNum; i++) {
ring = new createjs.Shape();
g = ring.graphics;
g.beginFill("#ccc");
g.drawCircle(0, 0, (stageH/2-10)/ringNum*i);
g.beginFill("#333");
g.drawCircle(0, 0, (stageH/2-10)/ringNum*i-(stageH/2-10)/ringNum/2);
ring.alpha = 1;
rings.addChildAt(ring,0);
}
preload.ringNum = ringNum;
// set up proportion object to hide rings based on loading
var baseMin = 0;
var baseMax = 1;
var targetMin = 0;
var targetMax = ringNum;
var factor = 1; // direct relationship
var round = true; // round the converted number
var proportion = new zim.Proportion(baseMin, baseMax, targetMin, targetMax, factor, round);
preload.loadManifest(manifest);
stage.update();
// animate preloader
function animateProgress(e) {
// zog("progress " + e.target.progress);
// the progress property gives a number from 0-1 representing progress
// in percent we would multiply by 100
// this event runs quite quickly and often
// e.target is the preload object in this case
// use ZIM proportion to convert our numbers
// do not removeChild because that will adjust the number of children
// and can't swap the direction due to rings overlapping so just hide ring
var ringNum = proportion.convert(e.target.progress) - 1; // let the first ring be seen
var r = progress.rings.getChildAt(ringNum);
if (r) r.alpha = 0;
stage.update();
}
function nanora() {
zog("nanora");
// start with the most basic element of the appliction
// in this case, let's start with the board and then the peices
// calling functions for main things like this help organize our code
// define some initial parameters
var boardX = 10; // moved this over a touch
var boardY = 80; // leave room above for info bar
var boardW = 800; // leave room at right for lens of lost
var boardH = 480; // leave room below for nav bar
var squareS = 12; // spacing between squares
var cols = 5;
var rows = 4;
var buttonHeight = 58;
var monksAcrossCount = 0;
var botData = [];
// was going to let the user set these
// but that can cause disparancy in scores
// also, an increasing speed can be good for levels
// or even in the same level to create suspense
// so we will try a stepped increasing speed
// start at 5 and go to 3 after 5 cycles - speed up sound too
// then perhaps 2 for the final monk
// could increase speed for each monk across
// but in many cases, most of the monks cross at the same time
// so this would not be very effective
var cycleDuration = 5; // seconds between each cycle
// at this point the main game is done
// I have tried it out a few times and here are some observations
// instead of three different times on automatic,
// I think it would neat to speed up the game as you play it
// so 10 seconds pause for the first ten moves
// then 5 seconds and one second less for each monk you clear
// or something like that - a little tweeking later on of course
// another thing is that it would be neat to play the same board over again
// so the reset message should be two buttons
// one to play the last and one to play the next
// in the end, perhaps high scored per pattern could be recorded, etc.
// maybe patterns could be shared
// a future game could have larger grids, etc.
// the pressing of the cycle text to go to the next cycle manually is obtuse
// it is great when you know what to do - so it requires some thought
// I think using the Lens area as a message box works fairly well
// it is vertical which is a touch awkward but also unique
// and on mobile, less of an issue
// We still need to bring in graphics and sound
// and the logo and intro
// would like to import text so will look into that
// instead of a settings button we should put the toggle right on the bottom
// so lets finish off the auto/manual toggle and the replay / new buttons
// perhaps put these on the bottom too - yes
// also work in a message on manual to press the cycle button
// perhaps making the toggle button the same color will help people see it as a button
// ---------------
// calculate the width and height of squares:
// might calculate this in makeBoard()
// but could need it for other things like positioning pieces
// make a little diagram if this will help
var squareW = (boardW - (cols-1) * squareS) / cols;
var squareH = (boardH - (rows-1) * squareS) / rows;
var board;
var monks;
var bots;
var info;
var metaMonks;
var dragCheck = false; // make sure can't key while dragging
var keyCheck = false; // make sure they can't hold the key down constantly
var skipCheck = false; // keep track as to whether we have skipped a cycle
var cycleText;
var cycleButton;
var arrows;
var lens;
var lensText;
var autoCheck = true;
makeBoard();
makeCycle();
makeBots();
makeLens();
makeNav();
makeMonks();
makeInfo();
// the progress has finished are run the init function
// so fade in the info page
stage.addChild(progress); // make sure it stays on top of the others
progressDone();
// I like calling functions, etc. as what the characters, etc. are in the app
// this way it is more fun as you are coding
// also, note that my next function goes above the previous function
// this means, the latest function is on top - that is my preference
// others might want it flipped around
// as the code gets more complex, you can rearrange things
// fade out the backing and fade in the intro
function progressDone() {
info.getChildAt(0).alpha = 0; // backing
info.getChildAt(1).alpha = 0; // logo
info.getChildAt(2).alpha = 0; // blurb
info.getChildAt(3).alpha = 0; // mute
stage.addChild(info);
stage.update();
var listener = createjs.Ticker.on("tick", stage);
createjs.Tween.get(info.getChildAt(0))
.to({alpha:1},300);
createjs.Tween.get(info.getChildAt(1))
.wait(1500)
.to({alpha:1},3000);
createjs.Tween.get(info.getChildAt(2))
.wait(3000)
.to({alpha:1},1000);
createjs.Tween.get(info.getChildAt(3))
.wait(5000)
.to({alpha:1},0)
.call(function(){
createjs.Ticker.off("tick", listener);
stage.removeChild(progress);
progress = null;
stage.update();
});
createjs.Sound.play("logo");
// after progress - we will start with the info screen
// I do this quite often - have a rules or intro available from a menu
// but start off with that rules or intro - seems an efficient way to do it
//stage.addChild(info);
if (autoCheck) {
stopAuto();
}
}
var muteButton;
var backingSound1, backingSound2, backingSound3, backingSound4;
function makeInfo() {
// make the info screen with backing to cover everything
// could make close button but will just make the whole screen clickable
// since this has a promanent logo - that should work
info = new createjs.Container();
var backing = new zim.Rectangle(stageW, stageH, "#333");
info.cursor = "pointer";
info.addChild(backing);
info.on("click", function() {
if (autoCheck) {
startAuto();
}
// loop the sound using -1 in fifth position (sigh)
// looping unfortunately cannot be done seemlessly at this moment
// there is a pop - so I had to fade out backing sounds and fade back in (sigh)
// here I play the same beep sound but delayed so they overlap the fadeout
// I just let the growl sound fade out and in
// when I used the same technique on the growl it was too busy
if (!backingSound1) { // make sure we do not make new sounds each time
backingSound1 = createjs.Sound.play("backing1", "none", 0, 0, -1);
backingSound2 = createjs.Sound.play("backing1", "none", 3000, 0, -1);
backingSound3 = createjs.Sound.play("backing2", "none", 0, 0, -1);
backingSound4 = createjs.Sound.play("quiet", "none", 0, 0, -1);
backingSound1.setVolume(.7);
backingSound2.setVolume(.7);
backingSound3.setVolume(.7);
backingSound4.setVolume(.4);
backingSound4.setMute(true);
}
var listener = createjs.Ticker.on("tick", stage);
// I don't like crossfading text so fade it out first
// then fade out the background
createjs.Tween.get(info.getChildAt(1))
.to({alpha:0},200);
createjs.Tween.get(info.getChildAt(2))
.to({alpha:0},200);
createjs.Tween.get(info.getChildAt(3))
.to({alpha:0},200)
createjs.Tween.get(info.getChildAt(0))
.wait(300)
.to({alpha:0},300)
.call(function(){
createjs.Ticker.off("tick", listener);
stage.update();
stage.removeChild(info);
});
});
var logo = new createjs.Text("NANORA", "200px hoveragevintage", "white");
logo.textAlign = "center";
logo.x = stageW/2;;
logo.y = 200;
info.addChild(logo);
var description =
"SNEAK PUZZLE GAME TO GET ALL META MONKS\n" +
"PAST NANO BOTS TO THE LENS OF THE LOST\n" +
"MOVE ANY NUMBER OF MONKS PER CYCLE";
var blurb = new createjs.Text(description, "26px Arial", "#CCC");
blurb.textAlign = "center";
blurb.x = stageW/2;
blurb.y = 402;
info.addChild(blurb);
// should have a mute button - ideally on the main app screen
// but we are out of room so put it in the info/intro
// will have to go and adjust the transitions to tween the mute button too
var muteLabel = new zim.Label("MUTE", 32, null, "#ddd", "white");
var muteLabel2 = new zim.Label("SOUND", 32, null, "#ddd", "white");
muteButton = zim.Button(160, buttonHeight, muteLabel, "#444", "#666", null, null, null, -1);
muteButton.x = stageW / 2 + 50;
muteButton.y = stageH - 120;
muteButton.on("click", toggleMute);
info.addChild(muteButton);
function toggleMute(e) {
zop(e);
if (createjs.Sound.getMute()) {
createjs.Sound.setMute(false);
e.target.text = "MUTE";
} else {
createjs.Sound.setMute(true);
e.target.text = "SOUND";
}
stage.update();
}
}
var infoButton; var retryButton; var nextButt;
function makeNav() {
// make three buttons at the bottom
// one for the info page or rules
// one for replay and one for next
// we also have to fit in the logo at the very left
// the buttons can use the same rollOn and rollOff type functions
// we can automate this some more though as follows
var buttons = new createjs.Container();
buttons.x = boardX;
buttons.y = boardY + boardH + squareS;
stage.addChild(buttons);
// call a function (defined below) to make a button
// add each of these buttons to the buttons container
// and put one event on the container for the rollovers
// we will add the individual click events after
var buttonWidth = stageW-boardW-board.x-squareS*2; // same as others
var infoLabel = new zim.Label("INFO", 32, null, "#ddd", "white");
infoButton = zim.Button(buttonWidth, buttonHeight, infoLabel, "#333", "#666", null, null, null, -1);
infoButton.x =(squareW + squareS) * 2 + (squareW - buttonWidth) / 2;
infoButton.on("click", doInfo);
buttons.addChild(infoButton);
var retryLabel = new zim.Label("RETRY", 32, null, "#ddd", "white");
retryButton = zim.Button(buttonWidth, buttonHeight, retryLabel, "#333", "#666", null, null, null, -1);
retryButton.x =(squareW + squareS) * 3 + (squareW - buttonWidth) / 2;
retryButton.on("click", doRetry);
buttons.addChild(retryButton);
var newLabel = new zim.Label("NEW", 32, null, "#ddd", "white");
newButton = zim.Button(buttonWidth, buttonHeight, newLabel, "#333", "#666", null, null, null, -1);
newButton.x =(squareW + squareS) * 4 + (squareW - buttonWidth) / 2;
newButton.on("click", doNew);
buttons.addChild(newButton);
// add the logo
var logo = new createjs.Text("NANORA", "50px hoveragevintage", "white");
logo.textBaseline = "alphabetic";
logo.x = boardX;
logo.y = boardY + boardH + 60;
stage.addChild(logo);
var blurb = new createjs.Text("SNEAK PUZZLE GAME", "16px hoverage", "#777");
blurb.textBaseline = "alphabetic";
blurb.x = boardX + squareW + squareS + 8;
blurb.y = boardY + boardH + 60;
stage.addChild(blurb);
// after some user testing - it helps to label the meta monks
// because one might think they are just tiles in the game
// since the meta monks move, I remove the label after the second cycle
// and bring it back for each new game
metaMonks = new createjs.Text("META MONKS", "20px Arial", "#CCC");
metaMonks.x = boardX + 9;
metaMonks.y = 44;
stage.addChild(metaMonks);
}
function doInfo(e) {
if (autoCheck) {
stopAuto();
}
// don't like to crossfade text so fade up background first
var listener = createjs.Ticker.on("tick", stage);
info.getChildAt(0).alpha = 0; // backing
info.getChildAt(1).alpha = 0; // logo
info.getChildAt(2).alpha = 0; // blurb
info.getChildAt(3).alpha = 0; // mute
stage.addChild(info);
stage.update();
createjs.Tween.get(info.getChildAt(0))
.to({alpha:1},300);
createjs.Tween.get(info.getChildAt(1))
.wait(400)
.to({alpha:1},300);
createjs.Tween.get(info.getChildAt(2))
.wait(400)
.to({alpha:1},300);
createjs.Tween.get(info.getChildAt(3))
.wait(700)
.to({alpha:.7},0)
.call(function(){createjs.Ticker.off("tick", listener); stage.update();});
}
// make the functions to retry and next
function doRetry(e) {
zog("retry");
createjs.Sound.play("new");
// this will be similar to restarting but we need to remember the start set up
// and direction for the bots
// so when we create the bots we need to record their position and direction
// then we set them back to these on retry
if (autoCheck) {
stopAuto();
}
monksAcrossCount = 0;
cycleText.num = 0;
cycleText.text = 0;
// could do everything again like:
// clear all fullCheck, currentSquare, cycleSquare
// put monks back, reposition bots and set new directions
// but might be easier to remove the monks and bots
// then just call their make functions again
// could remove the board and make it again - but will just leave it
// so need to clear the square fullChecks
var square;
for (var i=0; i<board.getNumChildren(); i++) {
square = board.getChildAt(i);
square.fullCheck = false;
}
stage.removeChild(monks);
stage.removeChild(bots);
stage.removeChild(arrows);
monks = null; // remove them from memory
bots = null;
arrows = null;
// the data is still in botData so it will rebuild the same board as last time
makeBots(); // arrows are made in here
makeMonks();
cycleDuration = 5;
if (autoCheck) {
startAuto();
}
zim.animate(metaMonks, {alpha:1}, 1000);
stage.update();
}
function doNew(e) {
createjs.Sound.play("new");
// this replaces the temporary Replay button in the lens of the lost
// restarting the game
// could just refresh the page but not always the nicest
// and might not work on mobile as an app - not sure...
// location.reload();
zog("doNew");
if (autoCheck) {
stopAuto();
}
monksAcrossCount = 0;
cycleText.num = 0;
cycleText.text = 0;
// could do everything again like:
// clear all fullCheck, currentSquare, cycleSquare
// put monks back, reposition bots and set new directions
// but might be easier to remove the monks and bots
// then just call their make functions again
// could remove the board and make it again - but will just leave it
// so need to clear the square fullChecks
var square;
for (var i=0; i<board.getNumChildren(); i++) {
square = board.getChildAt(i);
square.fullCheck = false;
}
stage.removeChild(monks);
stage.removeChild(bots);
stage.removeChild(arrows);
monks = null; // remove them from memory
bots = null;
arrows = null;
botData = []; // send no data so function will build new board
makeBots(); // arrows are made in here
makeMonks();
cycleDuration = 5;
if (autoCheck) {
startAuto();
}
zim.animate(metaMonks, {alpha:1}, 1000);
stage.update();
}
function makeButton(width, height, color, rollColor, label, labelColor, labelRollColor, labelSize) {
// this code would have been created for each button!
// it is better to make it once and pass in the little parts that change
// these we pass in through parameters - a fair number of them - sigh.
// in the future, this function could be made into a class
// and stored in a remote file perhaps a module to be used over and over
// create a container for both the backing and the label
var button = new createjs.Container();
button.mouseChildren = false; // means insides are ignored by mouse events
button.width = width;
button.height = height;
button.color = color;
button.rollColor = rollColor;
button.labelColor = labelColor;
button.labelRollColor = labelRollColor
button.cursor = "pointer";
var buttonBacking = new createjs.Shape();
var g = buttonBacking.graphics;
g.beginFill(button.color);
g.drawRoundRect(0, 0, button.width, button.height, 18);
button.backing = buttonBacking;
button.addChild(buttonBacking);
var buttonLabel = new createjs.Text(label, labelSize + "px Verdana", labelColor);
buttonLabel.textBaseline = "middle";
buttonLabel.textAlign = "center";
buttonLabel.x = button.width / 2;
buttonLabel.y = button.height / 2;
button.label = buttonLabel; // to access the text if we need to
button.addChild(buttonLabel);
return button;
}
// make lens - the area at the right where the monks are trying to go
function makeLens() {
var g;
lens = new zim.Rectangle(stageW-boardW-board.x-squareS*2, boardH, "white", null, null, 20);
lens.x = board.x + (squareW+squareS) * cols;
lens.y = board.y;
lens.alpha = .9;
stage.addChild(lens);
// trying out new font... changed my mind... but had to reduce size
// because logo was being dwarfed
// would have wanted to use bigger linespacing
// but not supported in EaselJS - would have to use DOM Element and got lazy
// plus if the font was bigger for the logo then there would be no room for the tag line
// too bad about the kerning on the L - quite promanent on the lens of the lost
lensText = new createjs.Text("LENS OF THE LOST", "32px Verdana", "#AAA");
lensText.textAlign = "center";
// for consistent vertical font positioning across browsers
lensText.textBaseline = "alphabetic";
lensText.rotation = -90;
lensText.x = lens.x + 75;
lensText.y = boardY + boardH / 2;;
stage.addChild(lensText);
}
// make the bots but just use shapes for now - bring in graphics after
function makeBots() {
// a type of bot goes on each column
// it starts off in a random location on the column
// and it has a random direction with two going up and two going down
// this is not that easy to figure out but this type of thing comes up 1/10 times
// make bots and put in random location:
bots = new createjs.Container();
bots.x = board.x;
bots.y = board.y;
stage.addChild(bots);
// assign directions with two up and two down
// we can shuffle the array using the danzen.js array prototype method
// so in the loop we just pick the shuffled value with the index
var directions = zim.shuffle([-1, -1, 1, 1]);
//zog(directions[0]);
arrows = new createjs.Container();
arrows.x = board.x;
arrows.y = board.y;
stage.addChild(arrows);
// if there is botData then we use it to make the bot
// this means the user hit the retry button
// if the data has length this will assign true to dataCheck
// else it will assign false to datacheck
// can't do this in the loop because the loop will start to add data to dataCheck
// that one got me for a few minutes ;-)
var dataCheck = (botData.length > 0);
var bot; var g; var rand; var square; var arrow; var gA;
var lastDirection = 0; var lastNormalized = 100; var normalized;
var picSide = 111;
var scale = squareH/picSide;
for (var i=0; i<4; i++) {
// replace the red dots with graphics
// I was really liking the red dots and the consistency of the vector look
// it may be that when I put the graphics in I won't like them
// but now with the mood of the sounds... perhaps I will
// bot = new createjs.Bitmap(preload.getResult("bot"+(i+1)));
// bot.scaleX = bot.scaleY = scale;
// decided to go back to vector circles for bots
// and add in little pictures of bots inside circles
var colors = ["purple", "green", "blue", "red"];
var shapeColors = ["pink", "lightgreen", "lightblue", "pink"];
var colors = ["mediumorchid", "darkgoldenrod", "darkcyan", "indianred"];
var shapeColors = ["pink", "burlywood", "lightblue", "pink"];
var lines = [
"Ai9AqIC9g1IAAAAIh0ioAAAC0IAAi/AB1izIh1CoAC+AqIi+g1",
"AB6iHIgNAAIhvCHIB3CLIjtABIB2iMIh0iHIDjAAIAEgE",
"Ah3iGIBzAsIBwgsIADgEAgECLIAAjlABsiGIAMAA",
"ACXCWQg/BAhYAAQhXAAg/hAQg+g+AAhYQAAhXA+g/QA/g/BXAAQBYAAA/A/QA/A/AABXQAABYg/A+g"
];
var dots = [ "AgVDMQgKgKAAgOQAAgOAKgKQAKgKAMAAQAOAAAKAKQAKAKAAAOQAAAOgKAKQgKAKgOAAQgMAAgKgKgAClBCQgLgKAAgOQAAgOALgKQAJgKAPAAQANAAALAKQAJAKAAAOQAAAOgJAKQgLAKgNAAQgPAAgJgKgAjTBCQgKgKAAgOQAAgOAKgKQALgKANAAQAOAAALAKQAKAKAAAOQAAAOgKAKQgLAKgOAAQgNAAgLgKgAgZAdIgFgFIgBgBQgOgPAAgSQAAgVAOgOQAOgPATAAQAVAAAOAPQAJAIAEAMQABAHAAAIQAAASgOAPQgOAPgVAAQgPAAgMgJgABcibQgKgKAAgOQAAgOAKgKQAKgKAOAAQAOAAAKAKQAKAKAAAOQAAAOgKAKQgKAKgOAAQgOAAgKgKgAiLibQgKgKAAgOQAAgOAKgKQAKgKAOAAQAOAAAKAKQAKAKAAAOQAAAOgKAKQgKAKgOAAQgOAAgKgKg",
"ABUCoQgMgMAAgRQAAgRAMgMQAMgNARAAQARAAANANQANAMgBARQABARgNAMQgNAMgRAAQgRAAgMgMgAiSCoQgNgMAAgRQAAgRANgMQAMgNARAAQASAAALANQANAMAAARQAAARgNAMQgLAMgSAAQgRAAgMgMgABYhsQgNgMAAgRQAAgRANgMQAMgNARAAQASAAALANQAOAMAAARQAAARgOAMQgLAMgSAAQgRAAgMgMgAiShsQgNgMAAgRQAAgRANgMQAMgNARAAQASAAALANQANAMAAARQAAARgNAMQgLAMgSAAQgRAAgMgMg",
"AgaC4QgMgMAAgRQAAgRAMgMQAMgNAPAAQASAAAMANQAMAMABARQgBARgMAMQgMAMgSAAQgPAAgMgMgAiwh5QgNgMAAgRQAAgSANgMQAMgNARAAQARAAANANQAMAMAAASQAAARgMAMQgNAMgRgBQgRABgMgMgAB2h7QgMgNAAgQQAAgTAMgMQAMgMARAAQARAAANAMQANAMgBATQABAQgNANQgNALgRAAQgRAAgMgLg",
"AiQDRQgMgMAAgRQAAgSAMgMQANgMAQAAQASAAAMAMQANAMAAASQAAARgNAMQgMAMgSAAQgQAAgNgMgABWiUQgNgNAAgQQAAgSANgMQAMgNAQAAQASAAAMANQANAMAAASQAAAQgNANQgMALgSAAQgQAAgMgLg"
];
bot = new createjs.Shape();
g = bot.graphics;
g.beginFill(colors[i]);
g.drawCircle(0, 0, squareW/4);
// exported vector shapes from Flash using http://www.adobe.com/devnet/createjs.html
g.f(shapeColors[i]).p(dots[i]);
g.f().s(shapeColors[i]).ss(1,1,1).p(lines[i]);
// rgba(255,255,255,.5) // to set with alpha
// if we had data to start then use it
// else do what we were doing before and make the data
// at the end of the else we store the data in dataCheck
// for each bot we make
// then we make the bot with either the old data or the new data
if (dataCheck) {
rand = botData[i][0];
bot.direction = botData[i][1];
} else {
// board squares are laid out in columns first
// so first column has child 0, 1, 2, 3
// the next is 4, 5, 6, 7 then 8, 9, 10, 11
// so each column starts at i * rows
// we are starting in one row
// now pick a random number from (i+1) * rows to (i+1) * rows + (rows-1)
// there is a function in danzen.js to do this
rand = zim.rand((i+1) * rows, (i+1) * rows + (rows-1));
// zog(rand);
// turns out you can't get by diagonals going the same way
// could add diagonal movement
// could force alternate directions
// could run the random positoning again if we get diagonals
// this last one would be my preference but it might be tricky
// get a direction from the array and assign it to the bot
bot.direction = directions[i];
// normalized in this case just means to take the random number
// which counts from the top left down and across so
// 0,1,2,3,4,5,6,7,8,9, etc.
// and figure out which row that number is on i.e. 0,1,2 or 3
normalized = rand - (i+1)*rows; // this is what row the random number is on
// zog("normalized " + normalized);
// only need to check for diagonals
// if bot is going in the same direction as the last bot
if (bot.direction == lastDirection) {
// zog("same direction");
// zog("last normalized " + lastNormalized);
// keep on looping and finding a new random number
// until the normalized value is not diagonally next to
// the last normalized value - remember it wraps as well (sigh)
// if this is not true, then it jumps out of the loop
// if it was not true to start, then it does not go into the loop
// if you wanted to run the loop at least once, use a do{} while() loop
while ( normalized == lastNormalized+1 ||
normalized == lastNormalized-1 ||
(normalized == 0 && lastNormalized == 3) ||
(normalized == 3 && lastNormalized == 0) ) {
rand = zim.rand((i+1) * rows, (i+1) * rows + (rows-1));
normalized = rand - (i+1)*rows;
// zog("renormalized " + normalized);
}
}
lastNormalized = normalized;
lastDirection = bot.direction;
// record the bot data
botData[i] = [rand, bot.direction];
}
// so the selected square is
square = board.getChildAt(rand);
// bot.x = board.getChildAt(rand).x + (squareW-picSide*scale)/2;
// bot.y = board.getChildAt(rand).y
bot.x = board.getChildAt(rand).x + squareW/2;
bot.y = board.getChildAt(rand).y + squareH/2;
bots.addChild(bot);
// set the fullCheck of the square to true
square.fullCheck = true;
// draw arrow on top of column
arrow = new zim.Triangle(40,28,28,"#999");
arrow.rotation = bot.direction*90 + 90;
arrow.x = (squareW + squareS) * (i + 1) + squareW/2;
arrow.y = -30;
arrows.addChild(arrow);
// remember what square the bot is on
bot.square = square;
}
}
// the cycle is a text display of how many cycles have gone by
// it also comes with a button to advance cycles if set to manual (default)
function makeCycle() {
cycleWidth = stageW-boardW-board.x-squareS*2;
cycleButton = new zim.Rectangle(cycleWidth,buttonHeight,null,null,null,18);
cycleButton.cursor = "pointer";
cycleButton.color = "violet";
cycleButton.rollColor = "plum";
cycleButton.setFill(cycleButton.color);
cycleButton.x = board.x + boardW + squareS;
cycleButton.y = squareS;
stage.addChild(cycleButton);
cycleText = new createjs.Text("0", "40px Verdana", "white");
cycleText.num = 0;
cycleText.textBaseline = "alphabetic";
cycleText.textAlign = "center";
cycleText.x = cycleButton.x + cycleButton.width / 2;
cycleText.y = cycleButton.y + 43;
stage.addChild(cycleText);
cycleButton.on("mouseover", rollOn);
cycleButton.on("click", doCycle);
function doCycle(e) {
if (autoCheck) return;
advanceCycle();
}
function rollOn(e) {
e.target.on("mouseout", rollOff);
e.target.setFill(e.target.rollColor);
stage.update();
}
function rollOff(e) {
e.target.off("mouseout", rollOff);
e.target.setFill(e.target.color);
stage.update();
}
autoCheck = true;
startAuto();
cycleWidth = stageW-boardW-board.x-squareS*2;
cycleToggle = new zim.Rectangle(cycleWidth,buttonHeight,null,null,null,18);
cycleToggle.cursor = "pointer";
cycleToggle.color = "darkorange";
cycleToggle.rollColor = "orange";
cycleToggle.setFill(cycleToggle.color);
cycleToggle.x = board.x + boardW + squareS;
cycleToggle.y = boardY + boardH + squareS;
stage.addChild(cycleToggle);
toggleText = new createjs.Text("STEP", "32px Verdana", "white");
toggleText.textBaseline = "middle";
toggleText.textAlign = "center";
toggleText.x = cycleToggle.x + cycleToggle.width / 2;
toggleText.y = cycleToggle.y + buttonHeight / 2 + 1;
stage.addChild(toggleText);
cycleToggle.on("mouseover", rollOn);
cycleToggle.on("click", doToggle);
}
var autoInterval;
function doToggle(e) {
//switch the button colors
var tempColor = cycleToggle.color;
var tempRollColor = cycleToggle.rollColor;
cycleToggle.color = cycleButton.color;
cycleToggle.rollColor = cycleButton.rollColor;
cycleButton.color = tempColor;
cycleButton.rollColor = tempRollColor;
// redraw button backings
cycleButton.setFill(cycleButton.color);
cycleToggle.setFill(cycleToggle.color);
if (autoCheck) {
backingSound1.setMute(true);
backingSound2.setMute(true);
backingSound3.setMute(true);
backingSound4.setMute(false);
autoCheck = false;
toggleText.text = "AUTO";
stopAuto();
lensText.text = "PRESS NUMBER";
lensText.color = "white";
lens.setFill(cycleButton.rollColor);
clearTimeout(delay); //just in case
delay = setTimeout(function(){
lensText.text = "LENS OF THE LOST";
lensText.color = "#AAA";
lens.setFill("white");
stage.update();
}, 2000);
} else {
backingSound1.setMute(false);
backingSound2.setMute(false);
backingSound3.setMute(false);
backingSound4.setMute(true);
autoCheck = true;
toggleText.text = "STEP";
startAuto();
lens.setFill(cycleButton.rollColor);
lensText.text = "AUTOMATIC";
lensText.color = "white";
clearTimeout(delay); //just in case
delay = setTimeout(function(){
lensText.text = "LENS OF THE LOST";
lensText.color = "#AAA";
lens.setFill("white");
stage.update();
}, 2000);
}
stage.update();
}
function startAuto() {
autoInterval = setInterval(advanceCycle, cycleDuration*1000);
}
function stopAuto() {
clearInterval(autoInterval);
}
function advanceCycle() {
// problems when you pick up a monk and hold on to it
// and the cycle passes - you can let bot go right under
// in some cases you can drop on bot
// could test to see if bot is under monk when dropping
// but what if you let bot go right under you
// and then two cycles later let go of the monk
// Also, if you pick up first bot and another bot gets hit
// then that bot goes back to start in picked up bot's location
// could test to see if that location originally belongs
// to a picked up monk
// or... could just not advance the cyle when a monk is picked up
// try this first and see how it feels when playing game
if (dragCheck) {
// initially, just had a return here
// it worked well but sometimes it seemed awkward to skip the cycle
// and wait another 5 seconds...
// optimally, we would want to skip the cycle but as soon as they drop
// then advance the cycle right away
// so remember that we have skipped the cycle
skipCheck = true;
return;
}
// seems to work pretty well
// people can now almost pause the game
// by picking up a monk and holding it - gives time to think
// so... not quite optimal but you can call it a game-play technique
createjs.Sound.play("cycle");
cycleText.num++;
cycleText.text = cycleText.num;
if (cycleText.num == 5) {
cycleDuration = 3;
// do this in two steps
// if they do manual we still want to be fast
// if they move to automatic
if (autoCheck) {
stopAuto();
startAuto();
}
}
if (cycleText.num == 3) {
zim.animate(metaMonks, {alpha:0}, 1000);
}
// each time the cycle goes, the monks get a new cycleSquare
var monk; var i;
for (i=0; i<4; i++) {
monk = monks.getChildAt(i);
monk.cycleSquare = monk.currentSquare;
}
// move the bots in their direction
var bot; var j; var k; var square; var monk; var startSquare;
for (i=0; i<4; i++) {
bot = bots.getChildAt(i);
// tell the bot's current square that it is no longer full
bot.square.fullCheck = false;
// move the bot
bot.y += bot.direction * (squareH + squareS);
if (bot.y < 0) { // off the top so wrap to bottom
bot.y += (squareH + squareS) * 4;
}
if (bot.y > boardH) { // off the bottom so wrap to top
bot.y -= (squareH + squareS) * 4;
}
// find out which new square the bot is on
// could try and calculate this either by height or by memory
// but I think I will just do it by hitTest
// remember not to use i for iterator as we are inside a loop using i
for (var j=0; j<board.getNumChildren(); j++) {
square = board.getChildAt(j);
if (bot.hitTest(square.x+squareW/2-bot.x, square.y+squareH/2-bot.y)) {
break; // we got the square so exit the loop
}
}
// if the square is full then that means we are hitting monk
if (square.fullCheck) {
zog("hitting a monk!");
// decide what to do in game
// could end the game
// could add 10 cycles or whatever
// could make the monk go back to the start
// could whiten square so can't use it
// let's send the monk back to the first open square from the top and left
// find out which monk is on the square
for (k=0; k<4; k++) {
monk = monks.getChildAt(k);
if (monk.currentSquare == square) {
// we have found monk so exit loop
break;
}
}
// now send monk back
// would want to play a sound too!
createjs.Sound.play("back");
for (k=0; k<4; k++) {
var startSquare = board.getChildAt(k);
if (!startSquare.fullCheck) {
monk.x = startSquare.x;
monk.y = startSquare.y;
monk.currentSquare = startSquare;
monk.cycleSquare = startSquare; // nearly forgot this one...
startSquare.fullCheck = true;
break;
}
}
}
square.fullCheck = true;
bot.square = square;
}
stage.update();
}
// add key event listener for arrows
// these will act just like a mouse move
// so we need to be careful and apply the various checks and start values, etc.
window.addEventListener("keydown", doKey);
function doKey(e) {
if (!e) e=event; // for some old IE or something...
var k = e.keyCode;
// zog(k); // this is how you find out the key number that is pressed
if (keyCheck) {return;} // currently processing a key
keyCheck = true;
if (dragCheck) {return;} // currently dragging
if (!currentMonk) {return;} // they have not selected monk
// forgot about this and had to solve bug
currentMonk.startX = currentMonk.x;
currentMonk.startY = currentMonk.y;
if (k == 37) { // left
currentMonk.x -= (squareW+squareS);
doKeyMove();
} else if (k == 38) { // up
currentMonk.y -= (squareH+squareS);
doKeyMove();
} else if (k == 39) { // right
currentMonk.x += (squareW+squareS);
doKeyMove();
} else if (k == 40) { // down
currentMonk.y += (squareH+squareS);
doKeyMove();
}
function doKeyMove() {
// forgot about fullCheck and had to solve bug
currentMonk.currentSquare.fullCheck = false;
checkMove(currentMonk);
}
}
function makeMonks() {
// make a container for the monks
monks = new createjs.Container();
monks.x = boardX;
monks.y = boardY;
stage.addChild(monks);
var monk; var g;
for (var i=0; i<4; i++) {
monk = new createjs.Shape();
g = monk.graphics;
g.beginStroke("white").setStrokeStyle(2);
g.beginFill("#000");
g.drawRoundRect(0, 0, squareW, squareH, 20);
g.beginFill("#FFF"); // outer circle
g.drawCircle(squareW/2, squareH/2, squareH/2*.8);
g.beginFill("#000"); // inner circle
g.drawCircle(squareW/2, squareH/2, squareH/2*.5);
monk.x = 0;
monk.y = i*(squareH+squareS);
monks.addChild(monk);
// since we are putting monks on these squares
// we need to record which square the monk is on
monk.currentSquare = board.getChildAt(i);
// need to check that they move one square away per cycle
// so introduce the monk's cycleSquare
monk.cycleSquare = board.getChildAt(i);
// we also need to set the square's fullCheck property to true
monk.currentSquare.fullCheck = true;
}
// add moving monks
// indicate which monk is moving and leave indication as selected monk
// this means we have to clear any previously selected monk
monks.cursor = "pointer";
monks.on("mousedown", function(e) {
// remember that we are dragging so we do not let keypress move while dragging
dragCheck = true;
e.target.diffX = e.stageX - e.target.x;
e.target.diffY = e.stageY - e.target.y;
// recording where we started for snapping back if no hit
e.target.startX = e.target.x;
e.target.startY = e.target.y;
clearSelected(); // this clears any previously selected monks
e.target.graphics.beginFill("#fff"); // inner circle
// black circle edge was showing on ipad so make white center circle a bit bigger
e.target.graphics.drawCircle(squareW/2, squareH/2, squareH/2*.6);
monks.cursor = "move";
monks.addChild(e.target); // adds it to the top (could use setChildIndex())
// need to mark this square as no longer full
e.target.currentSquare.fullCheck = false;
stage.update();
});
monks.on("pressmove", function(e) {
e.target.x = e.stageX-e.target.diffX;
e.target.y = e.stageY-e.target.diffY;
stage.update();
});
monks.on("pressup", function(e) {
monks.cursor = "pointer";
// move the dragCheck to after checkMove and tween back
// remember that we stopped dragging so we can use keys
// dragCheck = false;
// adding key moves and they will check monks the same way
// so move the on press up stuff to a more generic checkMove() function
// remember to move it outside of the makeMonks() function
// so the keydown event can access it
checkMove(e.target);
});
}
var currentMonk; var delay;
function checkMove(monk) {
currentMonk = monk; // use for keypress
// see if the monk has reached the end or the lens of lost
// the holder for lens and monks do not start at the same place
// so need to adjust to same coordinate space (pain in the neck)
// check to make sure monk can move to end as well
// comment out the first part of the if statement for testing...
if (monk.cycleSquare.x > squareW * 4 &&
lens.hitTest(monks.x+monk.x+squareW/2-lens.x, monks.y+monk.y+squareH/2-lens.y)) {
monk.x = -4000; // do not removeChild() as that messes up looping
monk.currentSquare.fullCheck = false;
lensText.color = "white";
lens.setFill("red");
monksAcrossCount++;
clearTimeout(delay); // if clear the last two at once creates bug
// adjusted to only show end message not be a button
// as the restart buttons are now at the bottom
var delayTime;
if (monksAcrossCount >= 4) {
// will want to play sound
createjs.Sound.play("win");
lensText.text = "LEVEL COMPLETE!";
if (autoCheck) {
stopAuto();
}
delayTime = 2000;
} else {
if (monksAcrossCount == 3) {
cycleDuration = 2;
if (autoCheck) {
stopAuto();
startAuto();
}
}
// will want to play sound
createjs.Sound.play("across");
lensText.text = "CONGRATULATIONS!";
delayTime = 1000;
}
delay = setTimeout(function(){
lensText.text = "LENS OF THE LOST";
lensText.color = "#AAA";
lens.setFill("white");
stage.update();
}, delayTime);
stage.update();
dragCheck = false;
skipCheck = false;
keyCheck = false;
return;
}
// snap the object onto a square
// could be done with code to find the nearest position on a grid
// will do it by looping through hitTests
var square;
var hitCheck = false;
var lastSquare;
var goodCheck
for (var i=0; i<board.getNumChildren(); i++) {
square = board.getChildAt(i);
if (square.hitTest(monk.x-square.x+squareW/2, monk.y-square.y+squareH/2)) {
// make sure square only moves one square away (sigh)
// see how even the simplest things can get complex?
// could go by eligible grid locations
// like if we are at B2 then we can go to A2, B1, B3, C2
// we could test if x is the same then y needs to be one square away
// if y is the same then x needs to be one square away
// else it is not a valid square - I like this one
lastSquare = monk.currentSquare;
var goodCheck = false;
if (lastSquare.x == square.x) {
//zog("x is same");
if (Math.abs(lastSquare.y - square.y) <= squareH + squareS*2) { // little extra
goodCheck = true;
}
} else if (lastSquare.y == square.y) {
//zog("y is same");
if (Math.abs(lastSquare.x - square.x) <= squareW + squareS*2) { // little extra
goodCheck = true;
}
}
if (!goodCheck) {
break; // exits the loop
}
// need to check that they move one square away per cycle
// so introduce the monk's cycleSquare
// use the cycleSquare instead of the currentSquare
// this check is exactly the same as above but we need both
// otherwise it allows for diagonal movement
// so we can just reuse the lastSquare and goodCheck variable names
// in a sense, we are making a bunch of gates - or checks
// if it ever fails, we exit the loop
lastSquare = monk.cycleSquare;
goodCheck = false;
if (lastSquare.x == square.x) {
if (Math.abs(lastSquare.y - square.y) <= squareH + squareS*2) { // little extra
goodCheck = true;
}
} else if (lastSquare.y == square.y) {
if (Math.abs(lastSquare.x - square.x) <= squareW + squareS*2) { // little extra
goodCheck = true;
}
}
if (!goodCheck) {
break; // exits the loop
}
// check if square is full
if (square.fullCheck) {
break; // exits the loop
}
// now the square is full
square.fullCheck = true;
// remember which square the monk is on
monk.currentSquare = square;
hitCheck = true;
zim.move(monk, square.x, square.y, 50, null, doneAnimating); // what, where, how fast ms
}
}
if (!hitCheck) {
// put square back to where it came from
createjs.Sound.play("wrong");
monk.currentSquare.fullCheck = true;
zim.move(monk, monk.startX, monk.startY, 200, null, doneAnimating);
} else {
// sort of detracts from the sneaking
// createjs.Sound.play("move");
}
stage.update();
}
var tweenTick;
function doneAnimating() {
createjs.Ticker.off("tick", tweenTick);
keyCheck = false;
// moved this to here too
// otherwise, glitch could happen
// as monk is animating back to place and cycle changes
dragCheck = false;
// if we have been holding the monk at a scheduled cycle change
// then we do not do the cycle change and we set skipCheck to true
// now that we have finished the monk move, we should advance the cycle
// and reset the cycle duration
// if we do not reset the cycle duration, it could go twice quickly
// Also, don't think it will matter, we only want to do this on auto
if (skipCheck && autoCheck) {
skipCheck = false; // no longer skipping
advanceCycle(); // right away!
stopAuto(); // reset timing - almost forget to do this
startAuto();
}
}
function clearSelected() {
var monk; // this will store the temporary monk as we loop
for (var i=0; i<4; i++) {
monk = monks.getChildAt(i);
monk.graphics.beginFill("#000"); // inner circle
monk.graphics.drawCircle(squareW/2, squareH/2, squareH/2*.5);
}
}
function makeBoard() {
// we could draw each board square in one shape
// but perhaps we will want individual shapes
// so that we can do hitTest, etc. on them
// make a container for all the board squares:
// and position it at the boardX and boardY
board = new createjs.Container();
stage.addChild(board);
board.x = boardX;
board.y = boardY;
// grids usually mean a loop within a loop
// set it up and test with a zog to make sure it is as expected
// you can do each column for a row or each row for a column
// it is up to you
var i; var j; var rect; var g;
for (i=0; i<cols; i++) {
for (j=0; j<rows; j++) {
rect = new zim.Rectangle(squareW, squareH, "#ccc", "white", 2, 20);
rect.x = i*(squareW+squareS);
rect.y = j*(squareH+squareS);
rect.fullCheck = false;
board.addChild(rect);
}
}
}
stage.update();
} // end of app
}); // end of ready
</script>
</head>
<body>
<!-- canvas with id="myCanvas" is made by zim Frame -->
</body>
</html>