How to get visual representation of binary tree codding by javascript? - javascript

I created a binery tree using javascript but i don't know how to print it in html i tried a lot of methods but nothings work good, can someone help me in this ?
I tried canavas but i'm not perfect in this so it's doesn't work for me , i tried to append ul and li form javascript but it's give a lot of problem.
class Node {
constructor(data,left= null,right= null){
this.data = data;
this.left = left;
this.right = right;
}
}
// AB = Binery Tree
class AB {
constructor(){
this.root = null;
}
add(data,i){
const node = this.root;
console.log(data,i);
if(node === null){
this.root = new Node(data);
return;
}else{
const SEARCHTREE = function(node){
if(data <= node.data){
if(node.left === null){
node.left = new Node(data);
return;
} else if (node.left !== null){
return SEARCHTREE(node.left);
}
} else if (data > node.data) {
if(node.right === null){
node.right = new Node(data);
return;
}
else if (node.right !== null){
return SEARCHTREE(node.right);
}
} else {
return null;
}
};
return SEARCHTREE(node);
}
}
};
const ab = new AB();
for (var i = 0; i < 19; i++) {
ab.add(Math.floor(Math.random() * 10) + 1,i);
}
console.log(ab)
This the tree that i get :

I found the solution thank you.
this the js file :
class Node {
constructor(x,y,r, ctx, data){
this.data = data;
this.leftNode = null;
this.rightNode = null;
this.x = x;
this.y = y;
this.r = r;
this.ctx = ctx;
}
draw(){
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.r, 0, 2*Math.PI);
this.ctx.stroke();
this.ctx.closePath();
this.ctx.strokeText(this.data, this.x, this.y);
}
getData(){
return this.data;
}
getX(){
return this.x;
}
getY(){
return this.y;
}
getRadius(){
return this.r;
}
leftCoordinate(x,y,r){
return {cx: (x - (4*r)), cy: (y + (4*r))};
}
rightCoordinate(x,y,r){
return {cx: (x + (3*r)), cy: (y + (3*r))};
}
}
class Line {
// Takes
// x,y - starting x,y coordinate
// toX, toY - ending x,y coordinate
draw(x, y, toX, toY, r, ctx) {
var moveToX = x;
var moveToY = y + r;
var lineToX = toX;
var lineToY = toY - r;
ctx.beginPath();
ctx.moveTo(moveToX, moveToY);
ctx.lineTo(lineToX, lineToY);
ctx.stroke();
};
}
class BTree {
constructor(){
this.c = document.getElementById('my-canvas');
this.ctx = this.c.getContext('2d');
this.line = new Line();
this.root = null;
}
add(data){
// If root exists, then recursively find the place to add the new node
if(this.root) {
this.recursiveAddNode(this.root, null, null, data);
} else {
// If not, the add the element as a root
this.root = this.addAndDisplayNode(200, 20, 15, this.ctx,data);
return;
}
}
// Recurively traverse the tree and find the place to add the node
recursiveAddNode(node, prevNode, coordinateCallback, data) {
if(node === null) {
// This is either node.leftCoordinate or node.rightCoordinate
var xy = coordinateCallback;
var newNode = this.addAndDisplayNode(xy.cx, xy.cy, 15, prevNode.ctx, data);
this.line.draw(prevNode.getX(), prevNode.getY(), xy.cx, xy.cy, prevNode.getRadius(), prevNode.ctx);
return newNode;
}
else {
if(data < node.getData()) {
if(node.leftNode === null){
var xy = node.leftCoordinate(node.x,node.y,node.r);
node.leftNode = this.addAndDisplayNode(xy.cx, xy.cy, 15, node.ctx, data);
this.line.draw(node.getX(), node.getY(), xy.cx, xy.cy, node.getRadius(), node.ctx);
return;
}else if(node.leftNode !== null){
return
this.recursiveAddNode(node.leftNode,node,node.leftCoordinate(node.x,node.y,node.r), data);
}
}else if(data > node.getData()){
if(node.rightNode === null){
var xy = node.rightCoordinate(node.x,node.y,node.r);
node.rightNode = this.addAndDisplayNode(xy.cx, xy.cy, 15, node.ctx, data);
this.line.draw(node.getX(), node.getY(), xy.cx, xy.cy, node.getRadius(), node.ctx);
return;
}else if(node.rightNode !== null){
return
this.recursiveAddNode(node.rightNode,node,node.rightCoordinate(node.x,node.y,node.r), data);
}
}else {
return null;
}
}
}
// Adds the node to the tree and calls the draw function
addAndDisplayNode(x, y, r, ctx, data) {
var node = new Node(x, y, r, ctx, data);
node.draw();
return node;
}
}
var btree = new BTree();
for (var i = 0; i < 19; i++) {
btree.add(Math.floor(Math.random() * 10) + 1);
}
And this is html file :
<!DOCTYPE html>
<html>
<head>
<title></title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="tree center">
<canvas id='my-canvas' width="1000" height="1000">
Your browser doesnot support canvas
</canvas>
</div>
</body>
<script type="text/javascript" src="arbre.js"></script>
</html>
And this the css file :
.center {margin: auto; width: 50%;}

Related

Recursion loop to check if conditions good, or try again

I have to generate several objects on a canvas.
The problem is that if my conditions (IF statements) are working, it is not looping again if the conditions are not met.
So I sometimes have only 1 player instead of 2, etc.
The function which sets the piece is setPiece(), with different conditions depending of the objects (obstacle, weapon, player).
Each object has in own function which call setPiece: setObstacles, setWeapons, setPlayers.
I have already tried to call these functions after an "else", so setPiece loop again. But it causes a stack error in the console, too many loops.
I also tried a While loop but don't succeded to implement it, so it is also an infinite loop.
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-compatible" content="IE-edge">
<meta name="viewport" content="width=device-width, initial-scale-1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.0/animate.css">
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<title>Lava Temple</title>
<style>
* {
padding: 0;
border: 0;
}
body{
background-color: #181818;
}
#board {
display: block;
background-color: white;
margin: 0 auto;
margin-top: 100px;
}
</style>
</head>
<body>
<canvas id="board" width="800" height="800"></canvas>
<script>
// BOARD OBJECT
function Board(width, height) {
this.width = width;
this.height = height;
this.chartBoard = [];
// Création du plateau logique
for (var i = 0; i < this.width; i++) {
const row = [];
this.chartBoard.push(row);
for (var j = 0; j < this.height; j++) {
const col = {};
row.push(col);
}
}
}
let board = new Board(10, 10);
console.log(board);
const ctx = $('#board').get(0).getContext('2d');
Board.prototype.drawBoard = function () {
for (var i = 0; i < this.width; i++) {
for (var j = 0; j < this.height; j++) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.strokeRect(j * 64, i * 64, 64, 64);
ctx.closePath();
}
}
};
board.drawBoard();
// OBJECTS OF THE GAME
function Obstacle(name, sprite) {
this.name = name;
this.sprite = sprite;
}
const lava = new Obstacle("Lave", "assets/lave.png");
const lava1 = new Obstacle("Lave1", "assets/lave.png");
const lava2 = new Obstacle("Lave2", "assets/lave.png");
const lava3 = new Obstacle("Lave3", "assets/lave.png");
const lava4 = new Obstacle("Lave4", "assets/lave.png");
const lava5 = new Obstacle("Lave5", "assets/lave.png");
const lava6 = new Obstacle("Lave6", "assets/lave.png");
const lava7 = new Obstacle("Lave7", "assets/lave.png");
const lava8 = new Obstacle("Lave8", "assets/lave.png");
const lava9 = new Obstacle("Lave9", "assets/lave.png");
const lavaArray = [lava, lava1, lava2, lava3, lava4, lava5, lava6, lava7, lava8, lava9];
function Weapon(name, sprite, damage) {
this.name = name;
this.sprite = sprite;
this.damage = damage;
}
const dagger = new Weapon("Dague", "assets/dague.png", 5);
const sword = new Weapon("Epée", "assets/epee.png", 10);
const axe = new Weapon("Hache", "assets/hache.png", 15);
const flail = new Weapon("Fléau", "assets/fleau.png", 20);
const weaponArray = [dagger, sword, axe, flail];
function Player(name, sprite, life) {
this.name = name;
this.sprite = sprite;
this.life = life;
}
const player1 = new Player("Joueur 1", "assets/joueur1.png", 100);
const player2 = new Player("Joueur 2", "assets/joueur2.png", 100);
const playerArray = [player1, player2];
// INIT OF THE GAME (code about my question)
Board.prototype.setPiece = function (piece) {
let randomX = Math.floor(Math.random() * board.width);
let randomY = Math.floor(Math.random() * board.height);
let drawX = randomX * 64;
let drawY = randomY * 64;
if (randomX >= this.width || randomY >= this.height) {
throw new Error('Pièce hors limite');
}
if (piece instanceof Obstacle) {
if (!(this.chartBoard[randomY][randomX] instanceof Obstacle)) {
this.chartBoard[randomY][randomX] = piece;
ctx.fillRect(drawX, drawY,64,64);
}
} else if (piece instanceof Weapon) {
if (!(this.chartBoard[randomY][randomX] instanceof Obstacle) && (!(this.chartBoard[randomY][randomX] instanceof Weapon))) {
this.chartBoard[randomY][randomX] = piece;
ctx.fillStyle = "red";
ctx.fillRect(drawX, drawY,64,64);
}
} else if (piece instanceof Player) {
if (!(this.chartBoard[randomY][randomX] instanceof Obstacle) &&
(!(this.chartBoard[randomY][randomX] instanceof Weapon) &&
(!(this.chartBoard[randomY][randomX] instanceof Player) &&
((!(this.chartBoard[randomY][randomX + 1] instanceof Player)) || (typeof this.chartBoard[randomY][randomX + 1] === undefined)) &&
((!(this.chartBoard[randomY][randomX - 1] instanceof Player)) || (typeof this.chartBoard[randomY][randomX - 1] === undefined)) &&
((!(this.chartBoard[randomY + 1][randomX] instanceof Player)) || (typeof this.chartBoard[randomY + 1][randomX] === undefined)) &&
((!(this.chartBoard[randomY - 1][randomX] instanceof Player)) || (typeof this.chartBoard[randomY - 1][randomX] === undefined))))) {
this.chartBoard[randomY][randomX] = piece;
ctx.fillStyle = "blue";
ctx.fillRect(drawX, drawY,64,64);
}
} else {
throw new Error('Pièce non valide');
}
};
Board.prototype.setObstacles = function () {
for (let lava of lavaArray) {
const obstacle = board.setPiece(lava);
}
};
board.setObstacles();
Board.prototype.setWeapons = function () {
let numWeapons = 4;
let randomWeapon;
let spawnWeapon;
for (let i = 0; i < numWeapons; i++) {
randomWeapon = Math.floor(Math.random() * weaponArray.length);
spawnWeapon = board.setPiece(weaponArray[randomWeapon]);
}
};
board.setWeapons();
Board.prototype.setPlayers = function () {
for (let player of playerArray) {
const spawnPlayer = board.setPiece(player);
}
};
board.setPlayers();
</script>
</body>
</html>
Actual results: Sometimes everything works well, but there are 2 problems:
- A piece might not be set on the board (canvas).
- There is an error with the player object because the IF condition (random + or - x / y) try to check outside of the length of the board, so I get an error Undefined. Don't know how to resolve it.
Expected: Understand how to generate a dynamic generation of objects on this canvas, and to repeat a loop with multiple conditions. Also learn how to implement the limits of the canvas so there is no Undefined error.
I think the problem is with going out of boundaries with on of those randomY increments and decrements. If you console.log(randomY) right after else if (piece instanceof Player) { ... then it seems that you get only one player in those scenarios only when randomY is either 0 or 9.
You can try doing something along these lines right after else if (piece instanceof Player) { ... and this should resolve the issue.
if (randomY === 0){
randomY = randomY+1
}
if (randomY === 9){
randomY = randomY-1
}
I'm not sure if this would still be considered "random". Tiles would still be able to take edges though, so mechanics still feel "random".

Javascript Inheritance, overwriting behavior?

This may be a lengthy post. I'm having to learn JS on the fly and could be making simple mistakes.
I'm writing a game in JS/html, and am using EasyStar to put in some pathfinding behavior for some entities. I'm using inheritance to give the behavior outlined in Walker4 below to multiple entities, example of one at the bottom:
function Walker4(game, img, Ai, lX, lY) {
this.easyStar = Ai;
this.dX = 0;
this.dY = -1;
this.animation = [];
this.animation["NE"] = null;
this.animation["NW"] = null;
this.animation["SE"] = null;
this.animation["SW"] = null;
this.currAnimation = null;
this.facing = "";
this.img = img;
this.isWalking = false;
this.isFindingPath = false;
this.destX = null;
this.destY = null;
this.path = [];
this.next = null;
this.loadCount = 0;
Entity.call(this, game, lX, lY);
}
Walker4.prototype = new Entity();
Walker4.prototype.constructor = Walker4;
Walker4.prototype.update = function () {
if (this.isFindingPath) return;
if (this.isWalking) this.walkPath();
if (this.destX != null && this.destY != null) {
this.isFindingPath = true;
that = this;
easyStar.findPath(this.x, this.y, this.destX, this.destY, function (path) {
if (path === null) {
console.log("No path :(");
} else {
console.log("Path! The first Point is " + path[0].x + " " + path[0].y);
that.path = path;
that.next = that.path.shift();
that.isWalking = true;
}
});
this.destX = null;
this.destY = null;
this.isFindingPath = false;
easyStar.calculate();
}
Entity.prototype.update.call(this);
}
Walker4.prototype.walkPath = function () {
if (this.path.length == 0) {
if (Math.floor(this.x) == this.next.x && Math.floor(this.y) == this.next.y) {
this.dX = 0;
this.dY = 0;
}
isWalking = false;
return;
}
if (Math.floor(this.x) == this.next.x && Math.floor(this.y) == this.next.y) {
this.next = this.path.shift();
this.dX = setDirection(Math.floor(this.x), this.next.x);
this.dY = setDirection(Math.floor(this.y), this.next.y);
this.currAnimation = this.animation[setFace(this.dX, this.dY)];
}
this.x += this.dX * this.game.clockTick * speed;
this.y += this.dY * this.game.clockTick * speed;
}
Walker4.prototype.draw = function (ctx) {
pt1 = twodtoisoX(this.x, this.y) + 27 - this.currAnimation.frameWidth / 2;
pt2 = twodtoisoY(this.x, this.y) + 10 - this.currAnimation.frameHeight / 2;
ctx.fillRect(pt1, pt2, 5, 5);
//console.log(pt1, pt2);
this.currAnimation.drawFrame(this.game.clockTick, ctx, pt1, pt2);
Entity.prototype.draw.call(this);
}
//Cart Walkers
function eCartMan(game, img, Ai, lX, lY) {
Walker4.call(this, game, img, Ai, lX, lY);
this.animation["NE"] = new Animation(img, 0, 0, 60, 48, 12, aSpeed, 12, true);
this.animation["NW"] = new Animation(img, 0, 1, 60, 48, 12, aSpeed, 12, true);
this.animation["SE"] = new Animation(img, 0, 2, 60, 48, 12, aSpeed, 12, true);
this.animation["SW"] = new Animation(img, 0, 3, 60, 48, 12, aSpeed, 12, true);
this.currAnimation = this.animation["NE"];
}
eCartMan.prototype = new Walker4();
eCartMan.prototype.constructor = eCartMan;
All these entities are added to a list of entities that is updated in a loop each game tick, each having update and draw called respectively. Easy Star seems to give each entity a path, but then only the most recently added entity will actually follow their given path. What am I missing?
Any help appreciated.
Alright, scoped down the problem. Thanks to #HMR for helping.
Ended up a scope issue on that = this;, and that each walker needed its own version of the EasyStar pathfinder. that became global, and opened up all instances of the walkers to modifying each other.

HTML Canvas & Javascript - Triggering Audio by Selection (From Multiple Places)

I have a selection menu in my HTML canvas that I would like to trigger corresponding audio files. I have tried implementing this by declaring the images inside the if (this.hovered) & (this.clicked) part of the makeSelection function within the selectionForMenu prototype, such that on each new selection the selected audio file is redefined, but this causes problems like slow loading and overlapping audio. It is also problematic as I am trying to get the speaker button at the bottom of the screen to play the audio corresponding to the current selection too, so if it is only defined within that function it is not accessible to the makeButton function.
You can see the selection menu and speaker button in the snippet below. Each new selection in the menu should play once an audio file that corresponds to it (which I have not been able to add to this demonstration). It can be replayed by re-clicking the selection or clicking the speaker button, but each click should only provoke one play of the audio and of course overlapping is undesired. Any help will be appreciated.
var c=document.getElementById('game'),
canvasX=c.offsetLeft,
canvasY=c.offsetTop,
ctx=c.getContext('2d');
var button = function(id, x, strokeColor) {
this.id = id;
this.x = x;
this.strokeColor = strokeColor;
this.hovered = false;
this.clicked = false;
}
button.prototype.makeInteractiveButton = function() {
if (this.hovered) {
if (this.clicked) {
this.fillColor = '#DFBCDE';
} else {
this.fillColor = '#CA92C8'
}
} else {
this.fillColor = '#BC77BA'
}
ctx.strokeStyle=this.strokeColor;
ctx.fillStyle=this.fillColor;
ctx.beginPath();
ctx.lineWidth='5';
ctx.arc(this.x, 475, 20, 0, 2*Math.PI);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
button.prototype.hitTest = function(x, y) {
return (Math.pow(x-this.x, 2) + Math.pow(y-475, 2) < Math.pow(20, 2));
}
var selectionForMenu = function(id, text, y) {
this.id = id;
this.text = text;
this.y = y;
this.hovered = false;
this.clicked = false;
this.lastClicked = false;
}
selectionForMenu.prototype.makeSelection = function() {
var fillColor='#A84FA5';
if (this.hovered) {
if (this.clicked) {
if (this.lastClicked) {
fillColor='#E4C7E2';
} else {
fillColor='#D5A9D3';
}
} else if (this.lastClicked) {
fillColor='#D3A4D0';
} else {
fillColor='#BA74B7';
}
} else if (this.lastClicked) {
fillColor='#C78DC5';
} else {
fillColor='#A84FA5';
}
ctx.beginPath();
ctx.fillStyle=fillColor;
ctx.fillRect(0, this.y, 350, 30)
ctx.stroke();
ctx.font='10px Noto Sans';
ctx.fillStyle='white';
ctx.textAlign='left';
ctx.fillText(this.text, 10, this.y+19);
}
selectionForMenu.prototype.hitTest = function(x, y) {
return (x >= 0) && (x <= (350)) && (y >= this.y) && (y <= (this.y+30)) && !((x >= 0) && (y > 450));
}
var Paint = function(element) {
this.element = element;
this.shapes = [];
}
Paint.prototype.addShape = function(shape) {
this.shapes.push(shape);
}
Paint.prototype.render = function() {
ctx.clearRect(0, 0, this.element.width, this.element.height);
for (var i=0; i<this.shapes.length; i++) {
try {
this.shapes[i].makeSelection();
}
catch(err) {}
}
ctx.beginPath();
ctx.fillStyle='#BC77BA';
ctx.fillRect(0, 450, 750, 50);
ctx.stroke();
for (var i=0; i<this.shapes.length; i++) {
try {
this.shapes[i].makeInteractiveButton();
}
catch(err) {}
}
var speaker = new Image(25, 25);
speaker.src='https://i.stack.imgur.com/lXg2I.png';
ctx.drawImage(speaker, 162.5, 462.5);
}
Paint.prototype.setHovered = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].hovered = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setClicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].clicked = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setUnclicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].clicked = false;
if (Number.isInteger(this.shapes[i].id)) {
this.shapes[i].lastClicked = this.shapes[i] == shape;
}
}
this.render();
}
Paint.prototype.select = function(x, y) {
for (var i=this.shapes.length-1; i >= 0; i--) {
if (this.shapes[i].hitTest(x, y)) {
return this.shapes[i];
}
}
return null
}
var paint = new Paint(c);
var btn = new button('speaker', 175, '#FFFCF8');
var selection = [];
for (i=0; i<15; i++) {
selection.push(new selectionForMenu(i+1, i, i*30));
}
paint.addShape(btn);
for (i=0; i<15; i++) {
paint.addShape(selection[i])
}
paint.render();
function mouseDown(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
paint.setClicked(shape);
}
function mouseUp(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
paint.setUnclicked(shape);
}
function mouseMove(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
paint.setHovered(shape);
}
c.addEventListener('mousedown', mouseDown);
c.addEventListener('mouseup', mouseUp);
c.addEventListener('mousemove', mouseMove);
canvas {
z-index: -1;
margin: 1em auto;
border: 1px solid black;
display: block;
background: #9F3A9B;
}
img {
z-index: 0;
position: absolute;
pointer-events: none;
}
#speaker {
top: 480px;
left: 592px;
}
#snail {
top: 475px;
left: 637.5px;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>uTalk Demo</title>
<link rel='stylesheet' type='text/css' href='wordpractice.css' media='screen'></style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
</head>
<body>
<canvas id="game" width = "350" height = "500"></canvas>
<script type='text/javascript' src='wordpractice copy.js'></script>
</body>
</html>
When you want responsiveness with audio, forget about MediaElements, and go with the Web Audio API.
MediaElements (<audio> and <video>) are slow, and http caching is an nightmare.
With the Web Audio API, you can first download all you media as arrayBuffers, decode their audio data to AudioBuffers, that you'll attach to your js objects.
From there, you'll be able to play new instances of these media in µs.
Beware, ES6 syntax below, for older browsers, here is an ES5 rewrite, also note that Internet Explorer < Edge does not support the Web Audio API, if you need to support these browsers, you'll have to make an fallback with audio elements.
(function myFirstDrumKit() {
const db_url = 'https://dl.dropboxusercontent.com/s/'; // all our medias are stored on dropbox
// we'll need to first load all the audios
function initAudios() {
const promises = drum.parts.map(part => {
return fetch(db_url + part.audio_src) // fetch the file
.then(resp => resp.arrayBuffer()) // as an arrayBuffer
.then(buf => drum.a_ctx.decodeAudioData(buf)) // then decode its audio data
.then(AudioBuf => {
part.buf = AudioBuf; // store the audioBuffer (won't change)
return Promise.resolve(part); // done
});
});
return Promise.all(promises); // when all are loaded
}
function initImages() {
// in this version we have only an static image,
// but we could have multiple per parts, with the same logic as for audios
var img = new Image();
img.src = db_url + drum.bg_src;
drum.bg = img;
return new Promise((res, rej) => {
img.onload = res;
img.onerror = rej;
});
}
let general_solo = false;
let part_solo = false;
const drum = {
a_ctx: new AudioContext(),
generate_sound: (part) => {
// called each time we need to play a source
const source = drum.a_ctx.createBufferSource();
source.buffer = part.buf;
source.connect(drum.gain);
// to keep only one playing at a time
// simply store this sourceNode, and stop the previous one
if(general_solo){
// stop all playing sources
drum.parts.forEach(p => (p.source && p.source.stop(0)));
}
else if (part_solo && part.source) {
// stop only the one of this part
part.source.stop(0);
}
// store the source
part.source = source;
source.start(0);
},
parts: [{
name: 'hihat',
x: 90,
y: 116,
w: 160,
h: 70,
audio_src: 'kbgd2jm7ezk3u3x/hihat.mp3'
},
{
name: 'snare',
x: 79,
y: 192,
w: 113,
h: 58,
audio_src: 'h2j6vm17r07jf03/snare.mp3'
},
{
name: 'kick',
x: 80,
y: 250,
w: 200,
h: 230,
audio_src: '1cdwpm3gca9mlo0/kick.mp3'
},
{
name: 'tom',
x: 290,
y: 210,
w: 110,
h: 80,
audio_src: 'h8pvqqol3ovyle8/tom.mp3'
}
],
bg_src: '0jkaeoxls18n3y5/_drumkit.jpg?dl=0',
};
drum.gain = drum.a_ctx.createGain();
drum.gain.gain.value = .5;
drum.gain.connect(drum.a_ctx.destination);
function initCanvas() {
const c = drum.canvas = document.createElement('canvas');
const ctx = drum.ctx = c.getContext('2d');
c.width = drum.bg.width;
c.height = drum.bg.height;
ctx.drawImage(drum.bg, 0, 0);
document.body.appendChild(c);
addEvents(c);
}
const isHover = (x, y) =>
(drum.parts.filter(p => (p.x < x && p.x + p.w > x && p.y < y && p.y + p.h > y))[0] || false);
function addEvents(canvas) {
let mouse_hovered = false;
canvas.addEventListener('mousemove', e => {
mouse_hovered = isHover(e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop)
if (mouse_hovered) {
canvas.style.cursor = 'pointer';
} else {
canvas.style.cursor = 'default';
}
})
canvas.addEventListener('mousedown', e => {
e.preventDefault();
if (mouse_hovered) {
drum.generate_sound(mouse_hovered);
}
});
const checkboxes = document.querySelectorAll('input');
checkboxes[0].onchange = function() {
general_solo = this.checked;
general_solo && (checkboxes[1].checked = part_solo = true);
};
checkboxes[1].onchange = function() {
part_solo = this.checked;
!part_solo && (checkboxes[0].checked = general_solo = false);
};
}
Promise.all([initAudios(), initImages()])
.then(initCanvas);
})()
/*
Audio Samples are from https://sampleswap.org/filebrowser-new.php?d=DRUMS+%28FULL+KITS%29%2FSpasm+Kit%2F
Original image is from http://truimg.toysrus.co.uk/product/images/UK/0023095_CF0001.jpg?resize=500:500
*/
<label>general solo<input type="checkbox"></label><br>
<label>part solo<input type="checkbox"></label><br>
You could create an Audio Loader, that loads all the audios and keeps track of them:
function load(srcs){
var obj={};
srcs.forEach(src=>obj[src]=new Audio(src));
return obj;
}
Then you could do sth like this onload:
var audios=load(["audio1.mp3", "audio2.mp3"]);
And later:
(audios[src] || new Audio(src)).play();
This will just load the audio if it isnt already in the audios object.

