Related
I'm making a project that includes many squares and if too many are rendered than it makes it really laggy. Is there a way to check if there is a block right next to it and not render a face of it in order to make it run faster?
Currently I am making boxes using 6 different planes and moving them around to make a box. I am aware this is not the most efficient way to do it and I have fixed that as of right now and that was the best way for my little brain to visualize it. Here is the code that checks if there is another box next to it:
let blRight = false
let blLeft = false
let blBack = false
let blFront = false
let blTop = false
let blBottom = false
for(let i = 0; i < blocks.coord.x.length; i++) {
if(blocks.coord.x[i] == x + 10) {
if(!blocks.coord.y[i] == y) {
blRight = true
}
}
}
for(let i = 0; i < blocks.coord.x.length; i++) {
if(blocks.coord.x[i] == x - 10) {
if(!blocks.coord.y[i] == y) {
blLeft = true
}
}
}
for(let i = 0; i < blocks.coord.y.length; i++) {
if(blocks.coord.y[i] == y + 10) {
blTop = false
}
}
for(let i = 0; i < blocks.coord.y.length; i++) {
if(blocks.coord.y[i] == y - 10) {
blBottom = true
}
}
for(let i = 0; i < blocks.coord.z.length; i++) {
if(blocks.coord.z[i] == z + 10) {
if(!blocks.coord.y[i] == y) {
blFront = true
}
}
}
for(let i = 0; i < blocks.coord.z.length; i++) {
if(blocks.coord.z[i] == x - 10) {
if(!blocks.coord.y[i] == y) {
blBack = true
}
}
}
if(blRight == false) {
scene.add(right)
}
if(blLeft == false) {
scene.add(left)
}
if(blTop == false) {
scene.add(blockTop)
}
if(blBottom == false) {
scene.add(bottom)
}
if(blFront == false) {
scene.add(front)
}
if(blBack == false) {
scene.add(back)
}
blocks.coord.x.push(x)
blocks.coord.y.push(y)
blocks.coord.z.push(z)
blocks.coord.id.push(3)
This is in a function that is called to make a new block. blTop meaning top face and so on. This is actually how Minecraft makes its game faster. Any suggestions to make it more effecient?
I want to create a drawing in sugar thing, much the same as you would throw some sugar on a table and using your fingers to "erase" the sugar particles to form an image.
Does anyone know of a js type tool I can use to make this happen?
I suppose I can take a photo of a desk, and a photo of desk with some sugar on it, and then just erase the top layer, but I'm worried that this won't give a real effect.
I'm currently thinking of having a photo of desk, and then using JS to generate a lot of "sugary" particles, which I can then erase. This sounds incredibly hard to do though. Is it? Can someone point me in a good direction?
Sand or is it sugar?
An interesting problem that I had to give a little time.
This works by creating several buffers to hold grains of sand (sugar) and give them life when they need to move.
There is no way that Javascript could do a whole screen of a million plus grains so this demo cheats by only updating a very few and prioritising for new movement rather than allow older moving grains to hog CPU time.
The arrays active, sandStatus, holds the sand gains. active has the pixel address as a 32Bit int and sandStatus has age. The Array sand holds the amount of sand at each pixel and is used to calculate the shadow effect (shadow could be much better using a webGL shader) and to work out which direction sand should slide if disturbed or dropped to the surface.
the var activeMax holds the max number of active sand grains. Increase for a better effect, decrease if the sim runs to slow.
To drop sand use the right mouse button. Hold at one spot to make a pile. Left button pushes the sand about. When you hit a bigger pile the machine may lag (depending on CPU power and browser (best in firefox)).
The push function checks the sand array for any sand. If found it pushes the sand away from the center and piles it up around the edge. Some sand will fall back.
The function sprinkle adds grains of sand (one are a time by pixel coordinate or by index). The function push does the sand drawing FX. update moves the sand grains by checking surrounding pixels heights and moving grains down hill. renderPix handles rendering grains, creating the shadows and deactivating sand grains. The Array shadowChange holds the index of pixels that have had changes so that the shadows can be updated.
Bottom half of the demo is just boilerplate for mouse and canvas setup. All the code in regard to the answer is in the first half.
"use strict";
var activeMax = 2280; // this is the number of sand grains that are processed at
// at time. Increase for better looking effect. decrease
// if the machine is not keeping up with the load
var cw;
var ch;
var w; //
var h;
var canvasBuf = document.createElement("canvas");
var ctxB
var globalTime; // global to this
var pixels
var sand;
var sandToFall;
var sandToFallCount = 36000;
var shadow; // shadow pixels
var activeMax = 2280;
var active; // index of pixel for active grain
var sandStatus; // status of active grain
var shadowChange; // holds index of pixels that have a shadow change
var pixels;
var buf;
var grain = 0xFFFFFFFF;
var shadowGrain = 0x00000000;
var ready = false;
var sandReady = 0;
var nextActive = 0;
var nextActiveShadow = 0;
var onResize = function(){
cw = canvas.width;
ch = canvas.height;
w = cw; //
h = ch;
pixels = w*h;
canvasBuf.width = w;
canvasBuf.height = h;
ctxB = canvasBuf.getContext("2d");
sand = new Uint8ClampedArray(pixels);
shadow = new Uint8ClampedArray(pixels); // shadow pixels
sandToFall = new Uint32Array(sandToFallCount);
activeMax = 2280;
active = new Uint32Array(activeMax); // index of pixel for active grain
sandStatus = new Uint16Array(activeMax); // status of active grain
shadowChange= new Uint32Array(activeMax); // holds index of pixels that have a shadow change
sandStatus.fill(0); // clear
active.fill(0);
shadowChange.fill(0);
ctxB.clearRect(0,0,w,h);
ctxB.fillStyle = "white";
ctxB.font = "84px arial";
ctxB.textAlign = "center";
ctxB.globalAlpha = 0.01;
for(var i = 0; i < 12; i ++){
ctxB.fillText("Sand Doodler!",w/2 + (Math.random()-0.5)*5,h/2 + (Math.random()-0.5)*5);
}
ctxB.globalAlpha = 1;
pixels = ctxB.getImageData(0,0,w,h);
buf = new Uint32Array(pixels.data.buffer);
for(i = 0; i < buf.length; i += 3){
if(buf[i] !== 0){
var c = buf[i] >>> 24;
buf[i] = 0;
while(c > 0){
var ind = Math.floor(Math.random()*sandToFallCount);
if(sandToFall[ind] === 0){
sandToFall[ind] = i;
}
c = c >>> 1;
}
}
}
buf.fill(0);
offsets = [1,w-1,w,w+1,-w-1,-w,-w+1,-1];
shadowOffsets = [-w-1,-w,-1];
ready = true;
sandReady=0;
}
function sprinkle(x,y){
var ind;
if(y === undefined){
ind = x;
}else{
ind = x + y*w;
}
var alreadyExists = active.indexOf(ind);
var ac = nextActive;
if(alreadyExists > -1){
sand[ind] += 1;
shadow[ind] = 0;
sandStatus[alreadyExists] = 66;
}else{
active[nextActive] = ind;
sandStatus[nextActive] = 66;
shadowChange[nextActiveShadow];
nextActiveShadow = (nextActiveShadow+1)%activeMax;
nextActive = (nextActive +1)%activeMax;
sand[ind] += 1;
shadow[ind] = 0;
}
return ac;
}
var offsets = [1,w-1,w,w+1,-w-1,-w,-w+1,-1];
var offsetCount = 8;
function update(){
var min,max,minDir,maxDir,dir,start,jj,j,ind,level,i,l1;
for( i = 0; i <activeMax; i ++){
if(sandStatus[i] !== 0){
ind = active[i];
level = sand[ind];
if(level === 1){
sandStatus[i] = 1; // deactive is cant move (level ground)
}else{
min = level;
var d;
minDir = offsets[Math.floor(Math.random()*16)];
dir = null;
start = Math.floor(Math.random()*16); // start at a random direction
for(j=0;j < offsetCount; j++){
jj = offsets[(j + start)%offsetCount];
l1 = sand[ind+jj];
if(l1 < min){
min = l1;
minDir = jj;
d = (j + start)%offsetCount;
}
}
dir = null;
if(min >= level - 1){ // nowhere to move
sandStatus[i] = 1;
}else
if(min < level-1){ // move to lowest
dir = minDir
}
if(dir !== null){
var lv = level-min;
while(lv > 2){
active[i] = ind + dir;
if(sand[ind] > 1){
sand[ind] -= 2;
sprinkle(ind)
}else{
sand[ind] -=1;
}
ind = ind+dir;
sand[active[i]] += 1;
if(sand[active[i] + offsets[d]] >=level){
d+= Math.random()<0.5? 1 : offsetCount -1;
d %=offsetCount;
}
lv -= 1;
}
if(sand[ind]>0){
active[i] = ind + dir;
sand[ind] -= 1;
}
sand[active[i]] += 1;
}
}
}
}
}
var shadowOffsets = [-w-1,-w,-1];
var shadowCols = [0xFFf0f0f0,0xFFd0d0d0,0xFFb0b0b0,0xFF909090];
var shadowDist = [0xf0000000,0xd0000000,0xb0000000,0x90000000]; // shadow col no sand
// renders grains and adds shadows. Deactivates gains when they are done
function renderPix(){
var ac = 0;
for(var i = 0; i < activeMax; i ++){
if(sandStatus[i] !== 0){
ac += 1;
var ind = active[i];
buf[ind] = grain;
}
}
for(var i = 0; i < activeMax; i ++){
if(sandStatus[i] !== 0){
var ind = active[i];
var level = sand[ind];
var col =0;
if(sand[ind + shadowOffsets[0]] > level ){
col = 2;
}else
if(sand[ind + shadowOffsets[1]] > level ){
col =1;
}else
if(sand[ind + shadowOffsets[2]] > level ){
col = 1;
}
buf[ind] = grain; // add a sand grain to the image
shadow[ind] = col;
shadowChange[nextActiveShadow] = ind;
nextActiveShadow = (nextActiveShadow + 1)%activeMax;
var c = 4;
while(c > 0){
c-=1;
ind += w + 1;
var s = sand[ind];
var dif = level - s;
if(dif > 0){
c-= dif;
}
shadow[ind] += 1;
shadowChange[nextActiveShadow] = ind;
nextActiveShadow = (nextActiveShadow + 1)%activeMax;
}
sandStatus[i] -= 1;
if(sandStatus[i] === 1){
sandStatus[i] = 0;
active[i] = 0;
}
}
}
// add calculated shadows
for(var i = 0; i < activeMax; i ++){
if(shadowChange[i] !== 0){
var ind = shadowChange[i];
var s = shadow[ind] <4 ? shadow[ind]-1:3;
if(sand[ind] > 0){
buf[ind]=shadowCols[s];
}else{
buf[ind]=shadowDist[s];
}
shadowChange[i] = 0;
}
}
}
// push sand about
function push(x,y,radius){
var iyy,iny
var rr = radius * radius ;
x = Math.floor(x);
y = Math.floor(y);
for(var iy = -radius + 1; iy < radius; iy ++){
iyy = iy * iy;
iny = (y+iy) * w;
for(var ix = -radius + 1; ix < radius; ix ++){
if(ix*ix + iyy <= rr){ // is inside radius
var ind = (x + ix) + iny;
if(sand[ind] > 0){
var dir = Math.random() * Math.PI * 2;
dir = Math.atan2(iy,ix)
var r = radius + Math.random() * radius *0.2
var xx = Math.cos(dir) * r;
var yy = Math.sin(dir) * r;
buf[ind] = 0x000000;
sand[ind] = 0;
ind = Math.floor(xx + x) + Math.floor(yy + y) * w;
sprinkle(ind);
}else{
buf[ind] = 0;
}
}
}
}
}
function showHeight(){ // for debugging only
for(var i = 0; i < sand.length; i ++){
buf[i] = 0xff000000;
var k = sand[i];
buf[i] +=(k <<16) + (k<<8) + (k);
}
}
// main update function
function display(){
if(!ready){ // only when ready
return;
}
//ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.clearRect(0,0,cw,ch);
var mx = Math.floor((mouse.x/cw)*w); // canvas buf mouse pos
var my = Math.floor((mouse.y/ch)*h);
// drop sand
if(mouse.buttonRaw & 4){
for(var i = 0; i < 120; i ++){
var dir = Math.random()*Math.PI;
var dist = ((Math.random()+Math.random()+Math.random())/3-0.5) * 62;
var x = Math.cos(dir) * dist;
var y = Math.sin(dir) * dist;
x += mx;
y += my;
x = Math.floor(x); // floor
y = Math.floor(y); // floor
sprinkle(x,y);
}
}else{
// drop sand for intro FX
if(sandReady <sandToFallCount){
for(var i = 0; i < 120; i ++){
if(sandToFall[sandReady] !== 0){
sprinkle(sandToFall[sandReady] + offsets[Math.floor(Math.random()*8)%offsets.length]);
}
sandReady += 1;
}
}
}
// push sand about.
if(mouse.buttonRaw & 1){
push(((mouse.x/cw)*w),((mouse.y/ch)*h),32); // scale mouse to canvasBuf size
}
update();
renderPix();
//showHeight();
ctxB.putImageData(pixels,0,0);
ctx.drawImage(canvasBuf,0,0,cw,ch);
}
//==================================================================================================
// The following code is support code that provides me with a standard interface to various forums.
// It provides a mouse interface, a full screen canvas, and some global often used variable
// like canvas, ctx, mouse, w, h (width and height), globalTime
// This code is not intended to be part of the answer unless specified and has been formated to reduce
// display size. It should not be used as an example of how to write a canvas interface.
// By Blindman67
const U = undefined;const RESIZE_DEBOUNCE_TIME = 100;
var canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0;
createCanvas = function () { var c,cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c;}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function"){ resizeCount += 1; setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);}
}
function debounceResize(){ resizeCount -= 1; if(resizeCount <= 0){ onResize();}}
setGlobals = function(){ cw = w = canvas.width; ch = h = canvas.height; mouse.updateBounds(); }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3],
active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}
e.preventDefault();
}
m.updateBounds = function(){
if(m.active){
m.bounds = m.element.getBoundingClientRect();
}
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
m.active = true;
m.updateBounds();
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
m.active = false;
}
}
return mouse;
})();
function main(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(main);
}
resizeCanvas();
mouse.start(canvas,true);
window.addEventListener("resize",resizeCanvas);
requestAnimationFrame(main);
body {
background:#49D;
}
.help {
text-align : center;
font-family : Arial,"Helvetica Neue",Helvetica,sans-serif;
font-size : 18px;
}
<div class="help">Right mouse to drop sand, left button to push it around.</div>
I'm brand new to JavaScript, and have a crashing application. I have no idea what would cause the crash.
Here is the code:
<script>
//constants
var Col = 20, Rows = 20;
var cellHeight = 25;
var cellWidth = 25;
var foodX;
var score;
var foodY;
var Nothing = 0, Snake = 1, Food = 2;
var Left = 37, Up = 38, Right = 39, Down = 40;
var canvas = document.getElementById('snakeCanvas');
var context = canvas.getContext('2d');
var dead = "false";
var snakeDirection = null;
var keystate;
var snake = [];
function start() //this is where we begin the long journey
{
init();
Tick();
}
function init() {
snake = [{ x: 5, y: 5 }];
snakeDirection = null;
score = 0;
document.getElementById("score").innerHTML = "Score: " + score;
setFood();
keystate = null;
}
function Tick() // just liker a timer tick
{
document.addEventListener("keydown", function (evt) {
keystate = evt.keyCode; // checks key presses
});
//document.addEventListener("keyup", function (evt) {
//delete keystate[evt.keyCode];
//});
update(); //after we check for a key press we update alllll the
stuff
setTimeout(Tick, 300);
//}
}
function update()
{
checkKey(); // checks what key has been pressed
for (var i = snake.length-1; i > 0; i--) {
snake[i].y = snake[i-1].y;
snake[i].x = snake[i-1].x
}
switch (snakeDirection) { // keys
case "DOWN":
snake[0].y++;
break;
case "UP":
snake[0].y--;
break;
case "RIGHT":
snake[0].x++;
break;
case "LEFT":
snake[0].x--;
break;
}
draw(); //draws all the stuff like food and snake
checkCollisions(); // self explaintory name
}
function checkKey() //Change the direction of the snake cant go
backwards too
{
if (keystate == Left && snakeDirection != "RIGHT" )
{
snakeDirection = "LEFT";
}
if (keystate == Up && snakeDirection != "DOWN")
{
snakeDirection = "UP";
}
if (keystate == Right && snakeDirection != "LEFT")
{
snakeDirection = "RIGHT";
}
if (keystate == Down && snakeDirection != "UP")
{
snakeDirection = "DOWN";
}
}
function setFood()
{ //WE ARE RUNNING OUT OF FOOD WE NEED NEW PROVISIONS
var next = "true"
do {
foodX = Math.floor((Math.random() * Rows));
foodY = Math.floor((Math.random() * Col));
for (var i = 0; i < snake.length; i++) { // IT SUCKS WHEN I
CANT EAT FOOD BECAUSE ITS ALREADY INSIDE OF ME
if (snake[i].x == foodX && snake[i].y == foodY) {
next = "false"
}
}
}
while (next == "false")
draw(); // Pretty pictures
}
function checkCollisions()
{
for (var i = 1; i < snake.length; i++) { // STOP hitting
yourself
if (snake[0].x == snake[i].x && snake[0].y == snake[i].y) {
init();
}
}
if (snake[0].y < 0 || snake[0].y > Rows || snake[0].x < 0 ||
snake[0].x > Col) // you are forbidon to veture from the canvas
{
init();
}
if (snake[0].x == foodX && snake[0].y == foodY) { //Yummy FOOD EAT
EAT EAT
score++;
document.getElementById("score").innerHTML = "Score: " + score;
setFood();
snake.push({ x: null, y: null }); // I got fatter
}
}
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height); // clears
canvas
context.fillStyle = "#FF0000"; // pretty colour for the head of
the snake
context.fillRect(snake[0].x * cellWidth, snake[0].y * cellWidth,
cellWidth, cellHeight);
context.fillStyle = "#09F";
for (var i = 1; i < snake.length; i++)
{
context.fillRect(snake[i].x * cellWidth, snake[i].y * cellWidth,
cellWidth, cellHeight);
}
context.fillStyle = "#F90"; // FOOD FOOD FOOD FOOD
context.fillRect(foodX * cellWidth, foodY * cellWidth, cellWidth,
cellHeight);
}
start(); // starts hence the name start
</script>
OK not bad for a beginer if you wrote it all your self.
Your problem is with the keydown event. You are creating a new handler each time you tick. This will lead to a crash. You only need to create the event handler once for the page, it will remain active until you leave the page.
To fix your problem move adding the keyDown listener to just above the function Start, as shown below.
var snake = [];
document.addEventListener("keydown", function (evt) {
keystate = evt.keyCode; // checks key presses
});
function start(){
init();
Tick();
}
Also just a because to me it looks weird. true and false are not strings you dont need to put quotes around them. Though using them as strings still works.
You have
function setFood() { //WE ARE RUNNING OUT OF FOOD WE NEED NEW PROVISIONS
var next = "true"
do {
foodX = Math.floor((Math.random() * Rows));
foodY = Math.floor((Math.random() * Col));
for (var i = 0; i < snake.length; i++) {
if (snake[i].x == foodX && snake[i].y == foodY) {
next = "false"
}
}
} while (next == "false")
draw();
}
would be better written as follows
function setFood() {
var next = true; // removed the qoutes
do {
foodX = Math.floor((Math.random() * Rows));
foodY = Math.floor((Math.random() * Col));
for (var i = 0; i < snake.length; i++) {
if (snake[i].x == foodX && snake[i].y == foodY) {
next = false; // removed the quotes.
// no point continuing the for loop as you know you need to
// reposition the food so use the break token
break; // breaks out of the closest loop
}
}
} while ( !next ) // removed next == "false" and replaced with
// ! next. "!" means "Not". do while next not true
// you have the draw here but you draw every tick so it would be best if
// you removed it as the next draw is less than 1/3 of a second away anyways
// draw(); // removed needless draw
}
Good work. Hope you get a good mark for it.
I am working with some classmates to make a word search game. It is pretty much done, but the only problem that we are facing is when the user decides to zoom in or out. When the user clicks and drags their cursor within the canvas, a red line highlights the letters under the cursor. After zooming, the highlighting appears somewhere other than under the mouse.
At first, we thought the problem was caused by window scrolling because the canvases were bigger than the screen, so we made them
You can recreate the problem by going here, zooming in, scrolling down a little bit, and trying to highlight a string of letters.
Please only include suggestions with Javascript: no JQuery, additional libraries, any other languages.
<html>
<head>
<title>Canvas Testing</title>
<script type="text/javascript">
var canvas,//canvas html tag
wordsCanvas,//displays words
wContext,//wordsCanvas context
context,//to edit canvas items
words = ["KING","HOMEWORK","BASEBALL","SIDEWALK","CUPCAKE","WHITEHOUSE","ISLAND","SOCCER","INDEPENDENCE","LOVE","CALCULUS","BEACH","SUMMER","PET","MICHIGAN","CANDY","WORLD","SIX","SNOW","SWEET"],//array of the words users must find
found = new Array(),
w,//width
h,//height
w1,//width of each letter (board 6 letters wide right now)
h1,//height of each letter (board 6 letters tall right now)
draw = false,//tells when the game should highlight a letter
letterPoints = new Array(),//holds the coordinates of each letter
lines = new Array(),//holds the coordinates of each line for a correct word highlighted
startLP = null,//holds the Letter object (letter, x, y) for the start of the line
endLP = null;//holds the Letter object (letter, x, y) for the end of the line
function init(){//initializes the canvas, context,w,h variables
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.height = window.innerHeight*600/630;
canvas.width = window.innerWidth*900/1380;
w=canvas.width;
h=canvas.height;
w1 = w/20;
h1 = h/20;
context.font = h1+"px Courier";
context.textAlign = "left";
context.textBaseline = "top";
wordsCanvas = document.getElementById("wordsCanvas");
wordsCanvas.height = window.innerHeight*600/630;
wordsCanvas.width = window.innerWidth*400/1380;
wContext = wordsCanvas.getContext("2d");
wContext.font = h1+"px Georgia";
wContext.textAlign = "left";
wContext.textBaseline = "top";
background();
fillWords(-1);
//add event listeners for mouse actions
canvas.addEventListener("mousedown", function(event) {
setLine("press",event);
});
canvas.addEventListener("mouseup", function(event) {
setLine("release",event);
});
canvas.addEventListener("mousemove", function(event) {
setLine("drag",event);
});
canvas.addEventListener("mouseout", function(event) {
setLine("out",event);
});
}
function fillWords(greenIndex){//displays words to search for
wContext.clearRect(0,0,300,h);
var index = 0;
if(greenIndex != -1)
{
found[found.length] = words.splice(greenIndex,1);
}
for(var i=0; i<h; i+=h1)
{
if(index<words.length)
{
wContext.fillStyle = "red";
wContext.fillText(words[index],10,i);
}
else
{
wContext.fillStyle = "green";
wContext.fillText(found[found.length-((h/h1)-index)],10,i);
}
index++;
}
wContext.fillStyle = "black";
}
function background(){//sets the background to the letters and then draws the lines that lay on correctly highlighted words words
var letterCount = 0;//counts # of letters on the board
//one string that represents all letters on the board
var backLets = "harinavesenanotheasp"+
"oobalremmusicwonsdpa"+
"momvtcalclsxvaiybaev"+
"swttysumvkingbddmrzd"+
"ahaeuyacemjtavpniche"+
"nivpnagihcimbseaceoy"+
"tthesteatkijeodchqmj"+
"ueesnodbsggmaccutsea"+
"mhmikalviabahceraqwm"+
"motislandbtcvetiwmoi"+
"eurwtenkeeeterntanra"+
"tsoolotewheewhthsekl"+
"sewroxvrarwdbaseball"+
"nehtvxjglmsadalkazqt"+
"odceetenkenstcvepcap"+
"ttaabvdlcupcakepeaxm"+
"rieqindependenceplia"+
"afbacucakehowrkdkisf"+
"chldlrowbrqmmuscqflg"+
"amerivegdsuluclacoev";
context.fillStyle = "black";
for(var y=0; y<h; y+=h1)//goes through the board and draws each letter, then stores their coordinates in the letterPoints array
{
for(var x=0; x<w; x+=w1)
{
r = y/h1 + 1;//tells the row that the letter is in
c = x/w1 + 1;//tells the column that the letter is in
temp = new Letter(backLets.charAt(letterCount),x,y,r,c);
context.fillText(temp.letter.toUpperCase(),temp.x,temp.y);
if(letterPoints.length<400)
letterPoints[letterPoints.length] = temp;
letterCount++;
}
}
/*go through lines array holding coordinates for lines that lay on correct words*/
for(var z=0; z<lines.length; z++)
{
//this if structure allows the words matching the list to be highlighted in different colors so adjacent words will not be highlighted into blocks
context.fillStyle = "lime";
var coords = lines[z];//elements of lines array are not empty but the drawLine isn't processing them
drawLine(coords[0],coords[1]);
}
}
function setLine(action, e){//sets the coordinates for the lines to be drawn
if(action == "press")
{
startLP = findNearestLP(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);//gets nearest coordenate to a click and returns a letter object with that info
if(startLP != null)
{
draw = true;
}
}
if(action == "drag")
{
//updates the last coordinates that the dragged mouse is on and draws a line to that point from the start
if(draw)
{
context.clearRect(0,0,w,h);
background();
endLP = findNearestLP(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
context.fillStyle = "red";
drawLine(startLP, endLP);
}
}
if(action == "release" || (action == "out" && draw))
{
draw = false;
/*If a correct word is highlighted, store the start and end coordinates
else clear*/
endLP = findNearestLP(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
//Get the letters that are highlighted by the line
if(endLP != null)
{
var word = getWord();//returns the word that was made from the start to end point by adding the characters that were highlighted
if(word != null && matchWords(word)/*This string will be replaced by a word from the list of word search targets*/)
{
lines[lines.length] = [startLP,endLP];
}
}
context.clearRect(0,0,w,h);//clears the board of any drawn lines
background();//if the line highlighted a word from the list, this method should redraw that line
if(words.length == 0)
alert("Congratulations! You win!");
}
}
//searches through the words array to see if the highlighted word is there
function matchWords(target){
if(words.indexOf(target.toUpperCase()) != -1)
{
fillWords(words.indexOf(target.toUpperCase()));
return true;
}
return false;
}
//uses coordinates from setLine() to draw the lines
function drawLine(start, end){
context.globalAlpha = 0.6;//sets transparency of lines
/*
Check up,down,left,right,diagonals
See if start and end can make a valid line
If yes, find the letters from start to end and store it as a word
*/
if(start.x == end.x && end.y>start.y)//checking down
{
context.fillRect(start.x,start.y,w1,(end.y+h1)-start.y);
}
else if(start.x == end.x && end.y<start.y)//checking up
{
context.fillRect(end.x,end.y,w1,(start.y+h1)-end.y);
}
else if(start.y == end.y && end.x>start.x)//checking left to right
{
context.fillRect(start.x,start.y,(end.x+w1)-start.x,h1);
}
else if(start.y == end.y && end.x<start.x)//checking right to left
{
context.fillRect(end.x,end.y,(start.x+w1)-end.x,h1);
}
else if(start.y > end.y && start.x < end.x && ((start.r-end.r)/(end.c-start.c))==1)//checking left to right diagonal (down to up)
{
for(var z = letterPoints.length-1; z>=0; z--)
{
var temp = letterPoints[z];
if((start.x<=temp.x && end.x>=temp.x && start.y>=temp.y && end.y<=temp.y) && ((start.y-temp.y==0 && temp.x-start.x==0) || ((start.r-temp.r)/(temp.c-start.c))==1))//((start.r-temp.r)/(temp.c-start.c))==1 means if the slope of between two points is 1
{
context.fillRect(temp.x,temp.y,w1,h1);
}
}
}
else if(start.y < end.y && start.x < end.x && ((end.r-start.r)/(end.c-start.c))==1)//checking left to right diagonal (top to bottom)
{
for(var z = 0; z<letterPoints.length; z++)
{
var temp = letterPoints[z];
if((start.x<=temp.x && end.x>=temp.x && start.y<=temp.y && end.y>=temp.y) && ((start.y-temp.y==0 && temp.x-start.x==0) || ((temp.r-start.r)/(temp.c-start.c))==1))//((temp.r-start.r)/(temp.c-start.c))==1 means if the slope of between two points is 1
{
context.fillRect(temp.x,temp.y,w1,h1);
}
}
}
else if(start.y > end.y && start.x > end.x && ((start.r-end.r)/(start.c-end.c))==1)//checking right to left diagonal (down to up)
{
for(var z = letterPoints.length-1; z>=0; z--)
{
var temp = letterPoints[z];
if((start.x>=temp.x && end.x<=temp.x && start.y>=temp.y && end.y<=temp.y) && ((start.y-temp.y==0 && start.x-temp.x==0) || ((start.r-temp.r)/(start.c-temp.c))==1))
{
context.fillRect(temp.x,temp.y,w1,h1);
}
}
}
else if(start.y < end.y && start.x > end.x && ((end.r-start.r)/(start.c-end.c))==1)//checking right diagonal (top to bottom)
{
for(var z = 0; z<letterPoints.length; z++)
{
var temp = letterPoints[z];
if((start.x>=temp.x && end.x<=temp.x && start.y<=temp.y && end.y>=temp.y) && ((start.y-temp.y==0 && start.x-temp.x==0) || ((temp.r-start.r)/(start.c-temp.c))==1))
{
context.fillRect(temp.x,temp.y,w1,h1);
}
}
}
context.globalAlpha = 1.0;//sets transparency back to 1
}
function findNearestLP(clickX,clickY){//finds the nearest letter coordinate from the user's click
for(var z = 0; z<letterPoints.length; z++)
{
var lp = letterPoints[z];
if((clickX<=lp.x+w1 && clickX>=lp.x) && (clickY<=lp.y+h1 && clickY>=lp.y))
{
return letterPoints[z];
}
}
return null;
}
function getWord()
{
var result = "";
/*
Check up,down,left,right,diagonals
See if startLP and endLP can make a valid line
If yes, find the letters from start to end and store it as a word
*/
if(startLP.x == endLP.x && endLP.y>startLP.y)//checking down
{
for(var z = 0; z<letterPoints.length; z++)
{
var temp = letterPoints[z];
if(temp.x == startLP.x && temp.y>=startLP.y && temp.y<=endLP.y)
{
result += temp.letter;
}
}
}
else if(startLP.x == endLP.x && endLP.y<startLP.y)//checking up
{
for(var z = letterPoints.length-1; z>=0; z--)
{
var temp = letterPoints[z];
if(temp.x == startLP.x && temp.y<=startLP.y && temp.y>=endLP.y)
{
result += temp.letter;
}
}
}
else if(startLP.y == endLP.y && endLP.x>startLP.x)//checking left to right
{
for(var z = 0; z<letterPoints.length; z++)
{
var temp = letterPoints[z];
if(temp.y == startLP.y && temp.x>=startLP.x && temp.x<=endLP.x)
{
result += temp.letter;
}
}
}
else if(startLP.y == endLP.y && endLP.x<startLP.x)//checking right to left
{
for(var z = letterPoints.length-1; z>=0; z--)
{
var temp = letterPoints[z];
if(temp.y == startLP.y && temp.x<=startLP.x && temp.x>=endLP.x)
{
result += temp.letter;
}
}
}
else if(startLP.y > endLP.y && startLP.x < endLP.x && ((startLP.r-endLP.r)/(endLP.c-startLP.c))==1)//checking left to right diagonal (down to up)
{
for(var z = letterPoints.length-1; z>=0; z--)
{
var temp = letterPoints[z];
if((startLP.x<=temp.x && endLP.x>=temp.x && startLP.y>=temp.y && endLP.y<=temp.y) && ((startLP.y-temp.y==0 && temp.x-startLP.x==0) || ((startLP.r-temp.r)/(temp.c-startLP.c))==1))
{
result += temp.letter;
}
}
}
else if(startLP.y < endLP.y && startLP.x < endLP.x && ((endLP.r-startLP.r)/(endLP.c-startLP.c))==1)//checking left to right diagonal (top to bottom)
{
for(var z = 0; z<letterPoints.length; z++)
{
var temp = letterPoints[z];
if((startLP.x<=temp.x && endLP.x>=temp.x && startLP.y<=temp.y && endLP.y>=temp.y) && ((startLP.y-temp.y==0 && temp.x-startLP.x==0) || ((temp.r-startLP.r)/(temp.c-startLP.c))==1))
{
result += temp.letter;
}
}
}
else if(startLP.y > endLP.y && startLP.x > endLP.x && ((startLP.r-endLP.r)/(startLP.c-endLP.c))==1)//checking right to left diagonal (down to up)
{
for(var z = letterPoints.length-1; z>=0; z--)
{
var temp = letterPoints[z];
if((startLP.x>=temp.x && endLP.x<=temp.x && startLP.y>=temp.y && endLP.y<=temp.y) && ((startLP.y-temp.y==0 && startLP.x-temp.x==0) || ((startLP.r-temp.r)/(startLP.c-temp.c))==1))
{
result += temp.letter;
}
}
}
else if(startLP.y < endLP.y && startLP.x > endLP.x && ((endLP.r-startLP.r)/(startLP.c-endLP.c))==1)//checking right diagonal (top to bottom)
{
for(var z = 0; z<letterPoints.length; z++)
{
var temp = letterPoints[z];
if((startLP.x>=temp.x && endLP.x<=temp.x && startLP.y<=temp.y && endLP.y>=temp.y) && ((startLP.y-temp.y==0 && startLP.x-temp.x==0) || ((temp.r-startLP.r)/(startLP.c-temp.c))==1))
{
result += temp.letter;
}
}
}
if(result != "")
return result;
return null;
}
//letter class
function Letter(letter,x,y){
//The letter variable is mainly used for getting the highlighted word
this.letter = letter.charAt(0);
//the x and y coordinate variables are used for drawing the highlighting line and getting the letter variable at certain coordinates
this.x = x;
this.y = y;
//the r and c variables are used to keep track of which rows and columns the letters are on. This is very helpful for drawing the highlighting line on a diagonal
this.r = r;
this.c = c;
}
</script>
</head>
<body onload="init()">
<canvas id="canvas" style="border: 1px solid #000000;"></canvas>
<canvas id="wordsCanvas" style="border: none;"></canvas>
</body>
By using only offsetLeft and offsetTop inside setLine, you do not take into account the scrolling.
Killer weapon here is getBoundingClientRect, that will give you relative position of the canvas taking every aspect into account :
function setLine(action, e){//sets the coordinates for the lines to be drawn
var bRect = canvas.getBoundingClientRect();
var relX = e.clientX - bRect.left ;
var relY = e.clientY - bRect.top;
if(action == "press")
{
startLP = findNearestLP(relX, relY);//gets nearest coordenate to a click and returns a letter object with that info
if(startLP != null)
{
draw = true;
}
}
if(action == "drag")
{
//updates the last coordinates that the dragged mouse is on and draws a line to that point from the start
if(draw)
{
context.clearRect(0,0,w,h);
background();
endLP = findNearestLP(relX, relY);
context.fillStyle = "red";
drawLine(startLP, endLP);
}
}
if(action == "release" || (action == "out" && draw))
{
draw = false;
/*If a correct word is highlighted, store the start and end coordinates
else clear*/
endLP = findNearestLP(relX, relY);
//Get the letters that are highlighted by the line
if(endLP != null)
{
var word = getWord();//returns the word that was made from the start to end point by adding the characters that were highlighted
if(word != null && matchWords(word)/*This string will be replaced by a word from the list of word search targets*/)
{
lines[lines.length] = [startLP,endLP];
}
}
context.clearRect(0,0,w,h);//clears the board of any drawn lines
background();//if the line highlighted a word from the list, this method should redraw that line
if(words.length == 0)
alert("Congratulations! You win!");
}
}
I'm really new to Javascript, so I decided to create a simple SnakeGame to embed in HTML. However, my code for changing the snake's direction freezes up after a few turns.
Note: I'm running this in an HTML Canvas.
Source:
var Canvas;
var ctx;
var fps = 60;
var x = 0;
var seconds = 0;
var lastLoop;
var thisLoop;
var tempFPS = 0;
var blockList = [];
var DEFAULT_DIRECTION = "Right";
var pendingDirections = [];
function update() {
x += 1;
thisLoop = new Date();
tempFPS = 1000 / (thisLoop - lastLoop);
lastLoop = thisLoop;
tempFPS = Math.round(tempFPS*10)/10;
if (x==10){
document.getElementById("FPS").innerHTML = ("FPS: " + tempFPS);
}
//Rendering
for (var i = 0; i<blockList.length; i++){
var block = blockList[i];
draw(block.x, block.y);
}
if (x==5){
x=0;
seconds+=1;
//Updates once per x frames
moveBlocks();
}
}
function moveBlocks(){
if(blockList.length === 0){
return;
}
for (var j = 0; j<pendingDirections.length; j++){
if (b >= blockList.length -1){
pendingDirections.shift();
}else {
//Iterates through each direction that is pending
var b = pendingDirections[j].block;
try{
blockList[b].direction = pendingDirections[j].direction;
} catch(err){
alert(err);
}
pendingDirections[j].block++;
}
}
for (var i = 0; i<blockList.length; i++){
var block = blockList[i];
clear(block.x, block.y);
if (block.direction == "Down"){
block.y += BLOCK_SIZE;
} else if (block.direction == "Up"){
block.y -= BLOCK_SIZE;
} else if (block.direction == "Left"){
block.x -= BLOCK_SIZE;
} else if (block.direction == "Right"){
block.x += BLOCK_SIZE;
} else {
alert(block.direction);
}
draw(block.x, block.y);
}
}
function init(){
lastLoop = new Date();
window.setInterval(update, 1000/fps);
Canvas = document.getElementById("Canvas");
ctx = Canvas.getContext("2d");
}
//The width/height of each block
var BLOCK_SIZE = 30;
//Draws a block
function draw(x, y) {
ctx.fillStyle = "#000000";
ctx.fillRect(x,y,BLOCK_SIZE,BLOCK_SIZE);
}
function clear(x,y){
ctx.fillStyle = "#FFFFFF";
ctx.fillRect(x,y,BLOCK_SIZE,BLOCK_SIZE);
}
function processInput(key){
if (key == 110){
//n (new)
newBlock(BLOCK_SIZE*4,0);
newBlock(BLOCK_SIZE*3,0);
newBlock(BLOCK_SIZE*2,0);
newBlock(BLOCK_SIZE*1,0);
newBlock(0,0);
} else if (key == 119){
changeDirection("Up");
} else if (key == 115){
changeDirection("Down");
} else if (key == 97){
changeDirection("Left");
} else if (key == 100){
changeDirection("Right");
} else if (key==122){
var pDir = "Pending Directions: ";
for (var i = 0; i<pendingDirections.length; i++){
pDir += pendingDirections[i].direction + ", ";
}
alert(pDir);
} else if (key == 120){
var dir = "Directions: ";
for (var j = 0; j<blockList.length; j++){
dir += blockList[j].direction + ", ";
}
alert(dir);
} else {
alert("KEY: " +key);
}
}
function changeDirection(d){
var LD = blockList[0].direction;
var valid = false;
if (d == "Up"){
if(LD != "Down"){
valid = true;
}
} else if (d == "Down"){
if(LD != "Up"){
valid = true;
}
} else if (d == "Left"){
if(LD != "Right"){
valid = true;
}
} else if (d == "Right"){
if(LD != "Left"){
valid = true;
}
}
if (d == LD) { valid = false;}
if (valid){
var dir = {'direction' : d, 'block' : 0};
pendingDirections.unshift(dir);
}
}
function newBlock(x, y){
var block = {'x': x, 'y' : y, 'direction' : DEFAULT_DIRECTION};
//This works: alert(block['x']);
draw(x,y);
blockList.push(block);
}
Thanks
As Evan said, the main issue is how you are handling pending directions.
The issue occurs when you turn twice in rapid succession, which causes two pending directions to be added for the same block. If these aren't handled in the correct order, then the blocks may move in the wrong direction. On every update, only one pending direction for each block is needed, so I redesigned how this is handled to avoid multiple directions on one block during a single update.
Here is the link to it: http://jsbin.com/EkOSOre/5/edit
Notice, when a change in direction is made, the pending direction on the first block is updated, overwriting any existing pending direction.
if (valid) {
blockList[0].pendingDirection = direction;
}
Then, when an update occurs, the list of blocks is looped through, and the pending direction of the next block is set to be the current direction of the current block.
if(!!nextBlock) {
nextBlock.pendingDirection = block.direction;
}
If the current block has a pending direction, set the direction to the pending direction.
if(block.pendingDirection !== null) {
block.direction = block.pendingDirection;
}
Then update the block locations like normal.
You also had various other issues, such as using a variable (b) before it was initialized, and how you caught the null/undefined error (you should just do a check for that situation and handle it appropriately), but this was the main issue with your algorithm.
You'll also want to remove the old blocks when the user hits 'n', because the old one is left, increasing the speed and number of total blocks present.
Good luck with the rest of the game, and good luck learning JavaScript.