initial
This commit is contained in:
308
sketch_250819b_CMYKGame.js
Normal file
308
sketch_250819b_CMYKGame.js
Normal 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());
|
||||
Reference in New Issue
Block a user