feat(binary): add full binary simulator with unsigned and two’s complement modes

- Introduce new Binary Simulator page with adjustable bit width (4–16 bits)
- Support unsigned and two’s complement representations with live conversion
- Add left/right shift operations and custom binary/denary input
- Implement accessible bulb-and-switch UI with MD3-inspired styling
- Add seven-segment display font assets for realistic number output
- Establish shared base layout, styles, and tooling for future simulators

Signed-off-by: Alexander Lyall <alex@adcm.uk>
This commit is contained in:
2025-12-14 16:57:31 +00:00
parent 7a694bf400
commit 50829688e3
113 changed files with 6769 additions and 6663 deletions

View File

@@ -0,0 +1,115 @@
// Browser-only script. Safe because it's loaded via <script> (not server-imported).
const BIT_COUNT = 8; // unsigned page = 8 bits
const bitValues = [128, 64, 32, 16, 8, 4, 2, 1];
const elDenary = document.getElementById("denaryNumber");
const elBinary = document.getElementById("binaryNumber");
const elSwitches = document.getElementById("bitSwitches");
const btnCustomDenary = document.getElementById("btnCustomDenary");
const btnCustomBinary = document.getElementById("btnCustomBinary");
const btnReset = document.getElementById("btnReset");
let bits = new Array(BIT_COUNT).fill(false);
function renderSwitches() {
elSwitches.innerHTML = "";
bitValues.forEach((value, index) => {
const id = `bit-${value}`;
const wrapper = document.createElement("div");
wrapper.className = "switch-col";
const labelTop = document.createElement("div");
labelTop.className = "bit-label";
labelTop.textContent = value;
const label = document.createElement("label");
label.className = "rocker";
label.setAttribute("for", id);
const input = document.createElement("input");
input.type = "checkbox";
input.id = id;
input.checked = bits[index];
input.addEventListener("change", () => {
bits[index] = input.checked;
updateNumbers();
});
const span = document.createElement("span");
span.className = "rocker-body";
span.setAttribute("aria-hidden", "true");
label.appendChild(input);
label.appendChild(span);
wrapper.appendChild(labelTop);
wrapper.appendChild(label);
elSwitches.appendChild(wrapper);
});
}
function updateNumbers() {
const binary = bits.map(b => (b ? "1" : "0")).join("");
const denary = bits.reduce((acc, b, i) => acc + (b ? bitValues[i] : 0), 0);
elBinary.textContent = binary;
elDenary.textContent = denary.toString();
}
function resetAll() {
bits = new Array(BIT_COUNT).fill(false);
renderSwitches();
updateNumbers();
}
function requestCustomDenary() {
let input = prompt(`Enter a denary number (0 to 255):`);
if (input === null) return;
const n = Number.parseInt(input, 10);
if (Number.isNaN(n) || n < 0 || n > 255) {
alert("Invalid input. Enter a number from 0 to 255.");
return;
}
let remaining = n;
bits = bitValues.map(v => {
if (remaining >= v) {
remaining -= v;
return true;
}
return false;
});
renderSwitches();
updateNumbers();
}
function requestCustomBinary() {
let input = prompt(`Enter an ${BIT_COUNT}-bit binary number (e.g. 01010101):`);
if (input === null) return;
input = input.trim();
const re = new RegExp(`^[01]{${BIT_COUNT}}$`);
if (!re.test(input)) {
alert(`Invalid input. Enter exactly ${BIT_COUNT} digits using only 0 or 1.`);
return;
}
bits = input.split("").map(c => c === "1");
renderSwitches();
updateNumbers();
}
btnCustomDenary?.addEventListener("click", requestCustomDenary);
btnCustomBinary?.addEventListener("click", requestCustomBinary);
btnReset?.addEventListener("click", resetAll);
renderSwitches();
updateNumbers();

View File

@@ -0,0 +1,72 @@
// public/js/tools/unsigned-binary.js
// Lightweight: no frameworks. Works on weak devices.
const BIT_COUNT = 8;
const MAX_DENARY = 255;
let bits = new Array(BIT_COUNT).fill(false);
function bitsToBinaryString(){
return bits.map(b => (b ? "1" : "0")).join("");
}
function bitsToDenary(){
// MSB on the left: 128..1
const weights = [128,64,32,16,8,4,2,1];
return bits.reduce((acc, b, i) => acc + (b ? weights[i] : 0), 0);
}
function render(){
const grid = document.getElementById("bitGrid");
grid.innerHTML = "";
const weights = [128,64,32,16,8,4,2,1];
bits.forEach((on, i) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "btn";
btn.style.width = "100%";
btn.style.justifyContent = "space-between";
btn.setAttribute("aria-pressed", on ? "true" : "false");
btn.innerHTML = `<span>${weights[i]}</span><b>${on ? "1" : "0"}</b>`;
btn.addEventListener("click", () => {
bits[i] = !bits[i];
update();
});
grid.appendChild(btn);
});
}
function update(){
document.getElementById("binaryNumber").innerText = bitsToBinaryString();
document.getElementById("denaryNumber").innerText = bitsToDenary();
render();
}
function requestBinary(){
let v;
do{
v = prompt("Enter an 8-bit binary value (8 digits, only 0 or 1):", bitsToBinaryString());
if(v === null) return;
v = v.trim();
}while(!/^[01]{8}$/.test(v));
bits = v.split("").map(ch => ch === "1");
update();
}
function requestDenary(){
let v;
do{
v = prompt("Enter a denary value (0 to 255):", String(bitsToDenary()));
if(v === null) return;
v = Number(v);
}while(!Number.isInteger(v) || v < 0 || v > MAX_DENARY);
// set bits from MSB to LSB
const weights = [128,64,32,16,8,4,2,1];
bits = weights.map(w => {
if(v >= w){ v -= w; return true; }
return false;
});
update();
}
function reset(){
bits = new Array(BIT_COUNT).fill(false);
update();
}
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("btnCustomBinary")?.addEventListener("click", requestBinary);
document.getElementById("btnCustomDenary")?.addEventListener("click", requestDenary);
document.getElementById("btnReset")?.addEventListener("click", reset);
update();
});