Skip to content

Commit

Permalink
feat: add tree construction in tree-path-finder visualizer
Browse files Browse the repository at this point in the history
  • Loading branch information
vighnesh153 committed Dec 12, 2023
1 parent fc0ed4d commit b5383b7
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script lang="ts">
import { onMount } from 'svelte';
import { TreePathFinderGame, CanvasWrapperImpl, type CanvasWrapper } from '@vighnesh153/graphics-programming';
let canvasElement: HTMLCanvasElement;
let canvasWrapper: CanvasWrapper;
let game: TreePathFinderGame;
function newGame() {
if (game) {
game.stop();
}
if (canvasWrapper) {
game = new TreePathFinderGame(canvasWrapper);
const frames = game.start();
function showNextFrame() {
if (!frames.next().done) {
requestAnimationFrame(showNextFrame);
}
}
showNextFrame();
}
}
onMount(() => {
canvasWrapper = new CanvasWrapperImpl(canvasElement);
newGame();
});
</script>

<div class="flex justify-center items-center gap-10"></div>
<canvas class="mt-6 mx-auto w-full max-w-3xl aspect-video bg-text" bind:this={canvasElement}>
Sorry your browser doesn't support the canvas element
</canvas>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
import { graphicsProjectsMap } from '@vighnesh153/graphics-programming';
import { classes, verifyGraphicsProjectPath } from '@/utils';
import { projectNavItems } from '@/constants';
import ContentLayout from '@/layouts/ContentLayout.astro';
import ProjectRoot from '@/components/projects/graphics/tree-path-finder/ProjectRoot.svelte';
const project = graphicsProjectsMap.treePathFinder;
verifyGraphicsProjectPath(project, Astro.request.url);
const title = `Vighnesh Raut | Graphics Projects - Tree Path Finder`;
const description = `Find a path between two nodes in a tree`;
---

<ContentLayout title={title} description={description} navItems={projectNavItems} showFooter={false}>
<div class={classes(`mt-28 mb-12 max-w-xl mx-auto lg:max-w-[unset] scroll-mt-8`)}>
<h1 class="text-3xl mb-6 text-center">Tree Path Finder</h1>
<ProjectRoot client:load />
</div>
</ContentLayout>
1 change: 1 addition & 0 deletions nodejs-tools/nodejs-lib/graphics-programming/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from './sierpinskis-triangle';
export * from './sorting-visualizer';
export * from './symmetric-binary-tree';
export * from './tower-of-hanoi';
export * from './tree-path-finder';
export * from './twinkling-stars';

export * from './canvas-wrapper';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { CanvasWrapper } from '@/canvas-wrapper';
import { TreeNode } from './TreeNode';

interface ConfigOptions {
color?: string;
thinkness?: number;
}

