<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<canvas id="canvas" width="800" height="450">
Canvas not supported
</canvas>
<div id="scoreboard" class="floatingControls">0</div>
<div id="controls" class="floatingControls">
Launch velocity (m/s): <output id="launchVelocityOutput">15.46</output> m/s<br>
Launch angle (degrees): <output id="launchAngleOutput">24.66</output> degrees<br>
</div>
<br>
<button class="btn">lunch</button>
x : <input id="x" > y : <input id="y" > <br><br>
<button class="btn in">ball in :)</button>
</body>
<script src="script.js"></script>
<script src="requestNextAnimationFrame.js"></script>
<script src="sprites.js"></script>
<script src="example.js"></script>
</html>
// Code goes here
$(function(){
$("#canvas").on("click",function(e){
//alert("canvas clicked !");
});
$(".btn").on("click",function(e){
if($(this).hasClass("in")){
launchBall(163,600);
}
else {
var x = $("#x").val();
var y = $("#y").val();
console.log(x,y);
if(!isNaN(x) && !isNaN(y) && x>0 && y>0)
launchBall(x,y)
}
})
function launchBall(x,y) {
var mousemove = new jQuery.Event("mousemove");
mousemove.clientX = x;
mousemove.clientY =y;
var mousedown = new jQuery.Event("mousedown");
$("#canvas").trigger(mousemove);
$("#canvas").trigger(mousedown);
}
})
/* Styles go here */
output {
color: blue;
}
.floatingControls {
background: rgba(0, 0, 0, 0.1);
border: thin solid skyblue;
-webkit-box-shadow: rgba(0,0,0,0.3) 2px 2px 4px;
-moz-box-shadow: rgba(100,140,230,0.5) 2px 2px 6px;
box-shadow: rgba(100,140,230,0.5) 2px 2px 6px;
padding: 15px;
font: 12px Arial;
}
#canvas {
background: skyblue;
-webkit-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
-moz-box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
box-shadow: 4px 4px 8px rgba(0,0,0,0.5);
cursor: pointer;
}
#scoreboard {
background: rgba(255,255,255,0.5);
position: absolute;
left: 755px;
top: 20px;
color: blue;
font-size: 18px;
padding: 5px;
}
#controls {
position: absolute;
left: 285px;
top: 20px;
}
/*
* Copyright (C) 2012 David Geary. This code is from the book
* Core HTML5 Canvas, published by Prentice-Hall in 2012.
*
* License:
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* The Software may not be used to create training material of any sort,
* including courses, books, instructional videos, presentations, etc.
* without the express written consent of David Geary.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
scoreboard = document.getElementById('scoreboard'),
launchVelocityOutput = document.getElementById('launchVelocityOutput'),
launchAngleOutput = document.getElementById('launchAngleOutput'),
elapsedTime = undefined,
launchTime = undefined,
score = 0,
lastScore = 0,
lastMouse = { left: 0, top: 0 },
threePointer = false,
needInstructions = true,
LAUNCHPAD_X = 50,
LAUNCHPAD_Y = context.canvas.height-50,
LAUNCHPAD_WIDTH = 50,
LAUNCHPAD_HEIGHT = 12,
BALL_RADIUS = 8,
ARENA_LENGTH_IN_METERS = 10,
INITIAL_LAUNCH_ANGLE = Math.PI/4,
launchAngle = INITIAL_LAUNCH_ANGLE,
pixelsPerMeter = canvas.width / ARENA_LENGTH_IN_METERS,
// LaunchPad.................................................
launchPadPainter = {
LAUNCHPAD_FILL_STYLE: 'rgb(100,140,230)',
paint: function (ledge, context) {
context.save();
context.fillStyle = this.LAUNCHPAD_FILL_STYLE;
context.fillRect(LAUNCHPAD_X, LAUNCHPAD_Y,
LAUNCHPAD_WIDTH, LAUNCHPAD_HEIGHT);
context.restore();
}
},
launchPad = new Sprite('launchPad', launchPadPainter),
// Ball......................................................
ballPainter = {
BALL_FILL_STYLE: 'rgb(255,255,0)',
BALL_STROKE_STYLE: 'rgb(0,0,0,0.4)',
paint: function (ball, context) {
context.save();
context.shadowColor = undefined;
context.lineWidth = 2;
context.fillStyle = this.BALL_FILL_STYLE;
context.strokeStyle = this.BALL_STROKE_STYLE;
context.beginPath();
context.arc(ball.left, ball.top,
ball.radius, 0, Math.PI*2, false);
context.clip();
context.fill();
context.stroke();
context.restore();
}
},
// Lob behavior..............................................
lob = {
lastTime: 0,
GRAVITY_FORCE: 9.81, // m/s/s
applyGravity: function (elapsed) {
ball.velocityY = (this.GRAVITY_FORCE * elapsed) -
(launchVelocity * Math.sin(launchAngle));
},
updateBallPosition: function (updateDelta) {
ball.left += ball.velocityX * (updateDelta) * pixelsPerMeter;
ball.top += ball.velocityY * (updateDelta) * pixelsPerMeter;
},
checkForThreePointer: function () {
if (ball.top < 0) {
threePointer = true;
}
},
checkBallBounds: function () {
if (ball.top > canvas.height || ball.left > canvas.width) {
reset();
}
},
execute: function (ball, context, time) {
var updateDelta,
elapsedFlightTime;
if (ballInFlight) {
elapsedFrameTime = (time - this.lastTime)/1000,
elapsedFlightTime = (time - launchTime)/1000;
this.applyGravity(elapsedFlightTime);
this.updateBallPosition(elapsedFrameTime);
this.checkForThreePointer();
this.checkBallBounds();
}
this.lastTime = time;
}
},
ball = new Sprite('ball', ballPainter, [ lob ]),
ballInFlight = false,
// Bucket....................................................
catchBall = {
ballInBucket: function() {
return ball.left > bucket.left + bucket.width/2 &&
ball.left < bucket.left + bucket.width &&
ball.top > bucket.top && ball.top < bucket.top + bucket.height/3;
},
adjustScore: function() {
if (threePointer) lastScore = 3;
else lastScore = 2;
score += lastScore;
scoreboard.innerText = score;
},
execute: function (bucket, context, time) {
if (ballInFlight && this.ballInBucket()) {
reset();
this.adjustScore();
}
}
},
BUCKET_X = 668,
BUCKET_Y = canvas.height - 100,
bucketImage = new Image(),
bucket = new Sprite('bucket',
{
paint: function (sprite, context) {
context.drawImage(bucketImage, BUCKET_X, BUCKET_Y);
}
},
[ catchBall ]
);
// Functions.....................................................
function windowToCanvas(x, y) {
var bbox = canvas.getBoundingClientRect();
return { x: x - bbox.left * (canvas.width / bbox.width),
y: y - bbox.top * (canvas.height / bbox.height)
};
}
function reset() {
ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2;
ball.top = LAUNCHPAD_Y - ball.height/2;
ball.velocityX = 0;
ball.velocityY = 0;
ballInFlight = false;
needInstructions = false;
lastScore = 0;
}
function showText(text) {
var metrics;
context.font = '42px Helvetica';
metrics = context.measureText(text);
context.save();
context.shadowColor = undefined;
context.strokeStyle = 'rgb(80,120,210)';
context.fillStyle = 'rgba(100,140,230,0.5)';
context.fillText(text,
canvas.width/2 - metrics.width/2,
canvas.height/2);
context.strokeText(text,
canvas.width/2 - metrics.width/2,
canvas.height/2);
context.restore();
}
function drawGuidewire() {
context.moveTo(ball.left, ball.top);
context.lineTo(lastMouse.left, lastMouse.top);
context.stroke();
};
function updateBackgroundText() {
if (lastScore == 3) showText('Three pointer!');
else if (lastScore == 2) showText('Nice shot!');
else if (needInstructions) showText('Click to launch ball');
};
function resetScoreLater() {
setTimeout(function () {
lastScore = 0;
}, 1000);
};
function updateSprites(time) {
bucket.update(context, time);
launchPad.update(context, time);
ball.update(context, time);
}
function paintSprites() {
launchPad.paint(context);
bucket.paint(context);
ball.paint(context);
}
// Event handlers................................................
canvas.onmousedown = function(e) {
var rect;
e.preventDefault();
if ( ! ballInFlight) {
ball.velocityX = launchVelocity * Math.cos(launchAngle);
ball.velocityY = launchVelocity * Math.sin(launchAngle);
ballInFlight = true;
threePointer = false;
launchTime = +new Date();
}
};
canvas.onmousemove = function (e) {
var rect;
e.preventDefault();
if ( ! ballInFlight) {
loc = windowToCanvas(e.clientX, e.clientY);
lastMouse.left = loc.x;
lastMouse.top = loc.y;
deltaX = Math.abs(lastMouse.left - ball.left);
deltaY = Math.abs(lastMouse.top - ball.top);
launchAngle = Math.atan(parseFloat(deltaY) / parseFloat(deltaX));
launchVelocity = 4 * deltaY / Math.sin(launchAngle) / pixelsPerMeter;
launchVelocityOutput.innerText = launchVelocity.toFixed(2);
launchAngleOutput.innerText = (launchAngle * 180/Math.PI).toFixed(2);
}
};
// Animation Loop................................................
function animate(time) {
var time = +new Date(); // ignore the time the browser passes to this function
elapsedTime = (time - launchTime) / 1000;
context.clearRect(0,0,canvas.width,canvas.height);
if (!ballInFlight) {
drawGuidewire();
updateBackgroundText();
if (lastScore !== 0) { // just scored
resetScoreLater();
}
}
updateSprites(time);
paintSprites();
window.requestNextAnimationFrame(animate);
}
// Initialization................................................
ball.width = BALL_RADIUS*2;
ball.height = ball.width;
ball.left = LAUNCHPAD_X + LAUNCHPAD_WIDTH/2;
ball.top = LAUNCHPAD_Y - ball.height/2;
ball.radius = BALL_RADIUS;
context.lineWidth = 0.5;
context.strokeStyle = 'rgba(0,0,0,0.5)';
context.shadowColor = 'rgba(0,0,0,0.5)';
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.shadowBlur = 4;
context.stroke();
bucketImage.src = 'http://corehtml5canvas.com/code-live/shared/images/bucket.png';
bucketImage.onload = function (e) {
bucket.left = BUCKET_X;
bucket.top = BUCKET_Y;
bucket.width = bucketImage.width;
bucket.height = bucketImage.height;
};
window.requestNextAnimationFrame(animate);
/*
* Copyright (C) 2012 David Geary. This code is from the book
* Core HTML5 Canvas, published by Prentice-Hall in 2012.
*
* License:
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* The Software may not be used to create training material of any sort,
* including courses, books, instructional videos, presentations, etc.
* without the express written consent of David Geary.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
window.requestNextAnimationFrame =
(function () {
var originalWebkitRequestAnimationFrame = undefined,
wrapper = undefined,
callback = undefined,
geckoVersion = 0,
userAgent = navigator.userAgent,
index = 0,
self = this;
// Workaround for Chrome 10 bug where Chrome
// does not pass the time to the animation function
if (window.webkitRequestAnimationFrame) {
// Define the wrapper
wrapper = function (time) {
if (time === undefined) {
time = +new Date();
}
self.callback(time);
};
// Make the switch
originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;
window.webkitRequestAnimationFrame = function (callback, element) {
self.callback = callback;
// Browser calls the wrapper and wrapper calls the callback
originalWebkitRequestAnimationFrame(wrapper, element);
}
}
// Workaround for Gecko 2.0, which has a bug in
// mozRequestAnimationFrame() that restricts animations
// to 30-40 fps.
if (window.mozRequestAnimationFrame) {
// Check the Gecko version. Gecko is used by browsers
// other than Firefox. Gecko 2.0 corresponds to
// Firefox 4.0.
index = userAgent.indexOf('rv:');
if (userAgent.indexOf('Gecko') != -1) {
geckoVersion = userAgent.substr(index + 3, 3);
if (geckoVersion === '2.0') {
// Forces the return statement to fall through
// to the setTimeout() function.
window.mozRequestAnimationFrame = undefined;
}
}
}
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback, element) {
var start,
finish;
window.setTimeout( function () {
start = +new Date();
callback(start);
finish = +new Date();
self.timeout = 1000 / 60 - (finish - start);
}, self.timeout);
};
}
)
();
/*
* Copyright (C) 2012 David Geary. This code is from the book
* Core HTML5 Canvas, published by Prentice-Hall in 2012.
*
* License:
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* The Software may not be used to create training material of any sort,
* including courses, books, instructional videos, presentations, etc.
* without the express written consent of David Geary.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
// Painters...................................................................
// Painters paint sprites with a paint(sprite, context) method. ImagePainters
// paint an image for their sprite.
var ImagePainter = function (imageUrl) {
this.image = new Image;
this.image.src = imageUrl;
};
ImagePainter.prototype = {
image: undefined,
paint: function (sprite, context) {
if (this.image !== undefined) {
if ( ! this.image.complete) {
this.image.onload = function (e) {
sprite.width = this.width;
sprite.height = this.height;
context.drawImage(this, // this is image
sprite.left, sprite.top,
sprite.width, sprite.height);
};
}
else {
context.drawImage(this.image, sprite.left, sprite.top,
sprite.width, sprite.height);
}
}
}
};
SpriteSheetPainter = function (cells) {
this.cells = cells;
};
SpriteSheetPainter.prototype = {
cells: [],
cellIndex: 0,
advance: function () {
if (this.cellIndex == this.cells.length-1) {
this.cellIndex = 0;
}
else {
this.cellIndex++;
}
},
paint: function (sprite, context) {
var cell = this.cells[this.cellIndex];
context.drawImage(spritesheet, cell.left, cell.top,
cell.width, cell.height,
sprite.left, sprite.top,
cell.width, cell.height);
}
};
// Sprite Animators...........................................................
// Sprite animators have an array of painters that they succesively apply
// to a sprite over a period of time. Animators can be started with
// start(sprite, durationInMillis, restoreSprite)
var SpriteAnimator = function (painters, elapsedCallback) {
this.painters = painters;
if (elapsedCallback) {
this.elapsedCallback = elapsedCallback;
}
};
SpriteAnimator.prototype = {
painters: [],
duration: 1000,
startTime: 0,
index: 0,
elapsedCallback: undefined,
end: function (sprite, originalPainter) {
sprite.animating = false;
if (this.elapsedCallback) {
this.elapsedCallback(sprite);
}
else {
sprite.painter = originalPainter;
}
},
start: function (sprite, duration) {
var endTime = +new Date() + duration,
period = duration / (this.painters.length),
interval = undefined,
animator = this, // for setInterval() function
originalPainter = sprite.painter;
this.index = 0;
sprite.animating = true;
sprite.painter = this.painters[this.index];
interval = setInterval(function() {
if (+new Date() < endTime) {
sprite.painter = animator.painters[++animator.index];
}
else {
animator.end(sprite, originalPainter);
clearInterval(interval);
}
}, period);
},
};
// Sprites....................................................................
// Sprites have a name, a painter, and an array of behaviors. Sprites can
// be updated, and painted.
//
// A sprite's painter paints the sprite: paint(sprite, context)
// A sprite's behavior executes: execute(sprite, context, time)
var Sprite = function (name, painter, behaviors) {
if (name !== undefined) this.name = name;
if (painter !== undefined) this.painter = painter;
if (behaviors !== undefined) this.behaviors = behaviors;
return this;
};
Sprite.prototype = {
left: 0,
top: 0,
width: 10,
height: 10,
velocityX: 0,
velocityY: 0,
visible: true,
animating: false,
painter: undefined, // object with paint(sprite, context)
behaviors: [], // objects with execute(sprite, context, time)
paint: function (context) {
if (this.painter !== undefined && this.visible) {
this.painter.paint(this, context);
}
},
update: function (context, time) {
for (var i = this.behaviors.length; i > 0; --i) {
this.behaviors[i-1].execute(this, context, time);
}
}
};