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