export class TreeNodeEdge {
readonly #canvasWrapper: CanvasWrapper;
readonly #node1: TreeNode;
readonly #node2: TreeNode;

readonly #color: string;
readonly #thickness: number;

constructor(canvasWrapper: CanvasWrapper, node1: TreeNode, node2: TreeNode, options: ConfigOptions = {}) {
this.#canvasWrapper = canvasWrapper;
this.#node1 = node1;
this.#node2 = node2;

this.#color = options.color ?? 'green';
this.#thickness = options.thinkness ?? 2;
}

draw() {
const pos1 = this.#node1.position;
const pos2 = this.#node2.position;
if (pos1 === null || pos2 === null) {
return;
}
this.#canvasWrapper.drawLine(pos1.x, pos1.y, pos2.x, pos2.y, this.#thickness, this.#color);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Queue, not } from '@vighnesh153/utils';
import { CanvasWrapper } from '@/canvas-wrapper';
import { getCanvasBgColor } from '@/getCanvasBgColor';
import { TreeNode } from './TreeNode';
import { populateTreeNodePositions } from './populateTreeNodePositions';
import { createTreeStructure } from './createTreeStructure';
import { TreeNodeEdge } from './Edge';
import { createTreeNodeEdges } from './createTreeNodeEdges';

interface GameOptions {
bgColor?: string;
depth?: number;
}

export class TreePathFinderGame {
readonly #canvasWrapper: CanvasWrapper;
readonly #bgColor: string;
readonly #depth: number;

readonly #rootNode: TreeNode;
readonly #treeNodeEdges: TreeNodeEdge[];
#isRunning = false;

constructor(canvasWrapper: CanvasWrapper, options: GameOptions = {}) {
this.#canvasWrapper = canvasWrapper;
this.#bgColor = options.bgColor ?? getCanvasBgColor(canvasWrapper);
this.#depth = options.depth ?? 7;

const rootNode = createTreeStructure(this.#canvasWrapper, 1, this.#depth);
if (rootNode == null) {
throw new Error(`Depth should be an integer >= 1`);
}
this.#rootNode = rootNode;
populateTreeNodePositions(this.#rootNode, this.#depth, this.#canvasWrapper);
this.#treeNodeEdges = createTreeNodeEdges(this.#canvasWrapper, rootNode);
}

*start() {
this.#isRunning = true;
this.draw();
yield;
}

stop() {
this.#isRunning = false;
}

draw() {
this.drawEdges();
this.drawTreeNodes();
}

clear() {
const rect = this.#canvasWrapper.getBoundingClientRect();
const canvasWidth = rect.width;
const canvasHeight = rect.height;
this.#canvasWrapper.drawFilledRect(0, 0, canvasWidth, canvasHeight, this.#bgColor);
}

private drawTreeNodes(): void {
const nodes = new Queue<TreeNode>();
nodes.pushRight(this.#rootNode);

while (not(nodes.isEmpty)) {
const node = nodes.popLeft();
node.draw();
for (const child of node.children) {
nodes.pushRight(child);
}
}
}

private drawEdges(): void {
this.#treeNodeEdges.forEach((edge) => {
edge.draw();
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Position {
x: number;
y: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { CanvasWrapper } from '@/canvas-wrapper';
import { Position } from './Position';

interface TreeNodeOptions {
color?: string;
radius?: number;
}

export class TreeNode {
readonly #canvasWrapper: CanvasWrapper;
readonly #children: TreeNode[] = [];

readonly #color: string;
readonly #radius: number;

#position: Position | null = null;

get children(): TreeNode[] {
return [...this.#children];
}

get position(): Position | null {
if (this.#position === null) {
return null;
}
return { ...this.#position };
}

constructor(canvasWrapper: CanvasWrapper, options: TreeNodeOptions = {}) {
this.#canvasWrapper = canvasWrapper;

this.#color = options.color ?? 'green';
this.#radius = options.radius ?? 4;
}

setPosition(position: Position) {
this.#position = position;
}

addChild(child: TreeNode): void {
this.#children.push(child);
}

draw() {
if (this.#position == null) {
throw new Error(`Treenode's position is null`);
}
const { x, y } = this.#position;
const color = this.#color;
const radius = this.#radius;

this.#canvasWrapper.drawFilledCircle(x, y, radius, color);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { CanvasWrapper } from '@/canvas-wrapper';
import { TreeNodeEdge } from './Edge';
import { TreeNode } from './TreeNode';

export function createTreeNodeEdges(
canvasWrapper: CanvasWrapper,
rootNode: TreeNode,
result: TreeNodeEdge[] = []
): TreeNodeEdge[] {
for (const child of rootNode.children) {
result.push(new TreeNodeEdge(canvasWrapper, rootNode, child));
createTreeNodeEdges(canvasWrapper, child, result);
}
return result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { CanvasWrapper } from '@/canvas-wrapper';
import { TreeNode } from './TreeNode';

export function createTreeStructure(canvasWrapper: CanvasWrapper, level: number, maxLevel: number): TreeNode | null {
if (level > maxLevel) {
return null;
}

const node = new TreeNode(canvasWrapper);
const child1 = createTreeStructure(canvasWrapper, level + 1, maxLevel);
const child2 = createTreeStructure(canvasWrapper, level + 1, maxLevel);

if (child1 !== null) {
node.addChild(child1);
}
if (child2 !== null) {
node.addChild(child2);
}

return node;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TreePathFinderGame } from './Game';
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Queue, not } from '@vighnesh153/utils';
import { CanvasWrapper } from '@/canvas-wrapper';
import { TreeNode } from './TreeNode';

export function populateTreeNodePositions(rootNode: TreeNode, depth: number, canvasWrapper: CanvasWrapper) {
const nodes = new Queue({ node: rootNode, level: 1 });
const nodesPerLevel = new Map<number, TreeNode[]>();
while (not(nodes.isEmpty)) {
const { node, level } = nodes.popLeft();

nodesPerLevel.set(level, nodesPerLevel.get(level) ?? []);
nodesPerLevel.get(level)!.push(node);

for (const child of node.children) {
nodes.pushRight({
node: child,
level: level + 1,
});
}
}

const h = canvasWrapper.height / (depth + 1);
Array.from(nodesPerLevel.keys()).forEach((level) => {
const nodesAtLevel = nodesPerLevel.get(level) ?? [];
const w = canvasWrapper.width / (nodesAtLevel.length + 1);
for (let i = 0; i < nodesAtLevel.length; i++) {
const node = nodesAtLevel[i];
node.setPosition({
x: w * (i + 1),
y: h * level,
});
}
});
}

0 comments on commit b5383b7

Please sign in to comment.