Files
CMYKGame/sketch_250819b_CMYKGame.js
Aino Kovalainen d5798f74b8 ainon mmuutokset
2025-08-21 21:11:38 +03:00

314 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ==========================
// 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<gridRows; r++){
for(let c=0; c<gridCols; c++){
const ox = gapX + c*(cellW+gapX);
const oy = areaTop + gapY + r*(cellH+gapY);
const opt = options[idx];
// laatikon tausta
fill(28,28,36); rect(ox-8, oy-8, cellW+16, cellH+16, 18);
// väri
fill(rgbFromCMYK(opt)); rect(ox, oy, cellW, cellH, 14);
// label
fill(255); textSize(min(width, height)*0.025);
text(`C ${opt.c}% M ${opt.m}% Y ${opt.y}% K ${opt.k}%`, ox+cellW/2, oy+cellH+22);
optionBoxes.push({x:ox, y:oy, w:cellW, h:cellH, idx});
idx++;
}
}
}
function drawGameOver(){
fill(255);
textSize(min(width, height) * 0.095);
text('Aika loppui!', width/2, height*0.35);
fill(200);
textSize(min(width, height) * 0.04);
text('Pisteesi: ' + score, width/2, height*0.48);
fill(180);
text('▶ Aloita uudestaan', width/2, height*0.6);
}
function mousePressed(){
if(state==='menu' || state==='gameover'){ startGame(); return; }
if(state!=='playing') return;
// Tarkista osumat laatikoihin
for(const box of optionBoxes){
if(mouseX>=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;i<options.length;i++){
const d = rgbDist(rgbFromCMYK(options[i]), t);
if(d < best){ best = d; idx = i; }
}
return idx;
}
// ------- Äänet (valinnaiset, hyvin kevyet) -------
let ac; // AudioContext
function ensureAC(){ if(!ac){ const C = window.AudioContext||window.webkitAudioContext; ac = new C(); } }
function beep(freq, dur, type='sine', vol=0.05){ if(muted) return; ensureAC(); const t=ac.currentTime; const o=ac.createOscillator(); const g=ac.createGain(); o.type=type; o.frequency.value=freq; o.connect(g); g.connect(ac.destination); g.gain.value = vol; o.start(t); o.stop(t+dur); }
function playGood(){ beep(880, .08, 'square'); beep(1320, .10, 'square'); }
function playBad(){ beep(130, .12, 'sawtooth'); }
function toggleMute(){ muted = !muted; if(!muted) ensureAC(); }
// ------- Fullscreen -------
function toggleFullscreen(){ let fs = fullscreen(); fullscreen(!fs); }
// ------- Leaderboard (localStorage) -------
const LB_KEY = 'cmyk_lb_v1';
function pushScore(name, score){
const now = new Date();
const dayKey = now.toISOString().slice(0,10); // YYYY-MM-DD
const all = JSON.parse(localStorage.getItem(LB_KEY) || '{}');
if(!all[dayKey]) all[dayKey] = [];
all[dayKey].push({name, score});
all[dayKey].sort((a,b)=>b.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());