xxxxxxxxxx
598
let targetColor=[0,0,0], currentColor=[0,1,0.5];
let prevTargetColor;
let size, xMargin, yMargin;
let campaign=0;
const campaignModeLength = 3;
const modes = [
["x", "target", "target"],
["target", "y", "target"],
["target", "target", "z"],
["x", "y", "target"],
["x", "target", "z"],
["target", "y", "z"],
["x", "y", "z"],
]
let mode = 0;
const levels = modes.length * campaignModeLength;
let area;
function setup() {
createCanvas(400, 400);
windowResized();
colorMode(RGB, 1);
onTouchDevice = isTouchDevice();
controls = onTouchDevice ? -1 : "area";
generateTargetColor();
mode = 0;
campaign = 0;
generateHints();
}
const margin = 0.25;
function windowResized() {
size = min(windowWidth, windowHeight);
resizeCanvas(windowWidth, windowHeight);
// resizeCanvas(size, size);
xMargin = (margin * size + max(0, width - height) * 0.5) / width;
yMargin = (margin * size + max(0, height - width) * 0.5) / height;
area = {
x: xMargin * width,
y: yMargin * height,
w: (1 - 2 * xMargin) * width,
h: (1 - 2 * yMargin) * height,
}
}
let mouseZ=0.5;
let lastScrollTime=0;
function mouseWheel(evt) {
const dt = (frameCount - lastScrollTime);
const velocity = dt > 0 ? 1 / dt : 1;
const delta = - evt.delta * 0.0005 * velocity;
mouseZ = constrain(mouseZ + delta, 0, 1);
lastScrollTime = frameCount;
}
let controls;
let pressX, pressY, pressColor;
function mousePressed() {
onPressed();
}
function mouseReleased() {
if (controls !== "area")
controls = -1;
}
let onTouchDevice = false;
function touchStarted(evt) {
evt.preventDefault();
onPressed();
onTouchDevice = true;
}
function isTouchDevice() {
return (('ontouchstart' in window) ||
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0));
}
function onPressed() {
if (mouseX >= area.x && mouseX < area.x + area.w
&& mouseY >= area.y && mouseY < area.y + area.h) {
if (controls==="area" || onTouchDevice)
controls = -1;
else
controls="area";
}else{
pressX = mouseX;
pressY = mouseY;
pressColor = [currentColor];
controls=-1;
if (mouseY >= area.y && mouseY < area.y + area.h) {
if (mouseX < width * 0.5) {
controls=1;
}else{
controls=2;
}
}else if (mouseX >= area.x && mouseX < area.x + area.w) {
if (mouseY > height * 0.5) {
controls=0;
}
}
}
}
let doneTime, startTime=0, started=false;
const frames = 300;
let t=0;
let bgColor, fgColor;
let errorMargin, progress;
let cX, cY, doneAlpha=0, doneSize=0, prevTargetAlpha;
let blinkTime=0, rotateTime=0;
let hintColor;
let barSizes=[0,0,0];
function draw() {
drawingContext.shadowOffsetX = 5;
drawingContext.shadowOffsetY = 5;
drawingContext.shadowBlur = 0;
drawingContext.shadowColor = 'rgba(0,0,0,0.25)';
t = frameCount / frames;
const doneAnim = t - doneTime < 1;
if (doneAnim)
doneAnimation(t - doneTime);
else
game(t);
noStroke();
// stroke(1);
// strokeWeight(10);
background(bgColor);
fill(fgColor);
// circle(
// 0.5 * width,
// 0.5 * height,
// 0.5 * size
// );
// rotateTime += pow(progress, 2) * 0.05;
drawingContext.shadowColor = 'transparent';
push();
translate(cX, cY);
rotate(TAU * 0.075 * t);
translate(-cX, -cY);
rect(cX, cY - height * 2, width * 2, height * 4);
pop();
drawingContext.shadowColor = 'rgba(0,0,0,0.25)';
// const beamSize = 0.05 * size;
// fill(1,0,0); rect(0, height - beamSize * 1, currentColor[0] * width, beamSize);
// fill(0,1,0); rect(0, height - beamSize * 2, currentColor[1] * width, beamSize);
// fill(0,0,1); rect(0, height - beamSize * 3, currentColor[2] * width, beamSize);
// fill(0, 0.5);
// noStroke();
// rect(0, 0, width, yMargin * height);
// rect(0, height, width, -yMargin * height);
// rect(0, yMargin * height, xMargin * width, (1 - yMargin * 2) * height);
// rect(width, yMargin * height, -xMargin * width, (1 - yMargin * 2) * height);
noFill();
stroke(1); strokeWeight(3);
rect(area.x, area.y, area.w, area.h);
const xMode = modes[mode][0] === "x";
const yMode = modes[mode][1] === "y";
const zMode = modes[mode][2] === "z";
const barSize = 0.025 * size;
const barDist = barSize * 1.5;
strokeWeight(xMode ? 3 : 2);
for (let i=0; i<2; i++) {
if (i==0) drawingContext.shadowColor = 'rgba(0,0,0,0.25)'
else drawingContext.shadowColor = 'transparent';
let _barSize = xMode ? barSize * 3 : barSize;
barSizes[0] += (_barSize - barSizes[0]) * 0.05;
fill(1,0,0); noStroke();
rect(xMargin * width, yMargin * height + (1 - 2 * yMargin) * height + barDist, (1 - 2 * xMargin) * width * currentColor[0], barSizes[0]);
noFill(); stroke(1, xMode ? 1 : 0.25);
rect(xMargin * width, yMargin * height + (1 - 2 * yMargin) * height + barDist, (1 - 2 * xMargin) * width, barSizes[0]);
}
strokeWeight(yMode ? 3 : 2);
for (let i=0; i<2; i++) {
if (i==0) drawingContext.shadowColor = 'rgba(0,0,0,0.25)'
else drawingContext.shadowColor = 'transparent';
let _barSize = yMode ? barSize * 3 : barSize;
barSizes[1] += (_barSize - barSizes[1]) * 0.05;
fill(0,1,0); noStroke();
rect(xMargin * width - barDist, (1 - yMargin) * height, -barSizes[1] , - (1 - 2 * yMargin) * height * currentColor[1]);
noFill(); stroke(1, yMode ? 1 : 0.25);
rect(xMargin * width - barDist, (1 - yMargin) * height, -barSizes[1] , - (1 - 2 * yMargin) * height);
}
strokeWeight(zMode ? 3 : 2);
for (let i=0; i<2; i++) {
if (i==0) drawingContext.shadowColor = 'rgba(0,0,0,0.25)'
else drawingContext.shadowColor = 'transparent';
let _barSize = zMode ? barSize * 3 : barSize;
barSizes[2] += (_barSize - barSizes[2]) * 0.05;
fill(0,0,1); noStroke();
rect((1 - xMargin) * width + barDist, (1 - yMargin) * height, barSizes[2], - (1 - 2 * yMargin) * height * currentColor[2]);
noFill(); stroke(1, zMode ? 1 : 0.25);
rect((1 - xMargin) * width + barDist, (1 - yMargin) * height, barSizes[2], - (1 - 2 * yMargin) * height);
}
// drawingContext.shadowColor = 'rgba(0,0,0,0.25)';
drawingContext.shadowColor = 'transparent';
noFill();
let radius = errorMargin * size * (1 - margin * 2);
// blinkTime += pow(progress, 2) * 0.05;
// let alph = doneAnim ? 1 : sinn(blinkTime);
stroke(0); strokeWeight(5);
arc(cX, cY, radius * 2, radius * 2, -TAU * 0.25, TAU * (progress - 0.25));
// arc(cX, cY, radius * 2, radius * 2, 0, TAU);
stroke(1); strokeWeight(3);
arc(cX, cY, radius * 2, radius * 2, -TAU * 0.25, TAU * (progress - 0.25));
// arc(cX, cY, radius * 2, radius * 2, 0, TAU);
const weight = 15;
const blackWeight = 3;
const dist = 25;
// radius += min(xMargin * width, yMargin * height) - dist * 2;
radius += 50;
// const colors = [[1,0,0], [0,1,0], [0,0,1]];
// for (let i=0; i<3; i++) {
// stroke(0);
// strokeWeight(blackWeight / 2);
// radius -= weight / 2 + blackWeight / 4;
// arc(cX, cY, radius * 2, radius * 2, 0, TAU);
// radius += weight + blackWeight / 2;
// arc(cX, cY, radius * 2, radius * 2, 0, TAU);
// radius -= weight / 2 + blackWeight / 4;
// stroke(0);
// strokeWeight(weight + blackWeight);
// // arc(cX, cY, radius * 2, radius * 2, 0, TAU);
// arc(cX, cY, radius * 2, radius * 2, -TAU * 0.25, TAU * (currentColor[i] - 0.25));
// stroke(colors[i]); strokeWeight(weight);
// arc(cX, cY, radius * 2, radius * 2, -TAU * 0.25, TAU * (currentColor[i] - 0.25));
// radius += dist;
// }
strokeWeight(1.5);
// fill(1);
// stroke(0);
// circle(
// map( targetColor[0], 0, 1, xMargin, 1 - xMargin) * width,
// map(1 - targetColor[1], 0, 1, yMargin, 1 - yMargin) * height,
// 0.01 * size
// );
if (doneAnim) {
fill(1, doneAlpha);
stroke(0, doneAlpha);
circle(
map( prevTargetColor[0], 0, 1, xMargin, 1 - xMargin) * width,
map(1 - prevTargetColor[1], 0, 1, yMargin, 1 - yMargin) * height,
0.01 * size * doneSize
);
}
drawingContext.shadowColor = 'rgba(0,0,0,0.25)';
const h = yMargin * height;
let fSize = min(h, xMargin * width) * 0.305;
textSize(fSize);
textAlign(LEFT, BOTTOM);
textStyle(BOLD);
strokeWeight(3);
noStroke();
fill(1);
text("Color Matchr!", xMargin * width - fSize * 0.1, h - fSize * 0.8);
textSize(fSize * 0.6);
const txt = campaign < levels
? `${campaign + 1} / ${levels}`
: "Nice one!";
const offset = campaign < levels
? 0
: -fSize * 0.08;
text(txt, xMargin * width + offset, yMargin * height - fSize * 0.16);
textSize(fSize * 0.45);
textAlign(CENTER, CENTER);
const len = hints.length;
hints.forEach((hint, i) => {
const f = len > 1 ? i / (len - 1) - 0.5 : 0;
const x = f * (0.1 * len);
const alph = hintsTime[i] != null
? 1 - (t - hintsTime[i]) * 5
: 1;
stroke(0, alph);
fill(1, alph);
const dist = fSize * 0.5;
const y = 0.5 * height + pow(1 - alph, 0.75) * 25;
const func = ({
x: () => {
text("X axis", 0.5 * width + x * size, y - dist);
text("=", 0.5 * width + x * size, y);
fill(1,0,0, alph);
text("red", 0.5 * width + x * size, y + dist);
},
y: () => {
text("Y axis", 0.5 * width + x * size, y - dist);
text("=", 0.5 * width + x * size, y);
fill(0,1,0, alph);
text("green", 0.5 * width + x * size, y + dist);
},
z: () => {
text(onTouchDevice || controls !== "area" ? "Y axis >" : "scroll", 0.5 * width + x * size, y - dist);
text("=", 0.5 * width + x * size, y);
fill(0,0,1, alph);
text("blue", 0.5 * width + x * size, y + dist);
},
})[hint];
func && func();
});
drawingContext.shadowColor = 'transparent';
drawParticles();
if (controls === "area") {
if (mouseX >= xMargin * width && mouseX < (1 - xMargin) * width
&& mouseY >= yMargin * height && mouseY < (1 - yMargin) * height)
noCursor();
else
cursor();
}else{
cursor();
}
}
let prevT, hints = [], hintsTime = [];
function doneAnimation(t) {
if (t < 0.75) {
const lt = t * (1 + 1/3);
currentColor = currentColor.map((v, i) => lerp(v, prevTargetColor[i], lt));
bgColor = currentColor;
fgColor = prevTargetColor;
doneAlpha = 1;
doneSize = lerp(doneSize, 1, min(1, lt * 5));
} else {
if (prevT < 0.75) {
campaign++;
if (campaign < levels)
mode = floor(campaign / campaignModeLength);
else
mode = floor(random() * modes.length);
generateHints();
generateTargetColor();
}
const lt = (t - 0.75) * 4;
let actualCurrentColor = getCurrentColor();
currentColor = currentColor.map((v, i) => lerp(v, actualCurrentColor[i], lt));
bgColor = currentColor;
fgColor = prevTargetColor.map((v, i) => lerp(v, targetColor[i], lt));
cX = lerp(cX, map(currentColor[0], 0, 1, xMargin * width, (1 - xMargin) * width), lt * 0.5);
cY = lerp(cY,map(1 - currentColor[1], 0, 1, yMargin * height, (1 - yMargin) * height), lt * 0.5);
errorMargin = lerp(errorMargin, 0.005, lt * 0.5);
doneAlpha = lerp(doneAlpha, 0, lt * 0.5);
doneSize = lerp(doneSize, 0, lt * 0.5);
}
prevT = t;
}
function game(t) {
if (!started) {
started = true;
startTime = t;
}
currentColor = getCurrentColor();
bgColor = currentColor;
fgColor = targetColor;
errorMargin = (t - startTime) * 0.005 + 0.005;
const dist = sqrt(currentColor.reduce(
(tot, val, i) => tot + pow(val - targetColor[i], 2), 0));
if (dist <= errorMargin) {
doneTime = t;
prevTargetColor = targetColor;
started = false;
spawnParticles();
if (campaign < 21 && (campaign+1) % 3 == 0)
setTimeout(() => spawnParticles(), 1000);
if (campaign == 20)
setTimeout(() => spawnParticles(), 2000);
}
progress = constrain(1 - (dist - errorMargin), 0, 1);
// fill("white");
// text(dist, 13, 25);
cX = map(currentColor[0], 0, 1, xMargin * width, (1 - xMargin) * width);
cY = map(1 - currentColor[1], 0, 1, yMargin * height, (1 - yMargin) * height);
for (let i=0; i<3; i++) {
const index = hints.indexOf(["x", "y", "z"][i]);
if (index>=0 && hintsTime[index] == null && abs(currentColor[i] - hintColor[i]) > 0.01)
hintsTime[index] = t;
}
}
function getCurrentColor() {
const mX = mouseX / width;
const mY = 1 - mouseY / height;
const mZ = mouseZ;
if (controls=="area") {
return modes[mode].map((m, i) => ({
x: constrain(map(mX, xMargin, 1 - xMargin, 0, 1), 0, 1),
y: constrain(map(mY, yMargin, 1 - yMargin, 0, 1), 0, 1),
z: mZ,
target: targetColor[i]
})[m]);
}
const mdX = (mouseX - pressX) / ((1 - 2 * xMargin) * width);
const mdY = -(mouseY - pressY) /((1 - 2 * yMargin) * height);
if (controls==0) {
return modes[mode].map((m, i) => ({
x: constrain(pressColor[0] + mdX, 0, 1),
y: currentColor[1],
z: currentColor[2],
target: targetColor[i]
})[m]);
}
if (controls==1) {
return modes[mode].map((m, i) => ({
x: currentColor[0],
y: constrain(pressColor[1] + mdY, 0, 1),
z: currentColor[2],
target: targetColor[i]
})[m]);
}
if (controls==2) {
return modes[mode].map((m, i) => ({
x: currentColor[0],
y: currentColor[1],
z: constrain(pressColor[2] + mdY, 0, 1),
target: targetColor[i]
})[m]);
}
return modes[mode].map((m, i) => ({
x: currentColor[0],
y: currentColor[1],
z: currentColor[2],
target: targetColor[i]
})[m]);
}
function generateTargetColor() {
let dists = nArr(3, (i) => modes[mode][i] === "target" ? random() * 0.05 : random());
const total = dists.reduce((tot, val) => tot + val, 0);
dists = dists.map((d,i) => modes[mode][i] === "target" ? random() : d / (2 * total));
targetColor = [
fract(currentColor[0] + dists[0]),
fract(currentColor[1] + dists[1]),
fract(currentColor[2] + dists[2])
];
}
function generateHints() {
hints = modes[mode].filter(v => v !== "target");
hintsTime = [null, null, null];
hintColor = [getCurrentColor()];
console.log(hints);
}
function nArr(n, callback) {
n = Math.max(0, Math.floor(n));
return new Array(n).fill().map((_, i) => typeof callback === "function"
? callback(i)
: callback
);
}
let particles=[];
const spread = 0.1;
const velRange = 15;
const waveSpeedRange = 5;
const gravityRange = 0.05;
const liveRange = 1;
function spawnParticles() {
const count = min(1, pow(campaign / levels, 1.5)) * 30 + 20;
const colorDif = random();
for (let j=0; j<4; j++) {
const a = j / 4 + 0.125;
for (let i=0; i<count; i++) {
const x = 0.5 * width + scos(a) * 0.5 * size;
const y = 0.5 * height + ssin(a) * 0.5 * size;
const _a = a - 0.5 + (random() - 0.5) * spread;
const vel = random() * velRange;
const xVel = scos(_a) * vel;
const yVel = ssin(_a) * vel;
const waveOffset = random();
const waveSpeed = random() * waveSpeedRange;
const gravity = random() * gravityRange;
const spawnT = t;
const liveTime = random() * liveRange;
const colorF = random();
const color = nArr(3, (i) => sinn(colorF + i * colorDif));
particles.push({x, y, xVel, yVel, waveOffset, waveSpeed, gravity, spawnT, liveTime, color });
}
}
}
// const gravity = 0.01;
const friction = 0.99;
const wave = 0.1;
function drawParticles() {
particles.forEach(particle => {
const {gravity} = particle;
particle.yVel += gravity;
particle.xVel *= friction;
particle.yVel *= friction;
const {waveOffset, waveSpeed } = particle;
particle.xVel += ssin(t * waveSpeed + waveOffset) * wave;
const {xVel, yVel} = particle;
particle.x += xVel;
particle.y += yVel;
const { spawnT, liveTime } = particle;
const alph = max(0, 1 - ((t - spawnT) / liveTime));
particle.alph = alph;
const {color} = particle;
fill(1, alph);
stroke([color, alph]);
strokeWeight(3);
const {x, y} = particle;
circle(x,y,0.005 * size);
});
particles = particles.filter(p => p.alph > 0.001);
}
function ssin(v) {
return sin(v * TAU);
}
function scos(v) {
return cos(v * TAU);
}
function sinn(v) {
return sin(v * TAU) * 0.5 + 0.5;
}