Browse Source

re-implemented route-search

lookup-tables
Stephan Richter 4 years ago
parent
commit
7170952cb5
  1. 2
      pom.xml
  2. 2
      resources/translations/Application.de.translation
  3. 5
      src/main/java/de/srsoftware/web4rail/moving/Train.java
  4. 241
      src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java

2
pom.xml

@ -4,7 +4,7 @@ @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>de.srsoftware</groupId>
<artifactId>web4rail</artifactId>
<version>1.5.8</version>
<version>1.5.9</version>
<name>Web4Rail</name>
<packaging>jar</packaging>
<description>Java Model Railway Control</description>

2
resources/translations/Application.de.translation

@ -460,7 +460,7 @@ TurnoutRS : WeicheRS @@ -460,7 +460,7 @@ TurnoutRS : WeicheRS
TurnoutRW : WeicheRW
Turnouts : Weichen
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
Unknown action\: {} : Unbekannte Aktion: {}
Unknown decoder type : Unbekannter Decoder-Typ

5
src/main/java/de/srsoftware/web4rail/moving/Train.java

@ -360,7 +360,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -360,7 +360,7 @@ public class Train extends BaseClass implements Comparable<Train> {
}
public Block destination(){
LOG.debug("{}.destination()",this);
//LOG.debug("{}.destination()",this);
if (isNull(destination)) {
String destTag = destinationTag();
LOG.debug("→ processing \"{}\"...",destTag);
@ -985,7 +985,7 @@ public class Train extends BaseClass implements Comparable<Train> { @@ -985,7 +985,7 @@ public class Train extends BaseClass implements Comparable<Train> {
if (isSet(currentBlock)) plan.place(currentBlock);
}
x if (isSet(route)) {
if (isSet(route)) {
LOG.debug("Train already on route {}",route);
return null;
}
@ -1141,6 +1141,7 @@ x if (isSet(route)) { @@ -1141,6 +1141,7 @@ x if (isSet(route)) {
public Context updateTrace(Context context) {
LOG.debug("updateTrace({})",context);
if (isNull(context)) return null;
Tile from = context.tile();
if (isNull(from)) from = context.contact();
if (isNull(from)) {

241
src/main/java/de/srsoftware/web4rail/threads/RoutePrepper.java

@ -1,12 +1,11 @@ @@ -1,12 +1,11 @@
package de.srsoftware.web4rail.threads;
import java.util.HashMap;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.PriorityQueue;
import java.util.Vector;
import de.srsoftware.web4rail.Application;
import de.srsoftware.web4rail.BaseClass;
@ -19,21 +18,65 @@ import de.srsoftware.web4rail.tiles.Tile; @@ -19,21 +18,65 @@ import de.srsoftware.web4rail.tiles.Tile;
public class RoutePrepper extends BaseClass implements Runnable{
private static class Candidate{
public static class Trail extends Vector<Route> implements Comparable<Trail> {
private int score;
private Route route;
private static final long serialVersionUID = -620911121007236751L;
private int score = 0;
public Candidate(Route r, int s) {
route = r;
score = s;
@Override
public int compareTo(Trail other) {
return other.score() - this.score();
}
public Trail add(Route route, int score) {
add(route);
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 String toString() {
return route.toString().replace(")", ", score: "+score+")");
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 Route route;
@ -52,7 +95,7 @@ public class RoutePrepper extends BaseClass implements Runnable{ @@ -52,7 +95,7 @@ public class RoutePrepper extends BaseClass implements Runnable{
context = c;
}
private static TreeMap<Integer,LinkedList<Route>> availableRoutes(Context c){
private static PriorityQueue<Trail> availableRoutes(Context c){
boolean error = false;
Block startBlock = c.block();
@ -61,14 +104,14 @@ public class RoutePrepper extends BaseClass implements Runnable{ @@ -61,14 +104,14 @@ public class RoutePrepper extends BaseClass implements Runnable{
Train train = c.train();
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();
Direction startDirection = c.direction();
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)) {
LOG.debug("{} has no destination, returning {}",train,candidates);
@ -77,114 +120,57 @@ public class RoutePrepper extends BaseClass implements Runnable{ @@ -77,114 +120,57 @@ public class RoutePrepper extends BaseClass implements Runnable{
LOG.debug("{} is heading for {}, starting breadth-first search…",train,destination);
HashMap<Route,Candidate> predecessors = new HashMap<>() {
private static final long serialVersionUID = -42682947866294566L;
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<>();
for (int depth=1; depth<99; depth++) {
if (candidates.stream().filter(trail -> trail.endsAt(destination)).count() > 0) return candidates; // return connectingTrails + other routes, in case all connecting trails are occupied
while (!candidates.isEmpty()) {
Candidate candidate = pop(candidates);
LOG.debug(" - examining {}…",candidate);
Block endBlock = candidate.route.endBlock();
Direction endDir = candidate.route.endDirection;
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);
}
}
PriorityQueue<Trail> nextLevelCandidates = new PriorityQueue<Trail>();
for (Trail trail : candidates) {
Route lastRoute = trail.lastElement();
c.block(lastRoute.endBlock()).direction(lastRoute.endDirection);
PriorityQueue<Trail> ongoing = routesFrom(c);
if (!ongoing.isEmpty()) nextLevelCandidates.addAll(trail.derive(ongoing));
}
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);
candidates = nextLevelCandidates;
return routesToDest;
}
return new PriorityQueue<>();
}
private static Route chooseRoute(Context 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);
while (!availableRoutes.isEmpty()) {
if (context.invalidated()) break;
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:
// 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);
} else if (selectedRoute.isFreeFor(context)) {
LOG.debug("Chose \"{}\" with priority {}.", selectedRoute, entry.getKey());
return selectedRoute;
Vector<Route> preferredRoutes = new Vector<>();
Integer score = null;
while (!availableRoutes.isEmpty()) {
Trail trail = availableRoutes.peek();
if (score == null) score = trail.score();
if (score != trail.score()) break;
availableRoutes.remove(trail);
preferredRoutes.add(trail.firstElement());
}
LOG.debug("Selected route \"{}\" is not free for {}", selectedRoute, context);
preferredRoutes.remove(selectedRoute);
if (preferredRoutes.isEmpty()) availableRoutes.remove(availableRoutes.lastKey());
LOG.debug("preferredRoutes: {}", preferredRoutes);
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;
}
@ -217,21 +203,6 @@ public class RoutePrepper extends BaseClass implements Runnable{ @@ -217,21 +203,6 @@ public class RoutePrepper extends BaseClass implements Runnable{
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() {
if (isNull(context) || context.invalidated()) return fail();
route = chooseRoute(context);
@ -250,28 +221,28 @@ public class RoutePrepper extends BaseClass implements Runnable{ @@ -250,28 +221,28 @@ public class RoutePrepper extends BaseClass implements Runnable{
return route;
}
private static TreeMap<Integer,LinkedList<Route>> routesFrom(Context c){
private static PriorityQueue<Trail> routesFrom(Context context){
boolean error = false;
Block startBlock = c.block();
Block startBlock = context.block();
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 (error) return null;
Block destination = train.destination();
Direction startDirection = c.direction();
Direction startDirection = context.direction();
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()) {
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 (!train.pushPull) continue; // Zug kann nicht wenden
if (!startBlock.turnAllowed) continue; // Wenden im Block nicht gestattet
@ -286,16 +257,10 @@ public class RoutePrepper extends BaseClass implements Runnable{ @@ -286,16 +257,10 @@ public class RoutePrepper extends BaseClass implements Runnable{
LOG.debug(" …overridden by destination of train!", route, train);
}
if (route.endBlock() == destination) score = 100_000;
LinkedList<Route> routesForScore = routes.get(score);
if (isNull(routesForScore)) routes.put(score, routesForScore = new LinkedList<Route>());
LOG.debug(" → candidate!");
routesForScore.add(route);
trails.add(new Trail().add(route,score));
}
return routes;
return trails;
}
@Override

Loading…
Cancel
Save