How to add dash function with vectors - javascript

In my 2D (top-down) game, I am attempting to add a dash function.
Using an if (keyWentDown("e")) {} condition inside a function named dash. I have a thing set up to face the player's direction, being: plrvector.rotation = 90, and I want my character to move about 50px smoothly when E is pressed.
Problem is, I have no clue how to use vectors. Any tips or directions? I have attempted to use various techniques; however, I could not find any tutorials or anything that could help me.

I am not sure you want to move the player exactly 50px. You would just temporarily increase the velocity by a factor. In the example below, if the E key is pressed, a "turbo" mode is activate increasing the speed by a factor of 3.
You would have to do something along the lines of:
function update(progress) {
const { player } = state;
const turbo = state.pressedKeys.turbo ? 3 : 1; // x3 speed (E)
if (state.pressedKeys.left) {
if (player.speed.x > 0) player.speed.x *= -1;
player.position.x += progress * player.speed.x * turbo;
}
}
Note: This can be modified to only allow turbo for a short duration. For instance, the player may have a turbo meter that they need to fill in order to use this mode.
The following example is adapted from: "Quick Tip: How to Make a Game Loop in JavaScript". I did not utilize vectors pre se, but I store the position and velocity (speed) in a vector-like object. If you want to convert this to vectors, you can check out victor.js. It can easily add/multiply/normalize vectors for you.
// Canvas context reference
const ctx = document.querySelector('#game').getContext('2d');
const hud = document.querySelector('#hud');
// Set the canvas size
Object.assign(ctx.canvas, { width: 600, height: 160 });
// Game state
const state = {
player: {
dimensions: { depth: 10, height: 10, width: 10 },
position: { x: Math.floor(ctx.canvas.width / 2), y: Math.floor(ctx.canvas.height / 2) },
speed: { x: 0.25, y: 0.25 }
},
pressedKeys: {
left: false,
right: false,
turbo: false,
up: false,
down: false
}
};
// Track keys
const keyMap = new Map(Object.entries({
ArrowDown: 'down',
ArrowLeft: 'left',
ArrowRight: 'right',
ArrowUp: 'up',
a: 'left',
d: 'right',
e: 'turbo',
s: 'down',
w: 'up',
}));
// Game Loop ~ Update
function update(progress) {
const { player } = state;
hud.innerText = Object.entries(state.pressedKeys)
.filter(([k, v]) => v)
.map(([k]) => `<${k}>`)
.sort()
.join(' ');
const turbo = state.pressedKeys.turbo ? 3 : 1; // x3 speed (E)
// Check pressed keys to update position
if (state.pressedKeys.left) {
if (player.speed.x > 0) player.speed.x *= -1;
player.position.x += progress * player.speed.x * turbo;
}
if (state.pressedKeys.right) {
if (player.speed.x < 0) player.speed.x *= -1;
player.position.x += progress * player.speed.x * turbo;
}
if (state.pressedKeys.up) {
if (player.speed.y > 0) player.speed.y *= -1;
player.position.y += progress * player.speed.y * turbo;
}
if (state.pressedKeys.down) {
if (player.speed.y < 0) player.speed.y *= -1;
player.position.y += progress * player.speed.y * turbo;
}
// Check bounds
const threshold = player.dimensions.width;
if (player.position.x > ctx.canvas.width - threshold) {
player.position.x = ctx.canvas.width - threshold;
} else if (player.position.x < threshold) {
player.position.x = threshold;
}
if (player.position.y > ctx.canvas.height - threshold) {
player.position.y = ctx.canvas.height - threshold;
} else if (player.position.y < threshold) {
player.position.y = threshold;
}
}
// Game Loop ~ Draw
function draw() {
const { player, pressedKeys } = state;
ctx.fillStyle = 'black';
ctx.beginPath();
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(player.position.x, player.position.y, player.dimensions.width, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
// Game Loop ~ Main
function loop(timestamp) {
const progress = timestamp - lastRender;
update(progress);
draw();
lastRender = timestamp;
window.requestAnimationFrame(loop);
}
// Begin game
let lastRender = 0;
window.requestAnimationFrame(loop);
// Register keyboard events
window.addEventListener('keydown', onKeyDown, false);
window.addEventListener('keyup', onKeyUp, false);
// Event handlers
function onKeyDown({ key }) {
toggleKey(key, true);
}
function onKeyUp({ key }) {
toggleKey(key, false);
}
// Convenience
function toggleKey(key, value) {
const index = keyMap.get(key);
if (index) {
const currentValue = state.pressedKeys[index];
state.pressedKeys[index] = value ?? !currentValue;
}
}
*, *::before,*::after { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; padding: 0; }
body {
display: flex; flex-direction: column;
align-items: center; justify-content: flex-start; gap: 0.25em;
padding: 0.25em;
}
#hud { white-space: pre; text-transform: uppercase; }
<!-- See: https://www.sitepoint.com/quick-tip-game-loop-in-javascript/ -->
<canvas id="game"></canvas>
<div id="hud"></div>
Vector math
Here is an example of adding/multiplying vectors:
Add t and u to get { x: 4, y: 7 }
Multiply the result above by t to get { x: 4, y: 14 }
const vectorAdd = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
const vectorMultiply = (a, b) => ({ x: a.x * b.x, y: a.y * b.y });
let t = { x: 1, y: 2 }, u = { x: 3, y: 5 }, v = vectorMultiply(vectorAdd(t, u), t);
console.log(v); // { x: 4, y: 14 }
If you want to chain these calls, you can try creating a wrapper:
const vectorAdd = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
const vectorMultiply = (a, b) => ({ x: a.x * b.x, y: a.y * b.y });
const vector = function({ x, y }) {
[this.x, this.y] = [x, y];
this.add = (other) => {
const { x, y } = vectorAdd(this, other);
[this.x, this.y] = [x, y];
return this;
};
this.multiply = (other) => {
const { x, y } = vectorMultiply(this, other);
[this.x, this.y] = [x, y];
return this;
};
this.value = () => ({ x: this.x, y: this.y });
return this;
}
let t = { x: 1, y: 2 }, u = { x: 3, y: 5 }, v = vector(t).add(u).multiply(t).value();
console.log(v); // { x: 4, y: 14 }

Related

How to draw a rubberband or lasso to make a selection in word selection game

I am trying to put a word search game on my web page.
I see here is a beautiful example: Embed a Word Search in Your Site
I want to make similar gui, so that when the user selects the word letters from the grid by dragging mouse, it will draw a red elastic rubber band around the letters selected.
How do they do it on their web page? I tried to "inspect" the html after the band is drawn, however did not see any element added.
Can somebody give me any clue?
Here is a JS example
class App {
constructor(div) {
this.div = div;
this.canvas = document.getElementById("canvas");
this.ctx = canvas.getContext('2d');
this.dirty = true;
this.prev = +new Date();
this.resize();
window.addEventListener('resize', () => this.resize(), false);
this.canvas.addEventListener("mousedown", (event) => {
this.touchdown(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("mousemove", (event) => {
this.touchmove(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("mouseup", (event) => {
this.touchup(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchstart", (event) => {
this.touchdown(...App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchmove", (event) => {
this.touchmove(App.getmousePos(event));
event.preventDefault();
});
this.canvas.addEventListener("touchend", (event) => {
this.touchup(App.getmousePos(event));
event.preventDefault();
});
}
resize() {
this.canvas.width = this.div.clientWidth;
this.canvas.height = this.div.clientWidth;
this.draw();
}
loop() {
let now = +new Date();
let dt = now - this.prev;
this.prev = now;
this.update()
if (this.dirty)
this.draw();
window.requestAnimationFrame(() => this.loop());
}
update(dt) {}
draw() {
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.ctx.rect(0, 0, 100, 100);
this.ctx.fill();
}
touchdown(x, y) { console.log("down", x, y); }
touchmove(x, y) {}
touchup(x, y) {}
static getmousePos(event) {
if (event.changedTouches) {
return [event.changedTouches[0].pageX, event.changedTouches[0].pageY];
}
else {
var rect = event.target.getBoundingClientRect();
return [event.clientX- rect.left, event.clientY - rect.top];
}
}
}
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
add(v_x, y) {
if (y != undefined) {
return new Vector(this.x + v_x, this.y + y);
}
else {
return new Vector(this.x + v_x.x, this.y + v_x.y);
}
}
sub(v_x, y) {
if (y != undefined) {
return new Vector(this.x - v_x, this.y - y);
}
else {
return new Vector(this.x - v_x.x, this.y - v_x.y);
}
}
mul(s) {
return new Vector(this.x * s, this.y * s);
}
get slength() {
return this.x * this.x + this.y * this.y;
}
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
get angle() {
return Math.atan2(this.y, this.x);
}
}
class Grid {
constructor(width, height, data) {
this.width = width;
this.height = height;
this.data = data || new Array(width*height).fill("_");
}
get length() {
return this.width*this.height;
}
get(x_pos, y) {
if (y != undefined)
return this.data[x_pos+y*this.width];
else
return this.data[x_pos];
}
set(value, x_pos, y) {
if (y != undefined)
this.data[x_pos+y*this.width] = value;
else
this.data[x_pos] = value;
}
clone() {
return new Grid(this.width, this.height, this.data.slice());
}
finalize() {
this.data = this.data.map(v => v == "_" ? String.fromCharCode(Math.floor(Math.random() * 26) + 97) : v);
}
print() {
for (let i = 0; i < this.height; i++)
console.log(this.data.slice(i*this.width, (i+1)*this.width).join(","));
}
}
class Puzzle {
constructor(width, height, words, directions) {
this.grid = new Grid(width, height);
this.words = words;
this.placeWords(words, directions);
}
placeWords(words, directions) {
const range = (N) => Array.from({length: N}, (v, k) => k);
const shuffle = (array) => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
words = words.slice();
let positions = range(this.grid.length);
let stack = [ {
grid: this.grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice()) } ];
while (true) {
let current = stack[stack.length-1];
if (!current)
throw Error("impossible");
let dir = current.directions.pop();
if (!dir) {
current.positions.pop();
current.directions = shuffle(directions.slice());
dir = current.directions.pop();
}
if (current.positions.length <= 0) {
words.unshift(current.word);
stack.pop();
}
else {
let pos = current.positions[current.positions.length-1];
let grid = this.placeWord(current.grid, current.word, pos, dir);
if (grid) {
if (words.length > 0) {
stack.push({grid: grid,
word: words.shift(),
directions: shuffle(directions.slice()),
positions: shuffle(positions.slice())});
}
else {
grid.finalize();
this.grid = grid;
break;
}
}
}
}
}
placeWord(grid, word, position, direction) {
let copy = grid.clone();
position = new Vector(position % grid.width, Math.floor(position / grid.width));
let letters = [...word];
while (0 <= position.x && position.x < grid.width && 0 <= position.y && position.y < grid.height) {
if (letters.length <= 0)
break;
let letter = letters.shift();
if (copy.get(position.x, position.y) == "_" || copy.get(position.x, position.y) == letter) {
copy.set(letter, position.x, position.y);
position = position.add(direction);
}
else {
return null;
}
}
if (letters.length > 0)
return null;
else
return copy;
}
wordAt(position, direction, length) {
let word = new Array(length);
for (let i = 0; i < length; i++) {
word[i] = this.grid.get(position.x, position.y);
position = position.add(direction);
}
return word.join("");
}
print() {
this.grid.print();
}
}
class Selection {
constructor(position, fill=false) {
this.position = position;
this._fill = fill;
this.direction = new Vector(1, 0);
this.length = 0;
this.flength = 0;
}
clone() {
return new Selection(this.position).to(this.position.add(this.direction.mul(this.length)));
}
to(position) {
let direction = position.sub(this.position);
if (Math.abs(direction.y) == 0) {
this.direction = new Vector(direction.x >= 0 ? 1 : -1, 0);
this.length = direction.x * this.direction.x;
}
else if (Math.abs(direction.x) == 0) {
this.direction = new Vector(0, direction.y >= 0 ? 1 : -1);
this.length = direction.y * this.direction.y;
}
else {
this.direction = new Vector(direction.x >= 0 ? 1 : -1, direction.y >= 0 ? 1 : -1);
this.length = direction.x * this.direction.x;
}
this.flength = direction.length;
return this;
}
fill(fill=true) {
this._fill = fill;
return this;
}
draw(ctx, wsize, hsize) {
let pos = this.position.mul(wsize);
let length = wsize * (this.flength+1);
let angle = this.direction.angle;
//console.log(this.x, this.y, x, y, length, angle, this.dx, this.dy);
ctx.save();
ctx.strokeStyle = "black";
ctx.fillStyle = "yellow";
ctx.translate(pos.x+hsize*0.5, pos.y+hsize*0.5);
ctx.rotate(angle);
this.drawHighlight(ctx, -hsize*0.5, -hsize*0.5, length, hsize, hsize*0.5, this._fill, true);
ctx.restore();
}
drawHighlight(ctx, x, y, width, height, radius=20, fill=true, stroke=false) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}
}
class PuzzleApp extends App {
constructor(div, puzzle) {
super(div);
this.puzzle = puzzle;
this.renderList(document.getElementById("list"));
this.selections = new Array();
this.loop();
}
renderList(parent) {
this.puzzle.words.forEach(word => {
let li = document.createElement("li");
let text = document.createTextNode(word);
li.appendChild(text);
parent.appendChild(li);
});
}
gridSize() {
let wsize = Math.floor(this.canvas.width/this.puzzle.grid.width);
let hsize = Math.floor(this.canvas.width/this.puzzle.grid.height);
return [wsize, hsize];
}
clientToGrid(x, y) {
let [wsize, hsize] = this.gridSize();
x = Math.floor(x / wsize);
y = Math.floor(y / hsize);
return [x, y];
}
draw() {
if (!this.puzzle)
return;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
let [wsize, hsize] = this.gridSize();
this.selections.forEach(s => s.draw(this.ctx, wsize, hsize));
if (this.selection)
this.selection.draw(this.ctx, wsize, hsize);
let x = 0;
let y = 0;
this.ctx.fillStyle = "black";
this.ctx.font = (wsize * 0.5) + 'px sans-serif';
this.ctx.textAlign = "center";
this.ctx.textBaseline = "middle";
for (let j = 0; j < this.puzzle.grid.height; j++) {
for (let i = 0; i < this.puzzle.grid.width; i++) {
let letter = this.puzzle.grid.get(i, j);
this.ctx.fillText(letter, x+wsize * 0.5, y+wsize * 0.5);
x += wsize;
}
x = 0;
y += wsize;
}
}
touchdown(x, y) {
[x, y] = this.clientToGrid(x, y);
this.selection = new Selection(new Vector(x, y));
this.dirty = true;
}
touchmove(x, y) {
if (!this.selection)
return;
[x, y] = this.clientToGrid(x, y);
this.selection.to(new Vector(x, y));
}
touchup(x, y) {
if (!this.selection)
return;
let word = this.puzzle.wordAt(this.selection.position, this.selection.direction, this.selection.length+1);
console.log(word);
if (word) {
let list = document.getElementById("list");
let elements = list.getElementsByTagName("li");
Array.prototype.some.call(elements, li => {
if (li.innerText == word) {
li.classList.add("found");
this.selections.push(this.selection.clone().fill());
return true;
}
return false;
});
}
this.selection = null;
}
}
let app = null;
document.addEventListener("DOMContentLoaded", function(event) {
const wordLists = [
["pear", "apple", "banana", "peach", "kiwi", "prune", "persimon"]
]
const pick = (array) => array[Math.floor(Math.random() * (array.length))];
let params = (new URL(document.location)).searchParams;
let directions = [new Vector(1,0), new Vector(0,1)];
if (params.get("diagonal")) {
directions.push(new Vector(1,1));
}
if (params.get("backwards")) {
directions.push(new Vector(-1,0));
directions.push(new Vector(0,-1));
if (params.get("diagonal")) {
directions.push(new Vector(-1,-1));
}
}
let puzzle = new Puzzle(8, 8, pick(wordLists), directions);
puzzle.print();
app = new PuzzleApp(document.getElementById("canvasContainer"), puzzle);
});
html, body {
width: 100%;
height: 100%;
margin: 0px;
}
ul {
display: inline-block;
font-size: 7vmin;
padding-left: 14vmin;
}
li.found {
text-decoration: line-through;
}
div#canvasContainer {
width: 100%;
display: inline-block;
}
#media (min-aspect-ratio: 1/1) {
ul {
font-size: 4vmin;
padding-left: 7vmin;
}
div#canvasContainer {
width: 50%;
}
}
<div id="canvasContainer"><canvas id="canvas" width="320" height="320"></canvas></div>
<ul id="list"></ul>
I'll let you figure out how to change the thickness and color of the rubber band.
The red band is an svg. It doesn't get added when you click, but rather it gets redrawn.
I have inspected the referenced website and it seems like they draw rubber band thing in SVG. If you inspect this SVG tag while drawing:
you will notice it shows some updates.
You can use same SVG methodology. Or Another way is to use an absolute positioned div and update top, left css values based on mousedown, width based on mousemove event.
I have seen "what" they have done.
They have created an svg element. This svg sits exactly above and covers the entire table that contains the grid with letters. When the user clicks any letter and starts drag, they add a rectangle to the svg. Depending on how the cursor will move they do the angle transform to the rectangle. If the word is selected correctly, the rectangle survives; else, it is removed.
I need to find out now, "how" part of it.
Thanks to all for a very quick response.

Rendering to HTML canvas in recursive calls

I have implemented a recursive maze generation algorithm in Javascript, and am rendering using HTML canvas.
The problem I have, is that I am unable to render to the canvas while the recursive function is in progress. The render only appears after recursive calls have finished.
Here is my (rough) functioning recursive function :
function start(canvas_context)
{
//do some stuff here...
render(canvas_context);
if()...{
start(canvas_context);
}
else if(...)
{
if(...)
{
start(canvas_context);
}
}
}
Here is my rendering function:
function render(canvas_context)
{
canvas_context.fillStyle = 'black';
canvas_context.fillRect(0,0,canvas.width, canvas.height);
for(..)
{
for(...)
{
canvas_context.strokeStyle = 'red';
canvas_context.beginPath();
canvas_context.moveTo(start.x, start.y);
canvas_context.lineTo(end.x,end.y);
canvas_context.stroke();
}
}
}
Here are the full functions if they are needed:
function start(canvas_context)
{
//Vector2.print(game_config["current_grid_pos"]);
var current_block = game_config["grid_space"][game_config["current_grid_pos"].y][game_config["current_grid_pos"].x];
var random_neighbor_grid_pos = random_neighbor(current_block);
//render(canvas_context);
if(random_neighbor_grid_pos.x != -1 && random_neighbor_grid_pos.y != -1)
{
var next_block = game_config["grid_space"][random_neighbor_grid_pos.y][random_neighbor_grid_pos.x];
next_block.visited = true;
game_config["visited_stack"].push(random_neighbor_grid_pos);
remove_walls(current_block, next_block);
game_config["current_grid_pos"] = random_neighbor_grid_pos;
start(canvas_context);
}
else if (game_config["visited_stack"].length > 0)
{
current_block.visited = true;
game_config["visited_stack"].pop();
if(game_config["visited_stack"].length != 0)
{
game_config["current_grid_pos"] = game_config["visited_stack"][game_config["visited_stack"].length - 1];
start(canvas_context);
}
}
}
function render(canvas_context)
{
//Initialise the canvas background.
canvas_context.fillStyle = 'black';
canvas_context.fillRect(0,0,canvas.width, canvas.height);
for(var y = 0; y < game_config["grid_space"].length; y++)
{
for(var x = 0; x < game_config["grid_space"][0].length; x++)
{
var block = game_config["grid_space"][y][x];
var walls = block.get_walls();
for(var key in walls)
{
var wall = walls[key];
render_line(canvas_context, wall.start, wall.end);
}
}
}
}
function render_line(canvas_context, start, end)
{
canvas_context.strokeStyle = 'red';
canvas_context.beginPath();
canvas_context.moveTo(start.x, start.y);
canvas_context.lineTo(end.x,end.y);
canvas_context.stroke();
}
Is it even possible to render during recursive calls with HTML canvas?
I think you can do it without a recursion but I show both ways:
const cellsise = 10;
class block {
constructor (x, y){
this.x = x;
this. y = y;
this.delta = {x: x * cellsise , y: y * cellsise};
this.walls = {
left: {
start: {
x: 0 + this.delta.x,
y: 0 + this.delta.y
},
end: {
x: 0 + this.delta.x,
y: cellsise + this.delta.y
}
},
top: {
start: {
x: 0 + this.delta.x,
y: 0 + this.delta.y
},
end: {
x: cellsise + this.delta.x,
y: 0 + this.delta.y
}
},
right: {
start: {
x: cellsise + this.delta.x,
y: 0 + this.delta.y
},
end: {
x: cellsise + this.delta.x,
y: cellsise + this.delta.y
}
},
bottom: {
start: {
x: 0 + this.delta.x,
y: cellsise + this.delta.y
},
end: {
x: cellsise + this.delta.x,
y: cellsise + this.delta.y
}
}
};
}
get_walls(){
return this.walls;
}
}
const game_config = {
grid_space:[],
height: 10,
width: 10,
}
function generateGridSpace() {
game_config.grid_space=[];
for(y = 0 ; y < game_config.height; ++y){
game_config.grid_space.push([]);
for(x = 0 ; x < game_config.width; ++x){
game_config.grid_space[y].push(new block(x, y))
}
}
}
function remove_walls(current_block){
let left = Math.random() > 0.5;
let top = !left //Math.random() > 0.5;
let right = Math.random() > 0.5;
let bottom = !right //false// Math.random() > 0.5;
if (left && current_block.x - 1 > 0) {
delete current_block.walls.left;
delete game_config.grid_space[current_block.x-1][current_block.y].walls.right
}
if (top && current_block.y - 1 > 0) {
delete current_block.walls.top;
delete game_config.grid_space[current_block.x][current_block.y-1].walls.bottom;
}
if (right && current_block.x+1 < game_config.width -1) {
delete current_block.walls.right;
delete game_config.grid_space[current_block.x + 1][current_block.y].walls.left;
}
if (bottom && current_block.y+1 < game_config.height -1) {
delete current_block.walls.bottom;
delete game_config.grid_space[current_block.x][current_block.y + 1].walls.top;
}
}
function start_with_recursion(canvas_context, mix=null) {
if (!mix) {
mix = game_config.grid_space.reduce((acc, e) => [...acc,...e], [])
.sort(() => Math.random() - 0.5)
} else if (mix.length === 0) {
return;
}
const block = mix.pop();
const i = game_config.width * game_config.height - mix.length
setTimeout(() => {
remove_walls(block);
render_block(canvas_context, block);
}, i * 50)
start_with_recursion(canvas_context, mix)
}
function start_without_recursion(canvas_context) {
const mix = game_config.grid_space.reduce((acc, e) => [...acc,...e], [])
.sort(() => Math.random() - 0.5)
for(let i = 0; i < mix.length; ++i){
setTimeout(() => {
remove_walls(mix[i]);
render_block(canvas_context, mix[i]);
}, i * 50)
}
}
function render_block(canvas_context, block) {
var walls = block.get_walls();
for(var key in walls)
{
var wall = walls[key];
render_line(canvas_context, wall.start, wall.end);
}
}
function render_line(canvas_context, start, end) {
canvas_context.strokeStyle = 'red';
canvas_context.beginPath();
canvas_context.moveTo(
5 + start.x + .5,
5 + start.y + .5
);
canvas_context.lineTo(
5 + end.x + .5,
5 + end.y + .5
);
canvas_context.stroke();
}
canvas = document.querySelector('canvas');
canvas.height = game_config.height * cellsise + 5 * 2;
canvas.width = game_config.width * cellsise + 5 * 2;
ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.fillRect(0,0,canvas.width, canvas.height);
// ctx.lineWidth = 2;
generateGridSpace();
// start_without_recursion(ctx);
start_with_recursion(ctx);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<canvas></canvas>
</body>
</html>
also for get a random neighbor:
function random_neighbor(current_block) {
const {height, width} = game_config;
const neighbors = [
{x: current_block.x, y: current_block.y - 1}, // from top
{x: current_block.x - 1, y: current_block.y}, // from left
{x: current_block.x + 1, y: current_block.y}, // from right
{x: current_block.x, y: current_block.y + 1}, // from bottom
].filter(e => e.x >= 0 && e.x < width && e.y >= 0 && e.y < height)
const rnd = neighbors[Math.floor(Math.random() * neighbors.length)]
return rnd; // {x, y}
}
You also need to take into account that the drawn wall can be removed later, which will not correspond to reality. Therefore, it is better to render (even with a delay) after the maze is fully generated.
Suggest converting your recursive function into an iterator function, and then introducing a second function that makes use of setInterval to iteratively draw your maze, and once complete, then performs a clearInterval.
Here's a quick sample that I whipped up exemplifying the concept, which simply draws increasingly larger rectangles on the canvas every 50ms, with the iterator function returning the size of the next rectangle to be drawn. In your case, you'll likely want to yield an object defining the portion of the maze to be drawn...
var ctx = document.getElementById('canvas').getContext('2d');
function* recurse( n, max ) {
yield n;
if ( n < max ) {
yield* recurse( n + 1, max );
}
}
function drawRectangles() {
function drawIteratively() {
let nd = iterator.next();
if ( nd.done ) {
clearInterval( interval );
} else {
ctx.lineWidth = 1;
ctx.strokeStyle = '#FF0000';
ctx.beginPath();
ctx.rect( nd.value, nd.value, nd.value * 2, nd.value * 2 );
ctx.stroke();
}
}
let iterator = recurse( 25, 100 );
let interval = setInterval( drawIteratively, 50 );
}
drawRectangles();
<canvas id='canvas' width="300" height="300"></canvas>
Note that the iterator function is a very basic recursive function, but recursive nonetheless, showing the critical syntax involved in a recursive iterator function...

Bullets follows mouse position

Im making simple arena shooter in JS and HTML.
right now I wanted to add shooting mechanic,I thought i would use code from my enemy class with bits of tweaking
here:
//differences between objects
let dirx = mouse.x - player.x,
diry = mouse.y - player.y;
//normalizing
let dist = Math.sqrt(dirx * dirx + diry * diry);
dirx = dirx / dist;
diry = diry / dist;
this.x += dirx * 5;
this.y += diry * 5;
Since my player is moving the player.x& player.y coords in dirX & dirY will change, meaning bullet wont go to original coords.
So I would be pleased for some help.
You could use vectors to determine the trajectory of the bullets.
In the example below, when I fire a bullet:
Initialize the position at the source of the bullet
The speed is determined by:
Subtracting the mouse position from the source
Normalizing the distance
Applying a speed constant
class Bullet {
constructor(source, target, created) {
this.position = source.clone();
this.speed = this.position.clone()
.subtract(Victor.fromObject(target))
.normalize()
.multiply(new Victor(-2, -2));
this.size = 3;
this.timeLeft = created + 500;
}
update() {
this.position = this.position.add(this.speed);
}
draw(ctx) {
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.size / 2, 0, 2 * Math.PI);
ctx.fill();
}
}
Note: I also added a timeLeft variable to remove the bullet from the game after a set period of time e.g. 500ms.
Demo
Here is an example using the vector logic explained above.
Controls
W (Up) - Move up
A (Left) - Move left
S (Down) - Move down
D (Right) - Move right
Mouse 1 - Hold to fire
const VK_W = 87;
const VK_A = 65;
const VK_S = 83;
const VK_D = 68;
const VK_UP = 38;
const VK_DOWN = 40;
const VK_LEFT = 37;
const VK_RIGHT = 39;
const canvas = document.querySelector('.game'),
stateBtn = document.querySelector('.state-btn');
let game;
const main = () => {
stateBtn.addEventListener('click', changeState);
const ctx = canvas.getContext('2d');
game = new Game(ctx, { width: 400, height: 300 });
game.run();
};
const changeState = (e) => {
const btn = e.currentTarget, running = btn.dataset.running === 'true';
if (running) {
game.pause();
} else {
game.run();
}
btn.textContent = running ? 'Pause' : 'Resume';
btn.dataset.running = !running;
};
class Game {
constructor(ctx, options) {
const { width, height } = options;
this.lastRender = 0;
this.ctx = ctx;
this.state = { keySet: new Set() };
this.bullets = [];
Object.assign(ctx.canvas, { width, height });
const origin = new Victor(width / 2, height / 2);
this.player = new Player({
name: 'Bob',
position: origin
});
ctx.canvas.addEventListener('mousemove', (e) => this.followMouse(e));
ctx.canvas.addEventListener('mousedown', (e) => this.mouseDown(e));
ctx.canvas.addEventListener('mouseup', (e) => this.mouseUp(e));
document.addEventListener('keydown', (e) => this.addKey(e), false);
document.addEventListener('keyup', (e) => this.removeKey(e), false);
}
followMouse(e) {
this.state.mousePos = getMousePos(e.currentTarget, e);
}
mouseDown(e) {
this.state.mouseDown = true;
}
mouseUp(e) {
this.state.mouseDown = false;
}
addKey(e) {
const key = e.which || e.keyCode || 0;
this.state.keySet.add(key);
}
removeKey(e) {
const key = e.which || e.keyCode || 0;
this.state.keySet.delete(key);
}
update(progress) {
const ks = this.state.keySet;
const x = (ks.has(VK_D) || ks.has(VK_RIGHT))
? 1 : (ks.has(VK_A) || ks.has(VK_LEFT)) ? -1 : 0;
const y = (ks.has(VK_S) || ks.has(VK_DOWN))
? 1 : (ks.has(VK_W) || ks.has(VK_UP)) ? -1 : 0;
this.player.position.add(new Victor(x, y));
this.bullets.forEach((bullet, index) => {
if (this.lastRender > bullet.timeLeft) {
this.bullets.splice(index, 1);
}
bullet.update(this.lastRender);
});
if (this.state.mousePos && this.state.mouseDown) {
this.bullets.push(new Bullet(this.player.position,
this.state.mousePos, this.lastRender));
}
}
draw() {
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
this.player.draw(this.ctx);
this.bullets.forEach(bullet => bullet.draw(this.ctx));
}
run(timestamp) {
const progress = timestamp - this.lastRender;
this.update(progress);
this.draw();
this.lastRender = timestamp;
this.req = window.requestAnimationFrame((t) => this.run(t));
}
pause() {
const { width, height } = this.ctx.canvas;
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
this.ctx.fillRect(0, 0, width, height);
this.ctx.font = '32px Arial';
this.ctx.fillStyle = '#FFF';
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillText('Paused', width / 2, height / 2);
cancelAnimationFrame(this.req);
this.req = null;
}
isRunning() {
return this.req !== null;
}
}
class Player {
constructor(options) {
const opts = { ...Player.defaultOptions, ...options };
this.name = opts.name;
this.position = opts.position;
this.size = opts.size;
}
draw(ctx) {
ctx.fillStyle = '#00FF00';
ctx.fillRect(this.position.x - this.size / 2,
this.position.y - this.size / 2, this.size, this.size);
}
};
Player.defaultOptions = {
size: 10,
position: new Victor(0, 0)
};
class Bullet {
constructor(source, target, created) {
this.position = source.clone();
this.speed = this.position.clone()
.subtract(Victor.fromObject(target))
.normalize()
.multiply(new Victor(-2, -2));
this.size = 3;
this.timeLeft = created + 500;
}
update() {
this.position = this.position.add(this.speed);
}
draw(ctx) {
ctx.fillStyle = 'yellow';
ctx.beginPath();
ctx.arc(this.position.x, this.position.y, this.size / 2, 0, 2 * Math.PI);
ctx.fill();
}
}
const getMousePos = (canvas, event) =>
(({ top, left }) => ({
x: event.clientX - left,
y: event.clientY - top
}))(canvas.getBoundingClientRect());
main();
.game {
background: #111;
}
.as-console-wrapper { max-height: 4em !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/victor/1.1.0/victor.min.js"></script>
<canvas class="game"></canvas>
<div class="controls">
<button class="state-btn" data-running="true">Pause</button>
</div>

Assignment - Finding nearest point on a plane. Javascript

I have an assignment i'm working on and i a little stuck.. I think I'm on the right path, but this keeps returning undefined.
EDIT: the goal is to pass in an array of two points and find witch in the source array is closer.
Any guidance with some explanation would be greatly appreciated.
function calcDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x1 - y1, 2) + Math.pow(x2 - y2, 2));
}
function minimumDistance(inputArray, destArray) {
let inputX = inputArray.x;
let inputY = inputArray.y;
let minDistance = calcDistance(inputX, inputY, destArray[0].x, destArray[0].y)
let minPoint;
for (let i = 0; i < destArray.lenght; i++) {
let distance = calcDistance(inputX, inputY, destArray[i].x, destArray[i].y);
if (minDistance > distance) {
minDistance = distance;
minPoint = i;
}
return destArray[minPoint];
}
}
testData = { x: 0, y: 0 }
sourceArr = [{ x: 100, y: 0 }, { x: 200, y: 10 }]
console.log(minimumDistance(testDatac, sourceArr));
I'm not exactly sure what you're trying to achieve, but here are some changes I made:
start minDistance at +Infinity and then loop over all the points (alternatively, initialize minPoint on 0 does the same)
fix typo lenght => length
move return statement to after the loop (so the program can check all points before returning a value)
Fix the calcDistance function to use the right formula (sqrt((x2-x1)**2 + (y2-y1)**2))
function calcDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
function minimumDistance(inputArray, destArray) {
let inputX = inputArray.x;
let inputY = inputArray.y;
let minDistance = +Infinity;
let minPoint;
for (let i = 0; i < destArray.length; i++) {
let distance = calcDistance(inputX, inputY, destArray[i].x, destArray[i].y);
if (minDistance > distance) {
minDistance = distance;
minPoint = i;
}
}
return destArray[minPoint];
}
testSrc = { x: 0, y: 0 }
testArr = [{ x: 100, y: 0 }, { x: 200, y: 10 }]
console.log(minimumDistance(testSrc, testArr));
testSrc = { x: 200, y: 80 }
console.log(minimumDistance(testSrc, testArr));
I can only assume you want something like this.
// Calculate euclidean distance between a and b (which should be objects with x/y properties)
function euclideanDistance(a, b) {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
// Find an object `x` in `otherPoints` that minimizes `metric(point, x)`
function minimumDistance(point, otherPoints, metric) {
let minDistance, minPoint;
otherPoints.forEach((otherPoint) => {
const distance = metric(point, otherPoint);
if (minDistance === undefined || distance < minDistance) {
minDistance = distance;
minPoint = otherPoint;
}
});
return [minPoint, minDistance];
}
testSrc = { x: 0, y: 0 };
testArr = [
{ x: 100, y: 0 },
{ x: 200, y: 10 },
];
console.log(minimumDistance(testSrc, testArr, euclideanDistance));
You initialize minDistance with the distance the first point has to the inputArray and then compare all other distances to this. This works fine if you also initialize the minPoint to 0.
In the current program you would only enter the ifstatement if your first distance is not the smallest in total.
To fix this: initialize minPoint to 0.
Almost there yes! I fixed some typos and you omitted to default minPoint to 0
function calcDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
function minimumDistance(inputObject, destArray) {
let inputX = inputObject.x;
let inputY = inputObject.y;
let minDistance = calcDistance(inputX, inputY, destArray[0].x, destArray[0].y);
let minPoint = 0;
for (let i = 1; i < destArray.length; i++) {
let distance = calcDistance(inputX, inputY, destArray[i].x, destArray[i].y);
if (minDistance > distance) {
minDistance = distance;
minPoint = i;
}
}
return destArray[minPoint];
}
testSrc = { x: 0, y: 0 };
testArr = [{ x: 100, y: 0 }, { x: 200, y: 10 }]
console.log(minimumDistance(testSrc, testArr));

What are the options available for plotting graphs from a table? (Without using any plotting library )

What are the options available for plotting graphs upon selecting different columns from a Data Table. I have worked with ag-grid and I want some thing like that which will come directly out of the box without using any other graph library(like plotly or highcharts) and manually writing code.
As I said in my comment, I recommend looking into Canvas (mdn).
Here's a minimal exmaple that allows panning (mouse 1) and zooming (mouse 2) in addition to callbacks for clicks and mouse moves.
class Chart {
constructor(canvas, hoverCallback, clickCallback) {
this.width = canvas.width;
this.height = canvas.height;
this.ctx = canvas.getContext('2d');
this.ctx.font = '14px serif';
canvas.addEventListener('mousedown', e => {
this.dragged = false;
this.mouseDown = {x: e.offsetX, y: e.offsetY};
});
canvas.addEventListener('mousemove', e => {
hoverCallback?.(this.pixelToCoord(e.offsetX, e.offsetY));
if (!this.mouseDown)
return;
this.dragged = true;
if (e.buttons & 1 && !e.shiftKey)
this.panRange(e.offsetX - this.mouseDown.x, e.offsetY - this.mouseDown.y);
else if (e.buttons & 2 || e.shiftKey)
this.zoomRange(e.offsetX - this.mouseDown.x, e.offsetY - this.mouseDown.y);
this.mouseDown = {x: e.offsetX, y: e.offsetY};
});
canvas.addEventListener('mouseleave', () => hoverCallback?.());
document.addEventListener('mouseup', () => this.mouseDown = null);
canvas.addEventListener('click', e => {
if (this.dragged)
return;
clickCallback?.(this.pixelToCoord(e.offsetX, e.offsetY));
});
canvas.addEventListener('dblclick', e => {
if (this.dragged)
return;
this.resetRange(e.shiftKey);
});
this.pointSets = [];
this.resetRange();
}
set pointSets(value) {
this.pointSets_ = value;
this.draw();
}
resetRange(zeroMins = false) {
let allPoints = this.pointSets_
.flatMap(({points}) => points);
[this.minX, this.deltaX] = Chart.getRange(allPoints.map(({x}) => x), zeroMins);
[this.minY, this.deltaY] = Chart.getRange(allPoints.map(({y}) => y), zeroMins);
this.verifyRange();
this.draw();
}
panRange(x, y) {
this.minX -= x * this.deltaX / this.width;
this.minY += y * this.deltaY / this.height;
this.verifyRange();
this.draw();
}
zoomRange(x, y) {
let dx = x * this.deltaX / this.width;
let dy = -y * this.deltaY / this.height;
this.minX += dx;
this.minY += dy;
this.deltaX -= dx * 2;
this.deltaY -= dy * 2;
this.verifyRange();
this.draw();
}
verifyRange() {
this.minX = Math.max(this.minX, -this.deltaX / 10);
this.minY = Math.max(this.minY, -this.deltaY / 10);
}
draw() {
if (!this.pointSets_ || this.minX === undefined)
return;
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, this.width, this.height);
this.drawPoints();
this.drawAxis();
}
drawPoints() {
this.pointSets_.forEach(({color, fill, size, points, isPath}) => {
this.ctx.strokeStyle = color;
this.ctx.fillStyle = color;
if (isPath) {
this.ctx.lineWidth = size;
this.ctx.beginPath();
points.forEach((p, i) => {
let {x, y} = this.coordToPixel(p.x, p.y);
if (!i)
this.ctx.moveTo(x, y);
else
this.ctx.lineTo(x, y);
});
if (fill)
this.ctx.fill();
else
this.ctx.stroke();
} else {
points.forEach(p => {
let {x, y} = this.coordToPixel(p.x, p.y);
this.ctx[fill ? 'fillRect' : 'strokeRect'](x - size / 2, y - size / 2, size, size);
});
}
});
}
drawAxis() {
let n = 20;
let step = this.width / n;
let size = 10;
let sizeSmall = 1;
this.ctx.lineWidth = 1;
this.ctx.strokeStyle = `rgb(0, 0, 0)`;
this.ctx.fillStyle = `rgb(0, 0, 0)`;
this.ctx.strokeRect(this.width / n, this.height * (n - 1) / n, this.width * (n - 2) / n, 0); // x axis line
this.ctx.strokeRect(this.width / n, this.height / n, 0, this.width * (n - 2) / n); // y axis line
for (let i = 2; i < n; i += 2) {
let x = i * step;
let y = (n - i) * step;
let xText = Chart.numToPrint(this.minX + i / n * this.deltaX);
let yText = Chart.numToPrint(this.minY + i / n * this.deltaY);
this.ctx.fillText(xText, x - 9, step * (n - 1) + 17); // x axis text
this.ctx.fillText(yText, step - 28, y + 4, 30); // y axis text
this.ctx.fillRect(x - sizeSmall / 2, step * (n - 1) - size / 2, sizeSmall, size); // x axis dots
this.ctx.fillRect(step - size / 2, x - sizeSmall / 2, size, sizeSmall); // y axis dots
}
}
pixelToCoord(x, y) {
return {
x: x / this.width * this.deltaX + this.minX,
y: (1 - y / this.height) * this.deltaY + this.minY,
width: 20 / this.width * this.deltaX,
height: 20 / this.height * this.deltaY
};
}
coordToPixel(x, y) {
return {
x: x === Infinity ? this.width : (x - this.minX) / this.deltaX * this.width,
y: y === Infinity ? 0 : (1 - (y - this.minY) / this.deltaY) * this.height,
};
}
static getRange(values, zeroMin = false, buffer = .1) {
let min = values.length && !zeroMin ? Math.min(...values) : 0;
let max = values.length ? Math.max(...values) : 10;
let delta = max - min + .001;
return [min - delta * buffer, delta + delta * buffer * 2]
}
static numToPrint(n) {
return Math.round(n * 10) / 10;
}
}
let canvas = document.querySelector('canvas');
let chart = new Chart(canvas);
chart.pointSets = [
// e.g. {color: 'rgb(255,0,0)', fill: true, size: 5, points: [{x: 0, y: 1}, ...], isPath: true}
{
color: 'rgb(255,0,0)',
fill: false,
size: 3,
points: [
{x: 0, y: 1},
{x: 1, y: 1},
{x: 1, y: 0},
{x: 0, y: 0},
{x: 0, y: .5},
],
isPath: true,
}, {
color: 'rgb(0,0,255)',
fill: true,
size: 10,
points: [
{x: 0, y: 1},
{x: 0, y: .5},
{x: 3, y: 1},
{x: 6, y: 2},
{x: 7, y: 4},
{x: 6, y: 5},
],
isPath: false,
},
];
chart.resetRange();
canvas {
border: 1px solid;
}
<canvas width="500" height="500"></canvas>

Categories