JSFiddle - React, Tailwind, and code Playground

by Josh Pullen

HTML

<div id="timer"></div>
<canvas id="canvas"></canvas>
<p><b style="color: red">Prototype!</b> Click on the canvas to place a point. Try to find the absolute minimum of the invisible function as quickly as possible!</p>

CSS

#timer {
  color: red;
}

JavaScript

const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext("2d");

canvas.width = 640;
canvas.height = 480;

const timer = document.querySelector("#timer");
let timerStart = NaN;
let timerRunning = false;
function startTimer() {
	timerStart = Date.now();
  timerRunning = true;
  updateTimer();
}
function updateTimer() {
	if (timerRunning) {
  	requestAnimationFrame(updateTimer);
  }

  const s = Math.round((Date.now() - timerStart) || 0) / 1000;
  timer.innerText = s;
}
function stopTimer() {
	timerRunning = false;
}

/*
const polynomial = [0, -2, 0.3, 0.5, 0.065];

function f(x) {
	const sum = (values) => values.reduce((a, b) => a + b, 0);
	return sum(polynomial.map((term, n) => term * x ** n));
}
*/

const functions = [
	x => 0.005 * (x + 7) * (x + 1) * (x - 5)**2,
  x => 0.0002 * (x+7) * (x+6) * (x+1) * (x-2) * (x-4) * (x-8)
];

const f = functions[Math.floor(Math.random() * functions.length)];

function fPrime(x) {
	return (f(x + 0.01) - f(x - 0.01)) / 0.02;
}

function range(start, end, step) {
	let result = [];
  for (let x = start; x <= end; x += step) {
  	result.push(x);
  }
  return result;
}

const minValue = Math.min(...range(-8, 8, 0.001).map(f))


ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);

canvas.addEventListener("click", function (event) {
	if (!timerRunning) {
    startTimer();
  }

	const mouseX = event.clientX;
  const canvasX = event.target.getBoundingClientRect().left;
  
  const scale = 40;
  
  const x = ((mouseX - canvasX) - canvas.width / 2) / scale;
  const y = f(x);
  const yPrime = fPrime(x);
  
  const win = y < minValue + 0.01;
  
  if (win) {
    stopTimer();
  }
  
  ctx.strokeStyle = "#333";
  ctx.beginPath();
  ctx.moveTo(canvas.width / 2 + x * scale, 0);
  ctx.lineTo(canvas.width / 2 + x * scale, canvas.height);
  ctx.stroke();
  
  ctx.strokeStyle = "#ddd";
  ctx.beginPath();
  ctx.moveTo(
  	canvas.width / 2 + (x - 0.5) * scale,
    canvas.height / 2 - (y - 0.5 * yPrime) *...