diff --git a/core/src/main/java/ru/trader/analysis/RouteSearcher.java b/core/src/main/java/ru/trader/analysis/RouteSearcher.java index 5101762..e59c1ea 100644 --- a/core/src/main/java/ru/trader/analysis/RouteSearcher.java +++ b/core/src/main/java/ru/trader/analysis/RouteSearcher.java @@ -2,22 +2,20 @@ package ru.trader.analysis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ru.trader.analysis.graph.ConnectibleEdge; import ru.trader.analysis.graph.Crawler; import ru.trader.analysis.graph.Edge; import ru.trader.core.Order; -import ru.trader.core.TransitVendor; import ru.trader.core.Vendor; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; +import java.util.*; public class RouteSearcher { private final static Logger LOG = LoggerFactory.getLogger(RouteSearcher.class); - private final VendorsGraph vGraph; + private final Scorer scorer; public RouteSearcher(Scorer scorer) { - vGraph = new VendorsGraph(scorer); + this.scorer = scorer; } public List getRoutes(Vendor from, Vendor to, Collection vendors){ @@ -30,10 +28,13 @@ public class RouteSearcher { private List search(Vendor source, Vendor target, Collection vendors){ LOG.trace("Start search route to {} from {}", source, target); + VendorsGraph vGraph = new VendorsGraph(scorer); + LOG.trace("Build vendors graph"); vGraph.build(source, vendors); - + LOG.trace("Graph is builds"); RouteCollector collector = new RouteCollector(); Crawler crawler = vGraph.crawler(collector::add); + crawler.setMaxSize(scorer.getProfile().getLands()); if (target == null){ int count = vGraph.getProfile().getRoutesCount() / vendors.size(); for (Vendor vendor : vendors) { @@ -49,10 +50,11 @@ public class RouteSearcher { private class RouteCollector { private List routes = new ArrayList<>(); - public void add(List> edges){ + public boolean add(List> edges){ Route route = toRoute(edges); - route.setBalance(vGraph.getProfile().getBalance()); + route.setBalance(scorer.getProfile().getBalance()); routes.add(route); + return true; } public List get() { @@ -62,34 +64,31 @@ public class RouteSearcher { private Route toRoute(List> edges){ List entries = new ArrayList<>(edges.size()+1); Vendor buyer = null; - VendorsGraph.VendorsEdge edge = null; - for (int i = 0; i < edges.size(); i++) { - edge = (VendorsGraph.VendorsEdge) edges.get(i); - Vendor vendor = edge.getSource().getEntry(); - RouteEntry entry = new RouteEntry(vendor, edge.isRefill(), edge.getFuel(), edge.getWeight()); - if (buyer != null && vendor.equals(buyer)){ - entry.setLand(true); - } - List orders = edge.getOrders(); - if (!orders.isEmpty()){ - buyer = orders.get(0).getBuyer(); - if (vendor instanceof TransitVendor){ - Vendor seller = orders.get(0).getSell().getVendor(); - for (int j = i-1; j >= 0; j--) { - RouteEntry sEntry = entries.get(j); - if (sEntry.is(seller)){ - sEntry.addAll(orders); - break; - } - } - } else { - entry.addAll(orders); + VendorsGraph.VendorsEdge vEdge = null; + for (Edge e : edges) { + vEdge = (VendorsGraph.VendorsEdge) e; + List> transitEdges = vEdge.getPath().getEntries(); + for (int k = 0; k < transitEdges.size(); k++) { + ConnectibleEdge edge = transitEdges.get(k); + Vendor vendor = edge.getSource().getEntry(); + RouteEntry entry = new RouteEntry(vendor, edge.isRefill(), edge.getFuelCost(), 0); + if (buyer != null && vendor.equals(buyer)) { + entry.setLand(true); + buyer = null; } + if (k == 0) { + entry.setScore(vEdge.getWeight()); + List orders = vEdge.getOrders(); + if (!orders.isEmpty()) { + buyer = orders.get(0).getBuyer(); + entry.addAll(orders); + } + } + entries.add(entry); } - entries.add(entry); } - if (edge != null) { - RouteEntry entry = new RouteEntry(edge.getTarget().getEntry(), false, 0, 0); + if (vEdge != null) { + RouteEntry entry = new RouteEntry(vEdge.getTarget().getEntry(), false, 0, 0); if (buyer != null) entry.setLand(true); entries.add(entry); } diff --git a/core/src/main/java/ru/trader/analysis/Scorer.java b/core/src/main/java/ru/trader/analysis/Scorer.java index 3f34900..e7d2b51 100644 --- a/core/src/main/java/ru/trader/analysis/Scorer.java +++ b/core/src/main/java/ru/trader/analysis/Scorer.java @@ -76,10 +76,6 @@ public class Scorer { return avgDistance; } - public double getFuel(double distance){ - return profile.getShip().getFuelCost(distance); - } - public double getScore(RouteEntry entry, int jumps) { int lands = entry.isLand() ? 1 : 0; return getScore(entry.getVendor(), entry.getProfit(), jumps, lands, entry.getFuel()); diff --git a/core/src/main/java/ru/trader/analysis/TransitPath.java b/core/src/main/java/ru/trader/analysis/TransitPath.java new file mode 100644 index 0000000..5aaf54e --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/TransitPath.java @@ -0,0 +1,112 @@ +package ru.trader.analysis; + + +import ru.trader.analysis.graph.ConnectibleEdge; +import ru.trader.analysis.graph.ConnectibleGraph; +import ru.trader.analysis.graph.Path; +import ru.trader.core.Vendor; + +import java.util.ArrayList; +import java.util.List; + +public class TransitPath { + private final List> entries; + private double fuelCost; + private double remain; + private int refillCount; + + public TransitPath(Path path, double fuel) { + List.BuildEdge> edges = path.getEdges(); + entries = new ArrayList<>(edges.size()); + createEdges(edges, fuel); + } + + private void createEdges(List.BuildEdge> edges, double fuel) { + fuelCost = 0; refillCount = 0; + for (int i = edges.size() - 1; i >= 0; i--) { + ConnectibleGraph.BuildEdge edge = edges.get(i); + ConnectibleEdge cEdge = new ConnectibleEdge<>(edge.getSource(), edge.getTarget()); + double fuelCost = edge.getFuelCost(fuel); + this.fuelCost += fuelCost; + cEdge.setFuelCost(fuelCost); + entries.add(cEdge); + if (fuel < 0 || fuel < edge.getMinFuel()){ + refillCount++; + fuel = refill(edges, entries.size()-1); + } else { + fuel -= fuelCost; + } + } + remain = fuel; + } + + private double refill(List.BuildEdge> edges, int startIndex){ + double max = -1; + for (int i = startIndex; i >= 0; i--) { + ConnectibleGraph.BuildEdge e = edges.get(edges.size()-1-i); + if (max != -1){ + max += e.getFuelCost(max + e.getMinFuel()); + } + Vendor source = e.getSource().getEntry(); + if (source.canRefill()){ + ConnectibleEdge ce = entries.get(i); + if (ce.isRefill()){ + throw new IllegalStateException("Is not exists path"); + } + double remain = max != -1 ? Math.min(max, e.getRefill()) : e.getRefill(); + double fuelCost = e.getFuelCost(remain); + this.fuelCost += fuelCost - ce.getFuelCost(); + ce.setFuelCost(fuelCost); + ce.setRefill(remain); + remain = updateFuelCost(edges, i+1, startIndex, remain-fuelCost); + if (remain < 0){ + continue; + } + return remain; + } + if (max == -1 || e.getMaxFuel() < max){ + max = e.getMaxFuel(); + } + } + throw new IllegalStateException("Is not exists path"); + } + + private double updateFuelCost(List.BuildEdge> edges, int startIndex, int endIndex, double fuel){ + for (int i = startIndex+1; i <= endIndex; i++) { + ConnectibleGraph.BuildEdge e = edges.get(edges.size()-1-i); + ConnectibleEdge ce = entries.get(i); + double fuelCost = e.getFuelCost(fuel); + this.fuelCost = fuelCost - ce.getFuelCost(); + ce.setFuelCost(fuelCost); + fuel -= fuelCost; + if (fuel < 0 || fuel < e.getMinFuel()){ + return -1; + } + } + return fuel; + } + + public List> getEntries() { + return entries; + } + + public double getFuelCost() { + return fuelCost; + } + + public double getRemain() { + return remain; + } + + public int getRefillCount() { + return refillCount; + } + + public boolean isRefill() { + return entries.get(0).isRefill(); + } + + public int size() { + return entries.size(); + } +} diff --git a/core/src/main/java/ru/trader/analysis/VendorVertex.java b/core/src/main/java/ru/trader/analysis/VendorVertex.java new file mode 100644 index 0000000..e98b331 --- /dev/null +++ b/core/src/main/java/ru/trader/analysis/VendorVertex.java @@ -0,0 +1,32 @@ +package ru.trader.analysis; + +import ru.trader.analysis.graph.Edge; +import ru.trader.analysis.graph.Path; +import ru.trader.analysis.graph.Vertex; +import ru.trader.core.Vendor; + +import java.util.Collection; +import java.util.Optional; + +public class VendorVertex extends Vertex { + + public VendorVertex(Vendor entry, int index) { + super(entry, index); + } + + @Override + public synchronized void connect(Edge edge) { + VendorsGraph.VendorsBuildEdge vEdge = (VendorsGraph.VendorsBuildEdge) edge; + Optional> old = getEdge(edge.getTarget()); + if (old.isPresent()){ + VendorsGraph.VendorsBuildEdge oEdge = (VendorsGraph.VendorsBuildEdge) old.get(); + if (oEdge.getPaths() == null) return; + Collection> paths = vEdge.getPaths(); + for (Path path : paths) { + oEdge.add(path); + } + } else { + super.connect(edge); + } + } +} diff --git a/core/src/main/java/ru/trader/analysis/VendorsGraph.java b/core/src/main/java/ru/trader/analysis/VendorsGraph.java index 6237fd4..7eb3a30 100644 --- a/core/src/main/java/ru/trader/analysis/VendorsGraph.java +++ b/core/src/main/java/ru/trader/analysis/VendorsGraph.java @@ -3,21 +3,21 @@ package ru.trader.analysis; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.trader.analysis.graph.*; -import ru.trader.core.Order; -import ru.trader.core.TransitVendor; -import ru.trader.core.Vendor; +import ru.trader.core.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; +import java.util.*; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.RecursiveAction; +import java.util.function.Function; +import java.util.stream.Collectors; public class VendorsGraph extends ConnectibleGraph { private final static Logger LOG = LoggerFactory.getLogger(VendorsGraph.class); + private final static int THRESHOLD = 8; private final Scorer scorer; + private final List deferredTasks = new ArrayList<>(); public VendorsGraph(Scorer scorer) { super(scorer.getProfile()); @@ -29,18 +29,118 @@ public class VendorsGraph extends ConnectibleGraph { this.scorer = scorer; } - public VendorsCrawler crawler(Consumer>> onFoundFunc){ + public VendorsCrawler crawler(Function>, Boolean> onFoundFunc){ return new VendorsCrawler(onFoundFunc); } + @Override + protected Vertex newInstance(Vendor entry, int index) { + return new VendorVertex(entry, index); + } + @Override protected GraphBuilder createGraphBuilder(Vertex vertex, Collection set, int deep, double limit) { return new VendorsGraphBuilder(vertex, set, deep, limit); } - protected class VendorsGraphBuilder extends ConnectibleGraphBuilder { + @Override + protected void onEnd() { + super.onEnd(); + runDeferredTasks(); + updateVertexes(); + } + + protected void holdTask(VendorsGraphBuilder task){ + synchronized (deferredTasks){ + deferredTasks.add(task); + } + } + + private void runDeferredTasks(){ + deferredTasks.sort((b1,b2) -> Integer.compare(b2.getDeep(), b1.getDeep())); + for (VendorsGraphBuilder task : deferredTasks) { + ForkJoinTask.invokeAll(task); + } + deferredTasks.clear(); + } + + private void updateVertexes(){ + vertexes.removeIf(v -> v.getEntry() instanceof TransitVendor); + updateLevels(root); + } + + private void updateLevels(Vertex vertex){ + vertex.getEdges().forEach(e -> { + Vertex target = e.getTarget(); + if (target.getLevel()+1 subTasks = new ArrayList<>(THRESHOLD); + private final VendorsGraphBuilder head; + private final BuildEdge edge; + private boolean isAdding; + protected VendorsGraphBuilder(Vertex vertex, Collection set, int deep, double limit) { super(vertex, set, deep, limit); + this.head = null; + this.edge = null; + } + + private VendorsGraphBuilder(VendorsGraphBuilder head, BuildEdge edge, Collection set, int deep, double limit) { + super(edge.getTarget(), set, deep, limit); + this.head = head; + this.edge = edge; + } + + public int getDeep(){ + return deep; + } + + @Override + protected void compute() { + LOG.trace("Build graph from {}, limit {}, deep {}", vertex, limit, deep); + if (isAdding){ + addAlreadyCheckedEdges(); + } else { + checkVertex(); + } + if (!subTasks.isEmpty()){ + joinSubTasks(); + } + LOG.trace("End build graph from {} on deep {}", vertex, deep); + } + + private void checkVertex(){ + Iterator iterator = set.iterator(); + while (iterator.hasNext()) { + if (callback.isCancel()) break; + Vendor entry = iterator.next(); + LOG.trace("Check {}", entry); + if (entry == vertex.getEntry()) continue; + double nextLimit = onConnect(entry); + if (nextLimit >= 0) { + LOG.trace("Connect {} to {}", entry, vertex); + Vertex next = getInstance(entry, 0, deep); + BuildEdge e; + if (entry instanceof TransitVendor){ + e = super.createEdge(next); + } else { + e = createEdge(next); + vertex.connect(e); + } + addSubTask(e, nextLimit); + } else { + LOG.trace("Vertex {} is far away", entry); + } + if (subTasks.size() == THRESHOLD || !iterator.hasNext()){ + joinSubTasks(); + } + } } @Override @@ -48,23 +148,241 @@ public class VendorsGraph extends ConnectibleGraph { double nextlimit = super.onConnect(buyer); Vendor seller = vertex.getEntry(); if (nextlimit > 0){ - if (buyer instanceof TransitVendor && seller.getPlace().equals(buyer.getPlace())) nextlimit = -1; - if (seller instanceof TransitVendor && seller.getPlace().equals(buyer.getPlace())) nextlimit = -1; + if (buyer instanceof TransitVendor && (deep == 0 || seller.getPlace().equals(buyer.getPlace()))){ + LOG.trace("Buyer is transit of seller or is end, skipping"); + nextlimit = -1; + } + if (seller instanceof TransitVendor && seller.getPlace().equals(buyer.getPlace())){ + LOG.trace("Seller is transit of buyer, skipping"); + nextlimit = -1; + } } return nextlimit; } @Override - protected ConnectibleEdge createEdge(Vertex target) { - return new VendorsEdge(vertex, target, refill, fuelCost); + protected VendorsBuildEdge createEdge(Vertex target) { + BuildEdge cEdge = super.createEdge(target); + if (vertex.getEntry() instanceof TransitVendor){ + addEdgesToHead(cEdge); + } + return new VendorsBuildEdge(cEdge); + } + + private void addEdgesToHead(BuildEdge lastEdge){ + Vertex target = lastEdge.getTarget(); + assert vertex.getEntry() instanceof TransitVendor && !(target.getEntry() instanceof TransitVendor); + VendorsGraphBuilder h = this; + Path path = new Path<>(Collections.singleton(lastEdge)); + while (h != null){ + BuildEdge cEdge = h.edge; + Vertex source = cEdge.getSource(); + if (source.equals(vertex)){ + LOG.trace("Found loop, break"); + break; + } + path = path.add(cEdge); + if (!source.equals(target)){ + addEdge(source, target, path); + } + if (!(source.getEntry() instanceof TransitVendor)){ + break; + } + h = h.head; + } + } + + private void addEdge(Vertex source, Vertex target, Path path){ + LOG.trace("Is not transit, add edge to {}", source); + VendorsBuildEdge vEdge = new VendorsBuildEdge(source, target, path); + source.connect(vEdge); + } + + private void addAlreadyCheckedEdges(){ + LOG.trace("Adding already checked vertex"); + vertex.getEdges().parallelStream().forEach(aEdge -> { + VendorsBuildEdge e = (VendorsBuildEdge) aEdge; + if (callback.isCancel()) return; + Vendor entry = e.getTarget().getEntry(); + LOG.trace("Check {}", entry); + if (limit >= e.getMinFuel() && limit <= e.getMaxFuel()) { + LOG.trace("Connect {} to {}", entry, vertex); + if (vertex.getEntry() instanceof TransitVendor && !(entry instanceof TransitVendor)) { + addCheckedEdgesToHead(e); + } + } + }); + } + + private void addCheckedEdgesToHead(VendorsBuildEdge lastEdge){ + Vertex target = lastEdge.getTarget(); + assert vertex.getEntry() instanceof TransitVendor && !(target.getEntry() instanceof TransitVendor); + List> paths = lastEdge.paths; + int i = 1; + Path path = paths != null ? paths.get(0) : new Path<>(Collections.singleton((BuildEdge)lastEdge)); + while (path != null){ + VendorsGraphBuilder h = this; + while (h != null){ + if (h.limit >= path.getMinFuel() && h.limit <= path.getMaxFuel()){ + BuildEdge cEdge = h.edge; + Vertex source = cEdge.getSource(); + if (source.equals(vertex)){ + LOG.trace("Found loop, break"); + break; + } + path = path.add(cEdge); + if (!source.equals(target)){ + addEdge(source, target, path); + } + if (!(source.getEntry() instanceof TransitVendor)){ + break; + } + h = h.head; + } else { + break; + } + } + if (paths == null || i >= paths.size()){ + path = null; + } else { + path = paths.get(i++); + } + } + } + + private void addSubTask(BuildEdge e, double nextLimit){ + Vertex next = e.getTarget(); + // If level > deep when vertex already added on upper deep + if (next.getLevel() < deep || next.getEntry() instanceof TransitVendor) { + boolean adding = next.getLevel() >= deep; + if (!adding){ + next.setLevel(vertex.getLevel() - 1); + } + if (deep > 0 || adding) { + //Recursive build + VendorsGraphBuilder task = new VendorsGraphBuilder(this, e, set, deep - 1, nextLimit); + task.isAdding = adding; + if (adding){ + holdTask(task); + } else { + task.fork(); + subTasks.add(task); + } + } + } else { + LOG.trace("Vertex {} already check", next); + } + } + + private void joinSubTasks(){ + for (RecursiveAction subTask : subTasks) { + if (callback.isCancel()){ + subTask.cancel(true); + } else { + subTask.join(); + } + } + subTasks.clear(); + } + + } + + public class VendorsBuildEdge extends BuildEdge { + private List> paths = new ArrayList<>(); + private List orders; + + protected VendorsBuildEdge(Vertex source, Vertex target, Path path) { + super(source, target); + if (path == null) throw new IllegalArgumentException("Path must be no-null"); + paths.add(path); + update(path); + } + + protected VendorsBuildEdge(BuildEdge edge) { + super(edge.getSource(), edge.getTarget()); + Path path = new Path<>(Collections.singleton(edge)); + paths.add(path); + update(path); + } + + private void update(Path path){ + setFuel(path.getMinFuel(), path.getMaxFuel()); + } + + protected void setOrders(List orders){ + this.orders = orders; + } + + public List getOrders(){ + if (orders == null){ + Vendor seller = source.getEntry(); + Vendor buyer = target.getEntry(); + orders = MarketUtils.getOrders(seller, buyer); + } + return orders; + } + + public double getProfit(){ + return getOrders().stream().mapToDouble(Order::getProfit).sum(); + } + + public Collection> getPaths() { + return paths; + } + + private Path getPath(double fuel){ + Path res = null; + for (Path p : paths) { + if (fuel >= p.getMinFuel() && fuel <= p.getMaxFuel() || getSource().getEntry().canRefill()) { + if (getProfile().getPathPriority().equals(Profile.PATH_PRIORITY.FAST)) { + if (res == null || p.getSize() < res.getSize() && p.getRefillCount(fuel) <= res.getRefillCount(fuel)) { + res = p; + } + } else { + if (res == null || p.getFuelCost() < res.getFuelCost()) { + res = p; + } + } + + } + } + return res; + } + + public void add(Path path) { + for (Iterator> iterator = paths.iterator(); iterator.hasNext(); ) { + Path p = iterator.next(); + if (p.getSize() >= path.getSize() && p.getMinFuel() >= path.getMinFuel() && p.getRefillCount() >= path.getRefillCount()) { + iterator.remove(); + } else { + if (path.getSize() >= p.getSize() && path.getMinFuel() >= p.getMinFuel() && path.getRefillCount() >= p.getRefillCount()) { + return; + } + } + } + paths.add(path); + if (getMinFuel() > path.getMinFuel()) { + update(path); + } + } + + public VendorsEdge getInstance(double fuel, double balance){ + Path path = getPath(fuel); + if (path == null) return null; + VendorsEdge res = new VendorsEdge(source, target, new TransitPath(path,fuel)); + res.setOrders(MarketUtils.getStack(getOrders(), balance, getShip().getCargo())); + return res; } } public class VendorsEdge extends ConnectibleEdge { + private TransitPath path; private List orders; - protected VendorsEdge(Vertex source, Vertex target, boolean refill, double fuel) { - super(source, target, refill, fuel); + protected VendorsEdge(Vertex source, Vertex target, TransitPath path) { + super(source, target); + if (path == null) throw new IllegalArgumentException("Path must be no-null"); + this.path = path; } protected void setOrders(List orders){ @@ -84,100 +402,118 @@ public class VendorsGraph extends ConnectibleGraph { return orders; } + public double getRemain() { + return path.getRemain(); + } + + public boolean isRefill() { + return path.isRefill(); + } + + public TransitPath getPath() { + return path; + } + @Override protected double computeWeight() { int jumps = source.getEntry().getPlace().equals(target.getEntry().getPlace())? 0 : 1; - int lands = !(target.getEntry() instanceof TransitVendor) ? 1 : 0; - boolean transit = lands == 0 && source.getEntry() instanceof TransitVendor || target.getEntry() instanceof TransitVendor; + int lands = 1; double fuel = fuelCost; + if (path != null){ + jumps = path.size(); fuel = getFuelCost(); + lands += path.getRefillCount(); + } double profit = getProfit(); - double score = transit ? scorer.getTransitScore(fuel) : - scorer.getScore(target.getEntry(), profit, jumps, lands, fuel); + double score = scorer.getScore(target.getEntry(), profit, jumps, lands, fuel); score = scorer.getMaxScore() - score; - if (score < 0) - score = 0; + if (score < 0) score = 0; return score; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof VendorsEdge)) return false; + if (!super.equals(o)) return false; + VendorsEdge edge = (VendorsEdge) o; + return !(path != null ? !path.equals(edge.path) : edge.path != null); + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (path != null ? path.hashCode() : 0); + return result; + } + } - public class VendorsCrawler extends CCrawler { - protected VendorsCrawler(Consumer>> onFoundFunc) { + public class VendorsCrawler extends Crawler { + private double startFuel; + private double startBalance; + + protected VendorsCrawler(Function>, Boolean> onFoundFunc) { super(VendorsGraph.this, onFoundFunc); + startFuel = getShip().getTank(); + startBalance = getProfile().getBalance(); + } + + protected VendorsCrawler(Function, Boolean> isFoundFunc, Function>, Boolean> onFoundFunc) { + super(VendorsGraph.this, isFoundFunc, onFoundFunc); + startFuel = getShip().getTank(); + startBalance = getProfile().getBalance(); + } + + public void setStartFuel(double startFuel) { + this.startFuel = startFuel; + } + + public void setStartBalance(double startBalance) { + this.startBalance = startBalance; } @Override protected VendorsTraversalEntry start(Vertex vertex) { - double balance = getProfile().getBalance(); - return new VendorsTraversalEntry(super.start(vertex), balance); + return new VendorsTraversalEntry(super.start(vertex), startFuel, startBalance); } @Override - protected VendorsTraversalEntry travers(final CostTraversalEntry entry, final Edge edge, final Vendor target) { + protected VendorsTraversalEntry travers(final CostTraversalEntry entry, final Edge edge) { + VendorsTraversalEntry vEntry = (VendorsTraversalEntry)entry; VendorsEdge vEdge = (VendorsEdge) edge; - CCostTraversalEntry ce = super.travers(entry, edge, target); - return new VendorsTraversalEntry((VendorsTraversalEntry) entry, edge, ce.getFuel(), ((VendorsTraversalEntry)entry).balance + vEdge.getProfit()); + return new VendorsTraversalEntry(vEntry, vEdge); } - protected class VendorsTraversalEntry extends CCostTraversalEntry { + protected class VendorsTraversalEntry extends CostTraversalEntry { + private final double fuel; private final double balance; - protected VendorsTraversalEntry(CCostTraversalEntry entry, double balance) { - super(entry.getTarget(), entry.getFuel()); + protected VendorsTraversalEntry(CostTraversalEntry entry, double fuel, double balance) { + super(entry.getTarget()); + this.fuel = fuel; this.balance = balance; } - protected VendorsTraversalEntry(VendorsTraversalEntry head, Edge edge, double fuel, double balance) { - super(head, edge, fuel); - this.balance = balance; + protected VendorsTraversalEntry(VendorsTraversalEntry head, VendorsEdge edge) { + super(head, edge); + this.balance = head.balance + edge.getProfit(); + this.fuel = edge.getRemain(); } @Override - protected boolean check(Edge e) { - boolean good = super.check(e); - // remove transit cicles - if (good && e.getSource().getEntry() instanceof TransitVendor && !(e.getTarget().getEntry() instanceof TransitVendor)){ - Optional seller = getSeller(); - good = seller.isPresent() && !e.getTarget().isEntry(seller.get()); - } - return good; + public List> collect(Collection> src) { + return src.stream().filter(this::check).map(this::wrap).filter(e -> e != null).collect(Collectors.toList()); } - @Override - protected VendorsEdge wrap(Edge edge) { - ConnectibleEdge cEdge = super.wrap(edge); - Vendor buyer = edge.getTarget().getEntry(); - List orders = new ArrayList<>(); - orders.addAll(((VendorsEdge) edge).getOrders()); - if (edge.getSource().getEntry() instanceof TransitVendor && !(buyer instanceof TransitVendor)){ - LOG.trace("{} is transit, search seller", edge.getSource().getEntry()); - Optional seller = getSeller(); - if (seller.isPresent()){ - orders = MarketUtils.getOrders(seller.get(), buyer); - } - } - orders = MarketUtils.getStack(orders, balance, getShip().getCargo()); - VendorsEdge res = new VendorsEdge(edge.getSource(), edge.getTarget(), cEdge.isRefill(), cEdge.getFuel()); - res.setOrders(orders); - return res; + protected boolean check(Edge e){ + VendorsBuildEdge edge = (VendorsBuildEdge) e; + return fuel <= edge.getMaxFuel() && (fuel >= edge.getMinFuel() || edge.getSource().getEntry().canRefill()) && (edge.getProfit() > 0 || isFound(edge)); } - private Optional getSeller(){ - Vendor res = null; - Edge e = getEdge(); - Vendor seller = e.getSource().getEntry(); - if (!(seller instanceof TransitVendor)){ - res = seller; - } else { - for (int i = head.size() - 1; i >= 0; i--) { - e = head.getEdge(); - seller = e.getSource().getEntry(); - if (!(seller instanceof TransitVendor)){ - res = seller; - break; - } - } - } - return Optional.ofNullable(res); + protected VendorsEdge wrap(Edge e) { + VendorsBuildEdge edge = (VendorsBuildEdge) e; + return edge.getInstance(fuel, balance); } + } diff --git a/core/src/main/java/ru/trader/analysis/graph/AbstractGraph.java b/core/src/main/java/ru/trader/analysis/graph/AbstractGraph.java index 0363ee2..6b3d457 100644 --- a/core/src/main/java/ru/trader/analysis/graph/AbstractGraph.java +++ b/core/src/main/java/ru/trader/analysis/graph/AbstractGraph.java @@ -18,7 +18,6 @@ public abstract class AbstractGraph implements Graph { protected Vertex root; protected final List> vertexes; protected final GraphCallBack callback; - protected int minJumps; protected AbstractGraph() { @@ -37,17 +36,26 @@ public abstract class AbstractGraph implements Graph { minJumps = 1; root = getInstance(start, maxDeep, maxDeep); POOL.invoke(createGraphBuilder(root, set, maxDeep - 1, limit)); + onEnd(); callback.endBuild(); } - private Vertex getInstance(T entry, int level, int deep){ + protected void onEnd(){ + + } + + protected Vertex newInstance(T entry, int index){ + return new Vertex<>(entry, index); + } + + protected Vertex getInstance(T entry, int level, int deep){ Vertex vertex = getVertex(entry).orElse(null); if (vertex == null) { synchronized (vertexes){ vertex = getVertex(entry).orElse(null); if (vertex == null){ LOG.trace("Is new vertex"); - vertex = new Vertex<>(entry, vertexes.size()); + vertex = newInstance(entry, vertexes.size()); vertex.setLevel(level); vertexes.add(vertex); int jumps = root != null ? root.getLevel() - deep : 0; diff --git a/core/src/main/java/ru/trader/analysis/graph/CCrawler.java b/core/src/main/java/ru/trader/analysis/graph/CCrawler.java index 2884070..9c2aa2e 100644 --- a/core/src/main/java/ru/trader/analysis/graph/CCrawler.java +++ b/core/src/main/java/ru/trader/analysis/graph/CCrawler.java @@ -15,12 +15,12 @@ public class CCrawler> extends Crawler { private final static Logger LOG = LoggerFactory.getLogger(CCrawler.class); private double startFuel; - public CCrawler(Graph graph, Function>, Boolean> onFoundFunc) { + public CCrawler(ConnectibleGraph graph, Function>, Boolean> onFoundFunc) { super(graph, onFoundFunc); startFuel = getShip().getTank(); } - public CCrawler(Graph graph, Function, Boolean> isFoundFunc, Function>, Boolean> onFoundFunc) { + public CCrawler(ConnectibleGraph graph, Function, Boolean> isFoundFunc, Function>, Boolean> onFoundFunc) { super(graph, isFoundFunc, onFoundFunc); startFuel = getShip().getTank(); } @@ -78,22 +78,23 @@ public class CCrawler> extends Crawler { @Override public List> collect(Collection> src) { - return src.stream().filter(this::check).map(this::wrap).collect(Collectors.toList()); + return src.stream().filter(this::check).map(this::wrap).filter(e -> e != null).collect(Collectors.toList()); } protected boolean check(Edge e){ - ConnectibleEdge edge = (ConnectibleEdge) e; - return edge.getFuelCost() <= fuel || edge.getSource().getEntry().canRefill(); + ConnectibleGraph.BuildEdge edge = (ConnectibleGraph.BuildEdge) e; + return fuel <= edge.getMaxFuel() && (fuel >= edge.getMinFuel() || edge.getSource().getEntry().canRefill()); } - protected ConnectibleEdge wrap(Edge edge){ + protected ConnectibleEdge wrap(Edge e){ + ConnectibleGraph.BuildEdge edge = (ConnectibleGraph.BuildEdge) e; T source = edge.source.getEntry(); - double distance = source.getDistance(edge.target.getEntry()); - double fuelCost = getShip().getFuelCost(fuel, distance); - double nextLimit = getProfile().withRefill() ? fuel - fuelCost : getShip().getTank(); - double refill = nextLimit < 0 && source.canRefill() ? getShip().getRoundMaxFuel(distance) : 0; + double fuelCost = edge.getFuelCost(fuel); + double nextLimit = fuel - fuelCost; + if (nextLimit < 0 && !source.canRefill()) return null; + double refill = nextLimit < 0 ? edge.getRefill() : 0; if (refill > 0) { - fuelCost = getShip().getFuelCost(refill, distance); + fuelCost = edge.getFuelCost(refill); } ConnectibleEdge cEdge = new ConnectibleEdge<>(edge.getSource(), edge.getTarget()); cEdge.setRefill(refill); diff --git a/core/src/main/java/ru/trader/analysis/graph/ConnectibleGraph.java b/core/src/main/java/ru/trader/analysis/graph/ConnectibleGraph.java index ef76b7b..9e44570 100644 --- a/core/src/main/java/ru/trader/analysis/graph/ConnectibleGraph.java +++ b/core/src/main/java/ru/trader/analysis/graph/ConnectibleGraph.java @@ -8,7 +8,6 @@ import ru.trader.core.Ship; import ru.trader.graph.Connectable; import java.util.Collection; -import java.util.function.Predicate; public class ConnectibleGraph> extends AbstractGraph { private final static Logger LOG = LoggerFactory.getLogger(ConnectibleGraph.class); @@ -42,58 +41,95 @@ public class ConnectibleGraph> extends AbstractGraph super.build(start, set, profile.getJumps(), getShip().getTank()); } - private class DistanceFilter implements Predicate { - private final double limit; - private final T source; - - private DistanceFilter(double limit, T source) { - this.limit = limit; - this.source = source; - } - - @Override - public boolean test(Double distance) { - return distance <= getShip().getJumpRange(limit) || (profile.withRefill() && distance <= getShip().getMaxJumpRange() && source.canRefill()); - } - } - protected class ConnectibleGraphBuilder extends GraphBuilder { - private final DistanceFilter distanceFilter; - protected double refill; - protected double fuelCost; + protected double minFuel; + protected double maxFuel; + protected double distance; protected ConnectibleGraphBuilder(Vertex vertex, Collection set, int deep, double limit) { super(vertex, set, deep, limit); - distanceFilter = new DistanceFilter(limit, vertex.getEntry()); } @Override protected double onConnect(T entry) { - double distance = vertex.getEntry().getDistance(entry); - if (!distanceFilter.test(distance)){ + distance = vertex.getEntry().getDistance(entry); + if (distance > getShip().getMaxJumpRange()){ LOG.trace("Vertex {} is far away, {}", entry, distance); return -1; } - fuelCost = getShip().getFuelCost(limit, distance); - double nextLimit = profile.withRefill() ? limit - fuelCost : getShip().getTank(); - if (nextLimit < 0 && vertex.getEntry().canRefill()) { - LOG.trace("Refill"); - refill = getShip().getRoundMaxFuel(distance); - fuelCost = getShip().getFuelCost(refill, distance); - nextLimit = refill - fuelCost; - } else { - refill = 0; - } - return nextLimit; + maxFuel = getShip().getMaxFuel(distance); + minFuel = getShip().getMinFuel(distance); + double fuel = getProfile().withRefill() ? vertex.getEntry().canRefill() ? getShip().getRoundMaxFuel(distance) : limit : getShip().getTank(); + double fuelCost = getShip().getFuelCost(fuel, distance); + return fuel - fuelCost; } @Override - protected ConnectibleEdge createEdge(Vertex target) { - ConnectibleEdge res = new ConnectibleEdge<>(vertex, target); - res.setRefill(refill); - res.setFuelCost(fuelCost); + protected BuildEdge createEdge(Vertex target) { + BuildEdge res = new BuildEdge(vertex, target); + res.setFuel(minFuel, maxFuel); + res.setDistance(distance); return res; } } + public class BuildEdge extends Edge { + private double distance; + private double minFuel; + private double maxFuel; + + public BuildEdge(Vertex source, Vertex target) { + super(source, target); + } + + public double getMinFuel() { + return minFuel; + } + + public double getMaxFuel() { + return maxFuel; + } + + public double getRoundMaxFuel() { + return getShip().getRoundFuel(maxFuel); + } + + public void setFuel(double minFuel, double maxFuel) { + this.minFuel = minFuel; + this.maxFuel = maxFuel; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public double getDistance() { + return distance; + } + + public double getFuelCost(double fuel){ + return getProfile().withRefill() ? getShip().getFuelCost(fuel, distance) : 0; + } + + public double getRefill(){ + return getShip().getRoundMaxFuel(distance); + } + + public Ship getShip(){ + return ConnectibleGraph.this.getShip(); + } + + @Override + protected double computeWeight() { + return distance; + } + + @Override + public String toString() { + return source.getEntry().toString() + " - "+ weight + +"("+minFuel + " - " + maxFuel + ")" + +" -> " + target.getEntry().toString(); + } + + } } diff --git a/core/src/main/java/ru/trader/analysis/graph/Path.java b/core/src/main/java/ru/trader/analysis/graph/Path.java index e652d48..9579929 100644 --- a/core/src/main/java/ru/trader/analysis/graph/Path.java +++ b/core/src/main/java/ru/trader/analysis/graph/Path.java @@ -1,24 +1,112 @@ package ru.trader.analysis.graph; +import ru.trader.core.Ship; import ru.trader.graph.Connectable; import java.util.ArrayList; +import java.util.Collection; import java.util.List; public class Path> { - private final List> entries; + private final List.BuildEdge> entries; + private double minFuel; + private double maxFuel; + private double fuelCost; + private int refillCount; - public Path(List> edges) { - entries = new ArrayList<>(edges.size()); - for (int i = 0; i < edges.size(); i++) { - ConnectibleEdge edge = edges.get(i); - if (i==0) entries.add(new PathEntry<>(edge.getSource().getEntry(), false)); - entries.add(new PathEntry<>(edge.getTarget().getEntry(), edge.isRefill())); + + public Path(Collection.BuildEdge> edges) { + entries = new ArrayList<>(edges); + updateStat(); + } + + private void updateStat(){ + Ship ship = entries.get(0).getShip(); + double fuel = ship.getTank(); + minFuel = 0; maxFuel = 0; fuelCost = 0; refillCount = 0; + boolean adding = true; + for (int i = entries.size() - 1; i >= 0; i--) { + ConnectibleGraph.BuildEdge edge = entries.get(i); + if (i < entries.size() - 1 && edge.getSource().getEntry().canRefill()){ + adding = false; + fuel = edge.getMaxFuel(); + } + if (fuel < 0 || fuel < edge.getMinFuel()){ + minFuel = ship.getTank()+1; + } + double cost = edge.getFuelCost(fuel); + fuelCost += cost; + fuel -= cost; + if (adding) { + minFuel += edge.getMinFuel(); + } } + maxFuel = -1; + for (ConnectibleGraph.BuildEdge edge : entries) { + if (maxFuel != -1){ + maxFuel += edge.getFuelCost(maxFuel + edge.getMinFuel()); + } + if (maxFuel == -1 || edge.getMaxFuel() < maxFuel){ + maxFuel = edge.getMaxFuel(); + } + } + refillCount = getRefillCount(ship.getTank()); } - public PathEntry get(int index){ - return entries.get(index); + public List.BuildEdge> getEdges() { + return entries; } + public double getFuelCost() { + return fuelCost; + } + + public double getMinFuel() { + return minFuel; + } + + public double getMaxFuel() { + return maxFuel; + } + + public int getSize(){ + return entries.size(); + } + + public int getRefillCount() { + return refillCount; + } + + public int getRefillCount(double fuel){ + int res = 0; + for (int i = entries.size() - 1; i >= 0; i--) { + ConnectibleGraph.BuildEdge edge = entries.get(i); + fuel -= edge.getFuelCost(fuel); + if (fuel < 0) { + res++; + fuel = edge.getMaxFuel(); + } + } + return res; + } + + public Path add(ConnectibleGraph.BuildEdge edge){ + Path res = new Path<>(entries); + res.entries.add(edge); + res.updateStat(); + return res; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("{"); + for (int i = entries.size() - 1; i >= 0; i--) { + ConnectibleGraph.BuildEdge entry = entries.get(i); + sb.append(entry); + if (i>0) + sb.append(", "); + } + sb.append('}'); + return sb.toString(); + } } diff --git a/core/src/main/java/ru/trader/analysis/graph/PathEntry.java b/core/src/main/java/ru/trader/analysis/graph/PathEntry.java deleted file mode 100644 index 2007be6..0000000 --- a/core/src/main/java/ru/trader/analysis/graph/PathEntry.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.trader.analysis.graph; - -public class PathEntry { - private final T entry; - private boolean refill; - - public PathEntry(T entry, boolean refill) { - this.entry = entry; - this.refill = refill; - } - - public T getEntry() { - return entry; - } - - public boolean isRefill() { - return refill; - } -} diff --git a/core/src/main/java/ru/trader/core/Profile.java b/core/src/main/java/ru/trader/core/Profile.java index e317ea6..5c80e5c 100644 --- a/core/src/main/java/ru/trader/core/Profile.java +++ b/core/src/main/java/ru/trader/core/Profile.java @@ -1,6 +1,7 @@ package ru.trader.core; public class Profile { + public static enum PATH_PRIORITY {FAST, ECO} private double balance; private int jumps; @@ -14,6 +15,7 @@ public class Profile { private double jumpMult; private double landMult; private double fuelPrice; + private PATH_PRIORITY pathPriority; public Profile(Ship ship) { this.ship = ship; @@ -25,6 +27,7 @@ public class Profile { landMult = 0.95; fuelPrice = 100; jumpMult = 0.5; + pathPriority = PATH_PRIORITY.ECO; } public double getBalance() { @@ -114,4 +117,12 @@ public class Profile { public void setFuelPrice(double fuelPrice) { this.fuelPrice = fuelPrice; } + + public PATH_PRIORITY getPathPriority() { + return pathPriority; + } + + public void setPathPriority(PATH_PRIORITY pathPriority) { + this.pathPriority = pathPriority; + } } diff --git a/core/src/main/java/ru/trader/core/TransitVendor.java b/core/src/main/java/ru/trader/core/TransitVendor.java index 6394928..791b930 100644 --- a/core/src/main/java/ru/trader/core/TransitVendor.java +++ b/core/src/main/java/ru/trader/core/TransitVendor.java @@ -93,11 +93,6 @@ public class TransitVendor implements Vendor { return d == 0 ? 0 : d > 0 ? 1 : -1; } - @Override - public boolean canRefill() { - return false; - } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java b/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java index eec6170..56c089d 100644 --- a/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java +++ b/core/src/test/java/ru/trader/analysis/RouteSearcherTest.java @@ -78,6 +78,10 @@ public class RouteSearcherTest extends Assert{ assertEquals(route.getDistance(), 72.42, 0.01); List apaths = searcher.getRoutes(ithaca_st, ithaca_st, fWorld.getMarkets(true).collect(Collectors.toList())); +/* List apaths = searcher.getRoutes(ithaca_st, ithaca_st, Arrays.asList(ithaca_st, lhs3262_st, + morgor_st, lhs3006_st, ithaca.asTransit(), lhs3262.asTransit(), + morgor.asTransit(), lhs3006.asTransit())); +*/ Route actual = apaths.stream().findFirst().get(); assertEquals("Routes is different", route, actual); diff --git a/core/src/test/java/ru/trader/analysis/VendorsGraphTest.java b/core/src/test/java/ru/trader/analysis/VendorsGraphTest.java index 34859f3..d9cf7ef 100644 --- a/core/src/test/java/ru/trader/analysis/VendorsGraphTest.java +++ b/core/src/test/java/ru/trader/analysis/VendorsGraphTest.java @@ -6,6 +6,7 @@ import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ru.trader.analysis.graph.ConnectibleGraph; import ru.trader.analysis.graph.Crawler; import ru.trader.analysis.graph.SimpleCollector; import ru.trader.analysis.graph.Vertex; @@ -58,10 +59,10 @@ public class VendorsGraphTest extends Assert { LOG.info("Build vendors graph"); VendorsGraph vGraph = new VendorsGraph(scorer); vGraph.build(cabreraDock, fWorld.getMarkets(true).collect(Collectors.toList())); - + LOG.info("Search"); SimpleCollector paths = new SimpleCollector<>(); Crawler crawler = vGraph.crawler(paths::add); - + // Cabrera Dock -> Transit Wolf 1323 -> Transit Wolf 1325 -> Quimper Ring -> Transit Bhadaba -> Transit Wolf 1325 -> Cabrera Dock] crawler.findMin(cabreraDock, 100); assertEquals(100, paths.get().size()); paths.clear();