package de.srsoftware.web4rail.moving; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedSet; import java.util.TreeSet; import java.util.Vector; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.keawe.tools.translations.Translation; import de.srsoftware.tools.Tag; import de.srsoftware.web4rail.Application; import de.srsoftware.web4rail.BaseClass; import de.srsoftware.web4rail.PathFinder; import de.srsoftware.web4rail.Plan; import de.srsoftware.web4rail.Plan.Direction; import de.srsoftware.web4rail.Range; import de.srsoftware.web4rail.Route; import de.srsoftware.web4rail.Window; import de.srsoftware.web4rail.actions.Action.Context; import de.srsoftware.web4rail.tags.Button; import de.srsoftware.web4rail.tags.Checkbox; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Form; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Label; import de.srsoftware.web4rail.tags.Select; import de.srsoftware.web4rail.tiles.Block; import de.srsoftware.web4rail.tiles.Tile; public class Train extends BaseClass implements Comparable { private static final Logger LOG = LoggerFactory.getLogger(Train.class); private static final String CAR_ID = "carId"; public static final String LOCO_ID = "locoId"; private static final String TRACE = "trace"; private static final HashMap trains = new HashMap<>(); public static final String ID = "id"; public int id; private static final String NAME = "name"; private String name = null; private static final String ROUTE = "route"; public Route route; private static final String DIRECTION = "direction"; private Direction direction; private static final String PUSH_PULL = "pushPull"; public boolean pushPull = false; private static final String CARS = "cars"; private Vector cars = new Vector(); private static final String LOCOS = "locomotives"; private Vector locos = new Vector(); private static final String TAGS = "tags"; private static final String DESTINATION = "destination"; private HashSet tags = new HashSet(); private Block currentBlock,destination = null; LinkedList trace = new LinkedList(); private class Autopilot extends Thread{ boolean stop = false; int waitTime = 100; @Override public void run() { try { stop = false; while (true) { if (isNull(route)) { LOG.debug("Waiting {} secs...",waitTime/1000d); Thread.sleep(waitTime); if (waitTime > 100) waitTime /=2; if (stop) return; Train.this.start(); } Thread.sleep(250); } } catch (Exception e) { e.printStackTrace(); } } } public int speed = 0; private Autopilot autopilot = null; private Plan plan; public Train(Locomotive loco) { this(loco,null); } public Train(Locomotive loco, Integer id) { if (isNull(id)) id = Application.createId(); this.id = id; add(loco); trains.put(id, this); } public static Object action(HashMap params, Plan plan) throws IOException { String action = params.get(ACTION); if (isNull(action)) return t("No action passed to Train.action!"); if (!params.containsKey(Train.ID)) { switch (action) { case ACTION_PROPS: return manager(); case ACTION_ADD: return create(params,plan); } return t("No train id passed!"); } int id = Integer.parseInt(params.get(Train.ID)); Train train = trains.get(id); if (isNull(train)) return(t("No train with id {}!",id)); switch (action) { case ACTION_ADD: return train.addCar(params); case ACTION_AUTO: return train.automatic(); case ACTION_DROP: return train.dropCar(params); case ACTION_MOVE: return train.setDestination(params); case ACTION_PROPS: return train.props(); case ACTION_QUIT: return train.quitAutopilot(); case ACTION_START: return train.start(); case ACTION_STOP: return train.stopNow(); case ACTION_TURN: return train.turn(); case ACTION_UPDATE: return train.update(params); } return t("Unknown action: {}",params.get(ACTION)); } public void addToTrace(Vector newTiles) { boolean active = trace.isEmpty(); for (Tile tile : newTiles) { if (active) { trace.addFirst(tile); } else { Tile dummy = trace.getFirst(); if (dummy == tile) active = true; } } showTrace(); } @Override public int compareTo(Train o) { return name().compareTo(o.toString()); } public String directedName() { String result = name(); if (isNull(direction)) return result; switch (direction) { case NORTH: case WEST: return '←'+result; case SOUTH: case EAST: return result+'→'; } return result; } private Object addCar(HashMap params) { LOG.debug("addCar({})",params); if (!params.containsKey(CAR_ID)) return t("No car id passed to Train.addCar!"); Car car = Car.get(params.get(CAR_ID)); if (isNull(car)) return t("No car with id \"{}\" known!",params.get(CAR_ID)); if (car instanceof Locomotive) { locos.add((Locomotive) car); } else cars.add(car); return this; } public void add(Car car) { if (isNull(car)) return; if (car instanceof Locomotive) { locos.add((Locomotive) car); } else cars.add(car); car.train(this); } private String automatic() { if (isNull(autopilot)) { autopilot = new Autopilot(); autopilot.start(); } return t("{} now in auto-mode",this); } private Tag carList() { Tag locoProp = new Tag("li").content(t("Cars:")); Tag locoList = new Tag("ul").clazz("carlist").content(""); for (Car car : this.cars) { Tag li = new Tag("li"); car.link("span").addTo(li).content(NBSP); Map params = Map.of(REALM,REALM_TRAIN,ID,id,ACTION,ACTION_DROP,CAR_ID,car.id()); new Button("delete",params).addTo(li); li.addTo(locoList); } Tag addCarForm = new Form().content(t("add car:")+" "); new Input(REALM, REALM_TRAIN).hideIn(addCarForm); new Input(ACTION, ACTION_ADD).hideIn(addCarForm); new Input(ID,id).hideIn(addCarForm); Select select = new Select(CAR_ID); for (Car car : Car.list()) { if (!this.cars.contains(car)) select.addOption(car.id(), car); } if (!select.children().isEmpty()) { select.addTo(addCarForm); new Button(t("add")).addTo(addCarForm); addCarForm.addTo(new Tag("li")).addTo(locoList); } return locoList.addTo(locoProp); } public List cars(){ return new Vector(cars); } private static Object create(HashMap params, Plan plan) { Locomotive loco = (Locomotive) Locomotive.get(params.get(Train.LOCO_ID)); if (isNull(loco)) return t("unknown locomotive: {}",params.get(ID)); Train train = new Train(loco).plan(plan); if (params.containsKey(NAME)) train.name(params.get(NAME)); return train; } public Direction direction() { return direction; } private Object dropCar(HashMap params) { String carId = params.get(CAR_ID); if (isSet(carId)) cars.remove(Car.get(carId)); String locoId = params.get(LOCO_ID); if (isSet(locoId)) locos.remove(Car.get(locoId)); return props(); } public void dropTrace() { while (!trace.isEmpty()) trace.removeFirst().set(null); } public static Train get(int id) { return trains.get(id); } public Train heading(Direction dir) { direction = dir; if (isSet(currentBlock)) plan.place(currentBlock); return this; } public Tile headPos() { return trace.getFirst(); } private JSONObject json() { JSONObject json = new JSONObject(); json.put(ID, id); json.put(PUSH_PULL, pushPull); if (isSet(currentBlock)) json.put(BLOCK, currentBlock.id()); if (isSet(name))json.put(NAME, name); if (isSet(route)) json.put(ROUTE, route.id()); if (isSet(direction)) json.put(DIRECTION, direction); Vector locoIds = new Vector(); for (Locomotive loco : locos) locoIds.add(loco.id()); json.put(LOCOS, locoIds); Vector carIds = new Vector(); for (Car car : cars) carIds.add(car.id()); json.put(CARS,carIds); Vector tileIds = new Vector(); for (Tile tile : trace) tileIds.add(tile.id()); json.put(TRACE, tileIds); if (!tags.isEmpty()) json.put(TAGS, tags); return json; } public int length() { int result = 0; for (Locomotive loco : locos) result += loco.length; for (Car car : cars) result += car.length; return result; } public Tag link(String tagClass) { return link(tagClass, Map.of(REALM, REALM_TRAIN,ID,id,ACTION,ACTION_PROPS),name()+NBSP); } public static TreeSet list() { return new TreeSet(trains.values()); } public static void loadAll(String filename, Plan plan) throws IOException { BufferedReader file = new BufferedReader(new FileReader(filename)); String line = file.readLine(); while (isSet(line)) { JSONObject json = new JSONObject(line); int id = json.getInt(ID); Train train = new Train(null,id); train.plan(plan).load(json); line = file.readLine(); } file.close(); } private Train load(JSONObject json) { pushPull = json.getBoolean(PUSH_PULL); if (json.has(DIRECTION)) direction = Direction.valueOf(json.getString(DIRECTION)); if (json.has(NAME)) name = json.getString(NAME); if (json.has(TAGS)) json.getJSONArray(TAGS ).forEach(elem -> { tags.add(elem.toString()); }); if (json.has(TRACE)) json.getJSONArray(TRACE).forEach(elem -> { trace.add(plan.get(elem.toString(), false).set(this)); }); if (json.has(BLOCK)) currentBlock = (Block) plan.get(json.getString(BLOCK), false).set(this); // do not move this up! during set, other fields will be referenced! for (Object id : json.getJSONArray(CARS)) add(Car.get(id)); for (Object id : json.getJSONArray(LOCOS)) add((Locomotive) Car.get(id)); return this; } private Tag locoList() { Tag locoProp = new Tag("li").content(t("Locomotives:")); Tag locoList = new Tag("ul").clazz("locolist"); for (Locomotive loco : this.locos) { Tag li = new Tag("li"); loco.link("span").addTo(li); Map props = Map.of(REALM,REALM_LOCO,ID,loco.id(),ACTION,ACTION_TURN); new Button(t("turn within train"),props).addTo(li).addTo(locoList); Map params = Map.of(REALM,REALM_TRAIN,ID,id,ACTION,ACTION_DROP,LOCO_ID,loco.id()); new Button("delete",params).addTo(li); } Tag addLocoForm = new Form().content(t("add locomotive:")+" "); new Input(REALM, REALM_TRAIN).hideIn(addLocoForm); new Input(ACTION, ACTION_ADD).hideIn(addLocoForm); new Input(ID,id).hideIn(addLocoForm); Select select = new Select(CAR_ID); for (Car loco : Locomotive.list()) { if (!this.locos.contains(loco)) select.addOption(loco.id(), loco); } if (!select.children().isEmpty()) { select.addTo(addLocoForm); new Button(t("add")).addTo(addLocoForm); addLocoForm.addTo(new Tag("li")).addTo(locoList); } return locoList.addTo(locoProp); } public List locos(){ return new Vector(locos); } public static Object manager() { Window win = new Window("train-manager", t("Train manager")); new Tag("h4").content(t("known trains")).addTo(win); Tag list = new Tag("ul"); for (Train train : trains.values()) { train.link("li").addTo(list); } list.addTo(win); Form form = new Form(); new Input(ACTION, ACTION_ADD).hideIn(form); new Input(REALM,REALM_TRAIN).hideIn(form); Fieldset fieldset = new Fieldset(t("add new train")); new Input(Train.NAME, t("new train")).addTo(new Label(t("Name:")+NBSP)).addTo(fieldset); Select select = new Select(LOCO_ID); for (Car loco : Locomotive.list()) select.addOption(loco.id(),loco.name()); select.addTo(new Label(t("Locomotive:")+NBSP)).addTo(fieldset); new Button(t("Apply")).addTo(fieldset); fieldset.addTo(form).addTo(win); return win; } public String name() { return (isSet(name) ? name : locos.firstElement().name()); } private Train name(String newName) { this.name = newName; return this; } private Train plan(Plan plan) { this.plan = plan; return this; } public Tag props() { Window window = new Window("train-properties",t("Properties of {}",this)); Fieldset fieldset = new Fieldset(t("editable train properties")); Form form = new Form(); new Input(ACTION,ACTION_UPDATE).hideIn(form); new Input(REALM,REALM_TRAIN).hideIn(form); new Input(ID,id).hideIn(form); new Input(NAME,name).addTo(form); new Checkbox(PUSH_PULL, t("Push-pull train"), pushPull).addTo(form); new Input(TAGS,String.join(", ", tags)).addTo(new Label(t("Tags")+NBSP)).addTo(form); new Button(t("Apply")).addTo(form).addTo(fieldset); HashMap props = new HashMap(Map.of(REALM,REALM_TRAIN,ID,id)); props.put(ACTION, ACTION_TURN); new Button(t("Turn"), props).addTo(fieldset).addTo(window); fieldset = new Fieldset(t("other train properties")); Tag propList = new Tag("ul").clazz("proplist"); locoList().addTo(propList); carList().addTo(propList); new Tag("li").content(t("length: {}",length())).addTo(propList); if (!trace.isEmpty()) { Tag li = new Tag("li").content(t("Occupied area:")); Tag ul = new Tag("ul"); for (Tile tile : trace) new Tag("li").content(tile.toString()).addTo(ul); ul.addTo(li).addTo(propList); } Tag dest = new Tag("li").content(t("Destination: ")); if (isNull(destination)) { new Button(t("Select from plan"),"return selectDest("+id+");").addTo(dest); } else { link("span",Map.of(REALM,REALM_PLAN,ID,destination.id(),ACTION,ACTION_CLICK),destination.toString()).addTo(dest); } dest.addTo(propList); if (isSet(currentBlock)) { link("li",Map.of(REALM,REALM_PLAN,ID,currentBlock.id(),ACTION,ACTION_CLICK),t("Current location: {}",currentBlock)).addTo(propList); Tag actions = new Tag("li").clazz().content(t("Actions:")+NBSP); props.put(ACTION, ACTION_START); new Button(t("start"),props).addTo(actions); if (isNull(autopilot)) { props.put(ACTION, ACTION_AUTO); new Button(t("auto"),props).addTo(actions); } else { props.put(ACTION, ACTION_QUIT); new Button(t("quit autopilot"),props).addTo(actions); } actions.addTo(propList); } if (isSet(route)) { link("li", Map.of(REALM,REALM_ROUTE,ID,route.id(),ACTION,ACTION_PROPS), route).addTo(propList); } if (isSet(direction)) new Tag("li").content(t("Direction: heading {}",direction)).addTo(propList); Tag tagList = new Tag("ul"); for (String tag : tags()) new Tag("li").content(tag).addTo(tagList); tagList.addTo(new Tag("li").content(t("Tags"))).addTo(propList); propList.addTo(fieldset).addTo(window); return window; } public Object quitAutopilot() { if (isSet(autopilot)) { autopilot.stop = true; autopilot = null; return t("{} stopping at next block.",this); } else return t("autopilot not active."); } private void reverseTrace() { // TODO Auto-generated method stub } public static void saveAll(String filename) throws IOException { BufferedWriter file = new BufferedWriter(new FileWriter(filename)); for (Entry entry:trains.entrySet()) { Train train = entry.getValue(); file.write(train.json()+"\n"); } file.close(); } public static Select selector(Train preselected,Collection exclude) { if (isNull(exclude)) exclude = new Vector(); Select select = new Select(Train.class.getSimpleName()); new Tag("option").attr("value","0").content(t("unset")).addTo(select); for (Train train : Train.list()) { if (exclude.contains(train)) continue; Tag opt = select.addOption(train.id, train); if (train == preselected) opt.attr("selected", "selected"); } return select; } public void set(Block newBlock) { currentBlock = newBlock; if (isSet(currentBlock)) currentBlock.set(this); } private String setDestination(HashMap params) { String dest = params.get(DESTINATION); if (isNull(dest)) return t("No destination supplied!"); Tile tile = plan.get(dest, true); if (isNull(tile)) return t("Tile {} not known!",dest); if (tile instanceof Block) { destination = (Block) tile; return t("{} now heading for {}",this,destination); } return t("{} is not a block!",tile); } public void setSpeed(int v) { for (Locomotive loco : locos) loco.setSpeed(v); this.speed = v; } public void showTrace() { int remainingLength = length(); if (remainingLength<1) remainingLength=1; for (int i=0; i0) { remainingLength-=tile.length(); tile.set(this); } else { tile.set(null); trace.remove(i); i--; // do not move to next index: remove shifted the next index towards us } } } public String start() throws IOException { if (isNull(currentBlock)) return t("{} not in a block",this); if (isSet(route)) route.reset(); // reset route previously chosen Context context = new Context(this); route = PathFinder.chooseRoute(context); if (isNull(route)) return t("No free routes from {}",currentBlock); if (!route.lock()) return t("Was not able to lock {}",route); if (direction != route.startDirection) turn(); String error = null; if (!route.setTurnouts()) error = t("Was not able to set all turnouts!"); if (isNull(error) && !route.fireSetupActions(context)) error = t("Was not able to fire all setup actions of route!"); if (isNull(error) && !route.setSignals(null)) error = t("Was not able to set all signals!"); if (isNull(error) && !route.train(this)) error = t("Was not able to assign {} to {}!",this,route); if (isSet(error)) { route.reset(); return error; } setSpeed(128); return t("Started {}",this); } public Object stopNow() { quitAutopilot(); setSpeed(0); if (isSet(route)) route.reset(); return t("Stopped {}.",this); } private static String t(String message, Object...fills) { return Translation.get(Application.class, message, fills); } public SortedSet tags() { TreeSet list = new TreeSet(tags); for (Locomotive loco : locos) list.addAll(loco.tags()); for (Car car:cars) list.addAll(car.tags()); return list; } @Override public String toString() { return isSet(name) ? name : locos.firstElement().name(); } public Object turn() { LOG.debug("train.turn()"); if (isSet(direction)) { direction = direction.inverse(); for (Locomotive loco : locos) loco.turn(); reverseTrace(); if (isSet(currentBlock)) plan.place(currentBlock); } return t("{} turned.",this); } public Train update(HashMap params) { LOG.debug("update({})",params); pushPull = params.containsKey(PUSH_PULL) && params.get(PUSH_PULL).equals("on"); if (params.containsKey(NAME)) name = params.get(NAME); if (params.containsKey(TAGS)) { String[] parts = params.get(TAGS).replace(",", " ").split(" "); tags.clear(); for (String tag : parts) { tag = tag.trim(); if (!tag.isEmpty()) tags.add(tag); } } return this; } public void removeFromTrace(Tile tile) { trace.remove(tile); } public void setWaitTime(Range waitTime) { if (autopilot != null) autopilot.waitTime = waitTime.random(); } public Block currentBlock() { return currentBlock; } public Block destination() { return destination; } }