Generating random path in 2d array using javascript - javascript

I am trying to make a path in 2d array it supposes to go from point A to B currently I only try to make a path currently I have this script below some time it works sometimes it gets stuck in an endless loop.
My final goal is to make three random paths going from point A to B, C, D...
I hope you guys can help me.
I don't want to use a star since I want it to be random and not the shortest path.
var startRow = 0;
var startCol = 0;
var curRowPos = 0;
var curColPos = 0;
var width = 10;
var height = 10;
var mapCode = 0;
var finalCode = 0;
var dirction = [];
var map = GenerateArray(width,height,true);
var pos = {
start : [],
end : [],
}
GeneratePath();
printmap();
function printmap(){
var res = '';
console.log('-----------------------------------')
for(var i = 0; i < width; i++){
for(var j = 0; j < height; j++){
res += map[i][j] +','
}
res += '\n'
}
console.log(res)
console.log('-----------------------------------')
}
function GenerateArray(width,height,empty){
var arr = new Array(2);
for (var x = 0; x < width; x++){
arr[x] = new Array(2);
for (var y = 0; y < height; y++){
arr[x][y] = 0;
}
}
return arr;
}
function GeneratePath()
{
startRow = curRowPos = Math.floor(Math.random() * width);
curColPos = startCol; // pick starting column, row is 0
map[startRow][ startCol] = 1; // assign 1 to start square
pos.start = [startRow,startCol]
while ( finalCode == 0 )
{
getDirections();
SelectDir();
printmap(); // random selection of available directions
}
return;
}
function getDirections(){
var N ,S,W,E;
N = curRowPos - 1 > 0 && map[curRowPos - 1][ curColPos] == 0;
S = curRowPos + 1 < width && map[curRowPos + 1][ curColPos] == 0;
W = curColPos + 1 < height && map[curRowPos ][ curColPos+ 1] == 0;
E = curColPos -1 >= 0 && curColPos < height && map[curRowPos ][ curColPos- 1] == 0 ;
direction = [];
if(N){
direction.push("north");
}
if(S){
direction.push("south");
}
if(W){
direction.push("west");
}
if(E){
direction.push("east");
}
}
function SelectDir(){
var select = 0;
var selection = "";
if (direction.length != 0) // select random direction from list
{
select = Math.floor(Math.random() * direction.length);
selection = direction[select];
}
else selection = "blocked"; // no moves available
if (curColPos == width-1)
{
finalCode = 1; // set final square if row 7
}
if(finalCode == 1){
pos.end = [startRow,startCol]
return;
}
switch (selection)
{
case "blocked":
map[curRowPos][ curColPos] = -1;
//BackUp(); // back up to last avail square with move options
break;
case "north":
curRowPos = curRowPos - 1;
map[curRowPos][curColPos] = 1;
direction = [];
break;
case "south":
curRowPos = curRowPos + 1;
map[curRowPos][curColPos] = 1;
direction = [];
break;
case "east":
curColPos = curColPos - 1;
map[curRowPos][curColPos] = 1;
direction = [];
break;
case "west":
curColPos = curColPos + 1;
map[curRowPos][curColPos] = 1;
direction = [];
break;
default:
break;
}
return;
}
function BackUp()
{
console.log("backup")
map[curRowPos][curColPos] = 0; // set element to indicate no movement
direction = [];
var tempN = 0;
var tempS = 0;
var tempE = 0;
var tempW = 0;
// start logic to determine last space before dead end.
// gets mapCode for surrounding square, assign to temp int.
printmap();
if (curRowPos < width)
{
tempN = map[curRowPos-1][ curColPos];
}
if (curRowPos > 0)
{
tempS = map[curRowPos + 1][ curColPos];
}
if (curColPos < width)
{
tempE = map[curRowPos][ curColPos-1];
}
if (curColPos > 0)
{
tempW = map[curRowPos][ curColPos+1];
}
// determine highest value of temp int's. Highest value is the
// square previous to dead end. Set curRowPos or or curColPos accordingly.
if (tempN > tempS)
{
if (tempN > tempE)
{
if (tempN > tempW)
{
curRowPos = curRowPos + 1;
return;
}
else
{
curColPos = curColPos - 1;
return;
}
}
else if (tempE > tempW)
{
curColPos = curColPos + 1;
return;
}
else
{
curColPos = curColPos - 1;
return;
}
}
else if (tempS > tempE)
{
if (tempS > tempW)
{
curRowPos = curRowPos - 1;
return;
}
else
{
curColPos = curColPos - 1;
return;
}
}
else if (tempE > tempW)
{
curColPos = curColPos + 1;
return;
}
else
{
curColPos = curColPos - 1;
return;
}
}

First of all, I would not use global variables for that, as that makes the code more difficult to understand when you want to read it later on. Replacing them with function arguments should work in this case.
It seems that hitting a dead end will make the algorithm reach an endless loop - as it won't do anything in that case.
There are two more problems - the first one is, how do you define a random path? From what distribution should it be? Should it be a random shortest path? (Generally, the shortest path isn't unique.) Should it be as long as possible? (That's the Traveling Salesman Problem, something I believe you want to avoid)
This brings me to the second problem - the backtracking will make it take exponential time if unlucky. Maybe you want the path to be short, but not always the shortest path. In that case, the A* algorithm can still be used! Feeding it random weights for the edges will make it take a path that is still the shortest according to some metric, but that metric will be different for different paths - and this is even parametrizable - adding less noise will make the resulting path shorter and closer to the shortest path, while adding more noise will make it the algorithm take longer (but still quadratic time because this is the worst case of A*)

Related

How to write a number pattern generator program in JavaScript?

I have to make a pattern like this:
=========1=========
=======22122=======
====33322122333====
4444333221223334444
I have not found the logic yet. I tried to code it, but the output is different.
Here is the output of my working code snippet:
----1-----
---123----
--12345---
-1234567--
123456789-
function nomor3(input){
let temp = '';
for (let x = 1; x <= input; x++){
for (let y = input ; y > x; y--){
temp += "-";
}
for (let z = 1; z <= (x * 2) - 1; z++){
temp += z;
}
for (let k = input; k >= x; k--){
temp += "-";
}
temp += '\n';
}
return temp
}
console.log(nomor3(5));
The logic for each level - say 4th level - it begins with the digit of the level to the count of the digit, then one less and so on. So line 4 looks like 4444-333-22-1 and backwards (dashes added for demonstration).
So here we build each line like that, starting from the biggest so we know its length so we can center other lines with dashes. We use arrays here and reversing them because it's easier than strings. But lastly we join so we have a string.
function pyramide(level) {
var len = null;
var result = [];
while (level > 0) {
var arr = [];
for (var i = level; i > 1; i--) {
for (var repeat = 0; repeat < i; repeat++) {
arr.push(i)
}
}
var str_level = arr.join("") + "1" + arr.reverse().join("");
if (len === null) {
len = str_level.length;
}
while (str_level.length < len) {
str_level = "-" + str_level + "-";
}
result.push(str_level);
level--;
}
return result.reverse().join("\n");
}
console.log(pyramide(5))

Prevent the parent object from being changed by an array of child objects

I have a object that has many child objects and I only want the children to change a specific value, not affecting the parent object. In short, the children have values that WILL change but the children are changing the parent as well, which is bad.
Only Relevant JS:
let p1 = {super:{vel:{x:3,y:0}, timeLeft:100}} // Very Shortened player objects
let p2 = {y:100}
var supers = {p1:[], p2:[]};
function draw(){
superProject();
if (p1.super.timeLeft >= 1 && p1.super.type == 'MISSILE'){
p1.super.timeLeft--;
let randY = Math.round(random(50, 150));
let missile = {x:40, y:randY, vel:p1.super.vel, dist:{x:0, y:0}}
supers.p1.push(missile);
}
}
function superProject(){
if (supers.p1.length > 0){
for (let i = 0; i < supers.p1.length; i++){
supers.p1[i].x += supers.p1[i].vel.x;
supers.p1[i].y += supers.p1[i].vel.y;
let distY = 0;
distY = p2.y - supers.p1[i].y;
let distX = 0;
distX = 170 - supers.p1[i].x
supers.p1[i].dist.x = distX;
supers.p1[i].dist.y = distY;
if (distY > 0){
supers.p1[i].vel.y += 1;
} else if (distY < 0){
supers.p1[i].vel.y -= 1;
}
circle(supers.p1[i].x, supers.p1[i].y, 4);
}
}
}
Link to full source. I would appreciate any help you can give me.

Is there a way to reduce the number of coordinates in a complex enclosed SVG path?

