/    Sign up×
Community /Pin to ProfileBookmark

How to make canvas to response on dragging things on

I need to make canvas change its images once the outer image was dragged and dropped on top of it. Now it only works with dragging and dropping things on top of it but without any effect to the canvas so it could change it’s white images to blue ones along the road to the end when an imager dropped on top of it. I’d appreciate some working examples.

Here comes the ideal working example [upl-image-preview url=https://www.webdeveloper.com/assets/files/2020-01-07/1578423511-822773-example.png]
index.html

[code]<script src=”https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js”></script>
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”utf-8″>
<title>cameras of the city</title>
<link rel=”stylesheet” href=”js/jquery-ui-1.12.1/jquery-ui.min.css”>
<link rel=”stylesheet” href=”styles.css”>
<script src=”js/jquery-3.4.1.js”></script>
<script src=”js/jquery-ui-1.12.1/jquery-ui.min.js”></script>
</head>
<body>
<header>
<h1>cameras of the city</h1>
</header>
<div id=”game”>
<canvas height=”450px” width=”450px” id=”canvas”></canvas>
<div class=”cameras”>
<img id=”cam1″ class=”drag” src=”images/camera1.png”>
<img id=”cam2″ class=”drag” src=”images/camera2.png”>
<img id=”cam3″ class=”drag” src=”images/camera3.png”>
<img id=”cam4″ class=”drag” src=”images/camera4.png”>
</div>
</div>
<footer>
created:
</footer>

<script src=”js/core.js”></script>
</body>
</html>[/code]

styles.css

[code]body {
color: #000;
}

h1, footer {
text-align: center;
margin-top: 10px;
}

#game {
border: 1px solid #666;
position: relative;
width: 600px;
height: 450px;
margin: 0 auto;
z-index: 1;
background-color: gold;
}

.drag {
cursor: pointer;
}

#canvas {
cursor: pointer;
}

.cameras {
position: absolute;
flex-direction: column;
flex-wrap: wrap;
right: 0;
top: 0;
z-index: 200;
display: flex;
}[/code]

core.js

[code]// 1 – wall, 0 – free/street
const map1 = [
[1, 1, 1, 1],
[0, 0, 0, 0],
[1, 0, 1, 1],
[1, 0, 0, 0]
];
const map2 = [
[1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 1, 0, 1, 1, 1, 0, 1],
[0, 0, 1, 0, 1, 0, 0, 0, 1],
[1, 1, 1, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 0, 1, 1, 1, 1]
];

const map3 = [
[1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 1, 1, 1, 0, 1],
[1, 0, 1, 1, 1, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 1, 0, 1, 1, 1, 0, 1],
[0, 0, 1, 0, 1, 0, 0, 0, 1],
[1, 1, 1, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 1, 0, 1, 1, 1],
[1, 1, 1, 0, 0, 1, 1, 1, 1]
];

const city = map3;
const size = 41;

window.onload = function() {
const canvas = document.getElementById(“canvas”);
const ctx = canvas.getContext(“2d”);

ctx.fillStyle = “#FFF”;
ctx.fillRect(0, 20, 150, 50);
ctx.fillRect(40, 40, 50, 100);
ctx.fillRect(0, 130, 90, 50);

//draw images
const p = ctx.lineWidth; //padding
for (let i = 0; i < city.length; i++) {
for (let j = 0; j < city[i].length; j++) {
const x = i * size;
const y = j * size;
const img = new Image();
img.onload = function() {
ctx.drawImage(img, x, y, size+p*2, size+p*2);
};
img.src = city[i][j] == 0 ? “images/white.png” : “images/black.png”;
}
}
};

