/* Copyright 2014 Nebez Briefkani floppybird - main.js Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ var debugmode = false; var states = Object.freeze({ SplashScreen: 0, GameScreen: 1, ScoreScreen: 2 }); var currentstate; var gravity = 0.25; var velocity = 0; var position = 180; var rotation = 0; var jump = -4.6; var flyArea = $("#flyarea").height(); var score = 0; var highscore = 0; var pipeheight = 90; var pipewidth = 52; var pipes = new Array(); var replayclickable = false; //sounds var volume = 30; var soundJump = new buzz.sound("assets/sounds/sfx_wing.ogg"); var soundScore = new buzz.sound("assets/sounds/sfx_point.ogg"); var soundHit = new buzz.sound("assets/sounds/sfx_hit.ogg"); var soundDie = new buzz.sound("assets/sounds/sfx_die.ogg"); var soundSwoosh = new buzz.sound("assets/sounds/sfx_swooshing.ogg"); buzz.all().setVolume(volume); //loops var loopGameloop; var loopPipeloop; $(document).ready(function() { if(window.location.search == "?debug") debugmode = true; if(window.location.search == "?easy") pipeheight = 200; //get the highscore var savedscore = getCookie("highscore"); if(savedscore != "") highscore = parseInt(savedscore); //start with the splash screen //showSplash(); }); function getCookie(cname) { var name = cname + "="; var ca = document.cookie.split(';'); for(var i=0; i= $("#land").offset().top) { playerDead(); return; } //have they tried to escape through the ceiling? :o var ceiling = $("#ceiling"); if(boxtop <= (ceiling.offset().top + ceiling.height())) position = 0; //we can't go any further without a pipe if(pipes[0] == null) return; //determine the bounding box of the next pipes inner area var nextpipe = pipes[0]; var nextpipeupper = nextpipe.children(".pipe_upper"); var pipetop = nextpipeupper.offset().top + nextpipeupper.height(); var pipeleft = nextpipeupper.offset().left - 2; // for some reason it starts at the inner pipes offset, not the outer pipes. var piperight = pipeleft + pipewidth; var pipebottom = pipetop + pipeheight; if(debugmode) { var boundingbox = $("#pipebox"); boundingbox.css('left', pipeleft); boundingbox.css('top', pipetop); boundingbox.css('height', pipeheight); boundingbox.css('width', pipewidth); } //have we gotten inside the pipe yet? if(boxright > pipeleft) { //we're within the pipe, have we passed between upper and lower pipes? if(boxtop > pipetop && boxbottom < pipebottom) { //yeah! we're within bounds } else { //no! we touched the pipe playerDead(); return; } } //have we passed the imminent danger? if(boxleft > piperight) { //yes, remove it pipes.splice(0, 1); //and score a point playerScore(); } } //Handle space bar $(document).keydown(function(e){ //space bar! if(e.keyCode == 32) { //in ScoreScreen, hitting space should click the "replay" button. else it's just a regular spacebar hit if(currentstate == states.ScoreScreen) $("#replay").click(); else screenClick(); } }); //Handle mouse down OR touch start if("ontouchstart" in window) $(document).on("touchstart", screenClick); else $(document).on("mousedown", screenClick); function screenClick() { if(currentstate == states.GameScreen) { playerJump(); } else if(currentstate == states.SplashScreen) { startGame(); } } function playerJump() { velocity = jump; //play jump sound soundJump.stop(); soundJump.play(); } function setBigScore(erase) { var elemscore = $("#bigscore"); elemscore.empty(); if(erase) return; var digits = score.toString().split(''); for(var i = 0; i < digits.length; i++) elemscore.append("" + digits[i] + ""); } function setSmallScore() { var elemscore = $("#currentscore"); elemscore.empty(); var digits = score.toString().split(''); for(var i = 0; i < digits.length; i++) elemscore.append("" + digits[i] + ""); } function setHighScore() { var elemscore = $("#highscore"); elemscore.empty(); var digits = highscore.toString().split(''); for(var i = 0; i < digits.length; i++) elemscore.append("" + digits[i] + ""); } function setMedal() { var elemmedal = $("#medal"); elemmedal.empty(); if(score < 10) //signal that no medal has been won return false; if(score >= 10) medal = "bronze"; if(score >= 20) medal = "silver"; if(score >= 30) medal = "gold"; if(score >= 40) medal = "platinum"; elemmedal.append('' + medal +''); //signal that a medal has been won return true; } function playerDead() { //stop animating everything! $(".animated").css('animation-play-state', 'paused'); $(".animated").css('-webkit-animation-play-state', 'paused'); //drop the bird to the floor var playerbottom = $("#player").position().top + $("#player").width(); //we use width because he'll be rotated 90 deg var floor = flyArea; var movey = Math.max(0, floor - playerbottom); $("#player").transition({ y: movey + 'px', rotate: 90}, 1000, 'easeInOutCubic'); //it's time to change states. as of now we're considered ScoreScreen to disable left click/flying currentstate = states.ScoreScreen; //destroy our gameloops clearInterval(loopGameloop); clearInterval(loopPipeloop); loopGameloop = null; loopPipeloop = null; //mobile browsers don't support buzz bindOnce event if(isIncompatible.any()) { //skip right to showing score showScore(); } else { //play the hit sound (then the dead sound) and then show score soundHit.play().bindOnce("ended", function() { soundDie.play().bindOnce("ended", function() { showScore(); }); }); } } function showScore() { //unhide us $("#scoreboard").css("display", "block"); //remove the big score setBigScore(true); //have they beaten their high score? if(score > highscore) { //yeah! highscore = score; //save it! setCookie("highscore", highscore, 999); } //update the scoreboard setSmallScore(); setHighScore(); var wonmedal = setMedal(); //SWOOSH! soundSwoosh.stop(); soundSwoosh.play(); //show the scoreboard $("#scoreboard").css({ y: '40px', opacity: 0 }); //move it down so we can slide it up $("#replay").css({ y: '40px', opacity: 0 }); $("#scoreboard").transition({ y: '0px', opacity: 1}, 600, 'ease', function() { //When the animation is done, animate in the replay button and SWOOSH! soundSwoosh.stop(); soundSwoosh.play(); $("#replay").transition({ y: '0px', opacity: 1}, 600, 'ease'); //also animate in the MEDAL! WOO! if(wonmedal) { $("#medal").css({ scale: 2, opacity: 0 }); $("#medal").transition({ opacity: 1, scale: 1 }, 1200, 'ease'); } }); //make the replay button clickable replayclickable = true; } $("#replay").click(function() { //make sure we can only click once if(!replayclickable) return; else replayclickable = false; //SWOOSH! soundSwoosh.stop(); soundSwoosh.play(); //fade out the scoreboard $("#scoreboard").transition({ y: '-40px', opacity: 0}, 1000, 'ease', function() { //when that's done, display us back to nothing $("#scoreboard").css("display", "none"); //start the game over! newGame(); }); }); function playerScore() { score += 1; //play score sound soundScore.stop(); soundScore.play(); setBigScore(); } function updatePipes() { //Do any pipes need removal? $(".pipe").filter(function() { return $(this).position().left <= -100; }).remove() //add a new pipe (top height + bottom height + pipeheight == flyArea) and put it in our tracker var padding = 80; var constraint = flyArea - pipeheight - (padding * 2); //double padding (for top and bottom) var topheight = Math.floor((Math.random()*constraint) + padding); //add lower padding var bottomheight = (flyArea - pipeheight) - topheight; var newpipe = $('
'); $("#flyarea").append(newpipe); pipes.push(newpipe); } var isIncompatible = { Android: function() { return navigator.userAgent.match(/Android/i); }, BlackBerry: function() { return navigator.userAgent.match(/BlackBerry/i); }, iOS: function() { return navigator.userAgent.match(/iPhone|iPad|iPod/i); }, Opera: function() { return navigator.userAgent.match(/Opera Mini/i); }, Safari: function() { return (navigator.userAgent.match(/OS X.*Safari/) && ! navigator.userAgent.match(/Chrome/)); }, Windows: function() { return navigator.userAgent.match(/IEMobile/i); }, any: function() { return (isIncompatible.Android() || isIncompatible.BlackBerry() || isIncompatible.iOS() || isIncompatible.Opera() || isIncompatible.Safari() || isIncompatible.Windows()); } }; var pipe = $('
'); $("#flyarea").append(pipe); var gameSpeed = 20 var playing = false; function newGame() { if (playing) { playing = false; setTimeout(newGame, gameSpeed); return; } $('#loading').addClass('active'); fetch('/play').then(res => res.json()).then(function(states) { playing = true; $('#loading').removeClass('active'); $("#player").css({ rotate: 0 }); var i = 0; setTimeout(function p() { if (!playing) return; var state = states[i]; if (!state || state.lost) { $(".animated").css('animation-play-state', 'paused'); $(".animated").css('-webkit-animation-play-state', 'paused'); //drop the bird to the floor var playerbottom = $("#player").position().top + $("#player").width(); //we use width because he'll be rotated 90 deg var floor = flyArea; var movey = Math.max(0, floor - playerbottom); $("#player").transition({ top: movey + 'px', rotate: 90}, 1000, 'easeInOutCubic'); if(isIncompatible.any()) { //skip right to showing score showScore(); } else { //play the hit sound (then the dead sound) and then show score soundHit.play().bindOnce("ended", function() { soundDie.play().bindOnce("ended", function() { showScore(); }); }); } return; } score = Math.round((state.score - i * 0.1) / 10); setBigScore(); $("#player").css({ top: state.bird.y, left: state.bird.x }); $(".pipe").css({ left: state.wall.x }); $(".pipe_upper").css({ height: state.wall.gate.y }); $(".pipe_lower").css({ height: 200 - state.wall.gate.y - state.wall.gate.height }); i++; setTimeout(p, gameSpeed) }, gameSpeed) }); } $('#new').click(newGame) $('#faster').click(function() { gameSpeed = Math.max(5, gameSpeed - 5); }); $('#slower').click(function() { gameSpeed = Math.min(50, gameSpeed + 5); }); newGame();