First code
This commit is contained in:
parent
188b5083cb
commit
2ecafc019e
2 changed files with 139 additions and 1 deletions
12
README.md
12
README.md
|
@ -1 +1,13 @@
|
||||||
# javascript-tri-peaks-solitaire-solver
|
# javascript-tri-peaks-solitaire-solver
|
||||||
|
A brute force solver for Microsoft Tri-Peaks solitaire written in javascript.
|
||||||
|
|
||||||
|
You can see it in action on my [website](https://igniparoustempest.github.io/tri-peaks-solitaire-solver/):
|
||||||
|
|
||||||
|
1. Enter the string "8S TS 4D 7S 5D 7C 2D JH AC 3S 2H 3H 9H KC QC TD 8D 9C 7H 9D JS QS 4H 5C 5S 4C 2C QD 8C KD 3D KS JD 2S 7D KH AH 5H 9S 4S QH 6S 6D 3C JC TC 8H 6C TH AS AD 6H" into the textfield.
|
||||||
|
2. Click "import"
|
||||||
|
3. Click "solve"
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
This is probably quite a poor implementation. Please don't fault me, I am teaching myself javascript.
|
||||||
|
|
||||||
|
|
126
solver.js
Normal file
126
solver.js
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
class Card {
|
||||||
|
constructor(cardString) {
|
||||||
|
this.rank = cardString[0];
|
||||||
|
this.suit = cardString[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the face value of the card, from [0 .. 12].
|
||||||
|
* @returns {number} The value.
|
||||||
|
*/
|
||||||
|
get integerValue() {
|
||||||
|
return "A23456789TJQK".indexOf(this.rank);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the other card has a face value one above or below this card.
|
||||||
|
* @param card Another card.
|
||||||
|
* @returns {boolean} True if sequential, false otherwise.
|
||||||
|
*/
|
||||||
|
isSequential(card) {
|
||||||
|
return (this.integerValue + 1) % 13 === card.integerValue || (this.integerValue - 1) % 13 === card.integerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
const secondOffset = Math.floor((i - 3) / 2);
|
||||||
|
// This is the last row
|
||||||
|
if (i >= 18)
|
||||||
|
freeIndices.push(i);
|
||||||
|
// Third row
|
||||||
|
else if (i <= 17 && i >= 9 && this.array[i + 9] === 0 && this.array[i + 10] === 0)
|
||||||
|
freeIndices.push(i);
|
||||||
|
// Second row
|
||||||
|
else if (i <= 8 && i >= 3 && this.array[i + 6 + secondOffset] === 0 && this.array[i + 7 + secondOffset] === 0)
|
||||||
|
freeIndices.push(i);
|
||||||
|
// First row
|
||||||
|
else if (i <= 2 && i >= 0 && this.array[i + 3 + i] === 0 && this.array[i + 4 + i] === 0)
|
||||||
|
freeIndices.push(i);
|
||||||
|
}
|
||||||
|
return freeIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
static triangularNumber(n) {
|
||||||
|
return (n * (n + 1)) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MoveString {
|
||||||
|
static gameWon() {return "You have won.";}
|
||||||
|
static gameLost() {return "There are no more valid moves.";}
|
||||||
|
static match(cardA, cardB) {return "Move " + cardA + " onto the stock.";}
|
||||||
|
static flipStock() {return "Draw a new stock card.";}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 run out of stock cards
|
||||||
|
if (stockIndex >= stockArray.length) {
|
||||||
|
newMoveArray.push(MoveString.gameLost());
|
||||||
|
return [GameStates.lost, newMoveArray];
|
||||||
|
}
|
||||||
|
|
||||||
|
const topStock = new Card(stockArray[stockIndex]);
|
||||||
|
|
||||||
|
let 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.isSequential(topStock))
|
||||||
|
continue;
|
||||||
|
let newStock = JSON.parse(JSON.stringify(stockArray));
|
||||||
|
newStock.splice(stockIndex + 1, 0, cardA.toString);
|
||||||
|
stockIndex++;
|
||||||
|
|
||||||
|
newMoveArray = JSON.parse(JSON.stringify(moveArray));
|
||||||
|
newMoveArray.push(MoveString.match(cardA.toString, topStock.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;
|
||||||
|
|
||||||
|
// This node was useless
|
||||||
|
return [GameStates.inProgress, moveArray];
|
||||||
|
}
|
Loading…
Reference in a new issue