$(document).ready(function(){
let currentDroppable = null, clone;

function moveAt(pageX, pageY, shiftX, shiftY) {
clone.style.left = pageX – shiftX + ‘px’;
clone.style.top = pageY – shiftY + ‘px’;
}

function onMouseMove(event) {
moveAt(event.pageX, event.pageY, 0, 0);

clone.hidden = true;
let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
clone.hidden = false;

if (!elemBelow) return;

let droppableBelow = elemBelow.closest(‘#canvas’);
if (currentDroppable != droppableBelow) {
if (currentDroppable) { // null when we were not over a droppable before this event
leaveDroppable(currentDroppable);
}
currentDroppable = droppableBelow;
if (currentDroppable) { // null if we’re not coming over a droppable now
// (maybe just left the droppable)
enterDroppable(currentDroppable);
}
}
}

$(document).on(‘mousedown’, ‘.drag’, function(e){

var tmp = $(this).clone().removeClass(“drag”);
$(tmp).css({position: ‘absolute’, top: $(this).offset().top + “px”, left: $(this).offset().left + “px”, opacity: 1, ‘z-index’: 1000});
$(‘body’).append($(tmp));

clone = $(tmp)[0];

let shiftX = event.clientX – clone.getBoundingClientRect().left;
let shiftY = event.clientY – clone.getBoundingClientRect().top;

moveAt(event.pageX, event.pageY, shiftX, shiftY);

document.addEventListener(‘mousemove’, onMouseMove);

clone.onmouseup = function() {
document.removeEventListener(‘mousemove’, onMouseMove);
clone.onmouseup = null;
};

});

function enterDroppable(elem) {
elem.style.background = ‘blue’;
}

function leaveDroppable(elem) {
elem.style.background = ”;
}

cam1.ondragstart = function() {
return false;
};
});[/code]

Edited by site staff: Replaced backticks by code tags: [code]code here[/code]

to post a comment
HTMLJavaScript

84 Comments(s)

Copy linkTweet thisAlerts:
@SempervivumJan 07.2020 — Hi BenBruce and welcome to the forum!

I didn't dive into every detail of your code but noticed this:
  • 1. You include jQuery UI but you don't use it

  • 2. You include jQuery twice


  • IMO it make things complicated mixing up HTML and CSS (the images at the right) and canvas. Instead I recommend to do it all in canvas.

    And additionally I recommend using the library jCanvas. It makes handling the canvas easier and supports dragging objects:

    https://projects.calebevans.me/jcanvas/

    https://projects.calebevans.me/jcanvas/docs/draggableLayers/
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 07.2020 — Thanks, I'll try to do it but I have little time to remake it all using only canvas. I'm really new to it, could you at least show me the starting point on how to make it right because it took so much time to do this?
    Copy linkTweet thisAlerts:
    @SempervivumJan 07.2020 — it took so much time to do this?[/quote]
    This confirms my opinion:
    it make things complicated mixing up HTML and CSS[/quote]
    IMO it would be easier and faster to rebuild it from scratch using jCanvas than add the missing features to the existing code.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 07.2020 — Do you think it's not hard to implement all that logic with switching colors of the roads to the canvas?
    Copy linkTweet thisAlerts:
    @SempervivumJan 07.2020 — If I interpret your code correctly, switching the colors is not yet implemented. You need to check if your objects, image and road, intersect. This is not trivial but algorithms can be found in the web. It's not that complicated it the objects are basic shapes like circles and squares.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 07.2020 — I'll try to draw it all on canvas but could you help me with that canvas reacting stuff because I looked about that on the internet and I found none?
    Copy linkTweet thisAlerts:
    @SempervivumJan 07.2020 — I've created a simple demo that implements a static rectangle and a draggable circle. The color of the rectangle is changed when both objects or layers intersect.

    https://jsfiddle.net/Sempervivum/dgyb0rxL/28/
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — @Sempervivum#1612628 https://jsfiddle.net/BenBruceDjent/bn7x5c0g/

    I draw those images on canvas but I don't know how to add this drag function to the newly created camera images and I'm not sure that it's possible to implement color road changing logic through comparison coordinates simply because of there're several city maps (maps with roads) and it'd mean to make those coordinate intersection individually for each one
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — I'm not sure that it's possible to implement color road changing logic through comparison coordinates[/quote]
    I'm fairly shure that this [b]is[/b] possible and I do not see any other way to check it the camera is on a road. In order to demonstrate I extended my fiddle a bit:

    https://jsfiddle.net/Sempervivum/k02sq4jg/61/

    The code will work with any map data. Replace the map by another one and check.

    Replacing the circles by images should be easy.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — @Sempervivum#1612649 Could you take a look at the task description, maybe I got something wrong?

    https://gofile.io/?c=5nnjs6
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — If I read the description correctly, it's left up to you which technique you use. Thus canvas might be an option. And the description doesn't specify how the cameras should be moved. Might it be possible too to move them by the arrow keys? However using the mouse is more sophisticated.

    Unfortunately I could not find a description of that game. Apparently there are different types of camera and each type can view to different directions. And, to make the game more challenging, the directions are not visible to the user.

    Keeping this in mind I extended my demo in that way that the camera can be dropped on a road and then the road is highlighted downwards to it's end. Feel free to add more cameras, use images for the cameras and extend them for viewing in other directions.

    https://jsfiddle.net/Sempervivum/cjao49xe/62/
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — @Sempervivum#1612653 It seems like adding images goes a bit another way than drawing shapes, it needs getContext from getElementById function and when I did it once I move the cursor away from the canvas the image disappears https://jsfiddle.net/BenBruceDjent/bn7x5c0g/3/
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — No, drawing images is much easier when using jCanvas: No image object necessary, simply call `cv.drawImage</C>:
    <CODE> cv.drawImage({
    // source: img,
    layer: true,
    source: 'https://webentwicklung.ulrichbangert.de/images/bild1.jpg',
    x: xImg,
    y: yImg,
    width: size,
    height: size,
    fromCenter: false,
    draggable: true,
    drag: onDrag,
    dragstop: onDragEnd
    });
    </CODE>Additionally we need to set <C>
    layer</C> and <C>draggable`
    to true to make the image draggable. Then add the eventhandlers.

    onload event for the complete script is not necessary in jsfiddle as this is done automatically.

    This works so far:

    https://jsfiddle.net/Sempervivum/5odjs4f0/28/
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — Came something like that but it works really strange https://jsfiddle.net/BenBruceDjent/bn7x5c0g/6/
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — You have to include jCanvas from CDN like this:

    `&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jcanvas/21.0.1/jcanvas.js"&gt;&lt;/script&gt;`

    And you will have to load the images from an online resource like I did.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — https://jsfiddle.net/BenBruceDjent/en8ypg5a/2/

    Did it but it still works strange
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — Apparently you did something completely different. Change the head of the HTML to this:
    &lt;!DOCTYPE html&gt;
    &lt;html lang="en"&gt;
    &lt;head&gt;
    &lt;meta charset="utf-8"&gt;
    &lt;title&gt;cameras of the city&lt;/title&gt;
    &lt;link rel="stylesheet" href="styles.css"&gt;
    &lt;script src="https://code.jquery.com/jquery-3.4.1.min.js"&gt;&lt;/script&gt;
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jcanvas/21.0.1/jcanvas.js"&gt;&lt;/script&gt;
    &lt;/head&gt;
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — https://jsfiddle.net/BenBruceDjent/bn7x5c0g/9/

    The same result
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — @Sempervivum#1612669 Do all cameras change color only in one direction? Seems like it does but only with some random chance of success and sometimes it changes color only for one square in the middle of the road.
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — Do all cameras change color only in one direction?[/quote]
    Yes, currently they do. I left this up to you to implement different directions. The direction is defined in this loop:
    let finish = false;
    for (let i2 = i; i2 &lt; themap.length &amp;&amp; !finish; i2++) {
    if (themap[i2][j] == 0) {
    const themaplayer = maplayers[i2][j];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — When changing i2 by `i2++</C> the direction is downwards, when changing it by <C>i2--</C> the direction is upwards.<br/>
    Don't forget to adjust the limit to <C>
    i2 &gt;= 0`
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — And cloning those cameras because I can have several same cameras on the streets
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — @Sempervivum#1612673 i2 >= 0 only makes changing color all down the canvas, why it needs to be adjusted?
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — You have to change both, the limit and the incrementation:

    `for (let i2 = i; i2 &gt;= 0 &amp;&amp; !finish; i2--) {`
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — Now it barely works https://jsfiddle.net/BenBruceDjent/bn7x5c0g/12/
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — You have to change your image path:
    cv.drawImage({
    layer: true,
    source: 'https://res.cloudinary.com/benbruce/image/upload/v1578505333/camera' + (i+1) +'.png',
    x: 559,
    y: y,
    width: size,
    height: size,
    fromCenter: false,
    draggable: true,
    drag: onDrag,
    dragstop: onDragEnd
    });

    And before starting the second loop we have to set the variable finish to false again:
    function onDragEnd(layer) {
    console.log('dragend');
    // x and y are properties of the layer, i. e. the circle
    const x = layer.x + diameter, y = layer.y + diameter;
    const i = Math.floor(y / hRect), j = Math.floor(x / wRect);
    // get the map layer the mouse is currently over
    const themaplayer = maplayers[i][j];
    console.log(x, y, i, j);
    if (isIntersect(layer, themaplayer, themap[i][j])) {
    themaplayer.fillStyle = 'lightsalmon';
    }
    let finish = false;
    for (let i2 = i; i2 &lt; themap.length &amp;&amp; !finish; i2++) {
    if (themap[i2][j] == 0) {
    const themaplayer = maplayers[i2][j];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    finish = false; // &lt;-- has to be set to false again
    for (let i2 = i; i2 &gt;= 0 &amp;&amp; !finish; i2--) {
    if (themap[i2][j] == 0) {
    const themaplayer = maplayers[i2][j];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    cv.drawLayers();
    }
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — PS: And there was the `windows.onload` again. jsfiddle does this for us already.
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — This works for me:

    https://jsfiddle.net/Sempervivum/5xpyq1am/10/
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — PPS: In order to set x and y to the center of the circle image we have to divide the diameter by 2:
    function onDragEnd(layer) {
    console.log('dragend');
    // x and y are properties of the layer, i. e. the circle
    const x = layer.x + diameter / 2, y = layer.y + diameter / 2;
    const i = Math.floor(y / hRect), j = Math.floor(x / wRect);
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — https://gofile.io/?c=9QTdQW

    I recorded the workflow of the game. Does it work the same way as you have it?
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — I see, please read my previous posting (PPS).
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — @Sempervivum#1612681 Dividing didn't make much. It almost still works the same way as I recorded
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — Please post the URL of your last fiddle.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — https://jsfiddle.net/BenBruceDjent/bn7x5c0g/15/
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — I see, we have to divide in [b]both[/b] functions, onDrag and onDragEnd.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — Yeah, you were right. But all cameras do the same thing so it changes all from top to bottom and doesn't make the same to the right and left
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — Cameras are all the same for the roads but the first camera must cover only one direction. For instance, if it's placed in the middle of the road it must cover only one direction. The second camera does cover 2. Do you think is it real to make?
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — Yes, shurely this will be possible. Place the directions into an array like this:
    const searchDirs = [
    {up: true, down: false, left: false, right: false},
    {up: false, down: true, left: false, right: false},
    {up: false, down: false, left: true, right: false},
    {up: false, down: false, left: false, right: true},
    ];
    Then store the index in that array at the camera layer:
    for (let i = 0; i &lt; 4; i++) {
    const y = i * size;
    console.log(i+1,y, 'https://res.cloudinary.com/benbruce/image/upload/v1578505333/camera' + (i+1) +'.png')
    cv.drawImage({
    layer: true,
    source: 'https://res.cloudinary.com/benbruce/image/upload/v1578505333/camera' + (i+1) +'.png',
    x: 559,
    y,
    width: size,
    height: size,
    fromCenter: false,
    draggable: true,
    drag: onDrag,
    dragstop: onDragEnd,
    idxDirection: i // &lt;-- here
    });
    }
    At dragend you can then control if a search should be done or not:
    <i>
    </i> const directions = searchDirs[layer.idxDirection];
    if (directions.down) {
    for (let i2 = i; i2 &lt; themap.length &amp;&amp; !finish; i2++) {
    if (themap[i2][j] == 0) {
    const themaplayer = maplayers[i2][j];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    }
    if (directions.up) {
    finish = false;
    for (let i2 = i; i2 &gt;= 0 &amp;&amp; !finish; i2--) {
    if (themap[i2][j] == 0) {
    const themaplayer = maplayers[i2][j];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    }
    (untested)
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — https://gofile.io/?c=NiB1bd

    Here what I got
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — https://jsfiddle.net/BenBruceDjent/bn7x5c0g/17/
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — Left and right will not work yet, you need to add them in onDragEnd().
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — https://jsfiddle.net/BenBruceDjent/bn7x5c0g/19/ I kinda did but each camera works only in one direction now
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — Well done, this fiddle works as configured. We configured only one direction for each camera. If you want more you can simply adjust searchDirs:
    const searchDirs = [
    {up: true, down: true, left: false, right: false}, // the first camera will search up and down now
    {up: false, down: true, left: false, right: false},
    {up: false, down: false, left: true, right: false},
    {up: false, down: false, left: false, right: true},
    ];
    However there are more combinations. One might configure all and choose the entry randomly.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — The thing is that every camera has to watch every direction. The only difference as I understood it from the working example in the task is camera only allowed to watch 1, 2, 3, 4 directions at the same time because on the picture there 2 cameras placed with the same number of watching directions and the first one watches up and left when the second one up and down.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — I don't think that entering directions randomly will do because it must be based on the spot where camera is placed, it only doesn't matter for the 4th camera because it always watches every direction.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — I don't know but setting all directions to the 4th camera excluded the right direction. It's really weird
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — I'm not shure what the intended behaviour is. It would be much easier to let all cameras watch all directions, then we wood need no configuration. The loops would terminate immediately when there is no road in the watch direction.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — setting up to false on the 4th allows the right direction...
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — @Sempervivum#1612702 But it would make no sense of having 4 kinds of cameras
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — It would make the game more challenging, expecially if the user doesn't know which direction the camera watches :-)
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — And as I said setting all directions for all the cameras to true blocks right direction
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — @Sempervivum#1612705 I kinda need to stick up with the task, unfortunately.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — Can I know all available directions once I have a camera above the road but before dropping to configure directions in the searchDirs?
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — `finish = false` was not set for the right direction.

    Additionally we have to consider the 2nd dimension in the map array when limiting:
    finish = false;
    if (directions.right) {
    for (let j2 = j; j2 &lt; themap[i].length &amp;&amp; !finish; j2++) { // &lt;-- limit here
    if (themap[i][j2] == 0) {
    const themaplayer = maplayers[i][j2];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    }
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — It's got to be it
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — > @BenBruce#1612708 Can I know all available directions once I have a camera above the road but before dropping to configure directions in the searchDirs?

    So what do you think of it, is it real?
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — I'm fairly shure that it can be done, however I don't understand what you intend to achieve. Maybe there is some misunderstanding. When you configure all directions the code will know in which direction a road is located and if there is none it will terminate the search immediately and do nothing.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — Okay, maybe I wanted it to be too much sophisticated because I meant if you place a camera №2 on the crossroad with 3 roads it will color all three roads but it intended to watch only 3, that's what I'm talking about
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — It intended to watch opnly 2, I mistyped*
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — But what more important user must have more cameras than 4 so the original cameras must be at the same start point and dragging and dropping only copies of these cameras.
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — @BenBruce#1612715 I see. But why create a copy? I would propose to create a new one and transfer the attributes.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — Okay, but how can it be applied to this code because of I like did it but it was totally different occasion.

    let currentDroppable = null, clone;

    <i> </i><CODE>function moveAt(pageX, pageY, shiftX, shiftY) {
    <i> </i> clone.style.left = pageX - shiftX + 'px';
    <i> </i> clone.style.top = pageY - shiftY + 'px';
    <i> </i>}

    <i> </i>function onMouseMove(event) {
    <i> </i> moveAt(event.pageX, event.pageY, 0, 0);

    <i> </i> clone.hidden = true;
    <i> </i> let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
    <i> </i> clone.hidden = false;

    <i> </i> if (!elemBelow) return;

    <i> </i> let droppableBelow = elemBelow.closest('#canvas');
    <i> </i> if (currentDroppable != droppableBelow) {
    <i> </i> if (currentDroppable) { // null when we were not over a droppable before this event
    <i> </i> leaveDroppable(currentDroppable);
    <i> </i> }
    <i> </i> currentDroppable = droppableBelow;
    <i> </i> if (currentDroppable) { // null if we're not coming over a droppable now
    <i> </i> // (maybe just left the droppable)
    <i> </i> enterDroppable(currentDroppable);
    <i> </i> }
    <i> </i> }
    <i> </i>}

    <i> </i>$(document).on('mousedown', '.drag', function(e){

    <i> </i> var tmp = $(this).clone().removeClass("drag");
    <i> </i> $(tmp).css({position: 'absolute', top: $(this).offset().top + "px", left: $(this).offset().left + "px", opacity: 1, 'z-index': 1000});
    <i> </i> $('body').append($(tmp));

    <i> </i> clone = $(tmp)[0];

    <i> </i> let shiftX = event.clientX - clone.getBoundingClientRect().left;
    <i> </i> let shiftY = event.clientY - clone.getBoundingClientRect().top;

    <i> </i> moveAt(event.pageX, event.pageY, shiftX, shiftY);

    <i> </i> document.addEventListener('mousemove', onMouseMove);

    <i> </i> clone.onmouseup = function() {
    <i> </i> document.removeEventListener('mousemove', onMouseMove);
    <i> </i> clone.onmouseup = null;
    <i> </i> };
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — Unfortunately this code is not applicable to the code we developped before. The event handling of jCanvas is completely different from that of the DOM.

    However I implemented the cloning feature. Cloning is done by a function cloneIt():
    function cloneIt(layer) {
    if (!layer.dragged) {
    layer.dragged = true;
    cv.drawImage({
    dragged: false,
    layer: true,
    source: layer.source,
    x: layer.x,
    y: layer.y,
    width: size,
    height: size,
    fromCenter: false,
    draggable: true,
    dragstart: cloneIt,
    drag: onDrag,
    dragstop: onDragEnd,
    idxDirection: layer.idxDirection
    });
    }
    }
    for (let i = 0; i &lt; 4; i++) {
    const y = i * size;
    console.log(i+1,y, 'https://res.cloudinary.com/benbruce/image/upload/v1578505333/camera' + (i+1) +'.png')
    cv.drawImage({
    layer: true,
    dragged: false,
    source: 'https://res.cloudinary.com/benbruce/image/upload/v1578505333/camera' + (i+1) +'.png',
    x: 559,
    y: y,
    width: size,
    height: size,
    fromCenter: false,
    draggable: true,
    dragstart: cloneIt,
    drag: onDrag,
    dragstop: onDragEnd,
    idxDirection: i
    });
    }

    https://jsfiddle.net/Sempervivum/hx3b1e2s/14/
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — Huge thanks, man, it does work okay 😀
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — I still think about that functionality, for example, if you place a camera №1 on the crossroad with 3 roads it will color all three roads but it intended to do only 1. Do you think it's feasible to make such adjustments?
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — Yes, this would be possible. but how to choose which road is colored?
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — I think it doesn't matter
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — First step done. When a road was found and colored the following searches are not performed. This is controlled by the variable `done`:
    // finish indicates that the search has hit
    // a wall and needs to be terminated
    let finish = false,
    <br/>
    // done indicates that one road was found and colored
    done = false;

    const directions = searchDirs[layer.idxDirection];
    if (directions.down) {
    for (let i2 = i; i2 &lt; themap.length &amp;&amp; !finish; i2++) {
    if (themap[i2][j] == 0) {
    if (i2 &gt; i) done = true;
    const themaplayer = maplayers[i2][j];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    }
    finish = false;
    if (directions.up &amp;&amp; !done) {
    for (let i2 = i; i2 &gt;= 0 &amp;&amp; !finish; i2--) {
    if (themap[i2][j] == 0) {
    if (i2 &lt; i) done = true;
    const themaplayer = maplayers[i2][j];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    }
    if (directions.right &amp;&amp; !done) {
    for (let j2 = j; j2 &lt; themap.length &amp;&amp; !finish; j2++) {
    if (themap[i][j2] == 0) {
    if (j2 &gt; j) done = true;
    const themaplayer = maplayers[i][j2];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    }
    finish = false;
    if (directions.left &amp;&amp; !done) {
    for (let j2 = j; j2 &gt;= 0 &amp;&amp; !finish; j2--) {
    if (themap[i][j2] == 0) {
    if (j2 &lt; j) done = true;
    const themaplayer = maplayers[i][j2];
    themaplayer.fillStyle = 'lightsalmon';
    } else {
    finish = true;
    }
    }
    }
    https://jsfiddle.net/Sempervivum/hx3b1e2s/25/

    Next step: Prevent a road that is already colored from being searched along and colored again.
    Copy linkTweet thisAlerts:
    @SempervivumJan 08.2020 — My local time is 00:30 AM. I'm gonna finish this for today. Good night!
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 08.2020 — Good night, thank you a lot!
    Copy linkTweet thisAlerts:
    @KeverJan 09.2020 — In the instructions it says:The camera can be put only on the field that has the same number of free directions.[/quote].

    So you shouldn't be able to place camera 3 on the map where only 2 free directions are available.

    To keep track of which blocks are covered you could use 0 for walls, 1 for streets and numbers greater than 1 for coverage. That means when a block is covered by 2 cameras, it has value 3. When you then remove a camera it will have value 2, which means it is still covered.

    When you use a boolean for coverage and remove a camera, you would have to go over all cameras again to find out which blocks are covered.
    &lt;canvas style="border: 1px solid red"&gt;&lt;/canvas&gt;
    &lt;script&gt;

    var map = [
    [1, 1, 1, 1, 1, 1, 1, 1, 1],
    [0, 0, 0, 0, 1, 1, 1, 0, 1],
    [1, 0, 1, 1, 1, 1, 1, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0],
    [1, 0, 1, 0, 1, 1, 1, 0, 1],
    [0, 0, 1, 0, 1, 0, 0, 0, 1],
    [1, 1, 1, 0, 1, 0, 1, 1, 1],
    [1, 1, 1, 0, 1, 0, 1, 1, 1],
    [1, 1, 1, 0, 1, 0, 1, 1, 1],
    [1, 1, 1, 0, 0, 1, 1, 1, 1]
    ];


    var coverMap = map.flat().map(v =&gt; v ? 0 : 1);

    function coordToIndex(x, y) {
    return y * map[0].length + x;
    };

    function isValid(directions, x, y) {
    return !map[y][x] &amp;&amp;
    (directions == (coverMap[coordToIndex(x, y-1)] &gt; 0) +
    (coverMap[coordToIndex(x+1, y)] &gt; 0) +
    (coverMap[coordToIndex(x, y+1)] &gt; 0) +
    (coverMap[coordToIndex(x-1, y)] &gt; 0)
    );
    };

    function setCoverage(x, y, dc) {
    function walk(x, y, dx, dy) {
    while(coverMap[coordToIndex(x, y)]) {
    coverMap[coordToIndex(x, y)] += dc;
    x += dx;
    y += dy;
    }
    };
    walk(x, y, 0, -1);
    walk(x, y, 0, 1);
    walk(x, y, -1, 0);
    walk(x, y, 1, 0);
    };

    function addCamera(x, y) {
    setCoverage(x, y, 1);
    };

    function remCamera(x, y) {
    setCoverage(x, y, -1);
    };

    ///////////////////////////////////////////////////////////////////////////////////////////

    var cnv = document.querySelector('canvas');
    var ctx = cnv.getContext('2d');
    var size = 43;

    function drawCity(map) {
    cnv.height = size * map.length;
    cnv.width = size * map[0].length;
    for(let y=0; y&lt;map.length; y++) {
    for(let x=0; x&lt;map[y].length; x++) {
    ctx.fillStyle = ['#000', '#fff', ][coverMap[coordToIndex(x, y)]] || '#fc0';
    ctx.fillRect(x * size, y * size, size, size);
    }
    }
    };

    ///////////////////////////////////////////////////////////////////////////////////////////

    isValid(4, 7, 3) &amp;&amp; addCamera(7, 3);
    isValid(1, 0, 1) &amp;&amp; addCamera(0, 1);
    isValid(0, 0, 0) // false (wall)
    isValid(2, 1, 1) // false (camera 2, 3 open directions)
    isValid(3, 1, 1) &amp;&amp; addCamera(1, 1);
    remCamera(1, 1);
    drawCity(map);

    &lt;/script&gt;
    Copy linkTweet thisAlerts:
    @Thomasd55Jan 10.2020 — > @BenBruce#1612648 I draw those images on canvas but I don't know how to add this drag function to the newly created camera images and I'm not sure that it's possible to implement color road changing logic through comparison coordinates simply because of there're several city maps (maps with roads) and it'd mean to make those coordinate intersection individually for each one

    But you can insert the image and the code after you know.


    Links removed by Site Staff so it doesn't look like you're spamming us. Please don't post them again.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 10.2020 — @Sempervivum#1612724 What do you think of checking whether all streets are being monitored to pop up some message box?
    Copy linkTweet thisAlerts:
    @SempervivumJan 10.2020 — Yes, this would be fairly easy. Try this approach:

    We have an array of map layers containing one layer for each road. Whenever a road is set to be monitored add an attribute to that layer like this:
    if (directions.right &amp;&amp; !done) {
    for (let j2 = j; j2 &lt; themap.length &amp;&amp; !finish; j2++) {
    if (themap[i][j2] == 0) {
    if (j2 &gt; j) done = true;
    const themaplayer = maplayers[i][j2];
    themaplayer.fillStyle = 'lightsalmon';
    themaplayer.isMonitored = true; // &lt;-- here
    } else {
    finish = true;
    }
    }
    }
    (the same procedure for each direction)

    Then loop through that array and check if all streets are being monitored.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 11.2020 — Thank but I did it using checking for white squares in the layers because I'd need to add isMonitored property set to false to each white square
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 11.2020 — @Sempervivum#1612870 I want to load those maps from the server using AJAX
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 12.2020 — @Sempervivum Or maybe it can be done just with Jquery ui?
    Copy linkTweet thisAlerts:
    @SempervivumJan 12.2020 — jQuery is very useful when loading data via Ajax:

    https://api.jquery.com/jQuery.getJSON/
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 12.2020 — But typically it must be a database to take data from it via Ajax, am I right? And we've just got here three arrays.
    Copy linkTweet thisAlerts:
    @SempervivumJan 12.2020 — It's not absolutely necessary to use a database. For your project it would be sufficient to place the code of each map in a text or json file and download it.
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 12.2020 — I think so too
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 12.2020 — I need to make some kind of a window to be loaded before the rest of the game to choose a map from three of these
    Copy linkTweet thisAlerts:
    @BenBruceauthorJan 12.2020 — I figured out how to read those files and load arrays to the game but I don't know how to make this loading screen to choose maps so it waited till the user chooses the map to proceed further.
    Copy linkTweet thisAlerts:
    @thompsonmaxJan 13.2020 — Hello everyone. Interesting thread, thanks for the information.

    [upl-image-preview url=https://www.webdeveloper.com/assets/files/2020-01-13/1578916781-45664-pfe2.pdf]
    ×

    Success!

    Help @BenBruce spread the word by sharing this article on Twitter...

    Tweet This
    Sign in
    Forgot password?
    Sign in with TwitchSign in with GithubCreate Account
    about: ({
    version: 0.1.9 BETA 4.16,
    whats_new: community page,
    up_next: more Davinci•003 tasks,
    coming_soon: events calendar,
    social: @webDeveloperHQ
    });

    legal: ({
    terms: of use,
    privacy: policy
    });
    changelog: (
    version: 0.1.9,
    notes: added community page

    version: 0.1.8,
    notes: added Davinci•003

    version: 0.1.7,
    notes: upvote answers to bounties

    version: 0.1.6,
    notes: article editor refresh
    )...
    recent_tips: (
    tipper: @Yussuf4331,
    tipped: article
    amount: 1000 SATS,

    tipper: @darkwebsites540,
    tipped: article
    amount: 10 SATS,

    tipper: @Samric24,
    tipped: article
    amount: 1000 SATS,
    )...