From ac55415de01e029087cbd60f43262be68536e247 Mon Sep 17 00:00:00 2001 From: Lakshyajeet Jalal Date: Thu, 13 Jun 2024 19:30:57 +0530 Subject: [PATCH] prims --- lib/algorithms/algorithm.dart | 25 +++ lib/algorithms/mst/kruskal.dart | 142 ------------- lib/algorithms/mst/kruskals_algorithm.dart | 80 ++++++++ lib/algorithms/mst/lazy_prims_algorithm.dart | 96 +++++++++ lib/algorithms/mst/mst.dart | 26 +++ lib/algorithms/traversal/bft.dart | 11 +- lib/algorithms/traversal/dft.dart | 12 +- lib/algorithms/traversal/traversal.dart | 22 +- lib/graph_editor/graph_game.dart | 10 + lib/helpers/data_structures/dequeue.dart | 34 ---- lib/helpers/data_structures/union_find.dart | 62 ++++++ .../notifiers/priority_queue_notifier.dart | 30 +++ lib/helpers/notifiers/queue_notifier.dart | 10 +- lib/widgets/bft_widget.dart | 8 +- lib/widgets/components/edge_grid.dart | 6 +- lib/widgets/components/starting_vertex.dart | 6 +- lib/widgets/dft_widget.dart | 7 +- lib/widgets/kruskal_widget.dart | 105 +++++++--- lib/widgets/prims_widget.dart | 189 ++++++++++++++++++ lib/widgets/sidebar.dart | 15 +- pubspec.lock | 2 +- pubspec.yaml | 1 + 22 files changed, 649 insertions(+), 250 deletions(-) create mode 100644 lib/algorithms/algorithm.dart delete mode 100644 lib/algorithms/mst/kruskal.dart create mode 100644 lib/algorithms/mst/kruskals_algorithm.dart create mode 100644 lib/algorithms/mst/lazy_prims_algorithm.dart create mode 100644 lib/algorithms/mst/mst.dart delete mode 100644 lib/helpers/data_structures/dequeue.dart create mode 100644 lib/helpers/data_structures/union_find.dart create mode 100644 lib/helpers/notifiers/priority_queue_notifier.dart create mode 100644 lib/widgets/prims_widget.dart diff --git a/lib/algorithms/algorithm.dart b/lib/algorithms/algorithm.dart new file mode 100644 index 0000000..8299dbf --- /dev/null +++ b/lib/algorithms/algorithm.dart @@ -0,0 +1,25 @@ +import 'package:visual_graphs/graph_editor/globals.dart'; +import 'package:visual_graphs/graph_editor/models/graph.dart'; + +abstract class Algorithm { + bool isRunning = false; + + final Graph graph = Globals.game.graph; + + void start() async {} + + void initialize() { + Globals.game.gameMode = GameMode.lockedMode; + clear(); + Globals.game.greyOutGraphComponents(); + isRunning = true; + } + + void finalize() { + isRunning = false; + } + + void clear() { + isRunning = false; + } +} diff --git a/lib/algorithms/mst/kruskal.dart b/lib/algorithms/mst/kruskal.dart deleted file mode 100644 index 848a90b..0000000 --- a/lib/algorithms/mst/kruskal.dart +++ /dev/null @@ -1,142 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:visual_graphs/graph_editor/globals.dart'; -import 'package:visual_graphs/graph_editor/models/graph.dart'; -import 'package:visual_graphs/helpers/data_structures/pair.dart'; -import 'package:visual_graphs/helpers/material_color_generator.dart'; -import 'package:visual_graphs/helpers/notifiers/set_notifier.dart'; - -class Kruskal { - int weight = 0; - UnionFind unionFind = UnionFind(); - final SetNotifier includedEdges = SetNotifier(); - ValueNotifier currentEdge = ValueNotifier(null); - - void start() async { - greyOut(); - updateColors(); - - List edges = Globals.game.graph.edges - ..sort((a, b) => a.weight.compareTo(b.weight)); - - for (var edge in edges) { - await Future.delayed(const Duration(milliseconds: 500)); - await checkEdge(currentEdge.value = edge); - } - currentEdge.value = null; - } - - bool makesCycle(Edge edge) { - return unionFind.find(edge.from).first == unionFind.find(edge.to).first; - } - - Future checkEdge(Edge edge) async { - edge.component - ..edgeWidth += 2 - ..setColors(Colors.white, Colors.white); - - edge.from.component - ..radius = Globals.defaultVertexRadius + 5 - ..drawBorder = true; - - edge.to.component - ..radius = Globals.defaultVertexRadius + 5 - ..drawBorder = true; - - await Future.delayed(const Duration(milliseconds: 500)); - - edge.component.edgeWidth = Globals.defaultEdgeWidth; - edge.from.component - ..radius = Globals.defaultVertexRadius - ..drawBorder = false; - edge.to.component - ..radius = Globals.defaultVertexRadius - ..drawBorder = false; - - if (!edge.isSelfEdge && !makesCycle(edge)) { - includeEdge(edge); - } else { - edge.component.setColors(Colors.black, Colors.black); - } - updateColors(); - } - - void includeEdge(Edge edge) { - unionFind.union(edge.from, edge.to); - includedEdges.add(edge); - weight += edge.weight; - } - - void updateColors() { - for (var edge in includedEdges.set) { - var color = unionFind.find(edge.from).second ?? Colors.white; - edge.component.setColors(color, color); - edge.from.component.setColors(color, color); - edge.to.component.setColors(color, color); - } - } - - void greyOut() { - for (var vertex in Globals.game.graph.vertices) { - vertex.component.setColors(Colors.grey.shade800, Colors.grey.shade800); - } - for (var edge in Globals.game.graph.edges) { - edge.component.setColors(Colors.grey.shade800, Colors.grey.shade800); - } - } - - void clear() { - weight = 0; - unionFind.clear(); - includedEdges.clear(); - } -} - -class UnionFind { - final MaterialColorGenerator materialColorGenerator = - MaterialColorGenerator(); - - Map> parent = {}; - - union(T a, T b) { - var aParent = find(a); - var bParent = find(b); - - if (aParent.first == bParent.first) { - return; - } - - switch ((aParent.second != null, bParent.second != null)) { - case (true, true): - parent[aParent.first] = bParent; - break; - case (true, false): - parent[bParent.first] = aParent; - break; - case (false, true): - parent[aParent.first] = bParent; - break; - case (false, false): - var color = materialColorGenerator.next(); - parent[aParent.first] = Pair(bParent.first, color); - parent[bParent.first] = Pair(bParent.first, color); - break; - } - } - - Pair find(T a) { - if (parent[a] == null) { - parent[a] = Pair(a, null); - return parent[a]!; - } else { - if (parent[a]!.first == a) { - return parent[a]!; - } else { - return parent[a] = find(parent[a]!.first); - } - } - } - - void clear() { - parent.clear(); - } -} diff --git a/lib/algorithms/mst/kruskals_algorithm.dart b/lib/algorithms/mst/kruskals_algorithm.dart new file mode 100644 index 0000000..148185f --- /dev/null +++ b/lib/algorithms/mst/kruskals_algorithm.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:visual_graphs/algorithms/mst/mst.dart'; +import 'package:visual_graphs/graph_editor/globals.dart'; +import 'package:visual_graphs/graph_editor/models/graph.dart'; +import 'package:visual_graphs/helpers/data_structures/union_find.dart'; + +class KruskalsAlgorithm extends MinimumSpanningTree { + ColoredUnionFind unionFind = ColoredUnionFind(); + List edges = []; + + @override + void start() async { + initialize(); + edges = graph.edges..sort((a, b) => a.weight.compareTo(b.weight)); + + for (var edge in edges) { + await checkEdge(currentEdge.value = edge); + } + + finalize(); + } + + bool makesCycle(Edge edge) { + return unionFind.find(edge.from).first == unionFind.find(edge.to).first; + } + + Future checkEdge(Edge edge) async { + await Future.delayed(const Duration(milliseconds: 500)); + edge.component + ..edgeWidth += 2 + ..setColors(Colors.white, Colors.white); + edge.from.component + ..radius = Globals.defaultVertexRadius + 5 + ..drawBorder = true; + + edge.to.component + ..radius = Globals.defaultVertexRadius + 5 + ..drawBorder = true; + + await Future.delayed(const Duration(milliseconds: 500)); + + edge.component.edgeWidth = Globals.defaultEdgeWidth; + edge.from.component + ..radius = Globals.defaultVertexRadius + ..drawBorder = false; + edge.to.component + ..radius = Globals.defaultVertexRadius + ..drawBorder = false; + + if (!edge.isSelfEdge && !makesCycle(edge)) { + includeEdge(edge); + } else { + edge.component.setColors(Colors.black, Colors.black); + } + + updateColors(); + } + + void includeEdge(Edge edge) { + unionFind.union(edge.from, edge.to); + includedEdges.add(edge); + weightNotifier.value += edge.weight; + } + + void updateColors() { + for (var edge in includedEdges.set) { + var color = unionFind.find(edge.from).second ?? Colors.white; + edge.component.setColors(color, color); + edge.from.component.setColors(color, color); + edge.to.component.setColors(color, color); + } + } + + @override + void clear() { + super.clear(); + edges.clear(); + unionFind.clear(); + } +} diff --git a/lib/algorithms/mst/lazy_prims_algorithm.dart b/lib/algorithms/mst/lazy_prims_algorithm.dart new file mode 100644 index 0000000..c1be9d5 --- /dev/null +++ b/lib/algorithms/mst/lazy_prims_algorithm.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:visual_graphs/algorithms/mst/mst.dart'; +import 'package:visual_graphs/graph_editor/models/graph.dart'; +import 'package:visual_graphs/helpers/notifiers/priority_queue_notifier.dart'; + +class LazyPrimsAlgorithm extends MinimumSpanningTree { + final PriorityQueueNotifier pq = + PriorityQueueNotifier((a, b) => a.weight.compareTo(b.weight)); + + final Set seen = {}; + final Set visited = {}; + + @override + void start([Vertex? startingVertex]) async { + initialize(); + + see(startingVertex!); + await Future.delayed(const Duration(milliseconds: 500)); + await visit(startingVertex); + + while (pq.isNotEmpty && edgeCount < graph.vertices.length - 1) { + await checkEdge(currentEdge.value = pq.removeFirst()); + } + await Future.delayed(const Duration(seconds: 1)); + finalize(); + } + + Future visit(Vertex vertex) async { + vertex.component.setColors(Colors.green, Colors.greenAccent); + visited.add(vertex); + await Future.delayed(const Duration(milliseconds: 100)); + addEdges(vertex); + } + + void addEdges(Vertex vertex) { + vertex.neighbours.forEach( + (vertex, edges) { + if (!visited.contains(vertex)) { + for (var edge in edges) { + edge.component.setColors(Colors.orange, Colors.orange); + pq.add(edge); + } + } + Future.delayed(const Duration(milliseconds: 100)); + see(vertex); + }, + ); + } + + void see(Vertex vertex) { + if (seen.contains(vertex)) return; + seen.add(vertex); + vertex.component.setColors(Colors.orange, Colors.orange); + } + + Future checkEdge(Edge edge) async { + await Future.delayed(const Duration(seconds: 1)); + + if (!visited.contains(currentEdge.value!.to)) { + includeEdge(currentEdge.value!); + await visit(currentEdge.value!.to); + } else if (!edge.isDirected && !visited.contains(edge.from)) { + // this is just becuase i don't represent undirected egde as two directed + includeEdge(currentEdge.value!); + await visit(currentEdge.value!.from); + } else { + excludeEdge(currentEdge.value!); + } + } + + void includeEdge(Edge edge) { + edge.component.setColors( + Colors.lightGreen, + Colors.lightGreen, + ); + + weightNotifier.value += edge.weight; + edgeCount++; + includedEdges.add(edge); + } + + void excludeEdge(Edge edge) { + edge.component.setColors( + Colors.black, + Colors.black, + ); + } + + @override + void clear() { + super.clear(); + pq.clear(); + seen.clear(); + visited.clear(); + } +} diff --git a/lib/algorithms/mst/mst.dart b/lib/algorithms/mst/mst.dart new file mode 100644 index 0000000..1af4511 --- /dev/null +++ b/lib/algorithms/mst/mst.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:visual_graphs/algorithms/algorithm.dart'; +import 'package:visual_graphs/graph_editor/models/graph.dart'; +import 'package:visual_graphs/helpers/notifiers/set_notifier.dart'; + +class MinimumSpanningTree extends Algorithm { + final ValueNotifier weightNotifier = ValueNotifier(0); + final SetNotifier includedEdges = SetNotifier(); + ValueNotifier currentEdge = ValueNotifier(null); + int edgeCount = 0; + + @override + void clear() { + super.clear(); + includedEdges.clear(); + currentEdge.value = null; + weightNotifier.value = 0; + edgeCount = 0; + } + + @override + void finalize() { + super.finalize(); + currentEdge.value = null; + } +} diff --git a/lib/algorithms/traversal/bft.dart b/lib/algorithms/traversal/bft.dart index a9f1c97..066a00c 100644 --- a/lib/algorithms/traversal/bft.dart +++ b/lib/algorithms/traversal/bft.dart @@ -1,4 +1,5 @@ import 'package:visual_graphs/algorithms/traversal/traversal.dart'; +import 'package:visual_graphs/graph_editor/globals.dart'; import 'package:visual_graphs/graph_editor/models/graph.dart'; import 'package:visual_graphs/helpers/data_structures/pair.dart'; import 'package:visual_graphs/helpers/notifiers/queue_notifier.dart'; @@ -6,13 +7,13 @@ import 'package:visual_graphs/helpers/notifiers/queue_notifier.dart'; class BreadthFirstTraversal extends Traversal { final QueueNotifier> queue = QueueNotifier(); - BreadthFirstTraversal({required super.graph}); - @override - void start(Vertex startVertex) async { + void start([Vertex? startVertex]) async { clear(); + Globals.game.greyOutGraphComponents(); isRunning = true; - await see(Pair(startVertex, null)); + + await see(Pair(startVertex!, null)); await Future.delayed(delay); while (queue.isNotEmpty) { @@ -28,7 +29,7 @@ class BreadthFirstTraversal extends Traversal { } await Future.delayed(delay); } - await end(); + await finalize(); } @override diff --git a/lib/algorithms/traversal/dft.dart b/lib/algorithms/traversal/dft.dart index 04029aa..2a1ab8f 100644 --- a/lib/algorithms/traversal/dft.dart +++ b/lib/algorithms/traversal/dft.dart @@ -6,13 +6,11 @@ import 'package:visual_graphs/helpers/notifiers/stack_notifier.dart'; class DepthFirstTraversal extends Traversal { final StackNotifier> stack = StackNotifier(); - DepthFirstTraversal({required super.graph}); - @override - void start(Vertex startVertex) async { - clear(); - isRunning = true; - await see(Pair(startVertex, null)); + void start([Vertex? startVertex]) async { + initialize(); + + await see(Pair(startVertex!, null)); await Future.delayed(delay); while (stack.isNotEmpty) { @@ -28,7 +26,7 @@ class DepthFirstTraversal extends Traversal { } await Future.delayed(delay); } - await end(); + await finalize(); } @override diff --git a/lib/algorithms/traversal/traversal.dart b/lib/algorithms/traversal/traversal.dart index f6b104b..0bcf7ce 100644 --- a/lib/algorithms/traversal/traversal.dart +++ b/lib/algorithms/traversal/traversal.dart @@ -1,24 +1,21 @@ import 'package:flutter/material.dart'; +import 'package:visual_graphs/algorithms/algorithm.dart'; import 'package:visual_graphs/graph_editor/globals.dart'; import 'package:visual_graphs/graph_editor/models/graph.dart'; import 'package:visual_graphs/helpers/data_structures/pair.dart'; import 'package:visual_graphs/helpers/notifiers/set_notifier.dart'; -abstract class Traversal { - final Graph graph; +abstract class Traversal extends Algorithm { final SetNotifier visited = SetNotifier(); final SetNotifier seen = SetNotifier(); - final delay = const Duration(seconds: 1); - bool isRunning = false; - final ValueNotifier visitingVertexNotifier = ValueNotifier(null); + final delay = const Duration(seconds: 1); - Traversal({required this.graph}); - - void start(Vertex startVertex); + @override + void start([Vertex startVertex]); - Future end() async { - isRunning = false; + @override + Future finalize() async { if (visitingVertexNotifier.value != null) { visitingVertexNotifier.value?.component ?..radius = Globals.defaultVertexRadius @@ -27,9 +24,9 @@ abstract class Traversal { Colors.lightGreen, Colors.lightGreenAccent, ); - visitingVertexNotifier.value = null; } + super.finalize(); } Future see(Pair pair) async { @@ -67,10 +64,11 @@ abstract class Traversal { visited.add(pair.first); } + @override void clear() { + super.clear(); visited.clear(); seen.clear(); - isRunning = false; visitingVertexNotifier.value = null; } diff --git a/lib/graph_editor/graph_game.dart b/lib/graph_editor/graph_game.dart index 3c8ef13..4bfe8a5 100644 --- a/lib/graph_editor/graph_game.dart +++ b/lib/graph_editor/graph_game.dart @@ -89,6 +89,16 @@ class GraphGame extends FlameGame refreshGraphComponents(); } + void greyOutGraphComponents() { + for (var vertex in Globals.game.graph.vertices) { + vertex.component.setColors(Colors.grey.shade800, Colors.grey.shade800); + } + for (var edge in Globals.game.graph.edges) { + edge.component.setColors(Colors.grey.shade800, Colors.grey.shade800); + } + refreshGraphComponents(); + } + void refreshGraphComponents() { // ignore: invalid_use_of_internal_member world.children.clear(); diff --git a/lib/helpers/data_structures/dequeue.dart b/lib/helpers/data_structures/dequeue.dart deleted file mode 100644 index b86e392..0000000 --- a/lib/helpers/data_structures/dequeue.dart +++ /dev/null @@ -1,34 +0,0 @@ -class Dequeue { - final List _dequeue = []; - - void addFirst(T item) { - _dequeue.insert(0, item); - } - - void addLast(T item) { - _dequeue.add(item); - } - - T removeFirst() { - return _dequeue.removeAt(0); - } - - T removeLast() { - return _dequeue.removeLast(); - } - - T peekFirst() { - return _dequeue.first; - } - - T peekLast() { - return _dequeue.last; - } - - bool get isEmpty => _dequeue.isEmpty; - bool get isNotEmpty => _dequeue.isNotEmpty; - - void clear() { - _dequeue.clear(); - } -} diff --git a/lib/helpers/data_structures/union_find.dart b/lib/helpers/data_structures/union_find.dart new file mode 100644 index 0000000..ee42a16 --- /dev/null +++ b/lib/helpers/data_structures/union_find.dart @@ -0,0 +1,62 @@ +import 'dart:ui'; + +import 'package:visual_graphs/helpers/data_structures/pair.dart'; +import 'package:visual_graphs/helpers/material_color_generator.dart'; + +abstract class UnionFind { + Map parent = {}; + + void union(K a, K b); + + P find(K a); + + void clear() { + parent.clear(); + } +} + +class ColoredUnionFind extends UnionFind> { + final MaterialColorGenerator materialColorGenerator = + MaterialColorGenerator(); + + @override + union(T a, T b) { + var aParent = find(a); + var bParent = find(b); + + if (aParent.first == bParent.first) { + return; + } + + switch ((aParent.second != null, bParent.second != null)) { + case (true, true): + parent[aParent.first] = bParent; + break; + case (true, false): + parent[bParent.first] = aParent; + break; + case (false, true): + parent[aParent.first] = bParent; + break; + case (false, false): + var color = materialColorGenerator.next(); + parent[aParent.first] = Pair(bParent.first, color); + parent[bParent.first] = Pair(bParent.first, color); + break; + } + } + + @override + Pair find(T a) { + if (parent[a] == null) { + parent[a] = Pair(a, null); + return parent[a]!; + } else { + if (parent[a]!.first == a) { + return parent[a]!; + } else { + return parent[a] = find(parent[a]!.first); + } + } + } +} diff --git a/lib/helpers/notifiers/priority_queue_notifier.dart b/lib/helpers/notifiers/priority_queue_notifier.dart new file mode 100644 index 0000000..f15d490 --- /dev/null +++ b/lib/helpers/notifiers/priority_queue_notifier.dart @@ -0,0 +1,30 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; + +class PriorityQueueNotifier extends ChangeNotifier { + final PriorityQueue _pq; + + PriorityQueueNotifier(int Function(T, T) compare) + : _pq = PriorityQueue(compare); + + T removeFirst() { + final value = _pq.removeFirst(); + notifyListeners(); + return value; + } + + void add(T value) { + _pq.add(value); + notifyListeners(); + } + + void clear() { + _pq.clear(); + notifyListeners(); + } + + bool get isEmpty => _pq.isEmpty; + bool get isNotEmpty => _pq.isNotEmpty; + + List get values => _pq.toList(); +} diff --git a/lib/helpers/notifiers/queue_notifier.dart b/lib/helpers/notifiers/queue_notifier.dart index df01cfc..3442c86 100644 --- a/lib/helpers/notifiers/queue_notifier.dart +++ b/lib/helpers/notifiers/queue_notifier.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:visual_graphs/helpers/data_structures/queue.dart'; +import 'dart:collection'; class QueueNotifier with ChangeNotifier { - final QueueDS _queue = QueueDS(); + final Queue _queue = Queue(); void enqueue(T value) { - _queue.enqueue(value); + _queue.addLast(value); notifyListeners(); } T dequeue() { - final value = _queue.dequeue(); + final value = _queue.removeFirst(); notifyListeners(); return value; } @@ -20,7 +20,7 @@ class QueueNotifier with ChangeNotifier { notifyListeners(); } - List get queue => _queue.queue; + List get queue => _queue.toList(); bool get isEmpty => _queue.isEmpty; bool get isNotEmpty => _queue.isNotEmpty; diff --git a/lib/widgets/bft_widget.dart b/lib/widgets/bft_widget.dart index ceb96ff..3ab1b98 100644 --- a/lib/widgets/bft_widget.dart +++ b/lib/widgets/bft_widget.dart @@ -26,7 +26,7 @@ class _BFTWidgetState extends State { @override void initState() { super.initState(); - bft = BreadthFirstTraversal(graph: Globals.game.graph); + bft = BreadthFirstTraversal(); } @override @@ -49,7 +49,7 @@ class _BFTWidgetState extends State { ), ), const SizedBox(height: 10), - StartingVertex(vertexWidgets: vertexWidgets), + const StartingVertex(), const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -177,9 +177,6 @@ class _BFTWidgetState extends State { return; } - Globals.game.gameMode = GameMode.lockedMode; - - Globals.game.resetGraphColors(); bft.start(Globals.game.graph.pickedVertexNotifier.value!); }, child: const Text("Start BFT"), @@ -189,7 +186,6 @@ class _BFTWidgetState extends State { Globals.game.resetGraphColors(); Globals.game.graph.pickedVertexNotifier.value = null; bft.clear(); - setState(() {}); }, child: const Text("Reset"), ), diff --git a/lib/widgets/components/edge_grid.dart b/lib/widgets/components/edge_grid.dart index b280eec..e38ce14 100644 --- a/lib/widgets/components/edge_grid.dart +++ b/lib/widgets/components/edge_grid.dart @@ -4,8 +4,10 @@ import 'package:visual_graphs/widgets/components/edge_widget.dart'; import 'package:visual_graphs/widgets/components/empty_text.dart'; class EdgeGrid extends StatefulWidget { - const EdgeGrid(this.edges, this.colCount, this.rowCount, {super.key}); + const EdgeGrid(this.edges, this.colCount, this.rowCount, this.scrollToLast, + {super.key}); + final bool scrollToLast; final int colCount; final int rowCount; final Iterable edges; @@ -33,7 +35,7 @@ class _EdgeGridState extends State { @override Widget build(BuildContext context) { WidgetsBinding.instance.addPostFrameCallback((_) { - if (_scrollController.hasClients) { + if (widget.scrollToLast && _scrollController.hasClients) { _scrollController.jumpTo(_scrollController.position.maxScrollExtent); } }); diff --git a/lib/widgets/components/starting_vertex.dart b/lib/widgets/components/starting_vertex.dart index d1e3c74..b2ba6e9 100644 --- a/lib/widgets/components/starting_vertex.dart +++ b/lib/widgets/components/starting_vertex.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:visual_graphs/graph_editor/globals.dart'; import 'package:visual_graphs/graph_editor/models/graph.dart'; import 'package:visual_graphs/helpers/functions/pick_starting_vertex.dart'; +import 'package:visual_graphs/widgets/components/vertex_widget.dart'; class StartingVertex extends StatelessWidget { - const StartingVertex({super.key, required this.vertexWidgets}); - final Map vertexWidgets; + const StartingVertex({super.key}); @override Widget build(BuildContext context) { @@ -19,7 +19,7 @@ class StartingVertex extends StatelessWidget { valueListenable: Globals.game.graph.pickedVertexNotifier, builder: (context, vertex, child) { return (vertex != null) - ? vertexWidgets[vertex.id]! + ? VertexWidget(vertex) : Center( child: Text( "Click to pick", diff --git a/lib/widgets/dft_widget.dart b/lib/widgets/dft_widget.dart index 85fe2bb..b948a78 100644 --- a/lib/widgets/dft_widget.dart +++ b/lib/widgets/dft_widget.dart @@ -26,7 +26,7 @@ class _DFTWidgetState extends State { @override void initState() { super.initState(); - dft = DepthFirstTraversal(graph: Globals.game.graph); + dft = DepthFirstTraversal(); } @override @@ -49,7 +49,7 @@ class _DFTWidgetState extends State { ), ), const SizedBox(height: 10), - StartingVertex(vertexWidgets: vertexWidgets), + const StartingVertex(), const SizedBox(height: 20), Row( mainAxisSize: MainAxisSize.max, @@ -183,9 +183,6 @@ class _DFTWidgetState extends State { return; } - Globals.game.gameMode = GameMode.lockedMode; - - Globals.game.resetGraphColors(); dft.start(Globals.game.graph.pickedVertexNotifier.value!); }, child: const Text("Start DFT"), diff --git a/lib/widgets/kruskal_widget.dart b/lib/widgets/kruskal_widget.dart index 6a7164a..6105740 100644 --- a/lib/widgets/kruskal_widget.dart +++ b/lib/widgets/kruskal_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:visual_graphs/algorithms/mst/kruskal.dart'; +import 'package:visual_graphs/algorithms/mst/kruskals_algorithm.dart'; import 'package:visual_graphs/graph_editor/globals.dart'; import 'package:visual_graphs/helpers/functions/load_mst_sample_graph.dart'; import 'package:visual_graphs/widgets/components/edge_grid.dart'; @@ -15,7 +15,7 @@ class KruskalWidget extends StatefulWidget { } class _KruskalWidgetState extends State { - Kruskal kruskal = Kruskal(); + KruskalsAlgorithm kruskal = KruskalsAlgorithm(); @override Widget build(BuildContext context) { @@ -24,26 +24,75 @@ class _KruskalWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Text( - "Current Edge:", - style: TextStyle( - color: Colors.white, - fontSize: 16, - ), - ), - const SizedBox(height: 10), - WhiteBorder( - child: SizedBox( - width: 180, - height: 60, - child: ValueListenableBuilder( - valueListenable: kruskal.currentEdge, - builder: (context, value, child) { - if (value == null) return const EmptyText(); - return EdgeWidget(edge: value); - }, + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Text( + "Weight:", + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + const SizedBox(height: 10), + WhiteBorder( + child: SizedBox( + height: 60, + width: 60, + child: ValueListenableBuilder( + valueListenable: kruskal.weightNotifier, + builder: (context, weight, child) { + return Center( + child: Text( + weight.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 20, + ), + ), + ); + }, + ), + ), + ), + ], ), - ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Text( + "Current Edge:", + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + const SizedBox(height: 10), + WhiteBorder( + child: SizedBox( + width: 180, + height: 60, + child: ValueListenableBuilder( + valueListenable: kruskal.currentEdge, + builder: (context, value, child) { + if (value == null) return const EmptyText(); + return EdgeWidget(edge: value); + }, + ), + ), + ), + ], + ), + ], ), const SizedBox(height: 20), const Text( @@ -58,7 +107,7 @@ class _KruskalWidgetState extends State { child: ListenableBuilder( listenable: kruskal.includedEdges, builder: (context, child) { - return EdgeGrid(kruskal.includedEdges.set, 2, 8); + return EdgeGrid(kruskal.includedEdges.set, 2, 8, true); }, ), ), @@ -71,9 +120,15 @@ class _KruskalWidgetState extends State { children: [ FilledButton( onPressed: () { - Globals.game.gameMode = GameMode.lockedMode; - kruskal.clear(); - Globals.game.resetGraphColors(); + if (kruskal.isRunning) { + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("kruskal's Algorithm is already running"), + ), + ); + return; + } kruskal.start(); }, child: const Text("Start Kruskal"), diff --git a/lib/widgets/prims_widget.dart b/lib/widgets/prims_widget.dart new file mode 100644 index 0000000..e21cbe3 --- /dev/null +++ b/lib/widgets/prims_widget.dart @@ -0,0 +1,189 @@ +import 'package:flutter/material.dart'; +import 'package:visual_graphs/algorithms/mst/lazy_prims_algorithm.dart'; +import 'package:visual_graphs/graph_editor/globals.dart'; +import 'package:visual_graphs/helpers/functions/load_mst_sample_graph.dart'; +import 'package:visual_graphs/helpers/functions/pick_starting_vertex.dart'; +import 'package:visual_graphs/widgets/components/edge_grid.dart'; +import 'package:visual_graphs/widgets/components/edge_widget.dart'; +import 'package:visual_graphs/widgets/components/empty_text.dart'; +import 'package:visual_graphs/widgets/components/starting_vertex.dart'; +import 'package:visual_graphs/widgets/components/white_border.dart'; + +class PrimsWidget extends StatefulWidget { + const PrimsWidget({super.key}); + + @override + State createState() => _PrimsWidgetState(); +} + +class _PrimsWidgetState extends State { + LazyPrimsAlgorithm prims = LazyPrimsAlgorithm(); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Text( + "Starting Vertex:", + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + const SizedBox(height: 10), + const StartingVertex(), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Text( + "Weight:", + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + const SizedBox(height: 10), + WhiteBorder( + child: SizedBox( + height: 60, + width: 60, + child: ValueListenableBuilder( + valueListenable: prims.weightNotifier, + builder: (context, weight, child) { + return Center( + child: Text( + weight.toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 20, + ), + ), + ); + }, + ), + ), + ), + ], + ), + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const Text( + "Current Edge:", + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + const SizedBox(height: 10), + WhiteBorder( + child: SizedBox( + width: 180, + height: 60, + child: ValueListenableBuilder( + valueListenable: prims.currentEdge, + builder: (context, value, child) { + if (value == null) return const EmptyText(); + return EdgeWidget(edge: value); + }, + ), + ), + ), + ], + ), + ], + ), + const SizedBox(height: 20), + const Text( + "Priority queue:", + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + const SizedBox(height: 10), + WhiteBorder( + child: ListenableBuilder( + listenable: prims.pq, + builder: (context, child) { + return EdgeGrid(prims.pq.values, 2, 3, false); + }, + ), + ), + const SizedBox(height: 20), + const Text( + "Included Edges:", + style: TextStyle( + color: Colors.white, + fontSize: 16, + ), + ), + const SizedBox(height: 10), + WhiteBorder( + child: ListenableBuilder( + listenable: prims.includedEdges, + builder: (context, child) { + return EdgeGrid(prims.includedEdges.set, 2, 3, true); + }, + ), + ), + const SizedBox(height: 20), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + FilledButton( + onPressed: () { + if (prims.isRunning) { + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Prim's Algorithm is already running"), + ), + ); + return; + } + + if (Globals.game.graph.pickedVertexNotifier.value == null) { + pickStartingVertex(context); + return; + } + + prims.start(Globals.game.graph.pickedVertexNotifier.value!); + }, + child: const Text("Start Prim's"), + ), + ElevatedButton( + onPressed: () { + Globals.game.resetGraphColors(); + prims.clear(); + }, + child: const Text("Reset"), + ), + ], + ), + const SizedBox(height: 10), + const TextButton( + onPressed: loadMSTSampleGraph, + child: Text("Load Sample Graph"), + ), + const SizedBox(height: 10), + ], + ); + } +} diff --git a/lib/widgets/sidebar.dart b/lib/widgets/sidebar.dart index 5ed991d..a6ca5dc 100644 --- a/lib/widgets/sidebar.dart +++ b/lib/widgets/sidebar.dart @@ -3,11 +3,13 @@ import 'package:visual_graphs/graph_editor/globals.dart'; import 'package:visual_graphs/widgets/bft_widget.dart'; import 'package:visual_graphs/widgets/dft_widget.dart'; import 'package:visual_graphs/widgets/kruskal_widget.dart'; +import 'package:visual_graphs/widgets/prims_widget.dart'; enum Algorithms { bft, dft, kruskal, + prims, } class Sidebar extends StatefulWidget { @@ -63,9 +65,14 @@ class _SidebarState extends State { value: Algorithms.dft, label: "Depth First Traversal", ), - DropdownMenuEntry( - value: Algorithms.kruskal, - label: "Kruskal's Algorithm") + DropdownMenuEntry( + value: Algorithms.kruskal, + label: "Kruskal's Algorithm", + ), + DropdownMenuEntry( + value: Algorithms.prims, + label: "Prim's Algorithm", + ), ], ), ), @@ -118,6 +125,8 @@ class _SidebarState extends State { return DFTWidget(key: childKey); case Algorithms.kruskal: return KruskalWidget(key: childKey); + case Algorithms.prims: + return PrimsWidget(key: childKey); } } } diff --git a/pubspec.lock b/pubspec.lock index c982318..766574f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -50,7 +50,7 @@ packages: source: hosted version: "1.1.1" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a diff --git a/pubspec.yaml b/pubspec.yaml index 409f831..1d6bcc9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,6 +7,7 @@ environment: sdk: ">=3.4.1 <4.0.0" dependencies: + collection: ^1.18.0 flame: ^1.18.0 flutter: sdk: flutter