xxxxxxxxxx
170
// https://www.instructables.com/Pi-from-Toothpicks/
// Elements
let canvas;
let addPickBtn;
let countP;
let resultsP;
let recordP;
let autoplayLabel;
let autoplaySlider;
let showCanvasCheckbox;
let showGridCheckbox;
let showDebugCheckbox;
let showDebugPickCheckbox;
let highlightOverlapsCheckbox;
let debugPick;
// State
const picks = [];
const record = {
estimate: 0,
error: Infinity
};
const estimates = [];
// Helpers
const getOverlaps = () => picks.filter(e => e.overlaps).length;
const getEstimate = () => getOverlaps() ? 2 * picks.length / getOverlaps() : 0;
const getError = estimate => abs(PI - estimate);
const getMedian = numbers => {
const sorted = [numbers].sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
}
const createCustomP = (html) => {
const p = createP(html);
p.style('margin', '0.5em 0');
return p;
}
const getAutoplaySpeed = () => autoplaySlider.value() ? Math.pow(2, autoplaySlider.value() - 1) : 0;
function addPick() {
const pick = new Pick(
random(width - EDGES) + EDGES / 2,
random(height - EDGES) + EDGES / 2,
random(PI * 2)
);
picks.push(pick);
updateResults();
}
function updateResults() {
const estimate = getEstimate();
const error = getError(estimate);
estimates.push(estimate);
countP.html(`Picks: ${picks.length} | Crossings: ${getOverlaps()}`);
if(error < record.error) {
record.estimate = estimate;
record.error = error;
}
const avg = estimates.reduce((a, b) => a + b, 0) / estimates.length;
const avgError = getError(avg);
const med = getMedian(estimates);
const medError = getError(med);
const outputTemplate = (label, est, err) => `${label}: ${est} | Error: ${err}`;
// const outputTemplate = (label, est, err) => `${label}: <abbr title="Error: ${err}">${est}</abbr>`;
const outputs = [
['Current', estimate, error],
['Record', record.estimate, record.error],
['Average', avg, avgError],
['Median', med, medError]
];
resultsP.html(outputs.map(e => outputTemplate(e)).join(' <br /> '));
}
function setAutoplayLabel() {
const speed = getAutoplaySpeed();
const state = speed ? `x${speed}` : 'Off';
autoplayLabel.html(`Autoplay (${state}): `)
}
// Sketch
function setup() {
canvas = createCanvas(600, 400);
canvas.mouseClicked(() => addPick());
addPickBtn = createButton('Add Pick');
addPickBtn.mouseClicked(() => addPick());
countP = createCustomP();
resultsP = createCustomP();
recordP = createCustomP();
debugPick = new Pick(0, 0, PI / 2);
createCustomP('Configure')
.style('font-size', '1.25em')
.style('margin', '1em 0 0.5em');
autoplayLabel = createCustomP();
autoplaySlider = createSlider(0, 6, 0);
showCanvasCheckbox = createCheckbox('Show canvas', true);
showGridCheckbox = createCheckbox('Show grid', true);
showDebugCheckbox = createCheckbox('Show debug graphics', false);
showDebugPickCheckbox = createCheckbox('Show debug pick', false);
highlightOverlapsCheckbox = createCheckbox('Highlight overlaps', true);
}
function draw() {
background(30);
setAutoplayLabel();
for(let i = 0; i < getAutoplaySpeed(); i++) addPick();
if(!showCanvasCheckbox.checked()) {
canvas.hide();
return;
}
canvas.show();
if(showGridCheckbox.checked()) {
const linesToDraw = width / SPACING;
for(let i = 0; i <= linesToDraw; i++) {
stroke(255);
line(0, i * SPACING, width, i * SPACING);
}
}
for(const pick of picks) {
pick.draw(
highlightOverlapsCheckbox.checked(),
showDebugCheckbox.checked()
);
}
if(showDebugPickCheckbox.checked()) {
if(keyIsDown(LEFT_ARROW)) debugPick.angle -= PI / 180;
if(keyIsDown(RIGHT_ARROW)) debugPick.angle += PI / 180;
debugPick.update();
debugPick.draw(true, true);
}
}
function mouseMoved() {
if(showDebugPickCheckbox.checked()) {
debugPick.x = mouseX;
debugPick.y = mouseY;
debugPick.update();
}
}