<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="./style.css">
    <title>Document</title>
</head>
<body>

        <canvas id="canvas"></canvas>
        <script src="./script.js/astar.js"></script>
</body>
</html>
#canvas {
    display: block;
    margin: 0 auto;
}
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 1024;//32*96;
canvas.height = 600;//32*60;
let tileSize = 32;
let imageNumTiles = 2;
let x_offs = 0;// координаты смещения карты
let y_offs = 0;


const objectShip  = new Image();
const backgraund  = new Image();
objectShip.src = 'https://i.postimg.cc/VLz6kQt2/object-Ship.png';
backgraund.src = 'https://i.postimg.cc/bJLQsNsF/backgraund.png';
// инициализация массива с непроходимыми клетками заливаем все проходимым и прописываем только непроходимые
const points = {
  0:  [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,43,56,75,88],  
  1:  [24,43,56,75,88], 2: [11,24,43,56,75,88],
  3:  [11,24,43,56,75,88], 4:  [11,24,43,56,75,88],  5: [11,24,43,56,75,88],
  6:  [11,24,43,56,75,88], 7:  [11,24,43,56,75,88],  8: [11,24,43,56,75,88],
  9:  [11,24,43,56,75,88], 10: [11,24,43,56,75,88], 11: [11,24,43,56,75,88],
  12: [11,24,43,56,75,88], 13: [11,24,43,56,75,88], 14: [11,24,43,56,75,88],
  15: [11,24,43,56,75,88], 16: [11,24,43,56,75,88], 17: [11,24,43,56,75,88],
  18: [11,24,43,56,75,88], 19: [11,24,43,56,75,88], 20: [11,24,43,56,75,88],
  21: [11,24,43,56,75,88], 22: [11,24,43,56,75,88], 23: [11,24,43,56,75,88],
  24: [11,24,43,56,75,88], 25: [11,24,43,56,75,88], 26: [11,24,43,56,75,88],
  27: [11,24,43,56,75,88], 28: [11,24,43,56,75,88], 29: [11,24,43,56,75,88],
  30: [11,24,43,56,75,88], 31: [11,24,43,56,75,88], 32: [11,24,43,56,75,88],
  33: [11,24,43,56,75,88], 34: [11,24,43,56,75,88], 36: [11,24,43,56,75,88],
  37: [11,24,43,56,75,88], 38: [11,24,43,56,75,88], 39: [11,24,43,56,75,88],
  40: [11,24,43,56,75,88], 42: [11,24,43,56,75,88], 43: [11,24,43,56,75,88],
  44: [11,24,43,56,75,88], 45: [11,24,43,56,75,88], 46: [11,24,43,56,75,88],
  47: [11,24,43,56,75,88], 48: [11,24,43,56,75,88], 49: [11,24,43,56,75,88],
  50: [11,24,43,56,75,88], 51: [11,24,43,56,75,88], 52: [11,24,43,56,75,88],
  53: [11,24,43,56,75,88], 54: [11,24,43,56,75,88], 55: [11,24,43,56,75,88],
  56: [11,24,43,56,75,88], 57: [11,24,43,56,75,88], 58: [11,24,43,56,75,88],
  59: [11,24,43,56,75,88]
};
const a = Array(60)
.fill()
.map((_, y) => {
    const row = Array(96).fill(0);
    
    if (points[y] !== undefined) {
    points[y].forEach(p => row[p] = 1);
    }
    
    return row;
});
// стартовое и финишное положение игрока
let sx=16,sy=10,ex=16,ey=10;
// =============================================================================================
let way = []; //массив просчитанного пути
let wx = 0,wy = 0;
let oex = -1,oey = -1,clicknum = 1;
// клетки на карте с шагом в 32 пикселя
const step = {
    grid(n) {
  return n / 32
    }
};
// проверка на старт с непроходимым обьектом под кораблем ========================
function BadStartEnd()
{
  if (a[sy][sx]==1) return 1;
  if (a[ey][ex]==1) return 1;
  return 0;
}
// вычисления дистанции пути с учетом диагонали или горизонталь/вертикаль ==============================
function Dist(x1,x2,y1,y2)
{
  let dx = Math.abs(x1-x2);
  let dy = Math.abs(y1-y2);
  if (dx>dy)
  {
    return dy*14+(dx-dy)*10;
  }
  else
  {
    return dx*14+(dy-dx)*10;
  }
}
function Calc() {
  if (BadStartEnd()===1) {
    return;
  }
  for (let i=0;i<60;i++)
  {
    for (let j=0;j<96;j++)
    {   
      if (a[i][j]>1) {a[i][j]=0;}
    }
  }
  a[sy][sx] = Dist(sx,ex,sy,ey)*100+10;
  var b = true,i = 0;
  while ((b)&&(i<300))
  {
    b = AlgStep();
  }
  RecalcA();
  b = true,i = 0;
  while ((b)&&(i<300))
  {
    b = AlgStepWay();
  }   
  if (clicknum == 1)  
  {
    if ((oex==ex)&&(oey==ey))
      clicknum = 2;
  }   
      if (clicknum === 2)
  {
    //sx = ex;
    //sy = ey;
  }
  oex = ex;
  oey = ey;
} 
function AlgStep()
{
  minv=3072, xminv=-1, yminv = -1;

  for (let i=0;i<60;i++)
  {
    for (let j=0;j<96;j++)
    {
      if ( (a[i][j] % 10 == 0) && (Math.floor(a[i][j]/10) % 10 == 1) )
      {//поиск минимального значения среди открытых вершин не препятствий
        let temp = Math.floor(a[i][j]/100);
        if (temp<minv)
        {
          yminv = i;
          xminv = j;
          minv = temp;
        }
      }
    }
  }
  if (minv === 3072)
  {
    return false;
  }
  let af0=Math.floor(a[yminv][xminv]/100);
  let ah0 = Dist(xminv,ex,yminv,ey);
  let ag0 = af0 - ah0;
  //2. open/close для этой точки ищем соседей не препятствие у которых пересчитаваем массив
  for (let i=-1;i<=1;i++)
  {
    for (let j=-1;j<=1;j++)
    {
      if ((xminv+j>=0)&&(xminv+j<96)&&(yminv+i>=0)&&(yminv+i<60)&&(i*i+j*j>0))
      {
        let temp = a[yminv+i][xminv+j];
        if ( (Math.floor(temp/10) % 10 < 2) && (temp % 10 == 0) )
        {
          let incr = 10;
          if (i*i+j*j==2)
          {
            incr = 14;
          }
          let agtmp = ag0 + incr;
          
          let aftmp = agtmp + Dist(xminv+j,ex,yminv+i,ey);
          if ( (temp==0)||(temp>aftmp*100) )
          {
            a[yminv+i][xminv+j] = aftmp*100+10+0;
          }
        }
      }
    }
  }
  a[yminv][xminv]+=10;
  if ((yminv==ey)&&(xminv==ex))
  {
    return false;
  }
  return true;
}
function RecalcA()
{
  for (let i=0;i<60;i++)
  {
    for (let j=0;j<96;j++)
    {
      if (a[i][j]==1)
      {
        a[i][j]=1;
      }
      else if (Math.floor(a[i][j]/10)%10<2)
      {
        a[i][j]=0;
      }
      else
      {
        a[i][j]=a[i][j]-100*Dist(ex,j,ey,i)+10000;
      }
    }
  }
//Путешествуем с финиша, находим клетку где минимальное значение но с учетом равенства по соседям
  way = []; //обнуление массива
  let tmp = {};
  wx=ex;
  wy=ey;
  tmp.x = wx;
  tmp.y = wy;
  way.push(tmp);
}
function AlgStepWay()
{
  let d = 0,itmp = 0, jtmp = 0, dtmp = 0;
  for (let i=-1;i<=1;i++) for (let j=-1;j<=1;j++) if (i*i+j*j>0)
  {
    if (i*i+j*j==1) d=10;
    if (i*i+j*j==2) d=14;
    if ((a[wy+i][wx+j]>100)&&(a[wy+i][wx+j]+d*100==a[wy][wx])&&(dtmp<d))
    {
      itmp = i;
      jtmp = j;
      dtmp = d;
    }
  }
  wx+=jtmp;
  wy+=itmp;
  
  let tmp = {};
  tmp.x = wx;
  tmp.y = wy;
  way.push(tmp);  
  if ((wx==sx)&&(wy==sy))
    return false;
  else
    return true;
}
canvas.onmousedown = function(e) {
    let rect = this.getBoundingClientRect();
    x = e.clientX - rect.left ;
    y = e.clientY - rect.top ;
  if (clicknum===1)
  {
    ex = Math.floor(step.grid(x))+x_offs;
    ey = Math.floor(step.grid(y))+y_offs;
    Calc();
  }
  
};
// сдвиг карты кнопками
document.body.onkeydown = function(e) {
  if (e.key=='ArrowLeft') x_offs-=3;
  if (e.code=='ArrowRight') x_offs+=3;
  if (e.code=='ArrowUp') y_offs-=3;
  if (e.code=='ArrowDown') y_offs+=3;
  if (x_offs<0) x_offs = 0;
  if (y_offs<0) y_offs = 0;
  if (x_offs>80) x_offs = 80;
  if (y_offs>50) y_offs = 50;
};

// отрисовка объектов на канвасе ======================================================================================
function render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.strokeStyle = "rgb(255, 255, 0)";
    //1024/32=32, 640/32=20 
    for (let i = 0; i < a.length; i++) { 
        for (let j = 0; j < a[i].length; j++) { 
                let tile = a[i][j]%10;
            let tileRow = (tile / imageNumTiles) | 0; // Операция "побитовое ИЛИ" 
              let tileCol = (tile % imageNumTiles) | 0;
            ctx.drawImage(backgraund, tileCol * tileSize, tileRow * tileSize, tileSize, tileSize, j * tileSize  - x_offs*32  , i * tileSize - y_offs*32  , tileSize, tileSize);
        }
      }
    // если путь просчитан, то обрисовать желтым===================================================================
    if ((way.length>0)&&(ex===oex)&&(ey===oey))
    {
      for (let i=0;i<way.length;i++) {
        ctx.beginPath();
        ctx.arc((way[i].x-x_offs)*32+16,(way[i].y-y_offs)*32+16,7,0,Math.PI*2)
        ctx.stroke();
        ctx.fillStyle = "rgba(255, 255, 255, 0.3)";
        ctx.arc((way[i].x-x_offs)*32+16,(way[i].y-y_offs)*32+16,5,0,Math.PI*2)
        ctx.fill();
      }
    }
  //if (clicknum === 1) {
    ctx.drawImage(objectShip, (sx-x_offs)*32, (sy-y_offs)*32, 32, 32);
//}
  //else {ctx.drawImage(objectShip, (delWay.x-x_offs)*32, (delWay.y-y_offs)*32, 32, 32);}
};
// обновление объектов на канвасе ======================================================================================

//Получить угол между двумя точками
function getAngle(dx, dy, dx1, dy1) {
  return Math.atan2(dy - dy1, dx - dx1) + Math.PI;
}
const update = function() {
  if (way.length == 0) {
    clicknum = 1;
  }
  if(clicknum == 2 && way.length > 0){    
    let stepLen = 0.1;//ширина шага за итеарция, чем меньше тем дольше будет идти
    let stepCur = stepLen;
    while(way.length > 0 && stepCur > 0){
      let nx = way[way.length-1].x;//следующая точка куда идти
      let ny = way[way.length-1].y;
      let dist = Math.hypot(sx-nx,sy-ny);//Получить расстояние между игроком и следующей точкой
      if(dist <= stepCur){
        stepCur -=dist;
        way.pop()
        sx = nx;
        sy = ny;
      }
      else{
        let angle = getAngle(sx,sy,nx,ny);
        sx+= stepCur * Math.cos(angle);
        sy+= stepCur * Math.sin(angle);
        break;
      }
    }
    
      x_offs = Math.max(0,sx-16);
      y_offs = Math.max(0,sy-10);
      if (x_offs>80) x_offs = 80;
      if (y_offs>50) y_offs = 50;
  }
};

        // функция игрового цикла, замкнутая петля в канвас ============================================================
    let game = function() {
      render();
      update();
      requestAnimationFrame(game);
      };
      game();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.