I added something to my code that's supposed to detect when an object (pixel) already exists, and then replace the color instead of creating a new one. However, for some reason, it just doesn't do anything.
Code:
var gridSize = 16;
var pixels = [];
var draw = function() {
background(255, 255, 255);
for (var i = 0; i < gridSize; i++) {
stroke(0, 0, 0);
line(400/gridSize*i, 0, 400/gridSize*i, 400);
line(0, 400/gridSize*i, 400, 400/gridSize*i);
}
for (var i = 0; i < pixels.length; i++) {
noStroke();
fill(pixels[i][2][0], pixels[i][2][1], pixels[i][2][2]);
rect(pixels[i][0]*(400/gridSize), pixels[i][1]*(400/gridSize), 400/gridSize, 400/gridSize);
}
document.getElementById("output").innerHTML = pixels;
fill(255, 0, 0);
text(alreadyExists, 200, 200);
};
var mousePressed = function() {
alreadyExists = false;
for (var i = 0; i < pixels.length; i++) {
if (pixels[i] === [ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value]]) {
alreadyExists = true;
pixels[i][2] = [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value];
}
}
if (!alreadyExists) {
pixels.push([ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value]]);
}
};
var mouseDragged = function() {
alreadyExists = false;
for (var i = 0; i < pixels.length; i++) {
if (pixels[i] === [ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value]]) {
alreadyExists = true;
pixels[i][2] = [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value];
}
}
if (!alreadyExists) {
pixels.push([ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value]]);
}
};
When comparing arrays, you need to compare individually every item, in your case. I modified a the original example to minimize repetition of code blocks and to improve a bit speed and memory usage. You can also a working example here
The idea is to compare the pixels positions, encoded at index 0 for x and 1 for y. And then to compare the color channels, which are also encoded in an array so we need to then again compare individually every component.
The function samePixels in the following example is exactly doing that:
var samePixels = function (p1, p2) {
var samePosition = p1[0] === p2[0] && p1[1] === p2[1];
if (!samePosition) {
return false;
}
var colors1 = p1[2];
var colors2 = p2[2];
return (colors1[0] === colors2[0]) &&
(colors1[1] === colors2[1]) &&
(colors1[2] === colors2[2]);
}
Full source code for the js part:
var gridSize = 16;
var pixels = [];
var alreadyExists;
var color1 = document.getElementById('color1');
var color2 = document.getElementById('color2');
var color3 = document.getElementById('color3');
var draw = function() {
background(255, 255, 255);
for (var i = 0; i < gridSize; i++) {
stroke(0, 0, 0);
line(400/gridSize*i, 0, 400/gridSize*i, 400);
line(0, 400/gridSize*i, 400, 400/gridSize*i);
}
for (var i = 0; i < pixels.length; i++) {
stroke(0);
fill(pixels[i][2][0], pixels[i][2][1], pixels[i][2][2]);
rect(pixels[i][0]*(400/gridSize), pixels[i][1]*(400/gridSize), 400/gridSize, 400/gridSize);
}
document.getElementById("output").innerHTML = pixels;
fill(255, 0, 0);
text(alreadyExists, 200, 200);
};
var mousePressed = mouseDragged = function() {
alreadyExists = false;
closestPixel = [ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [color1.value, color2.value, color3.value]];
for (var i = 0; i < pixels.length; i++) {
if (samePixels(pixels[i], closestPixel)) {
alreadyExists = true;
pixels[i][2] = [color1.value, color2.value, color3.value];
break;
}
}
console.log('Does the pixel already exist?', alreadyExists);
if (!alreadyExists) {
pixels.push(closestPixel);
}
};
var samePixels = function (p1, p2) {
var samePosition = p1[0] === p2[0] && p1[1] === p2[1];
if (!samePosition) {
return false;
}
var colors1 = p1[2];
var colors2 = p2[2];
return (colors1[0] === colors2[0]) &&
(colors1[1] === colors2[1]) &&
(colors1[2] === colors2[2]);
}
Related
I am making a tree with space-colonization algorithm in javascript. (with p5.js)
I followed the tutorial of https://www.youtube.com/watch?v=kKT0v3qhIQY&ab_channel=TheCodingTrain
or in C#
http://www.jgallant.com/procedurally-generating-trees-with-space-colonization-algorithm-in-xna/
I want to make a customized area (in this case a circle) where the leaves are generated randomly.
in Leaf.js I try to include some calculations of how the leaves get the random Coordinates within the circle.
So there is sketch.js , leaf.js, branch.js, tree.js
//leaf.js
function Leaf() {
function getChord(){
var r = 150;
var angle = random(0, 2 * PI);
var xChord = 2*r + sqrt(r) * cos(angle);
var yChord = r + sqrt(r) * sin(angle);
return (createVector(xChord, yChord));
}
this.pos = getChord();
this.reached = false;
// // var t = 2 * PI * random(0,1);
// // var r = sqrt(random(0,1));
// // var x = r * cos(t);
// // var y = r * sin(t);
// // this.pos = createVector(x, y);
// this.reached = false;
this.show = function() {
fill(58, 126, 34);
noStroke();
ellipse(this.pos.x, this.pos.y, 4, 4);
};
}
//branch.js
function Branch(parent, pos, dir) {
this.pos = pos;
this.parent = parent;
this.dir = dir;
this.origDir = this.dir.copy();
this.count = 0;
this.len = 3;
this.reset = function() {
this.dir = this.origDir.copy();
this.count = 0;
};
this.next = function() {
var nextDir = p5.Vector.mult(this.dir, this.len);
var nextPos = p5.Vector.add(this.pos, nextDir);
var nextBranch = new Branch(this, nextPos, this.dir.copy());
return nextBranch;
};
this.show = function() {
if (parent != null) {
stroke(151, 53, 48);
strokeWeight(1);
line(this.pos.x, this.pos.y, this.parent.pos.x, this.parent.pos.y);
}
};
}
And then in tree.js I push every leaf into leaves.
//tree.js
function Tree() {
this.leaves = [];
this.branches = [];
for (var i = 0; i < 1500; i++) {
this.leaves.push(new Leaf());
}
var pos = createVector(width / 2, height);
var dir = createVector(0, -1);
var root = new Branch(null, pos, dir);
this.branches.push(root);
var current = root;
var found = false;
while (!found) {
for (var i = 0; i < this.leaves.length; i++) {
var d = p5.Vector.dist(current.pos, this.leaves[i].pos);
if (d < max_dist) {
found = true;
}
}
if (!found) {
var branch = current.next();
current = branch;
this.branches.push(current);
}
}
this.grow = function() {
for (var i = 0; i < this.leaves.length; i++) {
var leaf = this.leaves[i];
var closestBranch = null;
var record = max_dist;
for (var j = 0; j < this.branches.length; j++) {
var branch = this.branches[j];
var d = p5.Vector.dist(leaf.pos, branch.pos);
if (d < min_dist) {
leaf.reached = true;
closestBranch = null;
break;
} else if (d < record) {
closestBranch = branch;
record = d;
}
}
if (closestBranch != null) {
var newDir = p5.Vector.sub(leaf.pos, closestBranch.pos);
newDir.normalize();
closestBranch.dir.add(newDir);
closestBranch.count++;
}
}
for (var i = this.leaves.length - 1; i >= 0; i--) {
if (this.leaves[i].reached) {
this.leaves.splice(i, 1);
}
}
for (var i = this.branches.length - 1; i >= 0; i--) {
var branch = this.branches[i];
if (branch.count > 0) {
branch.dir.div(branch.count + 1);
this.branches.push(branch.next());
branch.reset();
}
}
};
this.show = function() {
for (var i = 0; i < this.leaves.length; i++) {
this.leaves[i].show();
}
for (var i = 0; i < this.branches.length; i++) {
this.branches[i].show();
}
};
}
//sketch.js
var tree;
var max_dist = 30;
var min_dist = 10;
function setup() {
createCanvas(600, 600);
tree = new Tree();
}
function draw() {
background(111, 149, 231);
tree.show();
tree.grow();
}
Some how in the function getChord I think there is an infinite loop or having trouble getting the random value? Because it is not loading at all... It works when I change this.pos = getChord(); to this.pos = createVector(random(width),random(height-100);
Does anyone know how to solve this?
Or how to write codes to make an area of the circle where the leaves can be generated?
Thank you!
I'm here again because I'm still having trouble trying to implement a flood fill tool in my drawing app.
I am attempting to make a fairly simple 2d paint application using p5.js with each drawing tool having its own constructor function. I've been having trouble understanding what I am doing wrong and why it is not working which is causing lots of frustration.
I have read a few articles on here and followed a tutorial on youtube but I still don't quite understand it. I will include what I have done so far so you can see. Specifically, I am not sure what to write for the draw function. I would like the flood fill to happen on mouseX and mouseY coordinates when the mouse is pressed. Also, I would like for the target colour to be a colour selected from a separate constructor function ColourPalette().
HTML:
<!DOCTYPE html>
<html>
<head>
<script src="lib/p5.min.js"></script>
<script src="lib/p5.dom.js"></script>
<script src="sketch.js"></script>
<!-- add extra scripts below -->
...
<script src="fillTool.js"></script>
</body>
</html>
The sketch file:
function setup() {
//create a canvas to fill the content div from index.html
canvasContainer = select('#content');
var c = createCanvas(canvasContainer.size().width, canvasContainer.size().height);
c.parent("content");
//create helper functions and the colour palette
helpers = new HelperFunctions();
colourP = new ColourPalette();
...
toolbox.addTool(new FillTool());
background(255);
}
function draw() {
//call the draw function from the selected tool.
//if there isn't a draw method the app will alert the user
if (toolbox.selectedTool.hasOwnProperty("draw")) {
toolbox.selectedTool.draw();
} else {
alert("it doesn't look like your tool has a draw method!");
}
}
The flood fill constructor function I need help with. I'm getting "Uncaught ReferenceError: floodFill is not defined" for line 112 (within the draw function) in the console and I'm a little stuck on how to fix it.:
function FillTool() {
//set an icon and a name for the object
this.icon = "assets/freehand.jpg";
this.name = "FillTool";
var colourNew = ColourPalette(colourP); //Placeholder - How do I do this?
function getPixelData(x,y){
var colour = [];
for (var i = 0; i < d; i++) {
for (var j = 0; j < d; j++) {
idx = 4 * ((y * d + j) * width * d + (x * d + i));
colour[0] = pixels[idx];
colour[1] = pixels[idx+1];
colour[2] = pixels[idx+2];
colour[3] = pixels[idx+3];
}
}
return colour;
}
function setPixelData(x, y, colourNew) {
for (var i = 0; i < d; i++) {
for (var j = 0; j < d; j++) {
idx = 4 * ((y * d + j) * width * d + (x * d + i));
pixels[idx] = colourNew[0];
pixels[idx+1] = colourNew[1];
pixels[idx+2] = colourNew[2];
pixels[idx+3] = colourNew[3];
}
}
}
function matchColour(xPos,yPos,oldColour){
var current = get(xPos,yPos);
if(current[0] == oldColour[0] && current[1] == oldColour[1] && current[2] == oldColour[2] && current[3] == oldColour[3]){
return true;
}
}
function checkPixel(x1,y1,pixelArray){
for (var i = 0 ; i < pixelArray.length; i+=2){
if(x1 == pixelArray[i] && y1 == pixelArray[i+1]){
return false;
}
else {
console.log(pixelArray.length)
return true;
}
}
}
function floodFill (xPos,yPos){
loadPixels();
colourOld = getPixelData(xPos, yPos);
var stack = [];
var pixelList = [];
stack.push(xPos,yPos);
pixelList.push(xPos,yPos);
console.log(stack);
while(stack.length > 0){
var yPos1 = stack.pop();
var xPos1 = stack.pop();
setPixelData(xPos1,yPos1,colourNew);
if(xPos1 + 1 <= width && xPos1 + 1 > 0 ){
if(matchColour(xPos1+1,yPos1,colourOld) && checkPixel(xPos1+1,yPos1,pixelList)){
stack.push(xPos1+1,yPos1);
pixelList.push(xPos1+1,yPos1);
}
}
if(xPos1+1 <= width && xPos1+1 > 0 ){
if(matchColour(xPos1-1,yPos1,colourOld) && checkPixel(xPos1-1,yPos1,pixelList)){
stack.push(xPos1-1,yPos1);
pixelList.push(xPos1-1,yPos1);
}
}
if(yPos1+1 <= height && yPos1+1 > 0 ){
if(matchColour(xPos1,yPos1+1,colourOld) && checkPixel(xPos1,yPos1+1,pixelList)){
stack.push(xPos1,yPos1+1);
pixelList.push(xPos1,yPos1+1);
}
}
if(yPos1-1 <= height && yPos1-1 > 0 ){
if(matchColour(xPos1,yPos1-1,colourOld) && checkPixel(xPos1,yPos1-1,pixelList)){
stack.push(xPos1,yPos1-1);
pixelList.push(xPos1,yPos1-1);
}
}
}
updatePixels();
console.log(pixelList);
}
}
this.draw = function() {
if(mouseIsPressed){
floodFill(mouseX,mouseY);
}
}
Sorry if its a bit of a mess, it's an accurate representation of my brain at the moment.
The function checkPixel was very very slow because pixelArray grows as you draw new pixels, so verifying if a new pixel was in the stack or had already been drawn took longer each time.
In javascript it is possible to use an object {} to store key/value pair like :
var colours = { 'red':{'r':255,'g':0,'b':0,'a':255}, 'black':{'r':0,'g':0,'b':0,'a':255} };
And calling the method hasOwnPorperty to verify the presence of the key is very fast.
colours.hasOwnPorperty('red') // is true
colours.hasOwnPorperty('green') // is false
If you had a 1,000,000 colours in colours, it would not take longer for hasOwnPorperty to find a colour in colours than if you had only 1 colour in colours. (It's O(1) has opposed to O(n) for your version of checkPixel)
Try clicking inside the circle ... or outside
let toolbox, d;
function setup() {
createCanvas(600, 400);
d = pixelDensity();
//create helper functions and the colour palette
//...
let colourRed = ColourPalette(255,0,0,255);
//...
toolbox = {'selectedTool': new FillTool() };
toolbox.selectedTool.setColour(colourRed);
background(255);
push();
strokeWeight(1);
stroke(0);
circle(75,75,100);
noStroke();
fill(0,255,0,255);
circle(125,75,100);
pop();
}
function draw() {
if (! toolbox.selectedTool.hasOwnProperty("draw")) {
alert("it doesn't look like your tool has a draw method!");
return;
}
toolbox.selectedTool.draw();
}
function FillTool() {
let self = this;
//set an icon and a name for the object
self.icon = "assets/freehand.jpg";
self.name = "FillTool";
self.colour = ColourPalette(0,0,0,255);
self.draw = function () {
if (mouseIsPressed) {
floodFill(mouseX, mouseY);
}
};
self.setColour = function (col) {
self.colour = col;
};
function matchColour (pos, oldColour) {
var current = getPixelData(pos.x, pos.y);
return ( current[0] === oldColour[0] && current[1] === oldColour[1]
&& current[2] === oldColour[2] && current[3] === oldColour[3] );
}
function getKey (pos) {
return ""+pos.x+"_"+pos.y;
}
function checkPixel(pos, positionSet) {
return ! positionSet.hasOwnProperty( getKey(pos) );
}
function floodFill (xPos, yPos) {
var stack = [];
var pixelList = {};
var first = {'x':xPos,'y':yPos};
stack.push( first );
pixelList[ getKey(first) ] = 1;
//console.log( JSON.stringify(stack) );
loadPixels();
var firstColour = getPixelData(xPos, yPos);
while (stack.length > 0) {
var pos1 = stack.pop();
setPixelData(pos1.x, pos1.y, self.colour);
var up = {'x':pos1.x, 'y':pos1.y-1};
var dn = {'x':pos1.x, 'y':pos1.y+1};
var le = {'x':pos1.x-1,'y':pos1.y};
var ri = {'x':pos1.x+1,'y':pos1.y};
if (0 <= up.y && up.y < height && matchColour(up, firstColour)) addPixelToDraw(up);
if (0 <= dn.y && dn.y < height && matchColour(dn, firstColour)) addPixelToDraw(dn);
if (0 <= le.x && le.x < width && matchColour(le, firstColour)) addPixelToDraw(le);
if (0 <= ri.x && ri.x < width && matchColour(ri, firstColour)) addPixelToDraw(ri);
}
updatePixels();
//console.log( JSON.stringify(pixelList) );
function addPixelToDraw (pos) {
if (checkPixel(pos, pixelList) ) {
stack.push( pos );
pixelList[ getKey(pos) ] = 1;
}
}
}
}
function ColourPalette (r,g,b,a) {
var self = (this !== window ? this : {});
if (arguments.length === 0) {
self['0'] = 0; self['1'] = 0; self['2'] = 0; self['3'] = 0;
} else if (arguments.length === 1) {
self['0'] = r[0]; self['1'] = r[1]; self['2'] = r[2]; self['3'] = r[3];
} else if (arguments.length === 4) {
self['0'] = r; self['1'] = g; self['2'] = b; self['3'] = a;
} else {
return null;
}
return self;
}
function getPixelData (x, y) {
var colour = [];
for (var i = 0; i < d; ++i) {
for (var j = 0; j < d; ++j) {
let idx = 4 * ((y * d + j) * width * d + (x * d + i));
colour[0] = pixels[idx];
colour[1] = pixels[idx+1];
colour[2] = pixels[idx+2];
colour[3] = pixels[idx+3];
}
}
return colour;
}
function setPixelData (x, y, colour) {
for (var i = 0; i < d; ++i) {
for (var j = 0; j < d; ++j) {
let idx = 4 * ((y * d + j) * width * d + (x * d + i));
pixels[idx] = colour[0];
pixels[idx+1] = colour[1];
pixels[idx+2] = colour[2];
pixels[idx+3] = colour[3];
}
}
}
body { background-color:#efefef; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
I want to make a dynamic water pipe with water flowing. The front picture is an error message.
The following picture is a method in the method in the vue page. I don't know if it will report an error. If you don't call this method, nothing will be reported.
The following code is the js file that introduces the vue page
//构造函数
function Createline(config) {
this.c = 0;
this.lines = [];
var self = this;
//初始化线
(function (self){
if(config.fx == "w"){
var number = config.canvas_w / (config.width + config.width/2 + config.jiange*2);
number = Math.ceil(number) + 1;
for(var n = 0 ; n < number ; n++){
var mxx = config.canvas_w - n*(config.width+config.width/2+config.jiange*2);
var lines_data = {
mx:mxx,
lx:mxx - config.width,
my:config.my,
ly:config.ly,
vx:config.vx,
vy:config.vy,
}
self.lines.push(lines_data);
var lines_data2 = {
mx:mxx-config.width-config.jiange,
lx:mxx-config.width-config.jiange-config.width/2,
my:config.my,
ly:config.ly,
vx:config.vx,
vy:config.vy,
}
self.lines.push(lines_data2);
}
}
if(config.fx == "h"){
var number = config.canvas_w / (config.width + config.width/2 + config.jiange*2);
number = Math.ceil(number) + 1;
for(var n = 0 ; n < number ; n++){
var myy = config.canvas_h - n*(config.width+config.width/2+config.jiange*2);
var lines_data3 = {
mx:config.mx,
lx:config.lx,
my:myy,
ly:myy - config.width,
vx:config.vx,
vy:config.vy,
}
self.lines.push(lines_data3);
var lines_data4 = {
mx:config.mx,
lx:config.lx,
my:myy-config.width-config.jiange,
ly:myy-config.width-config.jiange-config.width/2,
vx:config.vx,
vy:config.vy,
}
self.lines.push(lines_data4);
}
}
})(self);
}
//开始
Createline.prototype.begin = function (element,config) {
var canvasObj = document.getElementById(element);
var self = this;
var canvas = {
line_w: config.line_w || 3, //线条厚度
vx: config.vx || 0,//x轴运动速度
vy:config.vy || 0,//Y轴速度
color:config.color || "blue",//线条颜色
canvas_w:config.canvas_w || 0,//画布宽度
canvas_h:config.canvas_h || 0,//画布高度
mx: config.mx || 0,//线的开始位置X坐标
my: config.my || 0,//线的开始位置Y坐标
lx: config.lx || 0,//线的结束位置X坐标
ly: config.ly || 0,//线的结束位置Y坐标
fx:config.fx || "w",//运动方向 w 水平 ,h: 垂直
width:config.width || 20,//线的长度
jiange:config.jiange || 10, //线的间隔
}
if(canvasObj.getContext("2d")){
canvas.ctx = canvasObj.getContext("2d"),
canvasObj.width = canvas.canvas_w;
canvasObj.height = canvas.canvas_h;
}else{
console.log("当前环境不支持canvas");
return null;
}
setInterval(function(){
self.createline(self,canvas);
if(canvas.fx == "w"){
self.updateline_w(self,canvas);
}
if(canvas.fx == "h"){
self.updateline_h(self,canvas);
}
self.addline(self,canvas);
},config.time);
return canvas.ctx;
}
//画线
Createline.prototype.createline = function(self,canvas){
var content = canvas.ctx;
content.clearRect(0,0,canvas.canvas_w,canvas.canvas_h);
for(var i = 0 ; i < self.lines.length ; i++){
content.beginPath();
content.moveTo(self.lines[i].mx,self.lines[i].my);
//线经过的转折点或结束点
content.lineTo(self.lines[i].lx,self.lines[i].ly);
//线条宽度
content.lineWidth = canvas.line_w;
content.strokeStyle = canvas.color;
content.stroke();
//线条颜色
content.closePath();
//画线
}
}
//改变数据 水平运动
Createline.prototype.updateline_w = function(self,canvas){
for (var i = 0 ; i < self.lines.length ; i++) {
self.lines[i].mx = self.lines[i].mx + self.lines[i].vx;
self.lines[i].lx = self.lines[i].lx + self.lines[i].vx;
}
//超出画布的线条从数组内删除,减少系统消耗
var cnt = [];
for (var j = 0 ;j < self.lines.length ; j++) {
if(self.lines[j].lx > canvas.canvas_w){
cnt.push(j);
}
}
for(var k = 0; k < cnt.length ; k++){
self.lines.splice(k,1);
}
}
//改变数据 垂直运动
Createline.prototype.updateline_h = function(self,canvas){
for (var i = 0 ; i < self.lines.length ; i++) {
self.lines[i].my = self.lines[i].my + lines[i].vy;
self.lines[i].ly = self.lines[i].ly + lines[i].vy;
}
//超出画布的线条从数组内删除,减少系统消耗
var cnt = [];
for (var j = 0 ;j < self.lines.length ; j++) {
if( self.lines[j].ly > canvas.canvas_h){
cnt.push(j);
}
}
for(var k = 0; k < cnt.length ; k++){
self.lines.splice(k,1);
}
}
//增加线
Createline.prototype.addline = function(self,canvas){
var length = self.lines.length;
//横线
if(canvas.fx == "w"){
//判断是否需要生成线
if(self.lines[length-1].lx > 0){
//起点位置 等于 左右一根线的坐标减去间隔
var ww = self.lines[length - 1].lx - canvas.jiange;
//判断长短
if(self.c == 0){
var jg = canvas.width;
self.c = 10;
}else{
var jg = canvas.width/2;
self.c = 0;
}
var lxx = ww - jg;
var data = {
mx:ww,
lx:lxx,
my:canvas.my,
ly:canvas.ly,
vx:canvas.vx,
vy:canvas.vy,
}
self.lines.push(data);
}
}
//垂直
if(canvas.fx == "h"){
if(length>0){
//判断是否需要生成线
if(self.lines[length-1].ly > 0){
//起点位置 等于 左右一根线的坐标减去间隔
var my = self.lines[length - 1].ly - canvas.jiange;
//判断长短
if(c == 0){
var jg = canvas.width;
c = 10;
}else{
var jg = canvas.width/2;
c = 0;
}
var ly = my - jg;
var data = {
mx:canvas.mx,
lx:canvas.lx,
my:my,
ly:ly,
vx:canvas.vx,
vy:canvas.vy,
}
self.lines.push(data);
}
}else{
var data = {
mx:canvas.mx,
my:canvas.my,
lx:canvas.lx,
ly:canvas.ly,
vx:canvas.vx,
vy:canvas.vy,
}
self.lines.push(data);
}
}
}
export{
Createline
}
You need to use new when creating a instance for a constructor that uses this:
var cre = new Createline(data);
When a function is executed using new it does the following:
function User(name) {
// this = {}; (implicitly)
// add properties to this
this.name = name;
this.isAdmin = false;
// return this; (implicitly)
}
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();
}
}
I'm building a tetris-like game, where in stead of removing just one line when you've got a full line I remove all the connected pieces. This has got me stumped on the hard-drop after clearing the pieces.
See this example for a quick and dirty version of what I'm trying to do.
function Board (width, height) {
this.width = width;
this.height = height;
this.board = [];
this.pieces = [];
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
if (!this.board[y]) {
this.board[y] = [];
}
this.board[y][x] = null;
}
}
this.canPlace = function(piece, at) {
for (var y = 0; y < piece.getHeight(); y++) {
for (var x = 0; x < piece.getWidth(); x++) {
if ((y+at.y >= this.height) || this.board[y+at.y][x+at.x]) {
return false;
}
}
}
return true;
}
this.hasFullLine = function(line) {
for (var x = 0; x < this.width; x++) {
if (!this.board[line][x]) {
return false;
}
}
return true;
}
this.place = function(piece) {
var position = piece.getPosition();
var shape = piece.getShape();
for (var y = 0; y < piece.getHeight(); y++) {
for (var x = 0; x < piece.getWidth(); x++) {
if (shape[y][x]) {
this.board[y+position.y][x+position.x] = piece;
}
}
}
if (this.pieces.indexOf(piece) === -1) {
this.pieces.push(piece);
}
piece.render();
}
this.hardDropPieces = function() {
var pieces = this.pieces.slice();
pieces = pieces.sort(function(a,b) {
var aBottom = a.getPosition().y+a.getHeight();
var bBottom = b.getPosition().y+b.getHeight();
return bBottom-aBottom;
});
for (var i = 0; i < pieces.length; i++) {
this.hardDrop(pieces[i]);
}
}
this.hardDrop = function(piece) {
var position = piece.getPosition();
this.clearArea(piece);
while(this.canPlace(piece, {x: piece.getPosition().x, y: piece.getPosition().y+1})) {
piece.setPosition(piece.getPosition().x, piece.getPosition().y+1);
}
this.place(piece);
}
this.clearArea = function(piece) {
var position = piece.getPosition();
var shape = piece.getShape();
for (var y = 0; y < piece.getHeight(); y++) {
for (var x = 0; x < piece.getWidth(); x++) {
if (shape[y][x]) {
this.board[y+position.y][x+position.x] = null;
}
}
}
}
this.remove = function(piece) {
this.clearArea(piece);
this.pieces.splice(this.pieces.indexOf(piece),1);
}
this.clearPiecesOnLine = function(line) {
var piecesToClear = [];
for (var x = 0; x < this.width; x++) {
var piece = this.board[line][x];
if (piecesToClear.indexOf(piece) === -1) {
piecesToClear.push(piece);
}
}
for (var i = 0; i < piecesToClear.length; i++) {
this.remove(piecesToClear[i]);
}
return piecesToClear;
}
this.toString = function() {
var str = "";
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
str += this.board[y][x] ? "1" : "0";
}
str += "\n";
}
return str;
}
}
function Piece (shape, fill, stroke, paper, cellWidth) {
this.shape = shape;
this.fill = fill;
this.stroke = stroke;
this.cellWidth = cellWidth;
this.svgGroup = paper.g().append();
this.position = {x:0, y:0};
this.width = this.shape[0].length;
this.height = this.shape.length;
this.removed = false;
for (var y = 0; y < this.height; y++) {
for (var x = 0; x < this.width; x++) {
if (this.shape[y][x]) {
var rect = paper.rect(x*cellWidth, y*cellWidth, cellWidth, cellWidth);
rect.attr({
fill: this.fill,
stroke: this.stroke
});
rect.appendTo(this.svgGroup);
}
}
}
this.setPosition = function(x, y) {
this.position.x = x;
this.position.y = y;
}
this.getPosition = function() {
return this.position;
}
this.render = function() {
var matrix = new Snap.Matrix();
matrix.translate(this.position.x*cellWidth, this.position.y*cellWidth);
this.svgGroup.attr({
transform: matrix
});
}
this.getWidth = function() {
return this.width;
}
this.getHeight = function() {
return this.height;
}
this.getShape = function() {
return this.shape;
}
this.delete = function() {
this.svgGroup.remove();
}
this.isRemoved = function() {
return this.removed;
}
}
var shapes = [
[
[0,1,0],
[1,1,1]
],
[
[1,1,1,1]
],
[
[1,1,1],
[0,1,0],
[1,1,1]
],
[
[1,1],
[1,1]
],
[
[1,1,1],
[0,1,1],
[0,1,1],
[1,1,1]
],
[
[1,1,1,1],
[1,1,1,1],
[1,1,1,1],
[1,1,1,1]
],
[
[1,0,1],
[1,1,1]
]
];
var width = 10;
var height = 20;
var cellWidth = 20;
var paper = Snap("#svg");
var board = new Board(width, height);
var tick = 500;
paper.attr({
width: cellWidth*width,
height: cellWidth*height
});
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
var rect = paper.rect(x*cellWidth, y*cellWidth, cellWidth, cellWidth);
rect.attr({
fill: "#ccc",
stroke: "#ddd"
});
}
}
var piece = new Piece(shapes[0], "red", "white", paper, cellWidth);
piece.setPosition(0, 18);
board.place(piece);
piece = new Piece(shapes[1], "orange", "white", paper, cellWidth);
piece.setPosition(3, 19);
board.place(piece);
piece = new Piece(shapes[2], "yellow", "white", paper, cellWidth);
piece.setPosition(2, 8);
board.place(piece);
piece = new Piece(shapes[3], "green", "white", paper, cellWidth);
piece.setPosition(0, 17);
board.place(piece);
piece = new Piece(shapes[4], "blue", "white", paper, cellWidth);
piece.setPosition(2, 15);
board.place(piece);
piece = new Piece(shapes[5], "indigo", "white", paper, cellWidth);
piece.setPosition(1, 11);
board.place(piece);
piece = new Piece(shapes[6], "violet", "white", paper, cellWidth);
piece.setPosition(7, 17);
piece.render();
function update() {
if (piece.isRemoved()) {
return;
}
var position = piece.getPosition();
if (board.canPlace(piece, {x:position.x,y:position.y+1})) {
piece.setPosition(position.x,position.y+1);
board.place(piece);
for (var y = 0; y < piece.getHeight(); y++) {
if (board.hasFullLine(piece.getPosition().y+y)) {
var removed = board.clearPiecesOnLine(piece.getPosition().y+y);
setTimeout(function() {
for (var i = 0; i < removed.length; i++) {
removed[i].delete();
}
board.hardDropPieces();
},tick);
}
}
}
}
setTimeout(update, tick);
That's pretty much the gist of the Board-logic. Placed pieces kept by reference in an array, after clearing I sort the pieces that are not removed by their lowest point and then drop each one of them as far as they can go.
This works when no pieces are interconnected, but I just can't figure out how to do it when they are, as in this example.
Obviously, the blue piece is the lowest point, but it cannot move downards since the green piece is inside of it. I thought about merging them and dropping them, but that leads to other problems. Like what would happen in this case?
I'm pretty sure I'm just being thick, and there's a relatively easy way of fixing this...? Any help would be much appreciated!
All the pieces are automatically generated, and there's way too many, and more could be added any time, to not make a general solution.
I found two parts that had some missing logic. The first part was where you were performing the drops. You'll need to do it one step at a time for each block, and then keep doing it until you can drop no more. Like this
this.hardDropPieces = function() {
var pieces = this.pieces.slice();
pieces = pieces.sort(function(a,b) {
var aBottom = a.getPosition().y+a.getHeight();
var bBottom = b.getPosition().y+b.getHeight();
return bBottom-aBottom;
});
var canStillDrop = true;
while (canStillDrop) { // Keep going until we can't drop no more
canStillDrop = false;
for (var i = 0; i < pieces.length; i++) {
canStillDrop = this.hardDrop(pieces[i]) ? true : canStillDrop;
}
}
}
this.hardDrop = function(piece) {
var didDrop = false;
var position = piece.getPosition();
this.clearArea(piece);
if(this.canPlace(piece, {x: position.x, y: position.y+1})) {
piece.setPosition(position.x, position.y+1);
didDrop = true; // Oh, I see we have dropped
}
this.place(piece);
return didDrop; // Did we drop a spot? Then we should keep going
}
The second part is that you could use a little recursiveness to check if any of the tiles keeping you from dropping is actually connected to the floor. This one you already recognize:
this.canPlace = function(piece, at) {
// Will it fall below the floor? Then it's a no-go
if (piece.getHeight()+at.y > this.height) {
return false;
}
// Loop through shape
for (var y = 0; y < piece.getHeight(); y++) {
for (var x = 0; x < piece.getWidth(); x++) {
// Ignore non-shape positions
if (!piece.shape[y][x]) continue;
// Get piece at current shape position
var pieceAtPos = this.board[y+at.y][x+at.x];
// Is the piece (or any that it's resting on) connected to the floor?
if (pieceAtPos && pieceAtPos!==piece && this.isPieceGrounded(pieceAtPos, [piece]) ){
return false;
}
}
}
return true;
}
But say hello also to isPieceGrounded.
this.isPieceGrounded = function(piece, testedPieces) {
// Check all positions BELOW the piece
var at = { x: piece.getPosition().x, y: piece.getPosition().y+1 };
// Is it connected to the floor?
if (piece.getHeight()+at.y+1 >= this.height) {
return true;
}
// *Sigh* Loop through THIS whole piece
for (var y = 0; y < piece.getHeight(); y++) {
for (var x = 0; x < piece.getWidth(); x++) {
if (!piece.shape[y][x]) continue;
var pieceAtPos = this.board[y+at.y][x+at.x];
if (pieceAtPos && pieceAtPos!==piece && testedPieces.indexOf(pieceAtPos) < 0) {
// Keep a list of all tested pieces so we don't end up in an infinite loop by testing them back and forth
testedPieces.push(pieceAtPos);
// Let's test that one and all its connected ones as well
if (this.isPieceGrounded(pieceAtPos, testedPieces)) {
return true;
};
}
}
}
return false;
}
http://jsfiddle.net/971yvc8r/2/
I'm sure there are lots of different solutions, but I think something like this might be the most efficient.