Jõuluvana eksis ära ...


Liiigu: A,W,D,S või nooleklahvid

Alustame piltide ja helide (võib esialgu ära jätta) laadimisega:

 function loadImages(sources, callback) {
        var images = {};
        var loadedImages = 0;
        var numImages = 0;
        // get num of sources
        for(var src in sources) {
          numImages++;
        }
        for(var src in sources) {
          images[src] = new Image();
          images[src].onload = function() {
            if(++loadedImages >= numImages) {
              callback(images);
            }
          };
          images[src].src = sources[src];
        }
      }

    window.onload = function() {
        var canvas = document.getElementById("gameCanvas");
        ctx = canvas.getContext("2d");		
        var sources = {
          treesprite: "./images/winter/puud_lumes.gif",
          pakid: "./images/winter/pakid.png",
		  santa_ani: "images/winter/santad1.png",
		  flake: "images/winter/flake.gif",
		  lamp: "images/winter/lamp64.png"
		};
		loadImages(sources, initGame);
		}

 

Pildid

Kui kõik on laetud käivitub mängu initsialiseerimisfunktsioon:

var initGame = function(images){
	wt = 64; // module - width of a tree tile
	ht = 64;  //kui ebasümmeetrilised
	var k = 2; // how many tiles per side
	var n = 6; // how many squares in row/column on screen
	var vl = n+10; //number of columns in (virtual) labyrinth
	var rl = 20; //number of rows in (virtual) labyrinth
	d = k * wt; // width of one labyrinth square
	wc = n * d+wt; //canvas width
	hc = (n-1) * d+ht; //canvas height
	w0 = vl*d+wc; //width of virtual canvas
	h0 = rl*d+wt;  //height of virtual canvas
	xl = 0; //start position of labyrinth on screen
	yl = 0; //start position of labyrinth
	ctx.canvas.width = wc;
	ctx.canvas.height = hc;
	canvas0 = document.createElement('canvas');
	ctx0 = canvas0.getContext('2d');
	//canvas0.id     = "VirtualCanvas";
	canvas0.width  = w0;
	canvas0.height = h0;
	
	
	pick_snd =  document.getElementById("pick");
	fanfare_snd = document.getElementById("fanfare");
	
	trees = new spriteSheet(images.treesprite,[[0,0,64,64],[65,0,64,64],[128,0,64,64],[192,0,64,64]]);
	
	pakid = new spriteSheet(images.pakid,[[0,0,64,64],[64, 0, 64, 64],[128,0,64, 64],[192, 0, 64, 64],[0,64,64,64],[64, 64, 64, 64],[128,64,64, 64],[192, 64, 64, 64],[0,128,64,64],[64, 128, 64, 64],[128,128,64, 64],[192, 128, 64, 64],[0,192,64,64],[64, 192, 64, 64],[128,192,64, 64],[191, 192, 64, 64]]);
	
	
	rooms = create(vl,rl,n,wt,ht,d,w0,h0,k);
	map = draw_labyrinth(rooms,trees,d,k,vl,rl,wt,ht,w0,h0);
	labyr = ctx0.getImageData(0, 0, w0, h0);
	ctx.putImageData(labyr, xl,yl,-xl, -yl,wc,hc)
	
}

Nüüd võib juba testida, näit käsuga ctx.drawImage(images.treesprite,0,0) peaks kanvaale ilmuma puude spriit.

Spriidilehe põhjal loodud animeerimata staatilised objektid - puud (ja pakid) luuakse juba tuttava klassiga :

function spriteSheet(img,coords){
	this.coords = coords;
	//coords = [[x0,y0,w0,h0],...[xn,yn,wn,hn]]
	this.sheet = img;
	this.draw = function(nr,x,y){
		ctx.drawImage(img,this.coords[nr][0],this.coords[nr][1],this.coords[nr][2],this.coords[nr][3],x,y,this.coords[nr][2],this.coords[nr][3]);
	}
	this.draw0 = function(nr,x,y){
		ctx0.drawImage(img,this.coords[nr][0],this.coords[nr][1],this.coords[nr][2],this.coords[nr][3],x,y,this.coords[nr][2],this.coords[nr][3]);
	}
}

