HTML5 CANVAS: игра Lines

Lines

Lines (Color Lines, в народе Шарики) — логич. компьютерная игра, изобретённая в 1992 году.

В игре на экране показано поле, в случайные клетки на котором программа выставляет шарики разных цветов. За один ход игрок может передвинуть один шарик, выделив его и указав его новое местоположение. Для совершения хода необходимо, чтобы между начальной и конечной клетками существовал путь из свободных клеток. Цель игры состоит в удалении максимального количества шариков, которые исчезают при выстраивании шариков одного цвета. При исчезновении ряда шариков новые шарики не выставляются. В остальных случая каждый ход выставляются новые шарики.

Ради интереса решил сделать что-то подобное, используя элемент Canvas в HTML5.

Игра будет состоять из 2-х файлов: сама страница html и файл логики игры.

Содержимое html файла выглядит приблизительно так:

<html> <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <script src="lines5.js"></script><title>Lines</title></head><body><canvas id="Lines5">HTML5 нид, мэн</canvas><script>init(5,6,3);</script></body></html>

В общем-то большая часть работы ведётся во втором файле. Он состоит из метода инициализации игры:

//инициализацияfunction init(wCount, hCount, rCount) {var cellSize = 60;var canvas = document.getElementById("Lines5");canvas.width = cellSize*wCount+150; // ширинаcanvas.height = cellSize*hCount;// высотаvar context = canvas.getContext("2d");var field = new l5(); // создаём объект field.setParams(wCount, hCount, rCount);context.fillStyle = "#969696"; // цвет "заливки"context.fillRect(0, 0, canvas.width, canvas.height); // закраска   field.draw(context, cellSize);// функция производит необходимые действие при кликеfunction event(x, y) { field.move(x, y); }// обрабатка кликов мышиcanvas.onclick = function(e) { var x = (e.pageX - canvas.offsetLeft) / cellSize | 0;var y = (e.pageY - canvas.offsetTop)  / cellSize | 0;event(y, x); };   }

Ну и, собственно, класс, отвечающий за логику игры:

function l5() {var loser = false;var rCount = 0;//  количество рэндомно генерируемых шаров var wCount = 0;//  в ширину сколькоvar hCount = 0;  //  в высоту сколькоvar mas = null;//поле//var clicks = 0;var context = null;//контекстvar curHeight =  0;//текущая высота прыжка шарикаvar curAsc = true;//подпрыгивает или падает после прыжкаvar curI = -1;//строка выбранногоvar curJ = -1;//столбец выбранногоvar timer = null;var self = this;var points = 0;//очкиvar masRandom = Array();var randomCurrent = 0;//таймер для прыганья ^_^this.runMultiple = function (){i = curI;j = curJ;if(curAsc) curHeight--;else curHeight++;if(curHeight==-5) curAsc = false;if(curHeight==0) curAsc = true;this.clearCell(i,j);this.circleView(i,j,curHeight, this.getColor(mas[i][j]));};this.setParams = function(weightC, heightC, rC){rCount=rC;//  количество рэндомно генерируемых шаров wCount = weightC;//  в ширину сколькоhCount = heightC;  //  в высоту };this.clearTimer = function (i,j){curHeight =  0;curAsc = true;curI = -1;curJ = -1;this.clearCell(i,j);this.circleView(i,j,0, this.getColor(mas[i][j]));clearInterval(timer);}//проверка на проходимостьthis.checkPassability = function (iCheck, jCheck){var afterCount = 0;var beforeCount = 0;var curColor = mas[curI][curJ];mas[curI][curJ] = -1;while(true){afterCount = 0;beforeCount = 0;for(var i = 0; i < hCount; ++i)for(var j = 0; j < wCount; ++j)if(mas[i][j] == -1) ++beforeCount;for(var i = 0; i < hCount; ++i)for(var j = 0; j < wCount; ++j)if(mas[i][j] == -1){if((i-1 >= 0) &amp;amp;amp;&amp;amp;amp; (mas[i-1][j] == 0))mas[i-1][j] = -1;if((i+1 < hCount) &amp;amp;amp;&amp;amp;amp; (mas[i+1][j] == 0))mas[i+1][j] = -1;if((j-1 >= 0) &amp;amp;amp;&amp;amp;amp; (mas[i][j-1] == 0))mas[i][j-1] = -1;if((j+1 < wCount) &amp;amp;amp;&amp;amp;amp; (mas[i][j+1] == 0))mas[i][j+1] = -1;}for(var i = 0; i < hCount; ++i)for(var j = 0; j < wCount; ++j)if(mas[i][j] == -1) ++afterCount;if(afterCount == beforeCount) break;}var cool = false;if(mas[iCheck][jCheck] == -1)cool = true;mas[curI][curJ] = curColor;for(var i = 0; i < hCount; ++i)for(var j = 0; j < wCount; ++j)if(mas[i][j] == -1) mas[i][j] = 0;return cool;}this.eraseCircle = function (i,j){mas[i][j] = 0;this.clearCell(i, j);}this.getPoints = function(sameColorCount){return Math.pow(2,sameColorCount);}this.checkOfLosing = function (){if(loser) return loser;var emptyCellsCount = 0;for(var i = 0; i < hCount; ++i)for(var j = 0; j < wCount; ++j)if(mas[i][j] == 0) ++emptyCellsCount;loser = (emptyCellsCount==0);return loser;}this.showLosingMsg = function(){context.beginPath();context.fillStyle = "#7e7e7e";context.strokeStyle = "#000";context.moveTo(size*wCount+2, 0);context.fillRect(10, size*hCount/2-20, size*wCount-10, size*hCount/2+20);context.stroke();context.fillStyle = "#000"; context.font = "bold 20px Sans";context.fillText('Losing with Points: '+points,20,size*hCount/2);}//проверка горизонталиthis.checkHorizontalLine = function (iCheck, jCheck){var erase = false;var sameColorCount = 0;var jMasForErase = new Array();for(var j=jCheck+1; j < wCount; j++){if(mas[iCheck][j] == mas[iCheck][jCheck]){jMasForErase[sameColorCount] = j;++sameColorCount;}else break;}for(var j=jCheck; j >=0; j--){if(mas[iCheck][j] == mas[iCheck][jCheck]){jMasForErase[sameColorCount] = j;++sameColorCount;}else break;}if(sameColorCount>=4){erase = true;points += this.getPoints(sameColorCount);for(var j=0; j <sameColorCount; ++j)this.eraseCircle(iCheck,jMasForErase[j]);}return erase;}//проверка вертикалиthis.checkVerticalLine = function (iCheck, jCheck){var erase = false;var sameColorCount = 0;var iMasForErase = new Array();for(var i=iCheck+1; i < hCount; i++){if(mas[i][jCheck] == mas[iCheck][jCheck]){iMasForErase[sameColorCount] = i;++sameColorCount;}else break;}for(var i=iCheck; i >=0; i--){if(mas[i][jCheck] == mas[iCheck][jCheck]){iMasForErase[sameColorCount] = i;++sameColorCount;}else break;}if(sameColorCount>=4){erase = true;points += this.getPoints(sameColorCount);for(var i=0; i <sameColorCount; ++i)this.eraseCircle(iMasForErase[i],jCheck);}return erase;}//проверка на уничтожение линийthis.checkLines = function (i,j){var erase = false;var curColorN = mas[i][j];if(this.checkHorizontalLine(i,j)){erase = true;this.circleView(i,j,0,this.getColor(curColorN));mas[i][j] = curColorN;}if(this.checkVerticalLine(i,j) || erase){this.eraseCircle(i,j);erase = true;}return erase;}//при клике, действия с шарикомthis.move = function (i, j){if(loser) return;//если повторно на тот же шарик нажалиif(curI == i &amp;amp;amp;&amp;amp;amp; curJ == j)this.clearTimer(i,j);else{//alert(curI+"=="+curJ);if(mas[i][j] !== 0){//если был нажат ранее шарик, но сейчас жмём не на негоif(curI !== -1 &amp;amp;amp;&amp;amp;amp; curJ !== -1)this.clearTimer(curI,curJ);curI = i;curJ = j;timer = setInterval(function() {self.runMultiple();}, 50);}else{//если может пройтиif(this.checkPassability(i,j)){//ползётthis.circleGo(i,j);//если ничего не исчезлоif(!this.checkLines(i,j))//новые шарыthis.drawRandomCircles();else this.drawPoints();}else {}}}}//двигаем шарикthis.circleGo = function(i,j) {var iToClear = curI;var jToClear =  curJ;mas[i][j] = mas[curI][curJ];this.clearTimer(curI, curJ);mas[iToClear][jToClear] = 0;this.clearCell(iToClear, jToClear);this.circleView(i,j,0,this.getColor(mas[i][j]));}// рисуем ячейкуthis.cellView = function(x1,y1,x2,y2) {context.beginPath();   context.lineWidth = 2;context.moveTo(x1, y1);context.lineTo(x2, y1);context.lineTo(x2,y2);context.lineTo(x1, y2);context.lineTo(x1, y1);context.stroke();};//чистка ячейки  this.clearCell = function(y,x) {//сначала заливка фономcontext.beginPath();context.fillStyle = "#969696";context.strokeStyle = "#000";context.moveTo(size*x, size*y);context.fillRect(size*x, size*y, size, size);context.stroke();//затем перерисовкаthis.cellView(size*x, size*y, size*(x+1),size*(y+1));};//цвет для рэндомного шарикаthis.getColor = function(n) {rand = Math.random(); switch(n){case 1: return '#d71f42';case 2: return '#281fd7';case 3: return '#1fd74f';case 4: return '#e2ee59';default: return '#9721d4';}};  this.randomN = function(n) {rand=Math.random(); if(rand<0.2) return 1;if(rand<0.4) return 2;if(rand<0.6) return 3;if(rand<0.8) return 4;return 5;};//рисуем шарикthis.circleView = function(y,x, h, color) {var radius = size/4;context.strokeStyle = color;context.beginPath();context.moveTo((x+0.5)*this.size+radius ,(y+0.5)*size);context.arc((x+0.5)*size, (y+0.5)*size+h, radius , 0, 2*Math.PI);context.fillStyle = color; context.fill();context.stroke();//context.restore();};  this.drawRandomCircle = function() {if(this.checkOfLosing())  return;var randj=Math.round((Math.random())*(wCount-1)); var randi=Math.round((Math.random())*(hCount-1)); var fl=0;while(fl==0){if(mas[randi][randj]==0){mas[randi][randj] = this.randomN();this.circleView(randi,randj,0, this.getColor(mas[randi][randj])); masRandom[randomCurrent][0] = randi;masRandom[randomCurrent][1] = randj;randomCurrent++;fl=1;//alert('g');}else{randj=Math.round((Math.random())*(wCount-1)); randi=Math.round((Math.random())*(hCount-1)); }}if(this.checkOfLosing())  return;};this.drawPoints = function() {context.beginPath();context.fillStyle = "#969696";context.strokeStyle = "#000";context.moveTo(size*wCount+2, 0);context.fillRect(size*wCount+2, 0, size*wCount+150, size*hCount);context.stroke();context.fillStyle = "#000"; context.font = "bold 20px Sans";context.fillText(points,size*wCount+70,100);context.font = "bold 30px Sans";context.fillText('Points:',size*wCount+50,70);}this.drawRandomCircles = function(){for (var i = 0; i < rCount; i++) this.drawRandomCircle();//проверям, может рэндомные шары норм в линию на уничтожение всталиfor (var i = 0; i < rCount; i++) if(this.checkLines(masRandom[i][0],masRandom[i][1]))this.drawPoints();randomCurrent = 0;if(loser) this.showLosingMsg();}// рисуем полеthis.draw = function(cont, s) {context = cont;size = s;//поле пусто - забиваем нулямиmas = new Array(hCount);for(var i = 0; i < rCount; i++)masRandom[i] = new Array(2);for(var i = 0; i < hCount; i++){mas[i] = new Array(wCount);for(var j = 0; j < wCount; j++)mas[i][j] = 0;}context.strokeStyle = "#000";//рисуем ячейкиfor (var i = 0; i < hCount; i++) {for (var j = 0; j < wCount; j++) this.cellView(size*j, size*i, size*(j+1),size*(i+1));}//начальные рэндомные шарыthis.drawRandomCircles();this.drawPoints();};}

Код конечно грязноват, да и логика не вся продумана. Пока уничтожение только при совпадении по горизонтали и вертикали, диагональные элементы не проверяются. Плюс, если говорить об оригинале, то не показывается, где на следующем ходу появятся шары.

Если кто хочет оценить игру, смотрим.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *