Zap Orderer

The HTML file:

<html>
<head>
<script src="canvas.js"></script>
<script src="mover.js"></script>
<script src="game.js"></script>
<script src="ui.js" defer></script>
</head>
<body>

<h1> Zap Orderer </h1>
<div id="canvasHolder"></div>
<input type="button" id="verboseChoice" value="Switch To Verbose">
</body>
</html>

canvas.js

"use strict"

class Canvas {
    #context
    #width
    #canvas
    height
    constructor(place, w, h) {

        this.#width = w
        this.height = h
       
        this.#canvas = document.createElement("canvas")
        this.#canvas.width = w
        this.#canvas.height  = h
        this.#canvas.style = "border:1px solid black;"

        // without this line, the canvas will not receive keypress events
        this.#canvas.tabIndex = 0 

        let location = document.getElementById(place)
        location.appendChild(this.#canvas)

        this.#context = this.#canvas.getContext("2d")

    }

    SetPixel(x,y,r,g,b) {
        this.#context.strokeStyle = 'rgb(' + r + ',' + g + ',' + b + ')'
        this.#context.strokeRect(x,y,1,1) 
        this.#context.fillStyle = 'rgb(' + r + ',' + g + ',' + b + ')'
        this.#context.fillRect(x,y,1,1) 
    }

    Clear() {
       this.#context.clearRect(0,0, this.#width, this.height)
    }

    get width() {
       return this.#width
    }

    AddListener(eventType, callBack) {
       this.#canvas.addEventListener(eventType, callBack)
    }
}

mover.js

"use strict"

// a base class
class Mover{
   xpos
   ypos
   canvas
   constructor (x,y, canvas) {
      this.xpos = x
      this.ypos = y
      this.canvas = canvas 
   }

   Draw()  {
      console.log("someone should draw something")
   }

   Move(){
      console.log("I really should move")
   }

   get xpos() {
      return this.xpos
   }

   get ypos(){
      return this.ypos
   }
}

const RANDOM_MOVE_AMOUNT = 10

class AimPoint extends Mover {

   Draw() {
      for(let pos = -5; pos < 6; ++pos) {
         this.canvas.SetPixel(this.xpos+pos, this.ypos, 0, 0, 0)
         this.canvas.SetPixel(this.xpos, this.ypos+pos, 0, 0, 0)
      }
   }

   set xpos(pos) {
      if (pos > 10 && pos < this.canvas.width - 10) {
        this.xpos = pos;
      }
   }

   set ypos(pos) {
      if (pos > 0 && pos < this.canvas.height - 100) {
        this.ypos = pos;
      }
   }
}

class Target extends Mover {
   phase
   constructor (x,y,canvas) {
      super(x,y,canvas)
      this.phase = 1
   }

   Move() {
      let dx = (Math.random() * RANDOM_MOVE_AMOUNT
                     - RANDOM_MOVE_AMOUNT/2)
      let dy = (Math.random() * RANDOM_MOVE_AMOUNT 
                     - RANDOM_MOVE_AMOUNT/2)

      let newx = this.xpos+dx
      let newy = this.ypos+dy

      if (newx > 20 &&  newx <= this.canvas.width-20) {
         this.xpos = newx
      }

      if (newy < this.canvas.height - 100 && newy > 100) {
         this.ypos = newy
      }
   }

   Draw()  {
      for(let x = -5; x < 6; x+= this.phase) {
         for(let y = -4; y < 7; y+= this.phase) {
            this.canvas.SetPixel(this.xpos+x, this.ypos+y, 0,255,0)
         }
      }

      this.phase = (this.phase + 1) % 4 + 1
   }
}

class Gun extends Mover {
    Draw() {
        for(let x = -8; x < 9; x++) {
           for(let y = 0; y < 6; y++) {
              this.canvas.SetPixel(this.xpos+x, this.ypos-y, 0,0,255)
           }
        }
    }