Animatsiooniga objekt jõuluvana luuakse klassiga animated; jõuluvanal on 4 animatsiooni:

	
function animated(img,dx,dy,states, n_fr,state,pos,sp){ //states - array of coordinates
	  this.image = img;
	  //console.log('santa: '+this.image.height);
	  this.width = dx;
	  this.height = dy;
	  this.states = states;
	  this.dx = dx; //this.width;
	  this.dy = dy; //this.height/3; //32
	  this.n_frames = n_fr; //number of frames
	  this.actualFrame = 0;
	  this.speed = sp;  //how many *game* frames a frame is shown
	  this.step = 0;
	  this.y = 0;
	  this.x = 0;
	  this.state = state;	  
	  this.pos = pos;		  
	  
	  this.setPosition = function(X, Y){
		this.x = X;
		this.y = Y;
	  }

	  this.draw = function(cx,cy){
	  var cx=cx||0;
	  var cy=cy||0;
	  
	  if (this.pos == 0) //still
		ctx.drawImage(this.image,santa.states[santa.state][0],santa.states[santa.state][1],this.dx,this.dy,this.x+xl,this.y+yl,this.dx,this.dy);
		else {   // walking
		ctx.drawImage(this.image,santa.states[santa.state][0],santa.states[santa.state][1] + this.dy*this.actualFrame,this.dx,this.dy,this.x+xl,this.y+yl,this.dx,this.dy);
		
		this.step ++;
		if (this.step == this.speed) {
			this.actualFrame++;
			//this.actualFrame = this.actualFrame%this.n_frames 1;
			if (this.actualFrame == this.n_frames)
				this.actualFrame = 1;
			this.step = 0;
			}
			}
		}
	
	}

Labyrint luuakse nn 'uuristamismeetodiga' - algul on labyrindiga ühendatud vaid sissepääs (vasak ülanurk); igal sammul liidetakse uus veel ühendamata ruum, kui selle kõrval on juba ühendatud ruum (luuakse nende vahele uks):

function create(vl,rl,n,wt,ht,d,w0,h0,k){
	var ruudud = [];
	var connected = [];
	var free = [];
	var dirs = [0, 1, 2, 3];
	for (i = 0; i < rl; i++) {
			for (var j = 0; j < vl; j++) {
				ruudud.push(new ruut(i, j));
			}
		}
	var i0 = random(0,ruudud.length);
	connected.push(ruudud[i0]);
	//esimene ruum labyrindis
	free = ruudud.slice(0);
	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 (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,free);
				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);
				}
			}
		}
	}
	return connected;
	}

	
function draw_labyrinth(connected,trees,d,k,vl,rl,wt,ht,w0,h0){	
	map = [];
	for(var i=0;i < vl*k+1;i++) //colums in virtual
		map[i]=[];
	for(var i=0;i < vl*k+1;i++)
	for(var j=0;j < rl*k+1;j++)
	map[i][j]=0;
	
	for (var i = 0; i < connected.length; i++) {
		var xx = connected[i].m;
		var yy = connected[i].n;
		if (connected[i].doors.indexOf(0) == -1){
			treeline((xx + 1) * d, yy * d, (xx + 1) * d, (yy + 1) * d, wt,ht, trees);
			for(var i1=yy*k;i1 < (yy+1)*k;i1++)
				map[(xx+1)*k][i1]=1; }
		if (connected[i].doors.indexOf(1) == -1){
			treeline(xx * d, (yy + 1) * d, (xx + 1) * d+1, (yy + 1) * d, wt,ht, trees);
			for(var i1=xx*k;i1 < =(xx+1)*k;i1++)
				map[i1][(yy+1)*k]=1; }
		
		if (xx == 0 && connected[i].doors.indexOf(2) == -1){
			treeline(xx * d, yy * d, xx * d, (yy + 1) * d, wt,ht, trees);
			for(var i1=yy*k;i1 < (yy+1)*k;i1++)
				map[xx*k][i1]=1; }
		if (yy == 0 && connected[i].doors.indexOf(3) == -1){
			treeline(xx * d, yy * d, (xx + 1) * d, yy * d, wt,ht, trees);
			for(var i1=xx*k;i1 < (xx+1)*k;i1++)
				map[i1][yy*k]=1; }
	}
	ctx0.clearRect(w0-wt,h0-2*ht,wt,ht); // door out!
	map[vl*k][rl*k-1]=0;
	return map;
}

