implemented PathFinder, that searches for possible routes to a destinationblock
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -4,7 +4,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>de.srsoftware</groupId>
|
<groupId>de.srsoftware</groupId>
|
||||||
<artifactId>web4rail</artifactId>
|
<artifactId>web4rail</artifactId>
|
||||||
<version>0.11.4</version>
|
<version>0.11.5</version>
|
||||||
<name>Web4Rail</name>
|
<name>Web4Rail</name>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<description>Java Model Railway Control</description>
|
<description>Java Model Railway Control</description>
|
||||||
|
|||||||
116
src/main/java/de/srsoftware/web4rail/PathFinder.java
Normal file
116
src/main/java/de/srsoftware/web4rail/PathFinder.java
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package de.srsoftware.web4rail;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.srsoftware.web4rail.Plan.Direction;
|
||||||
|
import de.srsoftware.web4rail.actions.Action.Context;
|
||||||
|
import de.srsoftware.web4rail.moving.Train;
|
||||||
|
import de.srsoftware.web4rail.tiles.Block;
|
||||||
|
|
||||||
|
public class PathFinder extends BaseClass{
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(PathFinder.class);
|
||||||
|
|
||||||
|
private static TreeMap<Integer,List<Route>> availableRoutes(Context context,HashSet<Route> visitedRoutes){
|
||||||
|
TreeMap<Integer,List<Route>> availableRoutes = new TreeMap<Integer, List<Route>>();
|
||||||
|
|
||||||
|
String inset = "";
|
||||||
|
for (int i=0; i<visitedRoutes.size(); i++) inset+=" ";
|
||||||
|
|
||||||
|
boolean error = false;
|
||||||
|
Block block = context.block;
|
||||||
|
if (isNull(block)) {
|
||||||
|
LOG.warn("{} → {}.availableRoutes called without context.block!",inset,Train.class.getSimpleName());
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
Train train = context.train;
|
||||||
|
if (isNull(train)) {
|
||||||
|
LOG.warn("{}→ {}.availableRoutes called without context.train!",inset,Train.class.getSimpleName());
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
if (error) return availableRoutes;
|
||||||
|
|
||||||
|
Block destination = train.destination();
|
||||||
|
Direction direction = context.direction;
|
||||||
|
if (isSet(direction)) {
|
||||||
|
LOG.debug("{}Looking for {}-bound routes from {}",inset,direction,block);
|
||||||
|
} else {
|
||||||
|
LOG.debug("{}Looking for all routes from {}",inset,block);
|
||||||
|
}
|
||||||
|
if (isSet(destination) && visitedRoutes.isEmpty()) LOG.debug("{}- Destination: {}",inset,destination);
|
||||||
|
|
||||||
|
Route currentRoute = context.route;
|
||||||
|
|
||||||
|
for (Route routeCandidate : block.routes()) {
|
||||||
|
if (routeCandidate.path().firstElement() != block) continue; // Routen, die nicht vom aktuellen Block starten sind bubu
|
||||||
|
if (visitedRoutes.contains(routeCandidate)) {
|
||||||
|
LOG.debug("{}→ Candidate {} would create loop, skipping",inset,routeCandidate.shortName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!routeCandidate.allowed(context)) continue; // Zug darf auf Grund einer nicht erfüllten Bedingung nicht auf die Route
|
||||||
|
if (!routeCandidate.isFreeFor(train)) continue; // Route ist nicht frei
|
||||||
|
int priority = 0;
|
||||||
|
if (isSet(direction) && routeCandidate.startDirection != direction) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges
|
||||||
|
if (!train.pushPull) continue; // Zug kann nicht wenden
|
||||||
|
if (!block.turnAllowed) continue; // Wenden im Block nicht gestattet
|
||||||
|
priority -= 5;
|
||||||
|
}
|
||||||
|
if (routeCandidate == currentRoute) priority-=10; // möglichst andere Route als zuvor wählen // TODO: den Routen einen "last-used" Zeitstempel hinzufügen, und diesen mit in die Priorisierung einbeziehen
|
||||||
|
|
||||||
|
if (isSet(destination)) {
|
||||||
|
if (routeCandidate.endBlock() == destination) { // route goes directly to destination
|
||||||
|
LOG.debug("{}→ Candidate {} directly leads to {}",inset,routeCandidate.shortName(),destination);
|
||||||
|
priority = 1_000_000;
|
||||||
|
} else {
|
||||||
|
LOG.debug("{}- Candidate: {}",inset,routeCandidate.shortName());
|
||||||
|
Context forwardContext = new Context(train);
|
||||||
|
forwardContext.block = routeCandidate.endBlock();
|
||||||
|
forwardContext.direction = routeCandidate.endDirection.inverse();
|
||||||
|
forwardContext.route = null;
|
||||||
|
visitedRoutes.add(routeCandidate);
|
||||||
|
TreeMap<Integer, List<Route>> forwardRoutes = availableRoutes(forwardContext,visitedRoutes);
|
||||||
|
visitedRoutes.remove(routeCandidate);
|
||||||
|
if (forwardRoutes.isEmpty()) continue; // the candidate does not lead to a block, from which routes to the destination exist
|
||||||
|
Entry<Integer, List<Route>> entry = forwardRoutes.lastEntry();
|
||||||
|
LOG.debug("{}→ The following routes have connections to {}:",inset,destination);
|
||||||
|
for (Route rt: entry.getValue()) LOG.debug("{} - {}",inset,rt.shortName());
|
||||||
|
priority += entry.getKey()-10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Route> routeSet = availableRoutes.get(priority);
|
||||||
|
if (isNull(routeSet)) {
|
||||||
|
routeSet = new Vector<Route>();
|
||||||
|
availableRoutes.put(priority, routeSet);
|
||||||
|
}
|
||||||
|
routeSet.add(routeCandidate);
|
||||||
|
if (routeCandidate.endBlock() == destination) break; // direct connection to destination discovered, quit search
|
||||||
|
}
|
||||||
|
LOG.debug("{}→ Routes from {}: {}",inset,block,availableRoutes.isEmpty()?"none":"");
|
||||||
|
for (Entry<Integer, List<Route>> entry : availableRoutes.entrySet()) {
|
||||||
|
LOG.debug("{} - Priority {}:",inset,entry.getKey());
|
||||||
|
for (Route r : entry.getValue()) {
|
||||||
|
LOG.debug("{} - {}",inset,r.shortName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return availableRoutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Route chooseRoute(Context context) {
|
||||||
|
TreeMap<Integer, List<Route>> availableRoutes = PathFinder.availableRoutes(context,new HashSet<Route>());
|
||||||
|
if (availableRoutes.isEmpty()) return null;
|
||||||
|
Entry<Integer, List<Route>> entry = availableRoutes.lastEntry();
|
||||||
|
List<Route> preferredRoutes = entry.getValue();
|
||||||
|
Route selectetRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size()));
|
||||||
|
LOG.debug("Chose \"{}\" with priority {}.",selectetRoute,entry.getKey());
|
||||||
|
|
||||||
|
return selectetRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -70,7 +70,7 @@ public class Route extends BaseClass{
|
|||||||
private Vector<Contact> contacts;
|
private Vector<Contact> contacts;
|
||||||
private boolean disabled = false;
|
private boolean disabled = false;
|
||||||
private Block endBlock = null;
|
private Block endBlock = null;
|
||||||
private Direction endDirection;
|
public Direction endDirection;
|
||||||
private int id;
|
private int id;
|
||||||
private Vector<Tile> path;
|
private Vector<Tile> path;
|
||||||
private Vector<Signal> signals;
|
private Vector<Signal> signals;
|
||||||
@@ -683,4 +683,9 @@ public class Route extends BaseClass{
|
|||||||
}
|
}
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String shortName() {
|
||||||
|
String[] parts = name().split("-");
|
||||||
|
return parts[0].trim()+"–"+parts[parts.length-1].trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.srsoftware.web4rail.actions;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
@@ -15,6 +16,7 @@ import de.srsoftware.tools.Tag;
|
|||||||
import de.srsoftware.web4rail.Application;
|
import de.srsoftware.web4rail.Application;
|
||||||
import de.srsoftware.web4rail.BaseClass;
|
import de.srsoftware.web4rail.BaseClass;
|
||||||
import de.srsoftware.web4rail.Plan;
|
import de.srsoftware.web4rail.Plan;
|
||||||
|
import de.srsoftware.web4rail.Plan.Direction;
|
||||||
import de.srsoftware.web4rail.Route;
|
import de.srsoftware.web4rail.Route;
|
||||||
import de.srsoftware.web4rail.Window;
|
import de.srsoftware.web4rail.Window;
|
||||||
import de.srsoftware.web4rail.moving.Train;
|
import de.srsoftware.web4rail.moving.Train;
|
||||||
@@ -40,6 +42,7 @@ public abstract class Action extends BaseClass {
|
|||||||
public Route route = null;
|
public Route route = null;
|
||||||
public Train train = null;
|
public Train train = null;
|
||||||
public Block block = null;
|
public Block block = null;
|
||||||
|
public Direction direction = null;
|
||||||
|
|
||||||
public Context(Contact c) {
|
public Context(Contact c) {
|
||||||
contact = c;
|
contact = c;
|
||||||
@@ -66,8 +69,12 @@ public abstract class Action extends BaseClass {
|
|||||||
if (isSet(train)) {
|
if (isSet(train)) {
|
||||||
if (isNull(route)) route = train.route;
|
if (isNull(route)) route = train.route;
|
||||||
setBlock(train.currentBlock());
|
setBlock(train.currentBlock());
|
||||||
}
|
setDirection(train.direction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDirection(Direction dir) {
|
||||||
|
direction = dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setBlock(Block block) {
|
private void setBlock(Block block) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
@@ -25,6 +24,7 @@ import de.keawe.tools.translations.Translation;
|
|||||||
import de.srsoftware.tools.Tag;
|
import de.srsoftware.tools.Tag;
|
||||||
import de.srsoftware.web4rail.Application;
|
import de.srsoftware.web4rail.Application;
|
||||||
import de.srsoftware.web4rail.BaseClass;
|
import de.srsoftware.web4rail.BaseClass;
|
||||||
|
import de.srsoftware.web4rail.PathFinder;
|
||||||
import de.srsoftware.web4rail.Plan;
|
import de.srsoftware.web4rail.Plan;
|
||||||
import de.srsoftware.web4rail.Plan.Direction;
|
import de.srsoftware.web4rail.Plan.Direction;
|
||||||
import de.srsoftware.web4rail.Range;
|
import de.srsoftware.web4rail.Range;
|
||||||
@@ -172,56 +172,6 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
showTrace();
|
showTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Route chooseRoute(Context context) {
|
|
||||||
TreeMap<Integer, List<Route>> availableRoutes = availableRoutes(context);
|
|
||||||
if (availableRoutes.isEmpty()) return null;
|
|
||||||
Entry<Integer, List<Route>> entry = availableRoutes.firstEntry();
|
|
||||||
List<Route> preferredRoutes = entry.getValue();
|
|
||||||
Route selectetRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size()));
|
|
||||||
LOG.debug("Chose \"{}\" with priority {}.",selectetRoute,entry.getKey());
|
|
||||||
|
|
||||||
return selectetRoute;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TreeMap<Integer,List<Route>> availableRoutes(Context context){
|
|
||||||
TreeMap<Integer,List<Route>> availableRoutes = new TreeMap<Integer, List<Route>>();
|
|
||||||
|
|
||||||
boolean error = false;
|
|
||||||
if (isNull(context.block)) {
|
|
||||||
LOG.warn("{}.availableRoutes called without context.block!",Train.class.getSimpleName());
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
if (isNull(context.train)) {
|
|
||||||
LOG.warn("{}.availableRoutes called without context.train!",Train.class.getSimpleName());
|
|
||||||
error = true;
|
|
||||||
}
|
|
||||||
if (error) return availableRoutes;
|
|
||||||
|
|
||||||
Collection<Route> routes = context.block.routes();
|
|
||||||
|
|
||||||
for (Route rt : routes) {
|
|
||||||
if (rt.path().firstElement() != context.block) continue; // routen, die nicht vom aktuellen Block starten sind bubu
|
|
||||||
int priority = 0;
|
|
||||||
if (rt == context.route) priority-=10; // möglichst andere Route als zuvor wählen // TODO: den Routen einen "last-used" Zeitstempel hinzufügen, und diesen mit in die Priorisierung einbeziehen
|
|
||||||
if (isSet(context.train.direction) && rt.startDirection != context.train.direction) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges
|
|
||||||
if (!context.train.pushPull) continue; // Zug kann nicht wenden
|
|
||||||
if (!context.block.turnAllowed) continue; // Wenden im Block nicht gestattet
|
|
||||||
priority -= 5;
|
|
||||||
}
|
|
||||||
if (!rt.isFreeFor(context.train)) continue; // Route ist nicht frei
|
|
||||||
if (!rt.allowed(context)) continue; // Zug darf auf Grund einer nicht erfüllten Bedingung nicht auf die Route
|
|
||||||
|
|
||||||
List<Route> routeSet = availableRoutes.get(priority);
|
|
||||||
if (isNull(routeSet)) {
|
|
||||||
routeSet = new Vector<Route>();
|
|
||||||
availableRoutes.put(priority, routeSet);
|
|
||||||
}
|
|
||||||
routeSet.add(rt);
|
|
||||||
}
|
|
||||||
|
|
||||||
return availableRoutes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Train o) {
|
public int compareTo(Train o) {
|
||||||
return name().compareTo(o.toString());
|
return name().compareTo(o.toString());
|
||||||
@@ -622,10 +572,10 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
|
|
||||||
public String start() throws IOException {
|
public String start() throws IOException {
|
||||||
if (isNull(currentBlock)) return t("{} not in a block",this);
|
if (isNull(currentBlock)) return t("{} not in a block",this);
|
||||||
Context context = isSet(route) ? new Context( route ) : new Context( this);
|
if (isSet(route)) route.reset(); // reset route previously chosen
|
||||||
if (isSet(context.route)) context.route.reset(); // reset route previously chosen
|
|
||||||
|
|
||||||
route = chooseRoute(context);
|
Context context = new Context(this);
|
||||||
|
route = PathFinder.chooseRoute(context);
|
||||||
if (isNull(route)) return t("No free routes from {}",currentBlock);
|
if (isNull(route)) return t("No free routes from {}",currentBlock);
|
||||||
if (!route.lock()) return t("Was not able to lock {}",route);
|
if (!route.lock()) return t("Was not able to lock {}",route);
|
||||||
|
|
||||||
@@ -706,4 +656,8 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
public Block currentBlock() {
|
public Block currentBlock() {
|
||||||
return currentBlock;
|
return currentBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Block destination() {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ public abstract class Block extends StretchableTile{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getClass().getSimpleName()+"("+name+") @ ("+x+","+y+")";
|
return name + " @ ("+x+","+y+")";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ public abstract class Tile extends BaseClass{
|
|||||||
Tag routeList = new Tag("ol");
|
Tag routeList = new Tag("ol");
|
||||||
for (Route route : routes) {
|
for (Route route : routes) {
|
||||||
String json = new JSONObject(Map.of(REALM,ROUTE,ID,route.id(),ACTION,ACTION_PROPS,CONTEXT,REALM_PLAN+":"+id())).toString().replace("\"", "'");
|
String json = new JSONObject(Map.of(REALM,ROUTE,ID,route.id(),ACTION,ACTION_PROPS,CONTEXT,REALM_PLAN+":"+id())).toString().replace("\"", "'");
|
||||||
Tag li = new Tag("span").attr("onclick","return request("+json+");").content(route.name()+(route.isDisabled()?" ["+t("disabled")+"]" : "")+NBSP).addTo(new Tag("li").clazz("link"));
|
Tag li = new Tag("span").attr("onclick","return request("+json+");").content(route.shortName()+(route.isDisabled()?" ["+t("disabled")+"]" : "")+NBSP).addTo(new Tag("li").clazz("link"));
|
||||||
Map<String, Object> params = Map.of(REALM,REALM_ROUTE,ID,route.id(),ACTION,ACTION_DROP,Tile.class.getSimpleName(),id());
|
Map<String, Object> params = Map.of(REALM,REALM_ROUTE,ID,route.id(),ACTION,ACTION_DROP,Tile.class.getSimpleName(),id());
|
||||||
new Button(t("delete route"),params).addTo(li);
|
new Button(t("delete route"),params).addTo(li);
|
||||||
li.addTo(routeList);
|
li.addTo(routeList);
|
||||||
|
|||||||
Reference in New Issue
Block a user