Skip to content

Instantly share code, notes, and snippets.

@prayerslayer
Created July 3, 2023 07:21
Show Gist options
  • Save prayerslayer/89be3dcf0b7083f6414f7175bc6aba37 to your computer and use it in GitHub Desktop.
Save prayerslayer/89be3dcf0b7083f6414f7175bc6aba37 to your computer and use it in GitHub Desktop.
P5js Solitaire
const w = 400,
h = 400;
const cardWidth = 40,
cardAspectRatio = 64 / 89,
cardHeight = cardWidth / cardAspectRatio;
let trajectories = [];
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function setup() {
createCanvas(w, h);
for (let i = 0; i < 100; i++) {
trajectories.push(getBouncePath());
}
}
let last_ms = 0;
let trajIdx = 0,
pointIdx = 0;
function draw() {
background("white");
let r = 0;
angleMode(RADIANS);
for (const [i, t] of trajectories.entries()) {
for (const [j, p] of t[1].entries()) {
if (i <= trajIdx) {
if (i < trajIdx || j <= pointIdx) {
if (i == trajIdx) {
fill(d3.interpolateCool(j / pointIdx));
} else {
fill("white");
}
const [x, y] = p;
push();
translate(x, y);
rotate(r);
rect(-cardWidth / 2, -cardHeight / 2, cardWidth, cardHeight);
pop();
r += PI / 2 ** 5;
//rotate(0)
}
}
}
}
const ms = millis();
if (ms - last_ms > 50 && trajIdx < trajectories.length) {
pointIdx += 1;
if (pointIdx >= trajectories[trajIdx][1].length) {
pointIdx = 0;
trajIdx += 1;
}
//print(trajIdx, pointIdx)
}
}
function getPointOnEllipse(cx, cy, w, h, angle) {
const r =
(w * h) /
Math.sqrt((w * Math.sin(angle)) ** 2 + (h * Math.cos(angle)) ** 2);
const x = r * Math.cos(angle) + cx;
const y = r * Math.sin(angle) + cy;
return [x, y];
}
/**
* Tries to get equidistant points on the defined ellipse arc.
* Since apparently we can't have analytical or exact solutions,
* and I don't want to get into numerical integration with JS,
* we do a simple, if ineffecient, scanning procedure:
* Move in very small increments along the arc, keep track of the
* cumulative distance and add the point to the result set where
* cumdist >= arcLength.
*/
function getPointsOnEllipseSegment(
cx,
cy,
a,
b,
angle0,
angle1,
arcLength = 5
) {
let thetaDelta = 0.0001;
if (angle1 < angle0) {
angle1 += 2 * PI;
}
let theta = angle0;
let cumDist = 0;
let arcPoints = [getPointOnEllipse(cx, cy, a, b, theta)];
let ellPoints = [getPointOnEllipse(cx, cy, a, b, theta)];
while (theta <= angle1) {
theta += thetaDelta;
const nextPoint = getPointOnEllipse(cx, cy, a, b, theta);
const d = calcDist(arcPoints[arcPoints.length - 1], nextPoint);
cumDist += d;
arcPoints.push(nextPoint);
if (cumDist >= arcLength) {
arcPoints = [nextPoint];
ellPoints.push(nextPoint);
cumDist = 0;
}
}
return ellPoints;
}
function getBouncePath() {
const numBounces = random(4, 8);
const points = [];
const bounceTrajectory = [];
let dir = random() > 0.5; // right if true, left if false
const bounceWidth = Math.floor(random(cardWidth, w / 2));
for (let i = 0; i < numBounces; i++) {
if (i === 0) {
let x = Math.floor(random(cardWidth, w - cardWidth));
let y = Math.floor(random(cardHeight, h - cardHeight));
bounceTrajectory.push([x, y]);
continue;
}
const lastPoint = bounceTrajectory[bounceTrajectory.length - 1];
let x = dir ? lastPoint[0] + bounceWidth : lastPoint[0] - bounceWidth;
x = Math.floor(x);
let y = random(Math.min(h, lastPoint[1] + cardHeight), h);
y = Math.floor(y);
bounceTrajectory.push([x, y]);
if (y > h - cardHeight || i === numBounces - 1) {
break;
}
}
const keyframes = [];
for (const [i, p] of bounceTrajectory.entries()) {
const [x, y] = p;
const a = bounceWidth / 2;
const b = h - y;
const points = getPointsOnEllipseSegment(x, h, a, b, PI, 2 * PI);
if (!dir) {
points.reverse();
}
keyframes.push(...points);
}
return [bounceTrajectory, keyframes];
}
function calcDist(p0, p1) {
// Apparently this is faster than the built-in dist() function?
const [x0, y0] = p0;
const [x1, y1] = p1;
return Math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment