several layout & appearance changes

This commit is contained in:
Adam Piontek 2022-09-14 09:54:03 -04:00
parent b0c1c4c648
commit f3e1875a1b
15 changed files with 330 additions and 271 deletions

View file

@ -1,19 +1,15 @@
# javascript-tri-peaks-solitaire-solver # Tripeaks Solitaire Solver 73k
A brute force solver for Microsoft Tri-Peaks solitaire written in javascript. A brute force solver for Microsoft Tripeaks solitaire written in javascript.
This is a fork of [Courtney Pitcher's project](https://github.com/IgniparousTempest/javascript-tri-peaks-solitaire-solver), with several changes. The `dist` folder contains an HTML+JS interface for entering cards and getting solutions. You can open it directly in a browser to try it out.
## Changes The solver can provide a "best moves" list (the first set of moves found that removes the most cards from the board) for unsolvable games, and games-in-progress (when you don't already know all the cards).
- fixed card matching, implemented card matching tests This began as a fork of [Courtney Pitcher's project](https://github.com/IgniparousTempest/javascript-tri-peaks-solitaire-solver), and I'm grateful for the solving algorithm & inspiration.
- solver now returns a "first best set of moves found" for unsolvable games (the first set of moves found that removes the most cards from the board)
## Notes ## Notes
- Unsolvable games can take 6 or more minutes to solve, so be patient. - Unsolvable games can take 6 or more minutes to solve, so be patient.
- Per Courtney Pitcher, "This is probably quite a poor implementation." Please don't fault either of us, he was teaching himself javascript, and I'm probably even less qualified... - Per Courtney Pitcher, "This is probably quite a poor implementation." Please don't fault either of us, he was teaching himself javascript, and I'm just having fun.
- The HTML+JS interface is built with [Vite](https://vitejs.dev/), [Alpine.js](https://alpinejs.dev/), [Bootstrap](https://getbootstrap.com/), and some [free customized SVG playing cards](https://www.me.uk/cards/). Linting & formatting is included.
## TODO
- [ ] HTML ui/demo _in progress_

64
dist/assets/index.432054e5.js vendored Normal file

File diff suppressed because one or more lines are too long

6
dist/assets/index.58a4d054.css vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/assets/solverWorker.25a6ccd7.js vendored Normal file
View file

@ -0,0 +1 @@
(function(){"use strict";class O{constructor(t){this.rank=t[0],this.suit=t[1]}get integerValue(){return"A23456789TJQK".indexOf(this.rank)}isSequential(t){return(this.integerValue+13+1)%13===t.integerValue||(this.integerValue+13-1)%13===t.integerValue}get toString(){return this.rank+this.suit}}class v{constructor(t){this.array=t}get isCleared(){return this.array.every(t=>t===0)}get freeCardIndices(){let t=[];for(let e=this.array.length-1;e>=0;e--){if(this.array[e]===0)continue;const n=Math.floor((e-3)/2);(e>=18||e<=17&&e>=9&&this.array[e+9]===0&&this.array[e+10]===0||e<=8&&e>=3&&this.array[e+6+n]===0&&this.array[e+7+n]===0||e<=2&&e>=0&&this.array[e+3+e]===0&&this.array[e+4+e]===0)&&t.push(e)}return t}}class o{static gameWon(){return"You have won."}static gameLost(){return"There are no more valid moves."}static match(t){return"Move "+t+" onto the stock."}static flipStock(){return"Draw a new stock card."}}const l=Object.freeze({won:!0,lost:!1});function g(r,t){let e=r.filter(a=>a.startsWith("Move")).length;return t.filter(a=>a.startsWith("Move")).length>e?t:r}async function h(r,t,e=null,n=0,a=[],d=[]){let i=JSON.parse(JSON.stringify(a)),s=JSON.parse(JSON.stringify(d)),J=new v(r);if(s=g(s,i),s=JSON.parse(JSON.stringify(s)),e&&e.postMessage({msg:"solve-progress",moveCount:s.length}),J.isCleared)return i.push(o.gameWon()),[l.won,i,[]];if(n>=t.length)return i.push(o.gameLost()),[l.lost,i,s];const m=new O(t[n]);let f=J.freeCardIndices;for(let u=0;u<f.length;u++){let S=new O(r[f[u]]);if(!S.isSequential(m))continue;let N=JSON.parse(JSON.stringify(t));N.splice(++n,0,S.toString),i=JSON.parse(JSON.stringify(a)),i.push(o.match(S.toString));let y=JSON.parse(JSON.stringify(r));y[f[u]]=0;let p=await h(y,N,e,n,i,s);if(p[0]===l.won)return p;s=g(s,p[2]),s=JSON.parse(JSON.stringify(s))}i=JSON.parse(JSON.stringify(a)),i.push(o.flipStock());let c=await h(r,t,e,++n,i,s);return c[0]===l.won?c:(s=g(s,c[2]),s=JSON.parse(JSON.stringify(s)),[l.lost,a,s])}const w=async r=>{let t=await h(r.slice(0,28),r.slice(28,52),self);postMessage({msg:"solve-result",result:t})};addEventListener("message",async r=>{r.data.msg==="try-to-solve"&&w(r.data.game)})})();

1
dist/index.html vendored Normal file

File diff suppressed because one or more lines are too long

4
package-lock.json generated
View file

@ -1,11 +1,11 @@
{ {
"name": "javascript-tri-peaks-solitaire-solver", "name": "tripeaks-solitaire-solver-js-73k",
"version": "1.0.1", "version": "1.0.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "javascript-tri-peaks-solitaire-solver", "name": "tripeaks-solitaire-solver-js-73k",
"version": "1.0.1", "version": "1.0.1",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {

39
run.js
View file

@ -1,39 +0,0 @@
const solver = require("./solver");
// // Easy MS Tripeaks level
// let userCardsInput = " js as ts ad tc qd 9s 4s 2h 9h 8s jh 6c 3d ks 5s 5c 6h 9c 2c ac 8c 6d 5d th 8d kc kd 9d 4c 5h 8h qh 6s "
// userCardsInput = " 2d 7c 7d 3s kh qs jc 2s 7s " + userCardsInput
// userCardsInput = " qc 3h 3c 7h td 4h " + userCardsInput
// userCardsInput = " jd 4d ah " + userCardsInput
// Unsolvable board
let userCardsInput =
"2D 6D AD 9S 4C 7C 7S 7D 9C 2S AC 8D 6S 6H 3C 5H QS JS 4S JH 5C AS 3H 3S AH TD 4D 5S TH 7H KS QH 6C KD 8S 2C TC JC 5D 3D 2H TS 4H JD KC KH 8H QC 8C QD 9D 9H";
// continue!
userCardsInput = userCardsInput.toUpperCase();
let userCards = userCardsInput.split(" ").filter((s) => s);
userCards = Array(52 - userCards.length)
.fill("0")
.concat(userCards)
.map((c) => (c === "0" ? 0 : c));
let start_time = process.hrtime.bigint();
let result = solver.solve(
userCards.slice(0, 28),
userCards.slice(28, 52),
0,
[]
);
let end_time = process.hrtime.bigint();
let resultStr = JSON.stringify(result, null, 2);
process.stdout.write(resultStr + "\n");
process.stdout.write(`moves array length: ${result[1].length}\n`);
process.stdout.write(`best moves array length: ${result[2].length}\n`);
const MS_PER_NS = 1e-6;
const NS_PER_SEC = 1e9;
let elapsedMs = Number(end_time - start_time) * MS_PER_NS;
let elapsedS = Number(end_time - start_time) / NS_PER_SEC;
process.stdout.write(`solving took: ${elapsedMs} milliseconds\n`);
process.stdout.write(`solving took: ${elapsedS} seconds\n`);

View file

@ -101,8 +101,10 @@ function getBestMoveArray(bestMoveArray, newMoveArray) {
* Solves a Tri Peaks solitaire game. * Solves a Tri Peaks solitaire game.
* @param pyramidArray The cards in the pyramids, starting in the top-left peak. The cards are in left-to-right, then top-to-bottom order. * @param pyramidArray The cards in the pyramids, starting in the top-left peak. The cards are in left-to-right, then top-to-bottom order.
* @param stockArray The cards in the stock. * @param stockArray The cards in the stock.
* @param worker Optional Web Worker for reporting progress.
* @param stockIndex The index of the top stock card. * @param stockIndex The index of the top stock card.
* @param moveArray The list of moves that have been made to get a deck in this configuration. * @param moveArray The list of moves that have been made to get a deck in this configuration.
* @param bestMoveArray The best list of moves that have been found so far.
* @returns {Promise<{*[]|([*, *, *]|[*, *, *]|[*, *, *])}>} * @returns {Promise<{*[]|([*, *, *]|[*, *, *]|[*, *, *])}>}
*/ */
async function solve( async function solve(

View file

@ -1,2 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" class="card" face="2B" height="3.5in" preserveAspectRatio="none" viewBox="-120 -168 240 336" width="2.5in"><defs><pattern id="B2" width="6" height="6" patternUnits="userSpaceOnUse"><path d="M3 0L6 3L3 6L0 3Z" fill="#d40000"></path></pattern></defs><rect width="239" height="335" x="-119.5" y="-167.5" rx="12" ry="12" fill="#e4dccd" stroke="black"></rect><rect fill="url(#B2)" width="216" height="312" x="-108" y="-156" rx="12" ry="12"></rect></svg> <svg xmlns="http://www.w3.org/2000/svg" class="card" face="2B" height="3.5in" preserveAspectRatio="none" viewBox="-120 -168 240 336" width="2.5in"><defs><pattern id="B2" width="6" height="6" patternUnits="userSpaceOnUse"><path d="M3 0L6 3L3 6L0 3Z" fill="#001B50"></path></pattern></defs><rect width="239" height="335" x="-119.5" y="-167.5" rx="12" ry="12" fill="#e4dccd" stroke="black"></rect><rect fill="url(#B2)" width="216" height="312" x="-108" y="-156" rx="12" ry="12"></rect></svg>

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 543 B

1
src/img/73k.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 35 KiB

3
src/img/bi-github.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-github" viewBox="0 0 16 16">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
</svg>

After

Width:  |  Height:  |  Size: 716 B

3
src/img/bi-twitter.svg Normal file
View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter" viewBox="0 0 16 16">
<path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/>
</svg>

After

Width:  |  Height:  |  Size: 640 B

View file

@ -1,69 +1,33 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<!-- Required meta tags -->
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tripeaks Solver 73k</title> <title>Tripeaks Solver 73k</title>
</head> </head>
<body class="text-dark"> <body class="text-dark">
<nav class="navbar navbar-expand navbar-light bg-light mb-4"> <nav class="navbar navbar-expand navbar-dark bg-dark mb-4" x-data="navbar">
<div class="container-fluid"> <div class="container">
<a class="navbar-brand text-dark" href="#">Tripeaks Solver 73k</a> <div class="navbar-brand d-flex flex-column">
<span class="text-light fs-4">Tripeaks Solver 73k</span>
<span class="text-white-50 fs-6">by Adam Piontek</span>
</div>
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link fs-5" href="https://73k.us/"
>73k
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="currentColor"
class="bi bi-box-arrow-up-right"
viewBox="0 0 16 16"
style="margin-top: -0.27em"
>
<path
fill-rule="evenodd"
d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z"
/>
<path
fill-rule="evenodd"
d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z"
/></svg
></a>
</li>
<li class="nav-item"> <li class="nav-item">
<a <a
class="nav-link" class="nav-link"
href="https://github.com/apiontek/tripeaks-solitaire-solver-js-73k" href="https://github.com/apiontek/tripeaks-solitaire-solver-js-73k"
><svg x-html="biGithub"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
class="bi bi-github"
viewBox="0 0 16 16"
>
<path
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
/></svg
></a> ></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="https://twitter.com/adampiontek" <a class="nav-link" href="https://73k.us/" x-html="svg73k"></a>
><svg </li>
xmlns="http://www.w3.org/2000/svg" <li class="nav-item">
width="24" <a
height="24" class="nav-link"
fill="currentColor" href="https://twitter.com/adampiontek"
class="bi bi-twitter" x-html="biTwitter"
viewBox="0 0 16 16"
>
<path
d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"
/></svg
></a> ></a>
</li> </li>
</ul> </ul>
@ -71,15 +35,9 @@
</nav> </nav>
<div class="container"> <div class="container">
<div class="row"> <div class="row justify-content-center my-4">
<div class="col"> <div class="col-xl-9">
<!-- <h1 class="mb-3">Tripeaks Solver</h1> --> <div x-data="{show: false}" class="accordion" id="readMeAccordion">
<div
x-data="{show: false}"
class="accordion mb-4"
id="readMeAccordion"
>
<div class="accordion-item"> <div class="accordion-item">
<h2 id="readMeHeader" class="accordion-header"> <h2 id="readMeHeader" class="accordion-header">
<button <button
@ -108,17 +66,22 @@
</p> </p>
<p> <p>
Use a single zero ("0") for unknown cards. However, the last You don't have to know all the cards, this solver will give
34 cards (bottom row & stock) must be known, and you don't you as many moves as it can. Enter a "0" (a single zero) for
need to enter unknown cards before the first card you know. unknown cards. You don't need to enter unknown cards before
the first card you know. (However, the last 34 cards (base
of peaks, stock) must be known.)
</p> </p>
<p> <p>
Entry is case-insensitive, and you can separate cards with Entry is case-insensitive, and you can separate cards with
any character (space, comma, etc), or use no separator. any character (space, comma, etc), or use no separator.
Valid examples:
</p> </p>
<h5>Valid examples</h5>
<p>These will complete quickly:</p>
<ul> <ul>
<li> <li>
7S 5D 7C 2D 0 0 3S 2H 3H 9H KC QC TD 8D 9C 7H 9D JS QS 4H 7S 5D 7C 2D 0 0 3S 2H 3H 9H KC QC TD 8D 9C 7H 9D JS QS 4H
@ -134,164 +97,185 @@
7c03s0qsjc00JSasTSadtcqd9s4s2h9h8sjh6c3dks5s5c6h9C2Cac8C6d5DTH8dkckd9d4c5h8hqh6s 7c03s0qsjc00JSasTSadtcqd9s4s2h9h8sjh6c3dks5s5c6h9C2Cac8C6d5DTH8dkckd9d4c5h8hqh6s
</li> </li>
</ul> </ul>
<p>
This example will take several minutes, but will provide a
solution, and should demonstrate that the page stays
responsive and provides feedback on the solving progress:
</p>
<ul>
<li>
2D 6D AD 9S 4C 7C 7S 7D 9C 2S AC 8D 6S 6H 3C 5H QS JS 4S
JH 5C AS 3H 3S AH TD 4D 5S TH 7H KS QH 6C KD 8S 2C TC JC
5D 3D 2H TS 4H JD KC KH 8H QC 8C QD 9D 9H
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="row"> </div>
<div class="col-12 col-lg-9 col-xl-8">
<!-- User input of game cards --> <div class="row justify-content-center">
<form <div class="col-12 col-lg-9 col-xl-8 col-xxl-7">
id="cardsInputForm" <!-- User input of game cards -->
x-data="cardsInputForm" <form
@submit.prevent="$dispatch('solve-game')" id="cardsInputForm"
class="needs-validation" x-data="cardsInputForm"
:class="{'was-validated': isFormValid && inputValue.length > 2 }" @submit.prevent="$dispatch('solve-game')"
novalidate class="needs-validation"
> :class="{'was-validated': isFormValid && inputValue.length > 2 }"
<h3 id="cardsInputLabel" for="cardsInput" class="form-label"> novalidate
Enter Your Cards >
</h3> <h3 id="cardsInputLabel" for="cardsInput" class="form-label">
<div class="input-group"> Enter Your Cards
<input </h3>
id="cardsInput" <div class="input-group">
aria-describedby="cardsInputLabel" <input
type="text" id="cardsInput"
x-model="inputValue" aria-describedby="cardsInputLabel"
class="form-control" type="text"
:class="{ 'is-valid': isFormValid && inputValue.length > 2, 'is-invalid': !isFormValid && inputValue.length > 2 }" x-model="inputValue"
maxlength="206" class="form-control"
@keyup.debounce="validateCardsInput()" :class="{ 'is-valid': isFormValid && inputValue.length > 2, 'is-invalid': !isFormValid && inputValue.length > 2 }"
placeholder="e.g., 2S TC 4S QD KH 5S 9S ..." maxlength="206"
:disabled="$store.global.solvingInProgress" @keyup.debounce="validateCardsInput()"
/> placeholder="e.g., 2S TC 4S QD KH 5S 9S ..."
<button :disabled="$store.global.solvingInProgress"
id="cardsInputButton" />
type="submit" <button
class="btn btn-outline-primary" id="cardsInputButton"
:disabled="!isFormValid || $store.global.solvingInProgress" type="submit"
> class="btn btn-outline-primary"
Solve :disabled="!isFormValid || $store.global.solvingInProgress"
</button> >
</div> Solve
<div class="mb-3"> </button>
<template x-for="msg in validMessages"> </div>
<div <div class="mb-3">
class="valid-feedback" <template x-for="msg in validMessages">
:class="{ 'd-block': validCards.length > 0 }" <div
x-text="msg" class="valid-feedback"
></div> :class="{ 'd-block': validCards.length > 0 }"
</template> x-text="msg"
<template x-for="msg in invalidMessages"> ></div>
<div </template>
class="invalid-feedback" <template x-for="msg in invalidMessages">
:class="{ 'd-block': invalidMessages.length > 0 && inputValue.length > 2 }" <div
x-text="msg" class="invalid-feedback"
></div> :class="{ 'd-block': invalidMessages.length > 0 && inputValue.length > 2 }"
</template> x-text="msg"
</div> ></div>
</form> </template>
</div>
<!-- Friendly display of game cards --> </form>
<div
id="playingCardsPreview" <!-- Friendly display of game cards -->
x-data="playingCardsPreview" <div
class="mb-4" id="playingCardsPreview"
> x-data="playingCardsPreview"
<div class="mb-5"
id="gamePyramid" >
class="game-pyramid-container p-2 p-md-3 p-lg-4" <div
> id="gamePyramid"
<!-- first/top row (index 0, row 1) --> class="game-pyramid-container p-2 p-md-3 p-lg-4"
<span class="visually-hidden">Game Row 1, Top of Peaks</span> >
<template x-for="(card, index) in cardsBySlice(0, 3)"> <!-- first/top row (index 0, row 1) -->
<div <span class="visually-hidden">Game Row 1, Top of Peaks</span>
x-html="cardSvg(card)" <template x-for="(card, index) in cardsBySlice(0, 3)">
:class="`game-pyramid-card game-pyramid-card-0-${index}`" <div
></div> x-html="cardSvg(card)"
</template> :class="`game-pyramid-card game-pyramid-card-0-${index}`"
></div>
<!-- second row (index 1, row 2) --> </template>
<span class="visually-hidden">Game Row 2, Second Row</span>
<template x-for="(card, index) in cardsBySlice(3, 9)"> <!-- second row (index 1, row 2) -->
<div <span class="visually-hidden">Game Row 2, Second Row</span>
x-html="cardSvg(card)" <template x-for="(card, index) in cardsBySlice(3, 9)">
:class="`game-pyramid-card game-pyramid-card-1-${index}`" <div
></div> x-html="cardSvg(card)"
</template> :class="`game-pyramid-card game-pyramid-card-1-${index}`"
></div>
<!-- third row (index 2, row 3) --> </template>
<span class="visually-hidden">Game Row 3, Third Row</span>
<template x-for="(card, index) in cardsBySlice(9, 18)"> <!-- third row (index 2, row 3) -->
<div <span class="visually-hidden">Game Row 3, Third Row</span>
x-html="cardSvg(card)" <template x-for="(card, index) in cardsBySlice(9, 18)">
:class="`game-pyramid-card game-pyramid-card-2-${index}`" <div
></div> x-html="cardSvg(card)"
</template> :class="`game-pyramid-card game-pyramid-card-2-${index}`"
></div>
<!-- last/bottom row (index 3, row 4) --> </template>
<span class="visually-hidden">Game Row 4, Base of Peaks</span>
<template x-for="(card, index) in cardsBySlice(18, 28)"> <!-- last/bottom row (index 3, row 4) -->
<div <span class="visually-hidden">Game Row 4, Base of Peaks</span>
x-html="cardSvg(card)" <template x-for="(card, index) in cardsBySlice(18, 28)">
:class="`game-pyramid-card game-pyramid-card-3-${index}`" <div
></div> x-html="cardSvg(card)"
</template> :class="`game-pyramid-card game-pyramid-card-3-${index}`"
></div>
<!-- stock cards --> </template>
<span class="visually-hidden"
>Game Stock, the Draw Cards</span <!-- stock cards -->
> <span class="visually-hidden">Game Stock, the Draw Cards</span>
<template x-for="(card, index) in cardsBySlice(28, 52)"> <template x-for="(card, index) in cardsBySlice(28, 52)">
<div <div
x-html="card === 0 || card === '0' ? cardSvgs['2B'] : cardSvgs[card]" x-html="card === 0 || card === '0' ? cardSvgs['2B'] : cardSvgs[card]"
:class="`game-pyramid-card game-pyramid-card-4-${index}`" :class="`game-pyramid-card game-pyramid-card-4-${index}`"
></div> ></div>
</template> </template>
</div> </div>
</div> </div>
</div> </div>
<div class="col-12 col-lg-3 col-xl-4"> <div class="col-12 col-lg-3 col-xxl-4">
<!-- Display of solvingprogress or solution --> <!-- Display of solvingprogress or solution -->
<div id="gameSolving" x-data="gameSolving" x-init="onInit()"> <div id="gameSolving" x-data="gameSolving" x-init="onInit()">
<div class="d-flex justify-content-start"> <div class="d-flex justify-content-start">
<div <div
class="spinner-border me-2" class="spinner-border me-2"
role="status" role="status"
x-show="$store.global.solvingInProgress" x-show="$store.global.solvingInProgress"
></div> ></div>
<h3 <h3 @solve-game.window="startSolver()" x-text="headerText"></h3>
@solve-game.window="startSolver()" </div>
x-text="headerText"
></h3> <p
</div> x-show="solutionMoves.length === 0 && statusMessages.length === 0"
>
<ul x-show="$store.global.solvingInProgress"> Enter cards and click Solve to get a solution.
<template x-for="msg in statusMessages"> </p>
<li x-text="msg"></li>
</template> <ul x-show="$store.global.solvingInProgress">
</ul> <template x-for="msg in statusMessages">
<li x-text="msg"></li>
<!-- <p x-show="" x-text="statusMessage"></p> --> </template>
</ul>
<ol x-show="solutionMoves.length > 0">
<template x-for="move in solutionMoves"> <!-- <p x-show="" x-text="statusMessage"></p> -->
<li x-text="move"></li>
</template> <ol x-show="solutionMoves.length > 0">
</ol> <template x-for="move in solutionMoves">
</div> <li x-text="move"></li>
</div> </template>
</div> </ol>
</div>
</div> </div>
</div> </div>
</div> </div>
<footer class="py-3 my-4"> <footer class="py-3 mt-5 bg-light">
<p class="text-center text-muted"> <div class="text-center text-muted">
© 2022 <a href="https://73k.us/">Adam Piontek</a> Written in 2022 by
</p> <a href="https://73k.us/" class="link-secondary">Adam Piontek</a> |
<a
href="https://github.com/apiontek/tripeaks-solitaire-solver-js-73k"
class="link-secondary"
>Source on Github</a
>
</div>
</footer> </footer>
<script type="module" src="./js/main.js"></script> <script type="module" src="./js/main.js"></script>

View file

@ -3,6 +3,11 @@ import Alpine from "alpinejs";
import cardSvgs from "./cardSvgs"; import cardSvgs from "./cardSvgs";
import SolverWorker from "./solverWorker?worker"; import SolverWorker from "./solverWorker?worker";
// navbar svg icons
import svg73k from "../img/73k.svg?raw";
import biTwitter from "../img/bi-twitter.svg?raw";
import biGithub from "../img/bi-github.svg?raw";
// Some helpful constants // Some helpful constants
const suits = { const suits = {
C: "Clubs", C: "Clubs",
@ -65,6 +70,13 @@ Alpine.store("global", {
solvingInProgress: false, solvingInProgress: false,
}); });
// navbar logic
Alpine.data("navbar", () => ({
svg73k: svg73k.replaceAll('="16"', '="24"'),
biTwitter: biTwitter.replaceAll('="16"', '="24"'),
biGithub: biGithub.replaceAll('="16"', '="24"'),
}));
// card preview component logic // card preview component logic
Alpine.data("playingCardsPreview", () => ({ Alpine.data("playingCardsPreview", () => ({
cardSvgs, cardSvgs,
@ -198,11 +210,16 @@ const encouragements = [
"Strive for progress, not perfection.", "Strive for progress, not perfection.",
"I wish only to be alive and to experience this living to the fullest.", "I wish only to be alive and to experience this living to the fullest.",
"This too shall pass.", "This too shall pass.",
"Life is available only in the present moment.",
"Patience is the companion of wisdom.", "Patience is the companion of wisdom.",
"The mountains are calling and I must go.", "The mountains are calling and I must go.",
"Give time time.", "Give time time.",
"If you find a path with no obstacles, it probably doesn't lead anywhere", "If you find a path with no obstacles, it probably doesn't lead anywhere",
"A smooth sea never made a good sailor.", "A smooth sea never made a good sailor.",
"To be upset over what you dont have is to waste what you do have.",
"The ultimate freedom lies in being able to wait patiently for a good thing.",
"If it isn't good, let it die. If it doesn't die, make it good.",
"A watched pot never boils without applying heat.",
"Stick with the winners.", "Stick with the winners.",
"I immerse myself in the experience of living without having to evaluate or understand it.", "I immerse myself in the experience of living without having to evaluate or understand it.",
"Why fit in when you were born to stand out?", "Why fit in when you were born to stand out?",
@ -218,13 +235,18 @@ const encouragements = [
"We must let go of the life we have planned so as to accept the one that is waiting for us.", "We must let go of the life we have planned so as to accept the one that is waiting for us.",
"Somewhere, something incredible is waiting to be known.", "Somewhere, something incredible is waiting to be known.",
"If you spend your whole life waiting for the storm, you'll never enjoy the sunshine.", "If you spend your whole life waiting for the storm, you'll never enjoy the sunshine.",
"My actions are my only true belongings.",
"The way to have enough time is to never be in a hurry.",
"The more you know, the less you think you know.",
"Patience is also a form of action.",
]; ];
// game solving component logic // game solving component logic
Alpine.data("gameSolving", () => ({ Alpine.data("gameSolving", () => ({
encouragements, encouragements,
encourageIndex: null,
solverWorker: null, solverWorker: null,
headerText: "Solution will go here:", headerText: "Solution",
moveCount: 23, moveCount: 23,
statusMessages: [], statusMessages: [],
solutionMoves: [], solutionMoves: [],
@ -236,6 +258,7 @@ Alpine.data("gameSolving", () => ({
this.statusMessages = []; this.statusMessages = [];
this.nodesTried = 0; this.nodesTried = 0;
this.nodesTriedFloor = 0; this.nodesTriedFloor = 0;
this.encourageIndex = null;
}, },
onInit() { onInit() {
this.solverWorker = new SolverWorker(); this.solverWorker = new SolverWorker();
@ -254,10 +277,21 @@ Alpine.data("gameSolving", () => ({
)} possibilities tried. Still working`; )} possibilities tried. Still working`;
} }
if (this.nodesTriedFloor % 250000 === 0) { if (this.nodesTriedFloor % 250000 === 0) {
let randInRange = Math.floor( if (this.encourageIndex === null) {
Math.random() * this.encouragements.length this.encourageIndex = Math.floor(
Math.random() * this.encouragements.length
);
}
this.statusMessages.splice(
2,
0,
this.encouragements[this.encourageIndex]
); );
this.statusMessages.push(this.encouragements[randInRange]); let newEncourageIndex = this.encourageIndex + 1;
this.encourageIndex =
newEncourageIndex === this.encouragements.length
? 0
: newEncourageIndex;
} }
} }
} else if (e.data.msg === "solve-result") { } else if (e.data.msg === "solve-result") {

View file

@ -1,7 +1,10 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import path from 'path'; import path from "path";
export default defineConfig({ export default defineConfig({
root: path.resolve(__dirname, "src"), root: path.resolve(__dirname, "src"),
base: "./", base: "./",
build: {
outDir: "../dist",
},
}); });