JavaScript/HTML Canvas: mouse detection for looped row of buttons - javascript

I'm building a simple drum machine that uses canvas for the GUI. I have a row of buttons drawn with a for loop that toggle on/off when clicked.
Here's a sample on JSFiddle
While it works, I'm a bit embarrassed by my buttonToggleDetection function. It's the only solution I could think of to check which button the mouse is over. I'm wondering if anyone can suggest a better way to do this?
var buttonToggleDetection = function(posx, posy, x) {
if (posx < canvas.width/2 && posy > x && posy < x*2) {
if (posx > x*1 && posx < x*2) {
if (pattern[0] === 0) {
pattern[0] = 1;
} else {
pattern[0] = 0;
}
}
else if (posx > x*2 && posx < x*3) {
if (pattern[1] === 0) {
pattern[1] = 1;
} else {
pattern[1] = 0;
}
}
else if (posx > x*3 && posx < x*4) {
if (pattern[2] === 0) {
pattern[2] = 1;
} else {
pattern[2] = 0;
}
}
else if (posx > x*4 && posx < x*5) {
if (pattern[3] === 0) {
pattern[3] = 1;
} else {
pattern[3] = 0;
}
}
else if (posx > x*5 && posx < x*6) {
if (pattern[4] === 0) {
pattern[4] = 1;
} else {
pattern[4] = 0;
}
}
else if (posx > x*6 && posx < x*7) {
if (pattern[5] === 0) {
pattern[5] = 1;
} else {
pattern[5] = 0;
}
}
else if (posx > x*7 && posx < x*8) {
if (pattern[6] === 0) {
pattern[6] = 1;
} else {
pattern[6] = 0;
}
}
else if (posx > x*8 && posx < x*9) {
if (pattern[7] === 0) {
pattern[7] = 1;
} else {
pattern[7] = 0;
}
}
}
if (posx > canvas.width/2 && posy > x && posy < x*2) {
if (posx > x*9 && posx < x*10) {
if (pattern[8] === 0) {
pattern[8] = 1;
} else {
pattern[8] = 0;
}
}
else if (posx > x*10 && posx < x*11) {
if (pattern[9] === 0) {
pattern[9] = 1;
} else {
pattern[9] = 0;
}
}
else if (posx > x*11 && posx < x*12) {
if (pattern[10] === 0) {
pattern[10] = 1;
} else {
pattern[10] = 0;
}
}
else if (posx > x*12 && posx < x*13) {
if (pattern[11] === 0) {
pattern[11] = 1;
} else {
pattern[11] = 0;
}
}
else if (posx > x*13 && posx < x*14) {
if (pattern[12] === 0) {
pattern[12] = 1;
} else {
pattern[12] = 0;
}
}
else if (posx > x*14 && posx < x*15) {
if (pattern[13] === 0) {
pattern[13] = 1;
} else {
pattern[13] = 0;
}
}
else if (posx > x*15 && posx < x*16) {
if (pattern[14] === 0) {
pattern[14] = 1;
} else {
pattern[14] = 0;
}
}
else if (posx > x*16 && posx < x*17) {
if (pattern[15] === 0) {
pattern[15] = 1;
} else {
pattern[15] = 0;
}
}
}
return;
}