Ray-wall intersection

I'm writing a simple 2D game engine because I want to brush up my school math knowledge.
I know that maybe a lot of people already answered similar questions (I read a lot of questions and answers about this topic), but I can't manage why my method doesn't work.
In this example we can see a "room" with a camera and two rays that change direction depending on the direction of the camera.
These rays should represent left and right border of the field of view of the camera and should intersect the walls in front of the camera.
The problem is that when the camera moves and rotates (using "UP","DOWN","LEFT","RIGHT"), sometimes the rays disappear i.e. the intersection function fails.
Can someone help me find the solution?
Here there are the relevant parts of code:
function Player(x,y,angle) {
this.x = x;
this.y = y;
this.z = 30;
this.fieldOfView = {
degrees : 60,
rads : Game.utils.degree2Rad(60),
set : function (deg) {
var self = this;
self.fieldOfView.degrees = deg;
self.fieldOfView.rads = Game.utils.degree2Rad(rads);
}
};
this.angleDeg = angle;
this.angleRads = Game.utils.degree2Rad(angle);
this.rotate = function () {
/* Handle rotation and position
of the camera depending on the button pressed */
}
this.draw = function (canvas1,canvas2) {
var self = this;
var ctx1 = canvas1.getContext('2d');
var ctx2 = canvas2.getContext('2d');
/*Draw a circle on canvas1 at Game.player.x Game.player.y*/
// CODE
/*Draw a circle on canvas2 at the center of canvas*/
// CODE
/*Angles of the two rays in radians (FOV is 60°).
Add 30° to player's angle and find one,
Substract 30° to player's angle and find the other
*/
var rad1 = Game.utils.degree2Rad(Game.player.angleDeg+30);
var rad2 = Game.utils.degree2Rad(Game.player.angleDeg-30);
/*
The two Rays, defined with a point (player.x and player.y)
and a vector
*/
var _rad1 = new Ray2D(self.x,self.y,new Vector2D(Math.cos(rad1),Math.sin(rad1)));
var _rad2 = new Ray2D(self.x,self.y,new Vector2D(Math.cos(rad2),Math.sin(rad2)));
var _w = Game.walls;
var ps = [];
for(var i=0;i<_w.length;i++)
{
//FIND THE INTERSECTION POINTS
var j = _w[i];
var p =_rad1.intersectionWall(j);
if(p) {
ps.push(p);
}
var p2 = _rad2.intersectionWall(j);
if(p2) {
ps.push(p2);
}
}
if(ps.length>1)
{
// DRAW THE TWO INTERSECTION POINTS
ctx1.beginPath();
ctx1.moveTo(self.x,self.y);
ctx1.lineTo(ps[0].x,ps[0].y);
ctx1.stroke();
ctx1.beginPath();
ctx1.moveTo(self.x,self.y);
ctx1.lineTo(ps[1].x,ps[1].y);
ctx1.stroke();
//CODE
}
else {
console.log(_rad1,_rad2);
//console.log('non-p',ps[0]);
}
/*ctx1.beginPath();
ctx1.arc(self.x,self.y,2,0,Game.GLOBAL.CIRCLE);
ctx1.stroke();
ctx1.closePath();*/
},
this.update = function () {
this.rotate();
}
}
function Wall (x1,y1,x2,y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.update = function (context1,context2,player) {
};
this.draw = function (context1,context2,player) {
//CODE
}
}
function Vector2D (x,y) {
this.x = x;
this.y = y;
this.dot = function (v) {
//Dot product
return v.x*self.x+v.y*self.y;
}
this.cross = function (v) {
//cross product
return self.x*v.y-self.y*v.x;
}
}
function StraightLine2D (xo,yo,v) {
if(!(v instanceof(Vector2D)))
{
throw new Error('Invalid Argument supplied for constructor "StraightLine2D"');
}
this.xo = xo;
this.yo = yo;
this.v = v;
this.calc = function (t) {
var self = this;
return {
x : t*self.v.x + self.xo,
y : t*self.v.y + self.yo
}
}
this.intersectionLine = function (R) {
var self = this;
var x1 = R.xo;
var y1 = R.yo;
var v1 = R.v;
var _cross = self.v.cross(v1);
if(_cross == 0)
{
return null;
}
switch(true) {
case self.v.x == 0:
var t = (self.xo-x1)/v1.x;
return R.calc(t);
break;
case self.v.y == 0:
var t = (self.yo-y1)/v1.y;
return R.calc(t);
break;
default:
var t = (self.v.y*(self.xo-x1)+self.v.x*(y1-self.yo))/(-_cross);
//var t = (t1*(v1.x)+x1-self.xo)/self.v.x;
return R.calc(t);
break;
}
}
}
function Ray2D (xo,yo,v) {
if(!(v instanceof(Vector2D)))
{
throw new Error('Invalid Argument supplied for constructor "StraightLine2D"');
}
this.xo = xo;
this.yo = yo;
this.v = v;
this.calc = function (t) {
var self = this;
if(t<0) {
return null;
}
return {
x : t*self.v.x + self.xo,
y : t*self.v.y + self.yo
}
}
this.intersectionLine = function (R) {
var self = this;
var x1 = R.xo;
var y1 = R.yo;
var v1 = R.v;
var _cross = self.v.cross(v1);
if(_cross == 0.0)
{
return null;
}
switch(true) {
case self.v.x == 0:
var t = (self.xo-x1)/v1.x;
return t > 0 ? R.calc(t) : null;
break;
case self.v.y == 0:
var t = (self.yo-y1)/v1.y;
return t > 0 ? R.calc(t) : null;
break;
default:
var t1 = ((y1-self.yo)*self.v.x+self.v.y*(self.xo-x1))/(v1.x*self.v.y-v1.y*self.v.x);
var t = (t1*R.v.x+R.xo-self.xo)/self.v.x;
return t >= 0 ? self.calc(t) : null;
break;
}
}
this.intersectionWall = function (W) {
var self = this;
var R = new StraightLine2D(W.x1,W.y1,new Vector2D(W.x1-W.x2,W.y1-W.y2));
var point = self.intersectionLine(R);
if(point &&
point.x <= Math.max(W.x1,W.x2) && point.x >= Math.min(W.x1,W.x2) &&
point.y <= Math.max(W.y1,W.y2) && point.y >= Math.min(W.y1,W.y2))
{
return point;
}
return null;
}
}
EDIT: I tried to cut out the non-relevant part of the code. Synthesis is not one of my qualities, I hope now is more readable.

