One-handed Solitaire algorithm My attempt at a simulation of one-handed Solitaire. Any comments welcome!

// Basic implementation of one handed solitaire by Andrew Ewen.
//
// Not sure if working correctly as it wins roughly 1/90 games whereas wikipedia states it 
// is closer to 1/140.

function Card (suit, rank) {
    this.suit = suit;
    this.rank = rank;
}

Card.suits = ["Spades", "Hearts", "Clubs", "Diamonds"];
Card.prototype.toString = function () {
    var suit = Card.suits[this.suit];
    var rank = (this.rank + 1).toString();

    switch (this.rank) {
        case 0:
            rank = "Ace";
            break;
        case 10:
            rank = "Jack";
            break;
        case 11:
            rank = "Queen";
            break;
        case 12: 
            rank = "King";  
            break;
    }   

    return rank + " of " + suit;    
}

function debugLog(msg) {
    var enabled = false;
    if (enabled) console.log(msg);
}

var deck = function () {
    cards = [];
    // Create deck of cards
    for (var suit = 0; suit &lt; 4; suit++) {
        for (var rank = 0; rank &lt; 13; rank++) {
            cards.push(new Card(suit, rank));
        }
    }   

    // Shuffle cards (Fisher-Yates)
    for (var i = cards.length - 1; i &gt; 0; i--) {
        var j = Math.floor(Math.random() * (i+1));
        var t = cards[j];
        cards[j] = cards[i];
        cards[i] = t;
    }
    return cards;
};

// Stats
var wins = 0;
var losses = 0;

for(var i = 0; i &lt; 1000000; i++) {
    var pile = deck();
    var hand = [];
    var discard = [];

    var turns = 0;
    var gameOver = false;
    while (!gameOver) {
        debugLog("turn: " + turns++);

        Array.prototype.push.apply(hand, pile.splice(pile.length - 1, 1));
        // Keep drawing cards until there is either non left or we have enough
        while (hand.length &lt; 4 &amp;&amp; pile.length &gt; 0) {
            hand.push(pile.pop());
        }

        // Check if we're on our last 2 cards.
        var last2 = hand.length &lt; 4;
        // Always check last card in hand
        var cardA = hand[hand.length - 1];
        // If we're on the last two compare with the first, otherwise compare 
        // with 4th from the end.
        var cardB = last2 ? hand[0] : hand[hand.length - 4];

        // Check for matches and discard accordingly
        var match = false;
        if(cardA.suit === cardB.suit) {
            var removed;
            if (last2) {
                removed = hand.splice(0, hand.length);
            } else {
                removed = hand.splice(hand.length - 3, 2);
            }
            Array.prototype.push.apply(discard, removed);
            match = true;
        } else if (cardA.rank === cardB.rank) { 
            var removed;
            if (last2) {
                removed = hand.splice(0, hand.length);
            } else {
                removed = hand.splice(hand.length - 4, 4);
            }
            Array.prototype.push.apply(discard, removed);
            match = true;
        }   

        if (match) { debugLog("Match: " + cardA + " -&gt; " + cardB); }

        debugLog("hand: " + hand.length);
        debugLog("pile: " + pile.length);
        debugLog("discard: " + discard.length);
        debugLog("");

        // Detect win and loss conditions
        if (pile.length === 0 &amp;&amp; !match) {
            debugLog("You lose...");
            losses += 1;
            gameOver = true;
        }
        if (pile.length === 0 &amp;&amp; hand.length === 0) {
            debugLog("You win!");
            wins += 1;
            gameOver = true;
        }
    }
    console.log("Win rate: " + (wins/losses));
}
Overall it's pretty good, but there might be something iffy going on with the rules you use.

I had honestly never heard of one-handed solitaire, but if I understand it correctly, there are no special rules for the last cards. You either have exactly 4 cards left, and win if you can discard them all, or you lose.

But you have a last2 rule, where you change the rules to look at the 1st and 2nd card if there are less than four cards left. I imagine this skews the results.

Anyway, in terms of code:


Don't mix plain console.log and a custom logging function.
Put the code for playing 1 round into a function. Right now it's all inside a loop that runs 10000 times. It'd be a lot simpler to just call play() and get a won/lost boolean back. You can always loop that.
It'd be simpler to unshift cards onto the hand. Then you'd always be comparing hand[0] to hand[3] instead of having to subtract stuff from hand.length. Splicing also becomes just splice(0, 4) or splice(1, 2)
You can use concat instead of Array.prototype.push.apply (or the slightly shorter alternative [].push.apply). The difference, however, is that concat returns a new array instead of modifying the receiver like push does.
You can simplify Card and Deck a little by using a list of values for rank as well as suit:

function Card(suit, rank) {
  this.suit = suit;
  this.rank = rank;
}

Card.prototype = {
  toString: function () {
    return this.rank + this.suit;
  }
};

Card.suits = ["♠︎", "♥︎", "♣︎", "♦︎"];
Card.ranks = ["A", 2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K"];


function Deck() {
  var cards, shuffled, i;

  cards = Card.suits.reduce(function (cards, suit) {
    return cards.concat(Card.ranks.map(function (rank) {
      return new Card(suit, rank);
    }));
  }, []);

  shuffled = [];
  while(cards.length) {
    i = Math.random() * cards.length | 0;
    shuffled.push(cards.splice(i, 1)[0]);
  }

  return shuffled;
}


I've named Deck with PascalCase as though it's a constructor, which it isn't quite. But you can call it as new Deck or just Deck() and either thing'll work, as it explicitly returns something.


You could also take a different tack altogether. You don't really need a hand array: You can just examine the shuffled deck directly. For instance:

function play() {
  var deck = new Deck,
      i = deck.length,
      a, b;

  while(true) {
    // decrement if we're too close to the end of the deck
    i = Math.min(i, deck.length-4);

    // if we had to decrement past zero, game's over
    if(i &lt; 0) {
      return deck.length === 0;
    }

    // the cards to examine
    a = deck[i],
    b = deck[i+3];

    // match cards or decrement
    if(a.rank === b.rank) {
      deck.splice(i, 4);
    } else if(a.suit === b.suit) {
      deck.splice(i + 1, 2);
    } else {
      i--;
    }
  }
}


(Edit: I updated the code based on @Jonah's comment below to examine the deck from the end, rather than the beginning.)

Running 300,000 games, I get a 0.730% chance of winning, which is pretty close to 1 in 140.

Edit: It makes for a nice animation - see snippet below



var frameTime = 100;
var button = document.getElementsByTagName("button")[0];
var span = document.getElementsByTagName("span")[0];

function Card(suit, rank) {
  this.suit = suit;
  this.rank = rank;
}

Card.suits = ["♠︎", "♥︎", "♣︎", "♦︎"];
Card.ranks = ["A", 2, 3, 4, 5, 6, 7, 8, 9, 10, "J", "Q", "K"];


function Deck() {
  var cards, shuffled, i;
  
  cards = Card.suits.reduce(function (cards, suit) {
    return cards.concat(Card.ranks.map(function (rank) {
      return new Card(suit, rank);
    }));
  }, []);
  
  shuffled = [];
  while(cards.length) {
    i = Math.random() * cards.length | 0;
    shuffled.push(cards.splice(i, 1)[0]);
  }
  
  return shuffled;
}

function play() {
  var deck = new Deck,
      i = deck.length - 4;
  
  button.disabled = true;
  span.innerHTML = "";
  
  function step() {
    var a, b;
    
    i = Math.min(i, deck.length - 4);
    drawState(deck, i);
    
    if(i &lt; 0) {
      if(deck.length) {
        span.innerHTML = "Lost (" + deck.length + " cards remain)";
      } else {
        span.innerHTML = "Won!";
      }
      button.disabled = false;
      return;
    }

    a = deck[i];
    b = deck[i+3];
    
    if(a.rank === b.rank) {
      deck.splice(i, 4);
    } else if(a.suit === b.suit) {
      deck.splice(i+1, 2);
    } else {
      i--;
    }
    
    setTimeout(step, frameTime);
  }
  
  step();
}

function drawState(deck, offset) {
  var canvas = document.getElementsByTagName("canvas")[0],
      ctx = canvas.getContext("2d");
  
  canvas.width = 520;
  canvas.height = 35;
  
  function drawTriangle(x, y) {
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.lineTo(x + 3, y + 5);
    ctx.lineTo(x - 3, y + 5);
    ctx.closePath()
    ctx.fill();
  }
  
  if(offset &gt;= 0) {
    drawTriangle(offset * 10 + 5, 25);
    drawTriangle((offset + 3) * 10 + 5, 25);
    ctx.fillStyle = "rgba(0, 0, 0, 0.1)";
    ctx.fillRect(offset * 10, 0, 40, 22);
  }
  
  ctx.fillStyle = "#000";
  ctx.font = "10px serif";
  ctx.textAlign = "center";
  
  deck.forEach(function (card, x) {
    x = x * 10 + 5;
    ctx.fillText(card.rank, x, 10);
    ctx.fillText(card.suit, x, 20);
  });
}


button.addEventListener("click", play, false);
play();
&lt;canvas&gt;&lt;/canvas&gt;
&lt;br&gt;
&lt;button&gt;Play again&lt;/button&gt;
&lt;span&gt;&lt;/span&gt;