// ========================== // VÄRIMYRSKY CMYK – p5.js // Kosketusystävällinen refleksipeli messukioskille // ========================== let state = 'menu'; // menu | playing | gameover let duration = 60; // kierros sekunteina let startMillis = 0; let timeLeft = duration; let score = 0; let roundN = 0; let target = null; let options = []; let optionBoxes = []; const OPTIONS_COUNT = 6; const CMYK_STEPS = [0, 25, 50, 75, 100]; // helpottaa valintoja let clickSound, goodSound, badSound; let muted = true; function preload(){ // Kevyet piippisoundit (WebAudio, luodaan lennossa) } function setup(){ createCanvas(windowWidth, windowHeight); PFont mono; mono = createFont("FuturaBQ-DemiBold.otf", 128); textFont(mono); textAlign(CENTER, CENTER); noStroke(); refreshLeaderboard(); // UI-napit select('#btnStart').mousePressed(() => startGame()); select('#btnFull').mousePressed(() => toggleFullscreen()); select('#btnMute').mousePressed(() => toggleMute()); print("setup"); } function windowResized(){ resizeCanvas(windowWidth, windowHeight); } function startGame(){ score = 0; roundN = 0; state='playing'; startMillis = millis(); nextRound(); } function endGame(){ state='gameover'; // Nimi- / nimikirjainkysely. Kioskilla voi ohittaa. const name = prompt('Syötä nimikirjaimet leaderboardille (valinnainen):', '') || '—'; pushScore(name.slice(0,10), score); refreshLeaderboard(); } function draw(){ background('#0b0b0d'); if(state==='menu'){ drawTitle(); } else if(state==='playing'){ timeLeft = max(0, duration - floor((millis()-startMillis)/1000)); if(timeLeft<=0){ endGame(); return; } drawHUD(); drawTarget(); drawOptions(); } else if(state==='gameover'){ drawGameOver(); } } function drawTitle(){ fill(255); const t1 = 'VÄRIMYRSKY CMYK'; const t2 = 'Napauta sitä värinäytettä, joka vastaa parhaiten annettua CMYK-arvoa.'; textSize(min(width, height) * 0.10); text(t1, width/2, height*0.33); fill(200); textSize(min(width, height) * 0.03); text(t2, width/2, height*0.45, width*0.9); fill(180); text('▶ Aloita – 60 s kierros', width/2, height*0.6); } function drawHUD(){ // Yläpalkki: aika ja pisteet const pad = 20; const barH = 36; const timeFrac = timeLeft / duration; // tausta fill(20,20,26,220); rect(pad, pad, width - pad*2, barH, 12); // aika-progress fill(70,140,255,220); rect(pad+2, pad+2, (width - pad*2 - 4) * timeFrac, barH-4, 10); // teksti fill(255); textSize(20); textAlign(LEFT, CENTER); text('Aika: ' + nf(timeLeft,2) + ' s', pad+16, pad+barH/2); textAlign(RIGHT, CENTER); text('Pisteet: ' + score, width - pad - 16, pad+barH/2); textAlign(CENTER, CENTER); } function drawTarget(){ const areaH = height * 0.42; const boxW = min(width*0.7, 900); const boxH = areaH * 0.7; const x = width/2 - boxW/2; const y = height*0.12 + 12; // Tausta fill(28,28,36); rect(x-12, y-12, boxW+24, boxH+24, 20); // Väri fill(rgbFromCMYK(target)); rect(x, y, boxW, boxH, 16); // CMYK-arvo teksti fill(255); textSize(min(width, height)*0.035); const t = `Etsi: C ${target.c}% M ${target.m}% Y ${target.y}% K ${target.k}%`; text(t, width/2, y + boxH + 36); } function drawOptions(){ const gridRows = 2; const gridCols = 3; const gapX = 16; const gapY = 48; const areaTop = height*0.6; const cellW = (width - gapX*(gridCols+1)) / gridCols; const cellH = (height*0.36 - gapY*(gridRows+1)) / gridRows; optionBoxes = []; let idx = 0; for(let r=0; r=box.x && mouseX<=box.x+box.w && mouseY>=box.y && mouseY<=box.y+box.h){ handleSelection(box.idx); break; } } } function touchStarted(){ // Estä sivun vieritys kioskissa return false; } function handleSelection(i){ const chosen = options[i]; const distChosen = rgbDist(rgbFromCMYK(chosen), rgbFromCMYK(target)); const bestIndex = getBestMatchIndex(); if(i === bestIndex){ playGood(); score += 10; } else { playBad(); score = max(0, score - 5); } roundN++; nextRound(); } function nextRound(){ target = randomTarget(); options = buildOptions(target, OPTIONS_COUNT); } // ------- Väriapuja ------- function randomTarget(){ // Valitaan arvo askelista, jotta numerot pysyvät siisteinä return { c: randomChoice(CMYK_STEPS), m: randomChoice(CMYK_STEPS), y: randomChoice(CMYK_STEPS), k: randomChoice([0,10,20,30,40,50,60,70,80,90,100]) }; } function randomChoice(arr){ return arr[floor(random(arr.length))]; } function buildOptions(target, n){ let arr = []; // sijoita täydellinen vastaus arr.push({...target}); // tee häiritsijöitä: while(arr.length < n){ const delta = [0, 10, 20, -10, -20]; const c = clamp(target.c + randomChoice(delta), 0, 100); const m = clamp(target.m + randomChoice(delta), 0, 100); const y = clamp(target.y + randomChoice(delta), 0, 100); const k = clamp(target.k + randomChoice([-10, 0, 10, 20, -20]), 0, 100); const opt = {c,m,y,k}; if(!arr.some(o => o.c===opt.c && o.m===opt.m && o.y===opt.y && o.k===opt.k)){ arr.push(opt); } } // sekoita for(let i=arr.length-1;i>0;i--){ const j=floor(random(i+1)); [arr[i],arr[j]]=[arr[j],arr[i]]; } return arr; } function clamp(v, lo, hi){ return max(lo, min(hi, v)); } function cmykToRgb(cmyk){ // cmyk prosentteina (0..100) const C = cmyk.c/100, M = cmyk.m/100, Y = cmyk.y/100, K = cmyk.k/100; const r = 255 * (1-C) * (1-K); const g = 255 * (1-M) * (1-K); const b = 255 * (1-Y) * (1-K); return {r, g, b}; } function rgbFromCMYK(cmyk){ const {r,g,b} = cmykToRgb(cmyk); return color(r, g, b); } function rgbDist(a, b){ // a ja b voivat olla p5.Color tai plain rgb-objekti const ar = red(a), ag = green(a), ab = blue(a); const br = red(b), bg = green(b), bb = blue(b); const dr = ar-br, dg = ag-bg, db = ab-bb; return dr*dr + dg*dg + db*db; } function getBestMatchIndex(){ let best = Infinity, idx = 0; const t = rgbFromCMYK(target); for(let i=0;ib.score-a.score); all[dayKey] = all[dayKey].slice(0,10); localStorage.setItem(LB_KEY, JSON.stringify(all)); } function refreshLeaderboard(){ const lb = document.getElementById('lb'); lb.innerHTML = ''; const all = JSON.parse(localStorage.getItem(LB_KEY) || '{}'); const dayKey = new Date().toISOString().slice(0,10); const today = all[dayKey] || []; today.forEach((row, i)=>{ const li = document.createElement('li'); li.textContent = `${i+1}. ${row.name} — ${row.score}`; lb.appendChild(li); }); } // Estä pitkäpainallus- ja kontekstivalikko kioskissa window.addEventListener('contextmenu', e=> e.preventDefault());