patternjavascriptMinor
One-handed Solitaire algorithm
Viewed 0 times
handedalgorithmonesolitaire
Problem
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 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 0) {
hand.push(pile.pop());
}
// Check if we're on our last 2 cards.
var last2 = hand.length " + cardB); }
debugLog("hand: " + hand.length);
debugLog("pile: " + pile.length);
debugLog("discard: " + discard.length);
debugLog("");
// Detect win and loss conditions
if (pile.length === 0 && !match) {
debugLog("You lose...");
losses += 1;
gameOver = true;
}
if (pile.length === 0 && hand.length === 0) {
debugLog("You win!");
wins += 1;
gameOver = true;
}
}
console.log("Win rate: " + (wins/losses));
}Solution
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
Anyway, in terms of code:
-
Don't mix plain
-
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
-
It'd be simpler to
-
You can use
-
You can simplify
I've named
You could also take a different tack altogether. You don't really need a
(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
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 < 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 = 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();
Play again
Code Snippets
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;
}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 < 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--;
}
}
}Context
StackExchange Code Review Q#108645, answer score: 4
Revisions (0)
No revisions yet.