Balance Puzzle
<html> <head> <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0"> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> <link href="https://fonts.googleapis.com/css2?family=Alfa+Slab+One&display=swap" rel="stylesheet"> <link href="https://1119s.wdfiles.com/local--code/balance-puzzle/2" rel="stylesheet"> </head> <body> <div class="content-wrapper"> <div id="puzzle"> <div id="balance" class="balance"> <div class="bar"> <div id="plate_left" class="plate left"> </div> <div id="plate_right" class="plate right"> </div> </div> </div> <button onclick="weigh()" class="weigh">はかる</button> <div id="bottles" class="bottles"> <div id="bottle_a" class="bottle" data-tag="A"> </div> <div id="bottle_b" class="bottle" data-tag="B"> </div> <div id="bottle_c" class="bottle" data-tag="C"> </div> <div id="bottle_d" class="bottle" data-tag="D"> </div> <div id="bottle_e" class="bottle" data-tag="E"> </div> <div id="bottle_f" class="bottle" data-tag="F"> </div> <div id="bottle_g" class="bottle" data-tag="G"> </div> </div> </div> <div id="side-bar"> <div class="side-block"> <form id="seed"> シード(数字4桁): <br> <input type="text" name="seed" size="4"> <input type="submit" value="決定" pattern="\d{4}"> </form> </div> <div class="side-block"> <button onclick="newPuzzle()">ランダム出題</button> </div> <div class="side-block"> <button onclick="genPuzzle()">このシードで再挑戦</button> </div> <div class="side-block"> <button onclick="copySeed()">シードをコピー</button> <br> <button onclick="copyURL()">シード付URLをコピー</button> </div> <div class="menu"> <span></span> <span></span> <span></span> </div> </div> <script type="text/javascript" src="https://1119s.wdfiles.com/local--code/balance-puzzle/3"></script> </div> </body> </html>
body { margin: 0; } .content-wrapper { position: relative; height: 100%; } #puzzle { display: grid; grid-template-rows: 1fr auto; } .balance { width: 60%; max-width: 25em; aspect-ratio: 4 / 3; margin: 3em auto; position: relative; } .balance::before { content: ""; position: absolute; inset: 0; z-index: 1; background-image: var(--pillar); background-position: center; background-size: 100%; background-repeat: no-repeat; } .bar { position: absolute; top: 12%; left: 10%; width: 80%; height: 2.5%; background: #000; transform-origin: center; transform: rotate(var(--a)); transition: --a 1s; } .balance.tilt-left .bar { --a: -10deg; } .balance.tilt-right .bar { --a: 10deg; } button.weigh { justify-self: center; margin: 0 auto 3em; } .plate { position: absolute; top: 50%; width: 50%; aspect-ratio: 1; transform-origin: center 0; transform: rotate(calc(var(--a) * -1)); } .plate::before { content: ""; position: absolute; inset: 0; z-index: 1; background-image: var(--plate); background-position: center; background-size: 100%; background-repeat: no-repeat; } .plate.left { left: -16%; } .plate.right{ right: -16%; } .bottles { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; width: 90%; margin: 0 auto; } .bottle { display: flex; align-items: center; justify-content: center; width: 5rem; max-width: 25%; aspect-ratio: 1; background: var(--bottle) center no-repeat; outline: solid 0.3125em transparent; outline-offset: -0.625em; cursor: pointer; } .bottle::before { content: attr(data-tag); position: relative; top: 0.625em; display: flex; align-items: center; justify-content: center; width: 1.5em; aspect-ratio: 1; border-radius: 50%; background: #000; color: #fff; font-family: "Alfa Slab One", serif; font-size: 90%; } .bottle.selected_l { outline-color: #00f; } .bottle.selected_r { outline-color: #f44; } #side-bar { position: absolute; z-index: 9; top: 0; left: -18em; width: 15em; height: 100%; border-right: solid 2px #0cf; background-color: #e4e4e4; transition: left 0.25s; } #side-bar:hover { left: 0; } .side-block { padding: 1em; border-bottom: solid 2px #888; } .side-block form { margin: 0; } #side-bar .menu { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.5em; position: fixed; z-index: -1; left: 1em; bottom: 1em; width: 3em; height: 3em; transition: opacity 0.25s; } #side-bar .menu:hover { opacity: 0; } #side-bar .menu span { display: inline-block; width: 2.5em; height: 0.25em; background: #000; } :root { --bottle: url('https://upload.wikimedia.org/wikipedia/commons/5/5f/202003_Laboratory_instrument_reagent_bottle_mono.svg'); --pillar: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHZpZXdCb3g9IjAgMCAxNjAwIDEyMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0ibTgwMCAwIDQwIDYwLTIwIDIwaC00MGwtMjAtMjB6bS0yMCA4NWg0MGwyMCAyMGgtODB6Ii8+PHBhdGggZD0ibTc2MCAxMTBoODB2OTBoLTgwem0wIDk1aDgwdjMwaC04MHoiLz48cGF0aCBkPSJtNzYwIDI0MGg4MGwyMCAxNS0yMCAxNWgtODBsLTIwLTE1em0wIDM1aDgwdjQyMGgtODB6Ii8+PHBhdGggZD0ibTc2MCA3MDBoODBsMjAgMTUtMjAgMTVoLTgwbC0yMC0xNXptMCAzNWg4MGwyMCAzMGgtMTIweiIvPjxwYXRoIGQ9Im03NDAgNzcwaDEyMHYyODBoLTEyMHptMCAyODVoMTIwbDIwIDE1LTIwIDE1aC0xMjBsLTIwLTE1eiIvPjxwYXRoIGQ9Im03NDAgMTA5MGgxMjB2MjBoLTEyMHptLTY2MCAyNWgxNDQwdjMwaC0xNDQwem0tMzAgMzVoMTUwMHY1MGgtMTUwMHoiLz48cGF0aCBkPSJtNzU4IDExMHY5MG04NCAwdi05MCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjQiLz48L3N2Zz4='); --plate: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHZpZXdCb3g9IjAgMCA2NDAgNjQwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im00MyA1OTAgMjc3LTU5MCAyNzcgNTkwIiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMTAiLz48cGF0aCBkPSJtMCA1ODBoNjQwdjIwaC0yMGwtMTAgNDBoLTU4MGwtMTAtNDBoLTIweiIvPjwvc3ZnPg=='); } @property --a { syntax: "<angle>"; initial-value: 0deg; inherits: true; }
const bottles = document.getElementById("bottles"); const btls = Array.from(bottles.children); // ボトルの選択状況の付け外し機能 btls.forEach((elm) => { elm.addEventListener("click", function() { if (this.classList.length == 1) { this.classList.add("selected_l"); } else if (this.classList.contains("selected_l")) { this.classList.replace("selected_l", "selected_r"); } else { this.classList.remove("selected_r"); } }, false); }); const bottleTypes = new Map(); const bottle_list = ["pd", "pd", "pd", "pt", "ps", "ps", "ps"]; const WEIGHTS = { pd: 1, // power drug - 劇薬 pt: 2, // potion - 魔法薬 ps: 3 // poison - 毒薬 }; // URLからシードを取得する function getSeed() { const params = new URLSearchParams(window.location.search); const pz = params.get("pz"); if (pz && /^\d{4}$/.test(pz)) { return pz; } else { return null; } } // シード付き疑似乱数生成器 // https://github.com/cprosche/mulberry32 function mulberry32(seed) { return function() { let t = seed += 0x6D2B79F5; t = Math.imul(t ^ t >>> 15, t | 1); t ^= t + Math.imul(t ^ t >>> 7, t | 61); return frac(((t ^ t >>> 14) >>> 0), 4294967296); }; } function shuffle(array, seed) { const rng = mulberry32(seed); const arr = array.slice(); for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(rng() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } function genPuzzle() { const seed = parseInt(getSeed(), 10); if (seed == null) { seed = Math.floor(Math.random() * 10000); history.pushState(null, "", `?pz=${seed}`); } const shuffled = shuffle(bottle_list, seed); btls.forEach((bottle, i) => { const id = bottle.id; bottleTypes.set(id, shuffled[i]); }); console.log(seed, shuffled); } // ページロード時にパズルを生成する document.addEventListener("DOMContentLoaded", () => { genPuzzle(); }); // 瓶の重さの合計を求める function calcWeight(bottleElements) { return bottleElements.reduce((sum, el) => { let type = bottleTypes.get(el.id); return sum + WEIGHTS[type]; }, 0); } // 左右の皿を比べる function compare(leftWeight, rightWeight) { const balance = document.getElementById("balance"); if (leftWeight > rightWeight) { balance.className = "balance tilt-left"; } else if (rightWeight > leftWeight) { balance.className = "balance tilt-right"; } else { balance.className = "balance"; } } const sel_l = document.getElementsByClassName("selected_l"); const sel_r = document.getElementsByClassName("selected_r"); function weigh() { const left = Array.from(sel_l); const right = Array.from(sel_r); const lw = calcWeight(left); const rw = calcWeight(right); compare(lw, rw); console.log(`左: ${lw}, 右: ${rw}`); } /* ==== ナビゲーション ==== */ const seedForm = document.getElementById("seed"); seedForm.addEventListener("submit", (e) => { e.preventDefault(); const input = seedForm.elements["seed"].value.trim(); if (/^\d{4}$/.test(input)) { location.href = `?pz=${input}`; } else { alert("4桁の数値を入力してください"); } }); function newPuzzle() { const newSeed = Math.floor(Math.random() * 10000).toString().padStart(4, "0"); location.href = `?pz=${newSeed}`; } function copySeed() { const seed = getSeed(); navigator.clipboard.writeText(seed) .then(() => alert(`シード ${seed} をコピーしました`)) .catch(() => alert("コピーに失敗しました")); } function copyURL() { const url = location.href; navigator.clipboard.writeText(url) .then(() => alert("URLをコピーしました")) .catch(() => alert("コピーに失敗しました")); } // 除算記号を引用符判定するのやめてください function frac(dividend, divisor) { return dividend / divisor; }
ページリビジョン: 54, 最終更新: 20 Jul 2025 09:21





