Skip to content

Instantly share code, notes, and snippets.

@brandenhall
Created December 7, 2011 20:28
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save brandenhall/1444489 to your computer and use it in GitHub Desktop.
Save brandenhall/1444489 to your computer and use it in GitHub Desktop.
Secret Knock Detector module
//-----------------------------------------------------------------------------
// Secret Knock Detector
// Based on http://grathio.com/2009/11/secret_knock_detecting_door_lock/
//-----------------------------------------------------------------------------
// Created by Branden Hall, bhall at automatastudios dot com
//-----------------------------------------------------------------------------
// Copyright (c) Automata Studios Ltd.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer. Redistributions in binary
// form must reproduce the above copyright notice, this list of conditions and
// the following disclaimer in the documentation and/or other materials
// provided with the distribution. Neither the name of Automata Studios nor
// the names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------
define(function(require) {
var successCallback;
var secretKnock;
var knockInput = [];
var reject = 0.25;
var averageReject = 0.15;
var fadeTime = 150;
var completeTime = 1200;
var completeId;
var lastNow;
var getEventX;
var getEventY;
function enableKnock(callback, knock) {
successCallback = callback;
secretKnock = knock;
if ('ontouchstart' in document.documentElement) {
document.addEventListener("touchstart", detectKnock);
getEventX = function(e) {
return event.touches[0].pageX;
}
getEventY = function(e) {
return event.touches[0].pageY;
}
} else {
document.addEventListener("click", detectKnock);
getEventX = function(e) {
return event.pageX;
}
getEventY = function(e) {
return event.pageY;
}
}
}
function disableKnock() {
document.removeEventListener("touchstart", detectKnock);
document.removeEventListener("click", detectKnock);
clearInterval(completeId);
}
function detectKnock(e) {
var now = new Date().getTime();
knockInput.push({time:(new Date()).getTime(),
x:getEventX(e),
y:getEventY(e)});
clearTimeout(completeId);
completeId = setTimeout(completeKnock, 1200);
e.preventDefault();
return false;
}
function completeKnock() {
if (validateKnock()) {
successCallback();
}
knockInput = [];
}
function validateKnock() {
var maxTime = 0;
var times = [];
var time;
var result = true;
var dist, i;
var timeDiff, totalDiff;
// make sure they're the same length
if (secretKnock.length == knockInput.length) {
// first check the position of the knocks and gather the times
for (i=0; i<knockInput.length && result; ++i) {
dist = Math.sqrt(Math.pow(knockInput[i].x - secretKnock[i].x, 2) +
Math.pow(knockInput[i].y - secretKnock[i].y, 2));
if (dist < secretKnock[i].radius) {
if (i > 0) {
time = knockInput[i].time - knockInput[i - 1].time;
times.push(time);
maxTime = Math.max(maxTime, time);
}
} else {
result = false;
}
}
for (i=0; i<times.length && result; ++i) {
timeDiff = Math.abs((times[i] / maxTime) - secretKnock[i].delay);
totalDiff += timeDiff;
if (timeDiff > reject) {
result = false;
}
}
if (result && totalDiff/times.length > averageReject) {
result = false;
}
} else {
result = false;
}
return result;
}
var pub = {};
pub.enableKnock = enableKnock;
pub.disableKnock = disableKnock;
return pub;
});
// Shave-and-a-haircut, two bits! In the upper left corner
var secret = [{delay:0.5, x:0, y:0, radius:200},
{delay:0.25, x:0, y:0, radius:200},
{delay:0.25, x:0, y:0, radius:200},
{delay:0.5, x:0, y:0, radius:200},
{delay:1, x:0, y:0, radius:200},
{delay:0.5, x:0, y:0, radius:200},
{delay:0, x:0, y:0, radius:200}];
function allowInside() {
alert("Good job!");
}
knock.enableKnock(allowInside, secret);
@bousquetcm
Copy link

Original was not working. Error in firebug was "define is not defined". Reworked the class to be:

// JavaScript Document

//-----------------------------------------------------------------------------
// Secret Knock Detector
// Based on http://grathio.com/2009/11/secret_knock_detecting_door_lock/
//-----------------------------------------------------------------------------
// Created by Branden Hall, bhall at automatastudios dot com
// Updated by Christopher Bousquet, ProtoStatus - www.protostatus.com
//-----------------------------------------------------------------------------
// Copyright (c) Automata Studios Ltd.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:

// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer. Redistributions in binary
// form must reproduce the above copyright notice, this list of conditions and
// the following disclaimer in the documentation and/or other materials
// provided with the distribution. Neither the name of Automata Studios nor
// the names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

var knock = {
successCallback:'',
secretKnock:'',
knockInput:[],
reject:0.25,
averageReject:0.15,
fadeTime:150,
completeTime:1200,
completeId:'',
lastNow:'',
getEventX:'',
getEventY:'',
enableKnock:function (callback, knock) {
this.successCallback = callback;
this.secretKnock = knock;

    if ('ontouchstart' in document.documentElement) {
        document.addEventListener("touchstart", this.detectKnock);
        getEventX = function(e) {
            return event.touches[0].pageX;
        }
        getEventY = function(e) {
            return event.touches[0].pageY;
        }           

    } else {
        document.addEventListener("click", this.detectKnock);
        getEventX = function(event) {
            return event.pageX;
        }
        getEventY = function(event) {
            return event.pageY;
        }               
    }
},
disableKnock:function () {
    document.removeEventListener("touchstart", this.knock.detectKnock);
    document.removeEventListener("click", this.knock.detectKnock);
    clearInterval(completeId);
},
detectKnock:function (e) {
    var now = new Date().getTime();
    e.view.knock.knockInput.push({time:(new Date()).getTime(), 
                     x:getEventX(e),
                     y:getEventY(e)});

    clearTimeout(e.view.knock.completeId);
    e.view.knock.completeId = setTimeout(e.view.knock.completeKnock, 1200);
    e.preventDefault();
    return false;
},
completeKnock:function () {
    if (this.knock.validateKnock()) {
        this.knock.successCallback();
    }
    this.knock.knockInput = [];
},
validateKnock:function () {
    var maxTime = 0;
    var times = [];
    var time;
    var result = true;
    var dist, i;
    var timeDiff, totalDiff;

    // make sure they're the same length
    if (this.secretKnock.length == this.knockInput.length) {
        // first check the position of the knocks and gather the times
        for (i=0; i<this.knockInput.length && result; ++i) {
            dist = Math.sqrt(Math.pow(this.knockInput[i].x - this.secretKnock[i].x, 2) + 
                             Math.pow(this.knockInput[i].y - this.secretKnock[i].y, 2));
            if (dist < this.secretKnock[i].radius) {

                if (i > 0) {
                    time = this.knockInput[i].time - this.knockInput[i - 1].time;
                    times.push(time);
                    maxTime = Math.max(maxTime, time);
                }

            } else {
                result = false;
            }
        }

        for (i=0; i<times.length && result; ++i) {
            timeDiff = Math.abs((times[i] / maxTime) - this.secretKnock[i].delay);
            totalDiff += timeDiff;

            if (timeDiff > this.reject) {
                result = false;
            }
        }

        if (result && totalDiff/times.length > this.averageReject) {
            result = false;
        }

    } else {
        result = false;
    }

    return result;
}

};

@bousquetcm
Copy link

@brandenhall
Copy link
Author

brandenhall commented Dec 15, 2011 via email

@bousquetcm
Copy link

Ahhhh.... That explains it. I didn't see a reference to a required library above. Regardless, now we have one that works with library and one without a library. Variety is the spice of life!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment