Related
I'm working with an NES emulator that was written in JavaScript, updating it so it runs better. I'm not knowledgeable with the Web Audio API. Can someone point me in the right direction as to where the audio is being output in the following script?
I've spent a pretty long time going through it, but can't figure it out. My goal is to bring most of the math part of the emulation into a web worker. I have already brought most of the emulator over to the web worker and improved the performance greatly. This is the part I am stuck on though.
var masterVolumeValue = 0; //0.15;
const enableAudioEmulation = true;
function SetMasterVolume(value) {
masterVolumeValue = value;
Unmute();
}
function Mute() {
if (masterVolume) masterVolume.gain.setValueAtTime(0, audio.currentTime);
}
function Unmute() {
if (masterVolume) masterVolume.gain.setValueAtTime(masterVolumeValue, audio.currentTime);
}
var audio;
var masterVolume;
var pulse1 = {
oscillator: null,
haltLengthCounter: false,
constantFlag: false,
dutyCycle: 0,
volume: 0
};
var pulse2 = {
oscillator: null,
haltLengthCounter: false,
constantFlag: false,
dutyCycle: 0,
volume: 0
};
var triangle = {};
var noise = {};
var dmc = {};
var NewApu = (function() {
var running = false;
var pulseCycles = [
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 0, 0, 0],
[1, 0, 0, 1, 1, 1, 1, 1]
];
var triangleCycle = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
var bufferSize = 1024;
var mixer;
var wave, ctx;
function initAudio() {
if (audio || !enableAudioEmulation) return;
audio = new AudioContext();
masterVolume = audio.createGain();
masterVolume.gain.setValueAtTime(masterVolumeValue, audio.currentTime);
mixer = audio.createScriptProcessor(bufferSize, 0, 1);
masterVolume.connect(audio.destination);
mixer.connect(masterVolume);
clockStep = 1789773 / 2 / audio.sampleRate; // NES clock frequency / 2 = APU sample rate
clockStep *= 0.97;
mixer.onaudioprocess = resample;
initChannel(pulse1);
initChannel(pulse2);
initChannel(triangle);
noise.lengthCounter = 0;
dmc.load = 0;
dmc.loop = false;
dmc.frequency = 0;
dmc.timer = 0;
dmc.address = 0;
dmc.bytesRemaining = 0;
dmc.currentByte = 0;
dmc.shiftPointer = 0;
dmc.length = 0;
dmc.sample = 0;
if (debug) {
wave = new Float32Array(bufferSize);
ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 300;
ctx.canvas.height = 50;
ctx.canvas.style.width = "300px";
output.canvas.parentNode.appendChild(ctx.canvas);
}
}
var npulse1 = {
cycle: 2,
pointer: 0,
timer: 0,
sample: 0
};
var npulse2 = {
cycle: 2,
pointer: 0,
timer: 0,
sample: 0
};
var ntriangle = {
pointer: 0,
timer: 0,
sample: 0
};
var nnoise = {
shiftRegister: 1,
timer: 0,
sample: 0
}
var counting = 0;
function updateWaveform() {
var c = ctx;
c.clearRect(0, 0, 300, 50);
c.strokeStyle = "#ffffff";
c.beginPath()
c.moveTo(0, (-wave[0] * 10) + 50);
for (var i = 1; i < wave.length; i++) {
c.lineTo((i / wave.length) * 300, (-wave[i] * 10) + 50);
}
c.stroke();
}
// More accurate, but doesn't match output rate due to emulation, so will course clicks and/or lag
var buffer = new Float32Array(bufferSize * 10);
var bufferPointer = 100;
var readPointer = 0;
var bufferLength = 0;
var bufferStep;
function resample(e) {
if (!running) return;
if (bufferLength < 400) {
//console.log('buffer underrun');
return; // extreme underflow, in case of performance issues or the tab went into the background, we just want to skip this update
}
var output = e.outputBuffer.getChannelData(0);
var l = Math.min(bufferLength, output.length);
for (var i = 0; i < l; i++) {
//output[i] = Math.tanh(buffer[readPointer] / (15*3));
output[i] = buffer[readPointer];
readPointer++;
if (readPointer == buffer.length) readPointer = 0;
}
l = output.length;
for (var i = bufferLength; i < l; i++) {
asyncTick(clockStep);
//output[i] = Math.tanh(sample / (15*3)); // 15 = sequencer "full" volume
output[i] = sample;
}
bufferLength = Math.max(0, bufferLength - output.length);
if (debug) {
for (var i = 0; i < output.length; i++) wave[i] = output[i];
updateWaveform();
}
}
// Inaccurate, and might cause precise timing issues, but prevents buffer underflow
var sample = 0; // mixed sample from all 5 channels
var clockStep = 1789773 / 2 / 48000; // NES clock frequency / 2 = APU sample rate
function clockToOutput(e) {
if (!running) return;
var output = e.outputBuffer.getChannelData(0);
var l = output.length;
for (var i = 0; i < l; i++) {
asyncTick(clockStep);
output[i] = sample / (15); // 15 = sequencer "full" volume
}
if (debug) {
for (var i = 0; i < output.length; i++) wave[i] = output[i];
updateWaveform();
}
}
// APU clock updates:
function updatePulse(channel, ref) {
if (!ref.frequency) return;
channel.timer -= click;
while (channel.timer < 0) {
channel.timer += ref.frequency;
channel.pointer = (channel.pointer + 1) & 7;
}
var duty = ref.dutyCycle;
channel.sample = ref.lengthCounter && pulseCycles[duty][channel.pointer] ? (ref.constantFlag ? ref.volume : ref.decay) : 0;
//channel.sample >>= 1;
}
function updateTriangle(channel, ref) {
if (!ref.frequency) return;
channel.timer -= click + click;
while (channel.timer < 0) {
channel.timer += ref.frequency;
channel.pointer = (channel.pointer + 1) & 31;
}
channel.sample = ref.lengthCounter && ref.linearCounter ? triangleCycle[channel.pointer] : 0;
}
function updateNoise(channel, ref) {
if (!ref.frequency) return;
channel.timer -= click;
while (channel.timer < 0) {
channel.timer += ref.frequency;
var feedback = (channel.shiftRegister & 1) ^ ((channel.shiftRegister >> ref.mode) & 1);
channel.shiftRegister >>= 1;
if (feedback) channel.shiftRegister |= 0b100000000000000;
channel.high = channel.shiftRegister & 1;
}
channel.sample = (ref.lengthCounter && channel.high) ? (ref.constantFlag ? ref.volume : ref.decay) : 0;
}
function updateDmc(channel) {
if (!channel.frequency) return;
channel.timer -= click + click;
// TODO: average multiple samples if this loops multiple times
while (channel.timer < 0) {
channel.timer += channel.frequency;
if (channel.bytesRemaining) {
if (channel.shiftPointer == 0) {
// TODO: add 4 CPU cycles
var address = channel.address + channel.length - channel.bytesRemaining;
while (address > 0xFFFF) address -= 0x8000;
channel.currentByte = cpuMemory[address];
}
var delta = (channel.currentByte >> channel.shiftPointer) & 1;
if (delta) {
if (channel.load < 126) channel.load += 2;
} else {
if (channel.load > 1) channel.load -= 2;
}
channel.shiftPointer++;
if (channel.shiftPointer == 8) {
channel.shiftPointer = 0;
channel.bytesRemaining--;
if (channel.bytesRemaining == 0 && channel.loop) channel.bytesRemaining = channel.length;
}
}
channel.sample = channel.load;
//channel.sample >>= 2;
}
}
var click = 0; // The number of accumulated APU clock cycles (½CPU). Generate sample for output buffer ever 10 clicks
var step = 0; // Counts the frame counters steps (currently only LC/Sweep clocks count up)
function asyncTick(amount) {
click = amount;
apuFrames += click;
if (apuFrames >= lcClock0 && step == 0) {
apuClockLcSw(false);
step = 1;
}
if (apuFrames >= lcClock1 && step == 1) {
apuClockLcSw(true);
step = 2;
}
if (apuFrames >= lcClock2 && step == 2) {
apuClockLcSw(false);
step = 3;
}
if (apuFrames >= lcClock3 && step == 3) {
apuClockLcSw(true);
if (enableFrameIrq) pendingFrameCount = true;
apuFrames -= lcClock2 + 1;
step = 0;
}
updatePulse(npulse1, pulse1);
updatePulse(npulse2, pulse2);
updateTriangle(ntriangle, triangle);
updateNoise(nnoise, noise);
updateDmc(dmc);
var mixPulse = 95.88 / ((8128 / (npulse1.sample + npulse2.sample)) + 100);
var mixTnd = 159.79 / (1 / ((ntriangle.sample / 8227) + (nnoise.sample / 12241) + (dmc.sample / 22638)) + 100);
sample = mixPulse + mixTnd;
//sample = (npulse1.sample + npulse2.sample);
}
function tick(amount) {
running = true;
if (bufferLength >= bufferSize * 4) return; // Avoid buffer overflow
click += amount;
if (click > clockStep) {
asyncTick(click);
buffer[bufferPointer] = sample;
//buffer[bufferPointer] = ntriangle.sample;
bufferPointer++;
bufferLength++;
if (bufferPointer == buffer.length) bufferPointer = 0;
click -= clockStep;
}
}
function initChannel(channel) {
channel.volume = 0;
channel.lengthCounter = 0;
channel.sweepTimer = 0;
channel.linearReload = false;
}
var lengthCounterValues = [10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30];
var noisePeriodValues = [4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068];
var dmcTimerValues = [428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54];
var apuFrames = 0;
var lcClock0 = 3728;
var lcClock1 = 7456;
var lcClock2 = 11185;
var lcClock3 = 14914;
var enableFrameIrq = false;
var frameCounterMode = false;
function apuClockLcSw(updateSweeps) {
apuFrameLinearCounter();
if (!updateSweeps) return;
apuFrameSweepChannel(pulse1);
apuFrameSweepChannel(pulse2);
apuFrameSweepChannel(triangle);
apuFrameSweepChannel(noise);
}
function apuFrameSweepChannel(channel) {
if (channel.sweepEnabled) {
if (channel.sweepTimer <= 0) {
channel.sweepTimer += channel.sweepPeriod + 1;
var amount = ((channel.frequency >> channel.sweepShift));
if (channel.sweepNegate) amount = -amount - 1;
var target = (channel.frequency + amount) & 0xfff;
if (target < 0x800 && channel.frequency > 7) channel.frequency = target;
}
channel.sweepTimer -= 1;
}
if (!channel.haltLengthCounter) channel.lengthCounter = Math.max(0, channel.lengthCounter - 1);
}
function apuFrameLinearCounter() {
if (triangle.linearReload) {
triangle.linearCounter = triangle.linearReloadValue;
} else {
triangle.linearCounter = Math.max(0, triangle.linearCounter - 1);
}
if (!triangle.haltLengthCounter) triangle.linearReload = false;
clockDivider(pulse1);
clockDivider(pulse2);
clockDivider(noise);
}
function clockDivider(channel) {
if (channel.startFlag) {
channel.startFlag = false;
channel.decay = 15;
channel.divider = channel.volume;
} else {
if (channel.divider) {
channel.divider--;
return;
}
if (channel.decay) {
channel.divider = channel.volume;
channel.decay--;
return;
}
if (channel.haltLengthCounter) channel.decay = 15; // HLC flag is the same as the Decay loop flag
}
}
function dutyCycle(channel, value) {
channel.haltLengthCounter = ((value & 0x20) != 0);
channel.constantFlag = ((value & 0x10) != 0);
channel.dutyCycle = ((value & 0xC0) >> 6);
channel.volume = (value & 0x0F);
//if (!channel.started) channel.oscillator.start();
channel.started = true;
}
function triangleHalt(channel, value) {
channel.haltLengthCounter = ((value & 0x80) != 0);
channel.linearReloadValue = value & 0x7f;
}
function noisePeriod(value) {
var period = value & 0x0F;
noise.mode = (value & 0x80) ? 6 : 1;
noise.frequency = noisePeriodValues[period];
//noise.oscillator.playbackRate.setValueAtTime(1 / (1 << ((period)/6)), audio.currentTime);
}
function noiseLength(value) {
noise.lengthCounter = lengthCounterValues[(value & 0xf8) >> 3];
noise.startFlag = true;
}
function timer(channel, value) {
channel.frequency &= 0xf00;
channel.frequency |= value;
}
function length(channel, value) {
channel.frequency &= 0x0ff;
channel.frequency |= (value & 0x07) << 8;
channel.lengthCounter = lengthCounterValues[(value & 0xf8) >> 3];
channel.linearReload = true;
channel.startFlag = true;
}
function sweep(channel, value) {
channel.sweepEnabled = ((value & 0x80) != 0);
channel.sweepPeriod = (value & 0x70) >> 4;
channel.sweepTimer = channel.sweepPeriod + 1;
channel.sweepNegate = ((value & 0x08) != 0);
channel.sweepShift = (value & 0x07);
}
function frameCounter(value) {
//enableFrameIrq = (value & 0x40) == 0;
frameCounterMode = (value & 0x80) != 0;
lcClock3 = frameCounterMode ? 18640 : 14914;
if (frameCounterMode) resetStep();
}
function statusRead() {
//enableFrameIrq = true;
return (pulse1.lengthCounter == 0 ? 0 : 0x01) |
(pulse2.lengthCounter == 0 ? 0 : 0x02) |
(triangle.lengthCounter == 0 ? 0 : 0x04) |
(noise.lengthCounter == 0 ? 0 : 0x08) |
(dmc.bytesRemaining ? 0x10 : 0);
}
function statusWrite(value) {
if ((value & 0x01) == 0) pulse1.lengthCounter = 0;
if ((value & 0x02) == 0) pulse2.lengthCounter = 0;
if ((value & 0x04) == 0) triangle.lengthCounter = 0;
if ((value & 0x08) == 0) noise.lengthCounter = 0;
if (value & 0x10) {
if (dmc.bytesRemaining == 0) {
dmc.bytesRemaining = dmc.length;
dmc.shiftPointer = 0;
}
} else {
dmc.bytesRemaining = 0;
}
}
function dmcControl(value) {
dmc.irq = (value & 0x80) != 0;
dmc.loop = (value & 0x40) != 0;
dmc.frequency = dmcTimerValues[value & 0xf];
}
function dmcLoad(value) {
dmc.load = value & 0x7f;
}
function dmcAddress(value) {
dmc.address = 0xC000 | (value << 6);
}
function dmcLength(value) {
dmc.length = (value << 4) | 1;
}
function setApuRegisters() {
if (enableAudioEmulation) {
hwRegisters[0x4000] = new HwRegister(null, function(val) { dutyCycle(pulse1, val); });
hwRegisters[0x4004] = new HwRegister(null, function(val) { dutyCycle(pulse2, val); });
hwRegisters[0x400c] = new HwRegister(null, function(val) { dutyCycle(noise, val); });
hwRegisters[0x4001] = new HwRegister(null, function(val) { sweep(pulse1, val); });
hwRegisters[0x4005] = new HwRegister(null, function(val) { sweep(pulse2, val); });
hwRegisters[0x4002] = new HwRegister(null, function(val) { timer(pulse1, val); });
hwRegisters[0x4006] = new HwRegister(null, function(val) { timer(pulse2, val); });
hwRegisters[0x400A] = new HwRegister(null, function(val) { timer(triangle, val); });
hwRegisters[0x400E] = new HwRegister(null, noisePeriod);
hwRegisters[0x4003] = new HwRegister(null, function(val) { length(pulse1, val); });
hwRegisters[0x4007] = new HwRegister(null, function(val) { length(pulse2, val); });
hwRegisters[0x400B] = new HwRegister(null, function(val) { length(triangle, val); });
hwRegisters[0x400F] = new HwRegister(null, noiseLength);
hwRegisters[0x4008] = new HwRegister(null, function(val) { triangleHalt(triangle, val); });
hwRegisters[0x4010] = new HwRegister(null, dmcControl);
hwRegisters[0x4011] = new HwRegister(null, dmcLoad);
hwRegisters[0x4012] = new HwRegister(null, dmcAddress);
hwRegisters[0x4013] = new HwRegister(null, dmcLength);
}
hwRegisters[0x4015] = new HwRegister(statusRead, statusWrite);
hwRegisters[0x4017].write = frameCounter;
}
function resetStep() {
step = 0;
apuFrames = 0;
}
var apuInterface = {
tick: tick, // function() { running = true; },
init: initAudio,
setRegisters: setApuRegisters
};
return apuInterface;
});
apu = NewApu();
below is my javascript code which i use for combining 64 images to generate a panorama image. but when i load it first time it gives me broken image however at seconds or third time it gives me right image.
In Below combine function do all image combine process,
combinedArray contains all urls of images.
function combine(imgSources, width, height, right, left) {
return Promise.all(imgSources.map(function (url) {
return new Promise(function (resolve) {
let img = new Image();
img.crossOrigin = `anonymous`;
img.src = url;
img.onload = function () {resolve(img);};
});
})).then(function (images) {
let canvas = document.createElement(`canvas`);
canvas.width = width;
canvas.height = height;
// draw images to the canvas
let ctx = canvas.getContext(`2d`);
for (let i = 0; i < imgSources.length; i++) {
ctx.drawImage(images[i], right * i, left * i);
}
// return the resulting image in base64 form
return canvas.toDataURL(`image/jpeg`);
});
}
function generateParonama(angle = 10) {
let url=`https://cdn.yourvrtours.com/000679.fcf5f084b8794573a6789774f9bfcbaf.1122/A4CDD7C7-3F58-435A-9A5B-9522078DE10B/optimized/Point_7537F461-3868-4EFB-9B82-3A1E3CF81955/3/`;
let mainImage;
let allSides = [`f`, `r`, `b`, `l`];
let allImages = [];
let combinedArray = [];
let ind = 0;
// generates array for all 64 images
function genareteArray() {
for (let index = 0; index < allSides.length; index++) {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
let t = `${url + allSides[index] + j + '_' + i}.jpg`;
combinedArray.push(t);
}
}
}
}
// arrange array at given angle
function reArrangeArray() {
let position = 0 / 22.5;
let array2 = [];
if (position <= 8) {
position = (position + 8) * 4;
array2 = combinedArray.slice(position, 64);
combinedArray.splice(position)
combinedArray = array2.concat(combinedArray)
}
else {
position = (position - 8) * 4;
array2 = combinedArray.slice(0, position);
combinedArray.push(array2)
}
}
genareteArray();
reArrangeArray();
allSides.map((side, i) => {
return new Promise((res) => {
for (let index = 0; index < 4; index++) {
combine(
[
combinedArray[0 + ind],
combinedArray[1 + ind],
combinedArray[2 + ind],
combinedArray[3 + ind],
], 512, 2048, 0, 512)
.then(function (result) {
// result will be a column of 512 box
allImages.push(result);
if (allImages.length > 15) {
combine(allImages, 8192, 2048, 512, 0).then((r) => {
var img = new Image();
img.src = r;
document.body.appendChild(img);
});
}
});
ind = ind + 4;
}
});
});
}
generateParonama(10);
img{
max-width:600px;
}
Can you try this -
function combine(imgSources, width, height, right, left) {
return Promise.all(imgSources.map(function (url) {
return new Promise(function (resolve) {
let img = new Image();
img.crossOrigin = `anonymous`;
img.src = url;
img.onload = function () { resolve(img); };
});
})).then(function (images) {
let canvas = document.createElement(`canvas`);
canvas.width = width;
canvas.height = height;
// draw images to the canvas
let ctx = canvas.getContext(`2d`);
for (let i = 0; i < imgSources.length; i++) {
ctx.drawImage(images[i], right * i, left * i);
}
// return the resulting image in base64 form
return canvas.toDataURL(`image/jpeg`);
});
}
async function generateParonama(angle = 10) {
let url = `https://cdn.yourvrtours.com/000679.fcf5f084b8794573a6789774f9bfcbaf.1122/A4CDD7C7-3F58-435A-9A5B-9522078DE10B/optimized/Point_7537F461-3868-4EFB-9B82-3A1E3CF81955/3/`;
let mainImage;
let allSides = [`f`, `r`, `b`, `l`];
let allImages = [];
let combinedArray = [];
let ind = 0;
// generates array for all 64 images
function genareteArray() {
for (let index = 0; index < allSides.length; index++) {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
let t = `${url + allSides[index] + j + '_' + i}.jpg`;
combinedArray.push(t);
}
}
}
}
// arrange array at given angle
function reArrangeArray() {
let position = 0 / 22.5;
let array2 = [];
if (position <= 8) {
position = (position + 8) * 4;
array2 = combinedArray.slice(position, 64);
combinedArray.splice(position)
combinedArray = array2.concat(combinedArray)
}
else {
position = (position - 8) * 4;
array2 = combinedArray.slice(0, position);
combinedArray.push(array2)
}
}
genareteArray();
reArrangeArray();
console.log(combinedArray);
for (let i = 0; i < allSides.length; i++) {
const side = allSides[i];
for (let index = 0; index < 4; index++) {
var result = await combine(
[
combinedArray[0 + ind],
combinedArray[1 + ind],
combinedArray[2 + ind],
combinedArray[3 + ind],
], 512, 2048, 0, 512);
// result will be a column of 512 box
allImages.push(result);
if (allImages.length > 15) {
var r = await combine(allImages, 8192, 2048, 512, 0);
var img = new Image();
img.src = r;
document.body.appendChild(img);
}
ind = ind + 4;
}
}
}
generateParonama(10);
It is not the most efficient way of using promises. I only used async-await feature to make sure image components are getting loaded in sequence.
As an example this solution might be made more efficient by using Promise.All API (you can it by altering my code)
I am working on visualizing A*. I have a problem where the algorithm finds a path, but it is not the shortest. If I remove the part of the A* function code which is commented as 'tie-breaking', the algorithm finds the shortest path, but it searches the whole grid just like Dijkstra's algorithm, which I don't think A* is supposed to do. These are the pictures of the results with and without tie-breaking:
With Tie-Breaking
Without Tie-Breaking
What is wrong? Here is my A* function:
async a_star_search() {
this.clearSearchNotWalls();
let openSet = [];
let closedSet = [];
let start, end;
let path = [];
this.findNeighbors();
//shapes is a 2d array of squares... a grid
for (let i = 0; i < this.shapes.length; i++) {
for (let j = 0; j < this.shapes[0].length; j++) {
if (this.shapes[i][j].type == "Start") {
start = this.shapes[i][j];
}
if (this.shapes[i][j].type == "End") {
end = this.shapes[i][j];
}
}
}
openSet.push(start);
while (openSet.length > 0) {
let lowestIndex = 0;
//find lowest index
for (let i = 0; i < openSet.length; i++) {
if (openSet[i].F < openSet[lowestIndex].F)
lowestIndex = i;
}
//current node
let current = openSet[lowestIndex];
//if reached the end
if (openSet[lowestIndex] === end) {
path = [];
let temp = current;
path.push(temp);
while (temp.cameFrom) {
path.push(temp.cameFrom);
temp = temp.cameFrom;
}
console.log("Done!");
for (let i = path.length - 1; i >= 0; i--) {
this.ctxGrid.fillStyle = "#ffff00";
this.ctxGrid.fillRect(path[i].x, path[i].y, 14, 14);
await new Promise(resolve =>
setTimeout(() => {
resolve();
}, this.animDelay / 2)
);
}
break;
}
this.removeFromArray(openSet, current);
closedSet.push(current);
let my_neighbors = current.neighbors;
for (let i = 0; i < my_neighbors.length; i++) {
var neighbor = my_neighbors[i];
if (!closedSet.includes(neighbor) && neighbor.type != "Wall") {
let tempG = current.G + 1;
let newPath = false;
if (openSet.includes(neighbor)) {
if (tempG < neighbor.G) {
neighbor.G = tempG;
newPath = true;
}
} else {
neighbor.G = tempG;
newPath = true;
openSet.push(neighbor);
}
if (newPath) {
neighbor.H = this.heuristic(neighbor, end);
neighbor.G = neighbor.F + neighbor.H;
neighbor.cameFrom = current;
}
}
}
//draw
for (let i = 0; i < closedSet.length; i++) { //BLUE
this.ctxGrid.fillStyle = "#4287f5";
this.ctxGrid.fillRect(closedSet[i].x, closedSet[i].y, 14, 14);
}
for (let i = 0; i < openSet.length; i++) { //GREEN
this.ctxGrid.fillStyle = "#00ff00";
this.ctxGrid.fillRect(openSet[i].x, openSet[i].y, 14, 14);
}
await new Promise(resolve =>
setTimeout(() => {
resolve();
}, 10)
);
}
if (openSet.length <= 0) {
//no solution
}
}
Here is my heuristic function:
heuristic(a, b) {
//let d = Math.sqrt(Math.pow(b.I - a.I, 2) + Math.pow(b.J - a.J, 2));
let d = Math.sqrt(Math.pow(b.x - a.x, 2) + Math.pow(b.y - a.y, 2));
return d;
}
I have the following code that is using 3 columns to check for difference in input values.
The third column difference in my view is generated fine but while viewing it the table is not displaying it.
What is want to do is to compare base text with new text and thrid text input enteries and display the changes to user in inline or side by side format
index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<title>jsdifflib demo</title>
<link rel="stylesheet" type="text/css" href="diffview.css"/>
<script type="text/javascript" src="diffview.js"></script>
<script type="text/javascript" src="difflib.js"></script>
<style type="text/css">
body {
font-size: 12px;
font-family: Sans-Serif,serif;
}
h2 {
margin: 0.5em 0 0.1em;
text-align: center;
}
.top {
text-align: center;
}
.textInput {
display: block;
width: 49%;
float: left;
}
textarea {
width:100%;
height:300px;
}
label:hover {
text-decoration: underline;
cursor: pointer;
}
.spacer {
margin-left: 10px;
}
.viewType {
font-size: 16px;
clear: both;
text-align: center;
padding: 1em;
}
#diffoutput {
width: 100%;
}
</style>
<script type="text/javascript">
function diffUsingJS(viewType) {
"use strict";
var byId = function (id) { return document.getElementById(id); },
base = difflib.stringAsLines(byId("baseText").value),
newtxt = difflib.stringAsLines(byId("newText").value),
thirdText = difflib.stringAsLines(byId("3rdText").value),
sm = new difflib.SequenceMatcher(base, newtxt, thirdText),
opcodes = sm.get_opcodes(),
diffoutputdiv = byId("diffoutput"),
contextSize = byId("contextSize").value;
diffoutputdiv.innerHTML = "";
contextSize = contextSize || null;
diffoutputdiv.appendChild(diffview.buildView({
baseTextLines: base,
newTextLines: newtxt,
thirdTextLines: thirdText,
opcodes: opcodes,
baseTextName: "Base Text",
newTextName: "New Text",
thirdTextName: "Third Text",
contextSize: contextSize,
viewType: viewType
}));
}
</script>
</head>
<body>
<div class="top">
<strong>Context size (optional):</strong> <input type="text" id="contextSize" value="" />
</div>
<div class="textInput">
<h2>Base Text</h2>
<textarea id="baseText" >a</textarea>
</div>
<div class="textInput spacer">
<h2>New Text</h2>
<textarea id="newText">b</textarea>
</div>
<div class="textInput spacer">
<h2>3rd Text</h2>
<textarea id="3rdText">c</textarea>
</div>
<div class="viewType">
<input type="radio" name="_viewtype" id="sidebyside" onclick="diffUsingJS(0);" /> <label for="sidebyside">Side by Side Diff</label>
<input type="radio" name="_viewtype" id="inline" onclick="diffUsingJS(1);" /> <label for="inline">Inline Diff</label>
</div>
<div id="diffoutput"> </div>
</body>
</html>
diffview.js
var diffview = {
/**
* Builds and returns a visual diff view. The single parameter, `params', should contain
* the following values:
*
* - baseTextLines: the array of strings that was used as the base text input to SequenceMatcher
* - newTextLines: the array of strings that was used as the new text input to SequenceMatcher
* - opcodes: the array of arrays returned by SequenceMatcher.get_opcodes()
* - baseTextName: the title to be displayed above the base text listing in the diff view; defaults
* to "Base Text"
* - newTextName: the title to be displayed above the new text listing in the diff view; defaults
* to "New Text"
* - contextSize: the number of lines of context to show around differences; by default, all lines
* are shown
* - viewType: if 0, a side-by-side diff view is generated (default); if 1, an inline diff view is
* generated
*/
buildView: function (params) {
var baseTextLines = params.baseTextLines;
var newTextLines = params.newTextLines;
var thirdTextLines = params.thirdTextLines;
var opcodes = params.opcodes;
var baseTextName = params.baseTextName ? params.baseTextName : "Base Text";
var newTextName = params.newTextName ? params.newTextName : "New Text";
var thirdTextName = params.thirdTextName ? params.thirdTextName: "Third Text"
var contextSize = params.contextSize;
var inline = (params.viewType == 0 || params.viewType == 1) ? params.viewType : 0;
if (baseTextLines == null)
throw "Cannot build diff view; baseTextLines is not defined.";
if (newTextLines == null)
throw "Cannot build diff view; newTextLines is not defined.";
if (thirdTextLines == null)
throw "Cannot build diff view; thirdTextLines is not defined.";
if (!opcodes)
throw "Canno build diff view; opcodes is not defined.";
function celt (name, clazz) {
var e = document.createElement(name);
e.className = clazz;
return e;
}
function telt (name, text) {
var e = document.createElement(name);
e.appendChild(document.createTextNode(text));
return e;
}
function ctelt (name, clazz, text) {
var e = document.createElement(name);
e.className = clazz;
e.appendChild(document.createTextNode(text));
return e;
}
var tdata = document.createElement("thead");
var node = document.createElement("tr");
tdata.appendChild(node);
if (inline) {
node.appendChild(document.createElement("th"));
node.appendChild(document.createElement("th"));
node.appendChild(ctelt("th", "texttitle", baseTextName + " vs. " + newTextName + " vs. " + thirdTextName));
} else {
node.appendChild(document.createElement("th"));
node.appendChild(ctelt("th", "texttitle", baseTextName));
node.appendChild(document.createElement("th"));
node.appendChild(ctelt("th", "texttitle", newTextName));
node.appendChild(document.createElement("th"));
node.appendChild(ctelt("th", "texttitle", thirdTextName));
}
tdata = [tdata];
var rows = [];
var node2;
/**
* Adds two cells to the given row; if the given row corresponds to a real
* line number (based on the line index tidx and the endpoint of the
* range in question tend), then the cells will contain the line number
* and the line of text from textLines at position tidx (with the class of
* the second cell set to the name of the change represented), and tidx + 1 will
* be returned. Otherwise, tidx is returned, and two empty cells are added
* to the given row.
*/
function addCells (row, tidx, tend, textLines, change) {
if (tidx < tend) {
row.appendChild(telt("th", (tidx + 1).toString()));
row.appendChild(ctelt("td", change, textLines[tidx].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0")));
return tidx + 1;
} else {
row.appendChild(document.createElement("th"));
row.appendChild(celt("td", "empty"));
return tidx;
}
}
function addCellsInline (row, tidx, tidx2, textLines, change) {
row.appendChild(telt("th", tidx == null ? "" : (tidx + 1).toString()));
row.appendChild(telt("th", tidx2 == null ? "" : (tidx2 + 1).toString()));
row.appendChild(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0")));
}
for (var idx = 0; idx < opcodes.length; idx++) {
code = opcodes[idx];
change = code[0];
var b = code[1];
var be = code[2];
var n = code[3];
var ne = code[4];
var rowcnt = Math.max(be - b, ne - n);
var toprows = [];
var botrows = [];
for (var i = 0; i < rowcnt; i++) {
// jump ahead if we've alredy provided leading context or if this is the first range
if (contextSize && opcodes.length > 1 && ((idx > 0 && i == contextSize) || (idx == 0 && i == 0)) && change=="equal") {
var jump = rowcnt - ((idx == 0 ? 1 : 2) * contextSize);
if (jump > 1) {
toprows.push(node = document.createElement("tr"));
b += jump;
n += jump;
i += jump - 1;
node.appendChild(telt("th", "..."));
if (!inline) node.appendChild(ctelt("td", "skip", ""));
node.appendChild(telt("th", "..."));
node.appendChild(ctelt("td", "skip", ""));
// skip last lines if they're all equal
if (idx + 1 == opcodes.length) {
break;
} else {
continue;
}
}
}
toprows.push(node = document.createElement("tr"));
if (inline) {
if (change == "insert") {
addCellsInline(node, null, n++, newTextLines, change);
} else if (change == "replace") {
botrows.push(node2 = document.createElement("tr"));
if (b < be) addCellsInline(node, b++, null, baseTextLines, "delete");
if (n < ne) addCellsInline(node2, null, n++, newTextLines, "insert");
if (n < ne) addCellsInline(node2, null, n++, thirdTextLines, "insert");
} else if (change == "delete") {
addCellsInline(node, b++, null, baseTextLines, change);
} else {
// equal
addCellsInline(node, b++, n++, baseTextLines, change);
}
} else {
b = addCells(node, b, be, baseTextLines, change);
n = addCells(node, n, ne, newTextLines, change);
n = addCells(node, n, ne, thirdTextLines, change);
}
}
for (var i = 0; i < toprows.length; i++) rows.push(toprows[i]);
for (var i = 0; i < botrows.length; i++) rows.push(botrows[i]);
}
tdata.push(node = document.createElement("tbody"));
for (var idx in rows) rows.hasOwnProperty(idx) && node.appendChild(rows[idx]);
node = celt("table", "diff" + (inline ? " inlinediff" : ""));
for (var idx in tdata) tdata.hasOwnProperty(idx) && node.appendChild(tdata[idx]);
return node;
}
};
difflib.js
var __whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true};
var difflib = {
defaultJunkFunction: function (c) {
return __whitespace.hasOwnProperty(c);
},
stripLinebreaks: function (str) { return str.replace(/^[\n\r]*|[\n\r]*$/g, ""); },
stringAsLines: function (str) {
var lfpos = str.indexOf("\n");
var crpos = str.indexOf("\r");
var linebreak = ((lfpos > -1 && crpos > -1) || crpos < 0) ? "\n" : "\r";
var lines = str.split(linebreak);
for (var i = 0; i < lines.length; i++) {
lines[i] = difflib.stripLinebreaks(lines[i]);
}
return lines;
},
// iteration-based reduce implementation
__reduce: function (func, list, initial) {
if (initial != null) {
var value = initial;
var idx = 0;
} else if (list) {
var value = list[0];
var idx = 1;
} else {
return null;
}
for (; idx < list.length; idx++) {
value = func(value, list[idx]);
}
return value;
},
// comparison function for sorting lists of numeric tuples
__ntuplecomp: function (a, b) {
var mlen = Math.max(a.length, b.length);
for (var i = 0; i < mlen; i++) {
if (a[i] < b[i]) return -1;
if (a[i] > b[i]) return 1;
}
return a.length == b.length ? 0 : (a.length < b.length ? -1 : 1);
},
__calculate_ratio: function (matches, length) {
return length ? 2.0 * matches / length : 1.0;
},
// returns a function that returns true if a key passed to the returned function
// is in the dict (js object) provided to this function; replaces being able to
// carry around dict.has_key in python...
__isindict: function (dict) {
return function (key) { return dict.hasOwnProperty(key); };
},
// replacement for python's dict.get function -- need easy default values
__dictget: function (dict, key, defaultValue) {
return dict.hasOwnProperty(key) ? dict[key] : defaultValue;
},
SequenceMatcher: function (a, b, c, isjunk) {
this.set_seqs = function (a, b, c, ) {
this.set_seq1(a);
this.set_seq2(b);
this.set_seq3(c);
}
this.set_seq1 = function (a) {
if (a == this.a) return;
this.a = a;
this.matching_blocks = this.opcodes = null;
}
this.set_seq2 = function (b) {
console.log("Value in set ", b)
if (b == this.b) return;
this.b = b;
this.matching_blocks = this.opcodes = this.fullbcount = null;
this.__chain_b();
}
this.set_seq3 = function (c) {
console.log("Value in set ", c)
if (c == this.c) return;
this.c = c;
this.matching_blocks = this.opcodes = this.fullbcount = null;
this.__chain_c();
}
this.__chain_c = function () {
console.log("This is ", this.c)
var c = this.c;
var n = c.length;
var c2j = this.c2j = {};
var populardict = {};
for (var i = 0; i < c.length; i++) {
var celt = c[i];
if (c2j.hasOwnProperty(celt)) {
var indices = c2j[elt];
if (n >= 200 && indices.length * 100 > n) {
populardict[celt] = 1;
delete c2j[celt];
} else {
indices.push(i);
}
} else {
c2j[celt] = [i];
}
}
for (var elt in populardict) {
if (populardict.hasOwnProperty(elt)) {
delete c2j[elt];
}
}
var isjunk = this.isjunk;
var junkdict = {};
if (isjunk) {
for (var elt in populardict) {
if (populardict.hasOwnProperty(elt) && isjunk(elt)) {
junkdict[elt] = 1;
delete populardict[elt];
}
}
for (var elt in c2j) {
if (c2j.hasOwnProperty(elt) && isjunk(elt)) {
junkdict[elt] = 1;
delete c2j[elt];
}
}
}
this.iscjunk = difflib.__isindict(junkdict);
this.iscpopular = difflib.__isindict(populardict);
}
this.__chain_b = function () {
var b = this.b;
var n = b.length;
var b2j = this.b2j = {};
var populardict = {};
for (var i = 0; i < b.length; i++) {
var elt = b[i];
if (b2j.hasOwnProperty(elt)) {
var indices = b2j[elt];
if (n >= 200 && indices.length * 100 > n) {
populardict[elt] = 1;
delete b2j[elt];
} else {
indices.push(i);
}
} else {
b2j[elt] = [i];
}
}
for (var elt in populardict) {
if (populardict.hasOwnProperty(elt)) {
delete b2j[elt];
}
}
var isjunk = this.isjunk;
var junkdict = {};
if (isjunk) {
for (var elt in populardict) {
if (populardict.hasOwnProperty(elt) && isjunk(elt)) {
junkdict[elt] = 1;
delete populardict[elt];
}
}
for (var elt in b2j) {
if (b2j.hasOwnProperty(elt) && isjunk(elt)) {
junkdict[elt] = 1;
delete b2j[elt];
}
}
}
this.isbjunk = difflib.__isindict(junkdict);
this.isbpopular = difflib.__isindict(populardict);
}
this.find_longest_match = function (alo, ahi, blo, bhi) {
var a = this.a;
var b = this.b;
var b2j = this.b2j;
var isbjunk = this.isbjunk;
var besti = alo;
var bestj = blo;
var bestsize = 0;
var j = null;
var k;
var j2len = {};
var nothing = [];
for (var i = alo; i < ahi; i++) {
var newj2len = {};
var jdict = difflib.__dictget(b2j, a[i], nothing);
for (var jkey in jdict) {
if (jdict.hasOwnProperty(jkey)) {
j = jdict[jkey];
if (j < blo) continue;
if (j >= bhi) break;
newj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1;
if (k > bestsize) {
besti = i - k + 1;
bestj = j - k + 1;
bestsize = k;
}
}
}
j2len = newj2len;
}
while (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) {
besti--;
bestj--;
bestsize++;
}
while (besti + bestsize < ahi && bestj + bestsize < bhi &&
!isbjunk(b[bestj + bestsize]) &&
a[besti + bestsize] == b[bestj + bestsize]) {
bestsize++;
}
while (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) {
besti--;
bestj--;
bestsize++;
}
while (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b[bestj + bestsize]) &&
a[besti + bestsize] == b[bestj + bestsize]) {
bestsize++;
}
return [besti, bestj, bestsize];
}
this.get_matching_blocks = function () {
if (this.matching_blocks != null) return this.matching_blocks;
var la = this.a.length;
var lb = this.b.length;
var queue = [[0, la, 0, lb]];
var matching_blocks = [];
var alo, ahi, blo, bhi, qi, i, j, k, x;
while (queue.length) {
qi = queue.pop();
alo = qi[0];
ahi = qi[1];
blo = qi[2];
bhi = qi[3];
x = this.find_longest_match(alo, ahi, blo, bhi);
i = x[0];
j = x[1];
k = x[2];
if (k) {
matching_blocks.push(x);
if (alo < i && blo < j)
queue.push([alo, i, blo, j]);
if (i+k < ahi && j+k < bhi)
queue.push([i + k, ahi, j + k, bhi]);
}
}
matching_blocks.sort(difflib.__ntuplecomp);
var i1 = 0, j1 = 0, k1 = 0, block = 0;
var i2, j2, k2;
var non_adjacent = [];
for (var idx in matching_blocks) {
if (matching_blocks.hasOwnProperty(idx)) {
block = matching_blocks[idx];
i2 = block[0];
j2 = block[1];
k2 = block[2];
if (i1 + k1 == i2 && j1 + k1 == j2) {
k1 += k2;
} else {
if (k1) non_adjacent.push([i1, j1, k1]);
i1 = i2;
j1 = j2;
k1 = k2;
}
}
}
if (k1) non_adjacent.push([i1, j1, k1]);
non_adjacent.push([la, lb, 0]);
this.matching_blocks = non_adjacent;
return this.matching_blocks;
}
this.get_opcodes = function () {
if (this.opcodes != null) return this.opcodes;
var i = 0;
var j = 0;
var answer = [];
this.opcodes = answer;
var block, ai, bj, size, tag;
var blocks = this.get_matching_blocks();
for (var idx in blocks) {
if (blocks.hasOwnProperty(idx)) {
block = blocks[idx];
ai = block[0];
bj = block[1];
size = block[2];
tag = '';
if (i < ai && j < bj) {
tag = 'replace';
} else if (i < ai) {
tag = 'delete';
} else if (j < bj) {
tag = 'insert';
}
if (tag) answer.push([tag, i, ai, j, bj]);
i = ai + size;
j = bj + size;
if (size) answer.push(['equal', ai, i, bj, j]);
}
}
return answer;
}
// this is a generator function in the python lib, which of course is not supported in javascript
// the reimplementation builds up the grouped opcodes into a list in their entirety and returns that.
this.get_grouped_opcodes = function (n) {
if (!n) n = 3;
var codes = this.get_opcodes();
if (!codes) codes = [["equal", 0, 1, 0, 1]];
var code, tag, i1, i2, j1, j2;
if (codes[0][0] == 'equal') {
code = codes[0];
tag = code[0];
i1 = code[1];
i2 = code[2];
j1 = code[3];
j2 = code[4];
codes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n), j2];
}
if (codes[codes.length - 1][0] == 'equal') {
code = codes[codes.length - 1];
tag = code[0];
i1 = code[1];
i2 = code[2];
j1 = code[3];
j2 = code[4];
codes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)];
}
var nn = n + n;
var group = [];
var groups = [];
for (var idx in codes) {
if (codes.hasOwnProperty(idx)) {
code = codes[idx];
tag = code[0];
i1 = code[1];
i2 = code[2];
j1 = code[3];
j2 = code[4];
if (tag == 'equal' && i2 - i1 > nn) {
group.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]);
groups.push(group);
group = [];
i1 = Math.max(i1, i2-n);
j1 = Math.max(j1, j2-n);
}
group.push([tag, i1, i2, j1, j2]);
}
}
if (group && !(group.length == 1 && group[0][0] == 'equal')) groups.push(group)
return groups;
}
this.ratio = function () {
matches = difflib.__reduce(
function (sum, triple) { return sum + triple[triple.length - 1]; },
this.get_matching_blocks(), 0);
return difflib.__calculate_ratio(matches, this.a.length + this.b.length);
}
this.quick_ratio = function () {
var fullbcount, elt;
if (this.fullbcount == null) {
this.fullbcount = fullbcount = {};
for (var i = 0; i < this.b.length; i++) {
elt = this.b[i];
fullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1;
}
}
fullbcount = this.fullbcount;
var avail = {};
var availhas = difflib.__isindict(avail);
var matches = numb = 0;
for (var i = 0; i < this.a.length; i++) {
elt = this.a[i];
if (availhas(elt)) {
numb = avail[elt];
} else {
numb = difflib.__dictget(fullbcount, elt, 0);
}
avail[elt] = numb - 1;
if (numb > 0) matches++;
}
return difflib.__calculate_ratio(matches, this.a.length + this.b.length);
}
this.real_quick_ratio = function () {
var la = this.a.length;
var lb = this.b.length;
return _calculate_ratio(Math.min(la, lb), la + lb);
}
this.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction;
this.a = this.b = null;
this.set_seqs(a, b, c);
}
};
I'm doing a coding challenge from the coding train, and I'm trying to improve on his code. The idea is that the cars are driving around a race track. When I went back to check something, I noticed that I misspelled "activation: sigmoid", as in activation function. When I fixed it, the cars seemed to be driving in circles.
I'm a very new coder (as I am 12 years old), so many things in my code are broken, hard to understand, or just not finished. I'm also pretty new to stack overflow, so I might be breaking a lot of rules.
The link to download my project is here: https://1drv.ms/u/s!ApmY_SAko19ChzCKe5uNT7I9EZAX?e=YUg2ff
The misspelled words are at lines 29 and 34 in the nn.js file.
car.js
function pldistance(p1, p2, x, y) {
const num = abs((p2.y - p1.y) * x - (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x);
const den = p5.Vector.dist(p1, p2);
return num / den;
}
class Car {
constructor(brain, color = [random(255), random(255), random(255)]) {
this.colorGene = color;
this.dead = false;
this.finished = false;
this.fitness = 0;
this.rays = [];
this.wallRays = [];
this.degreeOfSight = degreeOfSight;
this.degreeOfRays = degreeOfSight / (numOfRays - 1);
if (this.degreeOfSight == 360) {
this.degreeOfRays = degreeOfSight / numOfRays;
}
this.pos = createVector(start.x, start.y);
this.vel = createVector();
this.acc = createVector();
this.sight = sight;
this.maxspeed = maxspeed;
this.maxforce = maxTurningSpeed;
this.currentGoal = 0;
this.timeTillDeadC = timeTillDead;
this.timeTillDead = this.timeTillDeadC;
this.goal;
this.rate = mutationRate;
if (degreeOfSight != 360) {
for (let a = -(this.degreeOfSight / 2); a <= this.degreeOfSight / 2; a += this.degreeOfRays) {
this.rays.push(new Ray(this.pos, radians(a)));
}
} else {
for (let a = -(this.degreeOfSight / 2); a < this.degreeOfSight / 2; a += this.degreeOfRays) {
this.rays.push(new Ray(this.pos, radians(a)));
}
}
for (let a = 0; a < 360; a += 45) {
this.wallRays.push(new Ray(this.pos, radians(a)));
}
if (brain) {
this.brain = brain.copy();
} else {
this.brain = new NeuralNetwork(this.rays.length + 2, 16, 2);
}
}
applyForce(force) {
this.acc.add(force);
}
update(x, y) {
this.timeTillDead--;
if (this.timeTillDead <= 0) {
this.dead = true;
}
if (!this.dead || this.finished) {
this.pos.add(this.vel);
this.vel.add(this.acc);
this.vel.limit(this.maxspeed);
this.acc.set(0, 0);
}
for (let i = 0; i < this.rays.length; i++) {
this.rays[i].rotate(this.vel.heading());
}
for (let i = 0; i < this.wallRays.length; i++) {
this.wallRays[i].rotate(this.vel.heading());
}
}
show(walls) {
push();
translate(this.pos.x, this.pos.y);
if (visualization) {
fill(this.colorGene[0], this.colorGene[1], this.colorGene[1]);
} else {
fill(0);
}
stroke(255);
const heading = this.vel.heading();
rotate(heading);
rectMode(CENTER);
rect(0, 0, 10, 5);
pop();
if (!this.dead) {
checkpoints[this.currentGoal].show();
}
for (let i = 0; i < this.rays.length; i++) {
let closest = null;
let record = this.sight;
for (let wall of walls) {
const pt = this.rays[i].cast(wall);
if (pt) {
const d = p5.Vector.dist(this.pos, pt);
if (d < record && d < this.sight) {
record = d;
closest = pt;
}
}
}
if (closest) {
if (showLines) {
ellipse(closest.x, closest.y, 4)
stroke(255, 100)
line(this.pos.x, this.pos.y, closest.x, closest.y);
}
}
}
}
check(checkpoints, walls) {
if (!this.dead) {
this.goal = checkpoints[this.currentGoal];
const d = pldistance(this.goal.a, this.goal.b, this.pos.x, this.pos.y);
if (d < 5) {
this.fitness++;
this.currentGoal++;
this.timeTillDead = this.timeTillDeadC;
if (this.currentGoal == checkpoints.length) {
this.finished = true;
this.fitness = this.fitness * 1.5;
if (endBarrier) {
this.dead = true;
} else {
this.currentGoal = 0;
}
}
}
}
for (let i = 0; i < this.wallRays.length; i++) {
let closest = null;
let record = this.sight;
for (let wall of walls) {
const pt = this.wallRays[i].cast(wall);
if (pt) {
const d = p5.Vector.dist(this.pos, pt);
if (d < record) {
record = d;
closest = pt;
}
}
}
if (record < 4) {
this.dead = true;
}
}
}
look(walls) {
const inputs = [];
for (let i = 0; i < this.wallRays.length; i++) {
let closest = null;
let record = this.sight;
for (let wall of walls) {
const pt = this.rays[i].cast(wall);
if (pt) {
const d = p5.Vector.dist(this.pos, pt);
if (d < record && d < this.sight) {
record = d;
closest = pt;
}
}
}
inputs[i] = map(record, 0, 50, 1, 0);
}
inputs.push(end.x);
inputs.push(end.y);
const output = this.brain.predict(inputs);
let angle = map(output[0], 0, 1, -PI, PI);
let speed = map(output[1], 0, 1, -this.maxspeed, this.maxspeed);
angle += this.vel.heading();
const steering = p5.Vector.fromAngle(angle);
steering.setMag(speed);
steering.limit(this.maxforce);
this.applyForce(steering);
}
mutateDemBabies() {
if (this.finished) {
this.rate = finishingMutationRate;
}
this.brain.mutate(this.rate);
let changeColor = this.brain.mutated();
if (changeColor) {
for (let color of this.colorGene) {
let r = map(random(20), 0, 20, -25, 25);
color += r;
}
}
this.rate = mutationRate;
}
dispose() {
this.brain.dispose();
}
}
nn.js
//<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#1.1.0/dist/tf.min.js"></script>
class NeuralNetwork {
//this how many inputs, hidden, and output nodes there are. modelC is the brain that we want to copy to give to the new bird
constructor(inputNumber, hiddenNumber, outputNumber, modelC) {
if (modelC instanceof tf.Sequential) {
//this is the making a copy of the neural network
this.input_nodes = inputNumber;
this.hidden_nodes = hiddenNumber;
this.output_nodes = outputNumber;
this.model = modelC;
} else {
//this is the creating a random brain
this.input_nodes = inputNumber;
this.hidden_nodes = hiddenNumber;
this.output_nodes = outputNumber;
this.model = this.createBrain();
}
this.changeColor = false;
}
createBrain() {
//the model is the neural network
const model = tf.sequential();
//configuring the hidden layer
const hiddenLayer = tf.layers.dense({
units: this.hidden_nodes,
inputShape: [this.input_nodes],
activaation: "sigmoid"
});
//configuring the output layer
const outputLayer = tf.layers.dense({
units: this.output_nodes,
activaation: "sigmoid"
});
//adding the hidden layer to the model
model.add(hiddenLayer);
//adding the output layer to the model
model.add(outputLayer);
//returning the model
return model;
}
predict(inputs) {
//clearing the tensors after using them
//then returning the output
return tf.tidy(() => {
//creating a tensor with the inputs
const xs = tf.tensor2d([inputs]);
//running the inputs through the neural network
const ys = this.model.predict(xs);
//getting the raw numbers from the tensor object
const outputs = ys.dataSync();
//returning the outputs
return outputs;
});
}
copy() {
//clearing the tensors after using them
//then returning the output
return tf.tidy(() => {
//creating a new neural network
const modelCopy = this.createBrain();
//getting the weights from the old neural network
const weights = this.model.getWeights();
//setting the new weights
modelCopy.setWeights(weights);
//making a new network but this time with all the weights then returning it
return new NeuralNetwork(
this.input_nodes,
this.hidden_nodes,
this.output_nodes,
modelCopy
);
});
}
mutate(rate, colorGene) {
//clearing the tensors after using them
tf.tidy(() => {
this.changeColor = false;
//getting the weights so that we can change them later
const weights = this.model.getWeights();
//the variable that will be holding the mutated weights
const mutatedWeights = [];
for (let i = 0; i < weights.length; i++) {
//getting the shape of the current weights
let shape = weights[i].shape;
//making a copy of the raw numbers from the object tensor
//dataSync gets the numbers, but doesn't make a copy, so slice will make the copy
let values = weights[i].dataSync().slice();
for (let j = 0; j < values.length; j++) {
//if the random number is less than mutation rate the it runs the code
if (random(1) < rate) {
this.changeColor = true;
//mutating the value
//randomGaussianis returns a float from a series of numbers with a mean of 0
values[j] = values[j] + randomGaussian();
}
}
//holding the new value of each weight
mutatedWeights[i] = tf.tensor(values, shape);
}
//setting the mutated weights as the new weights
this.model.setWeights(mutatedWeights);
});
}
mutated() {
if (this.changeColor) {
this.changeColor = false;
return true;
} else {
this.changeColor = false;
return false;
}
}
dispose() {
//disposing the brain so that memory doesn't leak
this.model.dispose();
}
}