<canvas class="game-area__ctx" width="500px" height="600px" style="border: 1px solid black;"></canvas>
const canvas = document.querySelector(".game-area__ctx");
const ctx = canvas.getContext("2d");

const gridCheckbox = document.querySelector("input[id=toggleGrid]");
let activeElement;
let gameObj;
let walls;

localStorage.setItem(
  "level-1",
  `{"walls":[{"x":82,"y":70,"w":1,"h":20,"id":1,"hide":0,"active":0,"bern":[0,0,1,0],"thru":[0,0,0,0]},{"x":10,"y":31,"w":31,"h":1,"id":2,"hide":0,"active":0,"bern":[0,1,0,1],"thru":[0,0,0,0]},{"x":61,"y":103,"w":1,"h":10,"id":3,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[1,1,1,0]},{"x":9,"y":8,"w":1,"h":24,"id":4,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":35,"y":47,"w":1,"h":21,"id":5,"hide":0,"active":0,"bern":[0,1,1,0],"thru":[0,1,1,0]},{"x":9,"y":39,"w":1,"h":28,"id":6,"hide":0,"active":0,"bern":[1,0,0,1],"thru":[0,1,1,0]},{"x":10,"y":39,"w":15,"h":1,"id":7,"hide":0,"active":1,"bern":[0,0,1,1],"thru":[1,0,1,0]},{"x":40,"y":32,"w":1,"h":15,"id":8,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":9,"y":67,"w":20,"h":1,"id":9,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":24,"y":40,"w":1,"h":7,"id":10,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":95,"y":112,"w":5,"h":1,"id":11,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":36,"y":47,"w":5,"h":1,"id":12,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":48,"y":67,"w":1,"h":15,"id":13,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":61,"y":95,"w":12,"h":1,"id":14,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":41,"y":90,"w":1,"h":14,"id":15,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":60,"y":82,"w":1,"h":14,"id":16,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":9,"y":112,"w":77,"h":1,"id":17,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":36,"y":67,"w":12,"h":1,"id":18,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":0,"y":104,"w":52,"h":1,"id":19,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":85,"y":103,"w":1,"h":9,"id":20,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":49,"y":81,"w":12,"h":1,"id":21,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":29,"y":80,"w":1,"h":10,"id":22,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":29,"y":90,"w":12,"h":1,"id":23,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":0,"y":79,"w":30,"h":1,"id":24,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":29,"y":47,"w":1,"h":21,"id":25,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":74,"y":90,"w":9,"h":1,"id":26,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":24,"y":47,"w":5,"h":1,"id":27,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":90,"y":33,"w":1,"h":70,"id":28,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":73,"y":90,"w":1,"h":15,"id":29,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]},{"x":85,"y":102,"w":5,"h":1,"id":30,"hide":0,"active":0,"bern":[0,0,0,0],"thru":[0,0,0,0]}]}`
);
const selectedLevel = JSON.parse(localStorage.getItem("level-1"));

const gridSettings = {
  squareWidth: 100,
  squareHeight: 120
};

const ball = {
  w: 25,
  h: 25
};

const w = canvas.width;
const h = canvas.height;

const getXPixelRatio = canvas.width / gridSettings.squareWidth;
const getYPixelRatio = canvas.height / gridSettings.squareHeight;

const xCoordsToPixels = (coords) => coords * getXPixelRatio;
const yCoordsToPixels = (coords) => coords * getYPixelRatio;

const renderGrid = () => {
  ctx.beginPath();
  ctx.strokeStyle = "#bdbdbd";
  ctx.lineWidth = 0.3;
  for (let x = getXPixelRatio; x < w; x += getXPixelRatio)
    ctx.strokeRect(x, 0, 0.1, h);
  for (let y = getYPixelRatio; y < h; y += getYPixelRatio)
    ctx.strokeRect(0, y, w, 0.1);
  ctx.fill();
  ctx.closePath();
};

const loadLevels = () => {
  gameObj = JSON.parse(localStorage.getItem(`level-1`));
  if (!gameObj) return;
  walls = gameObj.walls;
};

const drawElement = (element) => {
  let fillColor = "#2c8fdb";

  ctx.beginPath();
  ctx.rect(
    xCoordsToPixels(element.x),
    yCoordsToPixels(element.y),
    xCoordsToPixels(element.w),
    yCoordsToPixels(element.h)
  );

  ctx.fillStyle = element.color || "black";

  ctx.fill();
  ctx.closePath();
};

const drawRect = (x = 0, y = 0) => {
  ctx.fillStyle = "black";
  ctx.fillRect(x, y, ball.w, ball.h);
};

function edge(c, min, max) {
  return Math.max(min, Math.min(max, c));
}

// https://stackoverflow.com/questions/4977491/determining-if-two-line-segments-intersect/4977569#4977569
// returns true iff the line from (a,b)->(c,d) intersects with (p,q)->(r,s)
function lineLineIntersects(a, b, c, d, p, q, r, s) {
  var det, gamma, lambda;
  det = (c - a) * (s - q) - (r - p) * (d - b);
  if (det === 0) {
    return false;
  } else {
    lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
    gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
    return 0 < lambda && lambda < 1 && 0 < gamma && gamma < 1;
  }
}

function intersectBallWall(targetPos, currPos, ball, wall) {
  const isBallCantMove = lineLineIntersects(
    targetPos.x, targetPos.y,
    currPos.x, currPos.y,
    getXPixelRatio * wall.x, getXPixelRatio * wall.y,
    getYPixelRatio * (wall.x + wall.w), getYPixelRatio * (wall.y + wall.h)
  );
  
  const isTouchWall = !(
    getXPixelRatio * wall.x >= targetPos.x + ball.w ||
    getXPixelRatio * (wall.x + wall.w) <= targetPos.x ||
    getYPixelRatio * wall.y >= targetPos.y + ball.h ||
    getYPixelRatio * (wall.y + wall.h) <= targetPos.y
  );
  
  return isTouchWall || isBallCantMove;
}

const rectPos = { x: 0, y: 0 };
const currPos = { x: 0, y: 0 };

const mouseEvent = () => {
  canvas.onmousedown = (e) => {
    const start = { x: e.offsetX, y: e.offsetY };
    canvas.onmousemove = (e) => {
      const targetPos = {
        x: getXPixelRatio * Math.round((rectPos.x + e.offsetX - start.x) / getXPixelRatio),
        y: getYPixelRatio * Math.round((rectPos.y + e.offsetY - start.y) / getYPixelRatio)
      }


      if (walls.some((wall) => intersectBallWall(targetPos, currPos, ball, wall))) {
        return;
      }

      currPos.x = edge(targetPos.x, 0, w - 25);
      currPos.y = edge(targetPos.y, 0, h - 25);
      renderAll();
      drawRect(currPos.x, currPos.y);
    };
    document.onmouseup = (e) => {
      rectPos.x = currPos.x;
      rectPos.y = currPos.y;
      canvas.onmousemove = null;
    };
  };
};

const renderWalls = () => {
  walls.forEach((e) => drawElement(e));
};

const renderAll = () => {
  ctx.clearRect(0, 0, w, h);

  mouseEvent();

  renderGrid();
  renderWalls();
};

window.addEventListener("DOMContentLoaded", () => {
  loadLevels();
  renderAll();
  drawRect();
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.