Your way of bounds checking the buttons seems fine...but your loop could be tightened up a bit by defining your buttons in an array and then looping through that array.
Here's one way to draw and toggle buttons in html canvas:
Define each of your button's x,y,width,height & pressed-state
var buttons=[];
buttons.push({x:20,y:20,width:50,height:35,text:'One',isPressed:false});
buttons.push({x:80,y:20,width:50,height:35,text:'Two',isPressed:true});
buttons.push({x:140,y:20,width:50,height:35,text:'Three',isPressed:false});
Test if the mouse is over any button:
for(var i=0;i<buttons.length;i++){
var b=buttons[i];
if(mx>b.x && mx<b.x+b.width && my>b.y && my<=b.y+b.height){
b.isPressed=(!b.isPressed);
}
}
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var $canvas=$("#canvas");
var canvasOffset=$canvas.offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var scrollX=$canvas.scrollLeft();
var scrollY=$canvas.scrollTop();
ctx.textAlign='center';
ctx.textBaseline='middle';
ctx.font='14px verdana';
var buttons=[];
buttons.push({x:20,y:20,width:50,height:35,text:'One',isPressed:false});
buttons.push({x:80,y:20,width:50,height:35,text:'Two',isPressed:true});
buttons.push({x:140,y:20,width:50,height:35,text:'Three',isPressed:false});
draw();
function draw(){
var label;
ctx.clearRect(0,0,cw,ch);
for(var i=0;i<buttons.length;i++){
var b=buttons[i];
if(b.isPressed){
ctx.shadowBlur=0;
ctx.shadowOffsetX=0;
ctx.shadowOffsetY=0;
ctx.shadowColor=null;
ctx.fillStyle='powderblue';
label='ON';
}else{
ctx.shadowBlur=2;
ctx.shadowOffsetX=2;
ctx.shadowOffsetY=2;
ctx.shadowColor='black';
ctx.fillStyle='paleturquoise';
label='OFF';
}
ctx.strokeRect(b.x,b.y,b.width,b.height);
ctx.fillRect(b.x,b.y,b.width,b.height);
ctx.shadowBlur=0;
ctx.shadowOffsetX=0;
ctx.shadowOffsetY=0;
ctx.shadowColor=null;
ctx.fillStyle='black';
ctx.fillText(label,b.x+b.width/2,b.y+b.height/2);
ctx.fillStyle='gray';
ctx.fillText(label,b.x+b.width/2+1,b.y+b.height/2+1);
}
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mx=parseInt(e.clientX-offsetX);
my=parseInt(e.clientY-offsetY);
//
for(var i=0;i<buttons.length;i++){
var b=buttons[i];
if(mx>b.x && mx<b.x+b.width && my>b.y && my<=b.y+b.height){
b.isPressed=(!b.isPressed);
}
}
draw();
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Click the buttons</h4>
<canvas id="canvas" width=300 height=300></canvas>

I would recommend using svg instead of canvas. The content of an SVG node is very similar to that of a canvas node, but each shape is built out of XML markup or dynamically created elements, much like document.body's contents. This gives you a few big advantages:
You can apply classes to individual shapes, and use css to apply
colors/styles.
You can take advantage of css's :hover pseudo-style
to apply color to hovered elements.
Most importantly: You can bind events to each shape!
The code below is available to fiddle with at http://jsfiddle.net/3zevLyur/1/
Here's the html:
<svg id="drumMachine"/>
<div id="debugText"/>
Here's the javascript to build the pads and bind events:
var svg = document.getElementById("drumMachine");
var activePad = null;
var debugText = document.getElementById("debugText");
for (var x = 0; x < 16; x++) {
createNewPad(x);
}
function createNewPad(padNumber) {
var r = document.createElementNS("http://www.w3.org/2000/svg", "rect")
r.setAttribute("width", "40");
r.setAttribute("height", "40");
r.setAttribute("x", padNumber * 50);
r.setAttribute("data-pad-number", x);
r.onmouseenter = mouseOver;
r.onmouseleave = mouseOut;
svg.appendChild(r);
}
function mouseOver() {
activePad = this;
debugText.innerHTML = this.getAttribute("data-pad-number");
}
function mouseOut() {
activePad = null;
debugText.innerHTML = "";
}

Related

Background Error fault when applying condition

So if x = Number value background changes fine, my question is when x has no value. How can I have a background-color of #ffffff. Currently, code is defaulting to #db0000.
JS:
var grid_value = document.getElementById("Vone").innerHTML;
var x = grid_value;
if (x >= 0 && x < 15) {
document.getElementById("GridA").style.backgroundColor = "#db0000";
} else if (x >= 15 && x < 25) {
document.getElementById("GridA").style.backgroundColor = "#f0ad4e";
} else if (x >= 25) {
document.getElementById("GridA").style.backgroundColor = "#5cb85c";
} else if (x = "Pick" ) {
document.getElementById("GridA").style.backgroundColor = "#0085eb";
} else if (x = "" ) {
document.getElementById("GridA").style.backgroundColor = "#ffffff";
}
Here you haven't given any conditions if x doesn't have a value, so the default condition is x=0.
You should try else if (!x).
else if (!x) {
document.getElementById("GridA").style.backgroundColor = "#ffffff";
}
Hope this helps you out.
const gA = document.getElementById("GridA");
document.getElementById("Vone").addEventListener("blur", function(event){
gA.classList = "";
var x = this.value;
if (x === "") {
gA.classList.add("white");
} else if (x === "Pick") {
gA.classList.add("color1");
} else if (x >= 0 && x < 15) {
gA.classList.add("color2");
} else if (x >= 15 && x < 25) {
gA.classList.add("color3");
} else if (x >= 25) {
gA.classList.add("color4");
}
});
#GridA { height:100px; border:1px solid grey; margin-top:5px; }
.color1 { background-color:#dbf000; }
.color2 { background-color:#f0ad4e; }
.color3 { background-color:#5cb85c; }
.color4 { background-color:#0085eb; }
.white { background-color:#ffffff; }
<input id="Vone">
<div id="GridA"></div>

How to detect collisions for a complex object in JavaScript

How should I detect collisions for a sprite that can't have a rectangular hitbox? I am trying to make a platformer, and I'm currently working on collision detections for the level's platforms. Here's my physics:
function onTimerTick() {
if (moveY > 300) {
momentumY = momentumY - 2;
move(moveX, moveY);
} else if (moveY < 300) {
moveY = 300;
momentumY = 0;
move(moveX, moveY);
}
moveY = moveY + momentumY;
move(moveX, moveY);
if (moveY <= 300) {
if (momentumX > 0) {
right = true;
momentumX -= 1;
} else if (momentumX < 0) {
if (document.getElementById("spriteNotReal").getAttribute("scr") != "walkLeft.gif") {
document.getElementById("spriteNotReal").src = "walkLeft.gif";
}
right = false;
momentumX += 1;
} else {
if (right == true) {
document.getElementById("spriteNotReal").src = "amaincharacter.png";
} else {
document.getElementById("spriteNotReal").src = "maincharacterleft.png";
}
}
moveX = moveX + momentumX;
} else {
moveX += momentumX / 3 + 1;
document.getElementById("spriteNotReal").src = "jumpmain.gif";
}
document.getElementById("gold").innerHTML = momentumX;
}
I'm thinking of trying to break up the level into different rectangles, all in the same class, and then test for collisions within that class. Is there any way I could do this? Also, I think I should use boundingClientRect, but I don't know how to use this for collisions.

js game freezes with no error

I am working on a javascript game that involves building, destroying, and survival.
It has been working fine but after adding trees the game would randomly freeze after breaking blocks.
The code is here:
for (var bl in blocks) {
if (mouse.x >= blocks[bl].x-camera.x && mouse.y >= camera.y+blocks[bl].y && mouse.x <= blocks[bl].x-camera.x+64 && mouse.y <= camera.y+blocks[bl].y+64) {
document.body.style.cursor = "pointer";
if (mouse.down) {
if (!blocks[bl].d && blocks[bl].d !== 0) {
blocks[bl].d = 32;
} else if (blocks[bl].d > 0) {
blocks[bl].d -= 0.5;
if (tools[player.tool].n === 'axe') {
blocks[bl].d -= 1;
}
} else {
var fb = false;
for (var i in inventory) {
if (inventory[i].n === blocks[bl].n) {
inventory[i].a ++;
fb = true;
}
}
if (!fb) {
inventory.push({n: blocks[bl].n, a: 1});
}
blocks.splice(bl, 1);
}
}
}
}
I don't see any way there could be an infinite loop and no errors show up when it happens.
EDIT
I changed the code to
var spliceblock = {bl: 0, s: false};
for (var bl in blocks) {
if (mouse.x >= blocks[bl].x-camera.x && mouse.y >= camera.y+blocks[bl].y && mouse.x <= blocks[bl].x-camera.x+64 && mouse.y <= camera.y+blocks[bl].y+64) {
document.body.style.cursor = "pointer";
if (mouse.down) {
if (!blocks[bl].d && blocks[bl].d !== 0) {
blocks[bl].d = 32;
} else if (blocks[bl].d > 0) {
blocks[bl].d -= 0.5;
if (tools[player.tool].n === 'axe') {
blocks[bl].d -= 1;
}
} else {
var fb = false;
for (var i in inventory) {
if (inventory[i].n === blocks[bl].n) {
inventory[i].a ++;
fb = true;
}
}
if (!fb) {
inventory.push({n: blocks[bl].n, a: 1});
}
spliceblock.s = true;
spliceblock.bl = bl;
//blocks.splice(bl, 1);
}
}
}
}
if (spliceblock.s) {
blocks.splice(spliceblock.bl, 1);
}
but it still freezes randomly when trying to break blocks.
Modifying an array (using splice) while you're iterating through it is bound to cause problems. If you remove the block bl from the array and then continue to run through it, the counter will probably be off.
Instead, store the index of the block you're removing, then remove it after you're done looping through the blocks.

Generating a random grid of connected paths

I am trying to generate a random grid of connected paths. The image below shows how far I've managed to get it:
The rules that the grid needs to adhere to are the following:
all the paths must connect to each other in some way but there can be dead ends
there can be no blocks that have no connections (i.e. can't be reached)
the blocks on the edge must have paths that point inward, not outward.
As you can see in my image, my code isn't quite right, but I can't find the error.
Here is a fiddle with the code: jsfiddle.net/thatOneGuy/jz5sfr00/1
But I think my mistake is in this function:
function linkAll() {
for (var x = 0; x < 9; x++) {
for (y = 0; y < 9; y++) {
//link up to each other
var count = 0;
if ((x > 0) && (y > 0)) {
if (hz[x - 1][y].right) {
hz[x][y].left = 1;
} else count++;
if (hz[x][y - 1].bottom) {
hz[x][y].top = 1;
} else count++;
}
if ((x < 9) && (y < 9)) {
if (hz[x + 1][y].left) {
hz[x][y].right = 1;
} else count++;
if (hz[x][y + 1].top) {
hz[x][y].bottom = 1;
} else count++;
}
if (count == 4) {
var newPath = getDirection(getRandomInt(0, 3));
if (newPath == 'top') {
hz[x][y - 1].bottom = 1
hz[x][y].top = 1;
} else if (newPath == 'left') {
hz[x - 1][y].right = 1;
hz[x][y].left = 1;
} else if (newPath == 'bottom') {
hz[x][y + 1].top = 1;
hz[x][y].bottom = 1;
} else if (newPath == 'right') {
hz[x + 1][y].left = 1;
hz[x][y].right = 1;
}
}
} //end for (y)
} //end for (x)
}
UPDATE:
I realised that I confused the x and y values of the array. They should have been swopped.
So I added a linkEdges() function so that the edge blocks all link together:
function linkEdges(){
for(var x = 0; x < 10; x++){
for(var y = 0; y < 10; y++){
if((x==0) && (y > 0) && (y < 9)){
if(hz[0][y-1].right){
//console.log(x + ' ' + y + ' y-1 = 1');
//console.log('Inside (y-1) ');
//console.log(hz[x][y]);
if (hz[0][y].left != null)
{
hz[0][y].left = 1;
}
}
}
if ((y==0) && (x > 0)){
if(hz[x-1][0].bottom){
if(hz[x][0].top != null)
hz[x][0].top = 1;
}
}
if ((y==9) && (x < 9) && (x > 0)){
if(hz[x+1][9].top){
if(hz[x][9].bottom != null)
hz[x][9].bottom = 1;
}
}
if ((x==9) && (y < 9)){
if(hz[9][y+1].left){
if(hz[9][y].right != null)
hz[9][y].right = 1;
}
}
} //end for(y)
}//end for(x)
}
I also updated the linkAll() function:
function linkAll(){
for(var x=0; x < 9; x++){
for(var y=0; y < 9; y++){
//link up to each other
var count = 0;
if((x > 0) && (y > 0)){
if(hz[x-1][y].bottom){
hz[x][y].top = 1;
}
else count++;
if(hz[x][y-1].right){
hz[x][y].left = 1;
}
else count++;
}
if((x < 9) && (y < 9)){
if(hz[x+1][y].top){
hz[x][y].bottom = 1;
}
else count++;
if(hz[x][y+1].left){
hz[x][y].right = 1;
}
else count++;
}
if(count == 4){
//console.log('x: ' + x + ' y: '+y);
var newPath = getDirection(getRandomInt(0, 3));
//console.log(newPath);
if(newPath == 'top'){
hz[x][y-1].right = 1
hz[x][y].left = 1;
}
else if(newPath == 'left'){
hz[x-1][y].bottom = 1;
hz[x][y].top = 1;
}
else if(newPath == 'bottom'){
hz[x][y+1].left = 1;
hz[x][y].right = 1;
}
else if(newPath == 'right'){
hz[x+1][y].top = 1;
hz[x][y].bottom = 1;
}
}
}//end for (y)
}//end for (x)
}
My grid now looks like this:
I just don't know how to connect those last few edges. I think it has something to do with my linkEdges() function.

javascript array problem

i have written a snake program using javascript..
the problem is that the snake does not grow more than 2 blocks size....
<html>
<head>
<script type="text/javascript">
var matrix, body, dir, key, lastx, lasty, start, applex, appley, eat, hal;
function node(x, y) {
this.x = x;
this.y = y;
}
function draw() {
var str;
for (var i = 0; i < body.length; i++) {
matrix[body[i].x * 50 + body[i].y].bgColor = "black";
}
}
function halt() {
hal = 1 - hal;
if (hal == 0) automove();
}
function check_status() {
if (start == 1 && hal == 0) {
var ch;
if (eat == 1) {
do {
ch = 0;
applex = Math.round(49 * Math.random());
appley = Math.round(49 * Math.random());
for (var i = 0; i < body.length; i++)
if (body[i].x == applex && body[i].x == appley) ch = 1;
} while (ch == 1);
matrix[applex * 50 + appley].bgColor = "blue";
eat = 0;
}
lastx = body[body.length - 1].x;
lasty = body[body.length - 1].y;
for (var i = 1; i < body.length; i++) {
body[i].x = body[i - 1].x;
body[i].y = body[i - 1].y;
}
if (dir == 1)--body[0].x;
else if (dir == -1)++body[0].x;
else if (dir == 2)--body[0].y;
else if (dir == -2)++body[0].y;
if (body[0].x == -1 || body[0].x == 50 || body[0].y == 50 || body[0].y == -1) {
alert("GAME OVER!!");
start = 0;
}
for (var i = 1; i < body.length; i++) {
if (body[0].x == body[i].x && body[0].y == body[i].y) {
alert("GAME OVER!!");
start = 0;
i = 10000;
}
}
if (body[0].x == applex && appley == body[0].y) {
eat = 1;
body[body.length] = new node(lastx, lasty);
}
matrix[lastx * 50 + lasty].bgColor = "white";
draw();
}
}
function automove() {
if (start == 1 && hal == 0) {
if (key != -dir) dir = key;
check_status();
window.setTimeout("automove()", 200);
}
}
function init() {
start = 1;
var x = document.getElementById("mine");
var str = "<table id='tab' align='center' height='500px' cellSpacing='0' cellPadding='0' width='500px' border='4' >";
for (var i = 0; i < 50; i++) {
str += "<tr>";
for (var j = 0; j < 50; j++)
str += "<td></td>";
str += "</tr>";
}
str += "</table>";
x.innerHTML = str;
matrix = document.getElementsByTagName("td");
body = new Array();
body[0] = new node(0, 0);
draw();
dir = key = -1;
eat = 1;
v = 0;
hal = 0;
automove();
}
function keypress(e) {
if ((e.keyCode == 38) || ((e.which) && (e.which == 38))) //up
key = 1;
else if ((e.keyCode == 40) || ((e.which) && (e.which == 40))) //down
key = -1;
else if ((e.keyCode == 37) || ((e.which) && (e.which == 37))) //left
key = 2;
else if ((e.keyCode == 39) || ((e.which) && (e.which == 39))) //right
key = -2;
check_status();
}
</script>
</head>
<body onkeydown=keypress(event)>
<br/>
<input type="button" onClick="init()" value="play">
<input type="button" onClick="halt()" value="pause">
<p id="mine"></p>
<br><h5 id="score"></h5>
</body>
</html>
The problem is here:
lastx=body[body.length-1].x;
lasty=body[body.length-1].y;
for(var i=1;i<body.length;i++)
{
body[i].x=body[i-1].x;
body[i].y=body[i-1].y;
}
In that loop, body[1] is assigned to body[0], then body [2] is assigned to body[1] etc. This means that everything from index 1 to the end will be set equal to body[0], then body[0] is altered based on direction - so there are only two positions.
Look into the javascript unshift method.
You could replace that loop with:
body.unshift(body[0]);

Categories

Resources