/* * Make JavaScript Asteroids in One Video-HWuU5ly0taA The game Asteriods is played with a ship firing shots at polygons that break up into multiple, smaller polygons on a defined playing field. If the ship is hit, it reserects in the centre of the playing field and loses a life. When the ship runs out of lives, the game is over. Added features: more lives ship is now an isosceles triangle, not an equalateral two firing modes: semi-automatic and burst-automatic ship does not materialize inside of an asteroid and blow up, again, again, and again when a ship hits an asteroid, there are no points awarded but the asteroid breaks apart shields: with shields on, the ship is kicked around like a soccer ball. Firing drops the shields. Bumping reduces shields. game modifications in GET parameters: lives=extra, dropmines, goal=, test=true (starburst, everybodydies, dropmines, missiles) The playing field is a canvas. The ship is a circled-triangle of a defined radius. The shots are points with short contrails. The asteroids are polygons of three sizes. The number of lives and the score are displayed in certain positions on the screen. The game status text is displayed in a certain position on the screen when the play stops, either at the end of the round or when the ship runs out of lives. The ship has velocity, speed and direction. When the ship reserects, it is stopped, but once it starts moving it always has some momentum. If the ship goes off the screen it moves to the opposite side of the screen. The ship rotates, but it doesn't steer. Rotation does not have momentum. The ship is drawn as an equalateral triangle with vertices on the circle centred on the ship's location. The ship has a launch tube in the nose, defined as a point on the circle. Shots start from this point. When the ship respawns, it does so only when there are no asteroids within a certain distance from the spawning grounds. Shots have a constant velocity, speed and direction. Shots spawn from the launch tube of the ship. The velocity of the ship does not affect the velocity of the shots. Asteroids have constant speed and direction. Asteroid spawn in random positions and directions on the field, but not within a certain distance from the ship. Asteroids spawn as the largest size. Asteroids do not collide with each other. Asteroids collide with shots. Asteroids collide with the ship. Bullets, missiles, and mines do not collide with each other. Shots collide with asteroids. Shots do not collide with the ship. Nova bombs have a 1:100,000 chance of colliding with an asteroid, and make a big explosion that lasts for 30 seconds. There is a function that returns degrees to radians. There is a function that returns clockface to radians. There is a function that catches turn-left, turn-right, and accelerate. There is no brake. There is a function that takes velocity and returns x,y-velocities. */ let canvas; let ctx; let canvasWidth = screen.availWidth<1420?screen.availWidth-20:1400; let canvasHeight = screen.availHeight<720?screen.availHeight-20:700; let keys = []; let ship; let bullets = []; let asteroids = []; const initialSpeed = 1.4; const incrementSpeed = 0.3; let asteroidSpeed = initialSpeed; let score = 0; let lives = 3; let mineCount = 0; let missileCount = 0; let goalOfTheGame = ''; let shieldsBroken = false; let shieldsBrokenAt = 2000.0; let safetyBrakesAt = 5000.0; // HOMEWORK SOLUTION - Contributed by luckyboysunday let highScore; let localStorageName = "HighScore"; let localStorageNoCheat = "ValidScore"; document.addEventListener('DOMContentLoaded', SetupCanvas); function SetupCanvas() { testOrDie(); // If any parameters are used, implement them here. May be modified with the menu or with cookies. if(hasParameter('lives') && getParameterValue('lives') == 'extra') lives = 30; if(hasParameter('asteroids') && !isNaN(getParameterValue('asteroids'))) lives = (getParameterValue('asteroids')-8)*10; if(hasParameter('shields') && !isNaN(getParameterValue('shields'))) { shieldsBrokenAt = getParameterValue('shields'); safetyBrakesAt = 2.5 * shieldsBrokenAt; } if(hasParameter('dropmines')) { mineCount = 3; shieldsBroken = true; } if(hasParameter('missiles')) { missileCount = 2; shieldsBroken = true; } if(hasParameter('goal')) goalOfTheGame = getParameterValue('goal'); canvas = document.getElementById("my-canvas"); ctx = canvas.getContext("2d"); canvas.width = canvasWidth; canvas.height = canvasHeight; ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height); if(canvasHeight<=430 || canvasWidth<=298){ ctx.fillStyle = 'white'; ctx.font = '21px Arial'; ctx.fillText("THIS SCREEN ", 20, 35); ctx.fillText("IS TOO SMALL" , 20, 70); ctx.fillText("TO PLAY ASTEROIDS" , 20, 105); ctx.fillText("H: " + screen.height, 20, 140); ctx.fillText("W: " + screen.width, 20, 175); testDied('this screen is too small to play asteriods'); } ship = new Ship(); if(hasParameter('shieldPower') && !isNaN(getParameterValue('shieldPower'))) { ship.shieldPower = getParameterValue('shieldPower')/100; asteroidSpeed = ship.shieldPower * initialSpeed; ship.shieldsOn = true; shieldsBroken = true; } for (let i = 0; i < 8 + Math.floor(lives/10); i++) { asteroids.push(new Asteroid()); } // Store all possible keycodes in an array so that // multiple keys can work at the same time // document.body.addEventListener("keydown", function(e) { // keys[e.keyCode] = true; // }); // document.body.addEventListener("keyup", function(e) { // keys[e.keyCode] = false; // if (e.keyCode === 32){ // bullets.push(new Bullet(ship.angle)); // } // }); document.body.addEventListener("keydown", HandleKeyDown); document.body.addEventListener("keyup", HandleKeyUp); // HOMEWORK SOLUTION - Contributed by luckyboysunday // Retrieves locally stored high scores if (localStorage.getItem(localStorageName) == null || localStorage.getItem(localStorageNoCheat) == null) { highScore = 0; } else { highScore = localStorage.getItem(localStorageName); } if (hasParameter('test') && getParameterValue('test') == 'true') testHorizontalMovementOrDie(); Render(); } // HOMEWORK SOLUTION // Move event handling functions so that we can turn off // event handling if game over is reached // A ship cannot turn or fire if it has not spawned function HandleKeyDown(e) { keys[e.keyCode] = ship.visible; if (ship.visible && e.keyCode === 32 && ship.rapidFire && bullets.length < 20) { if (ship.shieldPower > 0) ship.Shields(false); else bullets.push(new Bullet(ship.angle)); } } function HandleKeyUp(e) { keys[e.keyCode] = false; if (ship.visible && e.keyCode === 32 && !ship.rapidFire) { if (ship.shieldPower > 0) ship.Shields(false); else bullets.push(new Bullet(ship.angle)); } if (e.keyCode === 70) { // Key F // if shields are up and ship is visible, drop a mine if (ship.visible && ship.shieldPower > 0 && mineCount == 6 && (!ship.rapidFire || missileCount <= 0)) bullets.push(new NovaBomb(ship.x, ship.y, ship.Angle(), ship.Speed())); else if (ship.visible && ship.shieldPower > 0 && mineCount%4 == 3 && (!ship.rapidFire || missileCount <= 0)) bullets.push(new MagneticMine(ship.x, ship.y, ship.Angle(), ship.Speed())); else if (ship.visible && ship.shieldPower > 0 && mineCount > 0 && (!ship.rapidFire || missileCount <= 0)) bullets.push(new Mine(ship.x, ship.y, ship.Angle(), ship.Speed())); else if (ship.visible && ship.shieldPower > 0 && missileCount > Math.ceil( Math.log(lives) ) && (ship.rapidFire || mineCount <= 0)) bullets.push(new Missile({angle: ship.Angle(), speed: ship.Speed()})); /* */ else if (ship.visible && ship.shieldPower > 0 && missileCount > 0 && (ship.rapidFire || mineCount <= 0)) bullets.push(new YakaArrow({angle: ship.Angle(), speed: ship.Speed()})); else { // toggle gun selection, even if ship is not visible ship.rapidFire = !ship.rapidFire; ship.gunPort = (ship.gunPort == 1 && ship.rapidFire) ? 2 : ship.rapidFire ? 1 : 0; } } if (e.keyCode === 83 && shieldsBroken || SystemEnergy() > shieldsBrokenAt) { // Key S // raise shields, even if ship is not visible ship.Shields(true); // only allow the shields after breaking SystemEnergy barrier shieldsBroken = true; } } class Ship { constructor() { this.visible = false; // Do not spawn ship until it is out of danger this.spawnClock = new Date(); this.x = canvasWidth / 2; this.y = canvasHeight / 2; this.shadow = []; this.movingForward = false; this.speed = 0.1; this.velX = 0; this.velY = 0; this.rotateSpeed = 5.000; this.radius = 15; this.collisionRadius = 11; this.angle = 0; this.strokeColor = 'white'; // Will be used to know where to fire the bullet from this.gunPortX = [canvasWidth / 2, canvasWidth / 2]; this.gunPortY = [canvasHeight / 2 - 15, canvasWidth / 2 + 15]; this.gunPort = 0; this.rapidFire = false; this.shieldPower = 0; this.shieldsOn = false; this.friction = true; this.shieldClock = 0; this.isInCollision = false; } Rotate(dir) { this.angle += this.rotateSpeed * dir; } Shields(powerUpShields) { this.shieldsOn = powerUpShields; } Update() { if(isValid(this.shadow)) { for(let i = this.shadow.length-1; i>=0; i--){ if(this.shadow[i].time>=-2.0) { this.shadow[i].time-=1.0; } else { this.shadow.splice(i, 1); } } this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // Get current direction ship is facing let radians = deg2rad(this.angle); // If moving forward calculate changing values of x & y // If you want to find the new point x use the // formula oldX + cos(radians) * distance // Forumla for y oldY + sin(radians) * distance if (this.movingForward) { this.velX += Math.cos(radians) * this.speed; this.velY += Math.sin(radians) * this.speed; } // If ship goes off board place it on the opposite side let overboard = false; if (this.x < -this.collisionRadius) { this.x = canvas.width + this.collisionRadius; overboard = true; } if (this.x > canvas.width + this.collisionRadius) { this.x = -this.collisionRadius; overboard = true; } if (this.y < -this.collisionRadius) { this.y = canvas.height + this.collisionRadius; overboard = true; } if (this.y > canvas.height + this.collisionRadius) { this.y = -this.collisionRadius; overboard = true; } if(overboard && isValid(this.shadow)) { this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // Slow ship speed when not holding key, and when not in fish bowl if (this.friction && !this.isInCollision) { // stop dead but don't glide through an asteroid this.velX *= (this.shieldPower>asteroidSpeed/initialSpeed? 1: this.shieldPower/asteroidSpeed*initialSpeed) * 0.0099 + Math.min(safetyBrakesAt/(SystemEnergy()+50), 0.99); this.velY *= (this.shieldPower>asteroidSpeed/initialSpeed? 1: this.shieldPower/asteroidSpeed*initialSpeed) * 0.0099 + Math.min(safetyBrakesAt/(SystemEnergy()+50), 0.99); } else if (this.isInCollision) { // always have some friction in fish bowl // change in velocity is one cause of fish bowl. the other is materializing inside of an asteroid, or having materializing around the ship this.velX *= 0.9999; this.velY *= 0.9999; /* */ } // Change value of x & y while accounting for dark matter ;) friction // Dark matter is denser in high enegy field ;) this.x -= this.velX; this.y -= this.velY; /* // if the shields timed out, drop them if (this.shields && (new Date).getTime() - this.shieldClock.getTime() > 5000) { // this.shields = false; } */ // Power up or power down or repair shields if (this.shieldsOn && this.shieldPower < asteroidSpeed/initialSpeed) { this.shieldPower += 0.001 * asteroidSpeed / initialSpeed; } else if (!this.shieldsOn && this.shieldPower > 0) { this.shieldPower -= 0.1; } else if (this.shieldsOn) { this.shieldPower = asteroidSpeed/initialSpeed; } else { this.shieldPower = 0; } if(isValid(this.shadow) && this.shadow.length>=1) { let newShadow = fromToTrail(this.shadow[this.shadow.length-1], this, this.radius/2); for(let i = 0; i 0) { let levelPower = (this.shieldPower>1?1:this.shieldPower); ctx.lineWidth = (this.shieldPower > 0?(this.shieldPower > 16?4:Math.ceil(this.shieldPower / 4)-(Math.floor(this.shieldPower)%4==0?1:0)):1); switch(Math.floor(this.shieldPower)%4) { case 0: ctx.strokeStyle = 'rgb(' + Math.floor(127 + 128 * levelPower) + ',' + Math.floor(255 * levelPower) + ',' + Math.floor(255 * levelPower) + ')'; break; case 1: ctx.strokeStyle = 'rgb(' + // 189, 183, 107 Math.floor(62 + 156 * levelPower + 66 * levelPower * levelPower) + ',' + Math.floor(60 + 151 * levelPower + 72 * levelPower * levelPower) + ',' + Math.floor(35 + 88 * levelPower + 148 * levelPower * levelPower) + ')'; break; case 2: ctx.strokeStyle = 'rgb(' + Math.floor(255 * levelPower) + ',' + Math.floor(255 * levelPower) + ',' + Math.floor(127 + 128 * levelPower) + ')'; break; case 3: default: ctx.strokeStyle = 'rgb(' + // 238,130,238 139, 0, 139 Math.floor(139 + 101 * levelPower + 17 * levelPower * levelPower) + ',' + Math.floor(0 + 130 * levelPower + 125 * levelPower * levelPower) + ',' + Math.floor(139 + 101 * levelPower + 17 * levelPower * levelPower) + ')'; break; } ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, deg2rad(this.angle) + Math.PI - Math.PI * levelPower, deg2rad(this.angle) + Math.PI + Math.PI * levelPower); ctx.stroke(); if (this.shieldPower > 1) { levelPower = (this.shieldPower==Math.floor(this.shieldPower)?1:this.shieldPower-Math.floor(this.shieldPower)); ctx.lineWidth = (this.shieldPower > 16?4:Math.ceil(this.shieldPower / 4)); switch(Math.floor(this.shieldPower)%4) { case 0: ctx.strokeStyle = 'rgb(' + Math.floor(127 + 128 * levelPower) + ',' + Math.floor(255 * levelPower) + ',' + Math.floor(255 * levelPower) + ')'; break; case 1: ctx.strokeStyle = 'rgb(' + // 189, 183, 107 Math.floor(62 + 156 * levelPower + 66 * levelPower * levelPower) + ',' + Math.floor(60 + 151 * levelPower + 72 * levelPower * levelPower) + ',' + Math.floor(35 + 88 * levelPower + 148 * levelPower * levelPower) + ')'; break; case 2: ctx.strokeStyle = 'rgb(' + Math.floor(255 * levelPower) + ',' + Math.floor(255 * levelPower) + ',' + Math.floor(127 + 128 * levelPower) + ')'; break; case 3: default: ctx.strokeStyle = 'rgb(' + // 238,130,238 139, 0, 139 Math.floor(139 + 101 * levelPower + 17 * levelPower * levelPower) + ',' + Math.floor(0 + 130 * levelPower + 125 * levelPower * levelPower) + ',' + Math.floor(139 + 101 * levelPower + 17 * levelPower * levelPower) + ')'; break; } ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, deg2rad(this.angle) + Math.PI - Math.PI * levelPower, deg2rad(this.angle) + Math.PI + Math.PI * levelPower); ctx.stroke(); } ctx.lineWidth = 1; } } setVelocity(params) { if (isValid(params.velX) && isValid(params.velY)) { this.velX = -params.velX; this.velY = -params.velY; } else if (isValid(params.angle) && isValid(params.speed)) { let radians = deg2rad(params.angle); this.velX = -Math.cos(radians) * params.speed; this.velY = -Math.sin(radians) * params.speed; } else throw ('setVelocity not set properly'); // console.log(params); } Speed() { return Math.sqrt(this.velX * this.velX + this.velY * this.velY); }; Angle() { return rad2deg(arctan(-this.velX, -this.velY)); } VelocityX() { return -this.velX; } VelocityY() { return -this.velY; } Mass() { /* return this.radius;*/ return Math.max(SystemEnergy()/(safetyBrakesAt+50), this.radius); } } class Bullet { constructor(angle) { this.visible = true; this.x = isValid(ship)?((isValid(ship.gunPort) && isValid(ship.gunPortX))?ship.gunPortX[ship.gunPort]:ship.x):canvasWidth/2; this.y = isValid(ship)?((isValid(ship.gunPort) && isValid(ship.gunPortY))?ship.gunPortY[ship.gunPort]:ship.y):canvasHeight/2; this.shadow = []; if(isValid(ship) && isValid(ship.gunPort)) { ship.gunPort = (ship.gunPort == 1 && ship.rapidFire) ? 2 : ship.rapidFire ? 1 : 0; } this.angle = angle; this.height = 4; this.width = 4; this.speed = 5; this.collisionRadius = 3; this.velX = isValid(ship)? ship.velX : 5; this.velY = isValid(ship)? ship.velY : 5; } Update() { // If bullet goes off board it disappears if (this.x < this.width || this.x > canvas.width - this.width || this.y < this.height || this.y > canvas.height - this.height) { this.visible = false; } let radians = deg2rad(this.angle); this.x -= Math.cos(radians) * this.speed + this.velX; this.y -= Math.sin(radians) * this.speed + this.velY; if(isValid(this.shadow) && this.shadow.length>=1) { let newShadow = fromToTrail(this.shadow[this.shadow.length-1], this, this.height/2); for(let i = 0; i=0; i--){ if(this.shadow[i].time>=-2.0) { this.shadow[i].time-=1.0; } else { this.shadow.splice(i, 1); } } this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } let radians = deg2rad(this.angle); // If mine goes off board place it on the opposite side let overboard = false; if (this.x < this.width) { this.x = canvas.width - this.width; overboard = true; } if (this.x > canvas.width - this.width) { this.x = this.width; overboard = true; } if (this.y < this.height) { this.y = canvas.height - this.height; overboard = true; } if (this.y > canvas.height - this.height) { this.y = this.height; overboard = true; } if(overboard && isValid(this.shadow)) { this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // accelerate towards the iron in the asteroids for(i=0; i=1) { let newShadow = fromToTrail(this.shadow[this.shadow.length-1], this, this.height/2); for(let i = 0; iasteroidSpeed*0.10) { // make a long shadow if(isValid(this.shadow)){ for(let i = 0; i { if(bullets.length<1000)bullets.push(new YakaArrow({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 7, speed: this.speed})); if(bullets.length<1000)bullets.push(new YakaArrow({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 27, speed: this.speed})); if(bullets.length<1000)bullets.push(new YakaArrow({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 47, speed: this.speed})); }, 150); setTimeout(() => { if(bullets.length<1000)bullets.push(new YakaArrow({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 14, speed: this.speed})); if(bullets.length<1000)bullets.push(new YakaArrow({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 34, speed: this.speed})); if(bullets.length<1000)bullets.push(new YakaArrow({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 53, speed: this.speed})); }, 300); } } Speed() { return this.speed; }; Angle() { return this.angle % 360.0; } VelocityX() { return Math.cos(deg2rad(this.angle)) * this.speed; } VelocityY() { return Math.sin(deg2rad(this.angle)) * this.speed; } Mass(){ return 0; } } // When a neutrino mine is dropped, it has the same position and velociy as the ship, but it is attracted to all of the asteroids subject to the inverse square rule. class NovaBomb { constructor(x, y, angle, speed) { this.visible = true; this.x = x; this.y = y; this.shadow = []; if(ship) ship.rapidFire = false; this.angle = angle; this.height = 5; this.width = 5; this.speed = speed; this.collisionRadius = 8; if(ship) mineCount -= 1; this.CanCollide = false; } Update() { if(isValid(this.shadow)) { for(let i = this.shadow.length-1; i>=0; i--){ if(this.shadow[i].time>=-2.0) { this.shadow[i].time-=1.0; } else { this.shadow.splice(i, 1); } } this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } let radians = deg2rad(this.angle); // If mine goes off board place it on the opposite side let overboard = false; if (this.x < this.width) { this.x = canvas.width - this.width; overboard = true; } if (this.x > canvas.width - this.width) { this.x = this.width; overboard = true; } if (this.y < this.height) { this.y = canvas.height - this.height; overboard = true; } if (this.y > canvas.height - this.height) { this.y = this.height; overboard = true; } if(overboard && isValid(this.shadow)) { this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // accelerate towards the iron in the asteroids for(i=0; i 1.10 * asteroidSpeed) this.speed *= 0.99; this.x += Math.cos(radians) * this.speed; this.y += Math.sin(radians) * this.speed; this.CanCollide = Math.random()>(this.speed>asteroidSpeed ? Math.sqrt(asteroidSpeed/this.speed) : 0.9999) && (SystemEnergy()>1000000||mineCount >= 4); // Particle interaction is a function of the square of the inertia. A slow particle won't explode. if(isValid(this.shadow) && this.shadow.length>=1) { let newShadow = fromToTrail(this.shadow[this.shadow.length-1], this, this.height/2); for(let i = 0; iasteroidSpeed) { // make a long shadow if(isValid(this.shadow)){ for(let i = 0; iasteroidSpeed*0.90) { // make a long shadow if(isValid(this.shadow)){ for(let i = 0; iasteroidSpeed*0.10) { // make a long shadow if(isValid(this.shadow)){ for(let i = 0; i0)bullets.push(new Mine(this.x, this.y, this.angle + i * 60 + 3, asteroidSpeed)); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 20)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 40)); }, 150); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 14)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 34)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 53)); }, 300); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 7)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 27)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 47)); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 7, speed: this.speed})); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 27, speed: this.speed})); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 47, speed: this.speed})); }, 450); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 14)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 34)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 53)); }, 900); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 20)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 40)); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 14, speed: this.speed})); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 34, speed: this.speed})); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 53, speed: this.speed})); }, 1800); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 7)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 27)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 47)); }, 3600); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 14)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 34)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 53)); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 14, speed: this.speed})); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 34, speed: this.speed})); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 53, speed: this.speed})); }, 7200); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 7)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 27)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 47)); }, 10800); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 14)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 34)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 53)); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 7, speed: this.speed})); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 27, speed: this.speed})); if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: this }, x: this.x, y: this.y, angle: this.angle + i * 60 + 47, speed: this.speed})); }, 14400); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 20)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 40)); }, 18000); setTimeout(() => { for(let j = 0; j < asteroids.length / 6; j++) { if(bullets.length<1000)bullets.push(new Missile({target: { asteroid: asteroids[i*6+j] }, x: this.x, y: this.y, angle: this.angle + i * 60 + 3, speed: this.speed})); } }, 24000); setTimeout(() => { let newAsteroid; // duplicate the average speed of the existing asteroids and spaceship let newSpeed = Math.min(asteroidSpeed,SystemEnergy() / (asteroids.length + 1) / 10); for(let j = 0; j < bullets.length / 6; j++) { if(asteroids.length<1000) { newAsteroid = new Asteroid(this.x, this.y, 10, 4, 8); newAsteroid.angle = Math.random() * 359.99; newAsteroid.speed = newSpeed * 0.90; asteroids.push(newAsteroid); } } }, 30000); setTimeout(() => { let newAsteroid; // duplicate the average speed of the existing asteroids and spaceship let newSpeed = Math.min(asteroidSpeed,SystemEnergy() / (asteroids.length + 1) / 10); for(let j = 0; j < bullets.length / 6; j++) { if(asteroids.length<1000) { newAsteroid = new Asteroid(this.x, this.y, 10, 4, 8); newAsteroid.angle = Math.random() * 359.99; newAsteroid.speed = newSpeed * 1.20; asteroids.push(newAsteroid); } } }, 45000); setTimeout(() => { let newAsteroid; // duplicate the average speed of the existing asteroids and spaceship let newSpeed = Math.min(asteroidSpeed,SystemEnergy() / (asteroids.length + 1) / 10); for(let j = 0; j < bullets.length / 6; j++) { if(asteroids.length<1000) { newAsteroid = new Asteroid(this.x, this.y, 10, 4, 8); newAsteroid.angle = Math.random() * 359.99; newAsteroid.speed = newSpeed * 1.50; asteroids.push(newAsteroid); } } }, 60000); } } Speed() { return this.speed; }; Angle() { return this.angle % 360.0; } VelocityX() { return Math.cos(deg2rad(this.angle)) * this.speed; } VelocityY() { return Math.sin(deg2rad(this.angle)) * this.speed; } Mass(){ return this.height; } } // When a mine is dropped, it has the same position and velociy as the ship. Friction (with dark matter?) slows it down. class Mine { constructor(x, y, angle, speed) { this.visible = true; this.x = x; this.y = y; this.shadow = []; if(ship) ship.rapidFire = false; this.angle = angle; this.height = 5; this.width = 5; this.speed = speed; this.collisionRadius = 8; if(ship) mineCount -= 1; } Update() { if(isValid(this.shadow)) { for(let i = this.shadow.length-1; i>=0; i--){ if(this.shadow[i].time>=-2.0) { this.shadow[i].time-=1.0; } else { this.shadow.splice(i, 1); } } this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } let radians = deg2rad(this.angle); // If mine goes off board place it on the opposite side let overboard = false; if (this.x < this.width) { this.x = canvas.width - this.width; overboard = true; } if (this.x > canvas.width - this.width) { this.x = this.width; overboard = true; } if (this.y < this.height) { this.y = canvas.height - this.height; overboard = true; } if (this.y > canvas.height - this.height) { this.y = this.height; overboard = true; } if(overboard && isValid(this.shadow)) { this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // Slow mine speed due to friction (with dark matter?) this.speed *= 0.99; this.x += Math.cos(radians) * this.speed; this.y += Math.sin(radians) * this.speed; if(isValid(this.shadow) && this.shadow.length>=1) { let newShadow = fromToTrail(this.shadow[this.shadow.length-1], this, this.height/2); for(let i = 0; iasteroidSpeed*0.10) { // make a long shadow if(isValid(this.shadow)){ for(let i = 0; i { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 7)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 27)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 47)); }, 150); setTimeout(() => { if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 14)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 34)); if(bullets.length<1000)bullets.push(new Shrapnel(this.x, this.y, this.angle + i * 60 + 53)); }, 300); } } Speed() { return this.speed; }; Angle() { return this.angle % 360.0; } VelocityX() { return Math.cos(deg2rad(this.angle)) * this.speed; } VelocityY() { return Math.sin(deg2rad(this.angle)) * this.speed; } } class Shrapnel { constructor(x, y, angle) { this.visible = true; this.x = x; this.y = y; this.shadow = []; this.height = 4; this.width = 4; this.collisionRadius = 3; this.velX = Math.sin(deg2rad(angle)) * 5; this.velY = Math.cos(deg2rad(angle)) * 5; } Update() { if(isValid(this.shadow)) { for(let i = this.shadow.length-1; i>=0; i--){ if(this.shadow[i].time>=-2.0) { this.shadow[i].time-=1.0; } else { this.shadow.splice(i, 1); } } this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // If shrapnel goes off board place it on the opposite side let overboard = false; if (this.x < this.width) { this.x = canvas.width - this.width; overboard = true; } if (this.x > canvas.width - this.width) { this.x = this.width; overboard = true; } if (this.y < this.height) { this.y = canvas.height - this.height; overboard = true; } if (this.y > canvas.height - this.height) { this.y = this.height; overboard = true; } if(overboard && isValid(this.shadow)) { this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } this.x -= this.velX; this.y -= this.velY; if(isValid(this.shadow) && this.shadow.length>=1) { let newShadow = fromToTrail(this.shadow[this.shadow.length-1], this, this.height/2); for(let i = 0; ithis.speed) { // match missile top speed to catch up to asteroid if missile is too slow. Both Missile & Asteroid speeds will always be non-zero. let divV = this.target.asteroid.Speed()/this.speed*1.2; // e.g.: if asteroid speed is 20, missile is 12, divV = 2.0; missile speed is reset to 24, and velocity is reset to -12 to compensate. // e.g.: if asteroid speed is 15, missile is 12, divV = 1.5; missile speed is reset to 18, and velocity is reset to -6 to compensate. // e.g.: if asteroid speed is 12, missile is 12, divV = 1.2; missile speed is reset to 14.4, and velocity is reset to -2.4 to compensate. let radians = deg2rad(this.angle); this.velX += Math.cos(radians) * this.speed * (1.0 - divV); this.velY += Math.sin(radians) * this.speed * (1.0 - divV); this.speed *= divV; } missileCount -= 1; } AquireTarget(position) { if (isValid(position.x) && isValid(position.y)) { for (let i = 0; i < asteroids.length; i++) { if (TrailCollision(this, asteroids[i])) return { asteroid: asteroids[i] }; } } if (isValid(position.asteroid)) return { asteroid: position.asteroid }; setTimeout(() => { this.target = this.AquireTarget({ asteroid: asteroids[Math.floor(Math.random() * asteroids.length)] }); }, 500 * Math.random() * bullets.length); return { return: ship }; } Update() { if(isValid(this.shadow)) { for(let i = this.shadow.length-1; i>=0; i--){ if(this.shadow[i].time>=-2.0) { this.shadow[i].time-=1.0; } else { this.shadow.splice(i, 1); } } this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // If missile goes off board place it on the opposite side let overboard = false; if (this.x < this.length) { this.x = canvas.width - this.length; overboard = true; } if (this.x > canvas.width - this.length) { this.x = this.length; overboard = true; } if (this.y < this.length) { this.y = canvas.height - this.length; overboard = true; } if (this.y > canvas.height - this.length) { this.y = this.length; overboard = true; } if(overboard && isValid(this.shadow)) { this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // turn the missile towards the target if (isValid(this.target) && isValid(this.target.asteroid) && this.target.asteroid.visible) { /* let pathAngle = Math.floor(rad2deg(arctan(this.target.asteroid.x - this.x, this.target.asteroid.y - this.y)) - this.angle) % 360; if (pathAngle < -this.turningSpeed *1.5 && pathAngle >= -180 || pathAngle >= 180 && pathAngle < 360 -this.turningSpeed *1.5) this.angle += this.turningSpeed; else if (pathAngle > this.turningSpeed *1.5 && pathAngle < 180 || pathAngle <= -180 && pathAngle > -360 +this.turningSpeed *1.5) this.angle -= this.turningSpeed; */ this.angle += this.PickBestTurn(this.target.asteroid); } else if (isValid(this.target) && isValid(this.target.return)) { /* let pathAngle = Math.floor(rad2deg(arctan(this.target.return.x - this.x, this.target.return.y - this.y)) - this.angle) % 360; if (pathAngle < 0 && pathAngle >= -180 || pathAngle >= 180) this.angle += this.turningSpeed; else this.angle -= this.turningSpeed; */ this.angle += this.PickBestTurn(this.target.return); } else { this.target = this.AquireTarget({ asteroid: asteroids[Math.floor(Math.random() * asteroids.length)] }); this.LogBlast(); } // missile accelerates to its own speed this.velX *= 0.999; this.velY *= 0.999; let radians = deg2rad(this.angle); this.x -= Math.cos(radians) * this.speed + this.velX; this.y -= Math.sin(radians) * this.speed + this.velY; if(isValid(this.shadow) && this.shadow.length>=1) { let newShadow = fromToTrail(this.shadow[this.shadow.length-1], this, this.length/2); for(let i = 0; iasteroidSpeed*0.10 || this.Speed()>4.0) { // make a long shadow if(isValid(this.shadow)){ for(let i = 0; i=Math.ceil( Math.log(lives) )?0:1; } PickBestTurn(targetPosition) { // turn toward the target in the nearest quadrant, but the target has to be ahead, less than 90° left or right. let metaTarget = {x:this.x, y:this.y, turn:0.0, distance:Math.sqrt(Math.pow(canvas.width, 2) + Math.pow(canvas.height, 2))*2}; let pathAngle; let turnRadius = 90.0/this.turningSpeed*this.Speed()*4.0/Math.PI; for( let i=-1; i<=1; i++ ) { for( let j=-1; j<=1; j++ ) { let metaX = targetPosition.x + i * canvas.width; let metaY = targetPosition.y + j * canvas.height; let pathDistance = Math.sqrt(Math.pow(this.x - metaX, 2) + Math.pow(this.y - metaY, 2)); metaX += pathDistance/(this.Speed()!=0?this.Speed():0.0001)*targetPosition.VelocityX(); metaY += pathDistance/(this.Speed()!=0?this.Speed():0.0001)*targetPosition.VelocityY(); pathDistance = Math.sqrt(Math.pow(this.x - metaX, 2) + Math.pow(this.y - metaY, 2)); pathAngle = rad2deg(arctan(this.x - metaX, this.y - metaY)) - this.Angle() % 360.0; if(pathAngle>180) pathAngle -= 360.0; else if(pathAngle<-180) pathAngle += 360.0; let pathRadius = 180.0/Math.max(0.001,Math.abs(pathAngle))*this.Speed()/Math.PI if(pathDistancepathRadius ? pathDistance>turnRadius : Math.abs(pathAngle)= -180 || pathAngle >= 180 && pathAngle < 360 -this.turningSpeed *1.5) turn = -this.turningSpeed; else if (pathAngle > this.turningSpeed *1.5 && pathAngle < 180 || pathAngle <= -180 && pathAngle > -360 +this.turningSpeed *1.5) turn = this.turningSpeed; return turn; } Speed() { return Math.sqrt(Math.pow(this.VelocityX(),2) + Math.pow(this.VelocityY(),2)); }; Angle() { return rad2deg(arctan(this.VelocityX(), this.VelocityY())); } VelocityX() { return Math.cos(deg2rad(this.angle)) * this.speed + this.velX; } VelocityY() { return Math.sin(deg2rad(this.angle)) * this.speed + this.velY; } Mass(){ return 0; } LogBlast() { if (isValid(this.target) && isValid(this.target.asteroid) && isValid(this.target.asteroid.x) && this.target.asteroid.visible) { console.log('let testAsteroid = new Asteroid('+ this.target.asteroid.x +', '+ this.target.asteroid.y +', '+ this.target.asteroid.radius +', '+ this.target.asteroid.level +', '+ this.target.asteroid.collisionRadius +');'); console.log('testAsteroid.angle = '+ this.target.asteroid.angle +';'); console.log('testAsteroid.speed = '+ this.target.asteroid.speed +';'); } else if (isValid(this.target) && isValid(this.target.return)) { console.log('ship.x = '+ this.target.return.x +'; '+ 'ship.y = '+ this.target.return.y +'; '); console.log('ship.SetVelocity( {angle:'+ this.target.return.Angle() +', '+ 'speed: '+ this.target.return.Speed() +'});'); } console.log('testMissile.angle = '+ this.angle +';'); //872.5021848875925 console.log('testMissile.speed = '+ this.speed +';'); //4.2; console.log('testMissile.turningSpeed = '+ this.turningSpeed +';'); //8.809014250079281; console.log('testMissile.velX = '+ this.velX +';'); //0.00035670879035128; console.log('testMissile.velY = '+ this.velY +';'); //-0.00038382190279197996; console.log('testMissile.x = '+ this.x +';'); //816.0179040858244; console.log('testMissile.y = '+ this.y +';'); //179.99223673094633; return 'complete'; } } // When a Yaka Arrow is launched, it steers towards the target then flies straight, kills everything in its path, hits target, returns home killing everything in its path. class YakaArrow { constructor(params) { this.visible = true; this.x = isValid(params.x)?params.x:isValid(ship)?ship.x:canvasWidth/2; this.y = isValid(params.y)?params.y:isValid(ship)?ship.y:canvasHeight/2; this.shadow = []; this.collisionRadius = 3; this.length = 8; this.width = 3; this.angle = params.angle ? params.angle : isValid(ship)?ship.angle:45; this.speed = Math.min(asteroidSpeed * 1.2, this.length * 2.5); this.turningSpeed = 30/this.speed + Math.random() * 4; this.velX = (isValid(params.angle) && isValid(params.speed))?params.speed * Math.cos(deg2rad(params.angle)):isValid(ship)?ship.velX:1; this.velY = (isValid(params.angle) && isValid(params.speed))?params.speed * Math.sin(deg2rad(params.angle)):isValid(ship)?ship.velY:1; // this.target = this.AquireTarget({ asteroid: asteroids[Math.floor(Math.random() * asteroids.length)] }); this.target = params.target ? params.target : this.FastestAsteroid(); this.LogBlast(); if (isValid(this.target) && isValid(this.target.asteroid) && this.target.asteroid.Speed()*1.2>this.speed) { // match missile top speed to catch up to asteroid if missile is too slow. Both Missile & Asteroid speeds will always be non-zero. let divV = this.target.asteroid.Speed()/this.speed*1.2; // e.g.: if asteroid speed is 20, missile is 12, divV = 2.0; missile speed is reset to 24, and velocity is reset to -12 to compensate. // e.g.: if asteroid speed is 15, missile is 12, divV = 1.5; missile speed is reset to 18, and velocity is reset to -6 to compensate. // e.g.: if asteroid speed is 12, missile is 12, divV = 1.2; missile speed is reset to 14.4, and velocity is reset to -2.4 to compensate. let radians = deg2rad(this.angle); this.velX += Math.cos(radians) * this.speed * (1.0 - divV); this.velY += Math.sin(radians) * this.speed * (1.0 - divV); this.speed *= divV; } missileCount -= 1; } AquireTarget(position) { if (isValid(position.x) && isValid(position.y)) { for (let i = 0; i < asteroids.length; i++) { if (TrailCollision(this, asteroids[i])) return { asteroid: asteroids[i] }; } } if (isValid(position.asteroid)) return { asteroid: position.asteroid }; if(CircleCollision(position.x, position.y, ship.collisionRadius, ship.x, ship.y, ship.collisionRadius)) { return { return: ship }; } if(!isValid(position.x) && !isValid(position.ship) && !isValid(position.return)) setTimeout(() => { this.target = this.AquireTarget({ asteroid: asteroids[Math.floor(Math.random() * asteroids.length)] }); }, 250); return { return: ship }; } FastestAsteroid() { if(asteroids.length == 0 ) { setTimeout(() => {this.target = this.FastestAsteroid();}, 50*bullets.length); return { return: ship }; } let preferredAsteroid = asteroids[0]; for (let i = 1; i < asteroids.length; i++) { if( asteroids[i].level > preferredAsteroid.level ) { preferredAsteroid = asteroids[i]; } else if( asteroids[i].level == preferredAsteroid.level && asteroids[i].Speed() > preferredAsteroid.Speed() ) { preferredAsteroid = asteroids[i]; } } return { asteroid: preferredAsteroid }; } Update() { if(isValid(this.shadow)) { for(let i = this.shadow.length-1; i>=0; i--){ if(this.shadow[i].time>=-2.0) { this.shadow[i].time-=1.0; } else { this.shadow.splice(i, 1); } } this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // If missile goes off board place it on the opposite side let overboard = false; if (this.x < this.length) { this.x = canvas.width - this.length; overboard = true; } if (this.x > canvas.width - this.length) { this.x = this.length; overboard = true; } if (this.y < this.length) { this.y = canvas.height - this.length; overboard = true; } if (this.y > canvas.height - this.length) { this.y = this.length; overboard = true; } if(overboard && isValid(this.shadow)) { this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } // turn the missile towards the target if (isValid(this.target) && isValid(this.target.asteroid) && this.target.asteroid.visible) { /* let pathAngle = Math.floor(rad2deg(arctan(this.target.asteroid.x - this.x, this.target.asteroid.y - this.y)) - this.angle) % 360; if (pathAngle < -this.turningSpeed *1.5 && pathAngle >= -180 || pathAngle >= 180 && pathAngle < 360 -this.turningSpeed *1.5) this.angle += this.turningSpeed; else if (pathAngle > this.turningSpeed *1.5 && pathAngle < 180 || pathAngle <= -180 && pathAngle > -360 +this.turningSpeed *1.5) this.angle -= this.turningSpeed; */ this.angle += this.PickBestTurn(this.target.asteroid); } else if (isValid(this.target) && isValid(this.target.return)) { /* let pathAngle = Math.floor(rad2deg(arctan(this.target.return.x - this.x, this.target.return.y - this.y)) - this.angle) % 360; if (pathAngle < 0 && pathAngle >= -180 || pathAngle >= 180) this.angle += this.turningSpeed; else this.angle -= this.turningSpeed; */ this.angle += this.PickBestTurn(this.target.return); } else { this.target = this.AquireTarget({ asteroid: asteroids[Math.floor(Math.random() * asteroids.length)] }); this.LogBlast(); } // missile accelerates to its own speed this.velX *= 0.999; this.velY *= 0.999; let radians = deg2rad(this.angle); this.x -= Math.cos(radians) * this.speed + this.velX; this.y -= Math.sin(radians) * this.speed + this.velY; if(isValid(this.shadow) && this.shadow.length>=1) { let newShadow = fromToTrail(this.shadow[this.shadow.length-1], this, this.length/2); for(let i = 0; iasteroidSpeed*0.10 || this.Speed()>4.0) { // make a long shadow if(isValid(this.shadow)){ for(let i = 0; i=Math.ceil( Math.log(lives) )?0:1; // let currentSpeed = this.Speed(); this.speed = ship.Speed() * 0.75; } PickBestTurn(targetPosition) { // turn toward the target in the nearest quadrant, but the target has to be ahead, less than 90° left or right. let metaTarget = {x:this.x, y:this.y, turn:0.0, distance:Math.sqrt(Math.pow(canvas.width, 2) + Math.pow(canvas.height, 2))*2}; let pathAngle; let turnRadius = 90.0/this.turningSpeed*this.Speed()*4.0/Math.PI; for( let i=-1; i<=1; i++ ) { for( let j=-1; j<=1; j++ ) { let metaX = targetPosition.x + i * canvas.width; let metaY = targetPosition.y + j * canvas.height; let pathDistance = Math.sqrt(Math.pow(this.x - metaX, 2) + Math.pow(this.y - metaY, 2)); metaX += pathDistance/(this.Speed()!=0?this.Speed():0.0001)*targetPosition.VelocityX(); metaY += pathDistance/(this.Speed()!=0?this.Speed():0.0001)*targetPosition.VelocityY(); pathDistance = Math.sqrt(Math.pow(this.x - metaX, 2) + Math.pow(this.y - metaY, 2)); pathAngle = rad2deg(arctan(this.x - metaX, this.y - metaY)) - this.angle % 360.0; if(pathAngle>180) pathAngle -= 360.0; else if(pathAngle<-180) pathAngle += 360.0; let pathRadius = 180.0/Math.max(0.001,Math.abs(pathAngle))*this.Speed()/Math.PI if(pathDistancepathRadius ? pathDistance>turnRadius : Math.abs(pathAngle)= -180 || pathAngle >= 180 && pathAngle < 360 -this.turningSpeed *1.5) turn = -this.turningSpeed; else if (pathAngle > this.turningSpeed *1.5 && pathAngle < 180 || pathAngle <= -180 && pathAngle > -360 +this.turningSpeed *1.5) turn = this.turningSpeed; return turn; } Speed() { return Math.sqrt(Math.pow(this.VelocityX(),2) + Math.pow(this.VelocityY(),2)); }; Angle() { return rad2deg(arctan(this.VelocityX(), this.VelocityY())); } VelocityX() { return Math.cos(deg2rad(this.angle)) * this.speed + this.velX; } VelocityY() { return Math.sin(deg2rad(this.angle)) * this.speed + this.velY; } Mass(){ return this.width; } LogBlast() { if (isValid(this.target) && isValid(this.target.asteroid) && isValid(this.target.asteroid.x) && this.target.asteroid.visible) { console.log('let testAsteroid = new Asteroid('+ this.target.asteroid.x +', '+ this.target.asteroid.y +', '+ this.target.asteroid.radius +', '+ this.target.asteroid.level +', '+ this.target.asteroid.collisionRadius +');'); console.log('testAsteroid.angle = '+ this.target.asteroid.angle +';'); console.log('testAsteroid.speed = '+ this.target.asteroid.speed +';'); } else if (isValid(this.target) && isValid(this.target.return)) { console.log('ship.x = '+ this.target.return.x +'; '+ 'ship.y = '+ this.target.return.y +'; '); console.log('ship.SetVelocity( {angle:'+ this.target.return.Angle() +', '+ 'speed: '+ this.target.return.Speed() +'});'); } console.log('testMissile.angle = '+ this.angle +';'); //872.5021848875925 console.log('testMissile.speed = '+ this.speed +';'); //4.2; console.log('testMissile.turningSpeed = '+ this.turningSpeed +';'); //8.809014250079281; console.log('testMissile.velX = '+ this.velX +';'); //0.00035670879035128; console.log('testMissile.velY = '+ this.velY +';'); //-0.00038382190279197996; console.log('testMissile.x = '+ this.x +';'); //816.0179040858244; console.log('testMissile.y = '+ this.y +';'); //179.99223673094633; return 'complete'; } } class Asteroid { constructor(x, y, radius, level, collisionRadius) { this.visible = true; this.x = x || Math.floor(Math.random() * canvasWidth); this.y = y || Math.floor(Math.random() * canvasHeight); this.shadow = []; this.radius = radius || 50; this.speed = Math.min(asteroidSpeed,300/this.radius); this.angle = Math.floor(Math.random() * 359); this.strokeColor = 'white'; this.collisionRadius = collisionRadius || 46; // Used to decide if this asteroid can be broken into smaller pieces this.level = level || 1; // avoid stealth asteroids if(x < 0 || y < 0 || x > canvasWidth || y > canvasHeight) { // off the edge. change direction, if necessary, to nudge off of the edge if(Math.abs(this.angle % 90) <= 0.001 ) this.angle += 0.001 } } Update() { if(isValid(this.shadow)) { for(let i = this.shadow.length-1; i>=0; i--){ if(this.shadow[i].time>=-2.0) { this.shadow[i].time-=1.0; } else { this.shadow.splice(i, 1); } } this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } let radians = deg2rad(this.angle); this.x += Math.cos(radians) * this.speed; this.y += Math.sin(radians) * this.speed; let overboard = false; if (this.x < -this.radius) { this.x = canvas.width + this.radius; overboard = true; } if (this.x > canvas.width + this.radius) { this.x = -this.radius; overboard = true; } if (this.y < -this.radius) { this.y = canvas.height + this.radius; overboard = true; } if (this.y > canvas.height + this.radius) { this.y = -this.radius; overboard = true; } if(overboard && isValid(this.shadow)) { this.shadow.push({x: this.x, y: this.y, time: 0, angle: this.Angle()}); } if(isValid(this.shadow) && this.shadow.length>=1) { let newShadow = fromToTrail(this.shadow[this.shadow.length-1], this, this.radius/2); for(let i = 0; iasteroidSpeed || this.Speed()>4.0) { // make a long shadow if(isValid(this.shadow)){ for(let j = 0; j < this.shadow.length; j++){ ctx.strokeStyle = `rgba( ${Math.ceil(31+64*j/this.shadow.length)}, ${Math.ceil(31+64*j/this.shadow.length)}, ${Math.ceil(31+64*j/this.shadow.length)}, ${Math.ceil(255*j/this.shadow.length)} )`; ctx.beginPath(); for (let i = 0; i < 6; i++) { ctx.lineTo(this.shadow[j].x - this.radius * Math.cos(vertAngle * i + radians), this.shadow[j].y - this.radius * Math.sin(vertAngle * i + radians)); } ctx.closePath(); ctx.stroke(); } } /* // make a long shadow let mineTrail = trailCoordinates(this); for(let j = 0; j < mineTrail.length; j++){ ctx.strokeStyle = `rgba( ${Math.ceil(31+64*j/mineTrail.length)}, ${Math.ceil(31+64*j/mineTrail.length)}, ${Math.ceil(31+64*j/mineTrail.length)}, ${Math.ceil(255*j/mineTrail.length)} )`; ctx.beginPath(); for (let i = 0; i < 6; i++) { ctx.lineTo(mineTrail[j].x - this.radius * Math.cos(vertAngle * i + radians), mineTrail[j].y - this.radius * Math.sin(vertAngle * i + radians)); } ctx.closePath(); ctx.stroke(); } */ } ctx.strokeStyle = 'white'; ctx.beginPath(); for (let i = 0; i < 6; i++) { ctx.lineTo(this.x - this.radius * Math.cos(vertAngle * i + radians), this.y - this.radius * Math.sin(vertAngle * i + radians)); } ctx.closePath(); ctx.stroke(); } setVelocity(params) { if (isValid(params.velX) && isValid(params.velY)) { this.angle = rad2deg(arctan(params.velX, params.velY)); this.speed = Math.sqrt(params.velX * params.velX + params.velY * params.velY); } else if (isValid(params.angle) && isValid(params.speed)) { this.angle = params.angle; this.speed = params.speed; } else throw ('setVelocity not set properly'); // console.log(params); } InCollision() { // Check if asteroid can be broken into smaller pieces if (this.level === 1) { asteroids.push(new Asteroid(this.x - this.collisionRadius, this.y - this.collisionRadius, 25, 2, 22)); asteroids.push(new Asteroid(this.x + this.collisionRadius, this.y + this.collisionRadius, 25, 2, 22)); } else if (this.level === 2) { asteroids.push(new Asteroid(this.x - this.collisionRadius, this.y - this.collisionRadius, 15, 3, 12)); asteroids.push(new Asteroid(this.x + this.collisionRadius, this.y + this.collisionRadius, 15, 3, 12)); } this.visible = false; } Speed() { return this.speed; }; Angle() { return this.angle % 360.0; } VelocityX() { return Math.cos(deg2rad(this.angle)) * this.speed; } VelocityY() { return Math.sin(deg2rad(this.angle)) * this.speed; } Mass(){ return this.radius; } } function hasParameter(getParameterName) { let getString = window.location.search; let regexPattern = new RegExp('(\\?|\\&)' + getParameterName + '(\\=|\\&|\\b)', 'i'); return (getString.search(regexPattern) >= 0); } function getParameterValue(getParameterName) { let getString = window.location.search; let regexPattern = new RegExp('(\\?|\\&)' + getParameterName + '\\=', 'i'); let returnString = ''; let parameterIndex = getString.search(regexPattern); if (parameterIndex >= 0) returnString = getString.substring(parameterIndex + 2 + getParameterName.length, getString.length); parameterIndex = returnString.indexOf('&'); if (parameterIndex > 0) returnString = returnString.substring(0, parameterIndex); return returnString.toLowerCase(); } function isNumeric(string){ if(string === ' '.repeat(string.length)){ return false; } return string - 0 === string * 1; } function TrailCollision(objectTop, objectBottom){ if(isValid(objectTop.x) && isValid(objectTop.y) && isValid(objectTop.collisionRadius) && isValid(objectBottom.x) && isValid(objectBottom.y) && isValid(objectBottom.collisionRadius)) if(CircleCollision(objectTop.x, objectTop.y, objectTop.collisionRadius, objectBottom.x, objectBottom.y, objectBottom.collisionRadius)) { return true; } if(isValid(objectTop.shadow)) { for(let i = 0; i0 && CircleCollision(objectTop.shadow[i].x, objectTop.shadow[i].y, objectTop.collisionRadius, objectBottom.x, objectBottom.y, objectBottom.collisionRadius)) { return true; } if(isValid(objectBottom.shadow)) { for(let j = 0; j0 && objectBottom.shadow[j].time>0 && CircleCollision(objectTop.shadow[i].x, objectTop.shadow[i].y, objectTop.collisionRadius, objectBottom.shadow[j].x, objectBottom.shadow[j].y, objectBottom.collisionRadius)) { return true; } } } } } if(isValid(objectBottom.shadow)) { for(let i = 0; i0 && CircleCollision(objectTop.x, objectTop.y, objectTop.collisionRadius, objectBottom.shadow[i].x, objectBottom.shadow[i].y, objectBottom.collisionRadius)) { return true; } } } return false; } function CircleCollision(p1x, p1y, r1, p2x, p2y, r2) { let radiusSum; let xDiff; let yDiff; radiusSum = r1 + r2; xDiff = p1x - p2x; yDiff = p1y - p2y; if (radiusSum > Math.sqrt((xDiff * xDiff) + (yDiff * yDiff))) { return true; } else { return false; } } // Detect if it is safe to spawn ship or asteroid // If the ship is moving at the end of a round, it's their own damn fault // If the ship dies, it is not fair play to spawn in the middle of an asteroid and lose all the remaining lives. // Detect if the ship is in an asteroid, return false. // Detect if the ship will be in an asteroid in a fair amount of time, return false. // Detect if the ship will be in the rectangle drawn by the asteroid during that future time, return false. // Otherwise, return true. function SafeToSpawn(object1, object2) { // Object 1 is usually the ship. It has position and radius. // Object 2 is usually the asteroid. It has position, radius, and velocity. if (TrailCollision(object1, object2)) return false; // ships and asteroids have speed object and angle object in common to determine velocity. // Object 2 will move its diameter (2×radius) at speed × diameter unsafely. let radians = deg2rad(object2.angle); let xDistance = object2.x + 4 * object2.radius * object2.speed * Math.cos(radians); let yDistance = object2.y + 4 * object2.radius * object2.speed * Math.sin(radians); if (CircleCollision(object1.x, object1.y, object1.radius, xDistance, yDistance, object2.radius)) return false; let forStep = (object2.speed < 3 ? 3 : object2.speed) * 5; let dx = (xDistance - object2.x) / forStep; let dy = (yDistance - object2.y) / forStep; for (let i = 1; i < forStep; i++) { if (CircleCollision(object1.x, object1.y, object1.radius, object2.x + dx * i, object2.y + dy * i, object2.radius)) return false; } return true; } // Handles drawing life ships on screen function DrawLifeShips() { let startX = canvasWidth-50; let startY = 10; let points = [[9, 9], [-9, 9]]; ctx.strokeStyle = 'white'; // Stroke color of ships if(lives>5) { ctx.font = '21px Arial'; ctx.fillText('+'+ (Math.floor((lives-1)/5)*5), startX - 5, startY + 11 ); startX -=30 } // Cycle through all live ships remaining for (let i = 0; i < (lives - 1)%5+1; i++) { // Start drawing ship ctx.beginPath(); // Move to origin point ctx.moveTo(startX, startY); // Cycle through all other points for (let j = 0; j < points.length; j++) { ctx.lineTo(startX + points[j][0], startY + points[j][1]); } // Draw from last point to 1st origin point ctx.closePath(); // Stroke the ship shape white ctx.stroke(); // Move next shape 30 pixels to the left startX -= 30; } // ctx.fillStyle = 'white'; // ctx.font = '21px Wingdings'; // ctx.fillText((ship.rapidFire ? String.fromCharCode(0xB5) : String.fromCharCode(0xB1)).padEnd(1 + mineCount, String.fromCharCode(0x4D)), startX - 16 * (2 + mineCount), startY + 11); ctx.fillStyle = 'white'; if (ship.shieldPower > 0 && ship.rapidFire && missileCount > 0 || ship.shieldPower > 0 && mineCount <= 0) { ctx.font = '21px Webdings'; ctx.fillText(String.fromCharCode(0x95), //0xF9 startX - 16 * (2 + (mineCount>0 ? mineCount : 0) + (missileCount > 0 ? missileCount : 0)), startY + 11 ); } else { ctx.font = '21px Wingdings'; ctx.fillText((ship.shieldPower > 0 ? String.fromCharCode(0x4D) : ship.rapidFire ? String.fromCharCode(0xB5) : String.fromCharCode(0xB1)), startX - 16 * (2 + (mineCount>0 ? mineCount : 0) + (missileCount > 0 ? missileCount : 0)), startY + 11 ); } ctx.font = '21px Wingdings'; for (let i = 0; i < mineCount; i++) { ctx.fillText(String.fromCharCode(0x4D), startX - 16 * ((mineCount>0 ? mineCount : 0) + (missileCount > 0 ? missileCount : 0) - i), startY + 11); } ctx.font = '21px Webdings'; for (let i = 0; i < missileCount; i++) { // ctx.fillText(String.fromCharCode(0x66), startX - 16 * (missileCount - i), startY + 11); ctx.fillText(String.fromCharCode(0x95), startX - 16 * ((missileCount > 0 ? missileCount : 0) - i), startY + 11); } } function Render() { // Check if the ship is moving forward ship.movingForward = (keys[87]); // Key W if (keys[68]) { // Key D // d key rotate right ship.Rotate(1); } if (keys[65]) { // Key A // a key rotate left ship.Rotate(-1); } ctx.clearRect(0, 0, canvasWidth, canvasHeight); if (keys[82] || ship.Speed() > 10.1 || SystemEnergy() > safetyBrakesAt) { // Key R // r key rotate and retro rockets // if the ship is pointed backwards, fire main rockets var angleOffset = (ship.angle - ship.Angle()) % 360; if (Math.abs(angleOffset) < 15.0 || Math.abs(angleOffset) > 345) ship.movingForward = (ship.Speed() > 0.1); if (angleOffset > 180 || angleOffset < 0 && angleOffset > -180) { ship.Rotate(1); } else { ship.Rotate(-1); } } // Display score ctx.fillStyle = 'white'; ctx.font = '21px Arial'; if( score > 0 ) ctx.fillText("SCORE : " + score.toString(), 20, 35); else ctx.fillText("CONTROLS : A W D S F R SPACE", 20, 35); // If no lives signal game over if (lives <= 0) { // HOMEWORK SOLUTION // If Game over remove event listeners to stop getting keyboard input document.body.removeEventListener("keydown", HandleKeyDown); document.body.removeEventListener("keyup", HandleKeyUp); ship.visible = false; ctx.fillStyle = 'white'; ctx.font = '50px Arial'; ctx.fillText("GAME OVER", canvasWidth / 2 - 150, canvasHeight / 2); } else if (!ship.visible) { // Check if it is safe to spawn var isSafeToSpawn = true; if (asteroids.length !== 0 && asteroids.length > 0) { for (let i = 0; i < asteroids.length; i++) { isSafeToSpawn = isSafeToSpawn && SafeToSpawn(ship, asteroids[i]); if(!isSafeToSpawn && Math.abs(asteroids[i].speed) < 0.001) { asteroids[i].speed = -0.1; break; } else if(!isSafeToSpawn && !isValid(ship.launchCodes) ) { ship.launchCodes = setTimeout(() => { for (let i = 0; i < asteroids.length; i++) { if(!SafeToSpawn(ship, asteroids[i])){ if(bullets.length 7 ) ship.Shields(true); } // Draw life ships DrawLifeShips(); // Check for collision of ship with asteroid if (ship.visible && asteroids.length !== 0) { for (let k = 0; k < asteroids.length; k++) { if (CircleCollision(ship.x,ship.y,ship.collisionRadius, asteroids[k].x,asteroids[k].y,asteroids[k].collisionRadius)) { // Check if the ship collided just after spawning: not in online version /* if ((new Date()).getTime() - ship.spawnClock.getTime() < 1000) { console.log('Asteroid ' + k + ' Position: X - ' + numberToString(asteroids[k].x) + ' Y - ' + numberToString(asteroids[k].y) + ' R - ' + numberToString(asteroids[k].radius) + ' S - ' + numberToString(asteroids[k].speed) + ' A - ' + numberToString(asteroids[k].angle) ); testDied('Unfair collision after spawning:' + (((new Date()).getTime() - ship.spawnClock.getTime()) / 1000)); } */ if (ship.shieldPower > 0) { let shipVelX = ship.velX; let shipVelY = ship.velY; // bounce the ship off of the asteroid Billiards(ship, asteroids[k]); ship.angle = ship.Angle()+180; // ship.setVelocity({speed:ship.Speed()*1.099,angle:ship.Angle()}); // shield power should go down in proportion to the magnitude of the impact ship.shieldPower -= 0.05 * Math.sqrt(Math.pow(ship.velX - shipVelX, 2) + Math.pow(ship.velY - shipVelY, 2)); ship.isInCollision = true; } else { ship.x = canvasWidth / 2; ship.y = canvasHeight / 2; ship.velX = 0; ship.velY = 0; lives -= 1; ship.visible = false; // Check if asteroid can be broken into smaller pieces if (asteroids[k].level === 1) { asteroids.push(new Asteroid(asteroids[k].x - 5, asteroids[k].y - 5, 25, 2, 22)); asteroids.push(new Asteroid(asteroids[k].x + 5, asteroids[k].y + 5, 25, 2, 22)); } else if (asteroids[k].level === 2) { asteroids.push(new Asteroid(asteroids[k].x - 5, asteroids[k].y - 5, 15, 3, 12)); asteroids.push(new Asteroid(asteroids[k].x + 5, asteroids[k].y + 5, 15, 3, 12)); } asteroids.splice(k, 1); } } } } // Check for collision with bullet and asteroid if (asteroids.length !== 0 && bullets.length != 0) { loop1: for (let l = asteroids.length-1; l >= 0; l--) { for (let m = bullets.length-1; m >= 0; m--) { if ((!isValid(bullets[m].CanCollide) || bullets[m].CanCollide) && TrailCollision(bullets[m], asteroids[l])) { asteroids[l].InCollision(); asteroids.splice(l, 1); bullets[m].InCollision(); score += 20; // Used to break out of loops because splicing arrays // you are looping through will break otherwise // break loop1; break; } // if the bullet falls off the edge, garbage collect if (!(bullets[m].visible)) { bullets.splice(m, 1); // break; } } } } // Check for collision between two asteroids ?!? if (ship.visible) { ship.Update(); ship.Draw(); } if (bullets.length !== 0) { for (let i = 0; i < bullets.length; i++) { bullets[i].Update(); bullets[i].Draw(); } } if (asteroids.length !== 0) { for (let j = 0; j < asteroids.length; j++) { asteroids[j].Update(); // Pass j so we can track which asteroid points // to store asteroids[j].Draw(j); } } // HOMEWORK SOLUTION // Updates the high score using local storage, unless cheat-code are in use if(window.location.search=='') { highScore = Math.max(score, highScore); localStorage.setItem(localStorageName, highScore); localStorage.setItem(localStorageNoCheat, true); } ctx.font = '21px Arial'; ctx.fillText("HIGH SCORE : " + highScore.toString(), 20, 70); // Display physics, when shields on if(ship.shieldPower > 0 || goalOfTheGame == 'zeromomentum') { ctx.fillStyle = 'white'; ctx.font = '21px Arial'; ctx.fillText("MOMENTUM : " + numberToString(SystemMomentum()), 20, 105); ctx.fillText("ENERGY : " + numberToString(SystemEnergy()), 20, 140); // ctx.fillText("H,W: " + screen.height +','+ screen.width, 20, 175); } if(SystemEnergy() > 1000000000) { /* console.log('ship energy is '+ Math.sqrt( Math.pow(Math.pow(ship.VelocityX(),2)*ship.Mass()/2,2) + Math.pow(Math.pow(Math.pow(ship.VelocityX(),2)*ship.Mass()/2,2) ,2)) +'. Speed: '+ ship.Speed() +' Radius: '+ ship.radius); */ for(let i=0;i= -radius && yCoord >= -radius && xCoord <= canvasWidth + radius && yCoord <= canvasHeight + radius) trail.push({x: xCoord, y: yCoord}); } } return trail; } function Billiards(object1, object2) { console.log('let testAsteroid = new Asteroid('+ object2.x +', '+ object2.y +', '+ object2.radius +', '+ object2.level +', '+ object2.collisionRadius +');'); console.log('testAsteroid.angle = '+ object2.angle +';'); console.log('testAsteroid.speed = '+ object2.speed +';'); console.log('ship.x = '+ object1.x +'; '+ 'ship.y = '+ object1.y +'; '); console.log('ship.SetVelocity( {angle:'+ object1.Angle() +', '+ 'speed: '+ object1.Speed() +'});'); // Check if objects materialised inside of each other // bounce objects respecting momentum // let radians = deg2rad(object2.angle); // these equations work where object1 is moving and object2 is stationary // vF1 = (m1 - m2)·vI1/(m1 + m2) // vF2 = 2·m1·vI1/(m1 + m2) let oldVelocityXForObject2 = object2.VelocityX(); let oldVelocityYForObject2 = object2.VelocityY(); // The observer moves with object2 until the collision then continues let oldVelocityXForObject1 = object1.VelocityX(); let oldVelocityYForObject1 = object1.VelocityY(); // p = v·m // let momentumX = speedX * object2.radius; // let momentumY = speedY * object2.radius; // e = ½m·v^2 // let energyX = speedX * speedX * object2.radius / 2; // let energyY = speedY * speedY * object2.radius / 2; // vF2 = 2·m1·vI1/(m1 + m2) let newVelocityXForObject2 = vF2(object1.Mass(), object2.Mass(), oldVelocityXForObject1, oldVelocityXForObject2); let newVelocityYForObject2 = vF2(object1.Mass(), object2.Mass(), oldVelocityYForObject1, oldVelocityYForObject2); // radians = deg2rad(object1.angle); // vF1 = (m1 - m2)·vI1/(m1 + m2) let newVelocityXForObject1 = vF1(object1.Mass(), object2.Mass(), oldVelocityXForObject1, oldVelocityXForObject2); let newVelocityYForObject1 = vF1(object1.Mass(), object2.Mass(), oldVelocityYForObject1, oldVelocityYForObject2); let newObject1 = {x:object1.x + newVelocityXForObject1,y:object1.y + newVelocityYForObject1,r:object1.collisionRadius}; let newObject2 = {x:object2.x + newVelocityXForObject2,y:object2.y + newVelocityYForObject2,r:object2.collisionRadius}; let isInBowl = isInFishBowl({x:object1.x, y:object1.y, oldVelX:oldVelocityXForObject1, oldVelY:oldVelocityYForObject1, newVelX:newVelocityXForObject1, newVelY:newVelocityYForObject1, r:object1.collisionRadius}, {x:object2.x, y:object2.y, oldVelX:oldVelocityXForObject2, oldVelY:oldVelocityYForObject2, newVelX:newVelocityXForObject2, newVelY:newVelocityYForObject2, r:object2.collisionRadius}); // if objects remain in collision after change in velocity, do not change velocity // May create a fish bowl if (!CircleCollision(newObject1.x, newObject1.y, newObject1.r, newObject2.x, newObject2.y, newObject2.r)/*|| !CircleCollision(object1.x + oldVelocityXForObject1, object1.y + oldVelocityYForObject1, object1.collisionRadius, object2.x + oldVelocityXForObject2, object2.y + oldVelocityYForObject2, object2.collisionRadius)*/) { object1.setVelocity({ velX: newVelocityXForObject1, velY: newVelocityYForObject1 }); object2.setVelocity({ velX: newVelocityXForObject2, velY: newVelocityYForObject2 }); } else if (!CircleCollision(newObject1.x-object1.VelocityX(),newObject1.y-object1.VelocityY(),newObject1.r, newObject2.x-object2.VelocityX(),newObject2.y-object2.VelocityY(),newObject2.r)/*|| !CircleCollision(object1.x + oldVelocityXForObject1, object1.y + oldVelocityYForObject1, object1.collisionRadius, object2.x + oldVelocityXForObject2, object2.y + oldVelocityYForObject2, object2.collisionRadius)*/) { object1.setVelocity({ velX: newVelocityXForObject1, velY: newVelocityYForObject1 }); object2.setVelocity({ velX: newVelocityXForObject2, velY: newVelocityYForObject2 }); } } // vF1 = (m1 - m2)·vI1/(m1 + m2) function vF1(m1, m2, vI1, vI2) { return (m1 - m2) * (vI1 - vI2) / (m1 + m2) + vI2; } // vF2 - 2·m1·vI1/(m1 + m2) function vF2(m1, m2, vI1, vI2) { return 2 * m1 * (vI1 - vI2) / (m1 + m2) + vI2; } function isInFishBowl(object1,object2){ if(!CircleCollision(object1.x, object1.y, object1.r, object2.x, object2.y, object2.r)) return false; if(!CircleCollision(object1.x + object1.newVelX, object1.y + object1.newVelY, object1.r, object2.x + object2.newVelX, object2.y + object2.newVelY, object2.r)) return false; if(!CircleCollision(object1.x + object1.oldVelX, object1.y + object1.oldVelY, object1.r, object2.x + object2.oldVelX, object2.y + object2.oldVelY, object2.r)) return false; if(!CircleCollision(object1.x - object1.oldVelX + object1.newVelY, object1.y - object1.oldVelY + object1.newVelY, object1.r, object2.x - object2.oldVelX + object2.newVelY, object2.y - object2.oldVelY + object2.newVelY, object2.r)) return false; return true; } function SystemMomentum(){ // p = v·m let momentumX = ship ? ship.VelocityX() * ship.Mass() : 0; let momentumY = ship ? ship.VelocityY() * ship.Mass() : 0; for(let i=0;i= 0; i--) { asteroids.splice(i, 1); } asteroids.push(testAsteroid); setTimeout(() => { ship.angle += 180; if (Math.abs(asteroids[0].angle % 360 - 210) > 0.001) testDied('asteroid direction should be 210. shows ' + asteroids[0].angle); if (Math.abs(ship.angle % 360 - 30) > 0.001) testDied('ship direction should be 30. shows ' + (ship.angle % 360)); if (Math.abs(asteroids[0].speed - 1) > 0.01) testDied('asteroid speed should be 1. shows ' + asteroids[0].speed); if (Math.abs(ship.velX + Math.sqrt(0.75)) > 0.001) testDied('ship horizontal velocity should be negRoot2. shows ' + (ship.velX < 0 ? 'neg' : '') + 'root ' + Math.pow(ship.velX, 2) + ''); if (Math.abs(ship.velY + 0.5) > 0.001) testDied('ship vertical velocity should be negRoot2. shows ' + (ship.velY < 0 ? 'neg' : '') + 'root ' + Math.pow(ship.velY, 2) + ''); asteroids.splice(0, 1); for(let i=0;i<8;i++){ asteroids.push(new Asteroid()); } }, 5000); */ /* targetPosition targetPosition.angle 265 targetPosition.collisionRadius 46 targetPosition.level 1 targetPosition.radius 50 targetPosition.speed 4.5 targetPosition.x 834.6368783301848 targetPosition.y 714.1369908686975 this this.angle 516.9580319484683 this.collisionRadius 3 this.length 8 this.speed 5.3999999999999995 this.target this.target.asteroid this.turningSpeed 9.364123593763534 this.velX 0.42123342110640866 this.velY 0.1283174822947487 this.visible true this.width 3 this.x 144.5869186025536 this.y 299.2720186682264 */ /* targetPosition targetPosition.angle 288 targetPosition.collisionRadius 12 targetPosition.level 3 targetPosition.radius 15 targetPosition.speed 3.5 targetPosition.x 116.95025659810275 targetPosition.y 515.2781315780203 this this.angle 872.5021848875925 this.collisionRadius 3 this.length 8 this.speed 4.2 this.target this.target.asteroid this.turningSpeed 8.809014250079281 this.velX 0.00035670879035128 this.velY -0.00038382190279197996 this.visible true this.width 3 this.x 816.0179040858244 this.y 179.99223673094633 ship.x 435.0952198062165 ship.y 689.954752907529 */ /* let testAsteroid = new Asteroid(116.95025659810275, 515.2781315780203, 15, 3, 12); testAsteroid.angle = 288; testAsteroid.speed = 3.5; for (let i = asteroids.length - 1; i >= 0; i--) { asteroids.splice(i, 1); } asteroids.push(testAsteroid); let testMissile = new YakaArrow(144.5869186025536, 299.2720186682264, 516.9580319484683, 0); testMissile.target = testAsteroid; testMissile.angle = 872.5021848875925 testMissile.collisionRadius = 3; testMissile.speed = 4.2; testMissile.turningSpeed = 8.809014250079281; testMissile.velX = 0.00035670879035128; testMissile.velY = -0.00038382190279197996; testMissile.x = 816.0179040858244; testMissile.y = 179.99223673094633; */ /* let testAsteroid = new Asteroid(350.6055694967736, 96.53264876135762, 15, 3, 12); testAsteroid.angle = 326; testAsteroid.speed = 3.5; for (let i = asteroids.length - 1; i >= 0; i--) { asteroids.splice(i, 1); } asteroids.push(testAsteroid); let testMissile = new YakaArrow(144.5869186025536, 299.2720186682264, 516.9580319484683, 0); testMissile.target = testAsteroid; testMissile.angle = 143.61005184111795; testMissile.speed = 4.2; testMissile.turningSpeed = 8.910411932648113; testMissile.velX = 0.057736475174477775; testMissile.velY = -0.30557039529353025; testMissile.x = 340.8884847815862; testMissile.y = 31.196239680313628; */ /* let testAsteroid = new Asteroid(516.4437880062636, 175.16721703285234, 15, 3, 12); testAsteroid.angle = 79.99999999999999 testAsteroid.speed = 3.3877342611124606 for (let i = asteroids.length - 1; i >= 0; i--) { asteroids.splice(i, 1); } asteroids.push(testAsteroid); let testMissile = new YakaArrow(144.5869186025536, 299.2720186682264, 516.9580319484683, 0); testMissile.target = testAsteroid; testMissile.angle = 595.0055652361378 testMissile.speed = 4.2 testMissile.turningSpeed = 8.439601845453872 testMissile.velX = 0.6358747917681413 testMissile.velY = 1.957021378329555 testMissile.x = 530.6902822357835 testMissile.y = 674.9757260254869 */ /* ship.x = -9.299073047188088; ship.y = 202.97750395812227; ship.SetVelocity( {angle:59.44302366467865, speed: 3.34568035307496}); let testMissile = new YakaArrow(144.5869186025536, 299.2720186682264, 516.9580319484683, 0); testMissile.angle = -267.51498132641507; testMissile.speed = 4.2; testMissile.turningSpeed = 11.09840184741671; testMissile.velX = -1.450322136459083; testMissile.velY = -2.456568989314432; testMissile.x = 1185.1796428486598; testMissile.y = 522.768875311426; testMissile.target = ship; */ /* let testAsteroid = new Asteroid(1170.8230723289157, 345.45990335286575, 25, 2, 22); testAsteroid.angle = 85; testAsteroid.speed = 0.8750000000000002; for (let i = asteroids.length - 1; i >= 0; i--) { asteroids.splice(i, 1); } asteroids.push(testAsteroid); let testMissile = new YakaArrow(144.5869186025536, 299.2720186682264, 516.9580319484683, 0); testMissile.angle = 239.4430236646787; testMissile.speed = 4.2; testMissile.turningSpeed = 11.09840184741671; testMissile.velX = -1.7313029128706272; testMissile.velY = -2.932496815673965; testMissile.x = 1109.3988919962617; testMissile.y = 411.3387519633911; testMissile.target = testAsteroid; let testAsteroid = new Asteroid(356.0769941165759, 63.20395386143971, 25, 2, 22); testAsteroid.angle = 164; testAsteroid.speed = 4; for (let i = asteroids.length - 1; i >= 0; i--) { asteroids.splice(i, 1); } asteroids.push(testAsteroid); let testMissile = new YakaArrow(144.5869186025536, 299.2720186682264, 516.9580319484683, 0); testMissile.target = testAsteroid; testMissile.angle = 248; testMissile.speed = 4.8; testMissile.turningSpeed = 7.379348270485023; testMissile.velX = -1.4925936004639873; testMissile.velY = -3.694298798001709; testMissile.x = 670.5196157211653; testMissile.y = 646.4297820806269; let testAsteroid = new Asteroid(398.221220623831, 689.1759385056071, 15, 3, 12); testAsteroid.angle = -89.03583684349245; testAsteroid.speed = 2.582771829765407; for (let i = asteroids.length - 1; i >= 0; i--) { asteroids.splice(i, 1); } asteroids.push(testAsteroid); let testMissile = new YakaArrow(144.5869186025536, 299.2720186682264, 516.9580319484683, 0); testMissile.target = testAsteroid; testMissile.angle = 317.77210244293406; testMissile.speed = 4.2; testMissile.turningSpeed = 8.403838664916304; testMissile.velX = 3.6750772004483725; testMissile.velY = -3.335618594263875; testMissile.x = 405.26172947216503; testMissile.y = 628.7949504555223; */ /* asteroids.js:734 let testAsteroid = new Asteroid(589.9729744868547, 219.84757735436042, 15, 3, 12); asteroids.js:739 testAsteroid.angle = 156.00000000000003; asteroids.js:740 testAsteroid.speed = 14.804992014338094; asteroids.js:748 testMissile.angle = -24.60801734450908; asteroids.js:749 testMissile.speed = 20; asteroids.js:750 testMissile.turningSpeed = 1.971619300951735; asteroids.js:751 testMissile.velX = 0; asteroids.js:752 testMissile.velY = 0; asteroids.js:753 testMissile.x = 1007.0924586460966; asteroids.js:754 testMissile.y = 24.652963750773957; asteroids.js:734 let testAsteroid = new Asteroid(589.9729744868547, 219.84757735436042, 15, 3, 12); asteroids.js:739 testAsteroid.angle = 156.00000000000003; asteroids.js:740 testAsteroid.speed = 14.804992014338094; asteroids.js:748 testMissile.angle = 2.053378815028699; asteroids.js:749 testMissile.speed = 24; asteroids.js:750 testMissile.turningSpeed = 1.6754102276267941; asteroids.js:751 testMissile.velX = -0.0049148463169132895; asteroids.js:752 testMissile.velY = 0.00034367953413405014; asteroids.js:753 testMissile.x = 1032.2097629046366; asteroids.js:754 testMissile.y = 368.40439391762084; asteroids.js:734 let testAsteroid = new Asteroid(589.9729744868547, 219.84757735436042, 15, 3, 12); asteroids.js:739 testAsteroid.angle = 156.00000000000003; asteroids.js:740 testAsteroid.speed = 14.804992014338094; asteroids.js:748 testMissile.angle = 161.49076216962794; asteroids.js:749 testMissile.speed = 24; asteroids.js:750 testMissile.turningSpeed = 2.478425653056055; asteroids.js:751 testMissile.velX = 0.007952292300698287; asteroids.js:752 testMissile.velY = 0.015586317084922524; asteroids.js:753 testMissile.x = 409.23882122850915; asteroids.js:754 testMissile.y = 293.97483934352766; */ let testAsteroid = new Asteroid(661.2197838796006, 48.44402654564152, 15, 3, 12); testAsteroid.angle = 319; testAsteroid.speed = 4; for (let i = asteroids.length - 1; i >= 0; i--) { asteroids.splice(i, 1); } asteroids.push(testAsteroid); let testMissile = new YakaArrow(144.5869186025536, 299.2720186682264, 516.9580319484683, 0); testMissile.angle = -33.439994260346396; testMissile.speed = 4.8; testMissile.turningSpeed = 7.060129163285517; testMissile.velX = -8.232594627144657; testMissile.velY = 5.467400826564827; testMissile.x = 170.30740061984363; testMissile.y = 6.709619756665916; testMissile.Draw = function() { let strokeWidth = ctx.lineWidth; ctx.strokeStyle = 'pink'; ctx.lineWidth = this.width; ctx.beginPath(); ctx.moveTo(this.x, this.y); let radians = deg2rad(this.angle); ctx.lineTo(this.x - this.length * Math.cos(radians), this.y - this.length * Math.sin(radians)); ctx.closePath(); ctx.stroke(); ctx.lineWidth = strokeWidth; let traceText = "A: " + (' '+Math.floor(this.angle%360/15+0.5)*15).slice(-4,15); // Fetch target object if (isValid(this.target) && isValid(this.target.asteroid) && isValid(this.target.asteroid.x) && this.target.asteroid.visible) { traceText += (' '+(Math.floor(this.PickBestTurn(this.target.asteroid)+0.5))).slice(-4,15); // Angle to target traceText += (' '+(Math.floor( rad2deg(arctan(this.x - this.target.asteroid.x,this.y - this.target.asteroid.y)) %360/15+0.5)*15)).slice(-4,15); // Distance to target traceText += (' '+(Math.floor( Math.sqrt(Math.pow(this.x - this.target.asteroid.x,2)+Math.pow(this.y - this.target.asteroid.y,2)) +0.5))).slice(-6,15); } else if (isValid(this.target) && isValid(this.target.return)) { traceText += (' '+(Math.floor(this.PickBestTurn(this.target.return)+0.5))).slice(-4,15); // Angle to target traceText += (' '+(Math.floor( rad2deg(arctan(this.x - this.target.return.x,this.y - this.target.return.y)) %360/15+0.5)*15)).slice(-4,15); // Distance to target traceText += (' '+(Math.floor( Math.sqrt(Math.pow(this.x - this.target.return.x,2)+Math.pow(this.y - this.target.return.y,2)) +0.5))).slice(-6,15); } ctx.strokeStyle = 'white'; ctx.fillStyle = 'pink'; ctx.font = '21px Consolas'; ctx.fillText(traceText, 20, 175); ctx.fillStyle = 'white'; }; testMissile.LogBlast = function() { if (isValid(this.target) && isValid(this.target.asteroid) && isValid(this.target.asteroid.x) && this.target.asteroid.visible) { console.log('let testAsteroid = new Asteroid('+ this.target.asteroid.x +', '+ this.target.asteroid.y +', '+ this.target.asteroid.radius +', '+ this.target.asteroid.level +', '+ this.target.asteroid.collisionRadius +');'); console.log('testAsteroid.angle = '+ this.target.asteroid.angle +';'); console.log('testAsteroid.speed = '+ this.target.asteroid.speed +';'); } else if (isValid(this.target) && isValid(this.target.return)) { console.log('ship.x = '+ this.target.return.x +'; '+ 'ship.y = '+ this.target.return.y +'; '); console.log('ship.SetVelocity( {angle:'+ this.target.return.Angle() +', '+ 'speed: '+ this.target.return.Speed() +'});'); } console.log('testMissile.angle = '+ this.angle +';'); //872.5021848875925 console.log('testMissile.speed = '+ this.speed +';'); //4.2; console.log('testMissile.turningSpeed = '+ this.turningSpeed +';'); //8.809014250079281; console.log('testMissile.velX = '+ this.velX +';'); //0.00035670879035128; console.log('testMissile.velY = '+ this.velY +';'); //-0.00038382190279197996; console.log('testMissile.x = '+ this.x +';'); //816.0179040858244; console.log('testMissile.y = '+ this.y +';'); //179.99223673094633; return 'complete'; }; /* // Display score if( score > 0 ) ctx.fillText("SCORE : " + score.toString(), 20, 35); else ctx.fillText("CONTROLS : A W D S F SPACE", 20, 35); */ bullets.push(testMissile); ship.x = 435.0952198062165; ship.y = 689.954752907529; } function testOrDie() { try{ if (deg2rad(0) != 0) testDied('0 degrees should be 0 radians'); if (deg2rad(180) != Math.PI) testDied('180 degrees should be pi radians'); if (deg2rad(360) != 2 * Math.PI) testDied('360 degrees should be 2pi radians'); if (deg2rad(90) != Math.PI / 2) testDied('90 degrees should be ½pi radians'); if (deg2rad(270) != Math.PI * 3 / 2) testDied('270 degrees should be 1½pi radians'); if (rad2deg(0) != 0) testDied('0 radians should be 0 degrees'); if (rad2deg(Math.PI) != 180) testDied('pi radians should be 180 degrees'); if (rad2deg(2 * Math.PI) != 360) testDied('2pi radians should be 0 degrees'); if (rad2deg(Math.PI / 2) != 90) testDied('½pi radians should be 90 degrees'); if (rad2deg(Math.PI * 3 / 2) != 270) testDied('1½pi radians should be 270 degrees'); if (Math.abs(rad2deg(arctan(1, 0)) - 0) > 0.001) testDied('arctan of 0/1 should be 0 degrees'); if (Math.abs(rad2deg(arctan(0.00, 0.00)) - 0) > 0.001) testDied('arctan of 0/0 should be 0 degrees. Returned ' + rad2deg(arctan(0.00, 0.00))); if (Math.abs(rad2deg(arctan(0, 1)) - 90) > 0.001) testDied('arctan of 1/0 should be 90 degrees'); if (Math.abs(rad2deg(arctan(-1, 0)) - 180) > 0.001) testDied('arctan of 0/-1 should be 180 degrees'); if (Math.abs(rad2deg(arctan(0, -1)) - 270) > 0.001) testDied('arctan of -1/0 should be 270 degrees'); if (Math.abs(rad2deg(arctan(Math.sqrt(.75), 0.5)) - 30) > 0.001) testDied('arctan of ½/v¾ should be 30 degrees'); if (Math.abs(rad2deg(arctan(1, 1)) - 45) > 0.001) testDied('arctan of 1/1 should be 45 degrees'); if (Math.abs(rad2deg(arctan(0.5, Math.sqrt(.75))) - 60) > 0.001) testDied('arctan of v¾/½ should be 60 degrees'); if (Math.abs(rad2deg(arctan(-1, 1)) - 135) > 0.001) testDied('arctan of 1/-1 should be 135 degrees'); for(let d=-89; d<=270; d++){ if (Math.abs(rad2deg(arctan(Math.cos(deg2rad(d)), Math.sin(deg2rad(d)))) - d) > 0.001) testDied('arctan should be '+ d +' degrees'); } if (Math.abs(vF1(1, 1, 1, 0)) > 0.001) testDied('velocity of test1 should be 0'); if (Math.abs(vF2(1, 1, 1, 0) - 1) > 0.001) testDied('velocity of test2 should be 1'); if (Math.abs(vF1(1, 1, 1, -1) + 1) > 0.001) testDied('velocity of test1 should be -1. was ' + vF1(1, 1, 1, -1)); if (Math.abs(vF2(1, 1, 1, -1) - 1) > 0.001) testDied('velocity of test2 should be 1. was ' + vF2(1, 1, 1, -1)); if (Math.abs(vF1(300, 400, 10, 0) + 1.4285714285714286) > 0.001) testDied('velocity of test3 should be -1.43. was ' + vF1(300, 400, 10, 0)); if (Math.abs(vF2(300, 400, 10, 0) - 8.571428571428571) > 0.001) testDied('velocity of test4 should be 8.57. was ' + vF2(300, 400, 10, 0)); if (Math.abs(vF1(400, 300, 10, 0) - 1.4285714285714286) > 0.001) testDied('velocity of test5 should be 1.43. was ' + vF1(400, 300, 10, 0)); if (Math.abs(vF2(400, 300, 10, 0) - 11.428571428571429) > 0.001) testDied('velocity of test6 should be 11.4. was ' + vF2(400, 300, 10, 0)); if (Math.abs(vF1(3, 5, 10, 0) + 2.5) > 0.001) testDied('velocity of test7 should be -2.5. was ' + vF1(3, 5, 10, 0)); if (Math.abs(vF2(3, 5, 10, 0) - 7.5) > 0.001) testDied('velocity of test8 should be 7.5. was ' + vF2(3, 5, 10, 0)); if (Math.abs(vF1(3, 5, 4, -6) + 8.5) > 0.001) testDied('velocity of test9 should be -8.5. was ' + vF1(3, 5, 4, -6)); if (Math.abs(vF2(3, 5, 4, -6) - 1.5) > 0.001) testDied('velocity of test10 should be 1.5. was ' + vF2(3, 5, 4, -6)); } catch (e) { testDied(e); } let testShip = new Ship(); let testAsteroid = new Asteroid(); testShip.x = 700.00; testShip.y = 350.00; testAsteroid.x = 700.00; testAsteroid.y = 350.00; let x = testShip.x.toString() + '.00'; let y = testShip.y.toString() + '.00'; let r = testShip.radius.toString() + '.00'; let a = testShip.angle.toString() + '.00'; console.log('Ship Position: X - ' + x.substring(0, x.indexOf('.') + 3) + ' Y - ' + y.substring(0, y.indexOf('.') + 3) + ' R - ' + r.substring(0, r.indexOf('.') + 3)); x = testAsteroid.x.toString() + '.00'; y = testAsteroid.y.toString() + '.00'; r = testAsteroid.radius.toString() + '.00'; // console.log('Asteroid Position: X - '+ x.substring(0,x.indexOf('.')+3) + ' Y - ' + y.substring(0,y.indexOf('.')+3) + ' R - ' + r.substring(0,r.indexOf('.')+3)); if (!CircleCollision(testShip.x, testShip.y, testShip.radius, testAsteroid.x, testAsteroid.y, testAsteroid.radius)) { testDied('should be in collision'); } if (!TrailCollision(testShip,testAsteroid)) { testDied('should be in collision'); } // console.log('in collision'); if (SafeToSpawn(testShip, testAsteroid)) { testDied('should not be safe to spawn'); } let asteroidParameters = {velX: testAsteroid.VelocityX(), velY: testAsteroid.VelocityY(), angle: testAsteroid.angle, speed: testAsteroid.speed}; if(testAsteroid.Angle()!= asteroidParameters.angle) { testDied('angles should be the same. Was '+ testAsteroid.Angle() +' and '+ asteroidParameters.angle); } if(testAsteroid.Angle()!= asteroidParameters.angle) { testDied('angles should be the same. Was '+ testAsteroid.Angle() +' and '+ asteroidParameters.angle); } if(testAsteroid.Angle()!= asteroidParameters.angle) { testDied('angles should be the same. Was '+ testAsteroid.Angle() +' and '+ asteroidParameters.angle); } if(testAsteroid.Angle()!= asteroidParameters.angle) { testDied('angles should be the same. Was '+ testAsteroid.Angle() +' and '+ asteroidParameters.angle); } testAsteroid.x = 990.44; testAsteroid.y = 483.70; testAsteroid.radius = 25; testAsteroid.speed = 3; testAsteroid.angle = 204.71831940251352185645676953036; x = testAsteroid.x.toString() + '.00'; y = testAsteroid.y.toString() + '.00'; r = testAsteroid.radius.toString() + '.00'; a = testAsteroid.angle.toString() + '.00'; let radians = deg2rad(testAsteroid.angle); let xPrime = testAsteroid.x + 4 * testAsteroid.radius * testAsteroid.speed * Math.cos(radians); let yPrime = testAsteroid.y + 4 * testAsteroid.radius * testAsteroid.speed * Math.sin(radians); console.log('Asteroid Position: X - ' + x.substring(0, x.indexOf('.') + 3) + ' Y - ' + y.substring(0, y.indexOf('.') + 3) + ' R - ' + r.substring(0, r.indexOf('.') + 3) + ' A - ' + a.substring(0, a.indexOf('.') + 3)); if (CircleCollision(testShip.x, testShip.y, testShip.radius, testAsteroid.x, testAsteroid.y, testAsteroid.radius)) { testDied('should not be in collision'); } if (TrailCollision(testShip,testAsteroid)) { testDied('should not be in collision'); } x = xPrime.toString() + '.00'; y = yPrime.toString() + '.00'; console.log('Asteroid imminent Position: X\'- ' + x.substring(0, x.indexOf('.') + 3) + ' Y\'- ' + y.substring(0, y.indexOf('.') + 3) + ' R - ' + r.substring(0, r.indexOf('.') + 3) + ' A - ' + a.substring(0, a.indexOf('.') + 3)); if (!CircleCollision(testShip.x, testShip.y, testShip.radius, xPrime, yPrime, testAsteroid.radius)) { testDied('should be in imminent collision'); } if (SafeToSpawn(testShip, testAsteroid)) { testDied('should not be safe to spawn'); } testAsteroid.radius = 50; if (SafeToSpawn(testShip, testAsteroid)) { testDied('should not be safe to spawn'); } testAsteroid.x = 753.74; testAsteroid.y = 276.76; testAsteroid.radius = 50; testAsteroid.speed = 3; testAsteroid.angle = 126.26943543763827454256791233573; x = testAsteroid.x.toString() + '.00'; y = testAsteroid.y.toString() + '.00'; r = testAsteroid.radius.toString() + '.00'; a = testAsteroid.angle.toString() + '.00'; radians = deg2rad(testAsteroid.angle); xPrime = testAsteroid.x + 1 * testAsteroid.radius * testAsteroid.speed * Math.cos(radians); yPrime = testAsteroid.y + 1 * testAsteroid.radius * testAsteroid.speed * Math.sin(radians); console.log('Asteroid Position: X - ' + x.substring(0, x.indexOf('.') + 3) + ' Y - ' + y.substring(0, y.indexOf('.') + 3) + ' R - ' + r.substring(0, r.indexOf('.') + 3) + ' A - ' + a.substring(0, a.indexOf('.') + 3)); if (CircleCollision(testShip.x, testShip.y, testShip.radius, testAsteroid.x, testAsteroid.y, testAsteroid.radius)) { testDied('should not be in collision'); } if (TrailCollision(testShip,testAsteroid)) { testDied('should not be in collision'); } x = xPrime.toString() + '.00'; y = yPrime.toString() + '.00'; console.log('Asteroid ¼ imminent Position: X\'- ' + x.substring(0, x.indexOf('.') + 3) + ' Y\'- ' + y.substring(0, y.indexOf('.') + 3) + ' R - ' + r.substring(0, r.indexOf('.') + 3) + ' A - ' + a.substring(0, a.indexOf('.') + 3)); if (!CircleCollision(testShip.x, testShip.y, testShip.radius, xPrime, yPrime, testAsteroid.radius)) { testDied('should be in ¼ imminent collision'); } if (SafeToSpawn(testShip, testAsteroid)) { testDied('should not be safe to spawn'); } testAsteroid.x = 749.99; testAsteroid.y = 281.35; testAsteroid.radius = 50; testAsteroid.speed = 3; testAsteroid.angle = 146; x = testAsteroid.x.toString() + '.00'; y = testAsteroid.y.toString() + '.00'; r = testAsteroid.radius.toString() + '.00'; a = testAsteroid.angle.toString() + '.00'; radians = deg2rad(testAsteroid.angle); xPrime = testAsteroid.x + 1 * testAsteroid.radius * testAsteroid.speed * Math.cos(radians); yPrime = testAsteroid.y + 1 * testAsteroid.radius * testAsteroid.speed * Math.sin(radians); console.log('Asteroid Position: X - ' + x.substring(0, x.indexOf('.') + 3) + ' Y - ' + y.substring(0, y.indexOf('.') + 3) + ' R - ' + r.substring(0, r.indexOf('.') + 3) + ' A - ' + a.substring(0, a.indexOf('.') + 3)); if (CircleCollision(testShip.x, testShip.y, testShip.radius, testAsteroid.x, testAsteroid.y, testAsteroid.radius)) { testDied('should not be in collision'); } if (TrailCollision(testShip,testAsteroid)) { testDied('should not be in collision'); } x = xPrime.toString() + '.00'; y = yPrime.toString() + '.00'; console.log('Asteroid ¼ imminent Position: X\'- ' + x.substring(0, x.indexOf('.') + 3) + ' Y\'- ' + y.substring(0, y.indexOf('.') + 3) + ' R - ' + r.substring(0, r.indexOf('.') + 3) + ' A - ' + a.substring(0, a.indexOf('.') + 3)); // if(!CircleCollision(testShip.x,testShip.y,testShip.radius, // xPrime,yPrime,testAsteroid.radius)) { // testDied('should be in ¼ imminent collision'); // } if (SafeToSpawn(testShip, testAsteroid)) { testDied('should not be safe to spawn'); } // test for high speed collision testShip.collisionRadius = 15; testShip.setVelocity({ velX: 100, velY: 0 }); testShip.shadow = []; testAsteroid.collisionRadius = 15; testAsteroid.setVelocity({ speed: 100, angle: 180 }); testAsteroid.shadow = []; if (TrailCollision(testShip,testAsteroid)) { testDied('should not be in collision'); } if (Math.abs(testShip.Speed() - 100) > 0.001) testDied('ship should have a speed of 100. shows ' + testShip.Speed()); if (Math.abs(testAsteroid.Speed() - 100) > 0.001) testDied('asteroid should have a speed of 100. shows ' + testAsteroid.Speed()); if (testShip.VelocityX() != 100) testDied('ship should have an horizontal velocity of 100. shows ' + testShip.VelocityX()); if (testShip.VelocityY() != 0) testDied('ship should have an vertical velocity of 0. shows ' + testShip.VelocityY()); if (testAsteroid.VelocityX() != -100) testDied('asteroid should have an horizontal velocity of -100. shows ' + testAsteroid.VelocityX()); if (Math.abs(testAsteroid.VelocityY() - 0) > 0.001) testDied('asteroid should have an vertical velocity of 0. shows ' + testAsteroid.VelocityY()); testShip.shadow = [{x: 300, y: 350, time: -1}]; testAsteroid.shadow = [{x: 300, y: 350, time: -1}]; if (TrailCollision(testShip,testAsteroid)) { testDied('should not be in collision'); } testShip.shadow = [{x: 300, y: 350, time: -0.11},{x: 350, y: 400, time: 0.11},{x: 400, y: 450, time: 0.89}]; testAsteroid.shadow = [{x: 300, y: 350, time: 0.11}]; if (TrailCollision(testShip,testAsteroid)) { testDied('should not be in collision'); } testShip.shadow = [{x: 300, y: 350, time: 0.11},{x: 300, y: 350, time: 0.89}]; testAsteroid.shadow = [{x: 300, y: 350, time: -0.11}]; if (TrailCollision(testShip,testAsteroid)) { testDied('should not be in collision'); } testShip.shadow = [{x: 300, y: 350, time: 0.01}]; testAsteroid.shadow = [{x: 300, y: 350, time: 0.89}]; if (!TrailCollision(testShip,testAsteroid)) { testDied('should be in collision'); } // test the bounce momenta let momentumX, momentumY, mxPrime, myPrime; testShip.radius = 15; // testShip.velX = 1; // testShip.velY = 0; testShip.setVelocity({ velX: 1, velY: 0 }); // testShip.speed = 0; testAsteroid.radius = 15; // testAsteroid.velX = 0; // testAsteroid.velY = 0; // testAsteroid.speed = 1; testAsteroid.setVelocity({ speed: 1, angle: 180 }); if (testShip.Speed() != 1) testDied('ship should have a speed of 1. shows ' + testShip.Speed()); if (testAsteroid.Speed() != 1) testDied('asteroid should have a speed of 1. shows ' + testAsteroid.Speed()); if (testShip.VelocityX() != 1) testDied('ship should have an horizontal velocity of 1. shows ' + testShip.VelocityX()); if (testShip.VelocityY() != 0) testDied('ship should have an vertical velocity of 0. shows ' + testShip.VelocityY()); if (testAsteroid.VelocityX() != -1) testDied('asteroid should have an horizontal velocity of -1. shows ' + testAsteroid.VelocityX()); if (Math.abs(testAsteroid.VelocityY() - 0) > 0.001) testDied('asteroid should have an vertical velocity of 0. shows ' + testAsteroid.VelocityY()); momentumX = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); momentumY = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); Billiards(testShip, testAsteroid); mxPrime = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); myPrime = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); if (Math.abs(momentumX - mxPrime) > 0.001 || Math.abs(momentumY - myPrime) > 0.001) testDied('momentum should have been preserved' + String.fromCharCode(13) + String.fromCharCode(10) + 'MX - ' + momentumX + ' MY - ' + momentumY + ' MX\'- ' + mxPrime + ' MY\'- ' + myPrime); if (testShip.Speed() != 1) testDied('ship should have a speed of 1. shows ' + testShip.Speed()); if (testAsteroid.Speed() != 1) testDied('asteroid should have a speed of 1. shows ' + testAsteroid.Speed()); if (testShip.Angle() != 180) testDied('ship should have an angle of 180. shows ' + testShip.Angle()); if (testAsteroid.Angle() != 0) testDied('asteroid should have an angle of 0. shows ' + testAsteroid.Angle()); testShip.radius = 15; testShip.velX = -1; testShip.velY = 0; // testShip.speed = 1; // testShip.angle = 0; testAsteroid = new Asteroid(); testAsteroid.radius = 15; // testAsteroid.velX = 0; // testAsteroid.velY = 0; testAsteroid.speed = 1; testAsteroid.angle = 90; momentumX = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); momentumY = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); Billiards(testShip, testAsteroid); mxPrime = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); myPrime = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); if (Math.abs(testShip.Speed() - 1) > 0.001) testDied('ship should have a speed of 1. shows ' + testShip.Speed()); if (Math.abs(testAsteroid.Speed() - 1) > 0.001) testDied('asteroid should have a speed of 1. shows ' + testAsteroid.Speed()); if (Math.abs(testShip.Angle() - 90) > 0.001) testDied('ship should have an angle of 90. shows ' + testShip.Angle()); if (Math.abs(testAsteroid.Angle() - 0) > 0.001) testDied('asteroid should have an angle of 0. shows ' + testAsteroid.Angle()); if (Math.abs(momentumX - mxPrime) > 0.001 || Math.abs(momentumY - myPrime) > 0.001) testDied('momentum should have been preserved' + String.fromCharCode(13) + String.fromCharCode(10) + 'MX - ' + momentumX + ' MY - ' + momentumY + ' MX\'- ' + mxPrime + ' MY\'- ' + myPrime); testShip.radius = 15; // testShip.velX = 0; // testShip.velY = 0; // testShip.speed = 1; // testShip.angle = 45; testShip.setVelocity({ angle: 45, speed: 1 }); testAsteroid.radius = 15; // testAsteroid.velX = 0; // testAsteroid.velY = 0; // testAsteroid.speed = 1; // testAsteroid.angle = 135; testAsteroid.setVelocity({ speed: 1, angle: 135 }); momentumX = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); momentumY = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); Billiards(testShip, testAsteroid); mxPrime = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); myPrime = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); if (Math.abs(momentumX - mxPrime) > 0.001 || Math.abs(momentumY - myPrime) > 0.001) testDied('momentum should have been preserved' + String.fromCharCode(13) + String.fromCharCode(10) + 'MX - ' + momentumX + ' MY - ' + momentumY + ' MX\'- ' + mxPrime + ' MY\'- ' + myPrime); if (Math.abs(testShip.Speed() - 1) > 0.001) testDied('ship should have a speed of 1. shows ' + testShip.Speed()); if (Math.abs(testAsteroid.Speed() - 1) > 0.001) testDied('asteroid should have a speed of 1. shows ' + testAsteroid.Speed()); if (Math.abs(testShip.Angle() - 135) > 0.001) testDied('ship should have a angle of 135. shows ' + testShip.Angle()); if (Math.abs(testAsteroid.Angle() - 45) > 0.001) testDied('asteroid should have an angle of 45. shows ' + testAsteroid.Angle()); testShip.radius = 15; // testShip.velX = 0; // testShip.velY = 0; // testShip.speed = 1; // testShip.angle = 45; testShip.setVelocity({ angle: 45, speed: 1 }); testAsteroid.radius = 15; // testAsteroid.velX = 0; // testAsteroid.velY = 0; testAsteroid.speed = 3; testAsteroid.angle = 135; // testAsteroid.setVelocity({speed:1,angle:135}); momentumX = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); momentumY = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); Billiards(testShip, testAsteroid); mxPrime = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); myPrime = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); if (Math.abs(momentumX - mxPrime) > 0.001 || Math.abs(momentumY - myPrime) > 0.001) testDied('momentum should have been preserved' + String.fromCharCode(13) + String.fromCharCode(10) + 'MX - ' + momentumX + ' MY - ' + momentumY + ' MX\'- ' + mxPrime + ' MY\'- ' + myPrime); if (Math.abs(testShip.Speed() - 3) > 0.001) testDied('ship should have a speed of 3. shows ' + testShip.Speed()); if (Math.abs(testAsteroid.Speed() - 1) > 0.001) testDied('asteroid should have a speed of 1. shows ' + testAsteroid.Speed()); if (Math.abs(testShip.Angle() - 135) > 0.001) testDied('ship should have a angle of 135. shows ' + testShip.Angle()); if (Math.abs(testAsteroid.Angle() - 45) > 0.001) testDied('asteroid should have an angle of 45. shows ' + testAsteroid.Angle()); testShip.radius = 15; testShip.velX = 0; testShip.velY = 0; // testShip.speed = 0; // testShip.angle = 45; testAsteroid.radius = 15; // testAsteroid.velX = 0; // testAsteroid.velY = 0; testAsteroid.speed = 3; testAsteroid.angle = 135; momentumX = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); momentumY = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); Billiards(testShip, testAsteroid); mxPrime = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); myPrime = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); if (Math.abs(momentumX - mxPrime) > 0.001 || Math.abs(momentumY - myPrime) > 0.001) testDied('momentum should have been preserved' + String.fromCharCode(13) + String.fromCharCode(10) + 'MX - ' + momentumX + ' MY - ' + momentumY + ' MX\'- ' + mxPrime + ' MY\'- ' + myPrime); if (Math.abs(testShip.Speed() - 3) > 0.001) testDied('ship should have a speed of 3. shows ' + testShip.Speed()); if (Math.abs(testAsteroid.Speed() - 0) > 0.001) testDied('asteroid should have a speed of 0. shows ' + testAsteroid.Speed()); if (Math.abs(testShip.Angle() - 135) > 0.001) testDied('ship should have a angle of 135. shows ' + testShip.Angle()); if (Math.abs(testAsteroid.Angle() - 45) > 0.001 && testAsteroid.angle != 0) testDied('asteroid should have an angle of 45. shows ' + testAsteroid.Angle()); testShip.radius = 15; testShip.velX = -Math.sqrt(0.5); testShip.velY = -Math.sqrt(0.5); // testShip.speed = 1; // testShip.angle = 45; testAsteroid.radius = 30; // testAsteroid.velX = 0; // testAsteroid.velY = 0; testAsteroid.speed = 1; testAsteroid.angle = 225; momentumX = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); momentumY = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); Billiards(testShip, testAsteroid); mxPrime = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); myPrime = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); if (Math.abs(momentumX - mxPrime) > 0.001 || Math.abs(momentumY - myPrime) > 0.001) testDied('momentum should have been preserved' + String.fromCharCode(13) + String.fromCharCode(10) + 'MX - ' + momentumX + ' MY - ' + momentumY + ' MX\'- ' + mxPrime + ' MY\'- ' + myPrime); if (Math.abs(testShip.Speed() - 5 / 3) > 0.001) testDied('ship should have a speed of 5/3. shows ' + testShip.Speed()); if (Math.abs(testAsteroid.Speed() - 1 / 3) > 0.001) testDied('asteroid should have a speed of 1/3. shows ' + testAsteroid.Speed()); if (Math.abs(testShip.Angle() - 225) > 0.001) testDied('ship should have a angle of 225. shows ' + testShip.Angle()); if (Math.abs(testAsteroid.Angle() - 45) > 0.001) testDied('asteroid should have an angle of 45. shows ' + testAsteroid.Angle()); testShip.radius = 15; testShip.velX = -Math.sqrt(0.5); testShip.velY = -Math.sqrt(0.5); // testShip.speed = 1; // testShip.angle = 45; testAsteroid.radius = 45; // testAsteroid.velX = 0; // testAsteroid.velY = 0; testAsteroid.speed = 3; testAsteroid.angle = 225; momentumX = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); momentumY = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); Billiards(testShip, testAsteroid); mxPrime = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); myPrime = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); if (Math.abs(momentumX - mxPrime) > 0.001 || Math.abs(momentumY - myPrime) > 0.001) testDied('momentum should have been preserved' + String.fromCharCode(13) + String.fromCharCode(10) + 'MX - ' + momentumX + ' MY - ' + momentumY + ' MX\'- ' + mxPrime + ' MY\'- ' + myPrime); if (Math.abs(testShip.Speed() - 5) > 0.001) testDied('ship should have a speed of 5. shows ' + testShip.Speed()); if (Math.abs(testAsteroid.Speed() - 1) > 0.001) testDied('asteroid should have a speed of 1. shows ' + testAsteroid.Speed()); if (Math.abs(testShip.Angle() - 225) > 0.001) testDied('ship should have a angle of 225. shows ' + testShip.Angle()); if (Math.abs(testAsteroid.Angle() - 225) > 0.001) testDied('asteroid should have an angle of 225. shows ' + testAsteroid.Angle()); testShip.radius = 15; testShip.velX = -Math.sqrt(0.5); testShip.velY = -Math.sqrt(0.5); // testShip.speed = 1; // testShip.angle = 45; testAsteroid.radius = 45; // testAsteroid.velX = 0; // testAsteroid.velY = 0; testAsteroid.speed = 3; testAsteroid.angle = 135; momentumX = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); momentumY = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); Billiards(testShip, testAsteroid); mxPrime = testShip.radius * (testShip.velX + Math.cos(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velX + Math.cos(deg2rad(testAsteroid.angle)) * testAsteroid.speed); myPrime = testShip.radius * (testShip.velY + Math.sin(deg2rad(testShip.angle)) * testShip.speed) + testAsteroid.radius * (testAsteroid.velY + Math.sin(deg2rad(testAsteroid.angle)) * testAsteroid.speed); if (Math.abs(momentumX - mxPrime) > 0.001 || Math.abs(momentumY - myPrime) > 0.001) testDied('momentum should have been preserved' + String.fromCharCode(13) + String.fromCharCode(10) + 'MX - ' + momentumX + ' MY - ' + momentumY + ' MX\'- ' + mxPrime + ' MY\'- ' + myPrime); if (Math.abs(testShip.Speed() - 4.527692569068709) > 0.001) testDied('ship should have a speed of 4.5. shows ' + testShip.Speed()); if (Math.abs(testAsteroid.Speed() - 1.58113883) > 0.001) testDied('asteroid should have a speed of 1.6. shows ' + testAsteroid.Speed()); if (Math.abs(testShip.Angle() - 141.3401917459099) > 0.001) testDied('ship should have a angle of 141. shows ' + testShip.Angle()); if (Math.abs(testAsteroid.Angle() - 116.56505117707799) > 0.001) testDied('asteroid should have an angle of 117. shows ' + testAsteroid.Angle()); // test for Mine and MagneticMine and Nova Bombs let testMine = new Mine(testShip.x, testShip.y, testShip.angle, testShip.speed); let mineParameters = {velX: testMine.VelocityX(), velY: testMine.VelocityY(), angle: testMine.angle, speed: testMine.speed}; let mineTrail = trailCoordinates(testMine); let trail = fromToTrail(testMine, {x: testMine.x + 10*testMine.VelocityX(), y: testMine.y + 10*testMine.VelocityY()}, 1); if (Math.abs(testShip.speed - mineParameters.speed) > 0.001) testDied('mine should have a speed of '+ testShip.speed +'. shows ' + mineParameters.speed); if(Math.abs(testShip.angle - mineParameters.angle) > 0.001) testDied('mine should have an angle of '+ testShip.angle +'. shows ' + mineParameters.angle); if(mineTrail.length < Math.ceil(mineParameters.speed)) testDied('mine should have a trail '+ Math.ceil(mineParameters.speed) +' units long. shows ' + mineTrail.length); if(!isValid(mineTrail[0]) || !isValid(mineTrail[0].x) || !isValid(mineTrail[0].y)) // check for undefined and NaN testDied('mine should have a valid trail. shows ' + mineTrail[0].x +','+ mineTrail[0].y); if(Math.abs(mineTrail[0].x - 699.7) > 0.01 || Math.abs(mineTrail[0].y - 350) > 0.01) testDied('mine should have a trail starting at 699.7,350. shows ' + mineTrail[0].x +','+ mineTrail[0].y); testMine.setVelocity({velX: testMine.VelocityX(), velY: testMine.VelocityY()}); mineTrail = trailCoordinates(testMine); if (Math.abs(testMine.Speed() - mineParameters.speed) > 0.001) testDied('mine should have a speed of '+ mineParameters.speed +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - mineParameters.angle) > 0.001) testDied('mine should have an angle of '+ mineParameters.angle +'. shows ' + testMine.Angle()); if(mineTrail.length < Math.ceil(mineParameters.speed)) testDied('mine should have a trail '+ Math.ceil(mineParameters.speed) +' units long. shows ' + mineTrail.length); if(Math.abs(mineTrail[0].x - 699.7) > 0.01 || Math.abs(mineTrail[0].y - 350) > 0.01) testDied('mine should have a trail starting at 699.7,350. shows ' + mineTrail[0].x +','+ mineTrail[0].y); testMine.setVelocity({velX: testAsteroid.VelocityX(), velY: testAsteroid.VelocityY()}); mineParameters = {velX: testMine.VelocityX(), velY: testMine.VelocityY(), angle: testMine.angle, speed: testMine.speed}; mineTrail = trailCoordinates(testMine); if (Math.abs(testAsteroid.Speed() - mineParameters.speed) > 0.001) testDied('mine should have a speed of '+ testAsteroid.Speed() +'. shows ' + mineParameters.speed); if(Math.abs(testAsteroid.Angle() - testMine.Angle()) > 0.001) testDied('mine should have an angle of '+ testAsteroid.angle +'. shows ' + testMine.Angle()); if(mineTrail.length < Math.ceil(mineParameters.speed)) testDied('mine should have a trail '+ Math.ceil(mineParameters.speed) +' units long. shows ' + mineTrail.length); if(Math.abs(mineTrail[0].x - 702.121) > 0.01 || Math.abs(mineTrail[0].y - 345.757) > 0.01) testDied('mine should have a trail starting at 702.121,345.757. shows ' + mineTrail[0].x +','+ mineTrail[0].y); testMine.setVelocity({velX: testMine.VelocityX(), velY: testMine.VelocityY()}); mineTrail = trailCoordinates(testMine); if (Math.abs(testMine.Speed() - mineParameters.speed) > 0.001) testDied('mine should have a speed of '+ mineParameters.speed +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - mineParameters.angle) > 0.001) testDied('mine should have an angle of '+ mineParameters.angle +'. shows ' + testMine.Angle()); if(mineTrail.length <= 0) testDied('mine should have a trail '+ Math.ceil(mineParameters.speed) +' units long. shows ' + mineTrail.length); if(Math.abs(mineTrail[0].x - 702.121) > 0.01 || Math.abs(mineTrail[0].y - 345.757) > 0.01) testDied('mine should have a trail starting at 702.121,345.757. shows ' + mineTrail[0].x +','+ mineTrail[0].y); testMine = new MagneticMine(testShip.x, testShip.y, testShip.angle, testShip.speed); mineParameters = {velX: testMine.VelocityX(), velY: testMine.VelocityY(), angle: testMine.angle, speed: testMine.speed}; mineTrail = trailCoordinates(testMine); if (Math.abs(testShip.speed - mineParameters.speed) > 0.001) testDied('mine should have a speed of '+ testShip.Speed() +'. shows ' + mineParameters.speed); if(Math.abs(testShip.angle - testMine.Angle()) > 0.001) testDied('mine should have an angle of '+ testShip.angle +'. shows ' + testMine.Angle()); if(mineTrail.length < Math.ceil(mineParameters.speed)) testDied('mine should have a trail '+ Math.ceil(mineParameters.speed) +' units long. shows ' + mineTrail.length); if(Math.abs(mineTrail[0].x - 699.7) > 0.01 || Math.abs(mineTrail[0].y - 350) > 0.01) testDied('mine should have a trail starting at 699.7,350. shows ' + mineTrail[0].x +','+ mineTrail[0].y); testMine.setVelocity({velX: testMine.VelocityX(), velY: testMine.VelocityY()}); mineTrail = trailCoordinates(testMine); if (Math.abs(testMine.Speed() - mineParameters.speed) > 0.001) testDied('mine should have a speed of '+ mineParameters.speed +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - mineParameters.angle) > 0.001) testDied('mine should have an angle of '+ mineParameters.angle +'. shows ' + testMine.Angle()); if(mineTrail.length <= 0) testDied('mine should have a trail '+ Math.ceil(mineParameters.speed) +' units long. shows ' + mineTrail.length); if(Math.abs(mineTrail[0].x - 699.7) > 0.01 || Math.abs(mineTrail[0].y - 350) > 0.01) testDied('mine should have a trail starting at 699.7,350. shows ' + mineTrail[0].x +','+ mineTrail[0].y); testMine.setVelocity({velX: testAsteroid.VelocityX(), velY: testAsteroid.VelocityY()}); mineParameters = {velX: testMine.VelocityX(), velY: testMine.VelocityY(), angle: testMine.angle, speed: testMine.speed}; mineTrail = trailCoordinates(testMine); if (Math.abs(testAsteroid.Speed() - mineParameters.speed) > 0.001) testDied('mine should have a speed of '+ testAsteroid.Speed() +'. shows ' + mineParameters.speed); if(Math.abs(testAsteroid.Angle() - testMine.Angle()) > 0.001) testDied('mine should have an angle of '+ testAsteroid.angle +'. shows ' + testMine.Angle()); if(mineTrail.length < Math.ceil(mineParameters.speed)) testDied('mine should have a trail '+ Math.ceil(mineParameters.speed) +' units long. shows ' + mineTrail.length); if(Math.abs(mineTrail[0].x - 702.121) > 0.01 || Math.abs(mineTrail[0].y - 345.757) > 0.01) testDied('mine should have a trail starting at 702.121,345.757. shows ' + mineTrail[0].x +','+ mineTrail[0].y); testMine.setVelocity({velX: testMine.VelocityX(), velY: testMine.VelocityY()}); mineTrail = trailCoordinates(testMine); if (Math.abs(testMine.Speed() - mineParameters.speed) > 0.001) testDied('mine should have a speed of '+ mineParameters.speed +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - mineParameters.angle) > 0.001) testDied('mine should have an angle of '+ mineParameters.angle +'. shows ' + testMine.Angle()); if(mineTrail.length <= 0) testDied('mine should have a trail '+ Math.ceil(mineParameters.speed) +' units long. shows ' + mineTrail.length); if(Math.abs(mineTrail[0].x - 702.121) > 0.01 || Math.abs(mineTrail[0].y - 345.757) > 0.01) testDied('mine should have a trail starting at 702.121,345.757. shows ' + mineTrail[0].x +','+ mineTrail[0].y); // Speed and angle tests for Bullet, Missile, and Yaka Arrow testMine = new Bullet(45); testMine.speed=1.0; testMine.velX=0; testMine.velY=0; if (Math.abs(testMine.Speed() - 1) > 0.001) testDied('mine should have a speed of '+ 1 +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - 45) > 0.001) testDied('mine should have an angle of '+ 45 +'. shows ' + testMine.Angle()); testMine.velX=Math.SQRT1_2; testMine.velY=Math.SQRT1_2; if (Math.abs(testMine.Speed() - 2) > 0.001) testDied('mine should have a speed of '+ 2 +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - 45) > 0.001) testDied('mine should have an angle of '+ 45 +'. shows ' + testMine.Angle()); testMine.velX=-Math.SQRT1_2; if (Math.abs(testMine.Speed() - Math.SQRT2) > 0.001) testDied('mine should have a speed of '+ Math.SQRT1_2 +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - 90) > 0.001) testDied('mine should have an angle of '+ 90 +'. shows ' + testMine.Angle()); testMine = new Missile({angle: 45, speed: 1.1}); testMine.speed=1.0; testMine.velX=0; testMine.velY=0; if (Math.abs(testMine.Speed() - 1) > 0.001) testDied('mine should have a speed of '+ 1 +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - 45) > 0.001) testDied('mine should have an angle of '+ 45 +'. shows ' + testMine.Angle()); testMine.velX=Math.SQRT1_2; testMine.velY=Math.SQRT1_2; if (Math.abs(testMine.Speed() - 2) > 0.001) testDied('mine should have a speed of '+ 2 +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - 45) > 0.001) testDied('mine should have an angle of '+ 45 +'. shows ' + testMine.Angle()); testMine.velX=-Math.SQRT1_2; if (Math.abs(testMine.Speed() - Math.SQRT2) > 0.001) testDied('mine should have a speed of '+ Math.SQRT1_2 +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - 90) > 0.001) testDied('mine should have an angle of '+ 90 +'. shows ' + testMine.Angle()); testMine = new YakaArrow({angle: 45, speed: 1.1}); testMine.speed=1.0; testMine.velX=0; testMine.velY=0; if (Math.abs(testMine.Speed() - 1) > 0.001) testDied('mine should have a speed of '+ 1 +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - 45) > 0.001) testDied('mine should have an angle of '+ 45 +'. shows ' + testMine.Angle()); testMine.velX=Math.SQRT1_2; testMine.velY=Math.SQRT1_2; if (Math.abs(testMine.Speed() - 2) > 0.001) testDied('mine should have a speed of '+ 2 +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - 45) > 0.001) testDied('mine should have an angle of '+ 45 +'. shows ' + testMine.Angle()); testMine.velX=-Math.SQRT1_2; if (Math.abs(testMine.Speed() - Math.SQRT2) > 0.001) testDied('mine should have a speed of '+ Math.SQRT1_2 +'. shows ' + testMine.Speed()); if(Math.abs(testMine.Angle() - 90) > 0.001) testDied('mine should have an angle of '+ 90 +'. shows ' + testMine.Angle()); /* Ship {visible: true, spawnClock: Tue Jul 13 2021 15:46:26 GMT-0400 (Eastern Daylight Time), x: 702.3970473193156, y: 350.3734036491287, shadow: Array(4), …} angle: 180 collisionRadius: 11 friction: true gunPort: 2 gunPortX: Array(3) 0: 717.3970473193156 1: 713.0036490371137 2: 713.0036490371137 length: 3 __proto__: Array(0) gunPortY: Array(3) 0: 350.3734036491287 1: 360.9800053669269 2: 339.7668019313305 length: 3 __proto__: Array(0) launchCodes: NaN movingForward: false radius: 15 rapidFire: false rotateSpeed: 5 shadow: Array(4) 0: {x: 702.3970473193156, y: 350.3734036491287, time: -3, angle: 0} 1: {x: 702.3970473193156, y: 350.3734036491287, time: -2, angle: 0} 2: {x: 702.3970473193156, y: 350.3734036491287, time: -1, angle: 0} 3: {x: 702.3970473193156, y: 350.3734036491287, time: 0, angle: 0} length: 4 __proto__: Array(0) shieldClock: 0 shieldPower: 75.78571428571382 shieldsOn: true spawnClock: Tue Jul 13 2021 15:46:26 GMT-0400 (Eastern Daylight Time) {} speed: 0.1 strokeColor: "white" velX: -0 velY: 0 visible: true x: 702.3970473193156 y: 350.3734036491287 __proto__: Object asteroids (21) [Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid] 0: Asteroid {visible: true, x: 4.549490679049137, y: 600.0405152861345, shadow: Array(4), radius: 50, …} 1: Asteroid {visible: true, x: 680.7029308841543, y: 285.67427589474534, shadow: Array(4), radius: 50, …} 2: Asteroid {visible: true, x: 1049.1784099330264, y: 181.87654865632337, shadow: Array(4), radius: 50, …} 3: Asteroid {visible: true, x: 645.0482066228409, y: 387.73772848127163, shadow: Array(4), radius: 50, …} 4: Asteroid {visible: true, x: 791.4544555831857, y: 407.0248142072633, shadow: Array(4), radius: 25, …} 5: Asteroid {visible: true, x: 694.4692850492052, y: 365.0852254473227, shadow: Array(4), radius: 15, …} 6: Asteroid {visible: true, x: 682.2996537203519, y: 341.921479228794, shadow: Array(4), radius: 15, …} 7: Asteroid {visible: true, x: 625.2002347496207, y: 199.4340177056592, shadow: Array(4), radius: 25, …} 8: Asteroid {visible: true, x: 486.449682547277, y: 444.3295365393436, shadow: Array(4), radius: 25, …} 9: Asteroid {visible: true, x: 723.4867881072291, y: 344.3297615012065, shadow: Array(4), radius: 15, …} 10: Asteroid {visible: true, x: 702.1079891767159, y: 369.2581806460317, shadow: Array(4), radius: 15, …} 11: Asteroid {visible: true, x: 772.7837765873708, y: 559.1072764135653, shadow: Array(4), radius: 25, …} 12: Asteroid {visible: true, x: 880.3960854869797, y: 687.7605700156272, shadow: Array(11), radius: 15, …} 13: Asteroid {visible: true, x: 707.5025977047908, y: 361.3058158454129, shadow: Array(4), radius: 15, …} 14: Asteroid {visible: true, x: 960.5453368767588, y: 328.7540787454353, shadow: Array(80), radius: 10, …} 15: Asteroid {visible: true, x: 699.767127813115, y: 361.03074414175796, shadow: Array(4), radius: 10, …} 16: Asteroid {visible: true, x: 394.84048682341984, y: 399.33788976510925, shadow: Array(80), radius: 10, …} 17: Asteroid {visible: true, x: 692.9079763636698, y: 359.9880604442463, shadow: Array(4), radius: 10, …} 18: Asteroid {visible: true, x: 702.4524254431934, y: 339.79056774293804, shadow: Array(4), radius: 10, …} 19: Asteroid {visible: true, x: 700.0921522694432, y: 328.98747123228156, shadow: Array(4), radius: 15, …} 20: Asteroid {visible: true, x: 723.8814624311073, y: 347.01237268114556, shadow: Array(4), radius: 15, …} length: 21 __proto__: Array(0) asteroids (21) [Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid, Asteroid] 0: Asteroid angle: 100.9999935484755 collisionRadius: 46 level: 1 radius: 50 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.051521598912740885 strokeColor: "white" visible: true x: 4.549490679049137 y: 600.0405152861345 __proto__: Object 1: Asteroid angle: -66.00005849983553 collisionRadius: 46 level: 1 radius: 50 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.009687856514370452 strokeColor: "white" visible: true x: 680.7029308841543 y: 285.67427589474534 __proto__: Object 2: Asteroid angle: 22.000306224983646 collisionRadius: 46 level: 1 radius: 50 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.0941698835075617 strokeColor: "white" visible: true x: 1049.1784099330264 y: 181.87654865632337 __proto__: Object 3: Asteroid angle: 266.00000034044086 collisionRadius: 46 level: 1 radius: 50 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.01490934347062646 strokeColor: "white" visible: true x: 645.0482066228409 y: 387.73772848127163 __proto__: Object 4: Asteroid angle: 38.00011957573411 collisionRadius: 22 level: 2 radius: 25 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.0016388363210199097 strokeColor: "white" visible: true x: 791.4544555831857 y: 407.0248142072633 __proto__: Object 5: Asteroid angle: 0 collisionRadius: 12 level: 3 radius: 15 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0 strokeColor: "white" visible: true x: 694.4692850492052 y: 365.0852254473227 __proto__: Object 6: Asteroid angle: 0 collisionRadius: 12 level: 3 radius: 15 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0 strokeColor: "white" visible: true x: 682.2996537203519 y: 341.921479228794 __proto__: Object 7: Asteroid angle: 46 collisionRadius: 22 level: 2 radius: 25 shadow: (4) [{…}, {…}, {…}, {…}] speed: 12 strokeColor: "white" visible: true x: 625.2002347496207 y: 199.4340177056592 __proto__: Object 8: Asteroid angle: 196.98745749616896 collisionRadius: 22 level: 2 radius: 25 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.0364482330017123 strokeColor: "white" visible: true x: 486.449682547277 y: 444.3295365393436 __proto__: Object 9: Asteroid angle: 0 collisionRadius: 12 level: 3 radius: 15 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0 strokeColor: "white" visible: true x: 723.4867881072291 y: 344.3297615012065 __proto__: Object 10: Asteroid angle: 0 collisionRadius: 12 level: 3 radius: 15 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0 strokeColor: "white" visible: true x: 702.1079891767159 y: 369.2581806460317 __proto__: Object 11: Asteroid angle: -24.00010248866864 collisionRadius: 22 level: 2 radius: 25 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.02502675989421635 strokeColor: "white" visible: true x: 772.7837765873708 y: 559.1072764135653 __proto__: Object 12: Asteroid angle: 207 collisionRadius: 12 level: 3 radius: 15 shadow: (11) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] speed: 20 strokeColor: "white" visible: true x: 880.3960854869797 y: 687.7605700156272 __proto__: Object 13: Asteroid angle: 0 collisionRadius: 12 level: 3 radius: 15 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0 strokeColor: "white" visible: true x: 707.5025977047908 y: 361.3058158454129 __proto__: Object 14: Asteroid angle: 62.48422266212773 collisionRadius: 8 level: 4 radius: 10 shadow: (80) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] speed: 95.48999999999943 strokeColor: "white" visible: true x: 960.5453368767588 y: 328.7540787454353 __proto__: Object 15: Asteroid angle: 252.12696305819995 collisionRadius: 8 level: 4 radius: 10 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.00028421837305790367 strokeColor: "white" visible: true x: 699.767127813115 y: 361.03074414175796 __proto__: Object 16: Asteroid angle: 220.59209161847124 collisionRadius: 8 level: 4 radius: 10 shadow: (80) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}] speed: 95.48999999999943 strokeColor: "white" visible: true x: 394.84048682341984 y: 399.33788976510925 __proto__: Object 17: Asteroid angle: -24.77428942948192 collisionRadius: 8 level: 4 radius: 10 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.0001454806138021904 strokeColor: "white" visible: true x: 692.9079763636698 y: 359.9880604442463 __proto__: Object 18: Asteroid angle: 86.0016454694019 collisionRadius: 8 level: 4 radius: 10 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0.00014168976374942163 strokeColor: "white" visible: true x: 702.4524254431934 y: 339.79056774293804 __proto__: Object 19: Asteroid angle: 0 collisionRadius: 12 level: 3 radius: 15 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0 strokeColor: "white" visible: true x: 700.0921522694432 y: 328.98747123228156 __proto__: Object 20: Asteroid angle: 0 collisionRadius: 12 level: 3 radius: 15 shadow: (4) [{…}, {…}, {…}, {…}] speed: 0 strokeColor: "white" visible: true x: 723.8814624311073 y: 347.01237268114556 __proto__: Object length: 21 __proto__: Array(0) */ } function testDied(logMessage) { if (window.stop) window.stop(); window.document.getElementById('container').innerHTML += '

' + logMessage + '

'; throw logMessage; } function numberToString(value) { let string; if (!isValid(value)) string = 'NaN.00'; else if (value < 0.01) string = '0.00'; else string = (value + '.00').toString(); return string.substring(0, string.indexOf('.') + 3); } function isValid(param) { if((typeof param) == 'number') return !isNaN(param); return (typeof param) != "undefined"; } function deg2rad(angle) { return angle / 180 * Math.PI; } function rad2deg(radians) { return radians / Math.PI * 180; } function arctan(xCoord, yCoord) { return (xCoord != 0 ? Math.atan(yCoord / xCoord) + (xCoord < 0 ? Math.PI : 0) : yCoord < 0 ? Math.PI * 3 / 2 : yCoord == 0 ? 0 : Math.PI / 2 ); }