function next(r, dir,free) {
	var nn1;
	var mm1;
	switch (dir) {
		case 0:
			nn1 = r.n;
			mm1 = r.m + 1;
			break;
		case 1:
			nn1 = r.n + 1;
			mm1 = r.m;
			break;
		case 2:
			nn1 = r.n;
			mm1 = r.m - 1;
			break;
		case 3:
			nn1 = r.n - 1;
			mm1 = r.m;
			break;
		default:
			nn1 = r.n;
			mm1 = r.m;
	}
	
	for (var i = 0; i < free.length; i++) {
		
		if ((free[i].n == nn1) && (free[i].m == mm1)) {
			return i;
			break;
		}
	}
	return -1;
}

function connection(n, m, direction) {
	//direction=1 - right
	//direction=2 - down
	//direction=3 - left
	//direction=4 - up
	switch (direction) {
		case 0:
			this.n1 = n;
			this.m1 = m;
			this.n2 = n + 1;
			this.m2 = m;
			this.dir = 0;
			break;
		case 1:
			this.n1 = n;
			this.m1 = m;
			this.n2 = n;
			this.m2 = m + 1;
			this.dir = 1;
			break;
		case 2:
			this.n1 = n;
			this.m1 = m;
			this.n2 = n - 1;
			this.m2 = m;
			this.dir = 2;
			break;
		case 3:
			this.n1 = n;
			this.m1 = m;
			this.n2 = n;
			this.m2 = m - 1;
			this.dir = 3;
			break;
		default:
			this.n1 = n;
			this.m1 = m;
			this.n2 = n;
			this.m2 = m;
			this.dir = 0;
		//no connection
	}
}

function reverse(dir) {
	return (dir + 2) % 4;
}
function ruut(n, m) {
	this.n = n;
	this.m = m;
	this.doors = [];
	}
	
function treeline(x0,y0,x1,y1,wt,ht, trees){
	var nx = (x1 - x0)/wt;
	var ny = (y1 - y0)/ht;
	var n = Math.max(nx,ny);
	var dx = (x1 - x0)/n;
	var dy = (y1 - y0)/n;
	for(var i=0;i < n;i++){
		trees.draw0(Math.floor(Math.random()*trees.coords.length),x0 + i*dx, y0 + i*dy);
		
	}
}

Kui need klassid on lisatud, võib juba kontrollida: lisada initsialiseerimisfunktsiooni lõppu metsa ja jõuluvana joonistamise:

xb = wt/2+(d-santa.width)/2; //santai (bogy) (alg)asend virtuaalses labyrindis
	yb = ht/2 + (d-santa.height)/2;
	santa.setPosition(xb,yb);
	santa.draw();
	
	rooms = create(vl,rl,n,wt,ht,d,w0,h0,k);
	map = draw_labyrinth(rooms,trees,d,k,vl,rl,wt,ht,w0,h0);
	labyr = ctx0.getImageData(0, 0, w0, h0);
	
	
	ctx.putImageData(labyr, xl,yl,-xl, -yl,wc,hc)
    

-ekraanile peaks ilmuma mets ja jõuluvana.

Pakid jagab metsa laiali funktdsioon placepakid, nad joonistab 8lisab metsale) drawpakid:

