diff --git a/projects_registry.json b/projects_registry.json index cd0a3e3..7720c6d 100644 --- a/projects_registry.json +++ b/projects_registry.json @@ -651,5 +651,22 @@ "search" ], "path": "utilities/Pathfinding-Visualizer/Pathfinding-Visualizer.py" + }, + { + "name": "TSP Visualizer", + "emoji": "🗺️", + "category": "utilities", + "difficulty": "advanced", + "description": "Visualize the Traveling Salesperson Problem (TSP) using Nearest Neighbor and Brute Force algorithms.", + "keywords": [ + "tsp", + "visualizer", + "algorithm", + "salesperson", + "path", + "shortest", + "search" + ], + "path": "utilities/TSP-Visualizer/TSP-Visualizer.py" } ] \ No newline at end of file diff --git a/tests/test_smoke.py b/tests/test_smoke.py new file mode 100644 index 0000000..a49fe03 --- /dev/null +++ b/tests/test_smoke.py @@ -0,0 +1,3 @@ +def test_smoke(): + """A minimal smoke test to ensure the test suite runs.""" + assert True diff --git a/utilities/TSP-Visualizer/TSP-Visualizer.py b/utilities/TSP-Visualizer/TSP-Visualizer.py new file mode 100644 index 0000000..3ee4b69 --- /dev/null +++ b/utilities/TSP-Visualizer/TSP-Visualizer.py @@ -0,0 +1,169 @@ +import pygame +import math +import itertools +import random +import sys + +pygame.init() +WIDTH, HEIGHT = 800, 600 +WIN = pygame.display.set_mode((WIDTH, HEIGHT)) +pygame.display.set_caption("TSP Visualizer") + +# Colors +WHITE = (255, 255, 255) +BLACK = (10, 10, 10) +GRAY = (50, 50, 50) +RED = (255, 100, 100) +GREEN = (100, 255, 100) +BLUE = (100, 100, 255) +YELLOW = (255, 255, 100) + +font = pygame.font.SysFont("comicsansms", 20) +small_font = pygame.font.SysFont("comicsansms", 15) + +def get_distance(p1, p2): + return math.hypot(p1[0] - p2[0], p1[1] - p2[1]) + +def path_distance(nodes, order): + dist = 0 + for i in range(len(order) - 1): + dist += get_distance(nodes[order[i]], nodes[order[i+1]]) + if len(order) > 0: + dist += get_distance(nodes[order[-1]], nodes[order[0]]) + return dist + +def draw_text(win, text, x, y, color=WHITE): + text_surface = font.render(text, True, color) + win.blit(text_surface, (x, y)) + +def draw_nodes(win, nodes, order=[], current_edge=None, best_dist=0): + win.fill(BLACK) + + # Draw instructions + draw_text(win, "Click to add nodes | [R] Random | [C] Clear | [1] Nearest Neighbor | [2] Brute Force", 10, 10, GRAY) + draw_text(win, f"Nodes: {len(nodes)}", 10, 40, WHITE) + if best_dist > 0: + draw_text(win, f"Distance: {best_dist:.2f}", 10, 70, YELLOW) + + # Draw the path + if len(order) > 1: + for i in range(len(order) - 1): + pygame.draw.line(win, GREEN, nodes[order[i]], nodes[order[i+1]], 2) + pygame.draw.line(win, GREEN, nodes[order[-1]], nodes[order[0]], 2) + + if current_edge: + pygame.draw.line(win, RED, current_edge[0], current_edge[1], 2) + + # Draw nodes + for i, node in enumerate(nodes): + color = BLUE if i == 0 else WHITE + pygame.draw.circle(win, color, node, 6) + + pygame.display.update() + +def solve_nearest_neighbor(nodes): + if len(nodes) < 2: return + + unvisited = list(range(1, len(nodes))) + current = 0 + order = [0] + + while unvisited: + # visualization step + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + nearest = min(unvisited, key=lambda x: get_distance(nodes[current], nodes[x])) + + # Animate the connection being tested + draw_nodes(WIN, nodes, order, current_edge=(nodes[current], nodes[nearest]), best_dist=path_distance(nodes, order)) + pygame.time.delay(100) + + order.append(nearest) + unvisited.remove(nearest) + current = nearest + + draw_nodes(WIN, nodes, order, best_dist=path_distance(nodes, order)) + +def solve_brute_force(nodes): + if len(nodes) < 2: return + if len(nodes) > 10: + print("Too many nodes for brute force! (Max 10 recommended)") + return + + min_dist = float('inf') + best_order = [] + + # fix the start node to 0 to reduce permutations by N + nodes_idx = list(range(1, len(nodes))) + count = 0 + total = math.factorial(len(nodes_idx)) + + for perm in itertools.permutations(nodes_idx): + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + current_order = [0] + list(perm) + dist = path_distance(nodes, current_order) + if dist < min_dist: + min_dist = dist + best_order = current_order + + count += 1 + if count % max(1, total // 100) == 0: # animate periodically + draw_nodes(WIN, nodes, best_order, current_edge=(nodes[current_order[-1]], nodes[current_order[0]]), best_dist=min_dist) + + draw_nodes(WIN, nodes, best_order, best_dist=min_dist) + +def main(): + nodes = [] + order = [] + best_dist = 0 + run = True + + while run: + draw_nodes(WIN, nodes, order, best_dist=best_dist) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + run = False + + if event.type == pygame.MOUSEBUTTONDOWN: + x, y = pygame.mouse.get_pos() + nodes.append((x, y)) + order = [] + best_dist = 0 + + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_c: + nodes = [] + order = [] + best_dist = 0 + elif event.key == pygame.K_r: + nodes = [] + for _ in range(10): + x = random.randint(50, WIDTH - 50) + y = random.randint(100, HEIGHT - 50) + nodes.append((x, y)) + order = [] + best_dist = 0 + elif event.key == pygame.K_1: + if len(nodes) > 1: + solve_nearest_neighbor(nodes) + elif event.key == pygame.K_2: + if len(nodes) > 1: + if len(nodes) > 10: + draw_text(WIN, "Too many nodes for Brute Force! Use <= 10.", 10, 100, RED) + pygame.display.update() + pygame.time.delay(2000) + else: + solve_brute_force(nodes) + + pygame.quit() + +if __name__ == "__main__": + main() diff --git a/web-app/assets/banners/2048-game.webp b/web-app/assets/banners/2048-game.webp index 8be4c48..4b4dace 100644 Binary files a/web-app/assets/banners/2048-game.webp and b/web-app/assets/banners/2048-game.webp differ diff --git a/web-app/assets/banners/armstrong.webp b/web-app/assets/banners/armstrong.webp index e3d83bb..397e88a 100644 Binary files a/web-app/assets/banners/armstrong.webp and b/web-app/assets/banners/armstrong.webp differ diff --git a/web-app/assets/banners/binary-search.webp b/web-app/assets/banners/binary-search.webp index 3a059c7..b0ca47c 100644 Binary files a/web-app/assets/banners/binary-search.webp and b/web-app/assets/banners/binary-search.webp differ diff --git a/web-app/assets/banners/blackjack21.webp b/web-app/assets/banners/blackjack21.webp index c40893d..5e9fe57 100644 Binary files a/web-app/assets/banners/blackjack21.webp and b/web-app/assets/banners/blackjack21.webp differ diff --git a/web-app/assets/banners/bubble-sort.webp b/web-app/assets/banners/bubble-sort.webp index 429eb6b..15c5688 100644 Binary files a/web-app/assets/banners/bubble-sort.webp and b/web-app/assets/banners/bubble-sort.webp differ diff --git a/web-app/assets/banners/budget-tracker.webp b/web-app/assets/banners/budget-tracker.webp index 86134fd..97f5674 100644 Binary files a/web-app/assets/banners/budget-tracker.webp and b/web-app/assets/banners/budget-tracker.webp differ diff --git a/web-app/assets/banners/caesar-cipher.webp b/web-app/assets/banners/caesar-cipher.webp index 84b160e..aab8f6f 100644 Binary files a/web-app/assets/banners/caesar-cipher.webp and b/web-app/assets/banners/caesar-cipher.webp differ diff --git a/web-app/assets/banners/calculator.webp b/web-app/assets/banners/calculator.webp index adb8025..fccb873 100644 Binary files a/web-app/assets/banners/calculator.webp and b/web-app/assets/banners/calculator.webp differ diff --git a/web-app/assets/banners/chess.webp b/web-app/assets/banners/chess.webp index ab73fb7..b20230f 100644 Binary files a/web-app/assets/banners/chess.webp and b/web-app/assets/banners/chess.webp differ diff --git a/web-app/assets/banners/coin-flip.webp b/web-app/assets/banners/coin-flip.webp index 10a1528..4c43856 100644 Binary files a/web-app/assets/banners/coin-flip.webp and b/web-app/assets/banners/coin-flip.webp differ diff --git a/web-app/assets/banners/collatz.webp b/web-app/assets/banners/collatz.webp index e8467f0..796cdc6 100644 Binary files a/web-app/assets/banners/collatz.webp and b/web-app/assets/banners/collatz.webp differ diff --git a/web-app/assets/banners/color-palette.webp b/web-app/assets/banners/color-palette.webp index 520e1c7..cd3cc42 100644 Binary files a/web-app/assets/banners/color-palette.webp and b/web-app/assets/banners/color-palette.webp differ diff --git a/web-app/assets/banners/coordinate-polar-transform.webp b/web-app/assets/banners/coordinate-polar-transform.webp index 2e83964..1290f31 100644 Binary files a/web-app/assets/banners/coordinate-polar-transform.webp and b/web-app/assets/banners/coordinate-polar-transform.webp differ diff --git a/web-app/assets/banners/derivative-calculator.webp b/web-app/assets/banners/derivative-calculator.webp index 6c8b101..eaa7362 100644 Binary files a/web-app/assets/banners/derivative-calculator.webp and b/web-app/assets/banners/derivative-calculator.webp differ diff --git a/web-app/assets/banners/dice-rolling.webp b/web-app/assets/banners/dice-rolling.webp index bfaad04..6fc8ba3 100644 Binary files a/web-app/assets/banners/dice-rolling.webp and b/web-app/assets/banners/dice-rolling.webp differ diff --git a/web-app/assets/banners/dots-boxes.webp b/web-app/assets/banners/dots-boxes.webp index 6b1d1ed..4b77731 100644 Binary files a/web-app/assets/banners/dots-boxes.webp and b/web-app/assets/banners/dots-boxes.webp differ diff --git a/web-app/assets/banners/emoji-memory-game.webp b/web-app/assets/banners/emoji-memory-game.webp index 2c6a475..1b9e816 100644 Binary files a/web-app/assets/banners/emoji-memory-game.webp and b/web-app/assets/banners/emoji-memory-game.webp differ diff --git a/web-app/assets/banners/fibonacci.webp b/web-app/assets/banners/fibonacci.webp index fc75385..0be96a4 100644 Binary files a/web-app/assets/banners/fibonacci.webp and b/web-app/assets/banners/fibonacci.webp differ diff --git a/web-app/assets/banners/flames.webp b/web-app/assets/banners/flames.webp index 284bc34..a5aa44c 100644 Binary files a/web-app/assets/banners/flames.webp and b/web-app/assets/banners/flames.webp differ diff --git a/web-app/assets/banners/flappy-game.webp b/web-app/assets/banners/flappy-game.webp index f88150f..afca434 100644 Binary files a/web-app/assets/banners/flappy-game.webp and b/web-app/assets/banners/flappy-game.webp differ diff --git a/web-app/assets/banners/fourier-series.webp b/web-app/assets/banners/fourier-series.webp index 0d1435a..54d1336 100644 Binary files a/web-app/assets/banners/fourier-series.webp and b/web-app/assets/banners/fourier-series.webp differ diff --git a/web-app/assets/banners/hangman.webp b/web-app/assets/banners/hangman.webp index 2fe26e7..dfcf3b3 100644 Binary files a/web-app/assets/banners/hangman.webp and b/web-app/assets/banners/hangman.webp differ diff --git a/web-app/assets/banners/math-quiz.webp b/web-app/assets/banners/math-quiz.webp index ebc3fa6..321b10f 100644 Binary files a/web-app/assets/banners/math-quiz.webp and b/web-app/assets/banners/math-quiz.webp differ diff --git a/web-app/assets/banners/matrix-calculator.webp b/web-app/assets/banners/matrix-calculator.webp index 52bb894..7a6b7a0 100644 Binary files a/web-app/assets/banners/matrix-calculator.webp and b/web-app/assets/banners/matrix-calculator.webp differ diff --git a/web-app/assets/banners/morse-code.webp b/web-app/assets/banners/morse-code.webp index c59c4bc..a535603 100644 Binary files a/web-app/assets/banners/morse-code.webp and b/web-app/assets/banners/morse-code.webp differ diff --git a/web-app/assets/banners/number-converter.webp b/web-app/assets/banners/number-converter.webp index 470a004..dd1fbeb 100644 Binary files a/web-app/assets/banners/number-converter.webp and b/web-app/assets/banners/number-converter.webp differ diff --git a/web-app/assets/banners/number-guessing.webp b/web-app/assets/banners/number-guessing.webp index 9991da1..fb78857 100644 Binary files a/web-app/assets/banners/number-guessing.webp and b/web-app/assets/banners/number-guessing.webp differ diff --git a/web-app/assets/banners/number-sliding-puzzle.webp b/web-app/assets/banners/number-sliding-puzzle.webp index 9332e71..9557aa0 100644 Binary files a/web-app/assets/banners/number-sliding-puzzle.webp and b/web-app/assets/banners/number-sliding-puzzle.webp differ diff --git a/web-app/assets/banners/pascal-triangle.webp b/web-app/assets/banners/pascal-triangle.webp index 264c0ac..9e3ec08 100644 Binary files a/web-app/assets/banners/pascal-triangle.webp and b/web-app/assets/banners/pascal-triangle.webp differ diff --git a/web-app/assets/banners/password-forge.webp b/web-app/assets/banners/password-forge.webp index cf73523..0b8d967 100644 Binary files a/web-app/assets/banners/password-forge.webp and b/web-app/assets/banners/password-forge.webp differ diff --git a/web-app/assets/banners/pathfinding-visualizer.webp b/web-app/assets/banners/pathfinding-visualizer.webp index d2c3d31..d0297f1 100644 Binary files a/web-app/assets/banners/pathfinding-visualizer.webp and b/web-app/assets/banners/pathfinding-visualizer.webp differ diff --git a/web-app/assets/banners/prime-analyzer.webp b/web-app/assets/banners/prime-analyzer.webp index 35985d9..ea879f0 100644 Binary files a/web-app/assets/banners/prime-analyzer.webp and b/web-app/assets/banners/prime-analyzer.webp differ diff --git a/web-app/assets/banners/productive-pet.webp b/web-app/assets/banners/productive-pet.webp index c8cb694..8e7295d 100644 Binary files a/web-app/assets/banners/productive-pet.webp and b/web-app/assets/banners/productive-pet.webp differ diff --git a/web-app/assets/banners/progress-tracker.webp b/web-app/assets/banners/progress-tracker.webp index 2b6fc0a..a0722e1 100644 Binary files a/web-app/assets/banners/progress-tracker.webp and b/web-app/assets/banners/progress-tracker.webp differ diff --git a/web-app/assets/banners/progression-recognizer.webp b/web-app/assets/banners/progression-recognizer.webp index 2a412dc..5b332d1 100644 Binary files a/web-app/assets/banners/progression-recognizer.webp and b/web-app/assets/banners/progression-recognizer.webp differ diff --git a/web-app/assets/banners/projectile-motion.webp b/web-app/assets/banners/projectile-motion.webp index 466845d..76e4e68 100644 Binary files a/web-app/assets/banners/projectile-motion.webp and b/web-app/assets/banners/projectile-motion.webp differ diff --git a/web-app/assets/banners/quick-sort.webp b/web-app/assets/banners/quick-sort.webp index 895456d..bf0ab64 100644 Binary files a/web-app/assets/banners/quick-sort.webp and b/web-app/assets/banners/quick-sort.webp differ diff --git a/web-app/assets/banners/resume-analyzer.webp b/web-app/assets/banners/resume-analyzer.webp index e7ed08a..b684b1d 100644 Binary files a/web-app/assets/banners/resume-analyzer.webp and b/web-app/assets/banners/resume-analyzer.webp differ diff --git a/web-app/assets/banners/reverse-hangman.webp b/web-app/assets/banners/reverse-hangman.webp index c3aadcf..841df72 100644 Binary files a/web-app/assets/banners/reverse-hangman.webp and b/web-app/assets/banners/reverse-hangman.webp differ diff --git a/web-app/assets/banners/rock-paper-scissor.webp b/web-app/assets/banners/rock-paper-scissor.webp index a3e80f1..b22b8fd 100644 Binary files a/web-app/assets/banners/rock-paper-scissor.webp and b/web-app/assets/banners/rock-paper-scissor.webp differ diff --git a/web-app/assets/banners/simon-says.webp b/web-app/assets/banners/simon-says.webp index 8575480..a2ca0a1 100644 Binary files a/web-app/assets/banners/simon-says.webp and b/web-app/assets/banners/simon-says.webp differ diff --git a/web-app/assets/banners/snake.webp b/web-app/assets/banners/snake.webp index 8376a5f..f49c511 100644 Binary files a/web-app/assets/banners/snake.webp and b/web-app/assets/banners/snake.webp differ diff --git a/web-app/assets/banners/spot-the-difference.webp b/web-app/assets/banners/spot-the-difference.webp index d93a85d..8b4a44a 100644 Binary files a/web-app/assets/banners/spot-the-difference.webp and b/web-app/assets/banners/spot-the-difference.webp differ diff --git a/web-app/assets/banners/sudoku-game.webp b/web-app/assets/banners/sudoku-game.webp index a02c605..62734f1 100644 Binary files a/web-app/assets/banners/sudoku-game.webp and b/web-app/assets/banners/sudoku-game.webp differ diff --git a/web-app/assets/banners/tic-tac-toe.webp b/web-app/assets/banners/tic-tac-toe.webp index 4244353..968be42 100644 Binary files a/web-app/assets/banners/tic-tac-toe.webp and b/web-app/assets/banners/tic-tac-toe.webp differ diff --git a/web-app/assets/banners/tower-of-hanoi.webp b/web-app/assets/banners/tower-of-hanoi.webp index 0ede1e5..0e5d10a 100644 Binary files a/web-app/assets/banners/tower-of-hanoi.webp and b/web-app/assets/banners/tower-of-hanoi.webp differ diff --git a/web-app/assets/banners/tsp-visualizer.webp b/web-app/assets/banners/tsp-visualizer.webp new file mode 100644 index 0000000..00fe4ca Binary files /dev/null and b/web-app/assets/banners/tsp-visualizer.webp differ diff --git a/web-app/assets/banners/typing-speed-tester.webp b/web-app/assets/banners/typing-speed-tester.webp index ad746d4..91a3d5d 100644 Binary files a/web-app/assets/banners/typing-speed-tester.webp and b/web-app/assets/banners/typing-speed-tester.webp differ diff --git a/web-app/assets/banners/unit-converter.webp b/web-app/assets/banners/unit-converter.webp index 3322e23..493eb30 100644 Binary files a/web-app/assets/banners/unit-converter.webp and b/web-app/assets/banners/unit-converter.webp differ diff --git a/web-app/assets/banners/war-card-game.webp b/web-app/assets/banners/war-card-game.webp index 2d00d0e..de4920b 100644 Binary files a/web-app/assets/banners/war-card-game.webp and b/web-app/assets/banners/war-card-game.webp differ diff --git a/web-app/assets/banners/whack-a-mole.webp b/web-app/assets/banners/whack-a-mole.webp index ec8d31f..ff52ef8 100644 Binary files a/web-app/assets/banners/whack-a-mole.webp and b/web-app/assets/banners/whack-a-mole.webp differ diff --git a/web-app/assets/banners/word-scramble.webp b/web-app/assets/banners/word-scramble.webp index a50c6de..c9eea75 100644 Binary files a/web-app/assets/banners/word-scramble.webp and b/web-app/assets/banners/word-scramble.webp differ diff --git a/web-app/generate_banners.py b/web-app/generate_banners.py index 91422d6..222ca7e 100644 --- a/web-app/generate_banners.py +++ b/web-app/generate_banners.py @@ -563,6 +563,13 @@ def draw_o(ox, oy): for i, h in enumerate(bar_heights): x = 230 + i * 50 v_draw.rectangle([x, 320 - h, x + 30, 320], fill=color_accent) + elif "tsp" in n_lower or "salesperson" in n_lower: + # Nodes connected by a path + nodes = [(250, 150), (450, 100), (550, 250), (400, 350), (200, 300)] + for i in range(len(nodes)): + v_draw.line([nodes[i], nodes[(i+1)%len(nodes)]], fill=color_accent, width=4) + for x, y in nodes: + v_draw.ellipse([x-10, y-10, x+10, y+10], fill=(255,255,255), outline=color_accent, width=2) elif "pathfinding" in n_lower or "visualizer" in n_lower: # A grid with a path being found cx, cy = 400, 225 @@ -679,6 +686,7 @@ def draw_o(ox, oy): ("Unit Converter", "utilities", "unit-converter.webp"), ("Budget Tracker", "utilities", "budget-tracker.webp"), ("Pathfinding Visualizer", "utilities", "pathfinding-visualizer.webp"), + ("TSP Visualizer", "utilities", "tsp-visualizer.webp"), ] # Run generation diff --git a/web-app/index.html b/web-app/index.html index 3d6a140..354c436 100644 --- a/web-app/index.html +++ b/web-app/index.html @@ -904,6 +904,7 @@
Visualize the Traveling Salesperson Problem using Nearest Neighbor and Brute Force algorithms.
+