Canvas: Drag and Drop Images - javascript

so I am relatively new to javascript. As a school project, we have to code a game with HTML, CSS, and Javascript.
So I want to make a cheap recreate of the game Sort or 'Splode' from New Super Mario Bros Ds.
I already programmed the random spawning of the bombs and animated the movement of the bombs. If they are too long in the middle sector (where they spawn) the explode.
But I didn't program the explosion yet. I want to program the drag and drop first. I just want to drag and drop a bomb. As simple as that, the rest I could do myself I guess.
I searched for a tutorial and found this webpage: https://html5.litten.com/how-to-drag-and-drop-on-an-html5-canvas/
I sort of recreated this in my game, but it is not working...
Sorry for my bad code. Could someone please explain to me how to do that?
Best,
Sebastian
enter image description here
<!DOCTYPE html>
<html>
<head>
<title>BOMB-Sorter</title>
<style>
canvas {
background-color:#fff;
border:7px solid #000;
border-radius: 5px;
display: inline;
background-image: url("img/background.jpg");
background-size: cover;
}
body{
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
class GraphObj {
constructor(x, y, w, h, c, x_vel, y_vel, t, d) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
this.x_vel = x_vel;
this.y_vel = y_vel;
this.t = t;
this.d = d;
}
info() {
return "\ngraphical object: x="+this.x + ", y="+this.y+", w="+this.w+", h="+this.h+", h="+this.c;
}
}
// Declaration of Lists
var bList = null; // List of Bombs
var xlist = null;
var ylist = null;
var ref_time=0; // Reference Time
var lives = 10;
var score = 0;
var dragok = false;
var c = document.getElementById("myCanvas");
function drawGrid(ele,ctx){ // declaration of a new function
ctx.globalAlpha = 0.5;
ctx.fillStyle = "#ff9999";
ctx.fillRect(0,0,ele.width/3,ele.height)
ctx.fillStyle = "#60615a";
ctx.fillRect(2* (ele.width/3),0,ele.width/3,ele.height)
ctx.globalAlpha = 1;
ctx.setLineDash([25, 15]);
ctx.moveTo(ele.width/3 +1,0);
ctx.lineTo(ele.width/3 +1, ele.height);
ctx.moveTo(2*(ele.width/3 -1),0);
ctx.lineTo(2*(ele.width/3 - 1), ele.height);
ctx.lineWidth = 10;
ctx.strokeStyle = "#f5f242";
ctx.stroke();
}
function drawAll()
{
var ele = document.getElementById("myCanvas");
var ctx = ele.getContext("2d");
ele.width = ele.width; // clear everything!
drawGrid(ele,ctx); // call the function ...
var bombb = document.getElementById("bombb");
var bombr = document.getElementById("bombr");
var bombr_step = document.getElementById("bombr_step");
var bombb_step = document.getElementById("bombb_step");
var bombw = document.getElementById("bombw");
// draw bombs
for (var i=0; i<bList.length; i++)
{
var o = bList[i];
if(o.c == 0 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombr, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.c == 1 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombb, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.c == 2 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombr_step, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.c == 3 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombb_step, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.t == 10 || o.t == 8){
ctx.drawImage(bombw, o.x,o.y,o.w,o.h);
draggable: true;
}
}
// draw lives
ctx.font = "normal small-caps 70px Minecraft";
ctx.fillStyle = "red";
ctx.fillText(+lives, ele.width - 105, 70);
// draw score
ctx.font = "normal small-caps 70px Minecraft";
ctx.fillStyle = "black";
ctx.fillText(+score, 20, 70);
}
function positionCheck(i, ele){
if(bList[i].x < ele.width/3){
bList[i].x_vel = bList[i].x_vel * (-1);
}
else if(bList[i].x > 2*(ele.width/3) -64){
bList[i].x_vel = bList[i].x_vel * (-1);
}
else if(bList[i].y < 0){
bList[i].y_vel = bList[i].y_vel * (-1);
}
else if(bList[i].y > ele.height -64){
bList[i].y_vel = bList[i].y_vel * (-1);
}
}
function animateStep(){
for(var i = 0;i < bList.length; i++){
if(bList[i].c == 0){
bList[i].c = 2;
bList[i].t++;
}
else if(bList[i].c == 1){
bList[i].c = 3;
bList[i].t++;
}
else if(bList[i].c == 2){
bList[i].c = 0;
}
else if(bList[i].c == 3){
bList[i].c = 1;
}
else if(bList[i].t == 8){
bList[i].c = 4;
}
else if(bList[i].t == 10){
bList[i].c = 4;
}
}
}
function functimer(){
var ele = document.getElementById("myCanvas");
// create new bomb every s
if (Date.now() - ref_time >= 1000)
{
// random number between 0 and ele.width
var pos_x = Math.floor(Math.random() * (ele.width/3 - 64)) + (ele.width/3 +1);
//random height
var pos_y = Math.floor(Math.random() * (ele.height - 70) -1);
//random color
var color = Math.round(Math.random());
//random x velocity
var x_vel = Math.ceil(Math.random() * 2) * (Math.round(Math.random()) ? 1 : -1);
//random y velocity
var y_vel = Math.ceil(Math.random() * 2) * (Math.round(Math.random()) ? 1 : -1);
var t = 0;
var o = new GraphObj(pos_x,pos_y,64,64, color, x_vel, y_vel, t); // create new bomb
bList.push(o);
animateStep();
ref_time = Date.now();
}
var xlist = null;
xlist = new Array();
var ylist = null;
ylist = new Array();
// move bombs
for (var i=0; i<bList.length; i++)
{
bList[i].y += bList[i].y_vel;
bList[i].x += bList[i].x_vel;
positionCheck(i, ele);
}
drawAll();
requestAnimationFrame(functimer); // draw everything BEFORE screen update
}
function myMove(i,e){
if(dragok == true){
bList[i].x = e.pageX - c.offsetLeft;
bList[i].y = e.pageY - c.offsetTop;
}
}
function myDown(e){
for(var i = 0; i < bList.lenght; i++){
if(e.pageX < bList[i].x + 64 + c.offsetLeft && e.pageX > bList[i].x + c.offsetLeft && e.pageY < bList[i].y + 64 + c.offsetTop && e.pageY > bList[i].y + c.offsetTop){
bList[i].x = e.pageX - c.offsetLeft;
bList[i].y = e.pageY - c.offsetTop;
bList[i].x_vele = 0;
bList[i].y_vel = 0;
dragok = true;
c.onmousemove = myMove(i,e);
}
}
}
function myUp(){
dragok = false;
}
function start() {
// create lists
bList = new Array();
var c = document.getElementById("myCanvas");
c.setAttribute('tabindex','0');
c.focus();
c.onmousedown = myDown;
c.onmouseup = myUp;
var xlist = null;
xlist = new Array();
var ylist = null;
ylist = new Array();
drawAll();
requestAnimationFrame(functimer); // draw everything BEFORE screen update
}
</script>
</head>
<body onload="start()">
<img id="bombb" src="img/bombb.png" alt="x" style="display: none;">
<img id="bombr" src="img/bombr.png" alt="y" style="display: none;">
<img id="bombr_step" src="img/bombr_step1.png" alt="y" style="display: none;">
<img id="bombb_step" src="img/bombb_step.png" alt="y" style="display: none;">
<img id="bombw" src="img/bombw.png" alt="y" style="display: none;">
<div>
<canvas id="myCanvas" width="1000" height="600"></canvas>
</div>
</body>
</html>

