This commit is contained in:
Aino Kovalainen
2025-08-19 18:08:48 +03:00
commit d6fe7d5475
4 changed files with 343 additions and 0 deletions

308
sketch_250819b_CMYKGame.js Normal file
View File

@@ -0,0 +1,308 @@
// ==========================
// 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);
textFont('system-ui');
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 gap = 16;
const areaTop = height*0.6;
const cellW = (width - gap*(gridCols+1)) / gridCols;
const cellH = (height*0.36 - gap*(gridRows+1)) / gridRows;
optionBoxes = [];
let idx = 0;
for(let r=0; r<gridRows; r++){
for(let c=0; c<gridCols; c++){
const ox = gap + c*(cellW+gap);
const oy = areaTop + gap + r*(cellH+gap);
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'){ 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());