xxxxxxxxxx
652
const POWERS_OF_TEN =
[
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
10000000000,
100000000000,
1000000000000,
10000000000000,
100000000000000,
1000000000000000,
];
let count = 0;
let factorizationArray = [];
let primeToIndexMap = {};
let largestRegisteredPrime;
let primeCount;
let bgColor;
let socketFillColor;
let socketStrokeColor;
let dimColor;
let litColor;
let highlightColor;
let numberColor;
let factorColor;
let primeFactorColor;
const M = 8;
const hudM = 4;
const canvasW = 800;
const canvasH = 450;
const length = 1000000;
const rows = Math.trunc(Math.log2(length));
const cols = Math.trunc((canvasW - 2 * M) / M);
const numberSize = 16 * hudM;
const factorSize = 6 * hudM;
const hudH = numberSize + factorSize + 2 * hudM;
const socketSize = M * 0.9;
const socketRimThickness = M * 0.05;
const bulbSize = M * 0.75;
const boardL = 0;
const boardT = 0;
const boardR = canvasW;
const boardB = boardT + rows * M - 1;
const keyRepeatDelay = 0.5; // in s
let currentNumber = 0;
let digitalInput = 0;
let primeDigitalInput = 0;
let smallestRegisteredWheelDelta = Infinity;
let keyTimer = 0;
let mouseCol = -1, mouseRow = -1;
function setup()
{
createCanvas(canvasW, canvasH);
colorMode(HSB);
bgColor = color(0, 0, 10, 1);
socketFillColor = color(45, 50, 15, 1);
socketStrokeColor = color(45, 25, 100, 0.2);
dimColor = color(45, 70, 50, 0.2);
litColor = color(45, 90, 100, 0.9);
highlightColor = color(200, 60, 90, 0.7);
numberColor = color(60, 100, 100, 1);
factorColor = color(0, 0, 100, 0.5);
primeFactorColor = color(60, 100, 100, 0.5);
textAlign(LEFT, BASELINE);
GeneratePrimeFactorizationArray(length);
}
function draw()
{
background(bgColor);
ProcessMouseInput();
DrawBoard();
DrawBulbs();
DrawHud();
let incHeld = keyIsDown(UP_ARROW) || keyIsDown(RIGHT_ARROW);
let decHeld = keyIsDown(DOWN_ARROW) || keyIsDown(LEFT_ARROW);
let delta = incHeld - decHeld;
if (incHeld || decHeld)
keyTimer += deltaTime / 1000;
if (keyTimer >= keyRepeatDelay)
ProcessDeltaInput(delta);
}
function ProcessMouseInput()
{
if (!MouseInBoard())
{
mouseCol = mouseRow = -1;
return;
}
mouseCol = Math.trunc((mouseX - boardL) / M);
mouseRow = Math.trunc((mouseY - boardT) / M);
}
function mouseWheel(e)
{
let delta = GetCalibratedDelta(e);
ProcessDeltaInput(delta);
if (MouseInCanvas())
return false;
}
function MouseInRect(l, t, r, b)
{
return l <= mouseX && mouseX <= r && t <= mouseY && mouseY <= b;
}
function MouseInCanvas()
{
return MouseInRect(0, 0, width, height);
}
function MouseInBoard()
{
return MouseInRect(boardL, boardT, boardR, boardB);
}
function GetCalibratedDelta(e)
{
let magnitude = Math.max(Math.abs(e.deltaY), 1);
if (magnitude < smallestRegisteredWheelDelta)
smallestRegisteredWheelDelta = magnitude;
return -e.deltaY / magnitude;
}
function keyPressed(e)
{
let delta = 0;
let code = e.key.charCodeAt(0);
let zeroCode = "0".charCodeAt(0);
let nineCode = "9".charCodeAt(0);
if (e.key === "ArrowUp" || e.key === "ArrowRight")
delta = 1;
else if (e.key === "ArrowDown" || e.key === "ArrowLeft")
delta = -1;
else if (e.key.toUpperCase() === "R")
Reset();
else if (zeroCode <= code && code <= nineCode)
ProcessDigitInput(code - zeroCode);
else if (e.key === "Backspace" || e.key === "Delete")
Backspace();
ProcessDeltaInput(delta);
keyTimer = 0;
}
function keyReleased(e)
{
if (e.key === " ")
primeDigitalInput = 0;
}
function mouseClicked(e)
{
ProcessMouseInput();
let prime = GetPrimeAtIndex(mouseCol);
let multiplicity = mouseRow + 1;
let multiplier = 1;
do
{
multiplier *= prime;
--multiplicity;
} while (multiplicity > 0)
if (currentNumber === 0)
currentNumber = 1;
currentNumber = min(currentNumber * multiplier, length - 1);
}
function Reset()
{
currentNumber = 0;
digitalInput = 0;
primeDigitalInput = 0;
}
function ProcessDeltaInput(delta)
{
if (keyIsDown(SHIFT))
delta *= 10;
if (keyIsDown(CONTROL))
delta *= 100;
if (keyIsDown(ALT))
delta *= 1000;
Add(delta);
}
function ProcessDigitInput(digit)
{
if (keyIsDown(32)) // Spacebar
{
digitalInput = 0;
primeDigitalInput = 10 * primeDigitalInput + digit;
SetFromPrimeDigitalInput();
}
else
{
primeDigitalInput = 0;
digitalInput = 10 * digitalInput + digit;
SetFromDigitalInput();
}
}
function Backspace()
{
if (digitalInput)
{
digitalInput = Math.trunc(digitalInput / 10);
SetFromDigitalInput();
}
else
{
primeDigitalInput = Math.trunc(primeDigitalInput / 10);
SetFromPrimeDigitalInput();
}
}
function SetFromDigitalInput()
{
currentNumber = Math.min(digitalInput, length - 1);
}
function SetFromPrimeDigitalInput()
{
let index = Math.min(primeDigitalInput, primeCount)
currentNumber = GetPrimeAtIndex(index - 1);
}
function trueMod(a, b) { return ((a % b) + b) % b; }
function Add(delta)
{
currentNumber = trueMod(currentNumber + delta, length);
}
function DrawBoard()
{
strokeWeight(socketRimThickness);
stroke(socketStrokeColor);
fill(socketFillColor);
for (let row = 0; row < rows; ++row)
for (let col = 0; col < cols; ++col)
DrawSocket(col, row);
}
function DrawSocket(col, row)
{
let x = (col + 0.5) * M + boardL;
let y = (row + 0.5) * M + boardT;
circle(x, y, socketSize);
}
function DrawBulbs()
{
noStroke();
fill(litColor);
let factors = GetPrimeFactors(currentNumber);
let prevFactor, col, row;
for (let factor of factors)
{
if (factor === prevFactor)
{
++row;
}
else
{
col = GetPrimeIndex(factor);
row = 0;
}
DrawBulb(col, row);
prevFactor = factor;
}
fill(highlightColor);
for (row = 0; row <= mouseRow; ++row)
DrawHighlight(mouseCol, row);
}
function DrawBulb(col, row)
{
let x = (col + 0.5) * M + boardL;
let y = (row + 0.5) * M + boardT;
circle(x, y, bulbSize);
}
function DrawHighlight(col, row)
{
DrawBulb(col, row);
}
function DrawHud()
{
let M = hudM;
let x = 2 * hudM;
// Print the number
fill(numberColor);
textSize(numberSize);
textStyle(BOLDITALIC);
text(currentNumber, x, height - ((numberSize * 0.7) + factorSize + M));
// Build a string of its factors
let factorStr = GetPrimeFactorString(currentNumber);
// Print the factors
textSize(factorSize);
textStyle(ITALIC);
let isPrime = IsPrime(currentNumber);
fill(isPrime ? primeFactorColor : factorColor);
let y = height - (factorSize + M);
text(factorStr, x, y);
if (IsPrime(currentNumber))
{
let nWidth = textWidth(currentNumber);
let leadStr = " — the ";
let postStr = " prime";
let leadWidth = textWidth(leadStr);
let postWidth = textWidth(postStr);
fill(factorColor);
text(leadStr, x + nWidth, y);
fill(primeFactorColor);
let index = GetPrimeIndex(currentNumber) + 1;
let ord, ordWidth;
if (index <= 12)
{
ord = GetOrdinalForm(index);
text(ord, x + nWidth + leadWidth, y);
fill(factorColor);
ordWidth = textWidth(ord);
}
else
{
let indexWidth = textWidth(index);
text(index, x + nWidth + leadWidth, y);
fill(factorColor);
let ordScalar = 0.7;
textSize(factorSize * ordScalar);
ord = GetOrdinalSuffix(index);
text(ord, x + nWidth + leadWidth + indexWidth, y - factorSize * (1 - ordScalar));
ordWidth = textWidth(ord) + indexWidth;
textSize(factorSize);
}
let postX = x + nWidth + leadWidth + ordWidth;
text(postStr, postX, y);
}
}
function GetPrimeFactors(n)
{
return factorizationArray[n];
}
function GetPrimeFactorString(n)
{
let factors = GetPrimeFactors(n);
if (factors.length < 1) return "";
let output = "" + factors[0];
for (let i = 1; i < factors.length; ++i)
output += ", " + factors[i];
return output;
}
function IsPrime(n) { return factorizationArray[n].length === 1; }
function GetPrimeIndex(p) { return primeToIndexMap[p]; }
function GetPrimeAtIndex(i)
{
for (let p in primeToIndexMap)
if (primeToIndexMap[p] === i) return parseInt(p);
return 0;
}
function PrintPrimeFactors(n)
{
print(GetPrimeFactorString(n));
}
function GeneratePrimeFactorizationArray(length)
{
let startTime = performance.now();
factorizationArray = new Array(length).fill(0).map(() => []);
let sieve = new Array(length).fill(true);
let primeIndex = 0;
for (let i = 2; i < length; ++i)
{
if (sieve[i])
{
factorizationArray[i].push(i);
primeToIndexMap[i] = primeIndex;
largestRegisteredPrime = i;
++primeIndex;
count += 2;
for (let j = 2 * i; j < length; j += i)
{
sieve[j] = false;
let m = j;
do
{
factorizationArray[j].push(i);
++count;
m /= i;
} while (m % i === 0)
}
}
}
primeCount = primeIndex;
let endTime = performance.now();
let duration = endTime - startTime;
let msg = "Length: " + length + " | Primes: " + (primeIndex + 1) + " | Allocated " +
(length + count) + " numbers | Total duration: " + duration + " ms";
print("Length: " + length);
print("Primes: " + (primeIndex + 1) + " | Largest: " + largestRegisteredPrime);
print("Allocated " + (length + count) + " numbers");
print("Total duration: " + duration + " ms");
}
function GenerateSieve(n)
{
}
function GetOrdinalForm(n)
{
if (n === 1) return "first";
if (n === 2) return "second";
if (n === 3) return "third";
if (n === 4) return "fourth";
if (n === 5) return "fifth";
if (n === 6) return "sixth";
if (n === 7) return "seventh";
if (n === 8) return "eigth";
if (n === 9) return "ninth";
if (n === 10) return "tenth";
if (n === 11) return "eleventh";
if (n === 12) return "twelfth";
return n + GetOrdinalSuffix(n);
}
function GetOrdinalSuffix(n)
{
if (n > 3 && n < 21) return "th";
switch (n % 10)
{
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}
}
function Digits(n, start, count)
{
topPower = Pow10(start + count);
bottomPower = Pow10(start);
return Math.floor((n % topPower) / bottomPower);
}
function Pow10(p) { return POWERS_OF_TEN[p]; }
function FourDivisibilityTest()
{
let ones;
let tens;
let actual;
let output = "";
for (let i = 0; i < 100; ++i)
{
actual = i % 4 === 0;
output += `${actual ? "# " : " "}`;
if (i < 10)
output += " ";
output += `${i}: `;
ones = i % 10;
if (ones % 2 !== 0)
{
output += `NO - Not even (${actual ? "FAIL" : "OK"})\n`;
continue;
}
tens = Math.floor((i % 100) / 10);
if ((ones / 2) % 2 !== tens % 2)
{
output += `NO - Tens parity mismatch (${actual ? "FAIL" : "OK"})\n`;
continue;
}
output += `YES - (${actual ? "OK" : "FAIL"})\n`
}
print(output);
}
function EightDivisibilityTest()
{
let ones;
let tens;
let hund;
let actual;
let output = "";
for (let i = 0; i < 1000; ++i)
{
actual = i % 8 === 0;
output += `${actual ? "# " : " "}`;
if (i < 10)
output += " ";
else if (i < 100)
output += " ";
output += `${i}: `;
ones = i % 10;
if (ones % 2 !== 0)
{
output += `NO - Not even (${actual ? "FAIL" : "OK"})\n`;
continue;
}
tens = Math.floor((i % 100) / 10);
if ((ones / 2) % 2 !== tens % 2)
{
output += `NO - Tens parity mismatch (${actual ? "FAIL" : "OK"})\n`;
continue;
}
hund = Math.floor((i % 1000) / 100);
if ((tens / 2) % 2 !== hund % 2)
{
output += `NO - Hundreds parity mismatch (${actual ? "FAIL" : "OK"})\n`;
continue;
}
output += `YES - (${actual ? "OK" : "FAIL"})\n`
}
print(output);
}