mirror of
https://github.com/hneemann/Digital.git
synced 2025-09-15 15:58:41 -04:00
playing around with fsm generation
This commit is contained in:
parent
03e048ee5e
commit
a0c134b01d
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -73,4 +73,9 @@ public interface VectorInterface {
|
||||
* @return a int vector
|
||||
*/
|
||||
Vector round();
|
||||
|
||||
/**
|
||||
* @return the length of the vector
|
||||
*/
|
||||
float len();
|
||||
}
|
||||
|
168
src/main/java/de/neemann/digital/fsm/FSM.java
Normal file
168
src/main/java/de/neemann/digital/fsm/FSM.java
Normal file
@ -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<State> states;
|
||||
private ArrayList<Transition> 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
125
src/main/java/de/neemann/digital/fsm/Movable.java
Normal file
125
src/main/java/de/neemann/digital/fsm/Movable.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
127
src/main/java/de/neemann/digital/fsm/State.java
Normal file
127
src/main/java/de/neemann/digital/fsm/State.java
Normal file
@ -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<String, Long> 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<State> 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<String, Long> 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;
|
||||
}
|
||||
}
|
124
src/main/java/de/neemann/digital/fsm/Transition.java
Normal file
124
src/main/java/de/neemann/digital/fsm/Transition.java
Normal file
@ -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<State> states, List<Transition> 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<State> states, List<Transition> 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)));
|
||||
}
|
||||
}
|
122
src/main/java/de/neemann/digital/fsm/gui/FSMComponent.java
Normal file
122
src/main/java/de/neemann/digital/fsm/gui/FSMComponent.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
124
src/main/java/de/neemann/digital/fsm/gui/FSMDialog.java
Normal file
124
src/main/java/de/neemann/digital/fsm/gui/FSMDialog.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
10
src/main/java/de/neemann/digital/fsm/package-info.java
Normal file
10
src/main/java/de/neemann/digital/fsm/package-info.java
Normal file
@ -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;
|
22
src/test/java/de/neemann/digital/fsm/FSMTest.java
Normal file
22
src/test/java/de/neemann/digital/fsm/FSMTest.java
Normal file
@ -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();
|
||||
}
|
||||
|
||||
}
|
26
src/test/java/de/neemann/digital/fsm/StateTest.java
Normal file
26
src/test/java/de/neemann/digital/fsm/StateTest.java
Normal file
@ -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.<State>asList(a, b));
|
||||
final float far = a.getForce().getXFloat();
|
||||
assertTrue(far <= 0);
|
||||
assertTrue(Math.abs(far) < Math.abs(near));
|
||||
|
||||
}
|
||||
}
|
42
src/test/java/de/neemann/digital/fsm/TransitionTest.java
Normal file
42
src/test/java/de/neemann/digital/fsm/TransitionTest.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user