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
- 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)
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.
## Notes
- 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...
## TODO
- [ ] HTML ui/demo _in progress_
- 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.

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",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "javascript-tri-peaks-solitaire-solver",
"name": "tripeaks-solitaire-solver-js-73k",
"version": "1.0.1",
"license": "ISC",
"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.
* @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 worker Optional Web Worker for reporting progress.
* @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 bestMoveArray The best list of moves that have been found so far.
* @returns {Promise<{*[]|([*, *, *]|[*, *, *]|[*, *, *])}>}
*/
async function solve(

View File

@ -1,2 +1,2 @@
<?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>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tripeaks Solver 73k</title>
</head>
<body class="text-dark">
<nav class="navbar navbar-expand navbar-light bg-light mb-4">
<div class="container-fluid">
<a class="navbar-brand text-dark" href="#">Tripeaks Solver 73k</a>
<nav class="navbar navbar-expand navbar-dark bg-dark mb-4" x-data="navbar">
<div class="container">
<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">
<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">
<a
class="nav-link"
href="https://github.com/apiontek/tripeaks-solitaire-solver-js-73k"
><svg
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
x-html="biGithub"
></a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://twitter.com/adampiontek"
><svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
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
<a class="nav-link" href="https://73k.us/" x-html="svg73k"></a>
</li>
<li class="nav-item">
<a
class="nav-link"
href="https://twitter.com/adampiontek"
x-html="biTwitter"
></a>
</li>
</ul>
@ -71,15 +35,9 @@
</nav>
<div class="container">
<div class="row">
<div class="col">
<!-- <h1 class="mb-3">Tripeaks Solver</h1> -->
<div
x-data="{show: false}"
class="accordion mb-4"
id="readMeAccordion"
>
<div class="row justify-content-center my-4">
<div class="col-xl-9">
<div x-data="{show: false}" class="accordion" id="readMeAccordion">
<div class="accordion-item">
<h2 id="readMeHeader" class="accordion-header">
<button
@ -108,17 +66,22 @@
</p>
<p>
Use a single zero ("0") for unknown cards. However, the last
34 cards (bottom row & stock) must be known, and you don't
need to enter unknown cards before the first card you know.
You don't have to know all the cards, this solver will give
you as many moves as it can. Enter a "0" (a single zero) for
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>
Entry is case-insensitive, and you can separate cards with
any character (space, comma, etc), or use no separator.
Valid examples:
</p>
<h5>Valid examples</h5>
<p>These will complete quickly:</p>
<ul>
<li>
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
</li>
</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 class="row">
<div class="col-12 col-lg-9 col-xl-8">
<!-- User input of game cards -->
<form
id="cardsInputForm"
x-data="cardsInputForm"
@submit.prevent="$dispatch('solve-game')"
class="needs-validation"
:class="{'was-validated': isFormValid && inputValue.length > 2 }"
novalidate
>
<h3 id="cardsInputLabel" for="cardsInput" class="form-label">
Enter Your Cards
</h3>
<div class="input-group">
<input
id="cardsInput"
aria-describedby="cardsInputLabel"
type="text"
x-model="inputValue"
class="form-control"
:class="{ 'is-valid': isFormValid && inputValue.length > 2, 'is-invalid': !isFormValid && inputValue.length > 2 }"
maxlength="206"
@keyup.debounce="validateCardsInput()"
placeholder="e.g., 2S TC 4S QD KH 5S 9S ..."
:disabled="$store.global.solvingInProgress"
/>
<button
id="cardsInputButton"
type="submit"
class="btn btn-outline-primary"
:disabled="!isFormValid || $store.global.solvingInProgress"
>
Solve
</button>
</div>
<div class="mb-3">
<template x-for="msg in validMessages">
<div
class="valid-feedback"
:class="{ 'd-block': validCards.length > 0 }"
x-text="msg"
></div>
</template>
<template x-for="msg in invalidMessages">
<div
class="invalid-feedback"
:class="{ 'd-block': invalidMessages.length > 0 && inputValue.length > 2 }"
x-text="msg"
></div>
</template>
</div>
</form>
<!-- Friendly display of game cards -->
<div
id="playingCardsPreview"
x-data="playingCardsPreview"
class="mb-4"
>
<div
id="gamePyramid"
class="game-pyramid-container p-2 p-md-3 p-lg-4"
>
<!-- first/top row (index 0, row 1) -->
<span class="visually-hidden">Game Row 1, Top of Peaks</span>
<template x-for="(card, index) in cardsBySlice(0, 3)">
<div
x-html="cardSvg(card)"
:class="`game-pyramid-card game-pyramid-card-0-${index}`"
></div>
</template>
<!-- second row (index 1, row 2) -->
<span class="visually-hidden">Game Row 2, Second Row</span>
<template x-for="(card, index) in cardsBySlice(3, 9)">
<div
x-html="cardSvg(card)"
:class="`game-pyramid-card game-pyramid-card-1-${index}`"
></div>
</template>
<!-- third row (index 2, row 3) -->
<span class="visually-hidden">Game Row 3, Third Row</span>
<template x-for="(card, index) in cardsBySlice(9, 18)">
<div
x-html="cardSvg(card)"
:class="`game-pyramid-card game-pyramid-card-2-${index}`"
></div>
</template>
<!-- last/bottom row (index 3, row 4) -->
<span class="visually-hidden">Game Row 4, Base of Peaks</span>
<template x-for="(card, index) in cardsBySlice(18, 28)">
<div
x-html="cardSvg(card)"
:class="`game-pyramid-card game-pyramid-card-3-${index}`"
></div>
</template>
<!-- stock cards -->
<span class="visually-hidden"
>Game Stock, the Draw Cards</span
>
<template x-for="(card, index) in cardsBySlice(28, 52)">
<div
x-html="card === 0 || card === '0' ? cardSvgs['2B'] : cardSvgs[card]"
:class="`game-pyramid-card game-pyramid-card-4-${index}`"
></div>
</template>
</div>
</div>
</div>
<div class="col-12 col-lg-3 col-xl-4">
<!-- Display of solvingprogress or solution -->
<div id="gameSolving" x-data="gameSolving" x-init="onInit()">
<div class="d-flex justify-content-start">
<div
class="spinner-border me-2"
role="status"
x-show="$store.global.solvingInProgress"
></div>
<h3
@solve-game.window="startSolver()"
x-text="headerText"
></h3>
</div>
<ul x-show="$store.global.solvingInProgress">
<template x-for="msg in statusMessages">
<li x-text="msg"></li>
</template>
</ul>
<!-- <p x-show="" x-text="statusMessage"></p> -->
<ol x-show="solutionMoves.length > 0">
<template x-for="move in solutionMoves">
<li x-text="move"></li>
</template>
</ol>
</div>
</div>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-xl-8 col-xxl-7">
<!-- User input of game cards -->
<form
id="cardsInputForm"
x-data="cardsInputForm"
@submit.prevent="$dispatch('solve-game')"
class="needs-validation"
:class="{'was-validated': isFormValid && inputValue.length > 2 }"
novalidate
>
<h3 id="cardsInputLabel" for="cardsInput" class="form-label">
Enter Your Cards
</h3>
<div class="input-group">
<input
id="cardsInput"
aria-describedby="cardsInputLabel"
type="text"
x-model="inputValue"
class="form-control"
:class="{ 'is-valid': isFormValid && inputValue.length > 2, 'is-invalid': !isFormValid && inputValue.length > 2 }"
maxlength="206"
@keyup.debounce="validateCardsInput()"
placeholder="e.g., 2S TC 4S QD KH 5S 9S ..."
:disabled="$store.global.solvingInProgress"
/>
<button
id="cardsInputButton"
type="submit"
class="btn btn-outline-primary"
:disabled="!isFormValid || $store.global.solvingInProgress"
>
Solve
</button>
</div>
<div class="mb-3">
<template x-for="msg in validMessages">
<div
class="valid-feedback"
:class="{ 'd-block': validCards.length > 0 }"
x-text="msg"
></div>
</template>
<template x-for="msg in invalidMessages">
<div
class="invalid-feedback"
:class="{ 'd-block': invalidMessages.length > 0 && inputValue.length > 2 }"
x-text="msg"
></div>
</template>
</div>
</form>
<!-- Friendly display of game cards -->
<div
id="playingCardsPreview"
x-data="playingCardsPreview"
class="mb-5"
>
<div
id="gamePyramid"
class="game-pyramid-container p-2 p-md-3 p-lg-4"
>
<!-- first/top row (index 0, row 1) -->
<span class="visually-hidden">Game Row 1, Top of Peaks</span>
<template x-for="(card, index) in cardsBySlice(0, 3)">
<div
x-html="cardSvg(card)"
:class="`game-pyramid-card game-pyramid-card-0-${index}`"
></div>
</template>
<!-- second row (index 1, row 2) -->
<span class="visually-hidden">Game Row 2, Second Row</span>
<template x-for="(card, index) in cardsBySlice(3, 9)">
<div
x-html="cardSvg(card)"
:class="`game-pyramid-card game-pyramid-card-1-${index}`"
></div>
</template>
<!-- third row (index 2, row 3) -->
<span class="visually-hidden">Game Row 3, Third Row</span>
<template x-for="(card, index) in cardsBySlice(9, 18)">
<div
x-html="cardSvg(card)"
:class="`game-pyramid-card game-pyramid-card-2-${index}`"
></div>
</template>
<!-- last/bottom row (index 3, row 4) -->
<span class="visually-hidden">Game Row 4, Base of Peaks</span>
<template x-for="(card, index) in cardsBySlice(18, 28)">
<div
x-html="cardSvg(card)"
:class="`game-pyramid-card game-pyramid-card-3-${index}`"
></div>
</template>
<!-- stock cards -->
<span class="visually-hidden">Game Stock, the Draw Cards</span>
<template x-for="(card, index) in cardsBySlice(28, 52)">
<div
x-html="card === 0 || card === '0' ? cardSvgs['2B'] : cardSvgs[card]"
:class="`game-pyramid-card game-pyramid-card-4-${index}`"
></div>
</template>
</div>
</div>
</div>
<div class="col-12 col-lg-3 col-xxl-4">
<!-- Display of solvingprogress or solution -->
<div id="gameSolving" x-data="gameSolving" x-init="onInit()">
<div class="d-flex justify-content-start">
<div
class="spinner-border me-2"
role="status"
x-show="$store.global.solvingInProgress"
></div>
<h3 @solve-game.window="startSolver()" x-text="headerText"></h3>
</div>
<p
x-show="solutionMoves.length === 0 && statusMessages.length === 0"
>
Enter cards and click Solve to get a solution.
</p>
<ul x-show="$store.global.solvingInProgress">
<template x-for="msg in statusMessages">
<li x-text="msg"></li>
</template>
</ul>
<!-- <p x-show="" x-text="statusMessage"></p> -->
<ol x-show="solutionMoves.length > 0">
<template x-for="move in solutionMoves">
<li x-text="move"></li>
</template>
</ol>
</div>
</div>
</div>
</div>
<footer class="py-3 my-4">
<p class="text-center text-muted">
© 2022 <a href="https://73k.us/">Adam Piontek</a>
</p>
<footer class="py-3 mt-5 bg-light">
<div class="text-center text-muted">
Written in 2022 by
<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>
<script type="module" src="./js/main.js"></script>

View File

@ -3,6 +3,11 @@ import Alpine from "alpinejs";
import cardSvgs from "./cardSvgs";
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
const suits = {
C: "Clubs",
@ -65,6 +70,13 @@ Alpine.store("global", {
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
Alpine.data("playingCardsPreview", () => ({
cardSvgs,
@ -198,11 +210,16 @@ const encouragements = [
"Strive for progress, not perfection.",
"I wish only to be alive and to experience this living to the fullest.",
"This too shall pass.",
"Life is available only in the present moment.",
"Patience is the companion of wisdom.",
"The mountains are calling and I must go.",
"Give time time.",
"If you find a path with no obstacles, it probably doesn't lead anywhere",
"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.",
"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?",
@ -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.",
"Somewhere, something incredible is waiting to be known.",
"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
Alpine.data("gameSolving", () => ({
encouragements,
encourageIndex: null,
solverWorker: null,
headerText: "Solution will go here:",
headerText: "Solution",
moveCount: 23,
statusMessages: [],
solutionMoves: [],
@ -236,6 +258,7 @@ Alpine.data("gameSolving", () => ({
this.statusMessages = [];
this.nodesTried = 0;
this.nodesTriedFloor = 0;
this.encourageIndex = null;
},
onInit() {
this.solverWorker = new SolverWorker();
@ -254,10 +277,21 @@ Alpine.data("gameSolving", () => ({
)} possibilities tried. Still working`;
}
if (this.nodesTriedFloor % 250000 === 0) {
let randInRange = Math.floor(
Math.random() * this.encouragements.length
if (this.encourageIndex === null) {
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") {

View File

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