Initial working gruphi
This commit is contained in:
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 New Issue
Block a user