Commits

Owen Nelson committed a04b3c0

initial checkin

Comments (0)

Files changed (3)

+(function () {
+//global vars
+var screenLineTop=0;
+//movement
+var left=false;
+var right=false;
+var canMove=false;
+//tracks the current interactable object
+var targetObject="bed";
+//tracks time for the clock
+var timeDelay=0;
+var timeFlip=4;
+//tracks penalty time for curfew
+var curfew=true;
+var obedience=0;
+var awakeTime=false;
+//tracks animation for zzZ
+var snooze=3; 
+var snoozeTime=0;
+//tracks if there is food in the machine
+var hasFood=false;
+//tracks hunger
+var hunger=0;
+//tracks if in the game
+var ingame=true;
+
+//CONSTANTS
+var MINX=242;
+var MAXX=842;
+
+
+//constructor.. kinda
+$(document).ready(function(){
+	resizeWin();
+	//startGame();
+	runIntro();
+});
+
+//runs the intro video
+function runIntro(){
+	$("#intro div").css("display","block");
+	$("#intro div").click(function(){
+		$("#intro").animate({opacity : 0},300,function(){
+			$("#intro").css("display","none");
+			startGame();
+		});
+	});
+}
+
+//starts the gameplay
+//binds events/
+//animates/etc
+function startGame(){
+	//starts screenlines
+	shiftScreenLines();
+	//pulses the shutter color
+	//hueShift("#shutters div div",50,190,"70%","6%",60,"background");
+	hueShift("#techPaths polygon, #techPaths rect",50,190,"70%","20%",60,"stroke");
+	hueShift("#techPaths polygon, #techPaths rect",50,190,"70%","10%",60,"fill");
+	//binds events for wasd interactions
+	$(window).on( "keypress", function(e){keyPress(e.which,"down");} );
+	$(window).on( "keyup", function(e){keyPress(e.which,"up");} );
+	//begins the gameloop
+	setInterval(gameLoop,20);
+	//listens for object interactions
+	$("#clickArea").click(function(e){useObject(e);});
+	//queue up teleporter animations
+	animtele();
+	//click for volume control
+	$("#volumeControl").click(function(){
+		vid("beyondReach",2200);
+	});
+}
+
+//handles window resize when its...... resized....
+$(window).resize(function(){
+	resizeWin();
+});
+
+/*
+* Main Loop for the game
+*/
+function gameLoop(){
  1. Owen Nelson author

    So, I've been trying to figure out how to best break this function down for a few hours over the past week or so. It's taken me a long time to pour over it, largely because of the way it is written (and this is a problem). Unfortunately, because I have had a hard time fully groking all the ins and outs, I am not able to recommend an appropriate design.

    Instead, I'm going to throw out some high-level general stuff...

    I'm not huge on code metrics per se, but in this case cyclomatic complexity is a problem. If you're not familiar, look it up on wikipedia (they have a lengthy explanation). I found some random-ass website that measures cyclomatic complexity of snippets of javascript and ran just the gameLoop() function through it: http://jsmeter.info/0natkg/3#results

    The general rule of thumb is that if your complexity is greater than 10, your code is considered "unmaintainable." This website reported the gameLoop() function at 32, which also contains an anonymous func with a complexity of 15.

    The TL;DR here is that you probably need to refactor this function, even just splitting it up smaller functions. One way to approach this might be to consider the SRP (Single Responsibility Principle). This code manages user input, managing the game clock, animations, game state, collision detection (overlaps)... that's a lot of different stuff.

    As a hypothetical, imagine this game was actually a contract job, and your client came back to you with an urgent piece of scope creep: "We need this to be 2 player on one keyboard!" How would that impact the design? For starters, I'd say that a lot of the global variables that are manipulated in this function (which is in and of itself a bad "code smell") should actually be properties on a player object that gets created as the game starts.

    I mentioned the possibility of grouping player-specific information on a player object - this would be a great start to reorganizing your code into 3 classifications: things that act upon the player, things that the player acts upon, and side effects of changes to the player.

    It'd become clear that checkInput(player) is checking for keyboard input for a player and applying the final result. Perhaps the player object has information about what keys are bound to what actions. In other frameworks, the checkInput() code might be implemented as a method on the player itself (common method names might be player.tick() or player.update() but those, by convention would accept a "timedelta" to know how much time had passed since the last game "frame" so that might be misleading).

    You might be thinking that it would be super annoying to track all your player state on an object instead of looking directly to global variables and attributes/data/classes on the DOM (a common pitfall in jQuery-powered code-bases). Observers are a thing that could have really helped. If you're not familiar with the design pattern, it's basically a way to register a handler/callback to value changes of objects/properties and membership changes in collections (like Arrays).

    Observers are tech that is coming to javascript in the next major version, so it's not supported everywhere yet, but in the mean time you could try a polyfill (a library that implements the new API using existing language features at the cost of a performance decrease). One such polyfill for observers is https://github.com/Polymer/observe-js The really nice thing about using a polyfill is that it can mean you can use a good design now, and it will just get faster and more performant as browsers finally implement the new language feature in their own native code. Eventually, you'll just remove the script from your game and use what the browser provides.

    Your updated implementation might then look like

    function Player(...) {
    
        // setup default values for player props.
        this.x =  100;
        this.y = 100;
        this.moveSpeed = 10;
        this.obedience = 0;
        // etc...
    
        // define callbacks for prop changes that update styles, or other DOM state based on player state.
    
        // take args and use them to set initial player state (ie, update props 
        // on 'this')  <- could happen before or after observers are registered depending on what you need.
    }
    
    
    var player1 = new Player(...);
    player1.x = player.x - 10; // observer sees decrease in x position so triggers the "walk left" animation
    

    I guess my general summary is:

    • Create objects for the specific "things" in your game world.
    • Make those things interact with each other to yield meaningful results.
    • Make those things responsible for their own concerns (Player knows how to animate players).
    • Don't rely on the DOM for data storage/retrieval - the DOM should be notified of changes to game state, but should not be the authority.
    • In fact... DOM interaction should be some of the lowest tier in your code and should be encapsulated away as much as possible.
+	if(ingame){
+		//controls character movement
+		if(canMove){
+			var charX=parseInt($("#chara").css("left"));
+			if(left){
+				if(charX>MINX){
+					charX-=1;
+				}
+			}else if(right){
+				if(charX<MAXX){
+					charX+=1;
+				}
+			}
+			//tracks if a valid object has been found
+			var valiObj=false;
+			//checks for overlap with interactable objects
+			$(".objectZone").each(function(){
+				//loop is processed only if a valid object has not yet been found
+				if(valiObj==false){
+					var canActivate=false; //notes if it can do the action
+					if($(this).attr("fullIn")=="yes" && (
+						charX>parseInt($(this).css("left")) && charX+$("#chara").width()<parseInt($(this).css("left"))+parseInt($(this).width())
+					)){ //if being fully inside is required and it is fully inside
+						canActivate=true;
+					}else if($(this).attr("fullIn")!="yes" && (
+						charX>parseInt($(this).css("left")) && charX<parseInt($(this).css("left"))+parseInt($(this).width()) ||
+						charX+$("#chara").width()>parseInt($(this).css("left")) && charX+$("#chara").width()<parseInt($(this).css("left"))+parseInt($(this).width())
+					)){ //if being fully inside is not required but it is touching.
+						canActivate=true;
+					}
+					//if there is no food and the food machine is the area
+					//it will disable the action
+					if(!hasFood&&$(this).attr("id")=="foodMachine"){
+						canActivate=false;
+					}
+					//if able to be activated
+					if(canActivate){
+						if(!curfew||$(this).attr("id")=="bed"){ //only sleep is allowed past curfew
+							//shows the action mark
+							$("#actionMark").css("display","block");
+							//sets the valid object
+							valiObj=true;
+							//sets the targeted object
+							targetObject=$(this).attr('id');
+							//makes character appear clickable
+							$("#clickArea").css("display","block");
+						}
+					}else{
+						//undoes any actions that may exist from previous state
+						$("#actionMark").css("display","none");
+						valiObj=false;
+						targetObject="";
+						$("#clickArea").css("display","none");
+					}
+				}
+			});
+			//updates character position
+			$("#chara").css("left",charX+"px");
+			//changes click area postion
+			$("#clickArea").css("left",charX+"px");
+		}else{
+			//animations for sleep
+			//if on tenth loop cycle
+			if(snoozeTime==10){
+				$("#zzz span").css("opacity","0.1"); //sets all zs to 0.1 alpha
+				$("#zzz span:nth-child("+snooze+")").css("opacity","1"); //active z is 100% alpha
+				snooze--; //decrements snooze counter
+				if(snooze<0){
+					snooze=3;
+				}
+				//resets timer
+				snoozeTime=0;
+			}else{
+				//increments count to next snooze animation
+				snoozeTime++;
+			}
+		}
+		//handles game clock
+		timeDelay++;
+		//if the counter is hit
+		if(timeDelay==timeFlip){
+			timeDelay=0; //resets counter
+			var time=$("#clock").html().split(":"); //splits current clock time
+			time[1]=parseInt(time[1])+1; //increments minutes
+			if(time[1]==60){
+				//updates clock
+				time[1]="00"; //if its been 1 game hour
+				time[0]=parseInt(time[0])+1; //adds one to hour
+				if(time[0]==24){
+					time[0]="00"; //if its been 1 game day resets to zero
+				}else if(time[0]<10){
+					time[0]="0"+time[0]; // if game hours is less than 10, adds zero for style
+				}
+				//events for specific times
+				if(time[0]=="06"){
+					console.log("morning");
+					vid("morningHasCome",2700); //anounces morning
+					curfew=false; //lifts curfew
+					openShutters(); //opens the bg video shutters
+				}
+				if(time[0]=="07"){
+					awakeTime=true;	//must be awake at this point
+				}
+				//dispenses food
+				if(time[0]=="12"){
+					vid("mealTime",800);
+				}
+				//meal time
+				if(time[0]=="11"){
+					console.log("lunch");
+					if(hasFood){ //if there is already food
+						hunger++; //increases hunger
+						if(hunger<2){
+							vid("maintainHealth",4000); //reminds player to eat
+							obey(-3);
+						}else if(hunger<4){
+							vid("sustanence",2200); 
+							obey(-5);
+						}
+						if(hunger>=4){
+							endGame("starvation"); //starvation occurs at 10 hunger
+						}
+					}else{
+						hasFood=true; // notes there is food
+						vid("dispensingFood",2200); 
+						sfx("food");
+						//visually dispenses food
+						$("#foodMachine").css("background",'url("style/images/fullFood.png")');
+					}
+				}
+				if(time[0]=="20"){
+					console.log("curfew");
+					vid("curfewBegins",2700);
+					awakeTime=false; //2 hours of free timeddd
+				}
+				if(time[0]=="22"){
+					console.log("past curfew");
+					curfew=true; //starts curfew
+					$("#videColor").css("background","#ff0");
+					vid("pastCurfew",2700);
+					setTimeout(function(){closeShutters();},2700);
+					//closeShutters(); //closes the bg video shutters
+				}
+				//if not asleep during curfew
+				if(curfew&&canMove){
+					obey(-3); //lowers noted obedience by -1
+				}else if(!curfew&&canMove){
+					obey(1); //raises if awake when should be
+				}
+				//if not awake during specified hours
+				if(awakeTime&&!canMove){
+					obey(-3); //lowers noted obedience by -1
  1. Owen Nelson author

    This comment seems inaccurate, or at least I hope it is. Better to accurately document the obey() function where it is defined and assume we can look at the function documentation to know what it does.

+				}else if(!awakeTime&&!canMove){
+					obey(1); //raises if asleep while should be
+				}
+				//color changes with obedience
+				obeyColor();
+			}else if(time[1]<10){
+				time[1]="0"+time[1]; //if minutes is less than 10, adds zer for style
+			}
+			$("#clock").html(time[0]+":"+time[1]); //updates the display clock
+		}
+	}
+}
+
+/*
+* Only clickable if over an object
+* Checks String Target Object for div id name
+* Performs actions based on id
+*/
+function useObject(e){
+	console.log(targetObject);
+	switch (targetObject){
+		//handles sleep
+		case "bed": 
+			canMove=!canMove;
+			//puts character in the bed
+			if(!canMove){
+				$("#bedFoot").css("display","block");
+				$("#chara").css("background",'url("style/images/charaSleep.gif")');
+				//shows zZZ
+				$("#zzz").css("display","block");
+				if(!awakeTime&&!curfew){
+					vid("goodNight",1500);
+				}
+			}else{
+				$("#bedFoot").css("display","none");
+				$("#zzz").css("display","none");
+				$("#chara").css("background",'url("style/images/charaStillL.gif")');
+			}
+			break;
+		//eating food
+		case "foodMachine":
+			//if there is food in the machine
+			if(hasFood){
+				hunger--; //decreases hunger
+				sfx("eat");
+				if(hunger<0){
+					hunger=0; //minimum hunger
+				}
+				hasFood=false; //notes that the food has been eaten
+				$("#foodMachine").css("background",'url("style/images/emptyFood.png")');
+				obey(2);//inceases obey by 2
+				obeyColor();
+				vid("loyalty",2500);
+			}//action not possible if there is no food
+			break;
+		//attempts to enter the teleporter
+		case "enterTeleport":
+			if(obedience>=20){
+				$("#teleGate").animate({height : 0},200);
+				endGame("broken");
+			}else{
+				sfx("gate");
+				$("#teleGate").animate({height : 141},200);
+				obey(-10);//inceases obey by 2
+				obeyColor();
+				vid("onlyAuthorizedExists",3400);
+			}
+			break;
+		//fail jump
+		case "jumpZone":
+			//animation sequence for jumping off edge
+			canMove=false;
+			$("#chara").css("background",'url("style/images/charaR.gif")');
+			$("#chara").animate({left : 892}, 900, "linear",function(){
+				sfx("lift");
+				$("#chara").animate({bottom : 0}, 300,function(){
+					vid("selfHarm",3700);
+					obey(-10);
+					obeyColor();
+					$("#chara").animate({bottom : 225},1100);
+					$("#lift").animate({bottom : 210},1100,function(){
+						sfx("gate");
+						$("#liftGate").animate({height : 141},200,function(){
+							$("#jumpZone").css("left","-200px");
+							$("#chara").css("background",'url("style/images/charaStillR.gif")');
+							MAXX=904;
+							canMove=true;
+						});
+					});
+				});
+			});
+			break;
+		case "comms":
+			//you can never use this, it is just there to tempt you.
+			//also I didnt minize this for transparency
+			//but if you are reading my .js before playing through
+			//its going to kill the "fun" :(
+			vid("communications",3000);
+			obey(-10);
+			obeyColor();
+			break;
+	}
+}
+
+/*
+* Handle toggle of boolean values based on keypress
+*/
+function keyPress(keyNum,direction){
+	if(canMove){
+		if(keyNum==65||keyNum==97){
+			//press "a" key
+			if(direction=="down"){
+				//switches art
+				if(!left){
+					$("#chara").css("background",'url("style/images/charaL.gif")');
+				}
+				left=true;
+			}else{
+				left=false;
+			}
+		}else if(keyNum==100||keyNum==68){
+			//press "d" key
+			if(direction=="down"){
+				//switches art
+				if(!right){
+					$("#chara").css("background",'url("style/images/charaR.gif")');
+				}
+				right=true;
+			}else{
+				right=false;
+			}
+		}
+	}
+	//standign still animation
+	if(canMove&&!left&&!right){
  1. Owen Nelson author

    There are many examples I could make this point for in this script, but I'm using this one because it involves variable names that are particularly readable when considered on their own....

    If Python has taught me one thing about programming that I take with me to other languages it's that readability is a primary concern. This notion is formalized by http://legacy.python.org/dev/peps/pep-0008/ which is essentially a style guide for the entire python community. I'll spare you many of the specifics and focus in on the relevant to this line - expression spacing.

    Consider what you have:

    if(canMove&&!left&&!right){ ... }
    

    and the alternative when following PEP8

    if(canMove && !left && !right){ ... }
    

    I favor the 2nd as more readable since I can clearly see while scanning that the elements in this expression are all contributing to it via &&s and that 2 of the items are being negated via !. It takes me much longer to read the line as you wrote it and come away with the correct understanding.

+		//sets left stand still
+		if(keyNum==65||keyNum==97){
+			$("#chara").css("background",'url("style/images/charaStillL.gif")');
+		}else{ //sets right standstill
+			$("#chara").css("background",'url("style/images/charaStillR.gif")');
+		}
+	}else if(!left&&!right&&targetObject=="bed"){
+		$("#chara").css("background",'url("style/images/charaSleep.gif")');
+	}
+}
+
+/* 
+* resizeWin handles resizing of elements on pageload and window resize
+*/
+function resizeWin(){
+	//gets the width and height for easy reference
+	var height=$(window).height();
+	var width=$(window).width();
+	//if the aspect ration is less than 4/3
+	if(width/4<height/3){
+		//centers the video horizontally but makes it max height
+		$("#bgVideo").css({
+			"height" : height+"px",
+			"width" : ((height/3)*4)+"px",
+			"margin-left" : (width-((height/3)*4))/2+"px",
+			"margin-top" : "none"
+		});		
+	}else{
+		//centers the video vertically but at max width
+		$("#bgVideo").css({
+			"width" : "100%",
+			"height" : "auto",
+			"margin-left" : "none",
+			"margin-top" : (height-((width/4)*3))/2+"px"
+		});	
+	}
+}
+
+
+/*
+* Perpeturally animates up top screen line, then drops down, creating the illusion of endless animation
+*/
+function shiftScreenLines(){
+	//gets the window height for measurement
  1. Owen Nelson author

    I'd scold a team member for writing a comment like this. Comments should explore ideas that are not right in front of you. I like to tell my peeps to shine a light on the "why." Perhaps the "why" is "measurement" as you say, but I don't understand what you mean.

    In fact... I see that you capture the window height in a variable, note it in a comment, then... you don't use it for anything? This whole thing raises the wtf/minute (reference to the book "Clean Code" http://amzn.com/0132350882 or https://www.42lines.net/2012/07/06/clean-code-reducing-wtfs-per-minute/).

    I sometimes think about cinema/TV: a good performance is usually not one where the actor verbally announces actions they are carrying out as they execute them. Dialogue is usually layered on top of actions to deliver deeper meaning. Imagine your code as a scene on TV. Is it a corny sitcom that makes you groan, or is it an episode of Cosmos that enriches your understanding of the universe being laid out in front of you.

+	var height=$(window).height();
+	$("#firstLine").animate({marginTop : -80}, 1000,"linear", function(){
+		shiftScreenLines();
+		$("#firstLine").css("margin-top","0px");
  1. Owen Nelson author

    Generally with a recursive function, you either frame the recursion call in some kind of flow control (if, while, etc) or the recursion call is at the end of the routine (making an infinite loop). I don't see when this line of code will ever execute as written.

+	});
+}
+
+
+/* Methods control the opening and closing of shutters
+*
+*/
+function openShutters(){
+	$("#shutters").css("opacity","0.9");
+	$("#shutters div div").animate({height : 0} ,500);
+}
+function closeShutters(){
+	$("#shutters div div").animate({height : ($(window).height()*.11)} ,500,function(){
+		$("#shutters div div").css("height","100%");
+	});
+}
+
+/* 
+* Shifts the current video to string
+* @string newVid
+* @int time
+*/
+function vid(newVid,time){
+	$("#mp4").attr("src","video/"+newVid+".mp4");
+	$("#ogv").attr("src","video/"+newVid+".ogv");
+	$("#bgVideo").load(); //sets the new video and loads it
+	setTimeout(function(){eyeVid();},time+110);
+}
+//defaults back to eyeball video
+function eyeVid(){
+	$("#mp4").attr("src","video/eyeball.mp4");
+	$("#ogv").attr("src","video/eyeball.ogv");
+	$("#bgVideo").load();
+	//color changes with obedience
+	obeyColor();
+}
+
+//changes color of items based on obey score
+function obeyColor(){
+	if(obedience>=20){
+		$("#videoColor").css("background","#009");
+		$(".interface").css("color","#335");
+	}else if(obedience>=0){
+		$("#videoColor").css("background","#0f0");
+		$(".interface").css("color","#353");
+	}else{
+		$("#videoColor").css("background","#f00");
+		$(".interface").css("color","#533");
+	}
+}
+
+/*
+* Handles the shifting of obey values
+*/
+function obey(mod){
+	obedience+=mod;
+	if(obedience>=20){
+		if($("#teleGate").height()>0){
+			sfx("gate");
+			$("#teleGate").animate({height : 0},200);
+		}
+	}
+	console.log(obedience);
+	//
+	var dispObey=obedience;
+	if(dispObey<0){
+		dispObey=dispObey*-1;
+		$("#obey span:first-child").html("-");
+	}else{
+		$("#obey span:first-child").html("+");
+	}
+	//adds zero if less than 2 digits
+	if(dispObey<10){
+		dispObey="0"+dispObey;
+	}
+	//the actual score display
+	$("#obey span:last-child").html(dispObey);
+}
+
+/*
+* Plays a SFX once
+*/
+function sfx(mp3File){
+	var embedElem = "<embed src='audio/"+mp3File+".mp3' autostart='true' loop='false'></embed>";
+	$("#sfxControl").html(embedElem);
+}
+
+//hanldes animation for teleporter
+function animtele(){
+	var telDelay=0;
+	$("#teleporter div").each(function(){
+		telDelay++;
+		$(this).delay(telDelay*300).animate({
+			opacity : 1,
+			bottom : 200,
+			width : 0,
+			left : 70
+		}, 500,function(){
+			$(this).css({
+				"bottom" : "0px",
+				"opacity" : "0",
+				"width" : "120",
+				"left" : "10px"
+			});
+			if($(this).attr("id")=="lastBeam"){
+				animtele();
+			}
+		});
+	});
+}
+
+/*
+* End Game states
+* @string reason
+* either starvation or broken
+*/
+function endGame(reason){
+	//ends games
+	ingame=false;
+	$("#ending").css("opacity","0");
+	$("#ending").css("display","block");
+	$("#obey").animate({opacity : 0},200);
+	//runs starvation ending
+	if(reason=="starvation"){
+		$("#dead").css("display","block");
+		canMove=false;
+		$("#actionMark").css("display","none");
+		$("#chara").css("background",'url("style/images/charaSleep.gif")');
+		$("#chara").animate({bottom : 200},100,function(){
+			vid("perished",6300);
+			$("#chara").delay(1000).animate({opacity : 0},2000,function(){
+				$("#ending p").html("By refusing to eat you proved your strength to yourself, but ultimately perished. Your death brought no change to the world.");
+				$("#ending").delay(300).animate({opacity : 1},500);
+			});
+		});
+	}
+	//ending for being broken
+	if(reason=="broken"){
+		$("#exemp").css("display","block");
+		canMove=false;
+		$("#actionMark").css("display","none");
+		$("#chara").css("background",'url("style/images/charaL.gif")');
+		$("#chara").animate({left : 100}, 1900, "linear",function(){
+			sfx("teleport");
+			$("#chara").css("background",'url("style/images/charaStillL.gif")');
+			$("#chara").delay(400).animate({opacity : 0}, 400,function(){
+				$("#ending p").html("You have been permitted to leave your Island. You have learned to obey, and to listen. You are an exemplary citizen.");
+				$("#ending").delay(300).animate({opacity : 1},500);
+			});
+		});
+	}
+}
+})();
+
+/* defaults */
+* {
+  margin: 0px;
+  padding: 0px;
+  border: 0px;
+  font-weight: normal;
+  text-indent: 0px;
+  text-decoration: none; }
+
+header, section, footer, aside, nav, article, figure {
+  display: block; }
+
+.clear {
+  clear: both; }
+
+* {
+  margin: 0px;
+  padding: 0px;
+  border: 0px;
+  font-family: "VT323";
+  color: #335533; }
+
+body {
+  background: #000;
+  min-width: 960px; }
+
+#contain {
+  display: block;
+  position: fixed;
+  top: 0px;
+  left: 0px;
+  width: 100%;
+  height: 100%;
+  overflow: hidden; }
+
+#bgVideo {
+  display: block;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  width: 100%;
+  height: 100%; }
+
+#videoEffects {
+  display: block;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  width: 100%;
+  height: 100%; }
+  #videoEffects #videoColor {
+    display: block;
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 100%;
+    height: 100%;
+    background: #0f0;
+    opacity: 0.3; }
+  #videoEffects #runLines {
+    display: block;
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 100%;
+    height: 100%; }
+    #videoEffects #runLines .line {
+      display: block;
+      float: left;
+      width: 100%; }
+      #videoEffects #runLines .line svg {
+        display: block;
+        width: 100%;
+        opacity: 0.35; }
+        #videoEffects #runLines .line svg line {
+          stroke-width: 4px;
+          stroke: #ccc; }
+  #videoEffects #static {
+    display: block;
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 100%;
+    height: 100%;
+    background: url("images/static.gif");
+    opacity: 0.05; }
+  #videoEffects #shutters {
+    display: block;
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    width: 100%;
+    height: 100%; }
+    #videoEffects #shutters div {
+      display: block;
+      float: left;
+      width: 100%;
+      height: 11%; }
+      #videoEffects #shutters div div {
+        display: block;
+        float: left;
+        width: 100%;
+        height: 100%;
+        background: #111; }
+
+#gamePlay {
+  display: block;
+  position: relative;
+  margin: 0px auto;
+  top: 0px;
+  left: 0px;
+  top: 0px;
+  left: 0px;
+  width: 960px;
+  height: 100%; }
+  #gamePlay #island {
+    width: 960px;
+    height: 538px;
+    background: url("images/baseIsland.png");
+    position: absolute;
+    bottom: 0%; }
+    #gamePlay #island #teleporter {
+      display: block;
+      position: absolute;
+      bottom: 198px;
+      width: 154px;
+      height: 53px;
+      left: 48px;
+      background: url("images/teleBase.png"); }
+      #gamePlay #island #teleporter div {
+        display: block;
+        position: absolute;
+        left: 10px;
+        bottom: 0px;
+        width: 120px;
+        height: 5px;
+        -webkit-border-radius: 100px;
+        -moz-border-radius: 100px;
+        -ms-border-radius: 100px;
+        -o-border-radius: 100px;
+        border-radius: 100px;
+        border: 1px solid #0ff;
+        opacity: 0; }
+    #gamePlay #island #islandTech {
+      position: relative; }
+    #gamePlay #island .gate {
+      display: block;
+      position: absolute;
+      bottom: 225px;
+      width: 9px;
+      height: 0px;
+      background: url("images/gate.png"); }
+    #gamePlay #island #teleGate {
+      left: 220px; }
+    #gamePlay #island #lift {
+      display: block;
+      position: absolute;
+      width: 85px;
+      height: 19px;
+      right: -10px;
+      bottom: -20px;
+      background: url("images/lift.png"); }
+      #gamePlay #island #lift .gate {
+        bottom: 14px;
+        right: 6px; }
+  #gamePlay #interactObjects {
+    display: block;
+    width: 960px;
+    height: 538px;
+    position: absolute;
+    bottom: 0px; }
+    #gamePlay #interactObjects #anyObject, #gamePlay #interactObjects #bed, #gamePlay #interactObjects #foodMachine, #gamePlay #interactObjects #comms, #gamePlay #interactObjects #jumpZone, #gamePlay #interactObjects #enterTeleport {
+      display: block;
+      position: absolute;
+      bottom: 225px; }
+    #gamePlay #interactObjects #bed {
+      width: 120px;
+      height: 60px;
+      left: 560px;
+      background: url("images/bed.png"); }
+    #gamePlay #interactObjects #foodMachine {
+      width: 90px;
+      height: 140px;
+      left: 400px;
+      background: url("images/emptyFood.png"); }
+    #gamePlay #interactObjects #comms {
+      width: 54px;
+      height: 90px;
+      left: 779px;
+      background: url("images/comms.png"); }
+    #gamePlay #interactObjects #jumpZone {
+      width: 3px;
+      height: 30px;
+      left: 880px; }
+    #gamePlay #interactObjects #enterTeleport {
+      width: 3px;
+      height: 30px;
+      left: 240px; }
+  #gamePlay #chara, #gamePlay #clickArea {
+    display: block;
+    position: absolute;
+    bottom: 225px;
+    width: 40px;
+    height: 90px;
+    left: 600px;
+    background: url("images/charaSleep.gif"); }
+    #gamePlay #chara #actionMark, #gamePlay #clickArea #actionMark {
+      display: block;
+      width: 20px;
+      height: 40px;
+      background: url("images/exclaim.png");
+      position: absolute;
+      top: -45px;
+      left: 10px; }
+    #gamePlay #chara #zzz, #gamePlay #clickArea #zzz {
+      display: block;
+      width: 40px;
+      height: 40px;
+      position: absolute;
+      top: -45px;
+      left: -45px; }
+      #gamePlay #chara #zzz span, #gamePlay #clickArea #zzz span {
+        position: absolute;
+        top: 0px;
+        left: 0px;
+        font-size: 48px;
+        color: #fff;
+        opacity: 0.1; }
+        #gamePlay #chara #zzz span + span, #gamePlay #clickArea #zzz span + span {
+          top: 20px;
+          left: 20px;
+          font-size: 30px; }
+          #gamePlay #chara #zzz span + span + span, #gamePlay #clickArea #zzz span + span + span {
+            top: 34px;
+            left: 34px;
+            font-size: 20px; }
+  #gamePlay #bedFoot {
+    display: block;
+    position: absolute;
+    bottom: 225px;
+    background: url("images/bedFoot.png") bottom center;
+    width: 120px;
+    height: 40px;
+    left: 560px; }
+  #gamePlay #clickArea {
+    background: none; }
+    #gamePlay #clickArea:hover {
+      cursor: pointer; }
+
+#clock {
+  display: block;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  width: 100%;
+  height: 100%;
+  width: 0px;
+  height: 0px;
+  font-size: 34px;
+  top: 10px;
+  left: 10px; }
+
+#obey {
+  display: block;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  width: 100%;
+  height: 100%;
+  top: 10px;
+  left: 50%;
+  width: 250px;
+  height: 0px;
+  margin-left: -125px;
+  font-size: 34px; }
+
+#volumeControl {
+  display: block;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  width: 100%;
+  height: 100%;
+  top: 10px;
+  left: auto;
+  right: 10px;
+  width: 35px;
+  height: 35px;
+  background: url("images/audio.png");
+  cursor: pointer; }
+
+#intro, #ending {
+  display: block;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  width: 100%;
+  height: 100%;
+  background: url("images/menuBG.jpg") top center no-repeat black;
+  overflow: hidden;
+  display: block; }
+  #intro h1, #ending h1 {
+    display: block;
+    margin: 50px auto;
+    width: 800px;
+    color: #72bbce;
+    font-family: "Yanone Kaffeesatz", sans-serif;
+    font-weight: light;
+    font-size: 37px; }
+  #intro p, #ending p {
+    display: block;
+    width: 600px;
+    margin: 0px auto;
+    color: #72bbce;
+    font-family: "Yanone Kaffeesatz", sans-serif;
+    font-weight: light;
+    font-size: 37px; }
+  #intro div, #ending div {
+    display: none;
+    width: 278px;
+    margin: 0px auto;
+    font-size: 46px;
+    color: #000;
+    background: #72bbce;
+    cursor: pointer;
+    font-family: "Yanone Kaffeesatz", sans-serif;
+    text-align: center;
+    padding: 8px 0px 10px 0px; }
+    #intro div:hover, #ending div:hover {
+      background: #000;
+      color: #fff; }
+  #intro #controls, #ending #controls {
+    display: block;
+    width: 659px;
+    margin: 0px auto;
+    margin-top: -100px; }
+
+#ending {
+  display: none; }
+  #ending #dead {
+    display: none;
+    position: absolute;
+    left: 50%;
+    top: 330px;
+    margin-left: -170px; }
+  #ending #exemp {
+    display: none;
+    position: absolute;
+    left: 50%;
+    top: 240px;
+    margin-left: -80px; }
+
+.hidden {
+  display: none; }
+//shifts the hue within a given rannge
+//object- string for object query eg: "#id" , ".class", "tag"
+//min - starting hue 0-259
+//max - ending hue 1-360
+//s - saturation value
+//l - light amount
+//prop - property to shift, eg "fill" "stop-color"
+function hueShift(object,min,max,s,l,speed,prop){
  1. Owen Nelson author

    I'd avoid naming vars and parameters object since this technically clashes with the root object ([object Object]) and could be confusing as the code-base increases in complexity.

+	//stores the target object and its name as string
+	var oObj=object;
+	object=$(object);
+	var newHue=parseInt(object.attr("onHue"));
+	//checks if a default starting value is given
+	if(object.attr("onHue")==""||object.attr("onHue")==undefined){
  1. Owen Nelson author

    Best practice is to rely on the identity operator, rather than equality (see http://stackoverflow.com/a/5447072/140396 for a little background on this). Generally speaking, relying on an implicit type-cast is less desirable than relying on a lack of one. JavaScript is one of the most ludicrous languages (see https://www.destroyallsoftware.com/talks/wat) when it comes to how it decides how to cast vals, so it's best to take that job away from the interpreter.

    When comparing to undefined, always use if (thing === undefined) { ... }. In other situations, you might only care about truthy vs falsy, in which cause consider casting the value to bool yourself before comparing, for example if (!!thing) { ... }

+		object.attr("onHue",min);
+		object.attr("hueDir","up");
+		newHue=parseInt(object.attr("onHue"));
+	}
+	//checks direction
+	if(object.attr("hueDir")=="up"){
+		newHue++;
+	}else{
+		newHue--;
+	}
+	//changes direction of hue shift if needed
+	if(newHue>=max){
+		object.attr("hueDir","down");
+	}
+	if(newHue<=min){
+		object.attr("hueDir","up");
+	}
+	//sets the new hue value
+	object.attr("onHue",newHue);
+	//makes new property get hue value
+	object.css(prop,"hsl("+newHue+","+s+","+l+")");
+	//repeats
+	setTimeout(function(){
+		hueShift(oObj,min,max,s,l,speed,prop);
+	},speed);
+}
+
+function fullHue(object,s,l,speed,prop){
+	//stores the target object and its name as string
+	var oObj=object;
+	object=$(object);
+	var newHue=parseInt(object.attr("onHue"));
+	//checks if a default starting value is given
+	if(object.attr("onHue")==""||object.attr("onHue")==undefined){
+		object.attr("onHue","0");
+		object.attr("hueDir","up");
+		newHue=parseInt(object.attr("onHue"));
+	}
+	//checks direction
+	if(object.attr("hueDir")=="up"){
+		newHue++;
+	}else{
+		newHue--;
+	}
+
+	//sets the new hue value
+	object.attr("onHue",newHue);
+	//makes new property get hue value
+	object.css(prop,"hsl("+newHue+","+s+","+l+")");
+	//repeats
+	setTimeout(function(){
+		fullHue(oObj,s,l,speed,prop);
+	},speed);
+}
+
+
+//contiuously spins an object
+//takes a string query for what to spin
+function spinObj(object,speed){
+	$(object).each(function(){
+		var targetObj=$(this);
+		//gets the objects current spin
+		var rotDeg=$(targetObj).attr("curSpin");
+		if(rotDeg==""||rotDeg=="360"||rotDeg==undefined){
+			rotDeg=0;
+		}
+		//turns to int
+		rotDeg=parseInt(rotDeg);
+		//increments angle
+		rotDeg+=2;
+		//updates attr
+		$(targetObj).attr("curSpin",rotDeg);
+		//applies the spin
+		$(targetObj).css({ 
+			transform: 'rotate(' + rotDeg + 'deg)'
+		});
+	});
+	
+	//repeats
+	setTimeout(function(){spinObj(object,speed)},speed);
+}
+
+//contiuously spins an object counter clockwise
+//takes a string query for what to spin
+function backspinObj(object,speed){
+	$(object).each(function(){
+		var targetObj=$(this);
+		//gets the objects current spin
+		var rotDeg=$(targetObj).attr("curSpin");
+		if(rotDeg==""||rotDeg=="0"||rotDeg==undefined){
+			rotDeg=360;
+		}
+		//turns to int
+		rotDeg=parseInt(rotDeg);
+		//increments angle
+		rotDeg-=2;
+		//updates attr
+		$(targetObj).attr("curSpin",rotDeg);
+		//applies the spin
+		$(targetObj).css({ 
+			transform: 'rotate(' + rotDeg + 'deg)'
+		});
+	});
+	
+	//repeats
+	setTimeout(function(){backspinObj(object,speed)},speed);
+}
+
+
+/**the starts of an attr animate function? **/
+$.fn.animateAttr = function (attr,end,speed,completion) {
+	var dist=parseInt(this.attr(attr))-end;
+	if(dist<0){
+		dist=dist*-1;
+	}
+	var steps=Math.floor(dist/10); //distance to travel divded by 10 px
+	function animAttr(targ,steps,onStep,completion){
+		onStep++;
+		var curAttr=parseInt(targ.attr(attr));
+		if(end>curAttr){
+			curAttr+=10;
+		}else if(end<curAttr){
+			curAttr-=10;
+		}
+		if(onStep==steps){
+			//calls passthrough
+			completion();
+		}else{
+			//sets att
+			targ.attr(attr,curAttr);
+			setTimeout(function(){
+				animAttr(targ,steps,onStep,completion);
+			},20);
+		}
+		
+	}
+	animAttr(this,steps,0,completion);
+};
  1. Owen Nelson author

    Last thing I'd say about this script is that it looks like it's meant to be a stand-alone collection of animation helpers, and as such should be written to be portable. One concern I'd have is that all these functions are defined in the global scope (which is to say they are attached to the window object.

    To reduce the chance of collisions, best practice is to claim a namespace and "export" your functions with it.

    /** Something to note on this closure: I'm passing in the global window object and renaming it "globals" on the inside.
     *  This is an effort to clearly communicate what it is we are doing. Rather than accidentally polluting the global 
     *  namespace, we are purposefully attaching things to it.
     */
    (function (globals) {
      // inside this closure, we can export our namespace and make it available to other scripts, etc
    
      globals.twa = globals.twa || {};  // if twa is already defined, we'll build on top of it, otherwise start fresh
    
      globals.twa.arts = {
        hueShift: function() {},
        fullHue: function() {},
        spinObj: function() {},
        backspinObj: function() {}
      };
    
    
    })(window);