What I'd like to do is take an SVG shape drawn by an enclosed path (in this case, a region of a map) and reduce the number of points to create a simpler shape.
I've tried implementing the Ramer-Douglas-Peucker algorithm to reduce the number of points. For example, here's a fiddle using the simplify.js library:
https://jsfiddle.net/0t3n8762/
After reading about the issue, if I understood it correctly, it seems the algorithm isn't really designed to work on enclosed shapes but open ended paths. I tried splitting each path into two (so there are two lines that together make the entire shape) and running the algorithm on each before recombining them, though the results seem essentially identical:
https://jsfiddle.net/caqwL3t7/
It may be (and indeed is quite likely) that I'm just not grasping how the algorithm is supposed to work and am implementing it incorrectly. Or perhaps that I should be trying a completely different method altogether.
coords = pathToCoords($("#JP-01").attr("d"));
for (var i = 0; i < coords.length; i++) {
newCoords[i] = simplify(coords[i], 2);
}
newPath = coordsToPath(newCoords);
$("#JP-01").attr("d", newPath);
What I would want to produce is a simpler path that still retains the overall shape of the original, drawn with fewer points. The actual result is a distorted shape that shares little in common with the original.
As Paul pointed out in the comments, you haven't considered about l and v command in a path.
I made a snippet to show how to achieve your goal but it won't work in all the cases (I guess) and it still needs to be improved.
Kindly find the snippet and comments below.
$("#simplify-1").on("click", function(){
var coords;
var newCoords = [];
var newPath;
var coordsObject;
// get the coordinates from a given path
function pathToCoords(path) {
// save each path individually (.i.e islands are stored separately)
var data = path.match(/(?<=m).*?(?=z)/igs);
var coordsArray = [];
var objectArray = [];
var objectArrayA = [];
var objectArrayB = [];
var objectContainer = [];
// split each pair of coordinates into their own arrays
for (var i = 0; i < data.length; i++) {
// should remove anything between h or v and l instead?
data[i] = data[i].split(/[LlHhVv]/);
coordsArray[i] = [];
for (var j = 0; j < data[i].length; j++) {
coordsArray[i].push(data[i][j].split(",").map(Number));
}
}
// convert each pair of coordinates into an object of x and y
for (var i = 0; i < coordsArray.length; i++) {
objectArray[i] = [];
for (var j = 0; j < coordsArray[i].length; j++) {
objectArray[i].push({
x: coordsArray[i][j][0],
y: coordsArray[i][j][1]
});
}
// split each array of coordinates in half
var halfway = Math.floor(objectArray[i].length / 2);
objectArrayB[i] = JSON.parse(JSON.stringify(objectArray[i]));;
objectArrayA[i] = objectArrayB[i].splice(0, halfway);
}
objectContainer = [objectArrayA, objectArrayB];
return objectContainer;
}
// convert the coordinates back into a string for the path
function coordsToPath(objectContainer) {
var objectArray = [];
var coordsArray = [];
var data;
// recombine the two objectArrays
for (var i = 0; i < objectContainer[0].length; i++) {
objectArray[i] = objectContainer[0][i].concat(objectContainer[1][i])
}
for (var i = 0; i < objectArray.length; i++) {
coordsArray[i] = [];
// take the X and Y values from the objectArray and strip the unwanted information
for (var j = 0; j < objectArray[i].length; j++) {
if (j == 0) {
// add 'M' in front of the first entry
coordsArray[i].push("M" + Object.values(objectArray[i][j]));
} else if (j == objectArray[i].length - 1) {
// add 'z' to the end of the last entry
coordsArray[i].push("l" + Object.values(objectArray[i][j]) + "z");
} else {
// add 'l' in front of each coordinate pair
coordsArray[i].push("l" + Object.values(objectArray[i][j]));
}
}
coordsArray[i] = coordsArray[i].toString();
}
// put everything back into a single valid SVG path string
data = coordsArray.join("");
return data;
}
// -- simplify.js -- //
/*
(c) 2017, Vladimir Agafonkin
Simplify.js, a high-performance JS polyline simplification library
mourner.github.io/simplify-js
*/
(function() {
'use strict';
// to suit your point format, run search/replace for '.x' and '.y';
// for 3D version, see 3d branch (configurability would draw significant performance overhead)
// square distance between 2 points
function getSqDist(p1, p2) {
var dx = p1.x - p2.x,
dy = p1.y - p2.y;
return dx * dx + dy * dy;
}
// square distance from a point to a segment
function getSqSegDist(p, p1, p2) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y;
if (dx !== 0 || dy !== 0) {
var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return dx * dx + dy * dy;
}
// rest of the code doesn't care about point format
// basic distance-based simplification
function simplifyRadialDist(points, sqTolerance) {
var prevPoint = points[0],
newPoints = [prevPoint],
point;
for (var i = 1, len = points.length; i < len; i++) {
point = points[i];
if (getSqDist(point, prevPoint) > sqTolerance) {
newPoints.push(point);
prevPoint = point;
}
}
if (prevPoint !== point) newPoints.push(point);
return newPoints;
}
function simplifyDPStep(points, first, last, sqTolerance, simplified) {
var maxSqDist = sqTolerance,
index;
for (var i = first + 1; i < last; i++) {
var sqDist = getSqSegDist(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
simplified.push(points[index]);
if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
}
}
// simplification using Ramer-Douglas-Peucker algorithm
function simplifyDouglasPeucker(points, sqTolerance) {
var last = points.length - 1;
var simplified = [points[0]];
simplifyDPStep(points, 0, last, sqTolerance, simplified);
simplified.push(points[last]);
return simplified;
}
// both algorithms combined for awesome performance
function simplify(points, tolerance, highestQuality) {
if (points.length <= 2) return points;
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
points = simplifyDouglasPeucker(points, sqTolerance);
return points;
}
// export as AMD module / Node module / browser or worker variable
if (typeof define === 'function' && define.amd) define(function() {
return simplify;
});
else if (typeof module !== 'undefined') {
module.exports = simplify;
module.exports.default = simplify;
} else if (typeof self !== 'undefined') self.simplify = simplify;
else window.simplify = simplify;
})();
// -- end simplify.js -- //
coords = pathToCoords($("#OriginalJP-01").attr("d"));
for (var i = 0; i < coords.length; i++) {
newCoords[i] = [];
for (var j = 0; j < coords[i].length; j++) {
newCoords[i][j] = simplify(coords[i][j], 1);
}
}
newPath = coordsToPath(newCoords);
$("#JP-01").attr("d", newPath);
});
$("#simplify-2").on("click", function(){
let d = $("#OriginalJP-01").attr("d");
let coordsArray = [];
let data = d.match(/(?<=m).*?(?=z)/igs);
// split each pair of coordinates into the array as an object {x, y}
for (var i = 0; i < data.length; i++) {
let ca = coordsArray[i] = [];
// split data[i] into each coordinate text
let matches = data[i].match(/((\w?-?\d+(\.\d+)?)(,-?\d+(\.\d+)?)?)/g);
for(let j=0;j<matches.length;j++){
let x, y,
text = matches[j],
// split with comma and convert it to a number
temp = text.split(",").map(v=>+v.replace(/^[^\-\d]/g,""));
switch(text[0]){
default:
case "L": // absolute
x = temp[0];
y = temp[1];
break;
case "l": // relative
x = ca[j-1].x + temp[0];
y = ca[j-1].y + temp[1];
break;
case "V": // absolute
x = ca[j-1].x;
y = temp[0];
break;
case "v": // relative
x = ca[j-1].x;
y = ca[j-1].y + temp[0];
break;
case "H": // absolute
x = temp[0];
y = ca[j-1].y;
break;
case "h": // relative
x = ca[j-1].x + temp[0];
y = ca[j-1].y;
break;
}
x = +x.toFixed(2);
y = +y.toFixed(2);
ca.push({x, y});
}
}
let mArray = [];
// calculate the slopes
for(let i=0;i<coordsArray.length;i++){
mArray[i] = [];
for(let j=0;j<coordsArray[i].length-1;j++){
let {x, y} = coordsArray[i][j], // current point's x and y
{x: nx, y: ny} = coordsArray[i][j+1], // next point's x and y
dy = (ny - y);
if(dy === 0) // to check if the denominator is legal or not
// in your case, it would not enter here
mArray[i].push(Infinity);
else
mArray[i].push((nx - x) / dy);
}
}
let abandonFactor = +$("#abandonFactor").val();
let newCoordsArray = [];
for(let i=0;i<mArray.length;i++){
let na = newCoordsArray[i] = [];
// calculate the abandonRate base on the amount of the original points
let abandonRate = coordsArray[i].length * abandonFactor;
for(let j=0;j<mArray[i].length-1;j++){
let m = mArray[i][j], // this slope
nm = mArray[i][j+1]; // next slope
let diffRate = Math.abs((m - nm) / m); // calculate the changes of the slope
// check if the diffRate is greater than abandonRate
// or the sign of m not equals the sign of nm
// you can try out removing the "sign check part" and see what would happen ;)
if(diffRate >= abandonRate || (Math.sign(m) !== Math.sign(nm))){
na.push(coordsArray[i][j]);
}
}
}
let newPath = [];
// create new path
for(let i=0;i<newCoordsArray.length;i++){
let temp = [];
for(let j=0;j<newCoordsArray[i].length;j++){
let {x, y} = newCoordsArray[i][j];
let p = `${x},${y}`;
temp.push(p);
}
newPath.push("M" + temp.join("L") + "z");
}
$("#JP-01").attr("d", newPath.join(""));
}).click();
$("#abandonFactor").on("change", function(){
$("#abandonFactor_text").text(`Abandon factor: ${this.value}`);
$("#simplify-2").click();
});
div {
width: 50%;
}
.original {
float: left;
}
.simplified {
float: right;
}
.map {
box-sizing: border-box;
width: 100%;
padding: 0.5rem;
text-shadow: 1px 1px white;
cursor: grab;
}
.land {
fill: lightgreen;
fill-opacity: 1;
stroke: white;
stroke-opacity: 1;
stroke-width: 0.5;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="original">
<svg id="OriginalMap" viewBox="324.55999755859375 0 126.44989013671875 111.65999603271484" class="map" xmlns="http://www.w3.org/2000/svg" xmlns:amcharts="http://amcharts.com/ammap">
<g>
<path id="OriginalJP-01" title="Hokkaido" class="land" d="
M344.04,71.29l2.71,-2.13l0.04,-0.59l-1.96,-3.17l-1.38,-0.99l-0.7,-1.16l0.68,-2.54l-0.27,-0.48l1.64,0.11l0.6,-0.42l0.21,-0.54l0.53,-0.21l2.75,2.66l1.93,0.87l1.13,1.28l2.48,-0.47l0.17,-0.42l1.02,-0.32l0.72,0.02l-0.14,1.17l3.04,1.39l2.2,-1.22l1.97,-2.15l0.99,-1.6l0.1,-2.35l-1.38,-2.76l0.54,-1.94L363,51.89l-0.45,-2.27l1.02,-2.1l1.47,-0.89l0.76,-0.05l2.03,-1.32l0.93,-1.89l0.41,-1.95l-0.35,-7.86l0.5,-0.32l1.64,-3.44l0.21,-2.86l0.38,-0.79l0.13,-1.6l-0.3,-3.27l-0.77,-3.84l-3.04,-7.32l-0.31,-1.16l0.07,-0.99l0.86,-1.27l0.79,-1.95l-0.41,-1.4l0.15,-1.28l0.75,0.77l0.13,0.52l0.39,0.16l1.76,-0.28l1.35,-0.94l0.55,-1.99L374.52,0l0.47,0.33l0.56,1.29l0.59,0.34l0.63,1.47l2.32,1.7l2.21,3.12l4.95,5.56l3.91,7.23l2.58,2.55l2.44,3.1l5.58,4.4l1.57,0.89l0.33,0.95l1.26,0.96l3.7,1.94l3.24,1.28l5.88,1.69l2.79,0.53l0.44,-0.31l0.29,0.77l-0.06,1.28l0.87,1.55l0.58,0.48l3.21,1.01l3.41,0.21l2.57,-0.32l2.28,-1.99l1.54,-1.92l2.18,-2.04l1.83,-1.26l0.66,-1.45l1.1,-1.16l0.78,-1.38l0.46,0.03l0.45,2.23l-1.21,2.49l-0.91,0.92l-0.6,2.4l-1.73,2.13l-0.94,2.12l0.1,0.83L436.06,48l0.22,1.06l1.35,2.58l1.49,1.97l1.52,5.23l1.49,1.96l-1.16,-0.53l-0.19,-0.53l-1.14,-0.22l-0.24,0.37l0.21,1.26l0.86,-0.67l0.47,0.4l-0.29,0.55l0.37,0.39l0.4,-0.28l2.55,0.62l2.09,-1.7l0.32,-0.6l1.11,-0.71l0.24,-0.5l3.28,0.2l-0.19,0.5l-1.17,0.91l-1.73,0.31l-1.87,1.03l-0.8,2.93l-0.77,-0.36l-1.24,-0.05l-2.71,0.51l-1.51,0.9l-1.26,-0.21l-0.62,0.6l-0.14,1.15l-1.6,2.15l-1.02,0.49l-1.9,-0.1l-0.9,-0.79l0.03,-0.53l0.33,-0.31l-0.55,-0.37l-0.73,0.1l-1.19,1.73l-0.02,0.41l1.15,1.13l-2.01,-0.12l-1.15,-0.35l-3.58,-0.02l-0.69,-0.15l-1.58,-1.27l-3.06,0.54l-2.43,1.18l-3.4,2.53l-5.27,5.27l-4.08,5.8l-1.53,3.31l-0.28,1.67l0.55,1.44l-0.82,3.88l-0.91,1.53l0,1.61l-3.7,-3.72l-4.85,-2.02l-8.09,-4.45l-2.38,-1.67l-1.84,-2.05l-3.21,-0.94l-2.21,-2.05l-1.82,-1.2l-1.83,-0.53l-2.38,-0.04l-3.17,1.04l-2.08,1.29l-2.68,2.28l-1.59,0.89l-2.12,2.45l-0.34,0.75l-1.07,-0.4l-0.33,-0.55l0.38,-0.29l0.59,0.64l0.12,-0.81l-1.35,-0.41l-0.87,-2.37l-0.79,-0.4l-1.07,-1.12l-0.12,-0.58l-1.27,-1.23l-1.24,-0.09l-1.25,0.53l-0.97,-0.59l-1.21,0.03l-1.68,1.81l-1.44,2.75l-0.71,2.55l0.28,1.94l2.32,1.2l2.74,2.35l0.75,0.14l1.37,-0.53l2.04,0.31l1.23,2.21l1.63,1.22l1.08,1.88l3.15,1.36l0.55,0.89l0.79,0.59l-0.61,0.61l-0.95,0.01l-1.1,1.3l-1.66,0.66l-0.86,-0.79l-2.78,-0.91l-0.93,0.16l-0.28,0.62l-0.41,0.1l-0.18,-0.61l0.68,-0.45l-0.2,-0.67l-0.59,-0.37l-0.86,0.08l-0.41,0.33l-0.43,1.68l-1.44,1.06l-1.29,0.1l-0.35,0.33l-0.31,3.94l-0.4,0.48l-0.94,0.03l-1.96,0.87l-0.9,1.89l-0.39,0.32l-1.02,-0.8l-1.14,0.22l-0.9,-0.71l-1.08,-2.72l-0.09,-0.86l0.41,-1.77l1.35,-3.03v-1.05l0.77,0.08l0.29,-0.59l0.31,-2.7l-0.4,-1.98l-1.9,-2.94l-0.67,-0.36l-1.3,-0.12L334.29,91l-0.85,-0.88l-1.14,-0.41l-0.39,-0.73l-0.19,-1.39l0.31,-1.12l0.94,-1.03l0.33,-0.95l0.03,-2.65l-0.49,-2.23l0.92,-1.45l1.12,-0.62l2.28,-0.06l0.61,-0.91l1.39,-0.73l0.85,-1.92l0.82,0.57l0.56,0.97l0.89,-0.23l0.09,-1.39l0.82,-0.84L344.04,71.29z
M358.76,9.86l-0.09,-1.23l1.28,-1.25l2.07,1.21l0.51,1.81l-1.81,1.49l-1.37,-1.07L358.76,9.86z
M326.88,91.03l-0.29,2.22l-0.39,0.63l-0.65,0.23l-0.02,0.36l-0.61,-0.54l0.06,-1.71l-0.42,-1.11l0.74,-1.09l1.91,-0.38l0.46,-0.52l0.03,0.64L326.88,91.03z
M357.23,4.25l-0.26,2.59l-0.39,0.13L355.7,4l0.22,-1.31l-0.61,-0.34l0.09,-0.73l0.65,0.85l0.66,-0.15l-0.02,-0.59L357.03,2l0.35,0.78L357.23,4.25z"/>
</g>
</svg> Original
</div>
<div class="simplified">
<svg id="Map" viewBox="324.55999755859375 0 126.44989013671875 111.65999603271484" class="map" xmlns="http://www.w3.org/2000/svg" xmlns:amcharts="http://amcharts.com/ammap">
<g>
<path id="JP-01" title="Hokkaido" class="land" d=""/>
</g>
</svg> Simplified
<button id="simplify-1">Your method</button>
<button id="simplify-2">New method</button>
<input type="range" min="0.0001" max="0.1" value="0.01" step="0.0001" class="slider" id="abandonFactor">
<div id="abandonFactor_text">Abandon factor: 0.01</div>
</div>

Javascript: Can't find errors in graphical genetic algorithm

I'm quite new to Javascript, and I've been working on a graphic-based genetic algorithm. However, I think I'm getting some tunnel vision after working on it a while and I can't seem to catch what's going wrong?
I'm using Javascript with p5.js.
Problem described below.
Premise
Create a bunch of red dots with probability-based movement. Fitness determined by how many randomly placed green dots they eat. Evolve to eat more.
Errors
When I run it, a green dot always appears in the upper left even though they should be randomly placed.
(RESOLVED) The breeding function doesn't seem to work properly. It should:
Create an array for the next generation (this works)
Draw random parents from mating pool (this works)
Randomly cross their chromosome arrays to form a new individual (not sure)
Apply mutation chance for each new individual (not sure)
Store offspring in the new generation (this works)
Replace a random offspring with fittest member of last generation (not sure)
Replace current generation array with new generation array (not sure)
Code
var settings = {
populationSize : 25,
geneLength : 8,
mutationProbability : 0.01,
forestSize : 1500,
rows : 124,
cols : 249,
year : 250,
end : 20,
};
function onCanvas(position){
return position*4+2;
}
function randombetween(min, max){
return Math.random()*max;
}
//set up sheep
var population = new Population(settings.populationSize, settings.geneLength);
function Sheep(g, dna){
this.genLen = g;
this.state = 0;
this.fitness=0;
this.xpos = Math.floor(Math.random()*settings.cols);
this.ypos = Math.floor(Math.random()*settings.rows);
this.chromosome = new Array(this.genLen);
if (dna != null){
this.chromosome = dna;
} else{
for(var x=0; x<this.genLen; x+=4){
this.chromosome[x] = Math.random();
this.chromosome[x+1] = randombetween(0, 1-this.chromosome[x]);
this.chromosome[x+2] = randombetween(0, 1-this.chromosome[x]-this.chromosome[x+1]);
this.chromosome[x+3] = 1-this.chromosome[x]-this.chromosome[x+1]-this.chromosome[x+2];
}
}
}
function Population(p, g){
this.popSize = p;
this.sheep = [];
this.matingPool = [];
this.maxFit;
this.maxFitIndex;
for (var x = 0; x < this.popSize; x++) {
this.sheep[x] = new Sheep(g, null);
}
this.evaluate = function() {
//find maximum fitness in generation
this.maxFit = 0;
this.maxFitIndex = 0;
for (var x = 0; x < this.popSize; x++) {
//this.sheep[x].calcFitness();
if (this.sheep[x].fitness > this.maxFit){
this.maxFitIndex = x;
this.maxFit = this.sheep[x].fitness;
}
}
//document.write("Maximum fitness: " + this.maxFit);
//normalize fitness
for (var i = 0; i < this.popSize; i++) {
this.sheep[i].fitness /= this.maxFit;
}
//reset mating pool every generation
this.matingPool = [];
//higher fitness means more representation in the pool
for (var i = 0; i < this.popSize; i++) {
var n = this.sheep[i].fitness *10;
for (var j = 0; j < n; j++) {
this.matingPool.push(this.sheep[i]);
}
}
}
//create children sheep
this.breed = function (){
var newsheep = [];
for (var i = 0; i < this.popSize; i++){
//pick random parents from the mating pool
let parentA = this.matingPool[Math.floor(Math.random()*this.matingPool.length)];
let parentB = this.matingPool[Math.floor(Math.random()*this.matingPool.length)];
//parent genes are randomly crossed
var newchromosome = [];
var midpoint = Math.floor(Math.random()*g);
for (var j = 0; j < g; j++){
if (j > midpoint){
newchromosome[j] = parentA.chromosome[j];
} else{
newchromosome[j] = parentB.chromosome[j];
}
}
//offspring may be mutated
if(Math.random()<=settings.mutationProbability){
newchromosome[Math.floor(Math.random()*g)]=Math.floor(Math.random()*10+1);
}
newsheep[i] = new Eater(g, newchromosome);
}
//elite offspring survive into next generation, replacing a random offspring
var random = Math.floor(Math.random()*this.popSize);
if(Math.random()<=settings.mutationProbability){
this.sheep[this.maxFitIndex].chromosome[Math.floor(Math.random()*g)]=Math.floor(Math.random()*10+1);
}
for(var x = 0; x < g; x++){
newsheep[random].chromosome[x] = this.sheep[this.maxFitIndex].chromosome[x];
}
//update array of sheep
for(var x=0; x<this.popSize; x++){
this.sheep[x] = newsheep[x];
}
}
}
//set up trees
var forest = new Forest(settings.forestSize);
function radialTreePopulation(x,y,r,count){
let trees = [];
for(let i = 0;i < count; i++){
trees.push({
posx : (x + Math.floor((Math.random()* r) * (Math.random() < 0.5 ? -1 : 1))),
posy : (y + Math.floor((Math.random()* r) * (Math.random() < 0.5 ? -1 : 1)))
});
}
return trees;
}
function Forest(f){
this.forSize = f/ 75;
this.trees = [];
for (var x = 0; x < this.forSize ; x++) {
this.trees.push(...radialTreePopulation(
(Math.floor(Math.random()*(settings.cols-20)+10))| 0,
(Math.floor(Math.random()*(settings.rows-20)+10))| 0,
11,
75)
);
}
}
//evaluate how to move
function moveHungry(x, move){
if(move<population.sheep[x].chromosome[0]){
return 0;
}
else if(move-population.sheep[x].chromosome[0]<population.sheep[x].chromosome[1]){
return 1;
}
else if(move-population.sheep[x].chromosome[0]-population.sheep[x].chromosome[1]<population.sheep[x].chromosome[2]){
return 2;
}
else{
return 3;
}
}
function moveEaten(x,move){
if(move<population.sheep[x].chromosome[4]){
return 0;
}
else if(move-population.sheep[x].chromosome[4]<population.sheep[x].chromosome[5]){
return 1;
}
else if(move-population.sheep[x].chromosome[4]-population.sheep[x].chromosome[5]<population.sheep[x].chromosome[6]){
return 2;
}
else{
return 3;
}
}
//count generations and days
var generation=0;
var counter = 0;
//create world
function createWorld(){
background("lightblue");
fill(0,255,0);
for(var x=0; x<settings.forestSize; x++){
rect(onCanvas(forest.trees[x].posx), onCanvas(forest.trees[x].posy), 4, 4);
}
fill(255,0,0);
for(var x=0; x<settings.populationSize; x++){
population.sheep[x].state=0;
rect(onCanvas(population.sheep[x].xpos), onCanvas(population.sheep[x].ypos), 4, 4);
}
//remove eaten trees
for(var x=0; x<settings.populationSize; x++){
for(var y=0; y<settings.forestSize; y++){
if(population.sheep[x].xpos==forest.trees[y].posx && population.sheep[x].ypos==forest.trees[y].posy){
forest.trees[y].posx=null;
forest.trees[y].posy=null;
population.sheep[x].state=1;
population.sheep[x].fitness++;
}
}
}
//move sheep based on chromosome
for(var x=0; x<settings.populationSize; x++){
var move = Math.random();
if(population.sheep[x].state==0){
switch(moveHungry(x, move)){
case 0: //up
if(population.sheep[x].ypos>0)
population.sheep[x].ypos-=1;
break;
case 1: //down
if(population.sheep[x].ypos<settings.rows-1)
population.sheep[x].ypos+=1;
break;
case 2: //right
if(population.sheep[x].xpos<settings.cols-1)
population.sheep[x].xpos+=1;
break;
case 3: //left
if(population.sheep[x].xpos>0)
population.sheep[x].xpos-=1;
}
}
else {
switch(moveEaten(x, move)){
case 0: //up
if(population.sheep[x].ypos>0)
population.sheep[x].ypos-=1;
break;
case 1: //down
if(population.sheep[x].ypos<settings.rows-1)
population.sheep[x].ypos+=1;
break;
case 2: //right
if(population.sheep[x].xpos<settings.cols-1)
population.sheep[x].xpos+=1;
break;
case 3: //left
if(population.sheep[x].xpos>0)
population.sheep[x].xpos-=1;
}
}
}
counter++;
}
function reset(){
counter=0;
//regrow forest
forest = new Forest(settings.forestSize);
//reset locations and fitness values
for(var x=0; x<settings.populationSize; x++){
population.sheep[x].xpos = Math.floor(Math.random()*settings.cols);
population.sheep[x].ypos = Math.floor(Math.random()*settings.rows);
population.sheep[x].fitness=0;
}
}
function setup() {
createCanvas(1000, 500);
}
function draw() {
createWorld();
if(counter>=settings.year){
population.evaluate();
population.breed();
reset();
generation++;
if(generation>=settings.end){
noLoop();
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

Making a canvas using p5.js

enter image description hereI got an exercise , where i have to create a rectangle-canvas using p5.js , but that canvas will consist small rects ,so i do it , but there is also 1 point in the exrecise . How can i get those small rects in 2 different colors , but 50% of those colores must be green and the other red , using matrix .
Here is the code .
var matrix = [
];
var ab = 36;
for (var y = 0; y < ab; y++) {
matrix.push([])
for (var x = 0; x < 36; x++) {
matrix[y][x] = Math.floor(Math.random() * 2)
}
}
console.log(matrix)
var side = 16;
function setup() {
createCanvas(matrix[0].length * side, matrix.length * side);
background('#acacac');
frameRate()
}
function draw() {
for (var y = 0; y < matrix.length; y++) {
for (var x = 0; x < matrix[y].length; x++) {
if (matrix[y][x] == 0) {
fill(0, 255, 0)
rect(y * side, x * side, side, side)
}
else if (matrix[y][x] == 1) {
fill("red")
rect(y * side, x * side, side, side)
}
function Shuffle (arguments) {
for(var k = 0; k < arguments.length; k++){
var i = arguments[k].length;
if ( i == 0 ) return false;
else{
while ( --i ) {
var j = Math.floor( Math.random() * ( i + 1 ) );
var tempi = arguments[k][i];
var tempj = arguments[k][j];
arguments[k][i] = tempj;
arguments[k][j] = tempi;
}
return arguments;
}
}
}
so as discussed in comments , the problem reduces to filling exactly half the matrix with one color and other half with other.
your matrix is in two dimension i will give a solution in one dimension, which should be quite easy to extend to 2-d
var count = 0;
var arr = [];
for( var i = 0 ;i < ab;i++){
arr[i] = 0;
}
while(true) {
var i = floor(random(ab));
if(arr[i] !==1) {
arr[i] = 1;
count++;
}
if(count === ab/2) break; // assume ab is even
}
there is one more way
fill half the array with 1 and half with 0 and then shuffle the array
you can very easily google algorithms for shuffling,
one pseudocode i could find
// after filling half elements with 1 and half with zero
// To shuffle an array a of n elements (indices 0..n-1):
for i from n - 1 downto 1 do
j = random integer with 0 <= j <= i
exchange a[j] and a[i]
source: https://www.geeksforgeeks.org/shuffle-a-given-array/
There it is my problem
var matrix = [
];
var ab = 36;
for (var y = 0; y < ab; y++) {
matrix.push([])
for(var x = 0 ; x<ab;x++){
matrix[y][x] = Math.floor(Math.random()*1)
}
for(var x = 0 ; x<ab/2;x++){
matrix[y][x] = 1
}
}
var count = 0;
var arr = [];
for( var i = 0 ;i < ab;i++){
arr[i] = 0;
}
while(true) {
var i = Math.floor(Random(ab));
if(arr[i] !==1) {
arr[i] = 1;
count++;
}
if(count === ab/2) break; // assume ab is even
}
console.log(arr)
var side = 16;
function setup() {
createCanvas(arr[0].length * side, arr.length * side);
background('#acacac');
frameRate()
}
function draw() {
for (var y = 0; y < arr.length; y++) {
for (var x = 0; x < arr[y].length; x++) {
if (matrix[y][x] == 0) {
fill(0, 255, 0)
rect(y * side, x * side, side, side)
}
else if (matrix[y][x] == 1) {
fill("red")
rect(y * side, x * side, side, side)
}
else if (matrix[y][x] == 2) {
fill(255, 255, 0)
rect(y * side, x * side, side, side)
}
else if (matrix[y][x] == 3) {
fill(255, 0, 0)
rect(y * side, x * side, side, side)
}
}
}
}

Categories

Resources