canvas#Interface
View Compiled
body {
   margin: 0;
   padding: 0;
   background-color: #000;
}
View Compiled
/**
* Use the Arrow Keys Left and Right to Rotate Token, 
* Use the Up Arrow to Move the Token in the Selected Direction
*/

const canvas = document.getElementById("Interface");

const isEven = (value) => (value % 2 === 0);
const getRandomInt = (min, max) => {
  min = Math.ceil(min);
  max = Math.floor(max);
  return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
}

canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

const ctx = canvas.getContext("2d");

class Cell {
   constructor(x, y, w, h) {
      this.x = x;
      this.y = y;
      this.width = w;
      this.height = h;
      this.magicYValue = 3.5
      this.isOccupied = false;
   } // close constructor
   
   getXOffset() {
      let xOffset = 0;
      if (isEven(this.y)) {
         xOffset = this.width / 2;
      }
      return (this.x * this.width) + xOffset;
   } // close getXOffset
   
   getYOffset() {
      let yOffset = this.y * ((this.height / this.magicYValue) * -1);
      
      return (this.y * this.height) + yOffset;
   } // close getYOffset
   
   getMyCenterPoint() {
      return {
         x: (this.getXOffset() + (this.width / 2)), 
         y: (this.getYOffset() + (this.height / 2))
      };
   } // close getMyCenterPoint
   
   draw() {
      const xPoint = this.width / 2;
      const yOffsetValue = this.height / this.magicYValue;
      const yPointTop = yOffsetValue;
      const yPointBottom = this.height - yOffsetValue;
      const thisXOffset = this.getXOffset();
      const thisYOffset = this.getYOffset();
      
      ctx.beginPath();
      ctx.strokeStyle = "#333";
      if (this.isOccupied) {
         ctx.fillStyle = "#000";
      } else {
         ctx.fillStyle = "#111";
      }
      ctx.lineWidth = 4;
      ctx.translate(thisXOffset, thisYOffset);
      ctx.moveTo(xPoint, 0);
      ctx.lineTo(this.width, yPointTop);
      ctx.lineTo(this.width, yPointBottom);
      ctx.lineTo(xPoint, this.height);
      ctx.lineTo(0, yPointBottom);
      ctx.lineTo(0, yPointTop);
      ctx.lineTo(xPoint, 0);
      ctx.stroke();
      ctx.fill();
      ctx.closePath();
      ctx.setTransform(1, 0, 0, 1, 0, 0);
   } // close draw
} // close class Cell

class ColliableObject {
   constructor(name, color = "red", startX = 4, startY = 4, theMap) {
      this.name = name;
      this.color = color;
      this.map = theMap;
      this.width = 30;
      this.height = 40;
      this.posX = startX;
      this.posY = startY;
      this.isStuck = false;
      this.hasHitEdge = false;
      this.shouldDestroy = false;
      this.shouldOccupySpace = true;
      this.isHit = false;
     
      this.map.occupyCell(this.posX, this.posY);
      
      this.directionIndex = 0; 
      this.directionMap = [
         {
            label: "W",
            value: -90
         },
         {
            label: "SW",
            value: -145
         },
         {
            label: "SE",
            value: 145
         },
         {
            label: "E",
            value: 90
         },
                  {
            label: "NE",
            value: 35
         },
         {
            label: "NW",
            value: -35
         }
      ];
   } // close constructor
   
   setDirection(value) {
      let newValue = new Number(this.directionIndex);
      
      if (value < 0) {
         newValue = newValue - 1;
      } else if (value > 0) {
         newValue = newValue + 1;
      }
      
      if (newValue < 0) {
         newValue = this.directionMap.length - 1;
      } else if (newValue >= this.directionMap.length) {
         newValue = 0;   
      }
      
      this.directionIndex = newValue;
   };
   
