Commits

David Chambers  committed 7532d26

Added "Klondike".

  • Participants
  • Parent commits 0bdfa83

Comments (0)

Files changed (7)

File klondike/favicon.ico

Added
New image

File klondike/index.html

+<!DOCTYPE html>
+<html>
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+	<title>Klondike</title>
+	<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
+	<link rel="stylesheet" type="text/css" href="klondike.css" />
+</head>
+<body>
+	<script src="klondike.js"></script>
+</body>
+</html>

File klondike/klondike.coffee

+###
+
+Klondike
+
+Crafted by an enthusiastic young web developer
+seeking a deeper understanding of JavaScript.
+
+https://bitbucket.org/davidchambers/klondike
+
+###
+
+$ = (html) ->
+  div = document.createElement 'div'
+  div.innerHTML = html
+  div.firstChild
+
+chain = (arg) =>
+  if typeof arg is 'function'
+    queue.push arg
+  else
+    queue.shift()?() # don't apply delay to queue's first item
+    if ms = delay = arg
+      while fn = queue.shift()
+        window.setTimeout fn, ms
+        ms += delay
+
+queue = []
+
+data = (el, card) ->
+  if card? then store[el.className] = card else store[el.className]
+
+store = {}
+
+
+class Card
+
+  constructor: (@rank, @rankValue, @suit, @suitValue) ->
+    @el = $ "<li class='#{rank} of #{suit}' rel='face-down'></li>"
+    data @el, this
+
+  inTableau: (parent) ->
+    while parent
+      return true if parent is tableau
+      parent = parent.parentNode
+    false
+
+  isDraggable: ->
+    parent = @el.parentNode
+    not dealing and (parent is waste and @el is parent.lastChild or @isFaceUp() and @inTableau parent)
+
+  isFaceUp: ->
+    @el.getAttribute('rel') is 'face-up'
+
+  isFaceDown: ->
+    @el.getAttribute('rel') is 'face-down'
+
+  flipFaceUp: ->
+    @el.setAttribute 'rel', 'face-up'
+    @el.className = @el.className # make IE8 behave
+
+  flipFaceDown: ->
+    @el.setAttribute 'rel', 'face-down'
+    @el.className = @el.className # make IE8 behave
+
+
+class Stack
+
+  n: 20
+
+  constructor: (el) ->
+    @x = 0
+    @y = 0
+
+    e = el
+    while e
+      @x += e.offsetLeft
+      @y += e.offsetTop
+      e = e.offsetParent
+
+    @stack = []
+    while el
+      @stack.push el
+      el = el.nextSibling
+
+    @el = $ "<ol class='stack' style='left:#{@x}px;top:#{@y}px'></ol>"
+    @el.appendChild el for el in @stack
+    body.appendChild @el
+
+  move: (x, y) ->
+    @el.style.left = (@x += x) + 'px'
+    @el.style.top = (@y += y) + 'px'
+
+  morph: (fromX, fromY, toX, toY, callback, n) ->
+    n ?= @n
+    fromX += (x = Math.round (toX - fromX) / n)
+    fromY += (y = Math.round (toY - fromY) / n)
+    @el.style.left = fromX + 'px'
+    @el.style.top = fromY + 'px'
+
+    if --n
+      window.setTimeout =>
+        @morph fromX, fromY, toX, toY, callback, n
+      , Math.sqrt(x * x + y * y) # Pythagoras -> constant speed
+    else
+      callback?()
+
+  slideInto: (el, callback) ->
+    dummy = $ '<li class="of" style="visibility:hidden"></li>'
+
+    # insert dummy card
+    el.appendChild dummy
+
+    # determine its position
+    toX = toY = 0
+    e = dummy
+    while e
+      toX += e.offsetLeft
+      toY += e.offsetTop
+      e = e.offsetParent
+
+    # remove dummy card
+    el.removeChild dummy
+
+    @morph @x, @y, toX, toY, =>
+      el.appendChild card for card in @stack
+      body.removeChild @el
+      callback?()
+
+
+body = document.body
+dropTargets = []
+piles = new Array 7
+ranks = 'ace deuce trey four five six seven eight nine ten jack queen king'.split ' '
+suits = 'spades hearts clubs diamonds'.split ' '
+foundations = $ '<ul id="foundations"></ul>'
+
+for suit in suits
+  el = $ "<li><ol id='#{suit}'></ol></li>"
+  foundations.appendChild el
+  dropTargets.push el.firstChild
+
+window.onmousedown = (event) ->
+  event or= window.event
+  return if event.button is 2 # right click
+
+  target = event.target or event.srcElement
+  parent = target.parentNode
+  card = data target
+  x = event.pageX
+  y = event.pageY
+
+  if card?.isDraggable()
+    stack = new Stack target
+
+    window.onmousemove = (event) ->
+      event or= window.event
+      pageX = event.pageX
+      pageY = event.pageY
+      stack.move pageX - x, pageY - y
+      x = pageX
+      y = pageY
+
+    target.onmouseup = (event) ->
+      window.onmousemove = null
+      target.onmouseup = null
+      foundation = document.getElementById card.suit
+      canGoToFoundation = target is target.parentNode.lastChild and
+                          foundation.childNodes.length is card.rankValue and
+                          (parent is waste or card.inTableau parent)
+      dummy = $ '<li class="of" style="visibility:hidden"></li>'
+      body.appendChild dummy
+      style = window.getComputedStyle dummy, null
+      w = parseInt style.getPropertyValue('width'), 10
+      h = parseInt style.getPropertyValue('height'), 10
+      body.removeChild dummy
+      for el in dropTargets
+        min = el.firstChild or el
+        minX = minY = 0
+        while min
+          minX += min.offsetLeft
+          minY += min.offsetTop
+          min = min.offsetParent
+        max = el.lastChild or el
+        maxX = w
+        maxY = h
+        while max
+          maxX += max.offsetLeft
+          maxY += max.offsetTop
+          max = max.offsetParent
+        if minX <= event.pageX <= maxX and minY <= event.pageY <= maxY
+          droppable = el
+          break
+
+      valid = ->
+        if droppable and droppable isnt parent
+          if droppable.parentNode.parentNode is foundations
+            # suitable foundation
+            return true if card.suit is droppable.id and card.rankValue is droppable.childNodes.length
+          else if el = droppable.lastChild
+            other = data el
+            # suitable pile
+            return true if (card.suitValue + other.suitValue) % 2 and card.rankValue is other.rankValue - 1
+          else
+            # king to empty pile
+            return true if card.rank is 'king'
+        false
+
+      stack.slideInto(if valid() then droppable else (if canGoToFoundation then foundation else parent))
+
+tableau = $ '<ul id="tableau"></ul>'
+
+for pile, index in piles
+  el = $ "<li><ol id='pile:#{index}'></ol></li>"
+  ol = el.firstChild
+  ol.onclick = (event) ->
+    event or= window.event
+    target = event.target or event.srcElement
+    card = data target
+    card.flipFaceUp() if card.isFaceDown() and target is target.parentNode.lastChild
+
+  piles[index] = ol
+  dropTargets.push ol
+  tableau.appendChild el
+
+cards = []
+for suit, s in suits
+  for rank, r in ranks
+    cards.push new Card rank, r, suit, s
+
+# shuffle
+random = Math.random
+i = cards.length
+while i
+  j = random() * i-- >> 0
+  ii = cards[i]
+  jj = cards[j]
+  cards[i] = jj
+  cards[j] = ii
+
+deck = $ '<ol id="deck"></ol>'
+deck.appendChild card.el for card in cards
+deck.onclick = ->
+  if @childNodes.length
+    dealing = true
+    els = []
+    el = @lastChild
+    while el
+      els.push(el)
+      break if els.length is 3
+      el = el.previousSibling
+
+    for el in els
+      do (el) ->
+        chain ->
+          data(el).flipFaceUp()
+          (new Stack el).slideInto waste, -> chain 0
+
+    chain -> dealing = false
+    chain 0
+
+  else
+    cards =
+      try
+        Array.prototype.slice.call(waste.childNodes)
+      catch error
+        [object[i] for i in [0...object.length]]
+
+    # replenish empty deck
+    for card in cards
+      data(card).flipFaceDown()
+      @appendChild card
+
+waste = $ '<ol id="waste"></ol>'
+game = $ '<div id="game"></div>'
+game.appendChild deck
+game.appendChild waste
+game.appendChild foundations
+game.appendChild tableau
+body.appendChild game
+
+dealing = true
+i = columnOffset = 0
+len = piles.length
+
+while columnOffset < len
+  do (i, columnOffset) ->
+    chain ->
+      el = deck.lastChild
+      data(el).flipFaceUp() if i is columnOffset
+      new Stack(el).slideInto piles[i]
+
+  i = ++columnOffset if ++i is len
+
+chain -> dealing = false
+chain 100
+
+document.onselectstart = -> false # prevent WebKit from displaying text selection cursor

