Initial working gruphi
This commit is contained in:
		
							parent
							
								
									19be81d88d
								
							
						
					
					
						commit
						08ba95459e
					
				
					 5 changed files with 407 additions and 0 deletions
				
			
		
							
								
								
									
										47
									
								
								src/main/java/de/oshgnacknak/gruphi/Canvas.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/main/java/de/oshgnacknak/gruphi/Canvas.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| package de.oshgnacknak.gruphi; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
| import java.awt.*; | ||||
| import java.util.function.Consumer; | ||||
| 
 | ||||
| public class Canvas extends JPanel { | ||||
| 
 | ||||
|     private final Consumer<Drawable> draw; | ||||
| 
 | ||||
|     public Canvas(Consumer<Drawable> draw) { | ||||
|         this.draw = draw; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void paint(Graphics graphics) { | ||||
|         var g = (Graphics2D) graphics; | ||||
| 
 | ||||
|         draw.accept(new Drawable() { | ||||
| 
 | ||||
|             @Override | ||||
|             public void fill(Color c) { | ||||
|                 g.setColor(c); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void strokeWeight(double w) { | ||||
|                 g.setStroke(new BasicStroke((int) w)); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void rect(double x, double y, double w, double h) { | ||||
|                 g.fillRect((int) x, (int) y, (int) w, (int) h); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void ellipse(double x, double y, double w, double h) { | ||||
|                 g.fillOval((int) (x - w/2), (int) (y - h/2), (int) w, (int) h); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void line(double x1, double y1, double x2, double y2) { | ||||
|                 g.drawLine((int) x1, (int) y1, (int) x2, (int) y2); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/main/java/de/oshgnacknak/gruphi/Drawable.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/main/java/de/oshgnacknak/gruphi/Drawable.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| package de.oshgnacknak.gruphi; | ||||
| 
 | ||||
| import java.awt.*; | ||||
| 
 | ||||
| public interface Drawable { | ||||
| 
 | ||||
|     void fill(Color c); | ||||
| 
 | ||||
|     void strokeWeight(double w); | ||||
| 
 | ||||
|     void rect(double x, double y, double w, double h); | ||||
| 
 | ||||
|     void ellipse(double x, double y, double w, double h); | ||||
| 
 | ||||
|     void line(double x1, double y1, double x2, double y2); | ||||
| } | ||||
							
								
								
									
										256
									
								
								src/main/java/de/oshgnacknak/gruphi/Gruphi.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								src/main/java/de/oshgnacknak/gruphi/Gruphi.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,256 @@ | |||
| package de.oshgnacknak.gruphi; | ||||
| 
 | ||||
| import h07.graph.DirectedGraph; | ||||
| 
 | ||||
| import javax.swing.*; | ||||
| import java.awt.*; | ||||
| import java.awt.event.KeyAdapter; | ||||
| import java.awt.event.KeyEvent; | ||||
| import java.awt.event.MouseAdapter; | ||||
| import java.awt.event.MouseEvent; | ||||
| import java.awt.geom.Point2D; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| class Gruphi extends JFrame { | ||||
| 
 | ||||
|     private static final int FRAME_DELAY = 1000 / 60; | ||||
|     private static final double VEL = 5; | ||||
|     private static final double NEIGHBOUR_DISTANCE = 50; | ||||
| 
 | ||||
|     DirectedGraph<Node, Double> graph = newGraph(); | ||||
| 
 | ||||
|     MazeGenerator<Node> mazeGenerator = new MazeGenerator<>(graph, (a, b) -> | ||||
|         a.pos.distance(b.pos) <= NEIGHBOUR_DISTANCE); | ||||
| 
 | ||||
|     Node selected = null; | ||||
|     Point2D.Double vel = new Point2D.Double(0, 0); | ||||
|     private boolean running = true; | ||||
| 
 | ||||
|     Gruphi() { | ||||
|         super("Gruphi - The Graph GUI - By Osh"); | ||||
| 
 | ||||
|         var canvas = new Canvas(this::draw); | ||||
|         add(canvas); | ||||
|         pack(); | ||||
|         setLocationRelativeTo(null); | ||||
|         setDefaultCloseOperation(EXIT_ON_CLOSE); | ||||
| 
 | ||||
|         var mouseListener = new MouseAdapter() { | ||||
| 
 | ||||
|             @Override | ||||
|             public void mousePressed(MouseEvent e) { | ||||
|                 switch (e.getButton()) { | ||||
|                     case MouseEvent.BUTTON1: { | ||||
|                         if (selected != null) { | ||||
|                             var clicked = findClickedNode(e); | ||||
|                             if (clicked.isPresent()) { | ||||
|                                 var n = clicked.get(); | ||||
|                                 if (graph.getChildrenForNode(selected).contains(n)) { | ||||
|                                     graph.disconnectNodes(selected, n); | ||||
|                                 } else { | ||||
|                                     graph.connectNodes(selected, 1.0, n); | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 selected.pos.x = e.getX(); | ||||
|                                 selected.pos.y = e.getY(); | ||||
|                             } | ||||
|                         } else { | ||||
|                             graph.addNode(new Node(e.getX(), e.getY())); | ||||
|                         } | ||||
|                     } break; | ||||
|                     case MouseEvent.BUTTON3: { | ||||
|                         if (selected != null) { | ||||
|                             selected.color = Node.COLOR; | ||||
|                             selected.radius = Node.RADIUS; | ||||
|                             selected = null; | ||||
|                         } else { | ||||
|                             findClickedNode(e) | ||||
|                                 .ifPresent(n -> { | ||||
|                                     selected = n; | ||||
|                                     selected.radius *= 2; | ||||
|                                     selected.color = Color.WHITE; | ||||
|                                 }); | ||||
|                             if (selected != null) { | ||||
|                                 selected.color = Color.RED; | ||||
|                             } | ||||
|                         } | ||||
|                     } break; | ||||
|                     default: break; | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         addMouseListener(mouseListener); | ||||
|         addMouseMotionListener(mouseListener); | ||||
| 
 | ||||
|         addKeyListener(new KeyAdapter() { | ||||
|             @Override | ||||
|             public void keyPressed(KeyEvent e) { | ||||
|                 switch (e.getKeyCode()) { | ||||
|                     case KeyEvent.VK_K: | ||||
|                     case KeyEvent.VK_W: | ||||
|                     case KeyEvent.VK_UP: { | ||||
|                         vel.y = -VEL; | ||||
|                     } break; | ||||
| 
 | ||||
|                     case KeyEvent.VK_J: | ||||
|                     case KeyEvent.VK_S: | ||||
|                     case KeyEvent.VK_DOWN: { | ||||
|                         vel.y = VEL; | ||||
|                     } break; | ||||
| 
 | ||||
|                     case KeyEvent.VK_H: | ||||
|                     case KeyEvent.VK_A: | ||||
|                     case KeyEvent.VK_LEFT: { | ||||
|                         vel.x = -VEL; | ||||
|                     } break; | ||||
| 
 | ||||
|                     case KeyEvent.VK_L: | ||||
|                     case KeyEvent.VK_D: | ||||
|                     case KeyEvent.VK_RIGHT: { | ||||
|                         vel.x = VEL; | ||||
|                     } break; | ||||
| 
 | ||||
|                     case KeyEvent.VK_X: | ||||
|                     case KeyEvent.VK_DELETE: | ||||
|                     case KeyEvent.VK_BACK_SPACE: { | ||||
|                         if (selected != null) { | ||||
|                             graph.removeNode(selected); | ||||
|                             selected = null; | ||||
|                         } | ||||
|                     } break; | ||||
| 
 | ||||
|                     case KeyEvent.VK_Q: | ||||
|                     case KeyEvent.VK_ESCAPE: { | ||||
|                         running = false; | ||||
|                     } break; | ||||
| 
 | ||||
|                     case KeyEvent.VK_C: { | ||||
|                         clearGraph(); | ||||
|                     } break; | ||||
| 
 | ||||
|                     case KeyEvent.VK_M: { | ||||
|                         if (selected != null) { | ||||
|                             mazeGenerator.generate(selected); | ||||
|                         } | ||||
|                     } break; | ||||
| 
 | ||||
|                     case KeyEvent.VK_G: { | ||||
|                         generateGrid(); | ||||
|                     } break; | ||||
| 
 | ||||
|                     default: break; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void keyReleased(KeyEvent e) { | ||||
|                 switch (e.getKeyCode()) { | ||||
|                     case KeyEvent.VK_K: | ||||
|                     case KeyEvent.VK_J: | ||||
|                     case KeyEvent.VK_W: | ||||
|                     case KeyEvent.VK_S: | ||||
|                     case KeyEvent.VK_DOWN: | ||||
|                     case KeyEvent.VK_UP: { | ||||
|                         vel.y = 0; | ||||
|                     } break; | ||||
| 
 | ||||
|                     case KeyEvent.VK_H: | ||||
|                     case KeyEvent.VK_L: | ||||
|                     case KeyEvent.VK_A: | ||||
|                     case KeyEvent.VK_D: | ||||
|                     case KeyEvent.VK_RIGHT: | ||||
|                     case KeyEvent.VK_LEFT: { | ||||
|                         vel.x = 0; | ||||
|                     } break; | ||||
| 
 | ||||
|                     default: break; | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void generateGrid() { | ||||
|         clearGraph(); | ||||
| 
 | ||||
|         var dist = NEIGHBOUR_DISTANCE; | ||||
| 
 | ||||
|         var rows = getHeight() / dist - 1; | ||||
|         var cols = getWidth() / dist - 1; | ||||
| 
 | ||||
|         for (int x = 0; x < cols; x++) { | ||||
|             for (int y = 0; y < rows; y++) { | ||||
|                 var n = new Node((x + 0.5) * dist, (y + 0.5) * dist); | ||||
|                 graph.addNode(n); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void clearGraph() { | ||||
|         selected = null; | ||||
|         for (var node : graph.getAllNodes()) { | ||||
|             graph.removeNode(node); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private Optional<Node> findClickedNode(MouseEvent e) { | ||||
|         return graph.getAllNodes() | ||||
|             .stream() | ||||
|             .filter(n -> | ||||
|                 n.inside(e.getX(), e.getY())) | ||||
|             .findFirst(); | ||||
|     } | ||||
| 
 | ||||
|     void draw(Drawable d) { | ||||
|         d.fill(Color.BLACK); | ||||
|         d.rect(0, 0, getWidth(), getHeight()); | ||||
| 
 | ||||
|         d.strokeWeight(2); | ||||
|         d.fill(Color.WHITE); | ||||
|         for (var node : graph.getAllNodes()) { | ||||
|             for (var child : graph.getChildrenForNode(node)) { | ||||
|                 d.line(node.pos.x, node.pos.y, child.pos.x, child.pos.y); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         for (var node : graph.getAllNodes()) { | ||||
|             d.fill(node.color); | ||||
|             d.ellipse(node.pos.x, node.pos.y, node.radius * 2, node.radius * 2); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void updateLoop() { | ||||
|         var last = System.currentTimeMillis(); | ||||
|         var acc = 0; | ||||
| 
 | ||||
|         while (running) { | ||||
|             while (acc > FRAME_DELAY) { | ||||
|                 update(); | ||||
|                 acc -= FRAME_DELAY; | ||||
|             } | ||||
|             repaint(); | ||||
| 
 | ||||
|             var current = System.currentTimeMillis(); | ||||
|             acc += current - last; | ||||
|             last = current; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void update() { | ||||
|         if (selected != null) { | ||||
|             selected.pos.x += vel.x; | ||||
|             selected.pos.y += vel.y; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     DirectedGraph<Node, Double> newGraph() { | ||||
|         throw new UnsupportedOperationException("Return a h07.graph.DirectedGraphImpl here"); | ||||
|     } | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         var gruphi = new Gruphi(); | ||||
|         gruphi.setVisible(true); | ||||
|         gruphi.updateLoop(); | ||||
|         System.exit(0); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										61
									
								
								src/main/java/de/oshgnacknak/gruphi/MazeGenerator.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/main/java/de/oshgnacknak/gruphi/MazeGenerator.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| package de.oshgnacknak.gruphi; | ||||
| 
 | ||||
| import h07.graph.DirectedGraph; | ||||
| 
 | ||||
| import java.util.*; | ||||
| import java.util.function.BiPredicate; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| public class MazeGenerator<V> { | ||||
| 
 | ||||
|     private final Random random; | ||||
|     private final DirectedGraph<V, Double> graph; | ||||
|     private final Set<V> visited; | ||||
|     private final BiPredicate<V, V> areNeighbours; | ||||
| 
 | ||||
|     public MazeGenerator(DirectedGraph<V, Double> graph, BiPredicate<V, V> areNeighbours) { | ||||
|         this.random = new Random(); | ||||
|         this.graph = graph; | ||||
|         this.areNeighbours = areNeighbours; | ||||
|         this.visited = new HashSet<>(); | ||||
|     } | ||||
| 
 | ||||
|     public void generate(V start) { | ||||
|         var stack = new Stack<V>(); | ||||
|         visited.clear(); | ||||
| 
 | ||||
|         for (var node : graph.getAllNodes()) { | ||||
|             for (var child : graph.getChildrenForNode(node)) { | ||||
|                 graph.disconnectNodes(node, child); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         visited.add(start); | ||||
|         stack.push(start); | ||||
|         while (!stack.isEmpty()) { | ||||
|             var current = stack.pop(); | ||||
|             var unvisited = getUnvisited(current); | ||||
| 
 | ||||
|             if (!unvisited.isEmpty()) { | ||||
|                 stack.push(current); | ||||
|                 var neighbour = unvisited.get(random.nextInt(unvisited.size())); | ||||
| 
 | ||||
|                 graph.connectNodes(current, random.nextDouble(), neighbour); | ||||
|                 graph.connectNodes(neighbour, random.nextDouble(), current); | ||||
| 
 | ||||
|                 visited.add(neighbour); | ||||
|                 stack.push(neighbour); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private List<V> getUnvisited(V node) { | ||||
|         return graph.getAllNodes() | ||||
|             .stream() | ||||
|             .filter(n -> | ||||
|                 n != node | ||||
|                 && areNeighbours.test(node, n) | ||||
|                 && !visited.contains(n)) | ||||
|             .collect(Collectors.toList()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/main/java/de/oshgnacknak/gruphi/Node.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/main/java/de/oshgnacknak/gruphi/Node.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| package de.oshgnacknak.gruphi; | ||||
| 
 | ||||
| import java.awt.*; | ||||
| import java.awt.geom.Point2D; | ||||
| 
 | ||||
| class Node { | ||||
| 
 | ||||
|     public static final double RADIUS = 10; | ||||
| 
 | ||||
|     public static final Color COLOR = Color.WHITE; | ||||
| 
 | ||||
|     Point2D.Double pos; | ||||
| 
 | ||||
|     double radius; | ||||
| 
 | ||||
|     Color color; | ||||
| 
 | ||||
|     Node(double x, double y) { | ||||
|        this.pos = new Point2D.Double(x, y); | ||||
|        this.radius = RADIUS; | ||||
|        this.color = COLOR; | ||||
|     } | ||||
| 
 | ||||
|     boolean inside(double x, double y) { | ||||
|         return pos.distance(x, y) <= radius; | ||||
|     } | ||||
| } | ||||
		Reference in a new issue