   move() {
      const playMap = this.map.playMap;
      const currentDir = this.directionMap[this.directionIndex].label;
       
      let newX = new Number(this.posX);
      let newY = new Number(this.posY);

      if (currentDir.match(/N/gi)) {
         newY = newY - 1;
      } else if (currentDir.match(/S/gi)) {
         newY = newY + 1;
      }

      if (currentDir.match(/E/gi)) {
         if (currentDir.match(/[NS]/gi)) {
            if (!isEven(newY)) {
               if (
                  (newY > 0) &&
                  (newY < playMap.length)
               ) {
                  newX = newX + 1;
               }
            } else {
               if (newX >= playMap[newY].length) {
                  if (currentDir.match(/N/gi)) {
                      newY = newY + 1;
                  } else {
                      newY = newY - 1;
                  }
               }
            }
         } else {
            newX = newX + 1;
         }
      } else if (currentDir.match(/W/gi)) {
         if (currentDir.match(/[NS]/gi)) {
            if (isEven(newY)) {
               newX = newX - 1;
               if (newX < 0) {
                  if (currentDir.match(/N/gi)) {
                      newY = newY + 1;
                  } else {
                      newY = newY - 1;
                  }
               }
            }
         } else {
            newX = newX - 1;
         }
      }
      
      if (newY < 0) {
         newY = 0;
         this.hasHitEdge = true;
      } else if (newY >= playMap.length) {
         newY = playMap.length - 1;
         this.hasHitEdge = true;
      }
      if (newX < 0) {
         newX = 0;
         this.hasHitEdge = true;
      } else if (newX >= playMap[newY].length) {
         newX = playMap[newY].length - 1;
         this.hasHitEdge = true;
      }
      
      if (!this.map.isCellOccupied(newX, newY)) {
         if (this.shouldOccupySpace) {
            this.map.clearCell(this.posX, this.posY);
            this.map.occupyCell(newX, newY);
         }

         this.posX = newX;
         this.posY = newY;
      } else {
         this.hasHitEdge = true;
         this.isStuck = true;
      }
   } // close move

   draw() {
      const x = this.map.playMap[this.posY][this.posX].getMyCenterPoint().x;
      const y = this.map.playMap[this.posY][this.posX].getMyCenterPoint().y;
      
      const thisPath = new Path2D();
      thisPath.arc(x, y, 20, 0, 2 * Math.PI);
      
      ctx.fillStyle = this.color;
      ctx.fill(thisPath);
   } // close draw
} // close class ColliableObject

class Rock extends ColliableObject {
   constructor(name, color = "Grey", startX = 4, startY = 4, theMap) {
      super(name, color, startX, startY, theMap);
   } // close constructor
} // close class Rock

class Bullet extends ColliableObject {
   constructor(startX = 4, startY = 4, directionIndex, theMap) {
      super("bullet", "Red", startX, startY, theMap);
      
      this.directionIndex = directionIndex;
      this.shouldOccupySpace = false;
      
      this.interval = setInterval(() => {
         this.move();
         if (this.hasHitEdge) {
            this.shouldDestroy = true;
            clearInterval(this.interval);
         } else {
            DrawAssets.forEach(asset => {
               if (
                  (asset.posX === this.posX) &&
                  (asset.posY === this.posY)
               ) {
                   asset.isHit = true;
               }
            })
         }
      }, 150)
   } // close constructor 
   
   draw() {
      const x = this.map.playMap[this.posY][this.posX].getMyCenterPoint().x;
      const y = this.map.playMap[this.posY][this.posX].getMyCenterPoint().y;
      
      const thisPath = new Path2D();
      thisPath.arc(x, y, 5, 0, 2 * Math.PI);
      
      ctx.fillStyle = this.color;
      ctx.fill(thisPath);
   } // close draw
} // close class Rock



class Ship extends ColliableObject {
   constructor(name, color = "red", startX = 4, startY = 4, theMap) {
      super(name, color, startX, startY, theMap);
   } // close constructor
   
   shoot() {
      const bullet = new Bullet(this.posX, this.posY, this.directionIndex, this.map);
      bullet.move();
      DrawAssets.push(bullet);
      this.map.occupyCell(this.posX, this.posY);
   } // close bullet
   
   draw() {
      const x = this.map.playMap[this.posY][this.posX].getMyCenterPoint().x;
      const y = this.map.playMap[this.posY][this.posX].getMyCenterPoint().y;
      const yOffset = (this.height / 2);
      const xOffset = (this.width / 2);
      const xOffset2 = (this.width / 8);
      const degree = this.directionMap[this.directionIndex].value;
      
      ctx.beginPath();
      ctx.fillStyle = this.color;
      if (this.isHit) {
         this.fillStyle = "Red";
      }
      ctx.translate(x, y);
      ctx.rotate(degree * Math.PI / 180);
      ctx.moveTo((xOffset2 * -1), (yOffset * -1));
      ctx.lineTo(xOffset2, (yOffset * -1));
      ctx.lineTo(xOffset, yOffset);
      ctx.lineTo((xOffset * -1), yOffset);
      ctx.closePath();
      ctx.fill();
      ctx.setTransform(1, 0, 0, 1, 0, 0);
   } // close draw
} // close class Ship 

