First code commit

This commit is contained in:
Courtney Pitcher 2019-07-23 18:32:17 +02:00
parent 9623f5d704
commit 10088ca03d
2 changed files with 212 additions and 1 deletions

View file

@ -1,2 +1,12 @@
# javascript-pyramid-solitaire-solver
A brute force solver for pyramid solitaire written in javascript
A brute force solver for pyramid solitaire written in javascript.
You can see it in action on my [website](https://igniparoustempest.github.io/pyramid-solitaire-solver/):
1. Enter the string "4C 8C 6S 7H 4D 6C 8S JH 2S 8H 4H KC AS 6H KD 7D JS TH AH 8D 9D AD 9S QS 7S TC 2D 5C 2C 9C TD 5S QC AC KH 9H 3C 7C 3S 3H 2H JD 5H 4S 6D QD 3D KS QH JC TS 5D" into the textfield.
2. Click "import"
3. Click "solve"
## Notes
This is probably quite a poor implementation. Please don't fault me, I am still teaching myself javascript.

201
solver.js Normal file
View file

@ -0,0 +1,201 @@
class Card {
constructor(cardString) {
this.rank = cardString[0];
this.suit = cardString[1];
}
get isKing() {
return this.rank === 'K';
}
get integerValue() {
return "A23456789TJQK".indexOf(this.rank) + 1;
}
/**
* Returns true if the other card makes a 13 complement with this card.
* @param card Another card.
* @returns {boolean} True if complement, false otherwise.
*/
isComplement(card) {
return (this.integerValue + card.integerValue) === 13;
}
get toString() {
return this.rank + this.suit;
}
}
class Pyramid {
constructor(pyramidArray) {
this.array = pyramidArray;
}
get isCleared() {
for (let i = 0; i < this.array.length; i++) {
if (this.array[i] !== 0)
return false;
}
return true;
}
get freeCardIndices() {
let freeIndices = [];
for (let i = this.array.length - 1; i >= 0; i--) {
if (this.array[i] === 0)
continue;
let row = Pyramid.triangularNumberRow(i);
// This is the last row
if (Pyramid.triangularNumber(row) === this.array.length)
freeIndices.push(i);
else if (row < 7 && this.array[i + row] === 0 && this.array[i + row + 1] === 0)
freeIndices.push(i);
}
return freeIndices;
}
static triangularNumberRow(number) {
let triangularNumbers = [1, 3, 6, 10, 15, 21, 28];
for (let i = 0; i < triangularNumbers.length; i++) {
if (number < triangularNumbers[i])
return i + 1;
}
throw "Pyramid is too big";
}
static triangularNumber(n) {
return (n * (n + 1)) / 2;
}
}
class MoveString {
static gameWon() {return "You have won.";}
static gameLost(reason) {return "You have lost. " + reason;}
static removeFromStock(card) {return "Remove " + card + " from the top of the stock.";}
static removeFromPyramid(card) {return "Remove " + card + " from the pyramid.";}
static match(cardA, cardB) {return "Match " + cardA + " with " + cardB + ".";}
static matchStock(cardA, cardB) {return "Match top of stock (" + cardA + ") to previous card in stock(" + cardB + ").";}
static flipStock() {return "Discard top card on stock.";}
static resetStock() {return "Reset stock.";}
}
const GameStates = Object.freeze({"won": 1, "lost": 2, "inProgress": 3});
function solve(pyramidArray, stockArray, stockIndex, remainingStocks, moveArray) {
let newMoveArray = JSON.parse(JSON.stringify(moveArray));
let pyramid = new Pyramid(pyramidArray);
// We cleared the pyramid
if (pyramid.isCleared) {
newMoveArray.push(MoveString.gameWon());
return [GameStates.won, newMoveArray];
}
// We have overflowed the stock
if (stockIndex >= stockArray.length) {
if (remainingStocks === 0) {
newMoveArray.push(MoveString.gameLost("Stock is emptied."));
return [GameStates.lost, newMoveArray];
}
else {
newMoveArray.push(MoveString.resetStock());
return solve(pyramidArray, stockArray, 0, remainingStocks - 1, newMoveArray)
}
}
const topStock = new Card(stockArray[stockIndex]);
// The top card in the stock is a king
if (topStock.isKing) {
newMoveArray.push(MoveString.removeFromStock(stockArray[stockIndex]));
let newStock = JSON.parse(JSON.stringify(stockArray));
newStock.splice(stockIndex, 1);
if (stockIndex > 0 && stockIndex >= newStock.length)
stockIndex--;
return solve(pyramidArray, newStock, stockIndex, remainingStocks, newMoveArray)
}
// The free cards are kings
let freeCardsIndices = pyramid.freeCardIndices;
for (let i = 0; i < freeCardsIndices.length; i++) {
let cardA = new Card(pyramidArray[freeCardsIndices[i]]);
if (cardA.isKing) {
newMoveArray.push(MoveString.removeFromPyramid(cardA.toString));
let newPyramidArray = JSON.parse(JSON.stringify(pyramidArray));
newPyramidArray[freeCardsIndices[i]] = 0;
return solve(newPyramidArray, stockArray, stockIndex, remainingStocks, newMoveArray)
}
}
// match free card with another free card
freeCardsIndices = pyramid.freeCardIndices;
for (let i = 0; i < Math.ceil(freeCardsIndices.length / 2); i++) {
for (let j = 0; j < freeCardsIndices.length; j++) {
if (i === j)
continue;
let cardA = new Card(pyramidArray[freeCardsIndices[i]]);
let cardB = new Card(pyramidArray[freeCardsIndices[j]]);
if (cardA.isComplement(cardB)) {
newMoveArray = JSON.parse(JSON.stringify(moveArray));
newMoveArray.push(MoveString.match(cardA.toString, cardB.toString));
let newPyramidArray = JSON.parse(JSON.stringify(pyramidArray));
newPyramidArray[freeCardsIndices[i]] = 0;
newPyramidArray[freeCardsIndices[j]] = 0;
let result = solve(newPyramidArray, stockArray, stockIndex, remainingStocks, newMoveArray);
if (result[0] === GameStates.won)
return result;
}
}
}
freeCardsIndices = pyramid.freeCardIndices;
// match free cards with stock
for (let i = 0; i < freeCardsIndices.length; i++) {
let cardA = new Card(pyramidArray[freeCardsIndices[i]]);
if (!cardA.isComplement(topStock))
continue;
let newStock = JSON.parse(JSON.stringify(stockArray));
newStock.splice(stockIndex, 1);
if (stockIndex > 0 && stockIndex >= newStock.length)
stockIndex--;
newMoveArray = JSON.parse(JSON.stringify(moveArray));
newMoveArray.push(MoveString.match(topStock.toString, cardA.toString));
let newPyramidArray = JSON.parse(JSON.stringify(pyramidArray));
newPyramidArray[freeCardsIndices[i]] = 0;
let result = solve(newPyramidArray, newStock, stockIndex, remainingStocks, newMoveArray);
if (result[0] === GameStates.won)
return result;
}
// Flip over a new card
newMoveArray = JSON.parse(JSON.stringify(moveArray));
newMoveArray.push(MoveString.flipStock());
let result = solve(pyramidArray, stockArray, stockIndex + 1, remainingStocks, newMoveArray);
if (result[0] === GameStates.won)
return result;
// match top stock card with previous one
if (stockIndex > 0) {
const previousStock = new Card(stockArray[stockIndex - 1]);
if (topStock.isComplement(previousStock)) {
let newStock = JSON.parse(JSON.stringify(stockArray));
newStock.splice(stockIndex, 1);
newStock.splice(stockIndex - 1, 1);
newMoveArray = JSON.parse(JSON.stringify(moveArray));
newMoveArray.push(MoveString.matchStock(topStock.toString, previousStock.toString));
let result = solve(pyramidArray, newStock, stockIndex - 1, remainingStocks, newMoveArray);
if (result[0] === GameStates.won)
return result;
}
}
// This node was useless
return [GameStates.inProgress, moveArray];
}