пятница, 7 августа 2015 г.

Пятнашки на canvas

http://rablv.blogspot.com/2015/08/canvas.html
Ранее я опубликовал урок о том, как создать игру пятнашки на Flash. Чуть позже мы портировали игру на Андроид сделав сборку с помощью Adobe Air и переписав управление под мультитач.

Теперь попробуем перевести игру рельсы HTML5. Использовать для будем исключительно элемент canvas (без CSS).



Самое главное отличие от flash-версии конечно будет способ отображения графической составляющей. Примитивная графика в нашей игре может быть программно сгенерирована прямо налету. Поэтому все элементы будем рисовать прямо на канвасе.

И так, приступим. Элементу <canvas> я задал id="map". Логика игры полностью повторяет логику flash-версии и просто переписана на Javascript.

window.onload = init;

var map;
var ctxMap;

var gameWidth=288;
var gameHeight=400;

var matrix = [[],[],[],[]] ;

var checkMatrix = [[1,  2,  3,  4 ],
       [5,  6,  7,  8 ],
       [9,  10, 11, 12],
       [13, 14, 15, 0 ] ] ;
var randomMas=[];
var countMc;
var moveCount=0;
var flagVictory=false;



function init(){
 
 map = document.getElementById("map");
 map.width=gameWidth;
 map.height=gameHeight;
 ctxMap = map.getContext("2d");

 game();
 
 
 map.addEventListener("click", clickFunc, false);
}




var Tile = function(){
 this.i=0;
 this.j=0;
 this.x=0;
 this.y=0;
 this.number=0;
 this.addTile = function(){
  ctxMap.fillStyle="#FFFFE8";
  ctxMap.fillRoundedRect(this.x, this.y, 64, 64, 10);
 }
 this.setText=function(text){
  with (ctxMap){
   font = '20pt impact';
   textBaseline = 'top';
   textAlign = 'center';
   fillStyle = '#000';
   fillText(text, this.x+31, this.y+20);
  }
 }
 
 this.clearTile = function(){
  ctxMap.fillStyle="#CCFFFF";
  ctxMap.fillRoundedRect(this.x, this.y, 64, 64, 10);

 }
}


function clickFunc(e){
 
 for (var j = 0; j < 4; j++ ) {
    for (var i = 0; i < 4; i++ ) {
 if (matrix[i][j].x+64 > e.offsetX && matrix[i][j].x < e.offsetX &&
        matrix[i][j].y+64 > e.offsetY && matrix[i][j].y < e.offsetY ) 
  move(matrix[i][j]);
 }}

 if (44.7+64 > e.offsetX && 44.7 < e.offsetX &&
        326.5+64 > e.offsetY && 326.5 < e.offsetY ) 
  newRandGame();
 
 
}



  // обработка клика на плику, перемещение плит
  function move(e) {
    
   var currNumber = e.number;  // сохраняем номер текущей плитки
    
   if (currNumber != 0 && !flagVictory) {   // если это не пустое поле и не победное положение, то продолжаем
     
  
   var i = e.i;    // сохраним координаты плитки в матрице
   var j = e.j;
   var flag = false;   // флаг для определения завершения хода
    
    
   for (var k = 0; k < 4; k++ ) {  // нам нужно проверить наличие пустой плитки с четырёх сторон
  
     switch(k) {
      case 0 :{ 
      if (i+1 < 4 && matrix[i + 1][j].number == 0) { // если плитка не крайняя и снизу есть пустое поле, то   
       matrix[i + 1][j].addTile();   // пустое поле превращаем в плитку
       matrix[i + 1][j].setText(e.number); // задаем новой плитке строку с числом
       matrix[i + 1][j].number = currNumber;  // теперь новая плитка превратилась в ту, на которую мы нажали
       flag = true;   // говорим что ход заврешен
       };
      break;
      }
       
      case 1 :{ 
       if (j+1 < 4 && matrix[i][j+1].number == 0) {
        matrix[i][j + 1].addTile();
        matrix[i][j + 1].setText(e.number);
        matrix[i][j + 1].number = currNumber;
       flag = true;
       };
      break;
      }
       
      case 2 :{ 
      if (i-1 > -1 && matrix[i - 1][j].number == 0) {
       
       matrix[i - 1][j].addTile();
       matrix[i - 1][j].setText(e.number);
       matrix[i - 1][j].number = currNumber;
       flag = true;
       };
      break;
      }
       
      case 3 :{ 
       if (j-1 > -1 && matrix[i][j-1].number == 0) {
        matrix[i][j - 1].addTile();
        matrix[i][j - 1].setText(e.number);
        matrix[i][j - 1].number = currNumber;
       flag = true;
       };
      break;
      }
     }
     if (flag) { // если ход завершен
      matrix[i][j].number = 0;  // текущую плитку делаем пустым полем
      e.clearTile();
      moveCount++;   // счетчик ходов
   setCountMc(moveCount); // увеличиваем счетчик
   //   if(check()) flagVictory=true; // проверка на победу
      break; 
     }
    }
   }
  }