class Map {
   constructor(numCols, numRows) {
      this.playMap = [];
      this.numCols = numCols;
      this.numRows = numRows;
      
      this.occupationMap = [];
      
      this.populateMap();
   } // close constructor
   
   occupyCell(x, y) {
      this.playMap[y][x].isOccupied = true;
      this.occupationMap.push({ x: x, y: y });
   }
   clearCell(x, y) {
      this.playMap[y][x].isOccupied = false;
      this.occupationMap = this.occupationMap.filter((value) => {
         return !(
            (value.x === x) &&
            (value.y === y)
         );
      });
   }
   isCellOccupied(x, y) {
      return this.playMap[y][x].isOccupied;
   }
   
   populateMap() {
      const cellWidth = canvas.width / (this.numCols + 1);
      const cellHeight = (canvas.height / this.numRows) * 1.34;
      this.playMap = [];
      for (let y = 0; y < this.numRows; y++) {
         let thisRow = [];
         for(let x = 0; x < this.numCols; x++) {
            thisRow.push(new Cell(x, y, cellWidth, cellHeight));
         }
         if (!isEven(y)) {
            thisRow.push(new Cell(this.numCols, y, cellWidth, cellHeight));
         }
         this.playMap.push(thisRow);
      }
      
      this.occupationMap.forEach(record => {
         this.playMap[record.y][record.x].isOccupied = true;
      });
   } // close populateMap
   
   draw() {
      for (let y = 0; y < this.playMap.length; y++) {
         for(let x = 0; x < this.playMap[y].length; x++) {
            const cell = this.playMap[y][x];
            cell.draw();
         }
      }
   } // close draw
} // close class Map

const gridX = 9;
const gridY = 9;
const theMap = new Map(gridX, gridY);
const myShip = new Ship("Intrepid", "green", 4, 4, theMap);
const targetShip = new Ship("Sential", "blue", 2,7, theMap);
let isAnimating = false;
let isSentinalRunning = false;
let DrawAssets = [
   myShip,
   targetShip
];

function animation() {
   isAnimating = true;
   requestAnimationFrame(animation);
   
   ctx.clearRect(0, 0, canvas.width, canvas.height);
   ctx.fillStyle = "#090000";
   ctx.fillRect(0, 0, canvas.width, canvas.height);
   theMap.draw();
   DrawAssets = DrawAssets.filter(asset => !asset.shouldDestroy);
   DrawAssets.forEach(asset => {
      asset.draw();
   });
} // close animation()

const sentinalRun = () => {
   isSentinalRunning = true;
   setTimeout(function() {
      const rndValue = getRandomInt(-1, 1);
      if (rndValue === 0) {
         targetShip.move(0);
      } else {
         targetShip.setDirection(rndValue);
      }
      sentinalRun();
   }, 500);
} // close sentinalRun

const init = (numRocks = 4) => {
   DrawAssets = [
      myShip,
      targetShip
   ];
   
   let RockCount = 0;
   while (RockCount < numRocks) {
      const randomX = getRandomInt(0, gridX - 1);
      const randomY = getRandomInt(0, gridY - 1);

      if (!theMap.isCellOccupied(randomX, randomY)) {
         DrawAssets.push(new Rock(`Rock ${RockCount}`,"gray",randomX, randomY, theMap));
         RockCount++;
      }
   }
   
   if (!isAnimating) {
      animation();
   }
   if (!isSentinalRunning) {
      sentinalRun();
   }
} // close init


document.addEventListener("keydown", (event) => {
   const key = event.key;
   
   // console.log(key);
   
   if (key === "ArrowLeft") {
      myShip.setDirection(1);
   } else if (key === "ArrowRight") {
      myShip.setDirection(-1);
   } else if (key === "ArrowUp") {
      myShip.move();
   } else if (key === " ") {
      myShip.shoot();
   }
}); // close addEventListener(keydown)

window.addEventListener("resize", () => {
   canvas.height = window.innerHeight;
   canvas.width = window.innerWidth;
   theMap.populateMap();
}); // close addEventListener(resize)

init(4);
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.