This is not a total answer because I do not know exactly what is required when dragging a bomb, but here are a few places to make changes to help further debugging:
the canvas element 'c' is declared in two places so when it comes to be used on mousedown etc it is undefined, the one with more local scope having been set up in the start function. I suggest removing the 'var' before the use of c in the start function so you pick up the one that has global scope.
In general, go through the code seeing where things are defined and if that is in the best position - look at the scope of all variables. The code in this respect is almost OK, for example you understand you cant draw an img on a canvas until it is downloaded, but a few things could do with tidying up.
There is a misspelling of 'length' (lenght) which means a function won't work.
In general use your browser's dev tools console to ensure you do not have anything undeclared or looking like null etc. Such Javascript errors will show there. Also use console.log at strategic points to make sure you understand the flow of the logic. Starting with console.log('at function xxx') at the beginning of the main functions might be useful. You can then see the order in which mousedown/up might happen on a bomb for example.
If you could describe further what should happen when a bomb is dragged that would be useful. Just for completeness here I reiterate what I said in a comment that the bombs are going to be treated like rectangles - so obscuring bombs behind them from the point of view of dragging - that may or may not matter to your application. There is a way round it if it is important, but it's probably better to get the basics sorted out first.

Related

Can anybody explain why I'm getting a typeError that states "this.draw is not a function at ballFunction.move [duplicate]

