LLM used to take keywords (or URL) and come up with list of words and clues…
What’s more fun than a classic game of Solitaire? They can be too simple, though…
AI can do better. Besides writing the game, a bonus is iterating through hundreds of true random deck shuffles to find one that you can play in “hard mode”.
Playable on the web with an easy UI.
(about 1 in 400 deals, you can’t move any card. I do that many to make an impossible hard game…or alternately, guarantee you can do something.)
That’s seriously my childhood…I’ll need to test that one out…
quick question…is it desktop only? Because it didn’t work on my phone.
The mad scientists are at it again.
If you thought “hard” mode in the previous Solitaire game was maybe a touch too hard, here’s another classic, with algorithms re-imagined to maximize frustration.
Minesweeper
Gameplay:
- This will take some time to load and pregenerate a cache of games to pull from, as there is a game solver working in the background;
- Click and avoid mines (your first click will always be safe)
- Use the numbers to infer the neighbors that have mines.
- Shift-click to mark your discoveries (or right-click on browsers that allow the context menu to be shut off)
- Always be stuck somewhere and have to make a potentially fatal guess!
(Making a game is easy; the underpinnings took many iterations of spelling out algorithms to AI models that could not understand…)
I wonder how many remember as a kid looking at the patterns on the curtains as they fell asleep… Something I was talking to my daughter about this evening…
Getting the tooling right is certainly the first step…
After that it cuts PHAS. ![]()
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Scissor Cut Patterns (Animated + Reliable Sound)</title>
<style>
html,body{height:100%;margin:0;background:#111;color:#eee;font-family:system-ui,Segoe UI,Arial}
.wrap{display:flex;flex-direction:column;gap:10px;max-width:980px;margin:0 auto;padding:14px}
.top{display:flex;flex-wrap:wrap;gap:10px;align-items:center;justify-content:space-between}
.panel{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
button,select{background:#222;color:#eee;border:1px solid #333;border-radius:10px;padding:10px 12px;cursor:pointer}
button:hover,select:hover{border-color:#666}
.stat{padding:8px 10px;border:1px solid #333;border-radius:10px;background:#161616}
canvas{background:#0b0b0b;border:1px solid #333;border-radius:14px;box-shadow:0 10px 30px rgba(0,0,0,.35)}
.hint{opacity:.85;font-size:13px;line-height:1.35}
.kbd{display:inline-block;padding:2px 7px;border:1px solid #444;border-bottom-width:2px;border-radius:8px;background:#1a1a1a}
</style>
</head>
<body>
<div class="wrap">
<div class="top">
<div class="panel">
<button id="newBtn">New Pattern</button>
<button id="resetBtn">Reset Cut</button>
<button id="soundBtn">Enable Sound</button>
<select id="patternSel">
<option value="zigzag">Zigzag Strip</option>
<option value="circle">Circle</option>
<option value="star">Star</option>
<option value="heart">Heart</option>
<option value="letters">Letters (PHAS)</option>
</select>
<select id="difficultySel">
<option value="easy">Easy</option>
<option value="normal" selected>Normal</option>
<option value="hard">Hard</option>
</select>
</div>
<div class="panel">
<div class="stat">Score: <span id="score">0</span></div>
<div class="stat">Accuracy: <span id="acc">0</span>%</div>
<div class="stat">Time: <span id="time">0.0</span>s</div>
<div class="stat">Mode: <span id="mode">Trace</span></div>
<div class="stat">Sound: <span id="soundState">Off</span></div>
</div>
</div>
<div class="hint">
<b>Trace mode:</b> hold mouse/touch and move to cut.<br/>
<b>Snip mode:</b> press <span class="kbd">Space</span>, then click/tap to snip (blades animate + sound) and cut short segments.
Press <span class="kbd">N</span> new, <span class="kbd">R</span> reset.
</div>
<canvas id="c" width="960" height="540"></canvas>
</div>
<script>
(() => {
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const accEl = document.getElementById('acc');
const timeEl = document.getElementById('time');
const modeEl = document.getElementById('mode');
const soundStateEl = document.getElementById('soundState');
const newBtn = document.getElementById('newBtn');
const resetBtn = document.getElementById('resetBtn');
const soundBtn = document.getElementById('soundBtn');
const patternSel = document.getElementById('patternSel');
const difficultySel = document.getElementById('difficultySel');
const W = canvas.width, H = canvas.height;
// -------- Sound (reliable unlock pattern) --------
let audioCtx = null;
let soundEnabled = false;
function ensureAudioFromGesture(){
const AC = window.AudioContext || window.webkitAudioContext;
if(!audioCtx) audioCtx = new AC();
if(audioCtx.state === 'suspended') audioCtx.resume();
soundEnabled = true;
if(soundStateEl){
soundStateEl.textContent = (audioCtx.state === 'running') ? "On" : audioCtx.state;
}
}
function snipSound(){
if(!soundEnabled || !audioCtx || audioCtx.state !== 'running') return;
const t0 = audioCtx.currentTime;
// noise burst
const bufferSize = Math.floor(audioCtx.sampleRate * 0.06);
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for(let i=0;i<bufferSize;i++){
const env = Math.exp(-i/(bufferSize*0.22));
data[i] = (Math.random()*2 - 1) * env * 0.9;
}
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const bp = audioCtx.createBiquadFilter();
bp.type = 'bandpass';
bp.frequency.setValueAtTime(1800, t0);
bp.Q.setValueAtTime(1.2, t0);
const gain = audioCtx.createGain();
gain.gain.setValueAtTime(0.0001, t0);
gain.gain.exponentialRampToValueAtTime(0.45, t0 + 0.005);
gain.gain.exponentialRampToValueAtTime(0.0001, t0 + 0.06);
noise.connect(bp).connect(gain).connect(audioCtx.destination);
noise.start(t0);
noise.stop(t0 + 0.07);
// metallic ping
const osc = audioCtx.createOscillator();
osc.type = 'triangle';
const og = audioCtx.createGain();
og.gain.setValueAtTime(0.0001, t0);
og.gain.exponentialRampToValueAtTime(0.18, t0 + 0.004);
og.gain.exponentialRampToValueAtTime(0.0001, t0 + 0.10);
osc.frequency.setValueAtTime(2900, t0);
osc.frequency.exponentialRampToValueAtTime(2200, t0 + 0.10);
osc.connect(og).connect(audioCtx.destination);
osc.start(t0);
osc.stop(t0 + 0.12);
}
// -------- Game State --------
const state = {
path: [],
cut: [],
isCutting: false,
startedAt: null,
finishedAt: null,
snipMode: false,
snipAnchor: null,
tolerance: 18,
lineWidth: 10,
dash: [10, 10],
minProgressToFinish: 0.92,
sc: {
x: W*0.5,
y: H*0.5,
angle: 0,
open: 1,
snipT: 0,
snipping: false,
vx: 0,
vy: 0
}
};
function setDifficulty(level){
if(level === 'easy'){
state.tolerance = 26;
state.lineWidth = 12;
state.dash = [12, 12];
state.minProgressToFinish = 0.88;
} else if(level === 'hard'){
state.tolerance = 12;
state.lineWidth = 9;
state.dash = [8, 10];
state.minProgressToFinish = 0.95;
} else {
state.tolerance = 18;
state.lineWidth = 10;
state.dash = [10, 10];
state.minProgressToFinish = 0.92;
}
}
function rnd(a,b){ return a + Math.random()*(b-a); }
function resample(points, step=6){
if(points.length < 2) return points.slice();
const out = [points[0]];
let carry = 0;
for(let i=1;i<points.length;i++){
const a = points[i-1], b = points[i];
const dx = b.x - a.x, dy = b.y - a.y;
const segLen = Math.hypot(dx,dy);
if(segLen < 1e-6) continue;
let dist = 0;
while(dist + (step - carry) <= segLen){
const t = (dist + (step - carry)) / segLen;
out.push({x: a.x + dx*t, y: a.y + dy*t});
dist += (step - carry);
carry = 0;
}
carry += segLen - dist;
if(carry >= step) carry = 0;
}
return out;
}
function makeZigZag(){
const margin = 90;
const pts = [];
const y = H*0.55;
const amp = H*0.18;
const waves = 9;
for(let i=0;i<=waves;i++){
const x = margin + (W-2*margin) * (i/waves);
const yy = y + (i%2===0 ? -amp : amp) * 0.6;
pts.push({x, y: yy});
}
return resample(pts, 6);
}
function makeCircle(){
const cx = W*0.5, cy = H*0.55;
const r = Math.min(W,H)*0.22;
const pts = [];
const n = 260;
for(let i=0;i<=n;i++){
const t = (i/n) * Math.PI*2;
pts.push({x: cx + Math.cos(t)*r, y: cy + Math.sin(t)*r});
}
return pts;
}
function makeStar(){
const cx = W*0.5, cy = H*0.55;
const r1 = Math.min(W,H)*0.24;
const r2 = r1*0.45;
const poly = [];
const spikes = 5;
for(let i=0;i<spikes*2;i++){
const r = (i%2===0) ? r1 : r2;
const t = -Math.PI/2 + (i/(spikes*2))*Math.PI*2;
poly.push({x: cx + Math.cos(t)*r, y: cy + Math.sin(t)*r});
}
poly.push(poly[0]);
return resample(poly, 5);
}
function makeHeart(){
const cx = W*0.5, cy = H*0.58;
const s = Math.min(W,H)*0.0135;
const pts = [];
const n = 420;
for(let i=0;i<=n;i++){
const t = (i/n)*Math.PI*2;
const x = 16*Math.pow(Math.sin(t),3);
const y = 13*Math.cos(t)-5*Math.cos(2*t)-2*Math.cos(3*t)-Math.cos(4*t);
pts.push({x: cx + x*s*14, y: cy - y*s*14});
}
return pts;
}
function makeLettersPHAS(){
const top = H*0.30, base = H*0.75;
const left = W*0.16;
const gap = W*0.14;
const stroke = [];
const w = W*0.10;
const add = (x,y) => stroke.push({x,y});
// P
add(left, base); add(left, top); add(left+w, top); add(left+w, (top+base)/2.1); add(left, (top+base)/2.1);
// H
const xH = left + gap;
add(xH, top); add(xH, base);
add(xH+w, top); add(xH+w, base);
add(xH, (top+base)/2); add(xH+w, (top+base)/2);
// A
const xA = left + gap*2;
add(xA, base); add(xA+w/2, top); add(xA+w, base);
add(xA+w*0.25, (top+base)/1.7); add(xA+w*0.75, (top+base)/1.7);
// S
const xS = left + gap*3;
add(xS+w, top); add(xS, top); add(xS, (top+base)/2.05); add(xS+w, (top+base)/2.05); add(xS+w, base); add(xS, base);
return resample(stroke, 5);
}
function makePattern(kind){
if(kind === 'circle') return makeCircle();
if(kind === 'star') return makeStar();
if(kind === 'heart') return makeHeart();
if(kind === 'letters') return makeLettersPHAS();
return makeZigZag();
}
function clearCut(){
state.cut = [];
state.isCutting = false;
state.startedAt = null;
state.finishedAt = null;
state.snipAnchor = null;
updateHUD();
}
function newPattern(){
setDifficulty(difficultySel.value);
state.path = makePattern(patternSel.value);
clearCut();
}
function clamp(v,a,b){ return Math.max(a, Math.min(b, v)); }
function getPos(e){
const r = canvas.getBoundingClientRect();
const p = ('touches' in e && e.touches.length) ? e.touches[0] : (e.changedTouches && e.changedTouches.length ? e.changedTouches[0] : e);
const x = p.clientX - r.left;
const y = p.clientY - r.top;
const sx = W / r.width, sy = H / r.height;
return {x: x*sx, y: y*sy};
}
function dist2(a,b){
const dx=a.x-b.x, dy=a.y-b.y;
return dx*dx+dy*dy;
}
function nearestIndexOnPath(p){
const path = state.path;
if(path.length === 0) return 0;
let start = 0, end = path.length-1;
if(state.cut.length > 0){
const last = state.cut[state.cut.length-1];
const hint = last._idx ?? 0;
start = clamp(hint - 80, 0, path.length-1);
end = clamp(hint + 140, 0, path.length-1);
}
let best = start, bestD = Infinity;
for(let i=start;i<=end;i++){
const d = dist2(p, path[i]);
if(d < bestD){ bestD = d; best = i; }
}
return best;
}
function pushCutPoint(p){
const idx = nearestIndexOnPath(p);
const onPath = Math.sqrt(dist2(p, state.path[idx])) <= state.tolerance;
state.cut.push({...p, _idx: idx, _on: onPath});
}
function progress(){
if(state.cut.length === 0) return 0;
let maxIdx = 0;
for(const p of state.cut) maxIdx = Math.max(maxIdx, p._idx ?? 0);
return maxIdx / (state.path.length-1);
}
function accuracy(){
if(state.cut.length === 0) return 0;
let good = 0;
for(const p of state.cut) if(p._on) good++;
return good / state.cut.length;
}
function computeScore(){
const acc = accuracy();
const prog = progress();
const done = prog >= state.minProgressToFinish;
const now = performance.now();
const t = (state.startedAt && (done ? state.finishedAt : now))
? (((done ? state.finishedAt : now) - state.startedAt) / 1000)
: 0;
let s = 0;
s += Math.round(acc * 700);
s += Math.round(prog * 300);
if(done){
const timeBonus = Math.round(200 * (1 / (1 + t/12)));
s += timeBonus + 150;
}
return {score: s, acc: acc, time: t, done};
}
function updateHUD(){
const {score, acc, time, done} = computeScore();
scoreEl.textContent = score;
accEl.textContent = Math.round(acc*100);
timeEl.textContent = done ? (time.toFixed(1) + " ✓") : time.toFixed(1);
modeEl.textContent = state.snipMode ? "Snip" : "Trace";
}
// -------- Scissors animation --------
function startSnip(){
if(state.sc.snipping) return;
state.sc.snipping = true;
state.sc.snipT = 0;
snipSound();
}
function updateScissors(dt){
const target = state.cut.length ? state.cut[state.cut.length-1] : null;
if(target){
const sc = state.sc;
const ax = (target.x - sc.x) * 18;
const ay = (target.y - sc.y) * 18;
sc.vx = (sc.vx + ax*dt) * 0.82;
sc.vy = (sc.vy + ay*dt) * 0.82;
sc.x += sc.vx * dt;
sc.y += sc.vy * dt;
if(state.cut.length > 1){
const prev = state.cut[state.cut.length-2];
sc.angle = Math.atan2(target.y - prev.y, target.x - prev.x);
}
}
const sc = state.sc;
if(sc.snipping){
sc.snipT += dt * 5.5;
const t = clamp(sc.snipT, 0, 1);
const e = t < 0.5 ? (2*t*t) : (1 - Math.pow(-2*t+2,2)/2);
sc.open = 1 - Math.sin(e * Math.PI);
if(sc.snipT >= 1){
sc.snipping = false;
sc.snipT = 0;
sc.open = 1;
}
} else {
if(state.isCutting && !state.snipMode) sc.open = 0.35;
else sc.open = 1;
}
}
function drawScissors(){
const sc = state.sc;
if(!state.cut.length) return;
ctx.save();
ctx.translate(sc.x, sc.y);
ctx.rotate(sc.angle);
const bladeLen = 34;
const handleR = 7;
const openAngle = (0.55 * sc.open);
if(state.isCutting || sc.snipping){
ctx.globalAlpha = 0.25;
ctx.fillStyle = "#00d3ff";
ctx.beginPath();
ctx.arc(0,0,18,0,Math.PI*2);
ctx.fill();
ctx.globalAlpha = 1;
}
ctx.lineCap = "round";
ctx.lineWidth = 2.2;
ctx.strokeStyle = "#cfd3da";
function blade(theta){
ctx.save();
ctx.rotate(theta);
ctx.beginPath();
ctx.moveTo(-3, 0);
ctx.lineTo(bladeLen, 0);
ctx.stroke();
ctx.restore();
}
blade(+openAngle);
blade(-openAngle);
ctx.fillStyle = "#cfd3da";
ctx.beginPath();
ctx.arc(0,0,2.4,0,Math.PI*2);
ctx.fill();
ctx.strokeStyle = "#cfd3da";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(-10, -7, handleR, 0, Math.PI*2);
ctx.arc(-10, +7, handleR, 0, Math.PI*2);
ctx.stroke();
if(state.snipMode){
ctx.globalAlpha = 0.85;
ctx.fillStyle = "rgba(0,211,255,.22)";
ctx.fillRect(bladeLen+4, -10, 20, 20);
}
ctx.restore();
}
// -------- Drawing --------
function draw(){
ctx.clearRect(0,0,W,H);
ctx.save();
ctx.globalAlpha = 0.08;
for(let i=0;i<1200;i++){
ctx.fillRect(rnd(0,W), rnd(0,H), 1, 1);
}
ctx.restore();
ctx.save();
ctx.lineWidth = state.lineWidth;
ctx.setLineDash(state.dash);
ctx.lineCap = "round";
ctx.strokeStyle = "#f2f2f2";
ctx.beginPath();
for(let i=0;i<state.path.length;i++){
const p = state.path[i];
if(i===0) ctx.moveTo(p.x,p.y);
else ctx.lineTo(p.x,p.y);
}
ctx.stroke();
ctx.restore();
if(state.path.length){
const a = state.path[0];
const b = state.path[state.path.length-1];
ctx.fillStyle = "#00d3ff";
ctx.beginPath(); ctx.arc(a.x,a.y,7,0,Math.PI*2); ctx.fill();
ctx.fillStyle = "#00ff7a";
ctx.beginPath(); ctx.arc(b.x,b.y,7,0,Math.PI*2); ctx.fill();
}
if(state.cut.length){
ctx.save();
ctx.lineWidth = 6;
ctx.lineCap = "round";
ctx.setLineDash([]);
ctx.strokeStyle = "#ffcc00";
ctx.beginPath();
for(let i=0;i<state.cut.length;i++){
const p = state.cut[i];
if(i===0) ctx.moveTo(p.x,p.y);
else ctx.lineTo(p.x,p.y);
}
ctx.stroke();
for(const p of state.cut){
if(!p._on){
ctx.fillStyle = "rgba(255,70,70,.8)";
ctx.beginPath(); ctx.arc(p.x,p.y,3,0,Math.PI*2); ctx.fill();
}
}
ctx.restore();
}
drawScissors();
const {done} = computeScore();
if(done){
ctx.save();
ctx.globalAlpha = 0.9;
ctx.fillStyle = "rgba(0,0,0,.45)";
ctx.fillRect(0,0,W,H);
ctx.fillStyle = "#fff";
ctx.font = "700 44px system-ui, Segoe UI, Arial";
ctx.textAlign = "center";
ctx.fillText("Pattern Cut!", W/2, H/2 - 10);
ctx.font = "500 16px system-ui, Segoe UI, Arial";
ctx.fillText("Press New Pattern or Reset Cut", W/2, H/2 + 26);
ctx.restore();
}
updateHUD();
}
let lastT = performance.now();
function loop(now){
const dt = Math.min(0.03, (now - lastT) / 1000);
lastT = now;
updateScissors(dt);
draw();
requestAnimationFrame(loop);
}
// -------- Cutting logic --------
function startCut(){
if(state.finishedAt) return;
state.isCutting = true;
if(!state.startedAt) state.startedAt = performance.now();
}
function endCut(){
state.isCutting = false;
const prog = progress();
if(!state.finishedAt && prog >= state.minProgressToFinish){
state.finishedAt = performance.now();
}
}
function doSnip(p){
if(state.finishedAt) return;
startCut();
startSnip();
if(!state.snipAnchor){
state.snipAnchor = p;
pushCutPoint(p);
endCut();
return;
}
const a = state.snipAnchor;
const dx = p.x - a.x, dy = p.y - a.y;
const len = Math.hypot(dx,dy);
const step = 6;
const steps = Math.max(1, Math.floor(len/step));
for(let i=1;i<=steps;i++){
const t = i/steps;
pushCutPoint({x: a.x + dx*t, y: a.y + dy*t});
}
state.snipAnchor = p;
endCut();
}
// -------- Input --------
// Reliable audio unlock paths
soundBtn.addEventListener('click', () => ensureAudioFromGesture());
canvas.addEventListener('pointerdown', () => ensureAudioFromGesture(), { once:true });
canvas.addEventListener('mousedown', (e)=>{
e.preventDefault();
const p = getPos(e);
if(state.snipMode){
doSnip(p);
return;
}
startCut();
pushCutPoint(p);
});
window.addEventListener('mousemove', (e)=>{
if(state.snipMode) return;
if(!state.isCutting || state.finishedAt) return;
pushCutPoint(getPos(e));
});
window.addEventListener('mouseup', ()=>{
if(state.snipMode) return;
endCut();
});
canvas.addEventListener('touchstart', (e)=>{
e.preventDefault();
const p = getPos(e);
if(state.snipMode){
doSnip(p);
return;
}
startCut();
pushCutPoint(p);
}, {passive:false});
canvas.addEventListener('touchmove', (e)=>{
e.preventDefault();
if(state.snipMode) return;
if(!state.isCutting || state.finishedAt) return;
pushCutPoint(getPos(e));
}, {passive:false});
canvas.addEventListener('touchend', (e)=>{
if(state.snipMode) return;
endCut();
});
// -------- Controls --------
newBtn.addEventListener('click', newPattern);
resetBtn.addEventListener('click', clearCut);
patternSel.addEventListener('change', newPattern);
difficultySel.addEventListener('change', newPattern);
window.addEventListener('keydown', (e)=>{
if(e.code === 'Space'){
e.preventDefault();
state.snipMode = !state.snipMode;
state.snipAnchor = null;
if(state.snipMode) startSnip();
updateHUD();
}
if(e.key === 'r' || e.key === 'R') clearCut();
if(e.key === 'n' || e.key === 'N') newPattern();
});
// Init
newPattern();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
I see your snipping tool, and was inspired to write another hosted UI - let the child in you program!
Another URL, with the query being run shown:
This application lets you write drawing programs in the LOGO language, which guide your turtle cursor into pretty designs depending on your creativity. Another bonus: the URL bar is rewritten with the program updates encoded as a query string, so you can share links to a program, like the above.
Don’t know what you’d write? Challenge an AI to write for you (not escaping a canvas area about 500x300 depending on your browser).
COMMAND SUPPORT STATUS:
Movement Commands
FORWARD/FD distance- Moves turtle forwardBACK/BK distance- Moves turtle backwardLEFT/LT angle- Turns left (counterclockwise)RIGHT/RT angle- Turns right (clockwise)HOME- Returns to origin (0,0) and heading 0
Pen Control
PENUP/PU- Lifts pen (no drawing)PENDOWN/PD- Lowers pen (draws)SETPENCOLOR/SETPC color- Sets pen colorSETPENSIZE width- Sets pen width
Position Commands
SETXY x y- Sets position (draws if pen down)SETX x- Sets X coordinateSETY y- Sets Y coordinateSETHEADING/SETH angle- Sets heading direction
Canvas Commands
CLEARSCREEN/CS- Clears and homes turtleCLEAN- Clears drawing onlyHIDETURTLE/HT- Hides turtle cursorSHOWTURTLE/ST- Shows turtle cursor
Control Structures
REPEAT count [commands]- Loops commandsTO name ... END- Defines procedures
Artifact v17 done elsewhere from iterations in vibe coding, while here we still are thinking on the initial prompt…
and failing, leaving nothing from the thinking that can be restarted on.
@_j same question as above….because I tried this also on my phone and got “bad gateway error 502”..or is it just my phone?
It is a symptom of free hosting that I use for sharing trivialities. The web server restarts every two hours to update DNS records and accounts which takes some time, 5-10 minutes. Since these little web apps have no backend and do not conduct a session, it just means you have to try again in a bit to load the page.
Not the same question as above, where I show that ChatGPT fails on my prompt. Just the same service level as far as rate of success in randomly-timed attempts.