File klondike/klondike.css

+@charset "UTF-8";
+
+html                                                { height: 100%; }
+body                                                { margin: 0; height: 100%; min-height: 527px; padding: 0;
+                                       /* fallback */ background: #470;
+                                         /* webkit */ background: #250 -webkit-gradient(linear, left top, left bottom, from(#470), to(#250), color-stop(45%, #470)) repeat-x; -webkit-background-size: 527px;
+                                          /* gecko */ background: #250 -moz-linear-gradient(top, #470 227px, #250 527px) repeat-x; }
+ol, ul                                              { margin: 0; padding: 0; list-style: none; }
+
+  #game                                             { width: 595px; min-height: 527px; padding: 15px; }
+    #deck, #waste                                   { position: relative; float: left; margin: 5px; width: 75px; height: 100px; }
+    #deck                                           { background: url(sprite.png) no-repeat -75px 0; }
+      #deck .of                                     { margin: 0 0 -98px; }
+      #waste .of                                    { margin: 0 0 -98px;
+                                                      -webkit-transition-property: margin-left, margin-top;
+                                                      -webkit-transition-duration: 0.5s;
+                                                      -webkit-transition-timing-function: linear;
+                                                      -o-transition-property: margin-left, margin-top;
+                                                      -o-transition-duration: 0.5s;
+                                                      -o-transition-timing-function: linear; }
+      #waste:hover .of:nth-child(2):last-child,
+      #waste:hover .of:nth-last-child(3)+.of        { margin: 18px 0 -98px 20px; }
+      #waste:hover .of:nth-last-child(3)+.of+.of    { margin: 18px 0 -98px 40px; }
+    #foundations                                    { float: right; }
+      #foundations li                               { float: left; }
+      #foundations ol                               { width: 75px; height: 100px; border: 5px solid #470; border-bottom-width: 107px; background: url(sprite.png) no-repeat; }
+      #foundations #spades                          { background-position: 0 -100px; }
+      #foundations #hearts                          { background-position: 0 -200px; }
+      #foundations #clubs                           { background-position: 0 -300px; }
+      #foundations #diamonds                        { background-position: 0 -400px; }
+        #foundations .of                            { margin: 0 0 -98px; }
+    #tableau                                        { clear: both; }
+      #tableau li                                   { float: left; }
+        #tableau ol                                 { width: 75px; padding: 5px 5px 105px; }
+          #tableau .of                              { margin: 0 0 -75px; }
+  .stack                                            { position: absolute; }
+  .stack .of                                        { margin: 0 0 -75px; }
+
+.of                                                 { position: relative; width: 75px; height: 100px; background: url(sprite.png) no-repeat; }
+.of[rel="face-down"]                                { background-position: 0 0 !important; }
+
+.ace.of.spades                                      { background-position: -75px -100px; }
+.ace.of.hearts                                      { background-position: -75px -200px; }
+.ace.of.clubs                                       { background-position: -75px -300px; }
+.ace.of.diamonds                                    { background-position: -75px -400px; }
+
+.deuce.of.spades                                    { background-position: -150px -100px; }
+.deuce.of.hearts                                    { background-position: -150px -200px; }
+.deuce.of.clubs                                     { background-position: -150px -300px; }
+.deuce.of.diamonds                                  { background-position: -150px -400px; }
+
+.trey.of.spades                                     { background-position: -225px -100px; }
+.trey.of.hearts                                     { background-position: -225px -200px; }
+.trey.of.clubs                                      { background-position: -225px -300px; }
+.trey.of.diamonds                                   { background-position: -225px -400px; }
+
+.four.of.spades                                     { background-position: -300px -100px; }
+.four.of.hearts                                     { background-position: -300px -200px; }
+.four.of.clubs                                      { background-position: -300px -300px; }
+.four.of.diamonds                                   { background-position: -300px -400px; }
+
+.five.of.spades                                     { background-position: -375px -100px; }
+.five.of.hearts                                     { background-position: -375px -200px; }
+.five.of.clubs                                      { background-position: -375px -300px; }
+.five.of.diamonds                                   { background-position: -375px -400px; }
+
+.six.of.spades                                      { background-position: -450px -100px; }
+.six.of.hearts                                      { background-position: -450px -200px; }
+.six.of.clubs                                       { background-position: -450px -300px; }
+.six.of.diamonds                                    { background-position: -450px -400px; }
+
+.seven.of.spades                                    { background-position: -525px -100px; }
+.seven.of.hearts                                    { background-position: -525px -200px; }
+.seven.of.clubs                                     { background-position: -525px -300px; }
+.seven.of.diamonds                                  { background-position: -525px -400px; }
+
+.eight.of.spades                                    { background-position: -600px -100px; }
+.eight.of.hearts                                    { background-position: -600px -200px; }
+.eight.of.clubs                                     { background-position: -600px -300px; }
+.eight.of.diamonds                                  { background-position: -600px -400px; }
+
+.nine.of.spades                                     { background-position: -675px -100px; }
+.nine.of.hearts                                     { background-position: -675px -200px; }
+.nine.of.clubs                                      { background-position: -675px -300px; }
+.nine.of.diamonds                                   { background-position: -675px -400px; }
+
+.ten.of.spades                                      { background-position: -750px -100px; }
+.ten.of.hearts                                      { background-position: -750px -200px; }
+.ten.of.clubs                                       { background-position: -750px -300px; }
+.ten.of.diamonds                                    { background-position: -750px -400px; }
+
+.jack.of.spades                                     { background-position: -825px -100px; }
+.jack.of.hearts                                     { background-position: -825px -200px; }
+.jack.of.clubs                                      { background-position: -825px -300px; }
+.jack.of.diamonds                                   { background-position: -825px -400px; }
+
+.queen.of.spades                                    { background-position: -900px -100px; }
+.queen.of.hearts                                    { background-position: -900px -200px; }
+.queen.of.clubs                                     { background-position: -900px -300px; }
+.queen.of.diamonds                                  { background-position: -900px -400px; }
+
+.king.of.spades                                     { background-position: -975px -100px; }
+.king.of.hearts                                     { background-position: -975px -200px; }
+.king.of.clubs                                      { background-position: -975px -300px; }
+.king.of.diamonds                                   { background-position: -975px -400px; }

File klondike/klondike.js

+(function() {
+  /*
+
+  Klondike
+
+  Crafted by an enthusiastic young web developer
+  seeking a deeper understanding of JavaScript.
+
+  https://bitbucket.org/davidchambers/klondike
+
+  */  var $, Card, Stack, body, card, cards, chain, columnOffset, data, dealing, deck, dropTargets, el, foundations, game, i, ii, index, j, jj, len, ol, pile, piles, queue, r, random, rank, ranks, s, store, suit, suits, tableau, waste, _i, _j, _len, _len2, _len3, _len4, _len5;
+  var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+  $ = function(html) {
+    var div;
+    div = document.createElement('div');
+    div.innerHTML = html;
+    return div.firstChild;
+  };
+  chain = __bind(function(arg) {
+    var delay, fn, ms, _base, _results;
+    if (typeof arg === 'function') {
+      return queue.push(arg);
+    } else {
+      if (typeof (_base = queue.shift()) === "function") {
+        _base();
+      }
+      if (ms = delay = arg) {
+        _results = [];
+        while (fn = queue.shift()) {
+          window.setTimeout(fn, ms);
+          _results.push(ms += delay);
+        }
+        return _results;
+      }
+    }
+  }, this);
+  queue = [];
+  data = function(el, card) {
+    if (card != null) {
+      return store[el.className] = card;
+    } else {
+      return store[el.className];
+    }
+  };
+  store = {};
+  Card = (function() {
+    function Card(rank, rankValue, suit, suitValue) {
+      this.rank = rank;
+      this.rankValue = rankValue;
+      this.suit = suit;
+      this.suitValue = suitValue;
+      this.el = $("<li class='" + rank + " of " + suit + "' rel='face-down'></li>");
+      data(this.el, this);
+    }
+    Card.prototype.inTableau = function(parent) {
+      while (parent) {
+        if (parent === tableau) {
+          return true;
+        }
+        parent = parent.parentNode;
+      }
+      return false;
+    };
+    Card.prototype.isDraggable = function() {
+      var parent;
+      parent = this.el.parentNode;
+      return !dealing && (parent === waste && this.el === parent.lastChild || this.isFaceUp() && this.inTableau(parent));
+    };
+    Card.prototype.isFaceUp = function() {
+      return this.el.getAttribute('rel') === 'face-up';
+    };
+    Card.prototype.isFaceDown = function() {
+      return this.el.getAttribute('rel') === 'face-down';
+    };
+    Card.prototype.flipFaceUp = function() {
+      this.el.setAttribute('rel', 'face-up');
+      return this.el.className = this.el.className;
+    };
+    Card.prototype.flipFaceDown = function() {
+      this.el.setAttribute('rel', 'face-down');
+      return this.el.className = this.el.className;
+    };
+    return Card;
+  })();
+  Stack = (function() {
+    Stack.prototype.n = 20;
+    function Stack(el) {
+      var e, _i, _len, _ref;
+      this.x = 0;
+      this.y = 0;
+      e = el;
+      while (e) {
+        this.x += e.offsetLeft;
+        this.y += e.offsetTop;
+        e = e.offsetParent;
+      }
+      this.stack = [];
+      while (el) {
+        this.stack.push(el);
+        el = el.nextSibling;
+      }
+      this.el = $("<ol class='stack' style='left:" + this.x + "px;top:" + this.y + "px'></ol>");
+      _ref = this.stack;
+      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+        el = _ref[_i];
+        this.el.appendChild(el);
+      }
+      body.appendChild(this.el);
+    }
+    Stack.prototype.move = function(x, y) {
+      this.el.style.left = (this.x += x) + 'px';
+      return this.el.style.top = (this.y += y) + 'px';
+    };
+    Stack.prototype.morph = function(fromX, fromY, toX, toY, callback, n) {
+      var x, y;
+      n != null ? n : n = this.n;
+      fromX += (x = Math.round((toX - fromX) / n));
+      fromY += (y = Math.round((toY - fromY) / n));
+      this.el.style.left = fromX + 'px';
+      this.el.style.top = fromY + 'px';
+      if (--n) {
+        return window.setTimeout(__bind(function() {
+          return this.morph(fromX, fromY, toX, toY, callback, n);
+        }, this), Math.sqrt(x * x + y * y));
+      } else {
+        return typeof callback === "function" ? callback() : void 0;
+      }
+    };
+    Stack.prototype.slideInto = function(el, callback) {
+      var dummy, e, toX, toY;
+      dummy = $('<li class="of" style="visibility:hidden"></li>');
+      el.appendChild(dummy);
+      toX = toY = 0;
+      e = dummy;
+      while (e) {
+        toX += e.offsetLeft;
+        toY += e.offsetTop;
+        e = e.offsetParent;
+      }
+      el.removeChild(dummy);
+      return this.morph(this.x, this.y, toX, toY, __bind(function() {
+        var card, _i, _len, _ref;
+        _ref = this.stack;
+        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+          card = _ref[_i];
+          el.appendChild(card);
+        }
+        body.removeChild(this.el);
+        return typeof callback === "function" ? callback() : void 0;
+      }, this));
+    };
+    return Stack;
+  })();
+  body = document.body;
+  dropTargets = [];
+  piles = new Array(7);
+  ranks = 'ace deuce trey four five six seven eight nine ten jack queen king'.split(' ');
+  suits = 'spades hearts clubs diamonds'.split(' ');
+  foundations = $('<ul id="foundations"></ul>');
+  for (_i = 0, _len = suits.length; _i < _len; _i++) {
+    suit = suits[_i];
+    el = $("<li><ol id='" + suit + "'></ol></li>");
+    foundations.appendChild(el);
+    dropTargets.push(el.firstChild);
+  }
+  window.onmousedown = function(event) {
+    var card, parent, stack, target, x, y;
+    event || (event = window.event);
+    if (event.button === 2) {
+      return;
+    }
+    target = event.target || event.srcElement;
+    parent = target.parentNode;
+    card = data(target);
+    x = event.pageX;
+    y = event.pageY;
+    if (card != null ? card.isDraggable() : void 0) {
+      stack = new Stack(target);
+      window.onmousemove = function(event) {
+        var pageX, pageY;
+        event || (event = window.event);
+        pageX = event.pageX;
+        pageY = event.pageY;
+        stack.move(pageX - x, pageY - y);
+        x = pageX;
+        return y = pageY;
+      };
+      return target.onmouseup = function(event) {
+        var canGoToFoundation, droppable, dummy, el, foundation, h, max, maxX, maxY, min, minX, minY, style, valid, w, _i, _len, _ref, _ref2;
+        window.onmousemove = null;
+        target.onmouseup = null;
+        foundation = document.getElementById(card.suit);
+        canGoToFoundation = target === target.parentNode.lastChild && foundation.childNodes.length === card.rankValue && (parent === waste || card.inTableau(parent));
+        dummy = $('<li class="of" style="visibility:hidden"></li>');
+        body.appendChild(dummy);
+        style = window.getComputedStyle(dummy, null);
+        w = parseInt(style.getPropertyValue('width'), 10);
+        h = parseInt(style.getPropertyValue('height'), 10);
+        body.removeChild(dummy);
+        for (_i = 0, _len = dropTargets.length; _i < _len; _i++) {
+          el = dropTargets[_i];
+          min = el.firstChild || el;
+          minX = minY = 0;
+          while (min) {
+            minX += min.offsetLeft;
+            minY += min.offsetTop;
+            min = min.offsetParent;
+          }
+          max = el.lastChild || el;
+          maxX = w;
+          maxY = h;
+          while (max) {
+            maxX += max.offsetLeft;
+            maxY += max.offsetTop;
+            max = max.offsetParent;
+          }
+          if ((minX <= (_ref = event.pageX) && _ref <= maxX) && (minY <= (_ref2 = event.pageY) && _ref2 <= maxY)) {
+            droppable = el;
+            break;
+          }
+        }
+        valid = function() {
+          var other;
+          if (droppable && droppable !== parent) {
+            if (droppable.parentNode.parentNode === foundations) {
+              if (card.suit === droppable.id && card.rankValue === droppable.childNodes.length) {
+                return true;
+              }
+            } else if (el = droppable.lastChild) {
+              other = data(el);
+              if ((card.suitValue + other.suitValue) % 2 && card.rankValue === other.rankValue - 1) {
+                return true;
+              }
+            } else {
+              if (card.rank === 'king') {
+                return true;
+              }
+            }
+          }
+          return false;
+        };
+        return stack.slideInto(valid() ? droppable : (canGoToFoundation ? foundation : parent));
+      };
+    }
+  };
+  tableau = $('<ul id="tableau"></ul>');
+  for (index = 0, _len2 = piles.length; index < _len2; index++) {
+    pile = piles[index];
+    el = $("<li><ol id='pile:" + index + "'></ol></li>");
+    ol = el.firstChild;
+    ol.onclick = function(event) {
+      var card, target;
+      event || (event = window.event);
+      target = event.target || event.srcElement;
+      card = data(target);
+      if (card.isFaceDown() && target === target.parentNode.lastChild) {
+        return card.flipFaceUp();
+      }
+    };
+    piles[index] = ol;
+    dropTargets.push(ol);
+    tableau.appendChild(el);
+  }
+  cards = [];
+  for (s = 0, _len3 = suits.length; s < _len3; s++) {
+    suit = suits[s];
+    for (r = 0, _len4 = ranks.length; r < _len4; r++) {
+      rank = ranks[r];
+      cards.push(new Card(rank, r, suit, s));
+    }
+  }
+  random = Math.random;
+  i = cards.length;
+  while (i) {
+    j = random() * i-- >> 0;
+    ii = cards[i];
+    jj = cards[j];
+    cards[i] = jj;
+    cards[j] = ii;
+  }
+  deck = $('<ol id="deck"></ol>');
+  for (_j = 0, _len5 = cards.length; _j < _len5; _j++) {
+    card = cards[_j];
+    deck.appendChild(card.el);
+  }
+  deck.onclick = function() {
+    var card, dealing, el, els, i, _fn, _i, _j, _len, _len2, _results;
+    if (this.childNodes.length) {
+      dealing = true;
+      els = [];
+      el = this.lastChild;
+      while (el) {
+        els.push(el);
+        if (els.length === 3) {
+          break;
+        }
+        el = el.previousSibling;
+      }
+      _fn = function(el) {
+        return chain(function() {
+          data(el).flipFaceUp();
+          return (new Stack(el)).slideInto(waste, function() {
+            return chain(0);
+          });
+        });
+      };
+      for (_i = 0, _len = els.length; _i < _len; _i++) {
+        el = els[_i];
+        _fn(el);
+      }
+      chain(function() {
+        return dealing = false;
+      });
+      return chain(0);
+    } else {
+      cards = (function() {
+        try {
+          return Array.prototype.slice.call(waste.childNodes);
+        } catch (error) {
+          return [
+            (function() {
+              var _ref, _results;
+              _results = [];
+              for (i = 0, _ref = object.length; (0 <= _ref ? i < _ref : i > _ref); (0 <= _ref ? i += 1 : i -= 1)) {
+                _results.push(object[i]);
+              }
+              return _results;
+            })()
+          ];
+        }
+      })();
+      _results = [];
+      for (_j = 0, _len2 = cards.length; _j < _len2; _j++) {
+        card = cards[_j];
+        data(card).flipFaceDown();
+        _results.push(this.appendChild(card));
+      }
+      return _results;
+    }
+  };
+  waste = $('<ol id="waste"></ol>');
+  game = $('<div id="game"></div>');
+  game.appendChild(deck);
+  game.appendChild(waste);
+  game.appendChild(foundations);
+  game.appendChild(tableau);
+  body.appendChild(game);
+  dealing = true;
+  i = columnOffset = 0;
+  len = piles.length;
+  while (columnOffset < len) {
+    (function(i, columnOffset) {
+      return chain(function() {
+        el = deck.lastChild;
+        if (i === columnOffset) {
+          data(el).flipFaceUp();
+        }
+        return new Stack(el).slideInto(piles[i]);
+      });
+    })(i, columnOffset);
+    if (++i === len) {
+      i = ++columnOffset;
+    }
+  }
+  chain(function() {
+    return dealing = false;
+  });
+  chain(100);
+  document.onselectstart = function() {
+    return false;
+  };
+}).call(this);

File klondike/licence.text

+Copyright 2010 David Chambers. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+   1. Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+
+   2. 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.
+
+THIS SOFTWARE IS PROVIDED BY DAVID CHAMBERS "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 DAVID CHAMBERS 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.
+
+The views and conclusions contained in the software and documentation are
+those of the authors and should not be interpreted as representing official
+policies, either expressed or implied, of David Chambers.

File klondike/sprite.png

Added
New image