This question already has answers here:
JavaScript setInterval and `this` solution
(9 answers)
Closed 3 years ago.
What Im trying to do is animate circles bouncing off the canvas borders. I can achieve that pretty will with just one ball. But my Home work asks of me to add a new ball every two seconds. Originally I tried this by making new objects listed in an array using a for loop, but had trouble coming up with a way to call "move" method out of each newly created object in an interval. At the end I settled with creating the variable j and setting it to 0 and having it increment by 10 since the interval will execute the function "startAnim" every 10 milliseconds. Then every two seconds have a new object created and then call that objects "move" method.
<!DOCTYPE html>
<html>
<head>
<title>Some document</title>
<body>
<h1></h1>
<canvas id="canvas" width = "400" height = "400"></canvas>
<script src= "https://code.jquery.com/jquery-2.1.0.js"></script>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var arrayOfCollor = ["green", "red", "blue","gold","black","purple"];
var ballFunction = function(){
this.x = 200;
this.y = 200;
this.xSpeed = 2;
this.ySpeed = -3;
this.radius = 10;
ctx.fillStyle = arrayOfCollor[Math.floor(Math.random() *
arrayOfCollor.length)];
}
ballFunction.prototype.draw = function(){
ctx.beginPath();
ctx.arc(this.x,this.y, this.radius, 0, Math.PI * 2, false);
ctx.fill();
}
ballFunction.prototype.move = function(){
ctx.clearRect(0,0,400,400);
ctx.strokeRect(0,0,400,400);
this.x += this.xSpeed;
this.y += this.ySpeed;
this.draw();
if(this.x > 400-this.radius|| this.x < 0 + this.radius){
this.xSpeed = -1* this.xSpeed;
}
if(this.y > 400 - this.radius || this.y < 0 + this.radius){
this.ySpeed = -1* this.ySpeed;
}
}
var j = 0;
function startAnim(){
j+= 10;
if( j === 2000){
var ballOne = new ballFunction
var ballOneInterval = setInterval(ballOne.move,10)
}
if( j === 4000){
var ballTwo = new ballFunction
var ballTwoInterval = setInterval(ballTwo.move,10)
}
if( j === 6000){
var ballThree = new ballFunction
var ballThreeInterval = setInterval(ballThree.move,10)
}
if( j === 8000){
var ballFour = new ballFunction
var ballFourInterval = setInterval(ballFour.move,10)
}
if( j === 10000){
var ballFive = new ballFunction
var ballFiveInterval = setInterval(ballFive.move,10)
}
if( j === 12000){
var ballSix = new ballFunction
var ballSixinterval = setInterval(ballSix.move,10)
}
}
var startAnimInt = setInterval(startAnim,10);
</script>
</body>
</html>
I keep receiving that the draw function is not a function at ballFunction.move even though I inserted it in.
This is one of several common problems people run into with the this reference in JS. What this actually refers to is determined at runtime, and depends on how the function is called - not on how/where it was defined. The problem you have here is that you are using setInterval:
setInterval(ballOne.move,10)
which of course results in the JS engine calling ballOne.move itself, once every 10 milliseconds. But unfortunately, the way the engine will call it is the same as it will any old "plain function" it is passed - it has no way of knowing that the function it is calling is supposed to "belong" to the ballOne object, and therefore its this reference won't refer to ballOne, but instead to the global (window) object.
You can fix this by using the bind method of JS functions, to create a new version which will always have the specified this reference. That is, you can replace the above line with:
setInterval(ballOne.move.bind(ballOne),10)
and similarly for the other setInterval calls. This will of course quickly get repetitious, so it's probably better to fix this in the constructor, by adding the line:
this.move = this.move.bind(this);
Note that, although your question has nothing to do with React, this is what ReactJS recommends to do in Component classes, for exactly the same reason - to make sure the this reference is always the object intended.
The problem is that you pass the ballFunction method to setInterval as the first parameter and hence its context is lost and the this inside the function will be the window object. You can pass a function calling your move:
<!DOCTYPE html>
<html>
<head>
<title>Some document</title>
<body>
<h1></h1>
<canvas id="canvas" width = "400" height = "400"></canvas>
<script src= "https://code.jquery.com/jquery-2.1.0.js"></script>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var arrayOfCollor = ["green", "red", "blue","gold","black","purple"];
var ballFunction = function(){
this.x = 200;
this.y = 200;
this.xSpeed = 2;
this.ySpeed = -3;
this.radius = 10;
ctx.fillStyle = arrayOfCollor[Math.floor(Math.random() *
arrayOfCollor.length)];
}
ballFunction.prototype.draw = function(){
ctx.beginPath();
ctx.arc(this.x,this.y, this.radius, 0, Math.PI * 2, false);
ctx.fill();
}
ballFunction.prototype.move = function(){
ctx.clearRect(0,0,400,400);
ctx.strokeRect(0,0,400,400);
this.x += this.xSpeed;
this.y += this.ySpeed;
this.draw();
if(this.x > 400-this.radius|| this.x < 0 + this.radius){
this.xSpeed = -1* this.xSpeed;
}
if(this.y > 400 - this.radius || this.y < 0 + this.radius){
this.ySpeed = -1* this.ySpeed;
}
}
var j = 0;
function startAnim(){
j+= 10;
if( j === 2000){
var ballOne = new ballFunction
var ballOneInterval = setInterval(function() {ballOne.move();},10)
}
if( j === 4000){
var ballTwo = new ballFunction
var ballTwoInterval = setInterval(function() {ballTwo.move();},10)
}
if( j === 6000){
var ballThree = new ballFunction
var ballThreeInterval = setInterval(function() {ballThree.move();},10)
}
if( j === 8000){
var ballFour = new ballFunction
var ballFourInterval = setInterval(function() {ballFour.move();},10)
}
if( j === 10000){
var ballFive = new ballFunction
var ballFiveInterval = setInterval(function() {ballFive.move();},10)
}
if( j === 12000){
var ballSix = new ballFunction
var ballSixinterval = setInterval(function() {ballSix.move();},10)
}
}
var startAnimInt = setInterval(startAnim,10);
</script>
</body>
</html>
The this inside the move function isn't referencing to the ballFunction object, but to the window.
Use something like:
var ballOne = new ballFunction
var ballOneInterval = setInterval(ballOne.move.bind(ballOne),10)
Instead of:
var ballOne = new ballFunction
var ballOneInterval = setInterval(ballOne.move,10)
for all balls.

How to I prevent the console from cheating in an HTML5 Game?

