commit
b58d52529b
17 changed files with 1093 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||||||
|
.idea |
||||||
|
|
@ -0,0 +1,6 @@ |
|||||||
|
{ |
||||||
|
"display_name": "Pathfinder", |
||||||
|
"info_text": "A graphical demonstration of some pathfinder algorithms", |
||||||
|
"visible": true, |
||||||
|
"tags": ["Simulation", "Tool"] |
||||||
|
} |
After Width: | Height: | Size: 318 B |
@ -0,0 +1,321 @@ |
|||||||
|
class Algorithm{ |
||||||
|
|
||||||
|
constructor(){ |
||||||
|
this.startNode = game.startNode; |
||||||
|
this.targetNode = game.targetNode; |
||||||
|
|
||||||
|
this.grid = game.grid; |
||||||
|
|
||||||
|
this.startDate = new Date(); |
||||||
|
this.timeDiff = 0; |
||||||
|
} |
||||||
|
|
||||||
|
get running(){ |
||||||
|
return !this.hasSucceeded && !this.hasFailed; |
||||||
|
} |
||||||
|
|
||||||
|
display(){ |
||||||
|
push(); |
||||||
|
scale(1 / this.grid.nodeSize.x, 1 / this.grid.nodeSize.y); |
||||||
|
fill(0); |
||||||
|
textSize(20); |
||||||
|
textAlign(CENTER, TOP); |
||||||
|
text('Time: ' + this.timeDiff / 1000 + 's', this.grid.size / 2, -this.grid.nodeSize.y / 2 - 50); |
||||||
|
pop(); |
||||||
|
} |
||||||
|
|
||||||
|
update(){ |
||||||
|
this.timeDiff = new Date().getTime() - this.startDate.getTime(); |
||||||
|
} |
||||||
|
|
||||||
|
success(){ |
||||||
|
console.log('Success!'); |
||||||
|
this.hasSucceeded = true; |
||||||
|
} |
||||||
|
|
||||||
|
failed(){ |
||||||
|
console.log('Failed!'); |
||||||
|
this.hasFailed = true; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function startAlgorithm(){ |
||||||
|
switch ($('#algorithm_type').val()){ |
||||||
|
case 'astar': |
||||||
|
game.start(new AStar()); |
||||||
|
break; |
||||||
|
case 'dijkstra': |
||||||
|
game.start(new Dijkstra()); |
||||||
|
break; |
||||||
|
case 'dstar': |
||||||
|
game.start(new DStar()); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
class DStar extends Algorithm{ |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super(); |
||||||
|
|
||||||
|
this.openList = [] |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
class AStar extends Algorithm{ |
||||||
|
|
||||||
|
constructor(){ |
||||||
|
super(); |
||||||
|
|
||||||
|
this.startNode.f = 0; |
||||||
|
this.startNode.g = 0; |
||||||
|
|
||||||
|
this.openList = [this.startNode]; |
||||||
|
this.closedList = []; |
||||||
|
} |
||||||
|
|
||||||
|
get minFNodeIndex(){ |
||||||
|
let f = this.openList[0].f, index = 0; |
||||||
|
this.openList.forEach((n, i) => { |
||||||
|
if (n.f < f){ |
||||||
|
f = n.f; |
||||||
|
index = i; |
||||||
|
} |
||||||
|
}); |
||||||
|
return index; |
||||||
|
} |
||||||
|
|
||||||
|
update(){ |
||||||
|
if (!this.running){ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
super.update(); |
||||||
|
|
||||||
|
if (!this.openList.length){ |
||||||
|
this.failed(); |
||||||
|
} else { |
||||||
|
let index = this.minFNodeIndex; |
||||||
|
let currentNode = this.openList[index]; |
||||||
|
this.openList.splice(index, 1); |
||||||
|
|
||||||
|
if (currentNode === this.targetNode){ |
||||||
|
this.success(); |
||||||
|
} else { |
||||||
|
this.closedList.push(currentNode); |
||||||
|
|
||||||
|
currentNode.successors.forEach(successor => { |
||||||
|
|
||||||
|
if (this.closedList.find(n => n === successor)){ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
let provisionalG = currentNode.g + currentNode.pos.dist(successor.pos); |
||||||
|
if (this.openList.find(n => n === successor) && provisionalG >= successor.g){ |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
successor.predecessor = currentNode |
||||||
|
successor.g = provisionalG; |
||||||
|
|
||||||
|
successor.f = provisionalG + successor.distanceToTarget; |
||||||
|
if (!this.openList.find(n => n === successor)){ |
||||||
|
this.openList.push(successor); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
display(appearance, isInformationVisible, pathSmoothness){ |
||||||
|
super.display(); |
||||||
|
|
||||||
|
if (isInformationVisible){ |
||||||
|
|
||||||
|
if (appearance === 'Circles'){ |
||||||
|
let r = 0.5; |
||||||
|
|
||||||
|
fill(200); |
||||||
|
this.openList.forEach(n => { |
||||||
|
ellipse(n.pos.x, n.pos.y, r * 2); |
||||||
|
}); |
||||||
|
|
||||||
|
fill(100); |
||||||
|
this.closedList.forEach(n => { |
||||||
|
if (n === this.startNode) |
||||||
|
return; |
||||||
|
ellipse(n.pos.x, n.pos.y, r * 2); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
if (appearance === 'Rectangles'){ |
||||||
|
fill(200); |
||||||
|
this.openList.forEach(n => { |
||||||
|
rect(n.pos.x - 0.5, n.pos.y - 0.5, 1, 1); |
||||||
|
}); |
||||||
|
|
||||||
|
fill(100); |
||||||
|
this.closedList.forEach(n => { |
||||||
|
if (n === this.startNode) |
||||||
|
return; |
||||||
|
rect(n.pos.x - 0.5, n.pos.y - 0.5, 1, 1); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
noFill(); |
||||||
|
stroke(50, 255, 50); |
||||||
|
strokeWeight(0.2); |
||||||
|
|
||||||
|
let node; |
||||||
|
if (!this.openList.length){ |
||||||
|
node = this.closedList[this.closedList.length - 1]; |
||||||
|
} else { |
||||||
|
node = this.openList[this.minFNodeIndex]; |
||||||
|
} |
||||||
|
if (this.hasSucceeded){ |
||||||
|
node = this.targetNode; |
||||||
|
} |
||||||
|
if (node){ |
||||||
|
|
||||||
|
if (pathSmoothness){ |
||||||
|
beginShape(); |
||||||
|
vertex(node.pos.x, node.pos.y); |
||||||
|
while (node.predecessor){ |
||||||
|
node = node.predecessor; |
||||||
|
|
||||||
|
if (!node.predecessor){ |
||||||
|
quadraticVertex(node.pos.x, node.pos.y, node.pos.x, node.pos.y); |
||||||
|
endShape(); |
||||||
|
} else { |
||||||
|
let cx = (node.pos.x + node.predecessor.pos.x) / 2; |
||||||
|
let cy = (node.pos.y + node.predecessor.pos.y) / 2; |
||||||
|
quadraticVertex(node.pos.x, node.pos.y, cx, cy); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
while (node.predecessor){ |
||||||
|
line(node.pos.x, node.pos.y, node.predecessor.pos.x, node.predecessor.pos.y); |
||||||
|
node = node.predecessor; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class Dijkstra extends Algorithm{ |
||||||
|
|
||||||
|
constructor(){ |
||||||
|
super(); |
||||||
|
|
||||||
|
this.nodes = this.grid.allowedNodes.slice(); |
||||||
|
this.nodes.forEach(n => n.distance = Infinity); |
||||||
|
this.startNode.distance = 0; |
||||||
|
} |
||||||
|
|
||||||
|
get minDistanceNodeIndex(){ |
||||||
|
let index = 0, distance = this.nodes[0].distance; |
||||||
|
this.nodes.forEach((n, i) => { |
||||||
|
if (n.distance < distance){ |
||||||
|
index = i; |
||||||
|
distance = n.distance; |
||||||
|
} |
||||||
|
}); |
||||||
|
return index; |
||||||
|
} |
||||||
|
|
||||||
|
update(){ |
||||||
|
if (!this.running){ |
||||||
|
return; |
||||||
|
} |
||||||
|
super.update(); |
||||||
|
|
||||||
|
let index = this.minDistanceNodeIndex; |
||||||
|
let currentNode = this.nodes[index]; |
||||||
|
|
||||||
|
this.nodes.splice(index, 1); |
||||||
|
|
||||||
|
if (currentNode === this.targetNode){ |
||||||
|
this.success(); |
||||||
|
} |
||||||
|
|
||||||
|
currentNode.successors.forEach(s => { |
||||||
|
if (this.nodes.find(n => n === s)){ |
||||||
|
let alternativeDistance = currentNode.distance + currentNode.pos.dist(s.pos); |
||||||
|
if (alternativeDistance < s.distance){ |
||||||
|
s.distance = alternativeDistance; |
||||||
|
s.predecessor = currentNode; |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
display(appearance, isInformationVisible, pathSmoothness){ |
||||||
|
super.display(); |
||||||
|
|
||||||
|
if (isInformationVisible){ |
||||||
|
if (appearance === 'Circles'){ |
||||||
|
let r = 0.5; |
||||||
|
fill(100); |
||||||
|
this.nodes.forEach(n => { |
||||||
|
if (n === this.startNode || n === this.targetNode) |
||||||
|
return; |
||||||
|
ellipse(n.pos.x, n.pos.y, r * 2); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
if (appearance === 'Rectangles'){ |
||||||
|
fill(100); |
||||||
|
this.nodes.forEach(n => { |
||||||
|
if (n === this.startNode || n === this.targetNode) |
||||||
|
return; |
||||||
|
rect(n.pos.x - 0.5, n.pos.y - 0.5, 1, 1); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
noFill(); |
||||||
|
stroke(50, 255, 50); |
||||||
|
strokeWeight(0.2); |
||||||
|
|
||||||
|
let node = this.nodes[this.minDistanceNodeIndex]; |
||||||
|
if (this.hasSucceeded){ |
||||||
|
node = this.targetNode; |
||||||
|
} |
||||||
|
if (node){ |
||||||
|
|
||||||
|
if (pathSmoothness){ |
||||||
|
beginShape(); |
||||||
|
vertex(node.pos.x, node.pos.y); |
||||||
|
while (node.predecessor){ |
||||||
|
node = node.predecessor; |
||||||
|
|
||||||
|
if (!node.predecessor){ |
||||||
|
quadraticVertex(node.pos.x, node.pos.y, node.pos.x, node.pos.y); |
||||||
|
endShape(); |
||||||
|
} else { |
||||||
|
let cx = (node.pos.x + node.predecessor.pos.x) / 2; |
||||||
|
let cy = (node.pos.y + node.predecessor.pos.y) / 2; |
||||||
|
quadraticVertex(node.pos.x, node.pos.y, cx, cy); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
while (node.predecessor){ |
||||||
|
line(node.pos.x, node.pos.y, node.predecessor.pos.x, node.predecessor.pos.y); |
||||||
|
node = node.predecessor; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
function keyPressed(){
|
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function keyReleased(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function mouseMoved(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function mouseDragged(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function mousePressed(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function mouseReleased(){ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function windowResized(){ |
||||||
|
resizeCanvas($('#canvas-holder').width(), $('#canvas-holder').height()); |
||||||
|
} |
@ -0,0 +1,111 @@ |
|||||||
|
class Game{ |
||||||
|
|
||||||
|
constructor(){ |
||||||
|
this.generateGrid(); |
||||||
|
this.updateVelocity(); |
||||||
|
this.updateDifficulty(); |
||||||
|
this.updateVisibilities(); |
||||||
|
} |
||||||
|
|
||||||
|
generateGrid(){ |
||||||
|
this.algorithm = null; |
||||||
|
|
||||||
|
let count = parseInt($('#cell_count').val()); |
||||||
|
let movement = $('#movement').val(); |
||||||
|
let difficulty = parseInt($('#difficulty').val()); |
||||||
|
let hasRandomDirection = $('#direction').is(':checked'); |
||||||
|
|
||||||
|
this.grid = new Grid(localSettings.app.grid, count, difficulty, hasRandomDirection); |
||||||
|
$('#cell_count_text').text(count + 'x' + count + ' nodes'); |
||||||
|
|
||||||
|
//Two main nodes, start and target
|
||||||
|
if (hasRandomDirection){ |
||||||
|
this.startNode = random(this.grid.allowedNodes); |
||||||
|
this.targetNode = random(this.grid.getAllowedNodesDistantTo(this.startNode) |
||||||
|
.filter(n => { |
||||||
|
if (movement === '1'){ |
||||||
|
return n.pos.x % 2 === this.startNode.pos.x % 2 |
||||||
|
&& n.pos.y % 2 === this.startNode.pos.y % 2; |
||||||
|
} |
||||||
|
return true; |
||||||
|
}) |
||||||
|
); |
||||||
|
} else { |
||||||
|
this.startNode = this.grid.nodes[0][0]; |
||||||
|
this.targetNode = this.grid.nodes[count - 1][count - 1]; |
||||||
|
} |
||||||
|
|
||||||
|
this.updateTester = 0; |
||||||
|
} |
||||||
|
|
||||||
|
update(){ |
||||||
|
if (this.algorithm){ |
||||||
|
this.updateTester += this.movesPerSecond / frameRate(); |
||||||
|
let i = 0; |
||||||
|
for (; i < floor(this.updateTester); i++){ |
||||||
|
this.algorithm.update(); |
||||||
|
} |
||||||
|
this.updateTester -= i; |
||||||
|
}
|
||||||
|
} |
||||||
|
|
||||||
|
display(){ |
||||||
|
this.grid.display(this.appearance, this.linesAreVisible); |
||||||
|
|
||||||
|
if (this.appearance === 'Circles'){ |
||||||
|
let r = 0.5; |
||||||
|
fill(50, 50, 255); |
||||||
|
ellipse(this.startNode.pos.x, this.startNode.pos.y, r * 2); |
||||||
|
fill(50, 255, 50); |
||||||
|
ellipse(this.targetNode.pos.x, this.targetNode.pos.y, r * 2); |
||||||
|
} |
||||||
|
if (this.appearance === 'Rectangles'){ |
||||||
|
fill(50, 50, 255); |
||||||
|
rect(this.startNode.pos.x - 0.5, this.startNode.pos.y - 0.5, 1, 1); |
||||||
|
fill(50, 255, 50); |
||||||
|
rect(this.targetNode.pos.x - 0.5, this.targetNode.pos.y - 0.5, 1, 1); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
if (this.algorithm) |
||||||
|
this.algorithm.display(this.appearance, this.isInformationVisible, this.pathSmoothness); |
||||||
|
} |
||||||
|
|
||||||
|
start(algorithm){ |
||||||
|
this.algorithm = algorithm; |
||||||
|
|
||||||
|
let movement = $('#movement').val(); |
||||||
|
this.grid.nodes.forEach(x => x.forEach(n => { |
||||||
|
n.setMovement(movement); |
||||||
|
n.connectToSuccessors(this.grid.nodes); |
||||||
|
})); |
||||||
|
} |
||||||
|
|
||||||
|
updateVelocity(){ |
||||||
|
let value = parseInt($('#velocity').val()); |
||||||
|
$('#velocity_text').text(value + ' moves per second'); |
||||||
|
this.movesPerSecond = value; |
||||||
|
} |
||||||
|
|
||||||
|
updateDifficulty(){ |
||||||
|
let value = parseInt($('#difficulty').val()); |
||||||
|
$('#difficulty_text').text('Difficulty: ' + value); |
||||||
|
this.generateGrid(); |
||||||
|
} |
||||||
|
|
||||||
|
updateVisibilities(){ |
||||||
|
let appearance = $('input[name=appearance]:checked').val(); |
||||||
|
let displayGrid = $('#gridVisibility').is(':checked'); |
||||||
|
let displayInformation = $('#information').is(':checked'); |
||||||
|
let pathSmoothness = $('#pathSmoothness').is(':checked'); |
||||||
|
|
||||||
|
if (!appearance) |
||||||
|
appearance = $('input[name=appearance]:eq(0)').val(); |
||||||
|
|
||||||
|
this.appearance = appearance; |
||||||
|
this.linesAreVisible = displayGrid; |
||||||
|
this.isInformationVisible = displayInformation; |
||||||
|
this.pathSmoothness = pathSmoothness; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,160 @@ |
|||||||
|
class Grid{ |
||||||
|
|
||||||
|
constructor(settings, cellCount, difficulty, randomDirection){ |
||||||
|
|
||||||
|
this.margin = settings.margin; |
||||||
|
|
||||||
|
this.cellCount = createVector(cellCount, cellCount); |
||||||
|
|
||||||
|
this.nodes = []; |
||||||
|
for (let x = 0; x < this.cellCount.x; x++){ |
||||||
|
let column = []; |
||||||
|
for (let y = 0; y < this.cellCount.y; y++){ |
||||||
|
let forbidden = ranBool(7 - difficulty); |
||||||
|
if (!randomDirection){ |
||||||
|
let allowed = [ |
||||||
|
{x: 0, y: 0}, |
||||||
|
{x: 1, y: 0}, |
||||||
|
{x: 1, y: 1}, |
||||||
|
{x: 0, y: 1}, |
||||||
|
{x: this.cellCount.x - 1, y: this.cellCount.y - 1}, |
||||||
|
{x: this.cellCount.x - 1, y: this.cellCount.y - 2}, |
||||||
|
{x: this.cellCount.x - 2, y: this.cellCount.y - 2}, |
||||||
|
{x: this.cellCount.x - 2, y: this.cellCount.y - 1}, |
||||||
|
]; |
||||||
|
allowed.forEach(a => { |
||||||
|
if (x === a.x && y === a.y){ |
||||||
|
forbidden = false; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
column.push(new PathNode(x, y, forbidden)); |
||||||
|
} |
||||||
|
this.nodes.push(column); |
||||||
|
} |
||||||
|
|
||||||
|
this.nodes.forEach(x => x.forEach(n => n.connectToSuccessors(this.nodes))); |
||||||
|
|
||||||
|
let nodes = []; |
||||||
|
this.nodes.forEach(x => nodes.push(...x.filter(n => n.isForbidden))); |
||||||
|
this.forbiddenNodes = nodes; |
||||||
|
|
||||||
|
nodes = []; |
||||||
|
this.nodes.forEach(x => nodes.push(...x.filter(n => !n.isForbidden))); |
||||||
|
this.allowedNodes = nodes; |
||||||
|
} |
||||||
|
|
||||||
|
get size(){ |
||||||
|
return Math.min(width - this.margin * 2, height - this.margin * 2); |
||||||
|
} |
||||||
|
|
||||||
|
get nodeSize(){ |
||||||
|
return { |
||||||
|
x: this.size / this.cellCount.x, |
||||||
|
y: this.size / this.cellCount.y |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
display(appearance, linesAreVisible){ |
||||||
|
translate(this.margin, this.margin); |
||||||
|
|
||||||
|
if (linesAreVisible){ |
||||||
|
let size = this.size; |
||||||
|
stroke(100); |
||||||
|
strokeWeight(1); |
||||||
|
for (let x = 0; x <= size + 1; x += size / this.cellCount.x){ |
||||||
|
line(x, 0, x, size); |
||||||
|
} |
||||||
|
for (let y = 0; y <= size + 1; y += size / this.cellCount.y){ |
||||||
|
line(0, y, size, y); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
translate(this.nodeSize.x / 2, this.nodeSize.y / 2); |
||||||
|
scale(this.nodeSize.x, this.nodeSize.y); |
||||||
|
|
||||||
|
noStroke(); |
||||||
|
fill(200, 50, 50); |
||||||
|
for (let n of this.forbiddenNodes){ |
||||||
|
let x = n.pos.x; |
||||||
|
let y = n.pos.y; |
||||||
|
|
||||||
|
if (appearance === 'Circles'){ |
||||||
|
let r = 0.5; |
||||||
|
ellipse(x, y, r * 2); |
||||||
|
} |
||||||
|
if (appearance === 'Rectangles'){ |
||||||
|
rect(x - 0.5, y - 0.5, 1, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
getAllowedNodesDistantTo(otherNode){ |
||||||
|
return this.allowedNodes.filter(n => n.pos.dist(otherNode.pos) > Math.max(this.cellCount.x, this.cellCount.y) / 2); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
class PathNode{ |
||||||
|
|
||||||
|
constructor(x, y, isForbidden){ |
||||||
|
this.pos = createVector(x, y); |
||||||
|
this.isForbidden = isForbidden; |
||||||
|
} |
||||||
|
|
||||||
|
get distanceToTarget(){ |
||||||
|
return this.pos.dist(game.targetNode.pos); |
||||||
|
} |
||||||
|
|
||||||
|
setMovement(movement){ |
||||||
|
this.isDiagonalAllowed = false; |
||||||
|
this.isStraightAllowed = false; |
||||||
|
switch (movement){ |
||||||
|
case "0": |
||||||
|
this.isStraightAllowed = true; |
||||||
|
break; |
||||||
|
case "1": |
||||||
|
this.isDiagonalAllowed = true; |
||||||
|
break; |
||||||
|
case "2": |
||||||
|
this.isStraightAllowed = true; |
||||||
|
this.isDiagonalAllowed = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
connectToSuccessors(nodes){ |
||||||
|
let allowed = [], successors = []; |
||||||
|
if (this.isStraightAllowed){ |
||||||
|
allowed.push( |
||||||
|
{x: 1, y: 0}, |
||||||
|
{x: 0, y: 1}, |
||||||
|
{x: -1, y: 0}, |
||||||
|
{x: 0, y: -1} |
||||||
|
); |
||||||
|
} |
||||||
|
if (this.isDiagonalAllowed){ |
||||||
|
allowed.push( |
||||||
|
{x: 1, y: 1}, |
||||||
|
{x: 1, y: -1}, |
||||||
|
{x: -1, y: 1}, |
||||||
|
{x: -1, y: -1} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
allowed.forEach(a => { |
||||||
|
let column = nodes[this.pos.x + a.x]; |
||||||
|
if (column){ |
||||||
|
let node = column[this.pos.y + a.y]; |
||||||
|
if (node){ |
||||||
|
if (!node.isForbidden){ |
||||||
|
successors.push(node); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
this.successors = successors; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
let debug = false, |
||||||
|
font, |
||||||
|
localSettings, |
||||||
|
loader; |
||||||
|
|
||||||
|
//Only for online games
|
||||||
|
let socket; |
||||||
|
|
||||||
|
let antiCacheQuery = '?_=' + new Date().getTime(); |
||||||
|
|
||||||
|
let game; |
||||||
|
|
||||||
|
function preload(){ |
||||||
|
localSettings = loadJSON('data/settings/settings.json' + antiCacheQuery, json => { |
||||||
|
console.log('Local settings loaded: ', json); |
||||||
|
}, error => { |
||||||
|
console.log('Local settings failed: ', error); |
||||||
|
}); |
||||||
|
|
||||||
|
font = loadFont('data/styles/font.ttf', json => { |
||||||
|
console.log('Local font loaded: ', json); |
||||||
|
}, error => { |
||||||
|
console.log('Local font failed: ', error); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function setup(){ |
||||||
|
canvasSetup(); |
||||||
|
interfaceSetup(); |
||||||
|
|
||||||
|
loadDynamicScripts().then(() => game = new Game()); |
||||||
|
} |
||||||
|
|
||||||
|
function draw(){ |
||||||
|
|
||||||
|
background(40); |
||||||
|
|
||||||
|
if (game){ |
||||||
|
game.update(); |
||||||
|
game.display(); |
||||||
|
} |
||||||
|
|
||||||
|
if (loader){ |
||||||
|
loader.update(); |
||||||
|
loader.display(); |
||||||
|
} |
||||||
|
|
||||||
|
if (debug) debugInformation(); |
||||||
|
} |
||||||
|
|
||||||
|
function canvasSetup(){ |
||||||
|
setFrameRate(60); |
||||||
|
let w = $('#canvas-holder').width(), |
||||||
|
h = $('#canvas-holder').height(); |
||||||
|
let canvas = createCanvas(w, h); |
||||||
|
canvas.parent('canvas-holder'); |
||||||
|
textFont(font); |
||||||
|
} |
||||||
|
|
||||||
|
function interfaceSetup(){ |
||||||
|
$('#cell_count').attr({ |
||||||
|
'min': localSettings.app.grid.size.min,
|
||||||
|
'max': localSettings.app.grid.size.max |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function loadDynamicScripts(){ |
||||||
|
|
||||||
|
return httpGet('data/settings/libraries.json' + antiCacheQuery, 'json') |
||||||
|
.then(json => { |
||||||
|
let requests = []; |
||||||
|
for (let script in json){ |
||||||
|
if (json[script]){ |
||||||
|
let url = '/lib/benjocraeft/' + script + '.js' |
||||||
|
requests.push($.getScript(url, () => { |
||||||
|
console.log('Successfully loaded script: ', url) |
||||||
|
})); |
||||||
|
} |
||||||
|
} |
||||||
|
return $.when(...requests); |
||||||
|
}) |
||||||
|
.then(() => { |
||||||
|
console.log("All dynamic scripts have been loaded!"); |
||||||
|
}); |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
'use strict'; |
||||||
|
|
||||||
|
function socketConnect(project, name = "noone"){ |
||||||
|
let urlQueries = '?game=' + project.name + '&name=' + name; |
||||||
|
let port = 3000; |
||||||
|
let url = location.hostname + ":" + port + urlQueries; |
||||||
|
|
||||||
|
socket = io.connect(url); |
||||||
|
socket.on('connect', () => console.log('Connected to ', url)); |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
{ |
||||||
|
"collision": false, |
||||||
|
"colorPicker": false, |
||||||
|
"cookie": false, |
||||||
|
"loader": false, |
||||||
|
"prototypes": false, |
||||||
|
"technical": true |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
{ |
||||||
|
"project": { |
||||||
|
"name": "pathfinder", |
||||||
|
"author": "BenjoCraeft", |
||||||
|
"version": "1.0.0", |
||||||
|
"playerCounts": [], |
||||||
|
"online": { |
||||||
|
"iceServers": [ |
||||||
|
{"urls": "stun:stun.l.google.com:19302"}, |
||||||
|
{ |
||||||
|
"urls": "turn:numb.viagenie.ca", |
||||||
|
"credential": "muazkh", |
||||||
|
"username": "webrtc@live.com" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
}, |
||||||
|
"frameWork": { |
||||||
|
"frameRate": 60, |
||||||
|
"width": null, |
||||||
|
"height": null |
||||||
|
}, |
||||||
|
"app": { |
||||||
|
"grid": { |
||||||
|
"margin": 50, |
||||||
|
"size": { |
||||||
|
"min": 20, |
||||||
|
"max": 100 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,88 @@ |
|||||||
|
#color_picker{ |
||||||
|
width: 300px; |
||||||
|
height: 25%; |
||||||
|
margin: 20px; |
||||||
|
margin-top: 50px; |
||||||
|
border: 5px solid #000; |
||||||
|
background-color: #000; |
||||||
|
-webkit-user-select: none; |
||||||
|
-moz-user-select: none; |
||||||
|
-ms-user-select: none; |
||||||
|
user-select: none; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
#color_picker_numeric{ |
||||||
|
width: 80%; |
||||||
|
padding: 5%; |
||||||
|
margin: 5%; |
||||||
|
background-color: #888; |
||||||
|
border-radius: 10px; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
.color_picker_rgb{ |
||||||
|
float: left; |
||||||
|
width: 22%; |
||||||
|
height: 35px; |
||||||
|
font-size: 25px; |
||||||
|
color: #000; |
||||||
|
} |
||||||
|
.color_picker_rgb:nth-child(1){ |
||||||
|
margin-right: 10%; |
||||||
|
margin-left: 3%; |
||||||
|
background-color: #F00; |
||||||
|
|
||||||
|
} |
||||||
|
.color_picker_rgb:nth-child(2){ |
||||||
|
background-color: #0F0; |
||||||
|
} |
||||||
|
.color_picker_rgb:nth-child(3){ |
||||||
|
margin-left: 10%; |
||||||
|
background-color: #00F; |
||||||
|
color: #FFF; |
||||||
|
} |
||||||
|
#color_picker_hex{ |
||||||
|
width: 50%; |
||||||
|
height: 30px; |
||||||
|
font-size: 25px; |
||||||
|
margin: 10% 25% 0 25%; |
||||||
|
} |
||||||
|
#saturation{ |
||||||
|
position: relative; |
||||||
|
width: calc(100% - 33px); |
||||||
|
height: 100%; |
||||||
|
background: linear-gradient(to right, #FFF 0%, #F00 100%); |
||||||
|
float: left; |
||||||
|
margin-right: 6px; |
||||||
|
} |
||||||
|
#value { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
background: linear-gradient(to top, #000 0%, rgba(255,255,255,0) 100%); |
||||||
|
} |
||||||
|
#sb_picker{ |
||||||
|
border: 2px solid; |
||||||
|
border-color: #FFF; |
||||||
|
position: absolute; |
||||||
|
width: 14px; |
||||||
|
height: 14px; |
||||||
|
border-radius: 10px; |
||||||
|
bottom: 50px; |
||||||
|
left: 50px; |
||||||
|
box-sizing: border-box; |
||||||
|
z-index: 10; |
||||||
|
} |
||||||
|
#hue { |
||||||
|
width: 27px; |
||||||
|
height: 100%; |
||||||
|
position: relative; |
||||||
|
float: left; |
||||||
|
background: linear-gradient(to bottom, #F00 0%, #F0F 17%, #00F 34%, #0FF 50%, #0F0 67%, #FF0 84%, #F00 100%); |
||||||
|
} |
||||||
|
#hue_picker { |
||||||
|
position: absolute; |
||||||
|
background: #000; |
||||||
|
border-bottom: 1px solid #000; |
||||||
|
top: 0; |
||||||
|
width: 27px; |
||||||
|
height: 2px; |
||||||
|
} |
Binary file not shown.
@ -0,0 +1,88 @@ |
|||||||
|
input[type=range] { |
||||||
|
-webkit-appearance: none; |
||||||
|
margin: 18px 0; |
||||||
|
width: 100%; |
||||||
|
background: none; |
||||||
|
} |
||||||
|
input[type=range]:focus { |
||||||
|
outline: none; |
||||||
|
} |
||||||
|
input[type=range]::-webkit-slider-runnable-track { |
||||||
|
width: 100%; |
||||||
|
height: 8.4px; |
||||||
|
cursor: pointer; |
||||||
|
animate: 0.2s; |
||||||
|
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||||
|
background: #3071a9; |
||||||
|
border-radius: 1.3px; |
||||||
|
border: 0.2px solid #010101; |
||||||
|
} |
||||||
|
input[type=range]::-webkit-slider-thumb { |
||||||
|
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||||
|
border: 1px solid #000000; |
||||||
|
height: 36px; |
||||||
|
width: 16px; |
||||||
|
border-radius: 3px; |
||||||
|
background: #ffffff; |
||||||
|
cursor: pointer; |
||||||
|
-webkit-appearance: none; |
||||||
|
margin-top: -14px; |
||||||
|
} |
||||||
|
input[type=range]:focus::-webkit-slider-runnable-track { |
||||||
|
background: #367ebd; |
||||||
|
} |
||||||
|
input[type=range]::-moz-range-track { |
||||||
|
width: 100%; |
||||||
|
height: 8.4px; |
||||||
|
cursor: pointer; |
||||||
|
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||||
|
background: #3071a9; |
||||||
|
border-radius: 1.3px; |
||||||
|
border: 0.2px solid #010101; |
||||||
|
} |
||||||
|
input[type=range]::-moz-range-thumb { |
||||||
|
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||||
|
border: 1px solid #000000; |
||||||
|
height: 36px; |
||||||
|
width: 16px; |
||||||
|
border-radius: 3px; |
||||||
|
background: #302a86; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
input[type=range]::-ms-track { |
||||||
|
width: 100%; |
||||||
|
height: 8.4px; |
||||||
|
cursor: pointer; |
||||||
|
animate: 0.2s; |
||||||
|
background: transparent; |
||||||
|
border-color: transparent; |
||||||
|
border-width: 16px 0; |
||||||
|
color: transparent; |
||||||
|
} |
||||||
|
input[type=range]::-ms-fill-lower { |
||||||
|
background: #2a6495; |
||||||
|
border: 0.2px solid #010101; |
||||||
|
border-radius: 2.6px; |
||||||
|
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||||
|
} |
||||||
|
input[type=range]::-ms-fill-upper { |
||||||
|
background: #3071a9; |
||||||
|
border: 0.2px solid #010101; |
||||||
|
border-radius: 2.6px; |
||||||
|
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||||
|
} |
||||||
|
input[type=range]::-ms-thumb { |
||||||
|
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||||
|
border: 1px solid #000000; |
||||||
|
height: 36px; |
||||||
|
width: 16px; |
||||||
|
border-radius: 3px; |
||||||
|
background: #ffffff; |
||||||
|
cursor: pointer; |
||||||
|
} |
||||||
|
input[type=range]:focus::-ms-fill-lower { |
||||||
|
background: #3071a9; |
||||||
|
} |
||||||
|
input[type=range]:focus::-ms-fill-upper { |
||||||
|
background: #367ebd; |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<script src="https://code.jquery.com/jquery-3.6.4.min.js" type="text/javascript"></script> |
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/init.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/events.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/online.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/game.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/algorithm.js" type="text/javascript"></script> |
||||||
|
<script src="data/scripts/grid.js" type="text/javascript"></script> |
||||||
|
<link href="styles.css" rel="stylesheet"> |
||||||
|
<link href="data/styles/color_picker.css" rel="stylesheet"> |
||||||
|
<link href="data/styles/range_input.css" rel="stylesheet"> |
||||||
|
<title>Pathfinding Algorithms</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="p5_loading"></div> |
||||||
|
<div id="content"> |
||||||
|
<div id="canvas-holder"></div> |
||||||
|
<div id="interface"> |
||||||
|
<form onchange="game.updateVisibilities();"> |
||||||
|
<label for="circles">Circles</label> |
||||||
|
<input id="circles" name="appearance" type="radio" value="Circles"> |
||||||
|
<br> |
||||||
|
<label for="rectangles">Rectangles</label> |
||||||
|
<input id="rectangles" name="appearance" type="radio" value="Rectangles"> |
||||||
|
</form> |
||||||
|
<div onchange="game.updateVisibilities();"> |
||||||
|
<label for="pathSmoothness">Smooth path: </label> |
||||||
|
<input id="pathSmoothness" type="checkbox"> |
||||||
|
</div> |
||||||
|
<div onchange="game.updateVisibilities();"> |
||||||
|
<label for="gridVisibility">Grid: </label> |
||||||
|
<input id="gridVisibility" type="checkbox"> |
||||||
|
</div> |
||||||
|
<div onchange="game.updateVisibilities();"> |
||||||
|
<label for="information">Additional information: </label> |
||||||
|
<input id="information" type="checkbox"> |
||||||
|
</div> |
||||||
|
<div onchange="game.generateGrid();"> |
||||||
|
<label for="direction">Random direction: </label> |
||||||
|
<input id="direction" type="checkbox"> |
||||||
|
</div> |
||||||
|
<select id="movement"> |
||||||
|
<option value="0">Only Straight</option> |
||||||
|
<option value="1">Only Diagonal</option> |
||||||
|
<option value="2">Straight and Diagonal</option> |
||||||
|
</select> |
||||||
|
<input id="difficulty" max="5" min="1" oninput="game.updateDifficulty()" type="range"> |
||||||
|
<span id="difficulty_text"></span> |
||||||
|
<input id="velocity" max="60" min="1" oninput="game.updateVelocity()" type="range"> |
||||||
|
<span id="velocity_text"></span> |
||||||
|
<input id="cell_count" onchange="game.generateGrid();" type="range"> |
||||||
|
<span id="cell_count_text"></span> |
||||||
|
<select id="algorithm_type"> |
||||||
|
<option value="astar">A* Algorithm</option> |
||||||
|
<option value="dijkstra">Dijkstra Algorithm</option> |
||||||
|
</select> |
||||||
|
<button onclick="startAlgorithm();">Start</button> |
||||||
|
<button onclick="game.generateGrid();">Generate</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,85 @@ |
|||||||
|
a:link, a:hover, a:active, a:visited{color: #000;} |
||||||
|
|
||||||
|
html, body{margin: 0; padding: 0; height: 100%; width: 100%;} |
||||||
|
|
||||||
|
canvas{margin: 0; padding: 0; border: none; display: block;} |
||||||
|
|
||||||
|
button:hover{cursor: pointer;} |
||||||
|
|
||||||
|
@font-face{ |
||||||
|
font-family: "Rametto"; |
||||||
|
src: url("data/styles/font.ttf"); |
||||||
|
} |
||||||
|
|
||||||
|
*{ |
||||||
|
font-family: "Rametto", serif; |
||||||
|
color: #000; |
||||||
|
font-size: 17px; |
||||||
|
} |
||||||
|
|
||||||
|
:root{ |
||||||
|
--width: 100vw; |
||||||
|
--height: 100vh; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Standard styles |
||||||
|
*/ |
||||||
|
|
||||||
|
#canvas-holder{ |
||||||
|
position: relative; |
||||||
|
width: 100vh; |
||||||
|
height: 100vh; |
||||||
|
float: left; |
||||||
|
} |
||||||
|
#canvas-holder canvas{ |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
right: 0; |
||||||
|
bottom: 0; |
||||||
|
border-radius: inherit; |
||||||
|
} |
||||||
|
#p5_loading{ |
||||||
|
display: none; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Interface |
||||||
|
*/ |
||||||
|
|
||||||
|
#interface{ |
||||||
|
width: calc(100% - 100vh); |
||||||
|
height: 80vh; |
||||||
|
padding: 10vh 0; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: space-between; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
float: left; |
||||||
|
background-color: rgb(40, 40, 40); |
||||||
|
} |
||||||
|
|
||||||
|
#interface > *{ |
||||||
|
margin: 10px; |
||||||
|
width: 300px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
span, label, select{ |
||||||
|
color: rgb(0, 0, 0); |
||||||
|
} |
||||||
|
|
||||||
|
button{ |
||||||
|
background-color: rgb(50, 200, 50); |
||||||
|
border: 2px solid #000; |
||||||
|
} |
||||||
|
button:hover{ |
||||||
|
background-color: rgb(20, 100, 20); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 174 KiB |
Loading…
Reference in new issue