re-implemented route-search
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>1.5.8</version>
|
<version>1.5.9</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>
|
||||||
|
|||||||
@@ -460,7 +460,7 @@ TurnoutRS : WeicheRS
|
|||||||
TurnoutRW : WeicheRW
|
TurnoutRW : WeicheRW
|
||||||
Turnouts : Weichen
|
Turnouts : Weichen
|
||||||
turn within train : innerhalb des Zugs drehen
|
turn within train : innerhalb des Zugs drehen
|
||||||
Turns the train, as if it went through a loop. : Dreht den ZUg, als wenn er eine Wendeschleife passiert hätte.
|
Turns the train, as if it went through a loop. : Dreht den Zug, als wenn er eine Wendeschleife passiert hätte.
|
||||||
Type : Typ
|
Type : Typ
|
||||||
Unknown action\: {} : Unbekannte Aktion: {}
|
Unknown action\: {} : Unbekannte Aktion: {}
|
||||||
Unknown decoder type : Unbekannter Decoder-Typ
|
Unknown decoder type : Unbekannter Decoder-Typ
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Block destination(){
|
public Block destination(){
|
||||||
LOG.debug("{}.destination()",this);
|
//LOG.debug("{}.destination()",this);
|
||||||
if (isNull(destination)) {
|
if (isNull(destination)) {
|
||||||
String destTag = destinationTag();
|
String destTag = destinationTag();
|
||||||
LOG.debug("→ processing \"{}\"...",destTag);
|
LOG.debug("→ processing \"{}\"...",destTag);
|
||||||
@@ -985,7 +985,7 @@ public class Train extends BaseClass implements Comparable<Train> {
|
|||||||
if (isSet(currentBlock)) plan.place(currentBlock);
|
if (isSet(currentBlock)) plan.place(currentBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
x if (isSet(route)) {
|
if (isSet(route)) {
|
||||||
LOG.debug("Train already on route {}",route);
|
LOG.debug("Train already on route {}",route);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -1141,6 +1141,7 @@ x if (isSet(route)) {
|
|||||||
|
|
||||||
public Context updateTrace(Context context) {
|
public Context updateTrace(Context context) {
|
||||||
LOG.debug("updateTrace({})",context);
|
LOG.debug("updateTrace({})",context);
|
||||||
|
if (isNull(context)) return null;
|
||||||
Tile from = context.tile();
|
Tile from = context.tile();
|
||||||
if (isNull(from)) from = context.contact();
|
if (isNull(from)) from = context.contact();
|
||||||
if (isNull(from)) {
|
if (isNull(from)) {
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package de.srsoftware.web4rail.threads;
|
package de.srsoftware.web4rail.threads;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.PriorityQueue;
|
||||||
import java.util.TreeMap;
|
import java.util.Vector;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import de.srsoftware.web4rail.Application;
|
import de.srsoftware.web4rail.Application;
|
||||||
import de.srsoftware.web4rail.BaseClass;
|
import de.srsoftware.web4rail.BaseClass;
|
||||||
@@ -19,21 +18,65 @@ import de.srsoftware.web4rail.tiles.Tile;
|
|||||||
|
|
||||||
public class RoutePrepper extends BaseClass implements Runnable{
|
public class RoutePrepper extends BaseClass implements Runnable{
|
||||||
|
|
||||||
private static class Candidate{
|
|
||||||
|
|
||||||
private int score;
|
public static class Trail extends Vector<Route> implements Comparable<Trail> {
|
||||||
private Route route;
|
|
||||||
|
|
||||||
public Candidate(Route r, int s) {
|
private static final long serialVersionUID = -620911121007236751L;
|
||||||
route = r;
|
private int score = 0;
|
||||||
score = s;
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Trail other) {
|
||||||
|
return other.score() - this.score();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public Trail add(Route route, int score) {
|
||||||
public String toString() {
|
add(route);
|
||||||
return route.toString().replace(")", ", score: "+score+")");
|
this.score += score;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<Trail> derive(Collection<Trail> ongoing) {
|
||||||
|
Vector<Trail> derived = new Vector<>();
|
||||||
|
for (Trail trail : ongoing) {
|
||||||
|
derived.add(trail.prepend(this));
|
||||||
|
}
|
||||||
|
return derived;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean endsAt(Block block) {
|
||||||
|
return lastElement().endBlock() == block;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Trail prepend(Trail trail) {
|
||||||
|
addAll(0, trail);
|
||||||
|
score += trail.score;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int score() {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder("\n"+getClass().getSimpleName());
|
||||||
|
sb.append("(Score ");
|
||||||
|
sb.append(score);
|
||||||
|
sb.append(": ");
|
||||||
|
for (Route r : this) {
|
||||||
|
sb.append(r.startBlock().name);
|
||||||
|
sb.append(" → ");
|
||||||
|
}
|
||||||
|
if (!isEmpty())sb.append(lastElement().endBlock().name);
|
||||||
|
return sb+")";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
private Route route;
|
private Route route;
|
||||||
@@ -52,7 +95,7 @@ public class RoutePrepper extends BaseClass implements Runnable{
|
|||||||
context = c;
|
context = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TreeMap<Integer,LinkedList<Route>> availableRoutes(Context c){
|
private static PriorityQueue<Trail> availableRoutes(Context c){
|
||||||
boolean error = false;
|
boolean error = false;
|
||||||
|
|
||||||
Block startBlock = c.block();
|
Block startBlock = c.block();
|
||||||
@@ -61,14 +104,14 @@ public class RoutePrepper extends BaseClass implements Runnable{
|
|||||||
Train train = c.train();
|
Train train = c.train();
|
||||||
if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.availableRoutes(…) called without a startBlock!");
|
if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.availableRoutes(…) called without a startBlock!");
|
||||||
|
|
||||||
if (error) return new TreeMap<>();
|
if (error) return new PriorityQueue<>();
|
||||||
|
|
||||||
Block destination = train.destination();
|
Block destination = train.destination();
|
||||||
|
|
||||||
Direction startDirection = c.direction();
|
Direction startDirection = c.direction();
|
||||||
LOG.debug("RoutePrepper.availableRoutes({},{},{}), dest = {}",startBlock,startDirection,train,destination);
|
LOG.debug("RoutePrepper.availableRoutes({},{},{}), dest = {}",startBlock,startDirection,train,destination);
|
||||||
|
|
||||||
TreeMap<Integer, LinkedList<Route>> candidates = routesFrom(c); // map: score → [route]
|
PriorityQueue<Trail> candidates = routesFrom(c); // map: route → score
|
||||||
|
|
||||||
if (isNull(destination)) {
|
if (isNull(destination)) {
|
||||||
LOG.debug("{} has no destination, returning {}",train,candidates);
|
LOG.debug("{} has no destination, returning {}",train,candidates);
|
||||||
@@ -77,114 +120,57 @@ public class RoutePrepper extends BaseClass implements Runnable{
|
|||||||
|
|
||||||
LOG.debug("{} is heading for {}, starting breadth-first search…",train,destination);
|
LOG.debug("{} is heading for {}, starting breadth-first search…",train,destination);
|
||||||
|
|
||||||
HashMap<Route,Candidate> predecessors = new HashMap<>() {
|
for (int depth=1; depth<99; depth++) {
|
||||||
private static final long serialVersionUID = -42682947866294566L;
|
if (candidates.stream().filter(trail -> trail.endsAt(destination)).count() > 0) return candidates; // return connectingTrails + other routes, in case all connecting trails are occupied
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return entrySet().stream()
|
|
||||||
.sorted((e1,e2) -> e1.getValue().toString().compareTo(e2.getValue().toString()))
|
|
||||||
.map(entry -> entry.getValue()+" → "+entry.getKey())
|
|
||||||
.collect(Collectors.joining("\n"));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
candidates.entrySet().stream().flatMap(entry -> entry.getValue().stream()).forEach(route -> predecessors.put(route, null));
|
|
||||||
TreeMap<Integer,LinkedList<Route>> routesToDest = new TreeMap<>();
|
|
||||||
|
|
||||||
int level = 0;
|
|
||||||
int level2 = 0;
|
|
||||||
|
|
||||||
while (!candidates.isEmpty()) {
|
|
||||||
LOG.debug("Candidates for level {}:",level);
|
|
||||||
candidates.entrySet().stream().flatMap(entry -> entry.getValue().stream()).forEach(route -> LOG.debug(" - {}",route));
|
|
||||||
|
|
||||||
TreeMap<Integer, LinkedList<Route>> queue = new TreeMap<>();
|
|
||||||
|
|
||||||
while (!candidates.isEmpty()) {
|
PriorityQueue<Trail> nextLevelCandidates = new PriorityQueue<Trail>();
|
||||||
Candidate candidate = pop(candidates);
|
for (Trail trail : candidates) {
|
||||||
LOG.debug(" - examining {}…",candidate);
|
Route lastRoute = trail.lastElement();
|
||||||
|
c.block(lastRoute.endBlock()).direction(lastRoute.endDirection);
|
||||||
Block endBlock = candidate.route.endBlock();
|
PriorityQueue<Trail> ongoing = routesFrom(c);
|
||||||
Direction endDir = candidate.route.endDirection;
|
if (!ongoing.isEmpty()) nextLevelCandidates.addAll(trail.derive(ongoing));
|
||||||
|
|
||||||
if (endBlock == destination) {
|
|
||||||
LOG.debug(" - {} reaches destination!",candidate);
|
|
||||||
int score = candidate.score;
|
|
||||||
|
|
||||||
// The route we found leads to the destination block.
|
|
||||||
// However it might be the last route in a long path.
|
|
||||||
// Thus, we need to get the first route in this path:
|
|
||||||
while (predecessors.containsKey(candidate.route)) {
|
|
||||||
Candidate predecessor = predecessors.get(candidate.route);
|
|
||||||
if (isNull(predecessor)) break;
|
|
||||||
LOG.debug(" - {} is predecessed by {}",candidate,predecessor);
|
|
||||||
candidate = predecessor;
|
|
||||||
score += candidate.score;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug(" → path starts with {} and has total score of {}",candidate.route,score);
|
|
||||||
LinkedList<Route> routesForScore = routesToDest.get(score);
|
|
||||||
if (isNull(routesForScore)) routesToDest.put(score, routesForScore = new LinkedList<Route>());
|
|
||||||
routesForScore.add(candidate.route);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug(" - {} not reaching {}, adding ongoing routes to queue:",candidate,destination);
|
|
||||||
TreeMap<Integer, LinkedList<Route>> successors = routesFrom(c.clone().block(endBlock).direction(endDir));
|
|
||||||
while (!successors.isEmpty()) {
|
|
||||||
int score = successors.firstKey();
|
|
||||||
LinkedList<Route> best = successors.remove(score);
|
|
||||||
score -= 25; // Nachfolgeroute
|
|
||||||
for (Route route : best) {
|
|
||||||
LOG.debug(" - queueing {} with score {}",route,score);
|
|
||||||
if (predecessors.containsKey(route)) {
|
|
||||||
LOG.debug("this route already has a predecessor: {}",predecessors.get(route));
|
|
||||||
continue; // Route wurde bereits besucht
|
|
||||||
}
|
|
||||||
predecessors.put(route, candidate);
|
|
||||||
|
|
||||||
LinkedList<Route> list = queue.get(score);
|
|
||||||
if (isNull(list)) queue.put(score, list = new LinkedList<>());
|
|
||||||
list.add(route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
candidates = nextLevelCandidates;
|
||||||
if (!routesToDest.isEmpty()) {
|
|
||||||
if (level2++ > 5) return routesToDest;
|
|
||||||
} else LOG.debug("No routes to {} found with distance {}!",destination,level);
|
|
||||||
level ++;
|
|
||||||
candidates = queue;
|
|
||||||
}
|
|
||||||
LOG.debug("No more candidates for routes towards {}!",destination);
|
|
||||||
|
|
||||||
return routesToDest;
|
}
|
||||||
|
return new PriorityQueue<>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Route chooseRoute(Context context) {
|
private static Route chooseRoute(Context context) {
|
||||||
LOG.debug("{}.chooseRoute({})", RoutePrepper.class.getSimpleName(), context);
|
LOG.debug("{}.chooseRoute({})", RoutePrepper.class.getSimpleName(), context);
|
||||||
TreeMap<Integer, LinkedList<Route>> availableRoutes = availableRoutes(context);
|
PriorityQueue<Trail> availableRoutes = availableRoutes(context);
|
||||||
LOG.debug("available routes: {}",availableRoutes);
|
LOG.debug("available routes: {}",availableRoutes);
|
||||||
while (!availableRoutes.isEmpty()) {
|
while (!availableRoutes.isEmpty()) {
|
||||||
if (context.invalidated()) break;
|
if (context.invalidated()) break;
|
||||||
LOG.debug("availableRoutes: {}", availableRoutes);
|
LOG.debug("availableRoutes: {}", availableRoutes);
|
||||||
Entry<Integer, LinkedList<Route>> entry = availableRoutes.lastEntry();
|
|
||||||
List<Route> preferredRoutes = entry.getValue();
|
|
||||||
LOG.debug("preferredRoutes: {}", preferredRoutes);
|
|
||||||
Route selectedRoute = preferredRoutes.get(random.nextInt(preferredRoutes.size()));
|
|
||||||
|
|
||||||
HashSet<Tile> stuckTrace = context.train().stuckTrace(); // if train has been stopped in between two blocks lastly:
|
Vector<Route> preferredRoutes = new Vector<>();
|
||||||
// only allow starting routes that do not conflict with current train position
|
Integer score = null;
|
||||||
if (isSet(stuckTrace) && !selectedRoute.path().containsAll(stuckTrace)) {
|
while (!availableRoutes.isEmpty()) {
|
||||||
LOG.debug("Stuck train occupies tiles ({}) outside of {} – not allowed.", stuckTrace, selectedRoute);
|
Trail trail = availableRoutes.peek();
|
||||||
} else if (selectedRoute.isFreeFor(context)) {
|
if (score == null) score = trail.score();
|
||||||
LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, entry.getKey());
|
if (score != trail.score()) break;
|
||||||
return selectedRoute;
|
availableRoutes.remove(trail);
|
||||||
|
preferredRoutes.add(trail.firstElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.debug("Selected route \"{}\" is not free for {}", selectedRoute, context);
|
LOG.debug("preferredRoutes: {}", preferredRoutes);
|
||||||
preferredRoutes.remove(selectedRoute);
|
|
||||||
if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey());
|
while (!preferredRoutes.isEmpty()) {
|
||||||
|
Route selectedRoute = preferredRoutes.remove(random.nextInt(preferredRoutes.size()));
|
||||||
|
HashSet<Tile> stuckTrace = context.train().stuckTrace(); // if train has been stopped in between two blocks lastly:
|
||||||
|
// only allow starting routes that do not conflict with current train position
|
||||||
|
if (isSet(stuckTrace) && !selectedRoute.path().containsAll(stuckTrace)) {
|
||||||
|
LOG.debug("Stuck train occupies tiles ({}) outside of {} – not allowed.", stuckTrace, selectedRoute);
|
||||||
|
continue;
|
||||||
|
} else if (!selectedRoute.isFreeFor(context)) {
|
||||||
|
LOG.debug("Selected route \"{}\" is not free for {}", selectedRoute, context);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, score);
|
||||||
|
return selectedRoute;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -217,21 +203,6 @@ public class RoutePrepper extends BaseClass implements Runnable{
|
|||||||
preparedListener = l;
|
preparedListener = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Candidate pop(TreeMap<Integer, LinkedList<Route>> candidates) {
|
|
||||||
while (!candidates.isEmpty()) {
|
|
||||||
int score = candidates.firstKey();
|
|
||||||
LinkedList<Route> list = candidates.get(score);
|
|
||||||
if (isNull(list) || list.isEmpty()) {
|
|
||||||
candidates.remove(score);
|
|
||||||
} else {
|
|
||||||
Candidate candidate = new Candidate(list.removeFirst(),score);
|
|
||||||
if (list.isEmpty()) candidates.remove(score);
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean prepareRoute() {
|
public boolean prepareRoute() {
|
||||||
if (isNull(context) || context.invalidated()) return fail();
|
if (isNull(context) || context.invalidated()) return fail();
|
||||||
route = chooseRoute(context);
|
route = chooseRoute(context);
|
||||||
@@ -250,28 +221,28 @@ public class RoutePrepper extends BaseClass implements Runnable{
|
|||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TreeMap<Integer,LinkedList<Route>> routesFrom(Context c){
|
private static PriorityQueue<Trail> routesFrom(Context context){
|
||||||
boolean error = false;
|
boolean error = false;
|
||||||
|
|
||||||
Block startBlock = c.block();
|
Block startBlock = context.block();
|
||||||
if (isNull(startBlock) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!");
|
if (isNull(startBlock) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!");
|
||||||
|
|
||||||
Train train = c.train();
|
Train train = context.train();
|
||||||
if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!");
|
if (isNull(train) && (error=true)) LOG.warn("RoutePrepper.routesFrom(…) called without a startBlock!");
|
||||||
|
|
||||||
if (error) return null;
|
if (error) return null;
|
||||||
|
|
||||||
Block destination = train.destination();
|
Block destination = train.destination();
|
||||||
|
|
||||||
Direction startDirection = c.direction();
|
Direction startDirection = context.direction();
|
||||||
|
|
||||||
LOG.debug(" RoutePrepper.routesFrom({},{},{}), dest = {}:",startBlock,startDirection,train,destination);
|
LOG.debug(" RoutePrepper.routesFrom({},{},{}), dest = {}:",startBlock,startDirection,train,destination);
|
||||||
|
|
||||||
TreeMap<Integer, LinkedList<Route>> routes = new TreeMap<>();
|
PriorityQueue<Trail> trails = new PriorityQueue<>();
|
||||||
|
|
||||||
for (Route route : startBlock.leavingRoutes()) {
|
for (Route route : startBlock.leavingRoutes()) {
|
||||||
int score = 0;
|
int score = (route.endBlock() == destination) ? 100_000 : 0;
|
||||||
|
|
||||||
if (isSet(startDirection) && route.startDirection != startDirection) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges
|
if (isSet(startDirection) && route.startDirection != startDirection) { // Route startet entgegen der aktuellen Fahrtrichtung des Zuges
|
||||||
if (!train.pushPull) continue; // Zug kann nicht wenden
|
if (!train.pushPull) continue; // Zug kann nicht wenden
|
||||||
if (!startBlock.turnAllowed) continue; // Wenden im Block nicht gestattet
|
if (!startBlock.turnAllowed) continue; // Wenden im Block nicht gestattet
|
||||||
@@ -286,16 +257,10 @@ public class RoutePrepper extends BaseClass implements Runnable{
|
|||||||
LOG.debug(" …overridden by destination of train!", route, train);
|
LOG.debug(" …overridden by destination of train!", route, train);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.endBlock() == destination) score = 100_000;
|
trails.add(new Trail().add(route,score));
|
||||||
|
|
||||||
|
|
||||||
LinkedList<Route> routesForScore = routes.get(score);
|
|
||||||
if (isNull(routesForScore)) routes.put(score, routesForScore = new LinkedList<Route>());
|
|
||||||
LOG.debug(" → candidate!");
|
|
||||||
routesForScore.add(route);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return routes;
|
return trails;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user