🎮ArcadeLab

ROOT & STONE — A Tiny Puzzle

by ThunderPhoenix57
173 lines11.4 KB
▶ Play
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="theme-color" content="#f3efe4">
<title>ROOT & STONE — A Tiny Puzzle</title>
<style>
:root{--paper:#f3efe4;--ink:#253127;--green:#496b48;--green2:#70896a;--line:#d8d0bd;--cream:#fffdf7;--brown:#846c50;--red:#a45e4d}
*{box-sizing:border-box}
html,body{margin:0;height:100%;width:100%;overflow:hidden;background:var(--paper);color:var(--ink);font-family:Georgia,"Times New Roman",serif}
button{font:inherit;color:inherit;-webkit-tap-highlight-color:transparent}
#app{height:100%;width:100%;max-width:1100px;margin:auto;position:relative;overflow:hidden}
.screen{position:absolute;inset:0;display:none}.screen.on{display:block}
#home{background:
radial-gradient(circle at 15% 10%,#fffdf7 0 12%,transparent 30%),
linear-gradient(145deg,#f5f0e4,#e8e0ce)}
.homewrap{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px}
.logo{text-align:center;margin-bottom:22px}.leaf{font-size:44px;color:var(--green);line-height:1;margin-bottom:7px}
.logo h1{font-size:clamp(42px,8vw,74px);letter-spacing:8px;margin:0;font-weight:700}.logo p{margin:8px 0 0;color:#6e786b;font-size:13px;letter-spacing:3px;text-transform:uppercase}
.play{border:0;background:var(--green);color:#fffdf7;padding:17px 65px;border-radius:12px;cursor:pointer;font-size:22px;letter-spacing:2px;box-shadow:0 5px 0 #354e35}
.play:active{transform:translateY(3px);box-shadow:0 2px 0 #354e35}
.subline{margin-top:18px;color:#7c8175;font-size:12px}
.help{position:absolute;right:18px;top:18px;width:43px;height:43px;border:1px solid #c8c0ad;background:#f8f4ea;border-radius:50%;font-weight:bold;font-size:22px;cursor:pointer;box-shadow:0 3px 10px #4b45361c}
.modalback{position:absolute;inset:0;background:#26312666;display:none;align-items:center;justify-content:center;padding:20px;z-index:20}
.modalback.show{display:flex}.modal{width:min(520px,100%);background:var(--cream);border:1px solid #d1c7b2;border-radius:18px;padding:25px;box-shadow:0 25px 80px #26312633;position:relative}
.modal h2{margin:0 0 12px;font-size:28px}.modal p{color:#60695e;line-height:1.65;margin:8px 0}.close{position:absolute;right:12px;top:10px;border:0;background:none;font-size:25px;cursor:pointer;color:#7d8178}
.rule{height:1px;background:var(--line);margin:17px 0}.example{display:flex;gap:7px;align-items:center;margin:12px 0}.mini{width:28px;height:28px;background:#dce3d3;border:1px solid #aebba4;border-radius:5px}.mini.on{background:var(--green)}
#play{background:#eee9dc}
.game{height:100%;width:min(100%,1000px);margin:auto;display:flex;flex-direction:column;padding:15px 18px}
.top{display:grid;grid-template-columns:44px 1fr 44px;align-items:center;gap:10px}
.icon{width:44px;height:44px;border:1px solid #c9c1b0;background:#f8f4ea;border-radius:12px;cursor:pointer}
.title{text-align:center}.title b{display:block;font-size:17px;letter-spacing:2px}.title span{font-size:12px;color:#7c8175}
.help2{border-radius:50%;font-weight:bold;font-size:19px}
.progress{height:5px;background:#d9d2c2;border-radius:9px;margin:12px 0}.progress i{display:block;height:100%;width:0;background:var(--green);border-radius:9px;transition:.3s}
.arena{flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:13px}
.targetlabel{font-size:11px;letter-spacing:2px;color:#777d72;text-transform:uppercase}
.target{display:grid;gap:3px;width:min(250px,52vw);aspect-ratio:1;border:1px solid #d2cab9;background:#f8f4e9;padding:7px;border-radius:13px}
.cell{border-radius:4px;background:#e1dccd}.cell.fill{background:var(--green)}
.board{display:grid;gap:5px;width:min(440px,78vw);aspect-ratio:1;padding:7px;background:#d7d0bf;border-radius:17px;box-shadow:0 10px 25px #5c56481b}
.bcell{border:0;background:#f8f4e9;border-radius:7px;cursor:pointer;position:relative}
.bcell.selected{box-shadow:inset 0 0 0 3px var(--brown);background:#e9e2d3}
.bcell.fill{background:var(--green)}
.bcell.fixed{background:var(--green2);cursor:default}
.bcell.wrong{animation:shake .18s 2;background:#d9b4a8}
@keyframes shake{25%{transform:translateX(-3px)}75%{transform:translateX(3px)}}
.bottom{display:flex;align-items:center;justify-content:space-between;padding:10px 2px 2px;color:#777d72;font-size:13px}
.action{border:0;background:none;color:var(--green);font-weight:bold;cursor:pointer}
.palette{display:flex;gap:9px}.piece{width:42px;height:42px;border:1px solid #c9c1b0;background:#f8f4ea;border-radius:10px;cursor:pointer;font-weight:bold}.piece.active{background:var(--green);color:white}
.win{position:absolute;inset:0;background:#26312666;display:none;align-items:center;justify-content:center;padding:20px;z-index:10}.win.show{display:flex}
.card{width:min(440px,100%);background:var(--cream);border-radius:20px;padding:28px;text-align:center;box-shadow:0 25px 80px #26312633;border:1px solid #d2c9b7}
.card .stamp{width:64px;height:64px;border-radius:50%;margin:0 auto 13px;border:2px solid var(--green);display:grid;place-items:center;color:var(--green);font-size:30px}.card h2{font-size:30px;margin:4px}.card p{color:#697066;line-height:1.5}.stats{display:flex;justify-content:center;gap:35px;margin:18px}.stats b{display:block;font-size:20px}.stats small{color:#7b8077}
@media(max-width:600px){.game{padding:10px 12px}.target{width:min(180px,43vw)}.board{width:min(390px,86vw);gap:4px}.palette .piece{width:38px;height:38px}}
</style>
</head>
<body>
<div id="app">
<section id="home" class="screen on">
<button class="help" onclick="help()">?</button>
<div class="homewrap">
 <div class="logo"><div class="leaf">⌁</div><h1>ROOT &amp; STONE</h1><p>A small puzzle about seeing the shape</p></div>
 <button class="play" onclick="play()">PLAY</button>
 <div class="subline">Short puzzles · No timer · New board every round</div>
</div>
</section>

<section id="play" class="screen">
<div class="game">
 <div class="top">
  <button class="icon" onclick="home()">←</button>
  <div class="title"><b id="round">ROUND 1</b><span id="moves">0 MOVES</span></div>
  <button class="icon help2" onclick="help()">?</button>
 </div>
 <div class="progress"><i id="prog"></i></div>
 <div class="arena">
  <div class="targetlabel">Remember this shape</div>
  <div id="target" class="target"></div>
  <div class="targetlabel">Build it below</div>
  <div id="board" class="board"></div>
  <div class="palette" id="palette"></div>
 </div>
 <div class="bottom"><span id="bestline">Best: —</span><button class="action" onclick="reset()">RESET</button></div>
</div>
</section>

<div id="help" class="modalback">
 <div class="modal"><button class="close" onclick="help()">×</button>
  <h2>How ROOT &amp; STONE works</h2>
  <p>At the top is a small pattern. It disappears while you solve the puzzle.</p>
  <p>Your job is to recreate the exact pattern on the larger board.</p>
  <div class="example"><div class="mini on"></div><div class="mini on"></div><div class="mini"></div><span>Tap cells to place or remove stone.</span></div>
  <div class="example"><div class="mini"></div><div class="mini on"></div><div class="mini"></div><span>Choose a stone shape below to change the way pieces are placed.</span></div>
  <p>There is no clock. The challenge is remembering the shape, planning your moves, and beating your own best.</p>
 </div>
</div>

<div id="win" class="win"><div class="card">
 <div class="stamp">✓</div><h2>THE SHAPE FITS</h2><p id="winmsg">Clean solve.</p>
 <div class="stats"><div><b id="wm">0</b><small>MOVES</small></div><div><b id="wb">0</b><small>BEST</small></div></div>
 <button class="play" style="font-size:17px;padding:13px 40px" onclick="next()">NEXT</button>
</div></div>
</div>

<script>
const $=x=>document.getElementById(x);
let round=1,N=5,target=[],board=[],moves=0,selected=0,rememberTimer=0;
const KEY="root_stone_v1";
const shapes=[
 [[0,0]], [[0,0],[0,1]], [[0,0],[1,0]], [[0,0],[1,0],[0,1]],
 [[0,0],[0,1],[1,1]], [[0,0],[1,0],[2,0]], [[0,0],[0,1],[0,2]],
 [[0,0],[1,0],[2,0],[1,1]], [[0,0],[0,1],[1,1],[1,2]]
];
function store(){try{return JSON.parse(localStorage.getItem(KEY)||"{}")}catch(e){return {}}}
function save(d){localStorage.setItem(KEY,JSON.stringify(d))}
function play(){round=Math.max(1,store().round||1);$("home").classList.remove("on");$("play").classList.add("on");make()}
function home(){$("play").classList.remove("on");$("home").classList.add("on");$("win").classList.remove("show")}
function help(){$("help").classList.toggle("show")}
function rng(seed){let x=seed>>>0;return()=>{x^=x<<13;x^=x>>>17;x^=x<<5;return(x>>>0)/4294967296}}
function make(){
 N=round<4?5:round<9?6:7;let r=rng(91+round*1777),density=Math.min(.38+round*.012,.62);
 target=Array(N*N).fill(0);
 let count=0,need=Math.floor(N*N*density);
 while(count<need){let i=Math.floor(r()*N*N);if(!target[i]){target[i]=1;count++}}
 board=Array(N*N).fill(0);moves=0;selected=0;
 // Ensure target is visually interesting and not empty/solid.
 if(target.every(v=>!v)||target.every(v=>v)){return make()}
 renderTarget();renderPalette();renderBoard();
 $("round").textContent="ROUND "+round;$("moves").textContent="0 MOVES";
 let best=bestFor(round);$("bestline").textContent=best?"Best: "+best:"Best: —";
 $("prog").style.width="0%";
 // Give the player a brief look, then hide it.
 clearTimeout(rememberTimer);rememberTimer=setTimeout(()=>{$("target").style.visibility="hidden"},Math.max(900,1800-round*20));
}
function renderTarget(){
 let t=$("target");t.style.gridTemplateColumns=`repeat(${N},1fr)`;t.innerHTML="";
 target.forEach(v=>{let c=document.createElement("div");c.className="cell"+(v?" fill":"");t.appendChild(c)})
 t.style.visibility="visible";
}
function renderPalette(){
 let p=$("palette");p.innerHTML="";
 [0,1,2].forEach((v,i)=>{let b=document.createElement("button");b.className="piece"+(selected===v?" active":"");b.textContent=["●","■","+"][v];b.title=["Single stone","2-stone piece","3-stone piece"][v];b.onclick=()=>{selected=v;renderPalette()};p.appendChild(b)})
}
function cellsFor(idx,type){
 let x=idx%N,y=Math.floor(idx/N);
 let list=type===0?[[0,0]]:type===1?[[0,0],[1,0]]:[[0,0],[1,0],[0,1]];
 return list.map(([dx,dy])=>[x+dx,y+dy]);
}
function clickCell(i){
 let cells=cellsFor(i,selected);
 if(cells.some(([x,y])=>x<0||y<0||x>=N||y>=N)){flash(i);return}
 let can=cells.every(([x,y])=>!board[y*N+x]);
 cells.forEach(([x,y])=>board[y*N+x]=can?1:0);moves++;
 $("moves").textContent=moves+" MOVES";renderBoard();
 if(board.join("")===target.join(""))win();
}
function flash(i){let e=$("board").children[i];e.classList.add("wrong");setTimeout(()=>e.classList.remove("wrong"),400)}
function renderBoard(){
 let b=$("board");b.style.gridTemplateColumns=`repeat(${N},1fr)`;b.innerHTML="";
 board.forEach((v,i)=>{let e=document.createElement("button");e.className="bcell"+(v?" fill":"");e.onclick=()=>clickCell(i);b.appendChild(e)})
 let same=board.reduce((n,v,i)=>n+(v===target[i]?1:0),0);$("prog").style.width=(same/(N*N)*100)+"%";
}
function bestFor(r){let d=store();return d.bests&&d.bests[r]||0}
function win(){
 let d=store(),old=bestFor(round),best=!old||moves<old?moves:old;d.bests=d.bests||{};d.bests[round]=best;d.solved=(d.solved||0)+1;d.round=round;save(d);
 $("wm").textContent=moves;$("wb").textContent=best;$("winmsg").textContent=moves===best?"New personal best.":"Pattern reconstructed.";
 $("win").classList.add("show")
}
function next(){round++;save({...store(),round});$("win").classList.remove("show");make()}
function reset(){make()}
document.addEventListener("keydown",e=>{if(e.key==="Escape")home()});
</script>
</body>
</html>

Game Source: ROOT & STONE — A Tiny Puzzle

Creator: ThunderPhoenix57

Libraries: none

Complexity: moderate (173 lines, 11.4 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: root-stone-a-tiny-puzzle-thunderphoenix57" to link back to the original. Then publish at arcadelab.ai/publish.