diff --git a/src/main/java/de/neemann/digital/draw/graphics/Vector.java b/src/main/java/de/neemann/digital/draw/graphics/Vector.java index 46899535d..11c71c7de 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/Vector.java +++ b/src/main/java/de/neemann/digital/draw/graphics/Vector.java @@ -153,6 +153,16 @@ public class Vector implements VectorInterface { return new Vector(x * a, y * a); } + /** + * Creates a new vector which has the value this*a + * + * @param a a + * @return this*a + */ + public VectorFloat mul(float a) { + return new VectorFloat(x * a, y * a); + } + @Override public Vector div(int d) { return new Vector(x / d, y / d); @@ -236,4 +246,10 @@ public class Vector implements VectorInterface { public Vector round() { return this; } + + @Override + public float len() { + return (float) Math.sqrt(x * x + y * y); + } + } diff --git a/src/main/java/de/neemann/digital/draw/graphics/VectorFloat.java b/src/main/java/de/neemann/digital/draw/graphics/VectorFloat.java index 4009a9dc6..6d90a9b6e 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/VectorFloat.java +++ b/src/main/java/de/neemann/digital/draw/graphics/VectorFloat.java @@ -126,4 +126,9 @@ public class VectorFloat implements VectorInterface { public Vector round() { return new Vector(getX(), getY()); } + + @Override + public float len() { + return (float) Math.sqrt(x * x + y * y); + } } diff --git a/src/main/java/de/neemann/digital/draw/graphics/VectorInterface.java b/src/main/java/de/neemann/digital/draw/graphics/VectorInterface.java index 348afe0ce..fc8c5efdf 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/VectorInterface.java +++ b/src/main/java/de/neemann/digital/draw/graphics/VectorInterface.java @@ -73,4 +73,9 @@ public interface VectorInterface { * @return a int vector */ Vector round(); + + /** + * @return the length of the vector + */ + float len(); } diff --git a/src/main/java/de/neemann/digital/fsm/FSM.java b/src/main/java/de/neemann/digital/fsm/FSM.java new file mode 100644 index 000000000..770f5d9ad --- /dev/null +++ b/src/main/java/de/neemann/digital/fsm/FSM.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.fsm; + +import de.neemann.digital.analyse.expression.Expression; +import de.neemann.digital.draw.graphics.Graphic; +import de.neemann.digital.draw.graphics.VectorFloat; + +import java.util.ArrayList; + +/** + * A simple finite state machine + */ +public class FSM { + + private ArrayList states; + private ArrayList transitions; + + /** + * Creates a new FSM containing the given states + * + * @param state the states + */ + public FSM(State... state) { + states = new ArrayList<>(); + transitions = new ArrayList<>(); + for (State s : state) + add(s); + } + + /** + * Adds a state to the FSM + * + * @param state the state to add + * @return this for chained calls + */ + public FSM add(State state) { + state.setNumber(states.size()); + states.add(state); + return this; + } + + /** + * Adds a transition to the FSM + * + * @param transition the transition to add + * @return this for chained calls + */ + public FSM add(Transition transition) { + transitions.add(transition); + return this; + } + + /** + * Adds a transition to the FSM + * + * @param from the from state + * @param to the to state + * @param condition the condition + * @return this for chained calls + * @throws FinitStateMachineException FinitStateMachineException + */ + public FSM transition(String from, String to, Expression condition) throws FinitStateMachineException { + return transition(findState(from), findState(to), condition); + } + + /** + * Adds a transition to the FSM + * + * @param from the from state + * @param to the to state + * @param condition the condition + * @return this for chained calls + * @throws FinitStateMachineException FinitStateMachineException + */ + public FSM transition(int from, int to, Expression condition) throws FinitStateMachineException { + return transition(findState(from), findState(to), condition); + } + + /** + * Adds a transition to the FSM + * + * @param from the from state + * @param to the to state + * @param condition the condition + * @return this for chained calls + * @throws FinitStateMachineException FinitStateMachineException + */ + public FSM transition(State from, State to, Expression condition) { + return add(new Transition(from, to, condition)); + } + + private State findState(String name) throws FinitStateMachineException { + for (State s : states) + if (s.getName().equals(name)) + return s; + throw new FinitStateMachineException("State " + name + " not found!"); + } + + private State findState(int number) throws FinitStateMachineException { + for (State s : states) + if (s.getNumber() == number) + return s; + throw new FinitStateMachineException("State " + number + " not found!"); + } + + /** + * Calculates all forces to move the elements + * + * @return this for chained calls + */ + public FSM calculateForces() { + for (State s : states) + s.calcExpansionForce(states); + for (Transition t : transitions) + t.calcForce(states, transitions); + return this; + } + + /** + * Draws the FSM + * + * @param gr the Graphic instance to draw to + */ + public void drawTo(Graphic gr) { + for (State s : states) + s.drawTo(gr); + for (Transition t : transitions) + t.drawTo(gr); + } + + /** + * Moved the elements + * + * @param dt the time step + */ + public void move(int dt) { + for (State s : states) + s.move(dt); + for (Transition t : transitions) + t.move(dt); + + } + + /** + * Orders all states in a big circle + */ + public void circle() { + double delta = 2 * Math.PI / states.size(); + double rad = 0; + for (State s : states) + if (s.getRadius() > rad) + rad = s.getRadius(); + + rad *= 4; + double phi = 0; + for (State s : states) { + s.setPosition(new VectorFloat((float) (Math.sin(phi) * rad), (float) (-Math.cos(phi) * rad))); + phi += delta; + } + + for (Transition t : transitions) + t.initPos(); + } +} diff --git a/src/main/java/de/neemann/digital/fsm/FinitStateMachineException.java b/src/main/java/de/neemann/digital/fsm/FinitStateMachineException.java new file mode 100644 index 000000000..4a22b2a6a --- /dev/null +++ b/src/main/java/de/neemann/digital/fsm/FinitStateMachineException.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.fsm; + +/** + * Esxeption thrown if there is a problem delaing with the FSM + */ +public class FinitStateMachineException extends Exception { + /** + * Creates a new exception + * + * @param message ther message + */ + public FinitStateMachineException(String message) { + super(message); + } +} diff --git a/src/main/java/de/neemann/digital/fsm/Movable.java b/src/main/java/de/neemann/digital/fsm/Movable.java new file mode 100644 index 000000000..c6ca85b97 --- /dev/null +++ b/src/main/java/de/neemann/digital/fsm/Movable.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.fsm; + +import de.neemann.digital.draw.graphics.VectorFloat; + +/** + * A movable element. + */ +public class Movable { + private VectorFloat position; + private VectorFloat speed; + private VectorFloat force; + + /** + * Creates a new instance + */ + public Movable() { + force = new VectorFloat(0, 0); + speed = new VectorFloat(0, 0); + position = new VectorFloat((float) Math.random() - 0.5f, (float) Math.random() - 0.5f).mul(100); + } + + /** + * Sets the position + * + * @param position the position + */ + public void setPos(VectorFloat position) { + this.position = position; + } + + /** + * Adds the given value to the force + * + * @param df the force to add + */ + public void addToForce(VectorFloat df) { + force = force.add(df); + } + + /** + * Applies a repulsive force which decreases with the square of the distance. + * + * @param pos the position of the causer of the force + * @param reach the reach of the force + */ + public void addRepulsive(VectorFloat pos, float reach) { + final VectorFloat dif = position.sub(pos); + float dist = dif.len(); + if (dist == 0) { + addToForce(new VectorFloat((float) Math.random() - 0.5f, (float) Math.random() - 0.5f).mul(2)); + } else { + float f = reach * reach / (dist * dist) / 2; + if (f > 100) + f = 100; + addToForce(dif.norm().mul(f)); + } + } + + /** + * Applies a repulsive force which decreases linear with the the distance. + * + * @param pos the position of the causer of the force + * @param reach the reach of the force + */ + public void addRepulsiveInv(VectorFloat pos, float reach) { + final VectorFloat dif = position.sub(pos); + float dist = dif.len(); + if (dist == 0) { + addToForce(new VectorFloat((float) Math.random() - 0.5f, (float) Math.random() - 0.5f).mul(2)); + } else { + float f = reach / dist / 2; + if (f > 100) + f = 100; + addToForce(dif.norm().mul(f)); + } + } + + /** + * Adds an attractive force + * + * @param target the targe + * @param scale a scaling factor + */ + public void addAttractiveTo(VectorFloat target, float scale) { + addToForce(target.sub(position).mul(scale)); + } + + /** + * @return the force + */ + public VectorFloat getForce() { + return force; + } + + /** + * Sets the force to zero + */ + public void resetForce() { + this.force = new VectorFloat(0, 0); + } + + /** + * @return the position + */ + public VectorFloat getPos() { + return position; + } + + /** + * Moves the element + * + * @param dt the time step + */ + public void move(int dt) { + speed = speed.add(force.mul(0.2f)); + position = position.add(speed); + speed = speed.mul(0.7f); + } + +} diff --git a/src/main/java/de/neemann/digital/fsm/State.java b/src/main/java/de/neemann/digital/fsm/State.java new file mode 100644 index 000000000..fd9192216 --- /dev/null +++ b/src/main/java/de/neemann/digital/fsm/State.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.fsm; + +import de.neemann.digital.draw.graphics.*; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +/** + * Represents a state + */ +public class State extends Movable { + private static final int RAD = 70; + private static final float REACH = 500; + + private int number; + private String name; + private int radius; + private TreeMap values; + + /** + * Creates a new state + * + * @param name the name of the state + */ + public State(String name) { + super(); + this.name = name; + this.radius = RAD; + values = new TreeMap<>(); + } + + /** + * Adds a outputvalue to the state + * + * @param name the name + * @param val the value + * @return this for chained calls + */ + public State val(String name, long val) { + values.put(name, val); + return this; + } + + /** + * @return the name of the state + */ + public String getName() { + return name; + } + + /** + * Sets the position + * + * @param position the position + * @return this for chained calls + */ + public State setPosition(VectorFloat position) { + setPos(position); + return this; + } + + /** + * Calculates the repolsive forces + * + * @param states the states to take into account + */ + public void calcExpansionForce(List states) { + resetForce(); + for (State s : states) + if (s != this) + addRepulsive(s.getPos(), REACH); + } + + + /** + * Draws the state + * + * @param gr the Graphic instance to draw to + */ + public void drawTo(Graphic gr) { + VectorInterface rad = new Vector(RAD, RAD); + gr.drawCircle(getPos().sub(rad), getPos().add(rad), Style.NORMAL); + + Vector delta = new Vector(0, Style.NORMAL.getFontSize()); + VectorFloat pos = getPos().add(delta.mul(-1)); + + gr.drawText(pos, pos.add(new Vector(1, 0)), Integer.toString(number), Orientation.CENTERCENTER, Style.NORMAL); + pos = pos.add(delta); + gr.drawText(pos, pos.add(new Vector(1, 0)), name, Orientation.CENTERCENTER, Style.NORMAL); + + for (Map.Entry v : values.entrySet()) { + pos = pos.add(delta); + String str = v.getKey() + "->" + v.getValue(); + gr.drawText(pos, pos.add(new Vector(1, 0)), str, Orientation.CENTERCENTER, Style.NORMAL); + } + + } + + /** + * @return the radius of the state + */ + public float getRadius() { + return radius; + } + + /** + * Sets the number of the state + * + * @param number the number + */ + public void setNumber(int number) { + this.number = number; + } + + /** + * @return the number of the state + */ + public int getNumber() { + return number; + } +} diff --git a/src/main/java/de/neemann/digital/fsm/Transition.java b/src/main/java/de/neemann/digital/fsm/Transition.java new file mode 100644 index 000000000..af8a9bcda --- /dev/null +++ b/src/main/java/de/neemann/digital/fsm/Transition.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.fsm; + +import de.neemann.digital.analyse.expression.Expression; +import de.neemann.digital.analyse.expression.format.FormatToExpression; +import de.neemann.digital.analyse.expression.format.FormatterException; +import de.neemann.digital.draw.graphics.*; + +import java.util.List; + +/** + * Represents a transition + */ +public class Transition extends Movable { + private static final FormatToExpression FORMAT = FormatToExpression.FORMATTER_UNICODE; + private static final float EXPANSION_TRANS = 0.001f; + + private final State fromState; + private final State toState; + private final Expression condition; + + /** + * Creates a new transition + * + * @param fromState the state to leave + * @param toState the state to enter + * @param condition the condition + */ + public Transition(State fromState, State toState, Expression condition) { + super(); + this.fromState = fromState; + this.toState = toState; + this.condition = condition; + initPos(); + } + + /** + * Calculates the repulsive forces + * + * @param states the states + * @param transitions the transitions + */ + public void calcForce(List states, List transitions) { + float preferredLen = Math.max(fromState.getRadius(), toState.getRadius()) * 5; + calcForce(preferredLen, states, transitions); + } + + /** + * Calculates the repulsive forces + * + * @param preferredDist the preferred distance + * @param states the states + * @param transitions the transitions + */ + public void calcForce(float preferredDist, List states, List transitions) { + + VectorFloat dir = fromState.getPos().sub(toState.getPos()); + float len = dir.len(); + float d = len - preferredDist; + dir = dir.mul(EXPANSION_TRANS * d); + toState.addToForce(dir); + fromState.addToForce(dir.mul(-1)); + + resetForce(); + VectorFloat center = fromState.getPos().add(toState.getPos()).mul(0.5f); + addAttractiveTo(center, 1); + + for (State s : states) + addRepulsive(s.getPos(), 2000); + + for (Transition t : transitions) + if (t != this) + addRepulsiveInv(t.getPos(), 1000); + + } + + /** + * Draws the transition + * + * @param gr the Graphic instance to draw to + */ + public void drawTo(Graphic gr) { + VectorFloat difFrom = getPos().sub(fromState.getPos()).norm().mul(fromState.getRadius()); + VectorFloat difTo = getPos().sub(toState.getPos()).norm().mul(toState.getRadius()); + + final VectorFloat start = fromState.getPos().add(difFrom); + final VectorFloat end = toState.getPos().add(difTo); + + Polygon p = new Polygon(false) + .add(start) + .add(getPos(), getPos(), end); + final Style arrowStyle = Style.SHAPE_PIN; + gr.drawPolygon(p, arrowStyle); + +// gr.drawLine(start, getPos(), Style.THIN); +// gr.drawLine(getPos(), end, Style.THIN); + + // arrow + VectorFloat lot = new VectorFloat(difTo.getYFloat(), -difTo.getXFloat()).mul(0.5f); + gr.drawLine(end, end.add(difTo.add(lot).mul(0.2f)), arrowStyle); + gr.drawLine(end, end.add(difTo.sub(lot).mul(0.2f)), arrowStyle); + if (condition != null) { + String format; + try { + format = FORMAT.format(condition); + } catch (FormatterException e) { + format = "error"; + } + gr.drawText(getPos(), getPos().add(new Vector(1, 0)), format, Orientation.CENTERCENTER, Style.NORMAL); + } + } + + /** + * Initializes the position of the transition + */ + public void initPos() { + setPos(fromState.getPos().add(toState.getPos()).mul(0.5f) + .add(new VectorFloat((float) Math.random() - 0.5f, (float) Math.random() - 0.5f).mul(30))); + } +} diff --git a/src/main/java/de/neemann/digital/fsm/gui/FSMComponent.java b/src/main/java/de/neemann/digital/fsm/gui/FSMComponent.java new file mode 100644 index 000000000..ef6d2ec07 --- /dev/null +++ b/src/main/java/de/neemann/digital/fsm/gui/FSMComponent.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.fsm.gui; + +import de.neemann.digital.draw.graphics.GraphicMinMax; +import de.neemann.digital.draw.graphics.GraphicSwing; +import de.neemann.digital.draw.graphics.Style; +import de.neemann.digital.draw.graphics.Vector; +import de.neemann.digital.fsm.FSM; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Point2D; + +/** + * The component to show the fsm + */ +public class FSMComponent extends JComponent { + + private boolean isManualScale; + private AffineTransform transform = new AffineTransform(); + private FSM fsm; + + /** + * Creates a new component + * + * @param fsm the fsm to visualize + */ + public FSMComponent(FSM fsm) { + this.fsm = fsm; + + fsm.circle(); + + addMouseWheelListener(e -> { + Vector pos = getPosVector(e); + double f = Math.pow(0.9, e.getWheelRotation()); + transform.translate(pos.x, pos.y); + transform.scale(f, f); + transform.translate(-pos.x, -pos.y); + isManualScale = true; + repaint(); + }); + + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent componentEvent) { + if (!isManualScale) + fitFSM(); + } + }); + + setPreferredSize(new Dimension(600, 600)); + } + + private Vector getPosVector(MouseEvent e) { + return getPosVector(e.getX(), e.getY()); + } + + private Vector getPosVector(int x, int y) { + try { + Point2D.Double p = new Point2D.Double(); + transform.inverseTransform(new Point(x, y), p); + return new Vector((int) Math.round(p.getX()), (int) Math.round(p.getY())); + } catch (NoninvertibleTransformException e1) { + throw new RuntimeException(e1); + } + } + + /** + * Fits the FSM to the window + */ + public void fitFSM() { + GraphicMinMax gr = new GraphicMinMax(); + fsm.drawTo(gr); + + AffineTransform newTrans = new AffineTransform(); + if (gr.getMin() != null && getWidth() != 0 && getHeight() != 0) { + Vector delta = gr.getMax().sub(gr.getMin()); + double sx = ((double) getWidth()) / (delta.x + Style.NORMAL.getThickness() * 2); + double sy = ((double) getHeight()) / (delta.y + Style.NORMAL.getThickness() * 2); + double s = Math.min(sx, sy); + + + newTrans.setToScale(s, s); // set Scaling + + Vector center = gr.getMin().add(gr.getMax()).div(2); + newTrans.translate(-center.x, -center.y); // move drawing center to (0,0) + + Vector dif = new Vector(getWidth(), getHeight()).div(2); + newTrans.translate(dif.x / s, dif.y / s); // move drawing center to frame center + isManualScale = false; + } else { + isManualScale = true; + } + if (!newTrans.equals(transform)) { + transform = newTrans; + repaint(); + } + + } + + @Override + protected void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + + graphics.setColor(Color.WHITE); + graphics.fillRect(0, 0, getWidth(), getHeight()); + + Graphics2D gr2 = (Graphics2D) graphics; + gr2.transform(transform); + GraphicSwing gr = new GraphicSwing(gr2, 1); + fsm.drawTo(gr); + } +} diff --git a/src/main/java/de/neemann/digital/fsm/gui/FSMDialog.java b/src/main/java/de/neemann/digital/fsm/gui/FSMDialog.java new file mode 100644 index 000000000..bbcc1283f --- /dev/null +++ b/src/main/java/de/neemann/digital/fsm/gui/FSMDialog.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.fsm.gui; + +import de.neemann.digital.analyse.expression.Expression; +import de.neemann.digital.analyse.parser.ParseException; +import de.neemann.digital.analyse.parser.Parser; +import de.neemann.digital.fsm.FSM; +import de.neemann.digital.fsm.State; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; + +/** + * The dialog to show the FSM + */ +public class FSMDialog extends JDialog { + + private final FSM fsm; + private final FSMComponent fsmComponent; + private final Timer timer; + + /** + * Creates a new instance + * + * @param frame the parents frame + * @param fsm the fsm to visualize + */ + public FSMDialog(Frame frame, FSM fsm) { + super(frame, "FSM"); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + this.fsm = fsm; + + + fsmComponent = new FSMComponent(fsm); + getContentPane().add(fsmComponent); + pack(); + setLocationRelativeTo(frame); + + fsmComponent.fitFSM(); + + timer = new Timer(100, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + fsm.calculateForces(); + fsm.move(100); + repaint(); + } + }); + + timer.start(); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent windowEvent) { + timer.stop(); + } + }); + } + + /** + * A simple test method + * + * @param args the programs arguments + * @throws Exception Exception + */ + public static void main(String[] args) throws Exception { + /* + FSM fsm = new FSM() + .add(new State("initial").val("Y", 0)) + .add(new State("1 match").val("Y", 0)) + .add(new State("2 matches").val("Y", 0)) + .add(new State("found").val("Y", 1)) + .transition(0, 1, new Parser("!E").parse().get(0)) + .transition(1, 2, new Parser("!E").parse().get(0)) + .transition(2, 3, new Parser("E").parse().get(0)) + + .transition(1, 0, new Parser("E").parse().get(0)) + .transition(3, 0, new Parser("E").parse().get(0)) + .transition(3, 1, new Parser("!E").parse().get(0));*/ + + State top = new State("top"); + State topSet = new State("topSet").val("Y", 1); + State leftA = new State("leftA"); + State leftB = new State("leftB"); + State bottom = new State("bottom"); + State bottomSet = new State("bottomSet").val("Y", 1); + State rightA = new State("rightA"); + State rightB = new State("rightB"); + FSM fsm = new FSM(top, topSet, leftA, leftB, bottom, bottomSet, rightA, rightB) + .transition(top, leftA, e("A & !B")) + .transition(top, rightA, e("!A & B")) + .transition(topSet, top, null) + + .transition(rightA, top, e("!A & !B")) + .transition(rightB, topSet, e("!A & !B")) + .transition(leftA, top, e("!A & !B")) + .transition(leftB, topSet, e("!A & !B")) + + .transition(bottom, leftB, e("A & !B")) + .transition(bottom, rightB, e("!A & B")) + .transition(bottomSet, bottom, null) + + .transition(rightB, bottom, e("A & B")) + .transition(rightA, bottomSet, e("A & B")) + .transition(leftB, bottom, e("A & B")) + .transition(leftA, bottomSet, e("A & B")); + + + new FSMDialog(null, fsm).setVisible(true); + + } + + private static Expression e(String s) throws IOException, ParseException { + return new Parser(s).parse().get(0); + } +} diff --git a/src/main/java/de/neemann/digital/fsm/gui/package-info.java b/src/main/java/de/neemann/digital/fsm/gui/package-info.java new file mode 100644 index 000000000..634be4427 --- /dev/null +++ b/src/main/java/de/neemann/digital/fsm/gui/package-info.java @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +/** + * Classes to visualize the FSM + */ +package de.neemann.digital.fsm.gui; diff --git a/src/main/java/de/neemann/digital/fsm/package-info.java b/src/main/java/de/neemann/digital/fsm/package-info.java new file mode 100644 index 000000000..82a20dfa4 --- /dev/null +++ b/src/main/java/de/neemann/digital/fsm/package-info.java @@ -0,0 +1,10 @@ +/* + * Copyright (c) 2018 Helmut Neemann. + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ + +/** + * The classes needed to describe a finite state machine + */ +package de.neemann.digital.fsm; diff --git a/src/test/java/de/neemann/digital/fsm/FSMTest.java b/src/test/java/de/neemann/digital/fsm/FSMTest.java new file mode 100644 index 000000000..2977d1ae4 --- /dev/null +++ b/src/test/java/de/neemann/digital/fsm/FSMTest.java @@ -0,0 +1,22 @@ +package de.neemann.digital.fsm; + +import de.neemann.digital.draw.graphics.VectorFloat; +import junit.framework.TestCase; + +public class FSMTest extends TestCase { + + public void testSimple() throws FinitStateMachineException { + FSM fsm = new FSM() + .add(new State("0").setPosition(new VectorFloat(-1,0))) + .add(new State("1").setPosition(new VectorFloat(0,1))) + .add(new State("2").setPosition(new VectorFloat(1,0))) + .add(new State("3").setPosition(new VectorFloat(0,-1))) + .transition("0", "1", null) + .transition("1", "2", null) + .transition("2", "3", null) + .transition("3", "0", null); + + fsm.calculateForces(); + } + +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/fsm/StateTest.java b/src/test/java/de/neemann/digital/fsm/StateTest.java new file mode 100644 index 000000000..48169091b --- /dev/null +++ b/src/test/java/de/neemann/digital/fsm/StateTest.java @@ -0,0 +1,26 @@ +package de.neemann.digital.fsm; + +import de.neemann.digital.draw.graphics.VectorFloat; +import junit.framework.TestCase; + +import java.util.Arrays; + +public class StateTest extends TestCase { + + public void testCalcExpansionForce() { + State a = new State("a").setPosition(new VectorFloat(0, 0)); + State b = new State("b").setPosition(new VectorFloat(100, 0)); + + a.calcExpansionForce(Arrays.asList(a, b)); + assertEquals(0, a.getForce().getYFloat(), 1e-5); + final float near = a.getForce().getXFloat(); + assertTrue(near <= 0); + + b.setPosition(new VectorFloat(200, 0)); + a.calcExpansionForce(Arrays.asList(a, b)); + final float far = a.getForce().getXFloat(); + assertTrue(far <= 0); + assertTrue(Math.abs(far) < Math.abs(near)); + + } +} \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/fsm/TransitionTest.java b/src/test/java/de/neemann/digital/fsm/TransitionTest.java new file mode 100644 index 000000000..df050b8d5 --- /dev/null +++ b/src/test/java/de/neemann/digital/fsm/TransitionTest.java @@ -0,0 +1,42 @@ +package de.neemann.digital.fsm; + +import de.neemann.digital.draw.graphics.VectorFloat; +import junit.framework.TestCase; + +import java.util.Arrays; + +public class TransitionTest extends TestCase { + + public void testCalcForceIsPreferred() { + State a = new State("a").setPosition(new VectorFloat(0, 0)); + State b = new State("b").setPosition(new VectorFloat(10, 0)); + Transition t = new Transition(a, b, null); + t.calcForce(10, Arrays.asList(a, b), Arrays.asList(t)); + assertEquals(0, a.getForce().getXFloat(), 1e-5); + assertEquals(0, a.getForce().getYFloat(), 1e-5); + assertEquals(0, b.getForce().getXFloat(), 1e-5); + assertEquals(0, b.getForce().getYFloat(), 1e-5); + } + + public void testCalcForceToClose() { + State a = new State("a").setPosition(new VectorFloat(0, 0)); + State b = new State("b").setPosition(new VectorFloat(10, 0)); + Transition t = new Transition(a, b, null); + t.calcForce(20, Arrays.asList(a, b), Arrays.asList(t)); + assertTrue(a.getForce().getXFloat() < 0); + assertEquals(0, a.getForce().getYFloat(), 1e-5); + assertTrue(b.getForce().getXFloat() > 0); + assertEquals(0, b.getForce().getYFloat(), 1e-5); + } + + public void testCalcForceToFar() { + State a = new State("a").setPosition(new VectorFloat(0, 0)); + State b = new State("b").setPosition(new VectorFloat(10, 0)); + Transition t = new Transition(a, b, null); + t.calcForce(5, Arrays.asList(a, b), Arrays.asList(t)); + assertTrue(a.getForce().getXFloat() > 0); + assertEquals(0, a.getForce().getYFloat(), 1e-5); + assertTrue(b.getForce().getXFloat() < 0); + assertEquals(0, b.getForce().getYFloat(), 1e-5); + } +} \ No newline at end of file