I have designed an HTML5 Game with a square that shoots at other squares. You have a certain amount of lives and gain a score. How do I prevent users from going into the console and doing something like this:
score=5000
planetHealth=200
Code to Game
$(document).ready(function() {
initStars(600);
});
var FPS = 60;
width = 300;
height = 400;
var gBackground = document.getElementById("canvas_background").getContext("2d");
var gPlayer = document.getElementById("canvas_player").getContext("2d");
var gEnemies = document.getElementById("canvas_enemies").getContext("2d");
var GUI = document.getElementById("canvas_ui").getContext("2d");
var bullets = [];
var enemies = [];
var shootTimer = 0;
var maxShootTimer = 15;
var score = 0;
var planetHealth = 50;
var gameState = "menu";
var Key = {
up: false,
down: false,
left: false,
right: false
};
var player = {
width: 16,
height: 16,
x: (width / 2) - 8,
speed: 3,
y: height - 20,
canShoot: true,
render: function() {
gPlayer.fillStyle="#24430A";
gPlayer.fillRect(this.x,this.y,this.width,this.height);
},
tick: function() {
if(Key.left && this.x > 0) this.x -= this.speed;
if(Key.right && this.x < width - 20) this.x += this.speed;
if(Key.space && this.canShoot) {
this.canShoot = false;
bullets.push(new Bullet(this.x,this.y - 4));
bullets.push(new Bullet(this.x + this.width,this.y - 4));
shootTimer = maxShootTimer;
}
}
};
stars = [];
addEventListener("keydown", function(e) {
var keyCode = (e.keyCode) ? e.keyCode : e.which;
switch(keyCode) {
case 38: // up
Key.up = true;
break;
case 40: // down
Key.down = true;
break;
case 37: // left
Key.left = true;
break;
case 39: // right
Key.right = true;
break;
case 32: //spacebar
Key.space = true;
break;
}
}, false);
addEventListener("keyup", function(e) {
var keyCode = (e.keyCode) ? e.keyCode : e.which;
switch(keyCode) {
case 38: // up
Key.up = false;
break;
case 40: // down
Key.down = false;
break;
case 37: // left
Key.left = false;
break;
case 39: // right
Key.right = false;
break;
case 32: //spacebar
Key.space = false;
break;
}
}, false);
function collision(obj1,obj2) {
return (
obj1.x < obj2.x+obj2.width &&
obj1.x + obj1.width > obj2.x &&
obj1.y < obj2.y+obj2.height &&
obj1.y + obj1.height > obj2.y
);
}
function Star(x,y) {
this.x = x;
this.y = y;
this.size = Math.random() * 2.5;
this.render = function() {
gBackground.fillStyle = "white";
gBackground.fillRect(this.x,this.y,this.size,this.size)
};
this.tick = function() {
this.y++;
}
}
function createStars(amount) {
for(i=0;i<amount;i ++) {
stars.push(new Star(Math.random() * width, -5));
}
}
function initStars(amount) {
for(i=0;i<amount;i++) {
stars.push(new Star(Math.random()*width,Math.random()*height));
}
}
function Bullet(x,y) {
this.x = x;
this.y = y;
this.width = 2;
this.height = 12;
this.speed = 3;
this.render = function() {
gPlayer.fillStyle = "red";
gPlayer.fillRect(this.x,this.y,this.width,this.height);
};
this.tick = function() {
if(this.y < -this.height) {
var index = bullets.indexOf(this);
bullets.splice(index,1);
}
this.y-=this.speed;
for(i in enemies) {
if(collision(this,enemies[i])) {
score = score + 50;
GUI.clearRect(0,0,width,height);
GUI.fillStyle ="white";
GUI.textBaseline = "top";
GUI.font = "bold 14px Tahoma";
GUI.fillText("Score: " + score, 2,2);
GUI.fillText("Lives: " + planetHealth, 2,16);
var enemyIndex = enemies.indexOf(enemies[i]);
enemies.splice(enemyIndex,1);
var bulletIndex = bullets.indexOf(this);
bullets.splice(bulletIndex,1);
}
}
};
}
function Enemy(x,y) {
this.x = x;
this.y = y;
this.width = 16;
this.height = 16;
this.speed = 0.5;
;
this.render = function() {
gEnemies.fillStyle = "red";
gEnemies.fillRect(this.x,this.y,this.width,this.height);
};
this.tick = function() {
if(this.y > this.height + height) {
this.y = -this.height;
planetHealth--;
GUI.clearRect(0,0,width,height);
GUI.fillStyle ="white";
GUI.textBaseline = "top";
GUI.font = "bold 14px Tahoma";
GUI.fillText("Score: " + score, 2,2);
GUI.fillText("Lives: " + planetHealth, 2,16);
}
this.y += this.speed;
}
}
for(x=0;x<8;x++) {
for(y=0;y<8;y++) {
enemies.push(new Enemy((x*24)+(width/2)-100,y*24));
}
}
function render() {
if(gameState == "play") {
gBackground.clearRect(0,0,width,height);
gPlayer.clearRect(0,0,width,height);
gEnemies.clearRect(0,0,width,height);
player.render();
for(i in stars) {
stars[i].render();
}
for(i in enemies) enemies[i].render();
for(i in bullets) bullets[i].render();
} else if(gameState == "gameOver") {
gBackground.clearRect(0,0,width,height);
for(i in stars) {
stars[i].render();
}
GUI.fillStyle = "white";
GUI.font = "bold 24px Tahoma";
GUI.fillText("You're a loser!", width / 2 - 100, height/2);
gEnemies.clearRect(0,0,width,height);
gPlayer.clearRect(0,0,width,height);
} else if(gameState == "gameWin") {
gBackground.clearRect(0,0,width,height);
for(i in stars) {
stars[i].render();
}
GUI.fillStyle = "white";
GUI.font = "bold 24px Tahoma";
GUI.fillText("You're a winner!", width / 2 - 100, height/2);
gEnemies.clearRect(0,0,width,height);
gPlayer.clearRect(0,0,width,height);
} else if(gameState == "menu") {
gBackground.clearRect(0,0,width,height);
for(i in stars) {
stars[i].render();
}
GUI.fillStyle = "white";
GUI.font = "bold 24px Tahoma";
GUI.fillText("Space Game!", width / 2 - 100, height/2);
GUI.font= "normal 16px Tahoma";
GUI.fillText("Press space to start", width / 2 - 90, (height/2)+28);
}
}
if(gameState == "play") {
GUI.fillStyle ="white";
GUI.textBaseline = "top";
GUI.font = "bold 14px Tahoma";
GUI.fillText("Score: " + score, 2,2);
GUI.fillText("Lives: " + planetHealth, 2,16);
}
function tick() {
createStars(1);
for(i in stars) stars[i].tick();
if(gameState == "play") {
if(planetHealth <= 0) gameState = "gameOver";
if(enemies.length <= 0) gameState = "gameWin";
player.tick();
for(i in enemies) enemies[i].tick();
for(i in bullets) bullets[i].tick();
if(shootTimer <= 0) player.canShoot = true;
shootTimer--;
} else if(gameState == "menu") {
if(Key.space) {
gameState = "play";
GUI.clearRect(0,0,width,height);
}
}
}
setInterval(function() {
render();
tick();
}, 1000/FPS );
<!DOCTYPE html>
<html>
<head>
<title> Game </title>
<style>
canvas {
position: absolute;
top: 0;
left: 0;
}
#canvas_background {
background: black;
}
</style>
</head>
<body>
<canvas id='canvas_background' width='300' height='400'></canvas>
<canvas id='canvas_player' width='300' height='400'></canvas>
<canvas id='canvas_enemies' width='300' height='400'></canvas>
<canvas id='canvas_ui' width='300' height='400'></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
<script src='game.js'></script>
</body>
</html>
You can't prevent a user from tampering with the console and the browser's dev tools gives you a lot of ways to take a peek anywhere in the code, even in closures as well as minified code.
But... you could make it harder.
First, you could do it like Facebook and just print a big red warning in the console saying "You shouldn't be here". We're essentially just scaring off the user, really.
Another option is to wrap the code in a closure, that way it's not exposed in the global scope. This avoids direct manipulation via the console.
;(function(){
// all code in here
());
Making it a bit harder is to use a minifier and an obfuscator.
The main purpose of a minifier is to shave file size by renaming names and rewriting code in a shorter way. The side effect is that the code becomes hard to read as most of the time it won't have any resemblance to your original code. It's worse without a source map and may take hours to trace and understand.
An obfuscator rewrites your code in a way that it still runs the same, just written in a different and often non-readable way. They even go as far as encoding the rewritten code in base64. For those who don't know what base64 is, they're good as gone.
Again, we're just making your code a bit harder to reach, fending off wannabe "hackers".
A more fool-proof way would be to just validate off-page, like on the server and use a variety of methods to determine tampered data. Games like speed typing impose a max score at a certain length of time since we all know we can't type a million words a second. Some games involve data analysis, if the data looks out of the ordinary.
You don't. If your game is entirely client-side, then you can't really stop players from cheating. You could make it more difficult with code obfuscation or taking variables out of the global scope, but that won't stop people who really want to cheat.
If players are connecting to a server for multiplayer or whatever you could implement server-side checks, since the users won't be able to touch that code.
You're running your code on the main scope of the Javascript, which is window.
When you create a global varable, this variable is scoped in the window object.
For that reason you can do this:
var a = 1;
console.log(a); // 1
console.log(window.a); // 1
It's very easy to avoid this, using the famous IIFE, which stands for Immediately Invoked Function Expression. You can read it at MDN.
Is just do this:
(function() {
// put all your code inside here
})();
When you do this, all the variable decladed inside that function, will be contained to the scope of that function.
But be aware, you can't prevent user cheating the game, you can only make it harder.

How can I treat different canvas-drawing as one , and how can I trace the sequence among them?

I have a scenario in which I am having a canvas, I am dragging some drawing from the left side of the canvas and dropping it on the right side of it and if I am dropping one box near to another one it also clubbing together. Now I need to do two more things -
First, as soon as I am clubbing one with another I want to give this set, of more than one boxes, a treatment of one box. meaning that if I am dragging any one of the box which are clubbed together, whole set of the boxes should be dragged together.
second, as and when I am dropping the boxes I want to generate some plain text based on the box which has been dropped, for example if I dropped "first box", the plain text should be something like "you dropped first box first". And this thing I need to do on the sequence, meaning if the second box is appearing first the text according to that box should appear first.
Here is the code which I have completed
<script type="text/javascript">
window.onload = function(){
draw();
}
</script>
<body style="margin: 0;padding:0;clear:both; cursor: pointer">
<canvas id="canvas" tabindex="1" style="float:left" ></canvas>
<div id="plainEnglish" tabindex="2" style="float: left;"></div>
</body>
<script>
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
c.width = 600;
c.height = 300;
//My mouse coordinates
var x,y;
c.addEventListener("mousedown",down);
c.addEventListener("mousemove",move);
c.addEventListener("mouseup",up);
var r = 0;
function counter() {
r++;
console.log(r);
}
//I'll save my boxes in this array
var myBoxes = new Array();
// Those boxes which I have moved to droppable area of the canvas.
var myDroppedBoxes = new Array();
//This function describes what a box is.
//Each created box gets its own values
function box(x,y,w,h,rgb,text) {
this.x = x,
this.y = y;
this.xS = x; //saving x
this.yS = y; //saving y
this.w = w;
this.h = h;
this.rgb = rgb;
this.text = text;
//to determine if the box is being draged
this.draging = false;
this.isBeingDragged = false;
}
//Let's make some boxes!!
myBoxes[0] = new box(20,20,75,20,"#6AA121","First");
myBoxes[1] = new box(20,50,75,20,"#6AA121", "Second");
myBoxes[2] = new box(20,80,75,20,"#6AA121","third");
//here we draw everything
function draw() {
ctx.clearRect(0,0,c.width,c.height);
//Dropable area
ctx.fillStyle="red";
ctx.fillRect(c.width/2,0,c.width,c.height);
//Boxes!
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging) { //box on the move
//Also draw it on the original spot
ctx.fillStyle=b.rgb;
ctx.fillRect(b.xS,b.yS,b.w,b.h);
ctx.strokeRect(b.xS,b.yS,b.w,b.h);
ctx.font = "14px Arial";
ctx.strokeText(b.text, b.xS + 5 , b.yS + 15);
}
ctx.fillStyle=b.rgb;
ctx.fillRect(b.x,b.y,b.w,b.h);
ctx.strokeRect(b.x,b.y,b.w,b.h);
ctx.font = "14px Arial";
ctx.strokeText(b.text, b.x + 5 , b.y + 15);
}
for(var i = 0; i< myDroppedBoxes.length; i++) {
var b = myDroppedBoxes[i];
ctx.fillStyle=b.rgb;
ctx.fillRect(b.x,b.y,b.w,b.h);
ctx.strokeRect(b.x,b.y,b.w,b.h);
ctx.font = "14px Arial";
ctx.strokeText(b.text, b.x + 5 , b.y + 15);
}
}
function down(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (x>b.xS && x<b.xS+b.w && y>b.yS && y<b.yS+b.h) {
myBoxes[i].draging = true;
myBoxes[i].isBeingDragged = true;
}
}
for (var i = 0; i<myDroppedBoxes.length; i++) {
var b = myDroppedBoxes[i];
if (x>b.x && x<b.x + b.w && y>b.y && y<b.y + b.h) {
b.draging = true;
b.isBeingDragged = true;
}
}
draw();
}
function move(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i<myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging && b.isBeingDragged) {
myBoxes[i].x = x;
myBoxes[i].y = y;
if (b.x>c.width/2) {
var length = myDroppedBoxes.length ;
myDroppedBoxes[length] = new box(x,y,b.w,b.h,b.rgb,b.text);
myDroppedBoxes[length].draging = true;
myDroppedBoxes[length].isBeingDragged = true;
b.x = b.xS;
b.y = b.yS;
b.isBeingDragged = false;
}
}
}
for (var i = 0; i<myDroppedBoxes.length; i++) {
var b = myDroppedBoxes[i];
if (b.draging && b.isBeingDragged) {
b.x = x;
b.y = y;
}
}
draw();
}
function up(event) {
event = event || window.event;
x = event.pageX - c.offsetLeft,
y = event.pageY - c.offsetTop;
for (var i = 0; i< myBoxes.length; i++) {
var b = myBoxes[i];
if (b.draging && b.isBeingDragged) {
//Let's see if the rectangle is inside the dropable area
if (b.x < c.width/2) {
myBoxes[i].x = b.xS;
myBoxes[i].y = b.yS;
myBoxes[i].draging = false;
b.isBeingDragged = false;
}
}
}
for (var i = 0; i< myDroppedBoxes.length; i++) {
var b = myDroppedBoxes[i];
if ( b.isBeingDragged) {
//Let's see if the rectangle is inside the dropable area
if (b.x>c.width/2) {
b.x = x;
b.y = y;
clubLegos(b);
plainTextMaker();
b.isBeingDragged = false;
}
else {
//No it's not, sending it back to its original spot
myDroppedBoxes.splice(i,1);
}
}
}
draw();
}
function clubLegos(b) {
// this loop is for checking that the box is lying near to which other box.
for(var j = 0; j < myDroppedBoxes.length; j++) {
var z = myDroppedBoxes[j];
if(!z.isBeingDragged) {
if(((x > z.x) && (x < (z.x + z.w))) && ((y > (z.y - 15)) && (y < (z.y + z.h + 10)))) {
b.x = z.x;
if( (y - z.y) >= 0) {
b.y = (z.y + z.h);
console.log("inside if " + y + " " + z.y);
}
else {
console.log("inside else " + y + " " + z.y);
b.y = (z.y - z.h);
}
}
}
}
}
function plainTextMaker() {
plainEnglishDiv = document.getElementById("plainEnglish");
plainEnglishDiv.innerHTML = "<h3>Here I am generating some plain text based on each drag and drop</h3>";
}
</script>
</html>
Here is the JS Fiddle for the same -
http://jsfiddle.net/akki166786/wa52f9pm/
Any help is greatly appreciated.
Thanks in advance.
Give each of your box objects something similar to Html's class.
This way you can drag every box with the same "class" simultaneously.
Add a club: property to each box.
// at top of app set a counter to make new clubs
var nextClub=0;
// each new box gets a unique "club"
function box(x,y,w,h,rgb,text) {
...
club:(nextClub++),
...
In clubLegos(b) when you're attaching dragged box b to another box z, also give the dropped box (b) & any member of (z)'s group a brand new club id:
// give the dropped box (b) & any member of (z)'s group a brand new club id
...if attaching b to z
b.club=nextClub;
for(var i=0;i<myBoxes.length;i++){
var bb=myBoxes[i];
if(bb.club==z.club){
bb.club=nextClub;
}
}
nextClub++;
This way, for example, if the mousedown has the user starting to drag a box with club==2, then you can add each myBoxes with club==2 into your dragging array and every box with club==2 will be dragged simultaneously.

Creating a bouncing box animation on canvas

I need to create an animation of dropping box, which supposed to bounce 10 times when it reaches a certain Y point on canvas, each time twice lower that the previous. So I have the animation of the dropping box, but I can't make the bounce work. Here are the functions that I wrote:
function dropBox(y, width, height) {
var img_box = new Image();
img_box.src = 'images/gift_box_small.png';
var box_y_pos = y;
if(y==0)
box_y_pos = y-img_box.naturalHeight;
img_box.onload = function(){
ctx_overlay.save();
ctx_overlay.clearRect(0,0,width,height);
ctx_overlay.drawImage(img_box, (width/2)-(img_box.naturalWidth/2), box_y_pos);
ctx_overlay.restore();
}
box_y_pos += 3;
var box_bottom_position = box_y_pos - img_box.naturalHeight;
if(box_y_pos+img_box.naturalHeight<height-25)
var loopTimer = setTimeout(function() {dropBox(box_y_pos, width, height)},24);
else
bounceBox(img_box, box_y_pos, box_y_pos, (height/2)-(img_box.naturalHeight/2), "up");
}
function bounceBox(img, img_final_pos, y, midway_pos, direction){
var midway = midway_pos;
var direction = direction;
var img_y_pos = y;
img.onload = function(){
ctx_overlay.save();
ctx_overlay.clearRect(0,0,docWidth,docHeight);
ctx_overlay.drawImage(img, (docWidth/2)-(img.naturalWidth/2), img_y_pos);
ctx_overlay.restore();
}
for(var i = 0; i < 10; i++){
if(direction=="up"){
//going up
if(img_y_pos>midway_){
img_y_pos -= 3;
var loopTimer = setTimeout(function() {bounceBox(img, img_final_pos, img_y_pos, midway_pos, "up")},24);
} else {
img_y_pos += 3;
midway = Math.floor(midway /= 2);
if(midway%2>0)
midway += 1;
var loopTimer = setTimeout(function() {bounceBox(img, img_final_pos, img_y_pos, midway_pos, "down")},24);
}
} else {
//going down
if(img_y_pos < img_final_pos){
img_y_pos += 3;
var loopTimer = setTimeout(function() {bounceBox(img, img_final_pos, img_y_pos, midway_pos, "down")},24);
}
}
}
}
JSFiddle: http://jsfiddle.net/n2derqgw/3/
Why isn't it working and how can I make it work?
To avoid getting a headache, you've better handle the animation within a single function called with a setInterval.
And keep all animation-related data in one object.
So the code below does not exactly what you want, but should get you started :
http://jsfiddle.net/n2derqgw/4/
Setup :
var canvas_overlay, ctx_overlay, docWidth, docHeight;
var img_box = new Image();
img_box.src = 'http://corkeynet.com/test/images/gift_box_small.png';
var mustBeReadyCount = 2; // must load image and window
img_box.onload = launchWhenReady;
window.onload = launchWhenReady;
var animationStep = '';
var boxAnimationData = {
animationStep: '',
y: 0,
maxY: 0,
bounceCount: 6,
direction: -1,
bounceHeight: 0
};
function launchWhenReady() {
mustBeReadyCount--;
if (mustBeReadyCount) return;
docWidth = window.innerWidth;
docHeight = window.innerHeight;
canvas_overlay = document.getElementById('canvas_overlay');
ctx_overlay = canvas_overlay.getContext('2d');
resizeCanvas(docWidth, docHeight);
boxAnimationData.animationStep = 'falling';
boxAnimationData.bounceHeight = docHeight / 2 - img_box.height;
setInterval(animateBox, 30);
};
More interesting code is here :
function animateBox() {
if (boxAnimationData.animationStep == 'falling') dropBox();
else if (boxAnimationData.animationStep == 'bouncing') bounceBox();
}
function dropBox() {
ctx_overlay.clearRect(0, 0, docWidth, docHeight);
boxAnimationData.y += 3;
if (boxAnimationData.y + img_box.height > docHeight) {
boxAnimationData.animationStep = 'bouncing';
}
ctx_overlay.drawImage(img_box, (docWidth / 2) - (img_box.width / 2), boxAnimationData.y);
}
function bounceBox() {
ctx_overlay.clearRect(0, 0, docWidth, docHeight);
boxAnimationData.y += boxAnimationData.direction * 3;
if (boxAnimationData.y + img_box.height > docHeight) {
// reached floor ? swap direction
boxAnimationData.direction *= -1;
// and reduce jump height
boxAnimationData.bounceHeight *= 3 / 2;
boxAnimationData.bounceCount--;
if (!boxAnimationData.bounceCount) boxAnimationData.animationStep = '';
} else if (boxAnimationData.y < boxAnimationData.bounceHeight) {
boxAnimationData.direction *= -1;
}
ctx_overlay.drawImage(img_box, (docWidth / 2) - (img_box.width / 2), boxAnimationData.y);
}

PaperJS - How do I connect all items that have a distance of X from any given item? (Item interactivity)

I have a project I am trying to get an animated <canvas> working with Paper JS. What I am curious about is if there is anything built into PaperJS that allows the ability to detect interactivity between items (i.e. if a item is X distance from any other item on the layer). Here is what I have so far:
HTML
<canvas id="myCanvas" resize></canvas>
CSS
html, body{margin:0; padding: 0;}
#myCanvas{width: 100%; height: 100%;}
JS
$(function(){
var canvas = $('#myCanvas')[0];
paper.setup(canvas);
var viewSize = paper.view.size;
var itemCount = 20;
var theBall = new paper.Path.Rectangle({
point : [0,0],
size : 10,
fillColor : '#00a950',
});
var theBallSymbol = new paper.Symbol(theBall);
// Create and place symbol on view
for (var i = 1; i <= itemCount; i++) {
var center = paper.Point.random().multiply(viewSize);
var placedSymbol = theBallSymbol.place(center);
placedSymbol.scale(i / itemCount);
placedSymbol.data = {
origin : center,
direction : (Math.round(Math.random())) ? 'right' : 'left',
}
placedSymbol.onFrame = function(e){
var pathWidth = this.bounds.width * 20;
var center = this.data.origin;
var moveValue = this.bounds.width / 20;
if(this.data.direction == 'right'){
if(this.position.x < center.x + pathWidth){
this.position.x += moveValue;
} else{
this.position.x -= moveValue;
this.data.direction = 'left';
}
} else {
if(this.position.x > center.x - pathWidth){
this.position.x -= moveValue;
} else {
this.position.x += moveValue;
this.data.direction = 'right';
}
}
}
}
paper.view.onFrame = function (e){
// For entire view
for (var i = 0; i < itemCount; i++) {
var item = paper.project.activeLayer.children[i];
// I imagine I would need to do something here
// I tried a hitTest already, but I'm not sure
// that will give me the information I would need
}
}
});
JSFiddle
That part so far is working well. What I am curious about how I can do the following:
Whenever any given item (the squares) come within a distance of X between each other, create a line (path) between them
The idea is very similar to this page: http://panasonic.jp/shaver/lamdash/dna/
Any ideas would be greatly appreciated. Thanks!
Paper.js does not keep track of the inter-point distance between an item's center and all other items. The only way to gather that information is to manually loop through them.
In your case, I think it would be easiest to:
Create an array of lines
Only keep lines that might become shorter than the threshold value
Loop through the lines array on each onFrame() and adjust the opacity.
By only choosing lines that will come within a threshold value, you can avoid creating unnecessary paths that would slow the framerate. Without this, you'd be checking ~5 times as many items.
Here's a quick example:
$(function(){
var canvas = $('#myCanvas')[0];
paper.setup(canvas);
var viewSize = paper.view.size;
var itemCount = 60;
//setup arrays to change line segments
var ballArray = [];
var lineArray = [];
//threshold distance for lines
var threshold = Math.sqrt(paper.view.size.width*paper.view.size.height)/5;
var theBall = new paper.Path.Rectangle({
point : [0,0],
size : 10,
fillColor : '#00a950',
});
var theBallSymbol = new paper.Symbol(theBall);
// Create and place symbol on view
for (var i = 1; i <= itemCount; i++) {
var center = paper.Point.random().multiply(viewSize);
var placedSymbol = theBallSymbol.place(center);
placedSymbol.scale(i / itemCount);
placedSymbol.data = {
origin : center,
direction : (Math.round(Math.random())) ? 'right' : 'left',
}
// Keep each placedSymbol in an array
ballArray.push( placedSymbol );
placedSymbol.onFrame = function(e){
var pathWidth = this.bounds.width * 20;
var center = this.data.origin;
var moveValue = this.bounds.width / 20;
if(this.data.direction == 'right'){
if(this.position.x < center.x + pathWidth){
this.position.x += moveValue;
} else{
this.position.x -= moveValue;
this.data.direction = 'left';
}
} else {
if(this.position.x > center.x - pathWidth){
this.position.x -= moveValue;
} else {
this.position.x += moveValue;
this.data.direction = 'right';
}
}
}
}
// Run through every possible line
// Only keep lines whose length might become less than threshold
for (var i = 0; i < itemCount; i++) {
for (j = i + 1, point1 = ballArray[i].data.origin; j < itemCount; j++) {
if ( Math.abs(point1.y - ballArray[j].bounds.center.y) < threshold && Math.abs(point1.x - ballArray[j].data.origin.x) < 4 * threshold) {
var line = new paper.Path.Line( point1, ballArray[j].bounds.center ) ;
line.strokeColor = 'black';
line.strokeWidth = .5;
//note the index of the line's segments
line.point1 = i;
line.point2 = j;
if (line.length > 1.4 * threshold && ballArray[j].data.direction == ballArray[i].data.direction) {
line.remove();
}
else {
lineArray.push(line);
}
}
}
}
paper.view.onFrame = function (e){
// Update the segments of each line
// Change each line's opacity with respect to distance
for (var i = 0, l = lineArray.length; i < l; i++) {
var line = lineArray[i];
line.segments[0].point = ballArray[line.point1].bounds.center;
line.segments[1].point = ballArray[line.point2].bounds.center;
if(line.length < threshold) {
line.opacity = (threshold - line.length) / threshold;
}
else line.opacity = 0;
}
}
});

Categories

Resources