function game(){
 
 ctxMap.fillStyle="#CECECE";
 ctxMap.fillRect(0,0,gameWidth,gameHeight);
 
 
 setRestartBtn();
 setCountMc(0);
 setTiles();
 
}


  function setRestartBtn() {
 with (ctxMap){
  fillStyle="#333333";
  fillRoundedRect(44.7, 326.5, 50, 50, 10);

 
  font = '10pt impact';
  textBaseline = 'top';
  textAlign = 'left';
  fillStyle = '#ffffff';
  fillText("Restart", 44.7+5, 326.5+17);
 }
  }




  function setCountMc(string) {
 
 with (ctxMap){
  fillStyle="#333333";
  fillRoundedRect(192.55, 326.5, 50, 50, 10);

 
  font = '10pt impact';
  textBaseline = 'top';
  textAlign = 'left';
  fillStyle = '#ffffff';
  fillText("MOVES", 192.55+7, 326.5+5);
  
  font = '18pt impact';
  textAlign = 'center';
  fillText(string, 192.55+25, 326.5+22);
 }
  }


  
  
  

function setTiles(){
 
 for (var j=0; j<4; j++){
  for (var i=0; i<4; i++){
   
   matrix[i][j]=new Tile();
   matrix[i][j].i=i;
   matrix[i][j].j=j;
   matrix[i][j].x=4+i*64+8*i;
   matrix[i][j].y=4+j*64+8*j;
 
  }
 }
 
 newRandGame();
}

 // функция создания новой игры
  function newRandGame() {
   flagVictory = false;  // сброс флага победы
   moveCount=0;  // сброс счетчика ходов
  // countMc.Count_field.text = String(moveCount); 
    
   var tempcount = 0;  // временная переменная для присвоения занчений в массиве
   random();    // генерируем рандомную последовательность от 1 до 15 в массив
   for (var j = 0; j < 4; j++ ) { // в двойном цикле пишем рандомную последовательность в нашу матрицу
    for (var i = 0; i < 4; i++ ) {
  matrix[i][j].addTile();
     matrix[i][j].setText(randomMas[tempcount]); //= String(randomMas[tempcount]);
     matrix[i][j].number = randomMas[tempcount];
     tempcount++;
    }
  }
    matrix[3][3].clearTile(); // плитку в правом нижнем углу делаем пустым полем
 matrix[3][3].number = 0;
  }
 // создание масива с рандомной последовательностью от 1 до 15
  function random() {
   var flag = true;
    
   var temp = 0;
   var count = 0;
   for (var k = 0; k < 16; k++ ) randomMas[k]=0 // обнуляем массив
     
    
   for (var j = 0; j <15; j++ ) { // в цикле создаем и записываем в массив 15 чисел
    while (flag) {    // этот цикл работает до тех пор, пока не сгенерируется число, которого еще нет в массиве
     if (count == 15) break;
     temp = Math.round(15 * Math.random() + 1);
     flag = false;
     for (var i = 0; i < 16; i++ ) {  // цикл проверяет сгенерированное число, на его наличие в массиве
      if (randomMas[i] == temp || temp==16) { flag = true; break; }
     }
    }
   //  console.log(temp);
    randomMas[j] = temp;
    count++;
    flag = true;
   }
  }
  
  
  
CanvasRenderingContext2D.prototype.fillRoundedRect = fillRoundedRect;
/* x: Координата верхнего левого угла по горизонтали
y: Координата верхнего левого угла по вертикали
w: Ширина прямоугольника
h: Высота прямоугольника
r: Радиус закруглений
*/
function fillRoundedRect(x, y, w, h, r){
this.beginPath();
this.moveTo(x+r, y);
this.lineTo(x+w-r, y);
this.quadraticCurveTo(x+w, y, x+w, y+r);
this.lineTo(x+w, y+h-r);
this.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
this.lineTo(x+r, y+h);
this.quadraticCurveTo(x, y+h, x, y+h-r);
this.lineTo(x, y+r);
this.quadraticCurveTo(x, y, x+r, y);
this.fill();
}

Вот, что в итоге у нас получилось:



В этом примере я не заморачивался со шрифтами и использовал Impact, но если у вас нет в системе этого шрифта, то буквы и цифры могут немного поплыть.

Комментариев нет:

Отправить комментарий