class KnightTour {
 constructor() {
  this.width = 856;
  this.height = 856;
  this.cellCount = 8;
  this.size = 0;
  this.knightPiece = "\u2658";
  this.knightPos = {
   x: 0,
   y: 0
  };
  this.ctx = null;
  this.step = this.width / this.cellCount;
  this.lastTime = 0;
  this.wait;
  this.delay;
  this.success;
  this.jumps;
  this.directions = [];
  this.visited = [];
  this.path = [];
  document.getElementById("start").addEventListener("click", () => {
   this.startHtml();
  });
  this.init();
  this.drawBoard();
 }

 drawBoard() {
  let a = false, xx, yy;
  for (let y = 0; y < this.cellCount; y++) {
   for (let x = 0; x < this.cellCount; x++) {
    if (a) {
     this.ctx.fillStyle = "#607db8";
    } else {
     this.ctx.fillStyle = "#aecaf0";
    }
    a = !a;
    xx = x * this.step;
    yy = y * this.step;
    this.ctx.fillRect(xx, yy, xx + this.step, yy + this.step);
   }
   if (!(this.cellCount & 1)) a = !a;
  }
  if (this.path.length) {
   const s = this.step >> 1;
   this.ctx.lineWidth = 3;
   this.ctx.fillStyle = "black";
   this.ctx.beginPath();
   this.ctx.moveTo(this.step * this.knightPos.x + s, this.step * this.knightPos.y + s);
   let a, b, v = this.path.length - 1;
   for (; v > -1; v--) {
    a = this.path[v].pos.x * this.step + s;
    b = this.path[v].pos.y * this.step + s;
    this.ctx.lineTo(a, b);
    this.ctx.fillRect(a - 5, b - 5, 10, 10);
   }
   this.ctx.stroke();
  }
 }

 createMoves(pos) {
  const possibles = [];
  let x = 0,
   y = 0,
   m = 0,
   l = this.directions.length;
  for (; m < l; m++) {
   x = pos.x + this.directions[m].x;
   y = pos.y + this.directions[m].y;
   if (x > -1 && x < this.cellCount && y > -1 && y < this.cellCount && !this.visited[x + y * this.cellCount]) {
    possibles.push({
     x,
     y
    })
   }
  }
  return possibles;
 }

 warnsdorff(pos) {
  const possibles = this.createMoves(pos);
  if (possibles.length < 1) return [];
  const moves = [];
  for (let p = 0, l = possibles.length; p < l; p++) {
   let ps = this.createMoves(possibles[p]);
   moves.push({
    len: ps.length,
    pos: possibles[p]
   });
  }
  moves.sort((a, b) => {
   return b.len - a.len;
  });
  return moves;
 }

 startHtml() {
  this.cellCount = parseInt(document.getElementById("cellCount").value);
  this.size = Math.floor(this.width / this.cellCount)
  this.wait = this.delay = parseInt(document.getElementById("delay").value);
  this.step = this.width / this.cellCount;
  this.ctx.font = this.size + "px Arial";
  document.getElementById("log").innerText = "";
  document.getElementById("path").innerText = "";
  this.path = [];
  this.jumps = 1;
  this.success = true;
  this.visited = [];
  const cnt = this.cellCount * this.cellCount;
  for (let a = 0; a < cnt; a++) {
   this.visited.push(false);
  }
  const kx = parseInt(document.getElementById("knightx").value),
   ky = parseInt(document.getElementById("knighty").value);
  this.knightPos = {
   x: (kx > this.cellCount || kx < 0) ? Math.floor(Math.random() * this.cellCount) : kx,
   y: (ky > this.cellCount || ky < 0) ? Math.floor(Math.random() * this.cellCount) : ky
  };
  this.mainLoop = (time = 0) => {
   const dif = time - this.lastTime;
   this.lastTime = time;
   this.wait -= dif;
   if (this.wait > 0) {
    requestAnimationFrame(this.mainLoop);
    return;
   }
   this.wait = this.delay;
   let moves;
   if (this.success) {
    moves = this.warnsdorff(this.knightPos);
   } else {
    if (this.path.length > 0) {
     const path = this.path[this.path.length - 1];
     moves = path.m;
     if (moves.length < 1) this.path.pop();
     this.knightPos = path.pos
     this.visited[this.knightPos.x + this.knightPos.y * this.cellCount] = false;
     this.jumps--;
     this.wait = this.delay;
    } else {
     document.getElementById("log").innerText = "Can't find a solution!";
     return;
    }
   }
   this.drawBoard();
   const ft = this.step - (this.step >> 3);
   this.ctx.fillStyle = "#000";
   this.ctx.fillText(this.knightPiece, this.knightPos.x * this.step, this.knightPos.y * this.step + ft);
   if (moves.length < 1) {
    if (this.jumps === this.cellCount * this.cellCount) {
     document.getElementById("log").innerText = "Tour finished!";
     let str = "";
     for (let z of this.path) {
      str += `${1 + z.pos.x + z.pos.y * this.cellCount}, `;
     }
     str += `${1 + this.knightPos.x + this.knightPos.y * this.cellCount}`;
     document.getElementById("path").innerText = str;
     return;
    } else {
     this.success = false;
    }
   } else {
    this.visited[this.knightPos.x + this.knightPos.y * this.cellCount] = true;
    const move = moves.pop();
    this.path.push({
     pos: this.knightPos,
     m: moves
    });
    this.knightPos = move.pos
    this.success = true;
    this.jumps++;
   }
   requestAnimationFrame(this.mainLoop);
  };
  this.mainLoop();
 }

 init() {
  const canvas = document.createElement("canvas");
  canvas.id = "cv";
  canvas.width = this.width;
  canvas.height = this.height;
  this.ctx = canvas.getContext("2d");
  document.getElementById("out").appendChild(canvas);
  this.directions = [{
    x: -1,
    y: -2
   }, {
    x: -2,
    y: -1
   }, {
    x: 1,
    y: -2
   }, {
    x: 2,
    y: -1
   },
   {
    x: -1,
    y: 2
   }, {
    x: -2,
    y: 1
   }, {
    x: 1,
    y: 2
   }, {
    x: 2,
    y: 1
   }
  ];
 }
}
new KnightTour();