superject/web/shuffle/deck.js

982 lines
22 KiB
JavaScript
Raw Normal View History

'use strict';
var Deck = (function () {
'use strict';
var ticking;
var animations = [];
function animationFrames(delay, duration) {
var now = Date.now();
// calculate animation start/end times
var start = now + delay;
var end = start + duration;
var animation = {
start: start,
end: end
};
// add animation
animations.push(animation);
if (!ticking) {
// start ticking
ticking = true;
requestAnimationFrame(tick);
}
var self = {
start: function start(cb) {
// add start callback (just one)
animation.startcb = cb;
return self;
},
progress: function progress(cb) {
// add progress callback (just one)
animation.progresscb = cb;
return self;
},
end: function end(cb) {
// add end callback (just one)
animation.endcb = cb;
return self;
}
};
return self;
}
function tick() {
var now = Date.now();
if (!animations.length) {
// stop ticking
ticking = false;
return;
}
for (var i = 0, animation; i < animations.length; i++) {
animation = animations[i];
if (now < animation.start) {
// animation not yet started..
continue;
}
if (!animation.started) {
// animation starts
animation.started = true;
animation.startcb && animation.startcb();
}
// animation progress
var t = (now - animation.start) / (animation.end - animation.start);
animation.progresscb && animation.progresscb(t < 1 ? t : 1);
if (now > animation.end) {
// animation ended
animation.endcb && animation.endcb();
animations.splice(i--, 1);
continue;
}
}
requestAnimationFrame(tick);
}
// fallback
window.requestAnimationFrame || (window.requestAnimationFrame = function (cb) {
setTimeout(cb, 0);
});
var style = document.createElement('p').style;
var memoized = {};
function prefix(param) {
if (typeof memoized[param] !== 'undefined') {
return memoized[param];
}
if (typeof style[param] !== 'undefined') {
memoized[param] = param;
return param;
}
var camelCase = param[0].toUpperCase() + param.slice(1);
var prefixes = ['webkit', 'moz', 'Moz', 'ms', 'o'];
var test;
for (var i = 0, len = prefixes.length; i < len; i++) {
test = prefixes[i] + camelCase;
if (typeof style[test] !== 'undefined') {
memoized[param] = test;
return test;
}
}
}
var has3d;
function translate(a, b, c) {
typeof has3d !== 'undefined' || (has3d = check3d());
c = c || 0;
if (has3d) {
return 'translate3d(' + a + ', ' + b + ', ' + c + ')';
} else {
return 'translate(' + a + ', ' + b + ')';
}
}
function check3d() {
// I admit, this line is stealed from the great Velocity.js!
// http://julian.com/research/velocity/
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (!isMobile) {
return false;
}
var transform = prefix('transform');
var $p = document.createElement('p');
document.body.appendChild($p);
$p.style[transform] = 'translate3d(1px,1px,1px)';
has3d = $p.style[transform];
has3d = has3d != null && has3d.length && has3d !== 'none';
document.body.removeChild($p);
return has3d;
}
function createElement(type) {
return document.createElement(type);
}
var maxZ = 54; // total no. of cards
var maxS = 3; // total no. of 'suits'
function _card(i) {
var transform = prefix('transform');
// calculate rank/suit, etc..
var rank = i % (maxZ/maxS) + 1;
var suit = i / (maxZ/maxS) | 0;
var z = (maxZ - i) / 2;
// create elements
var $el = createElement('div');
var $face = createElement('div');
var $back = createElement('div');
// states
var isDraggable = false;
var isFlippable = true;
// self = card
var self = { i: i, rank: rank, suit: suit, pos: i, $el: $el, mount: mount, unmount: unmount, setSide: setSide };
var modules = Deck.modules;
var module;
// add classes
$face.classList.add('face');
$back.classList.add('back');
// add default transform
$el.style[transform] = translate(-z + 'px', -z + 'px');
// add default values
self.x = -z;
self.y = -z;
self.z = z;
self.rot = 0;
// set default side to back
self.setSide('face'); // 'back'
// add drag/click listeners
addListener($el, 'mousedown', onMousedown);
addListener($el, 'touchstart', onMousedown);
// load modules
for (module in modules) {
addModule(modules[module]);
}
self.animateTo = function (params) {
var delay = params.delay;
var duration = params.duration;
var _params$x = params.x;
var x = _params$x === undefined ? self.x : _params$x;
var _params$y = params.y;
var y = _params$y === undefined ? self.y : _params$y;
var _params$rot = params.rot;
var rot = _params$rot === undefined ? self.rot : _params$rot;
var ease$$ = params.ease;
var onStart = params.onStart;
var onProgress = params.onProgress;
var onComplete = params.onComplete;
var startX, startY, startRot;
var diffX, diffY, diffRot;
animationFrames(delay, duration).start(function () {
startX = self.x || 0;
startY = self.y || 0;
startRot = self.rot || 0;
onStart && onStart();
}).progress(function (t) {
var et = ease[ease$$ || 'cubicInOut'](t);
diffX = x - startX;
diffY = y - startY;
diffRot = rot - startRot;
onProgress && onProgress(t, et);
self.x = startX + diffX * et;
self.y = startY + diffY * et;
self.rot = startRot + diffRot * et;
$el.style[transform] = translate(self.x + 'px', self.y + 'px') + (diffRot ? 'rotate(' + self.rot + 'deg)' : '');
}).end(function () {
onComplete && onComplete();
});
};
// set rank & suit
self.setRankSuit = function (rank, suit) {
var suitName = SuitName(suit);
$el.setAttribute('class', 'card ' + suitName + ' rank' + rank);
};
self.setRankSuit(rank, suit);
self.enableDragging = function () {
// this activates dragging
if (isDraggable) {
// already is draggable, do nothing
return;
}
isDraggable = true;
$el.style.cursor = 'move';
};
self.enableFlipping = function () {
if (isFlippable) {
// already is flippable, do nothing
return;
}
isFlippable = true;
};
self.disableFlipping = function () {
if (!isFlippable) {
// already disabled flipping, do nothing
return;
}
isFlippable = false;
};
self.disableDragging = function () {
if (!isDraggable) {
// already disabled dragging, do nothing
return;
}
isDraggable = false;
$el.style.cursor = '';
};
return self;
function addModule(module) {
// add card module
module.card && module.card(self);
}
function onMousedown(e) {
var startPos = {};
var pos = {};
var starttime = Date.now();
e.preventDefault();
// get start coordinates and start listening window events
if (e.type === 'mousedown') {
startPos.x = pos.x = e.clientX;
startPos.y = pos.y = e.clientY;
addListener(window, 'mousemove', onMousemove);
addListener(window, 'mouseup', onMouseup);
} else {
startPos.x = pos.x = e.touches[0].clientX;
startPos.y = pos.y = e.touches[0].clientY;
addListener(window, 'touchmove', onMousemove);
addListener(window, 'touchend', onMouseup);
}
if (!isDraggable) {
// is not draggable, do nothing
return;
}
// move card
$el.style[transform] = translate(self.x + 'px', self.y + 'px') + (self.rot ? ' rotate(' + self.rot + 'deg)' : '');
$el.style.zIndex = maxZ++;
function onMousemove(e) {
if (!isDraggable) {
// is not draggable, do nothing
return;
}
if (e.type === 'mousemove') {
pos.x = e.clientX;
pos.y = e.clientY;
} else {
pos.x = e.touches[0].clientX;
pos.y = e.touches[0].clientY;
}
// move card
$el.style[transform] = translate(Math.round(self.x + pos.x - startPos.x) + 'px', Math.round(self.y + pos.y - startPos.y) + 'px') + (self.rot ? ' rotate(' + self.rot + 'deg)' : '');
}
function onMouseup(e) {
if (isFlippable && Date.now() - starttime < 200) {
// flip sides
self.setSide(self.side === 'front' ? 'back' : 'front');
}
if (e.type === 'mouseup') {
removeListener(window, 'mousemove', onMousemove);
removeListener(window, 'mouseup', onMouseup);
} else {
removeListener(window, 'touchmove', onMousemove);
removeListener(window, 'touchend', onMouseup);
}
if (!isDraggable) {
// is not draggable, do nothing
return;
}
// set current position
self.x = self.x + pos.x - startPos.x;
self.y = self.y + pos.y - startPos.y;
}
}
function mount(target) {
// mount card to target (deck)
target.appendChild($el);
self.$root = target;
}
function unmount() {
// unmount from root (deck)
self.$root && self.$root.removeChild($el);
self.$root = null;
}
function setSide(newSide) {
// flip sides
if (newSide === 'front') {
if (self.side === 'back') {
$el.removeChild($back);
}
self.side = 'front';
$el.appendChild($face);
self.setRankSuit(self.rank, self.suit);
} else {
if (self.side === 'front') {
$el.removeChild($face);
}
self.side = 'back';
$el.appendChild($back);
$el.setAttribute('class', 'card');
}
}
}
function SuitName(suit) {
// return suit name from suit value
return suit === 0 ? 'spades' : suit === 1 ? 'hearts' : suit === 2 ? 'clubs' : suit === 3 ? 'diamonds' : 'joker';
}
function addListener(target, name, listener) {
target.addEventListener(name, listener);
}
function removeListener(target, name, listener) {
target.removeEventListener(name, listener);
}
var ease = {
linear: function linear(t) {
return t;
},
quadIn: function quadIn(t) {
return t * t;
},
quadOut: function quadOut(t) {
return t * (2 - t);
},
quadInOut: function quadInOut(t) {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
},
cubicIn: function cubicIn(t) {
return t * t * t;
},
cubicOut: function cubicOut(t) {
return --t * t * t + 1;
},
cubicInOut: function cubicInOut(t) {
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
},
quartIn: function quartIn(t) {
return t * t * t * t;
},
quartOut: function quartOut(t) {
return 1 - --t * t * t * t;
},
quartInOut: function quartInOut(t) {
return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
},
quintIn: function quintIn(t) {
return t * t * t * t * t;
},
quintOut: function quintOut(t) {
return 1 + --t * t * t * t * t;
},
quintInOut: function quintInOut(t) {
return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t;
}
};
var flip = {
deck: function deck(_deck) {
_deck.flip = _deck.queued(flip);
function flip(next, side) {
var flipped = _deck.cards.filter(function (card) {
return card.side === 'front';
}).length / _deck.cards.length;
_deck.cards.forEach(function (card, i) {
card.setSide(side ? side : flipped > 0.5 ? 'back' : 'front');
});
next();
}
}
};
var sort = {
deck: function deck(_deck2) {
_deck2.sort = _deck2.queued(sort);
function sort(next, reverse) {
var cards = _deck2.cards;
cards.sort(function (a, b) {
if (reverse) {
return a.i - b.i;
} else {
return b.i - a.i;
}
});
cards.forEach(function (card, i) {
card.sort(i, cards.length, function (i) {
if (i === cards.length - 1) {
next();
}
}, reverse);
});
}
},
card: function card(_card2) {
var $el = _card2.$el;
_card2.sort = function (i, len, cb, reverse) {
var z = i / 4;
var delay = i * 10;
_card2.animateTo({
delay: delay,
duration: 400,
x: -z,
y: -150,
rot: 0,
onComplete: function onComplete() {
$el.style.zIndex = i;
}
});
_card2.animateTo({
delay: delay + 500,
duration: 400,
x: -z,
y: -z,
rot: 0,
onComplete: function onComplete() {
cb(i);
}
});
};
}
};
function plusminus(value) {
var plusminus = Math.round(Math.random()) ? -1 : 1;
return plusminus * value;
}
function fisherYates(array) {
var rnd, temp;
for (var i = array.length - 1; i; i--) {
rnd = Math.random() * i | 0;
temp = array[i];
array[i] = array[rnd];
array[rnd] = temp;
}
return array;
}
function fontSize() {
return window.getComputedStyle(document.body).getPropertyValue('font-size').slice(0, -2);
}
var ____fontSize;
var shuffle = {
deck: function deck(_deck3) {
_deck3.shuffle = _deck3.queued(shuffle);
function shuffle(next) {
var cards = _deck3.cards;
____fontSize = fontSize();
fisherYates(cards);
cards.forEach(function (card, i) {
card.pos = i;
card.shuffle(function (i) {
if (i === cards.length - 1) {
next();
}
});
});
return;
}
},
card: function card(_card3) {
var $el = _card3.$el;
_card3.shuffle = function (cb) {
var i = _card3.pos;
var z = i / 4;
var delay = i * 2;
_card3.animateTo({
delay: delay,
duration: 200,
x: plusminus(Math.random() * 40 + 20) * ____fontSize / 16,
y: -z,
rot: 0
});
_card3.animateTo({
delay: 200 + delay,
duration: 200,
x: -z,
y: -z,
rot: 0,
onStart: function onStart() {
$el.style.zIndex = i;
},
onComplete: function onComplete() {
cb(i);
}
});
};
}
};
var __fontSize;
var poker = {
deck: function deck(_deck4) {
_deck4.poker = _deck4.queued(poker);
function poker(next) {
var cards = _deck4.cards;
var len = cards.length;
__fontSize = fontSize();
cards.slice(-5).reverse().forEach(function (card, i) {
card.poker(i, len, function (i) {
card.setSide('front');
if (i === 4) {
next();
}
});
});
}
},
card: function card(_card4) {
var $el = _card4.$el;
_card4.poker = function (i, len, cb) {
var delay = i * 250;
_card4.animateTo({
delay: delay,
duration: 250,
x: Math.round((i - 2.05) * 70 * __fontSize / 16),
y: Math.round(-110 * __fontSize / 16),
rot: 0,
onStart: function onStart() {
$el.style.zIndex = len - 1 + i;
},
onComplete: function onComplete() {
cb(i);
}
});
};
}
};
var intro = {
deck: function deck(_deck5) {
_deck5.intro = _deck5.queued(intro);
function intro(next) {
var cards = _deck5.cards;
cards.forEach(function (card, i) {
card.setSide('front');
card.intro(i, function (i) {
animationFrames(250, 0).start(function () {
card.setSide('back');
});
if (i === cards.length - 1) {
next();
}
});
});
}
},
card: function card(_card5) {
var transform = prefix('transform');
var $el = _card5.$el;
_card5.intro = function (i, cb) {
var delay = 500 + i * 10;
var z = i / 4;
$el.style[transform] = translate(-z + 'px', '-250px');
$el.style.opacity = 0;
_card5.x = -z;
_card5.y = -250 - z;
_card5.rot = 0;
_card5.animateTo({
delay: delay,
duration: 1000,
x: -z,
y: -z,
onStart: function onStart() {
$el.style.zIndex = i;
},
onProgress: function onProgress(t) {
$el.style.opacity = t;
},
onComplete: function onComplete() {
$el.style.opacity = '';
cb && cb(i);
}
});
};
}
};
var _fontSize;
var fan = {
deck: function deck(_deck6) {
_deck6.fan = _deck6.queued(fan);
function fan(next) {
var cards = _deck6.cards;
var len = cards.length;
_fontSize = fontSize();
cards.forEach(function (card, i) {
card.fan(i, len, function (i) {
if (i === cards.length - 1) {
next();
}
});
});
}
},
card: function card(_card6) {
var $el = _card6.$el;
_card6.fan = function (i, len, cb) {
var z = i / 4;
var delay = i * 10;
var rot = i / (len - 1) * 260 - 130;
_card6.animateTo({
delay: delay,
duration: 300,
x: -z,
y: -z,
rot: 0
});
_card6.animateTo({
delay: 300 + delay,
duration: 300,
x: Math.cos(deg2rad(rot - 90)) * 55 * _fontSize / 16,
y: Math.sin(deg2rad(rot - 90)) * 55 * _fontSize / 16,
rot: rot,
onStart: function onStart() {
$el.style.zIndex = i;
},
onComplete: function onComplete() {
cb(i);
}
});
};
}
};
function deg2rad(degrees) {
return degrees * Math.PI / 180;
}
var ___fontSize;
var bysuit = {
deck: function deck(_deck7) {
_deck7.bysuit = _deck7.queued(bysuit);
function bysuit(next) {
var cards = _deck7.cards;
___fontSize = fontSize();
cards.forEach(function (card) {
card.bysuit(function (i) {
if (i === cards.length - 1) {
next();
}
});
});
}
},
card: function card(_card7) {
var rank = _card7.rank;
var suit = _card7.suit;
_card7.bysuit = function (cb) {
var i = _card7.i;
var delay = i * 10;
_card7.animateTo({
delay: delay,
duration: 400,
x: -Math.round((6.75 - rank) * 8 * ___fontSize / 16),
y: -Math.round((2.5 - suit) * 220 * ___fontSize / 16), // distance between piles?
rot: 0,
onComplete: function onComplete() {
cb(i);
}
});
};
}
};
function queue(target) {
var array = Array.prototype;
var queueing = [];
target.queue = queue;
target.queued = queued;
return target;
function queued(action) {
return function () {
var self = this;
var args = arguments;
queue(function (next) {
action.apply(self, array.concat.apply(next, args));
});
};
}
function queue(action) {
if (!action) {
return;
}
queueing.push(action);
if (queueing.length === 1) {
next();
}
}
function next() {
queueing[0](function (err) {
if (err) {
throw err;
}
queueing = queueing.slice(1);
if (queueing.length) {
next();
}
});
}
}
function observable(target) {
target || (target = {});
var listeners = {};
target.on = on;
target.one = one;
target.off = off;
target.trigger = trigger;
return target;
function on(name, cb, ctx) {
listeners[name] || (listeners[name] = []);
listeners[name].push({ cb: cb, ctx: ctx });
}
function one(name, cb, ctx) {
listeners[name] || (listeners[name] = []);
listeners[name].push({
cb: cb, ctx: ctx, once: true
});
}
function trigger(name) {
var self = this;
var args = Array.prototype.slice(arguments, 1);
var currentListeners = listeners[name] || [];
currentListeners.filter(function (listener) {
listener.cb.apply(self, args);
return !listener.once;
});
}
function off(name, cb) {
if (!name) {
listeners = {};
return;
}
if (!cb) {
listeners[name] = [];
return;
}
listeners[name] = listeners[name].filter(function (listener) {
return listener.cb !== cb;
});
}
}
function Deck(jokers) {
// init cards array
var cards = new Array(jokers ? 55 : 52);
var $el = createElement('div');
var self = observable({ mount: mount, unmount: unmount, cards: cards, $el: $el });
var $root;
var modules = Deck.modules;
var module;
// make queueable
queue(self);
// load modules
for (module in modules) {
addModule(modules[module]);
}
// add class
$el.classList.add('deck');
var card;
// create cards
for (var i = cards.length; i; i--) {
card = cards[i - 1] = _card(i - 1);
card.setSide('back');
card.mount($el);
}
return self;
function mount(root) {
// mount deck to root
$root = root;
$root.appendChild($el);
}
function unmount() {
// unmount deck from root
$root.removeChild($el);
}
function addModule(module) {
module.deck && module.deck(self);
}
}
Deck.animationFrames = animationFrames;
Deck.ease = ease;
Deck.modules = { bysuit: bysuit, fan: fan, intro: intro, poker: poker, shuffle: shuffle, sort: sort, flip: flip };
Deck.Card = _card;
Deck.prefix = prefix;
Deck.translate = translate;
return Deck;
})();