How to change arrow length for arrow overlay

The Issue
I am using the following example code to draw arrows on google map.
Here is the main overlay function that creates the svg files for the arrow.
// A small directable Arrow overlay for GMaps V3
// Bill Chadwick Feb 2012 after my previous V2 API version
// Free for any use as far as I am concerned
// Some public domain code VML/SVG utility code of Ben Appleton's is reused at the end of this file
var arrowOverlayMarkerCounter; //unique id counter for SVG arrow head markers
function ArrowOverlay(map, location, rotation, color, opacity, tooltip) {
this.map_ = map;
this.location_ = location;
this.rotation_ = rotation || 0.0;
var r = this.rotation_ + 90; //compass to math
this.dx_ = 20 * Math.cos(r * Math.PI / 180); //other end of arrow line to point
this.dy_ = 20 * Math.sin(r * Math.PI / 180);
this.color_ = color || "#0000FF";
this.opacity_ = opacity || 0.7;
this.tooltip_ = tooltip || "";
this.div_ = null;
this.setMap(map);
this.handle_ = null;//click event handler handle
if (arrowOverlayMarkerCounter == null)
arrowOverlayMarkerCounter = 0;
else
arrowOverlayMarkerCounter += 1;
this.svgId_ = "ArrowOverlay" + arrowOverlayMarkerCounter.toString();
}
ArrowOverlay.prototype = new google.maps.OverlayView();
ArrowOverlay.prototype.onAdd = function() {
// Create the DIV and set some basic attributes.
var div = document.createElement('DIV');
div.title = this.tooltip_;
div.style.cursor = "help";
var obj = this;
this.handle_ = google.maps.event.addDomListener(div, 'click', function() { google.maps.event.trigger(obj, "click") });
//set up arrow invariants
if (supportsVML()) {
var l = createVmlElement('v:line', div);
l.strokeweight = "3px";
l.strokecolor = this.color_;
l.style.position = 'absolute';
var s = createVmlElement("v:stroke", l);
s.opacity = this.opacity_;
s.startarrow = "classic"; // or "block", "open" etc see VML spec
this.vmlLine_ = l;
}
else {
// make a 40x40 pixel space centered on the arrow
var svgNS = "http://www.w3.org/2000/svg";
var svgRoot = document.createElementNS(svgNS, "svg");
svgRoot.setAttribute("width", 40);
svgRoot.setAttribute("height", 40);
svgRoot.setAttribute("stroke", this.color_);
svgRoot.setAttribute("fill", this.color_);
svgRoot.setAttribute("stroke-opacity", this.opacity_);
svgRoot.setAttribute("fill-opacity", this.opacity_);
div.appendChild(svgRoot);
var svgNode = document.createElementNS(svgNS, "line");
svgNode.setAttribute("stroke-width", 3);
svgNode.setAttribute("x1", 20);
svgNode.setAttribute("y1", 20);
svgNode.setAttribute("x2", 20 + this.dx_);
svgNode.setAttribute("y2", 20 + this.dy_);
//make a solid arrow head, can't share these, as in SVG1.1 they can't get color from the referencing object, only their parent
//a bit more involved than the VML
if (this.rotation_ >= 0) {
var svgM = document.createElementNS(svgNS, "marker");
svgM.id = this.svgId_;
svgM.setAttribute("viewBox", "0 0 10 10");
svgM.setAttribute("refX", 0);
svgM.setAttribute("refY", 5);
svgM.setAttribute("markerWidth", 4);
svgM.setAttribute("markerHeight", 3);
svgM.setAttribute("orient", "auto");
var svgPath = document.createElementNS(svgNS, "path"); //could share this with 'def' and 'use' but hardly worth it
svgPath.setAttribute("d", "M 10 0 L 0 5 L 10 10 z");
svgM.appendChild(svgPath);
svgRoot.appendChild(svgM);
svgNode.setAttribute("marker-start", "url(#" + this.svgId_ + ")");
}
svgRoot.appendChild(svgNode);
this.svgRoot_ = svgRoot;
this.svgNode_ = svgNode;
}
// Set the overlay's div_ property to this DIV
this.div_ = div;
var panes = this.getPanes();
panes.overlayImage.appendChild(this.div_);
}
ArrowOverlay.prototype.draw = function() {
var overlayProjection = this.getProjection();
var p = overlayProjection.fromLatLngToDivPixel(this.location_);
var div = this.div_;
if (!div)
return;
if (!div.style)
return;
// Calculate the DIV coordinates of the ref point of our arrow
var x2 = p.x + this.dx_;
var y2 = p.y + this.dy_;
if (supportsVML()) {
this.vmlLine_.from = p.x + "px, " + p.y + "px";
this.vmlLine_.to = x2 + "px, " + y2 + "px";
}
else {
this.svgRoot_.setAttribute("style", "position:absolute; top:" + (p.y - 20) + "px; left:" + (p.x - 20) + "px");
}
}
ArrowOverlay.prototype.onRemove = function() {
if (this.handle_ != null) {
google.maps.eventclear.removeListener(this.handle_);
}
this.div_.parentNode.removeChild(this.div_);
}
ArrowOverlay.prototype.setVisible = function(v) {
if (v)
this.show();
else
this.hide();
}
ArrowOverlay.prototype.getVisible = function(v) {
if (this.div_) {
return (this.div_.style.display == "");
}
return false;
}
ArrowOverlay.prototype.hide = function() {
if (this.div_) {
this.div_.style.display = "none";
}
}
ArrowOverlay.prototype.show = function() {
if (this.div_) {
this.div_.style.display = "";
}
}
ArrowOverlay.prototype.setPosition = function(l) {
this.location_ = l;
this.draw();
}
ArrowOverlay.prototype.getPosition = function() {
return this.location_;
}
ArrowOverlay.prototype.setHeading = function(h) {
this.rotation_ = h || 0.0;
var r = this.rotation_ + 90; //compass to math
this.dx_ = 20 * Math.cos(r * Math.PI / 180); //other end of arrow line to point
this.dy_ = 20 * Math.sin(r * Math.PI / 180);
if (!supportsVML()) {
this.svgNode_.setAttribute("x2", 20 + this.dx_);
this.svgNode_.setAttribute("y2", 20 + this.dy_);
}
this.draw();
}
ArrowOverlay.prototype.getHeading = function() {
return this.rotation_;
}
ArrowOverlay.prototype.setTooltip = function(t) {
this.tooltip_ = t;
}
ArrowOverlay.prototype.getTooltip = function() {
return this.tooltip_;
}
ArrowOverlay.prototype.toggle = function() {
if (this.div_) {
if (this.div_.style.visibility == "hidden") {
this.show();
} else {
this.hide();
}
}
}
ArrowOverlay.prototype.fromDivPixelToLatLng = function(x, y) {
var overlayProjection = this.getProjection();
return overlayProjection.fromDivPixelToLatLng(new google.maps.Point(x, y));
}
ArrowOverlay.prototype.fromLatLngToContainerPixel = function(p) {
var overlayProjection = this.getProjection();
return overlayProjection.fromLatLngToContainerPixel(p);
}
// SVG utils from here http://appleton-static.appspot.com/static/simple_poly.js
// by Ben Appleton of Google
var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
function supportsSVG() {
return document.implementation.hasFeature(
'http://www.w3.org/TR/SVG11/feature#Shape',
'1.1');
}
// VML utils from here http://appleton-static.appspot.com/static/simple_poly.js
// by Ben Appleton of Google
var VML_NAMESPACE = 'urn:schemas-microsoft-com:vml';
function createVmlElement(tagName, parent) {
var element = document.createElement(tagName);
parent.appendChild(element);
element.style['behavior'] = 'url(#default#VML)';
return element;
}
function supportsVML() {
if (supportsVML.result_ == null) {
if (!maybeCreateVmlNamespace()) {
return supportsVML.result_ = false;
}
// Create some VML. Its 'adj' property will be an object only when VML
// is enabled.
var div = document.createElement('DIV');
document.body.appendChild(div);
div.innerHtml = '<v:shape id="vml_flag1" adj="1" />';
var child = div.firstChild;
if (child) child.style['behavior'] = 'url(#default#VML)';
supportsVML.result_ = !child || (typeof child['adj'] == 'object');
div.parentNode.removeChild(div);
}
return supportsVML.result_;
}
function maybeCreateVmlNamespace() {
var hasVmlNamespace = false;
if (document.namespaces) {
for (var x = 0; x < document.namespaces.length; x++) {
var ns = document.namespaces(x);
if (ns.name == 'v') {
if (ns.urn == VML_NAMESPACE) {
hasVmlNamespace = true;
} else {
throw new Error('document namespace v: is required for VML ' +
'but has been reserved for ' + ns.urn);
}
}
}
if (!hasVmlNamespace) {
// Import namespace
hasVmlNamespace = true;
document.namespaces.add('v', VML_NAMESPACE);
}
}
return hasVmlNamespace;
}
Question
I can not figure out how to change the length of those arrow? Can I somehow add a length parameter to the function ArrowOverlay?
I would do something like this (scale also works with x, y):
<svg>
<g id="idG" transform="scale(1, 1)">
..your arrow
</g>
</svg>
document.getElementById("idG").setAttribute("transform", "scale(" + horizontalScale + "," + verticalScale + ")");
Example: http://k8.no-ip.org/stackoverflow/13540341.htm

Categories