    Move(key) {
       switch(key) {
          case 'a':
          case 'j':
          case 'ArrowLeft':
             if (this.xpos > 10) {
                this.xpos--
             }
             break
          case 'd':
          case 'k':
          case 'ArrowRight':
             if (this.xpos < theCanvas.width-10) {
                this.xpos++; 
             }
          break;
       }
    }
}

ui.js

"use strict"

const theCanvas = new Canvas("canvasHolder", 700, 500)

let VERBOSE  = false
const theButton = document.getElementById("verboseChoice")
theButton.addEventListener("click", ChangeVerbose)

function ChangeVerbose() {
   if (VERBOSE == false) {
      theButton.value = "Switch To Quiet"
      VERBOSE = true;
   } else { 
      theButton.value = "Switch To Verbose"
      VERBOSE = false
   }
}

const UPDATE_TIME = 50
const TARGET_COUNT = 4

function TimeAction() {
   theCanvas.Clear()

   for  (let i = 0; i < targets.length; ++i) {
      targets[i].Move()
      targets[i].Draw()
   }
   aim.Draw()
   gun.Draw()

   cooldown = false
   timer = setTimeout(TimeAction,UPDATE_TIME)
}

function ClickGetter(event) {
   let clickX = event.offsetX
   let clickY = event.offsetY

   aim.xpos = clickX
   aim.ypos = clickY

   if(VERBOSE) {
      console.log("Click at (", clickX, ",", clickY, ")")
      console.log(event)
   }
}

function KeyGetter(event){
    if(VERBOSE) {
        console.log(event)
        console.log("Key ", event.key, " shift ", 
                        event.shiftKey, "ctl ", event.ctrlKey)
    }

    if (event.key == 'f' && !cooldown)  {
       Fire()
    } else {
       gun.Move(event.key)
    }
}

// set the canvas up to get events.
theCanvas.AddListener("click", ClickGetter);
theCanvas.AddListener("keydown", KeyGetter);

// make a gun
let gun = new Gun(theCanvas.width/2, theCanvas.height, theCanvas)
let aim = new AimPoint(theCanvas.width/2, theCanvas.height/2, theCanvas)

let targets = []
for (let i = 0; i < TARGET_COUNT; ++i) { 
    targets.push(MakeTarget())
}

let timer = setTimeout(TimeAction, UPDATE_TIME)
let cooldown = false

game.js

"use strict"

let explodeX = []
let explodeY = []
let explodeRadius
let hitTargets = []

const HIT_RADIUS = 20
const EXPLODE_SIZE = 12
const BOOM_TIME = 10 
const DELAY_TIME = 1000

function MakeTarget() {
    let x = 40 + Math.floor(Math.random() * (theCanvas.width-80))
    let y = 30 + Math.floor(Math.random() * (theCanvas.height-200))
    let target = new Target(x,y, theCanvas)
    return target
}

function Box(x,y,radius, r,g,b) {
   let edge
   for(edge = y-radius; edge <= y+radius; edge++) {
         theCanvas.SetPixel(x-radius, edge, r, g, b)
         theCanvas.SetPixel(x+radius, edge, r, g, b)
   }

   for(edge = x-radius; edge <= x+radius; edge++) {
         theCanvas.SetPixel(edge, y-radius, r, g, b)
         theCanvas.SetPixel(edge, y+radius, r, g, b)
   }
}

function Boom() {

   for(let i = 0; i < explodeX.length; ++i) {
      let x = explodeX[i]
      let y = explodeY[i]
  
      Box(x,y,explodeRadius, 255,0,0)
      Box(x,y,explodeRadius-2, 255,255,255)
   }

   explodeRadius += 1

   if (explodeRadius <= EXPLODE_SIZE) {
      timer = setTimeout(Boom, BOOM_TIME)
   } else {
      timer = setTimeout(TimeAction, 1000)

      explodeX = []
      explodeY = [] 

      for(let i = 0;  i < hitTargets.length; ++i) {
         let pos = hitTargets[i]
         targets[pos] = MakeTarget()
      }
      hitTargets = []
   }
}


function TargetExplodes(i) {
    explodeX.push(targets[i].xpos)
    explodeY.push(targets[i].ypos)
    hitTargets.push(i)
    explodeRadius = 1
}

function FindHit(i) {

   let target = targets[i]
   let hit = false

   let d
   let x,y

   if (gun.xpos == aim.xpos) {
       // line is vertical, how far off is the target
       d = Math.abs(target.xpos - gun.xpos) 
       x = gun.xpos;
       y = target.ypos 
   } else {
       let slope1 = (aim.ypos - gun.ypos)/(aim.xpos - gun.xpos)
       let slope2 = (gun.xpos - aim.xpos)/(aim.ypos - gun.ypos)

       x = (slope1*gun.xpos - slope2*target.xpos + target.ypos - gun.ypos)/
              (slope1 - slope2)
       y = slope1 * (x -gun.xpos) + gun.ypos

       let t1 = x-target.xpos
       let t2 = y - target.ypos
       d = Math.sqrt(t1*t1 +t2*t2)
   }

   console.log(d)

   for(let i = -2; i < 3; ++i) {
      theCanvas.SetPixel(x+i, y, 255,0,255)
      theCanvas.SetPixel(x, y+i, 255,0,255)
   }

   if (d <= HIT_RADIUS) {
       TargetExplodes(i)
       hit = true
   }

   return hit
}

function Fire() {
   // the entire world stops when a shot is fired
   clearTimeout(timer)
   timer = null
   cooldown = true

   let hit = 0

   let zx, zy // position of a zap point
   let dy     // change in y
   let dx = 1

   if (aim.xpos == gun.xpos) {
       dy = 1
   } else { 
       dy = (aim.ypos-gun.ypos)/(aim.xpos-gun.xpos)
   }

   if (aim.xpos < gun.xpos) {
       dx = -1
       dy = -dy
   }

   zy = gun.ypos


   for (let zx = gun.xpos; zx != aim.xpos; zx+= dx) {
        theCanvas.SetPixel(zx, zy, 255, 0,0) 
        zy += dy
   }

   for(let item = 0; item < targets.length; ++item) {
      if (FindHit(item)) {
         hit++
      }
   }

   if (hit > 0) {
        timer = setTimeout(Boom, BOOM_TIME)
   } else {
        timer = setTimeout(TimeAction, DELAY_TIME)
   }
}