I have this class:
class AGV {
constructor(id, x, y, agvcolor, matrixTable) {
this.id = id;
this.x = x;
this.y = y;
this.color = agvcolor;
this.xOffset = 1;
this.yOffset = 1;
this.matrix = matrixTable;
}
setX(newX) {
this.x = newX;
}
setY(newY) {
this.y = newY;
}
lastCoordinates() {
return JSON.stringify({
'x': this.x,
'y': this.y
});
}
spawn() {
var xCoord = this.x - this.xOffset;
var yCoord = this.y - this.yOffset;
var cell = this.matrix.childNodes[1].children[yCoord].children[xCoord];
cell.style.background = this.color;
cell.innerHTML = this.id;
}
moveToCell(x, y) {
console.log("rolling to x: " + x + " y: " + y);
this.clearPreviousCell(this.x, this.y);
// offsets are needed to convert array index to actual position
var xCoord = x - xOffset;
var yCoord = y - yOffset;
var cell = this.matrix.childNodes[1].children[yCoord].children[xCoord];
cell.style.background = this.color;
cell.innerHTML = this.id;
}
clearPreviousCell(x, y) {
var xCoord = x - xOffset;
var yCoord = y - yOffset;
var cell = this.matrix.childNodes[1].children[yCoord].children[xCoord];
cell.style.background = "#fff";
cell.innerHTML = "";
}
runTask(x, y, z) {
console.log("Running task. Target: X: " + x + " Y: " + y);
if (this.x < x) {
//while (this.x < x) {
this.x += 1;
setTimeout(function() {
moveToCell(this.x, y);
}, 800);
//}
}
}
}
And whenever I call moveToCell() function inside function runTask(), I get an error stating that the "moveToCell" function is not defined and as so, I cannot update the object's position in the map.
I've also tried this.moveToCell() insted, but results in the same problem. I don't understand what is wrong here.
Can anyone please post any thoughts on this? Thanks in advance
You need to specify the instance to call the method on, which should be this.
Also, this is not saved in normal closures. Use an arrow function to keep the context.
runTask(x, y, z) {
console.log("Running task. Target: X: " + x + " Y: " + y);
if (this.x < x) {
//while (this.x < x) {
this.x += 1;
setTimeout(() => this.moveToCell(this.x, y), 800);
//}
}
}
Since it's inside the class, you need to use the this operator. Change it to:
this.moveToCell(this.x, y);
As you're using it within setTimeout(function () { ... }), you need to cache this:
this.x += 1;
_this = this;
setTimeout(function() {
_this.moveToCell(this.x, y);
}, 800);
I am working on a circle packing algorithm in p5.js and vue 3. For some reason it is adding an item to an array that it shouldn't be able to add. I've written some collisions with other objects, and while the program is running it works fine, but when I initialise an object it doesn't seem to care about them. The collisions always return false on initialisation, and even before updating the object again, it says it does collide, but only after adding it to my array. Which is the thing I don't want to be happening, since this clutters my array.
My code:
export function circlePacking(element, imageToShow) {
return function (p) {
const p5Canvas = element;
let circles = [];
let image;
p.preload = function () {
image = p.loadImage(imageToShow);
};
p.setup = function () {
p.colorMode('RGB');
p.disableFriendlyErrors = true;
p.frameRate(60);
this.averageColor = averageColor(p, image);
const canvas = p.createCanvas(p5Canvas.offsetWidth - 1, p5Canvas.offsetHeight - 1, 'P2D');
canvas.parent(p5Canvas);
image.resize(p.width, p.height);
image.loadPixels();
};
p.draw = function () {
if (p5Canvas.offsetWidth - 1 !== p.width || p5Canvas.offsetHeight - 1 !== p.height) {
p.resizeCanvas(p5Canvas.offsetWidth - 1, p5Canvas.offsetHeight - 1);
}
p.background(this.averageColor);
for (let i = 0; i < 5; i++) {
let x = p.round(getRandom(0, image.width));
let y = p.round(getRandom(0, image.height));
let location = (x + y * image.width) * 4;
let color = p.color(image.pixels[location], image.pixels[location + 1], image.pixels[location + 2])
let c = new Circle(x, y, color);
console.log(c);
//here collidesWithCircle is false, even though it should be true
if (!c.collidesWithCircle() && !c.collidesWithWall()) {
circles.push(c);
}
}
circles.forEach(circle => {
circle.draw();
})
circles.forEach(circle => {
circle.checkCollision();
})
};
class Circle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.size = 1;
this.growing = true;
this.collidesWall = this.collidesWithWall();
this.collidesCircle = this.collidesWithCircle();
}
draw() {
p.stroke(0);
p.strokeWeight(1);
p.fill(this.color);
p.circle(this.x, this.y, this.size)
if (this.growing) {
this.grow()
}
}
grow() {
this.size += 0.3;
}
checkCollision() {
if (this.growing) {
this.collidesWithCircle();
this.collidesWithWall();
}
}
collidesWithCircle() {
circles.forEach(circle => {
if (p.dist(this.x, this.y, circle.x, circle.y) < (this.size / 2) + (circle.size / 2) && this !== circle) {
this.growing = false;
return true;
}
});
return false;
}
collidesWithWall() {
if (this.x + this.size / 2 > p.width || this.x - this.size / 2 < 0 || this.y + this.size / 2 > p.height || this.y - this.size / 2 < 0) {
this.growing = false;
return true;
}
return false;
}
}
};
}
I am trying to put a word search game on my web page.
I see here is a beautiful example: Embed a Word Search in Your Site
I want to make similar gui, so that when the user selects the word letters from the grid by dragging mouse, it will draw a red elastic rubber band around the letters selected.
How do they do it on their web page? I tried to "inspect" the html after the band is drawn, however did not see any element added.
Can somebody give me any clue?
Here is a JS example
class App {
constructor(div) {
this.div = div;
this.canvas = document.getElementById("canvas");
this.ctx = canvas.getContext('2d');
this.dirty = true;
this.prev = +new Date();
this.resize();
window.addEventListener('resize', () => this.resize(), false);
this.canvas.addEventListener("mousedown", (event) => {
this.touchdown(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("mousemove", (event) => {
this.touchmove(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("mouseup", (event) => {
this.touchup(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchstart", (event) => {
this.touchdown(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchmove", (event) => {
this.touchmove(App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchend", (event) => {
this.touchup(App.getmousePos(event));
event.preventDefault();
});
}
resize() {
this.canvas.width = this.div.clientWidth;
this.canvas.height = this.div.clientWidth;
this.draw();
}
loop() {
let now = +new Date();
let dt = now - this.prev;
this.prev = now;
this.update()
if (this.dirty)
this.draw();
window.requestAnimationFrame(() => this.loop());
}
update(dt) {}
draw() {
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.ctx.rect(0, 0, 100, 100);
this.ctx.fill();
}
touchdown(x, y) { console.log("down", x, y); }
touchmove(x, y) {}
touchup(x, y) {}
static getmousePos(event) {
if (event.changedTouches) {
return [event.changedTouches[0].pageX, event.changedTouches[0].pageY];
}
else {
var rect = event.target.getBoundingClientRect();
return [event.clientX- rect.left, event.clientY - rect.top];
}
}
}
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
add(v_x, y) {
if (y != undefined) {
return new Vector(this.x + v_x, this.y + y);
}
else {
return new Vector(this.x + v_x.x, this.y + v_x.y);
}
}
sub(v_x, y) {
if (y != undefined) {
return new Vector(this.x - v_x, this.y - y);
}
else {
return new Vector(this.x - v_x.x, this.y - v_x.y);
}
}
mul(s) {
return new Vector(this.x * s, this.y * s);
}
get slength() {
return this.x * this.x + this.y * this.y;
}
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
get angle() {
return Math.atan2(this.y, this.x);
}
}
class Grid {
constructor(width, height, data) {
this.width = width;
this.height = height;
this.data = data || new Array(width*height).fill("_");
}
get length() {
return this.width*this.height;
}
get(x_pos, y) {
if (y != undefined)
return this.data[x_pos+y*this.width];
else
return this.data[x_pos];
}
set(value, x_pos, y) {
if (y != undefined)
this.data[x_pos+y*this.width] = value;
else
this.data[x_pos] = value;
}
clone() {
return new Grid(this.width, this.height, this.data.slice());
}
finalize() {
this.data = this.data.map(v => v == "_" ? String.fromCharCode(Math.floor(Math.random() * 26) + 97) : v);
}
print() {
for (let i = 0; i < this.height; i++)
console.log(this.data.slice(i*this.width, (i+1)*this.width).join(","));
}
}
class Puzzle {
constructor(width, height, words, directions) {
this.grid = new Grid(width, height);
this.words = words;
this.placeWords(words, directions);
}
placeWords(words, directions) {
const range = (N) => Array.from({length: N}, (v, k) => k);
const shuffle = (array) => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
words = words.slice();
let positions = range(this.grid.length);
let stack = [ {
grid: this.grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice()) } ];
while (true) {
let current = stack[stack.length-1];
if (!current)
throw Error("impossible");
let dir = current.directions.pop();
if (!dir) {
current.positions.pop();
current.directions = shuffle(directions.slice());
dir = current.directions.pop();
}
if (current.positions.length <= 0) {
words.unshift(current.word);
stack.pop();
}
else {
let pos = current.positions[current.positions.length-1];
let grid = this.placeWord(current.grid, current.word, pos, dir);
if (grid) {
if (words.length > 0) {
stack.push({grid: grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice())});
}
else {
grid.finalize();
this.grid = grid;
break;
}
}
}
}
}
placeWord(grid, word, position, direction) {
let copy = grid.clone();
position = new Vector(position % grid.width, Math.floor(position / grid.width));
let letters = [...word];
while (0 <= position.x && position.x < grid.width && 0 <= position.y && position.y < grid.height) {
if (letters.length <= 0)
break;
let letter = letters.shift();
if (copy.get(position.x, position.y) == "_" || copy.get(position.x, position.y) == letter) {
copy.set(letter, position.x, position.y);
position = position.add(direction);
}
else {
return null;
}
}
if (letters.length > 0)
return null;
else
return copy;
}
wordAt(position, direction, length) {
let word = new Array(length);
for (let i = 0; i < length; i++) {
word[i] = this.grid.get(position.x, position.y);
position = position.add(direction);
}
return word.join("");
}
print() {
this.grid.print();
}
}
class Selection {
constructor(position, fill=false) {
this.position = position;
this._fill = fill;
this.direction = new Vector(1, 0);
this.length = 0;
this.flength = 0;
}
clone() {
return new Selection(this.position).to(this.position.add(this.direction.mul(this.length)));
}
to(position) {
let direction = position.sub(this.position);
if (Math.abs(direction.y) == 0) {
this.direction = new Vector(direction.x >= 0 ? 1 : -1, 0);
this.length = direction.x * this.direction.x;
}
else if (Math.abs(direction.x) == 0) {
this.direction = new Vector(0, direction.y >= 0 ? 1 : -1);
this.length = direction.y * this.direction.y;
}
else {
this.direction = new Vector(direction.x >= 0 ? 1 : -1, direction.y >= 0 ? 1 : -1);
this.length = direction.x * this.direction.x;
}
this.flength = direction.length;
return this;
}
fill(fill=true) {
this._fill = fill;
return this;
}
draw(ctx, wsize, hsize) {
let pos = this.position.mul(wsize);
let length = wsize * (this.flength+1);
let angle = this.direction.angle;
//console.log(this.x, this.y, x, y, length, angle, this.dx, this.dy);
ctx.save();
ctx.strokeStyle = "black";
ctx.fillStyle = "yellow";
ctx.translate(pos.x+hsize*0.5, pos.y+hsize*0.5);
ctx.rotate(angle);
this.drawHighlight(ctx, -hsize*0.5, -hsize*0.5, length, hsize, hsize*0.5, this._fill, true);
ctx.restore();
}
drawHighlight(ctx, x, y, width, height, radius=20, fill=true, stroke=false) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}
}
class PuzzleApp extends App {
constructor(div, puzzle) {
super(div);
this.puzzle = puzzle;
this.renderList(document.getElementById("list"));
this.selections = new Array();
this.loop();
}
renderList(parent) {
this.puzzle.words.forEach(word => {
let li = document.createElement("li");
let text = document.createTextNode(word);
li.appendChild(text);
parent.appendChild(li);
});
}
gridSize() {
let wsize = Math.floor(this.canvas.width/this.puzzle.grid.width);
let hsize = Math.floor(this.canvas.width/this.puzzle.grid.height);
return [wsize, hsize];
}
clientToGrid(x, y) {
let [wsize, hsize] = this.gridSize();
x = Math.floor(x / wsize);
y = Math.floor(y / hsize);
return [x, y];
}
draw() {
if (!this.puzzle)
return;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
let [wsize, hsize] = this.gridSize();
this.selections.forEach(s => s.draw(this.ctx, wsize, hsize));
if (this.selection)
this.selection.draw(this.ctx, wsize, hsize);
let x = 0;
let y = 0;
this.ctx.fillStyle = "black";
this.ctx.font = (wsize * 0.5) + 'px sans-serif';
this.ctx.textAlign = "center";
this.ctx.textBaseline = "middle";
for (let j = 0; j < this.puzzle.grid.height; j++) {
for (let i = 0; i < this.puzzle.grid.width; i++) {
let letter = this.puzzle.grid.get(i, j);
this.ctx.fillText(letter, x+wsize * 0.5, y+wsize * 0.5);
x += wsize;
}
x = 0;
y += wsize;
}
}
touchdown(x, y) {
[x, y] = this.clientToGrid(x, y);
this.selection = new Selection(new Vector(x, y));
this.dirty = true;
}
touchmove(x, y) {
if (!this.selection)
return;
[x, y] = this.clientToGrid(x, y);
this.selection.to(new Vector(x, y));
}
touchup(x, y) {
if (!this.selection)
return;
let word = this.puzzle.wordAt(this.selection.position, this.selection.direction, this.selection.length+1);
console.log(word);
if (word) {
let list = document.getElementById("list");
let elements = list.getElementsByTagName("li");
Array.prototype.some.call(elements, li => {
if (li.innerText == word) {
li.classList.add("found");
this.selections.push(this.selection.clone().fill());
return true;
}
return false;
});
}
this.selection = null;
}
}
let app = null;
document.addEventListener("DOMContentLoaded", function(event) {
const wordLists = [
["pear", "apple", "banana", "peach", "kiwi", "prune", "persimon"]
]
const pick = (array) => array[Math.floor(Math.random() * (array.length))];
let params = (new URL(document.location)).searchParams;
let directions = [new Vector(1,0), new Vector(0,1)];
if (params.get("diagonal")) {
directions.push(new Vector(1,1));
}
if (params.get("backwards")) {
directions.push(new Vector(-1,0));
directions.push(new Vector(0,-1));
if (params.get("diagonal")) {
directions.push(new Vector(-1,-1));
}
}
let puzzle = new Puzzle(8, 8, pick(wordLists), directions);
puzzle.print();
app = new PuzzleApp(document.getElementById("canvasContainer"), puzzle);
});
html, body {
width: 100%;
height: 100%;
margin: 0px;
}
ul {
display: inline-block;
font-size: 7vmin;
padding-left: 14vmin;
}
li.found {
text-decoration: line-through;
}
div#canvasContainer {
width: 100%;
display: inline-block;
}
#media (min-aspect-ratio: 1/1) {
ul {
font-size: 4vmin;
padding-left: 7vmin;
}
div#canvasContainer {
width: 50%;
}
}
<div id="canvasContainer"><canvas id="canvas" width="320" height="320"></canvas></div>
<ul id="list"></ul>
I'll let you figure out how to change the thickness and color of the rubber band.
The red band is an svg. It doesn't get added when you click, but rather it gets redrawn.
I have inspected the referenced website and it seems like they draw rubber band thing in SVG. If you inspect this SVG tag while drawing:
you will notice it shows some updates.
You can use same SVG methodology. Or Another way is to use an absolute positioned div and update top, left css values based on mousedown, width based on mousemove event.
I have seen "what" they have done.
They have created an svg element. This svg sits exactly above and covers the entire table that contains the grid with letters. When the user clicks any letter and starts drag, they add a rectangle to the svg. Depending on how the cursor will move they do the angle transform to the rectangle. If the word is selected correctly, the rectangle survives; else, it is removed.
I need to find out now, "how" part of it.
Thanks to all for a very quick response.
I am having a bit of difficulty understanding a few lines of code from page 108 of Marijn Haverbeke's "Eloquent JavaScript" book.
Namely, in the example below, I don't understand what "element = (x, y) => undefined" in the constructor parameter list is doing, when the Matrix object is instantiated with "let matrix = new Matrix(3, 3, (x, y) => value ${x}, ${y});"
Can some step-by-step it for me? It seems like we are passing a function to the constructor, but I don't get why the constructor parameter list is setup the way it is with another arrow function.
var Matrix = class Matrix {
constructor(width, height, element = (x, y) => undefined) {
this.width = width;
this.height = height;
this.content = [];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
this.content[y * width + x] = element(x, y);
}
}
}
get(x, y) {
return this.content[y * this.width + x];
}
set(x, y, value) {
this.content[y * this.width + x] = value;
}
}
var MatrixIterator = class MatrixIterator {
constructor(matrix) {
this.x = 0;
this.y = 0;
this.matrix = matrix;
}
next() {
if (this.y == this.matrix.height) return {done: true};
let value = {x: this.x,
y: this.y,
value: this.matrix.get(this.x, this.y)};
this.x++;
if (this.x == this.matrix.width) {
this.x = 0;
this.y++;
}
return {value, done: false};
}
}
Matrix.prototype[Symbol.iterator] = function() {
return new MatrixIterator(this);
};
let matrix = new Matrix(3, 3, (x, y) => `value ${x}, ${y}`);
for (let {x, y, value} of matrix) {
console.log(x, y, value);
}
Ok, I forgot in ES6+ an equal sign after a parameter is a default value if the parameter is missing or undefined. The way he did this is just a little awkward.
Im not sure how the value is being unpacked in:
for (let {y,x, value} of matrix) {
console.log(x, y, value);
}
how does it know to pull it from the next().value.value of the Matrix.prototype[Symbol.iterator]?
class Matrix {
constructor(width, height, element = (x, y) => undefined) {
this.width = width;
this.height = height;
this.content = [];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
this.content[y * width + x] = element(x, y);
}
}
}
get(x, y) {
return this.content[y * this.width + x];
}
set(x, y, value) {
this.content[y * this.width + x] = value;
}
}
let obj = new Matrix(2, 2, (x, y) => `value ${x},${y}`)
class MatrixIterator {
constructor(matrix) {
this.x = 0;
this.y = 0;
this.matrix = matrix;
}
next() {
if (this.y == this.matrix.height) return {done: true};
let value = {x: this.x,
y: this.y,
value: this.matrix.get(this.x, this.y)};
this.x++;
if (this.x == this.matrix.width) {
this.x = 0;
this.y++;
}
return {value, done: false};
}
}
Matrix.prototype[Symbol.iterator] = function() {
return new MatrixIterator(this);
};
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
for (let {y,x, value} of matrix) {
console.log(x, y, value);
}
// → 0 0 value 0,0
// → 1 0 value 1,0
// → 0 1 value 0,1
// → 1 1 value 1,1
Because for (let obj of matrix) gives you the object you're building in this line let value = {x: this.x, y: this.y, value: this.matrix.get(this.x, this.y)};, into every loop iteration, then the destructuring syntax {y, x, value} extracts every field.