function placepakid(v,r,d,n){
	//select random squares for mushrooms
	var indexis=[];
	var pakikohad1 = []; //ajutine massiiv otsimiseks
	for(var i = 0; i < v*r-1; i++) indexis[i] = i+1; //ruudud alates teisest - esimeses on 
	var i0 = Math.floor(Math.random()*indexis.length);
	pakikohad1.push(indexis[i0]);
	indexis.splice(i0,1); //korduste vältimine !
	i0 = Math.floor(Math.random()*indexis.length); //next!
	while(pakikohad1.length < pakke && pakikohad1.indexOf(indexis[i0])==-1){
	pakikohad1.push(indexis[i0]);
	indexis.splice(i0,1);
	i0 = Math.floor(Math.random()*indexis.length);
	}
	//console.log(pakikohad);
	for (var i = 0; i < pakke; i++) {
	var v1 = pakikohad1[i]%v;
	var r1 = Math.floor(pakikohad1[i]/v);
	var sx = v1*d + d/2;
	var sy = r1*d + d/2;
	pakikohad.push({r:indexis[i0],x:sx,y:sy, seen:Math.floor(Math.random()*n)});
	}
	return pakikohad;
}

function drawpakid() {
	ctx0.clearRect(0,0,w0,h0);
	ctx0.putImageData(labyr, 0,0);
	for(var i=0;i < pakikohad.length; i++) {
		pakid.draw0(pakikohad[i].seen,pakikohad[i].x, pakikohad[i].y );
	}

Pakkide lisamiseks lisame vastavate funktsioonide käivitamise ka mängu initsialiseerimisfunktsioonile:

pakke = 40; //kui palju lisatakse mängu (peab olema < v*r-1 - esimene ruut santa jaoks!)
	pakke = Math.min(vl*rl-1,pakke);
	pakikohad = []; //ruudud, kus pakid paiknevad
	korjatud = 0;
	pakikohad = placepakid(vl,rl,d,pakid.coords.length);
	drawpakid();
	pakimets = ctx0.getImageData(0, 0, w0, h0);
	ctx.putImageData(pakimets, xl,yl,-xl, -yl,wc,hc)

- viimase reaga peaks ekraanile ilmuma juba pakke täis mets.

Jõuluvana juhitakse klaviatuuriga, lisame klahvide seisu salvestamise:

keysDown = [];
	keysDown[37]=keysDown[38]=keysDown[39]=keysDown[40]=false;

Jõuluvana juhtimise testimiseks käivitame mängu kaadrivahetuse: lisame initsialiseerimisfunktsiooni lõppu

GameLoop();

Funktsioon GameLoop juhib kogu mängu; igas kaadris kontrollib funktsioon update jõuluvana liikumist (kasutab kokkupõrkefunktsioone)

function GameLoop(){
		update(speed); //speed - mitu pikslit kaadris, tuleb anda initsialiseerimisel
		
		if (santa.x+xl > wc/2+d && wc - xl < w0)
			{xl -= speed;  //metsa skrollimine
			}
		else if (santa.x+xl < wc/2 && xl > 0)
			{xl += speed;
			}
		
		if (santa.y+yl > hc/2 &&  hc - yl < h0)
			{yl -= speed; }
		else if (santa.y+yl < hc/2-d && yl < 0)
			{yl += speed;}
		ctx.clearRect(0,0,wc,hc);
		
		ctx.drawImage(canvas0, -xl,-yl,wc,hc,0,0,wc,hc); //pakimets
		santa.draw();
        //lumehelbed ja valgus - kui veel pole, tuleb välja kommenteerida
		/*
		ctx.globalAlpha=0.4;
		ctx.globalCompositeOperation = "source-over";
		ctx.globalCompositeOperation = "lighter";
		ctx.drawImage(light,0,0,64,64,0,0,wc,hc);
		for(var i=0;i < nrOfFlakes;i++)
			{flakes[i].update();}
		 ctx.globalAlpha = 1;
        */
		setTimeout(GameLoop, 30);
	}
    
function update(sp){

	var dx = 0;
	var dy = 0;
	
	if (keysDown[37]) { // left arrow
		dx = -sp;
		santa.state = "vasakule";
		santa.pos = 1;
	}
	else if (keysDown[38]) { // up arrow
		dy = -sp;
		santa.state = "sinna";
		santa.pos = 1;
	}
	else if (keysDown[39]) { // right arrow
		dx = sp;
		santa.state = "paremale";
		santa.pos = 1;
	}
	else if (keysDown[40]) { // down arrow
		dy = sp;
		santa.state = "siia";
		santa.pos = 1;
	}
	else
		santa.pos = 0;
		
	if(dx !=0 || dy !=0) {
		santa.pos = 1; //walking
		check_collision1(dx,dy,santa);
		}
	else
		santa.pos = 0;  //still
	
};

function collision(x1,y1,w1,h1,x2,y2,w2,h2){
	return 	x1 < x2 + w2 && x2 < x1 + w1 &&
			y1 < y2 + h2 && y2 < y1 + h1
	}
	
function check_collision(dx,dy,obj1,obj2){ //obj1 is moving, obj2 is bitmapdata to check
	
	var x = obj1.x;
	var y = obj1.y;
	var wb = obj1.width;		
	var hb = obj1.height;
	
	var points1;
	
	if (dx < 0) {
		if (dy == 0)
			points1 = [[x,y],[x,y+hb]]; 
		else if (dy > 0)
			points1 = [[x,y],[x,y+hb],[x+wb,y+hb]]; 
		else if (dy < 0)
			points1 = [[x+wb,y],[x,y],[x,y+hb]]; 
			};
	if (dx == 0) {
		if (dy > 0)
			points1 = [[x,y+hb],[x+wb,y+hb]]; 
		else if (dy < 0)
			points1 = [[x+wb,y],[x,y]];
			};
	if (dx > 0) {
		if (dy == 0)
			points1 = [[x+wb,y+hb],[x+wb,y]]; 
		else if (dy > 0)
			points1 = [[x,y+hb],[x+wb,y+hb],[x+wb,y]]; 
		else if (dy < 0)
			points1 = [[x+wb,y+hb],[x+wb,y],[x,y]]; 
			};
			 
	 var t = 0;
	 var j = Math.max(Math.abs(dx),Math.abs(dy)); //how many steps to check
	 var ddx = dx/j;
	 var ddy = dy/j;
	 found: do{
		t++;
		var i = 0;
	  do{
		 var x1 = points1[i][0]; //coordinate of the faremost corner
		 var y1 = points1[i][1];
		 
		 var xx1 = (x1+t*ddx);
		 var yy1 = (y1+t*ddy);
		 var p = 4*(xx1 + w0*yy1);
		 if (obj2.data[p+3]!=0 ) // not transparent!
		 { break found; }; 
			i++; 
		 } while (i < points1.length); 
		} while (t < j) ;
	
	xb = Math.floor(xb + (t-1)*ddx); //move obj1 !
	yb = Math.floor(yb + (t-1)*ddy);
	santa.setPosition(xb,yb);
	korja();
}
function check_collision1(vx,vy,obj1){
//vx,vy - move of obj1 on next step
// collision is based on map
	var x = obj1.x;
	var y = obj1.y;
	var wb = obj1.width;		
	var hb = obj1.height;
	
	var points1;
	
	if (vx < 0) {
		if (vy == 0)
			points1 = [[x,y],[x,y+hb]]; 
		else if (vy > 0)
			points1 = [[x,y],[x,y+hb],[x+wb,y+hb]]; 
		else if (vy < 0)
			points1 = [[x+wb,y],[x,y],[x,y+hb]]; 
			};
	if (vx == 0) {
		if (vy > 0)
			points1 = [[x,y+hb],[x+wb,y+hb]]; 
		else if (vy < 0)
			points1 = [[x+wb,y],[x,y]];
			};
	if (vx > 0) {
		if (vy == 0)
			points1 = [[x+wb,y+hb],[x+wb,y]]; 
		else if (vy > 0)
			points1 = [[x,y+hb],[x+wb,y+hb],[x+wb,y]]; 
		else if (vy < 0)
			points1 = [[x+wb,y+hb],[x+wb,y],[x,y]]; 
			};
			 
	 var t = 0;
	 var j = Math.max(Math.abs(vx),Math.abs(vy)); //how many steps to check
	 var dvx = vx/j;
	 var dvy = vy/j;
	 found: do{
		t++;
		var i = 0;
	  do{
		 var x1 = points1[i][0]; //coordinate of the faremost corner
		 var y1 = points1[i][1];
		 var xx1 = (x1+t*dvx);
		 var yy1 = (y1+t*dvy);
		 var v = Math.floor(xx1/wt);
		 var r = Math.floor(yy1/ht);
		 
		 if (map[v][r]!=0 ) // not transparent!
		 { 
		 break found; }; 
		 i++; 
		 } while (i < points1.length); 
		} while (t < j) ;
	
	xb = Math.floor(xb + (t-1)*dvx); //move obj1 !
	yb = Math.floor(yb + (t-1)*dvy);
	obj1.setPosition(xb,yb);
	korja();
}

Järgnev funktsioon on pakkide korjamiseks:

function korja(){
// kontrollime ruudu [xb,yb,32,32] lõikumist 
// ruuduga [pakikohad[i].x, pakikohad[i].y, 32,32]

	var i = 0;
	while (i < pakikohad.length){
		if 	(collision(xb,yb,wt,ht,pakikohad[i].x,pakikohad[i].y,wt,ht)) 
			break;
		else
			i++;}
	
	if (i < pakikohad.length) //oli seen!
	{	 
			
			
			korjatud ++;
			pick_snd.cloneNode(true).play();
			inf.innerHTML = 'Korjatud '+korjatud + ' pakki!';
			pakikohad.splice(i,1);
			drawpakid(); //uued!
				
	}			
}

Helbed luuakse klassidega

function snowFlake(img,  xPos, yPos) {
	
	// set initial snowflake properties
    this.img = img;
	this.w = img.width;
	this.h = img.height;
    //this.r = r;
    this.v = 1+4*Math.random();
    this.xPos = xPos;
    this.yPos = yPos;
	this.suund = (1+2*Math.random())*((Math.random()>0.5)?1:-1);
	// declare variables used for snowflake's motion
    //this.counter = 0;
    this.sign = (Math.random()>0.5)?1:-1; ////Math.floor(Math.random() * 2);
	// setting an initial opacity and size for our snowflake
    this.opacity = 0.1+Math.random();
    this.size = 0.5 + Math.random(); //0.5 .. 1.5
	
	// the function responsible for actually moving our snowflake
    this.update = function () {
    
		this.xPos += this.suund; //this.sign*this.v*Math.cos(this.counter)/20;
        this.yPos += this.v;
		if (Math.random() < 0.01)
			this.suund = (1+2*Math.random())*((Math.random()>0.5)?1:-1);
		// setting our snowflake's position
        ctx.globalAlpha = this.opacity;
		ctx.drawImage(this.img,this.xPos,this.yPos);
        // if snowflake goes below the browser window, move it back to the top
        if (this.yPos > hc) {
        	this.yPos = -50;
        }
    }
}
	
function createFlakes(img,nr){
	for (var i = 0; i <  nr; i++) {
      	// create new instance
		var x0 = 10 + (w0-20)*Math.random();
		var y0 = (h0-20)*math.random();
        var flake = new SnowFlake(img,x0,y0);
        flakes.push(flake);
      
    }
	return flakes;
}	

Helbed salvestatakse massiivis, mis luuakse mängu initsialiseerimisel:

nrOfFlakes = 25;
	flakes = [];
	for (var i = 0; i < nrOfFlakes; i++) {
      	// create new instance
		var x0 = 10 + Math.floor((wc-20)*Math.random());
		var y0 = Math.floor((hc-20)*Math.random());
		//console.log(x0+','+y0);
        var flake = new snowFlake(images.flake,x0,y0);
        flakes.push(flake);
    }
Valgustus on vaid üks spriit, mis mängu initsialiseerimisel salvestatakse eraldi kanvaal
light = document.createElement('canvas');
	ctxL = light.getContext('2d');
	ctxL.drawImage(images.lamp,0,0);

Mängu ajal) funktsioonis GameLoop) venitatakse see üle kogu kanvaa (ülal välja kommenteeritud).


Ülesandid

1. Modifitseeri helveste skripti, nii et tekiks erineva suurusega helbeid.

2. Modifitseeri valgustust - praegu ripub lamp mänguvälja keskel - tee nii, et see valgustaks jõuluvana ja liiguks koos temaga.