Täiesti tühjal väljal tavaliselt seeni pole. Teeme programmi mis genereerib etteantud ridade-veergude arvuga labyrindi; hiljem muudame labyrindi seinad puude ridadeks ja saame nii metsa.
Labyrintide genereerimiseks on leiutatud palju algoritme. Kasutame siin nn "uuristamismeetodit":
Labürindi suurus (ridade/veergude arv) on määratud parameetriga n; ruumi laius/kõrgus saadakse kanvaa laiuse/kõrguse jagamisel n-ga. Et tekitada mängu, lisame ka tumeda kolli (bogy) labürindi vasakusse ülanurka ja ereda väljapääsu (goal) paremasse alanurka; mängija peab nooleklahvide abil liigutama kolli eesmärgini.
Skripti algul deklareerime rea globaalseid muutujaid; kuna kohe kasutatakse kanvaad, tuleb see skript lisada html-dokumendi lõppu - siis on kanvaa juba laetud!
var canvas = document.getElementById('mycanvas'); // Make sure we don't execute when canvas isn't supported if (canvas.getContext) { ctx = canvas.getContext('2d'); //defineerime globaalsed muutujad var n = 20; var w = canvas.width; var h = canvas.height; var d = Math.floor(w / n); //width of one room var d0 = d/4; //radius of bogy var ruudud = []; var connected = []; var free = []; var dirs = [0, 1, 2, 3]; //uste võimalikud suunad var x0 = d/2; //kolli keskkoht var y0 = d/2; var labyr; // imageData ! var bogy = new Image(); var goal = new Image(); var xg; //goal x var yg; //goal y
Mängu ajal koll liigub. Liikumine tekib kogu kanvaa kustutamise ja siis kõigi elementide (labürint, koll, eesmärk) uues kohas joonistamisega. Kuna labürint ei tohi muutuda (see genereeritakse mängu algul) salvestatakse labürindi pikslid andmestruktuuris imageData. Selle struktuuri kaks esimest arvu on salvestatud pikselkujutise laius ja körgus (width, height) ja siis järgneb massiiv, kus iga piksel on salvestatud nelja arvuga: piksli punane, roheline, sinine ja läbipaistvuse (alpha) komponendid. Kui labürint on kanvaale joonistatud, toimub selle imageData loomine kanvaalt lugemisega käsuga getImageData(x,y,w,h), kus x,y - kanvaalt loetava ala vasak ülanurk, w,h - loetava pildiala laius ja kõrgus; salvestatud pikslite massiivi kanvaale joonistamine punkti x,y toimub käsuga putImageData(img,x,y) (sellel käsul võib olla ka rohkem parameetreid vaid pikslimassiivi osa kanvaale toomioseks).
Uue (tühja) ruumi loob funktsioon ruut:
function ruut(n, m) { this.r = n; this.v = m; this.doors = []; }
Javascriptis ei ole klasse, kuid parameetri this kasutamisel saab funktsiooni kasutada kui (klassi) konstruktorfunktsiooni: käsk new ruut(n,m) loob uue objekti, millele on paraameetrid .r (rida), .v (veerg), .doors (uste massiiv - loomisel tühi, sellepärast pole seda ka funktsiooni formaalsete parameetrite seas). Programmi algul luuakse kõik ruumid ja salvestatakse (lineaarses, s.t. ühemõõtmelises) massiivis ruudud:
for (var i = 0; i < n; i++) { for (var j = 0; j < n; j++) { ruudud.push(new ruut(i, j)); } }
Labürindile uue ruumi lisamiseks otsitakse kõigi juba lisatud ruumide seas selline, millel mingis suunas pole ust, kuid selles suunas (seina taga) on veel lisamata ruum. Sellepärast on tarvis funktsiooni, mis ruumi r ja (juhuslikult valitud) suuna d1 põhjal leiab selles suunas "seina taga" oleva ruumi ja kontrollib, kas see on veel ühendamata (kuulub massiivi free):
function next(r, d) { var nn1; var mm1; switch (d) { case 0: nn1 = r.r; mm1 = r.v + 1; break; case 1: nn1 = r.r + 1; mm1 = r.v; break; case 2: nn1 = r.r; mm1 = r.v - 1; break; case 3: nn1 = r.r - 1; mm1 = r.v; break; default: nn1 = r.r; mm1 = r.v; } for (var i = 0; i < free.length; i++) { //console.log(i+' '+n1+' '+m1); if ((free[i].r == nn1) && (free[i].v == mm1)) { return i; break; } } return -1; }
Labürindi joonistamiseks kasutatakse abifunktisooni line:
function line(x0, y0, x1, y1) { ctx.strokeStyle = '#0000cc'; ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.stroke(); }
Kolli ja eesmärgi joonistamiseks kasutatakse abifunktsiooni
function circle(x,y,r,fill,stroke){ //a - angle, d - direction ctx.fillStyle = fill; ctx.strokeStyle = stroke; ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI*2, true); ctx.stroke(); ctx.fill(); }
Kogu labyrindi, kolli ja eesmärgi joonistab neid abifunktisoone kasutades järgmine funktsioon, mis käivitatse diokumendi loomisel automaatselt (skriptis globaalsete muutujate deklaratsioonide järel):
window.onload = function () { for (var i = 0; i < n; i++) { for (var j = 0; j < n; j++) { ruudud.push(new ruut(i, j)); } } var i0 = random(0,ruudud.length); connected.push(ruudud[i0]); //esimene ruum labyrindis! free = ruudud.slice(0); //eemaldame free.splice(i0, 1); //Next line starts Firebug debugger! //debugger; while (free.length > 0) { var found = 0; while (found == 0) { i0 = random(0, connected.length); var r = connected[i0]; //kandidaat! var dirs = [0, 1, 2, 3]; for (var i = 0; i < 4; i++) { if (r.doors.indexOf(dirs[i]) > 0) dirs.splice(i, 1); } while (dirs.length > 0) { var j0 = random(0, dirs.length); var d1 = dirs[j0]; var i1 = next(r, d1); if (i1 >= 0) { var r1 = free[i1]; found = 1; r.doors.push(d1); var d2 = reverse(d1); r1.doors.push(d2); connected.push(r1); free.splice(i1, 1); break; } else { dirs.splice(j0, 1); } } } } //draw labyrinth for (var i = 0; i < connected.length; i++) { var xx = connected[i].v; var yy = connected[i].r; if (connected[i].doors.indexOf(0) == -1) //ust selles suunas pole line((xx + 1) * d, yy * d, (xx + 1) * d, (yy + 1) * d); if (connected[i].doors.indexOf(1) == -1) line(xx * d, (yy + 1) * d, (xx + 1) * d, (yy + 1) * d); if (xx == 0 && connected[i].doors.indexOf(2) == -1) line(xx * d, yy * d, xx * d, (yy + 1) * d); if (yy == 0 && connected[i].doors.indexOf(3) == -1) line(xx * d, yy * d, (xx + 1) * d, yy * d); } labyr = ctx.getImageData(0, 0, w, h); clear(0,0,d,d); //x0-d/4,y0-d/2,d/2,d/2); circle(x0,y0,d0,'#0000CC','#0000CC'); bogy = ctx.getImageData(x0-d/4,y0-d/4,d/2,d/2); clear(0,0,d,d); //x0-d/4,y0-d/2,d/2,d/2); circle(x0,y0,d0,'#FFAA00','#FF0000'); goal = ctx.getImageData(x0-d/4,y0-d/4,d/2,d/2); x0 = d/4; y0 = d/4; xg = w-3*d/4; yg = h-3*d/4; window.onkeydown = move; return frame=window.setInterval(animate, 10); //return setTimeout(animate,30); } //if (canvas.getContext }//drawLabyr
See funktsioon lisab aknale sündmuse keyDown kuulaja - funktsiooni move (siin saab kasutaja nooleklahvidega kolli juhtida) ja käivitab iga 10 ms järel funktsiooni animate - kanvaa puhastatakse ja joonistatkse siis uus seis:
function move(e) { var r = Math.floor(y0/d); var v = Math.floor(x0/d); var mx = 0; var my = 0; switch(e.keyCode) { case (37): if (ruudud[r*n+v].doors.indexOf(2)> -1) mx = -d; break; case 38: if (ruudud[r*n+v].doors.indexOf(3)> -1) my = -d; break; case 39: if (ruudud[r*n+v].doors.indexOf(0)> -1) mx = d; break; case 40: if (ruudud[r*n+v].doors.indexOf(1)> -1) my = d; break; default: mx = 0; my = 0; } //clear(x0,y0,d/2,d/2); x0 += mx; y0 += my; }
function animate(){ ctx.putImageData(labyr, 0, 0); ctx.putImageData(bogy, x0, y0); ctx.putImageData(goal, xg, yg); if (collision(x0,y0,bogy.width,bogy.height,xg,yg,goal.width,goal.height)) {window.clearInterval(frame); showText("Tegid ära!",16,"#FF0000");} //ctx.restore(); }Animatsioonifunktsioon animate kontrollib funktsiooni collision abil, kas koll on jõudnud eesmärgini ja kui on, näidatakse mängu aknas vastavat teksti ja kaadrivahetus peatatakse:
function collision(x1,y1,w1,h1,x2,y2,w2,h2){ return x1 < x2 + w2 && x2 < x1 + w1 && y1 < y2 + h2 && y2 < y1 + h1 } function showText(txt,sz,col){ // Score ctx.fillStyle = col; //"rgb(250, 100, 100)"; ctx.font = sz+"px Arial"; ctx.textAlign = "left"; ctx.textBaseline = "top"; ctx.fillText(txt, d0, d0); //text, x,y };Ja ongi kõik!