diff --git a/client/src/main/java/ru/trader/Main.java b/client/src/main/java/ru/trader/Main.java index 621a6fc..94bfe3d 100644 --- a/client/src/main/java/ru/trader/Main.java +++ b/client/src/main/java/ru/trader/Main.java @@ -93,6 +93,7 @@ public class Main extends Application { Screeners.loadPathsStage(getUrl(("paths.fxml"))); Screeners.loadSettingsStage(getUrl(("settings.fxml"))); Screeners.loadSEditorStage(getUrl(("sEditor.fxml"))); + Screeners.loadFilterStage(getUrl(("filter.fxml"))); } private static URL getUrl(String filename) throws MalformedURLException { diff --git a/client/src/main/java/ru/trader/Settings.java b/client/src/main/java/ru/trader/Settings.java index 564a285..7521ec6 100644 --- a/client/src/main/java/ru/trader/Settings.java +++ b/client/src/main/java/ru/trader/Settings.java @@ -2,6 +2,7 @@ package ru.trader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ru.trader.core.*; import java.io.*; import java.util.Properties; @@ -125,4 +126,12 @@ public class Settings { public int getPathsCount(){ return Integer.valueOf(values.getProperty("performance.limit","100")); } + + public MarketFilter getFilter(Market market){ + return MarketFilter.buildFilter(values, market); + } + + public void setFilter(MarketFilter filter){ + filter.writeTo(values); + } } diff --git a/client/src/main/java/ru/trader/World.java b/client/src/main/java/ru/trader/World.java index 4545cfe..6c28ef0 100644 --- a/client/src/main/java/ru/trader/World.java +++ b/client/src/main/java/ru/trader/World.java @@ -3,7 +3,6 @@ package ru.trader; import org.xml.sax.SAXException; import ru.trader.core.Market; import ru.trader.core.MarketAnalyzer; -import ru.trader.model.ModelFabric; import ru.trader.store.simple.SimpleMarket; import ru.trader.store.simple.Store; import ru.trader.store.XSSFImporter; @@ -47,6 +46,7 @@ public class World { MarketAnalyzer analyzer = new MarketAnalyzer(market); analyzer.setSegmentSize(Main.SETTINGS.getSegmentSize()); analyzer.setPathsCount(Main.SETTINGS.getPathsCount()); + analyzer.setFilter(Main.SETTINGS.getFilter(market)); return analyzer; } } diff --git a/client/src/main/java/ru/trader/controllers/FilterController.java b/client/src/main/java/ru/trader/controllers/FilterController.java new file mode 100644 index 0000000..4e53a76 --- /dev/null +++ b/client/src/main/java/ru/trader/controllers/FilterController.java @@ -0,0 +1,151 @@ +package ru.trader.controllers; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Parent; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.ListView; +import org.controlsfx.control.ButtonBar; +import org.controlsfx.control.action.Action; +import org.controlsfx.dialog.Dialog; +import org.controlsfx.dialog.DialogAction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ru.trader.core.MarketFilter; +import ru.trader.core.SERVICE_TYPE; +import ru.trader.model.MarketModel; +import ru.trader.model.ModelFabric; +import ru.trader.model.StationModel; +import ru.trader.model.SystemModel; +import ru.trader.model.support.BindingsHelper; +import ru.trader.view.support.Localization; +import ru.trader.view.support.NumberField; +import ru.trader.view.support.cells.CustomListCell; + + +public class FilterController { + private final static Logger LOG = LoggerFactory.getLogger(FilterController.class); + + @FXML + private ComboBox center; + @FXML + private NumberField radius; + @FXML + private NumberField distance; + @FXML + private ComboBox system; + @FXML + private ComboBox station; + @FXML + private CheckBox cbMarket; + @FXML + private CheckBox cbBlackMarket; + @FXML + private CheckBox cbRepair; + @FXML + private CheckBox cbMunition; + @FXML + private CheckBox cbOutfit; + @FXML + private CheckBox cbShipyard; + @FXML + private CheckBox cbMediumLandpad; + @FXML + private CheckBox cbLargeLandpad; + @FXML + private ListView excludes; + + public final Action actSave = new DialogAction(Localization.getString("dialog.button.save"), ButtonBar.ButtonType.OK_DONE, false, true, false, (e) -> save()); + + private MarketModel market; + private MarketFilter filter; + + @FXML + private void initialize(){ + system.valueProperty().addListener((ov, o, n) -> station.setItems(n.getStationsList())); + excludes.setCellFactory(new CustomListCell<>(s -> String.format("%s (%s)", s.getSystem().getName(), s.getName()))); + init(); + } + + void init(){ + market = MainController.getMarket(); + center.setItems(market.systemsListProperty()); + system.setItems(market.systemsProperty()); + } + + private void fill(MarketFilter filter){ + this.filter = filter; + center.setValue(market.getModeler().get(filter.getCenter())); + radius.setValue(filter.getRadius()); + distance.setValue(filter.getDistance()); + cbMarket.setSelected(filter.has(SERVICE_TYPE.MARKET)); + cbBlackMarket.setSelected(filter.has(SERVICE_TYPE.BLACK_MARKET)); + cbMunition.setSelected(filter.has(SERVICE_TYPE.MUNITION)); + cbRepair.setSelected(filter.has(SERVICE_TYPE.REPAIR)); + cbOutfit.setSelected(filter.has(SERVICE_TYPE.OUTFIT)); + cbShipyard.setSelected(filter.has(SERVICE_TYPE.SHIPYARD)); + cbMediumLandpad.setSelected(filter.has(SERVICE_TYPE.MEDIUM_LANDPAD)); + cbLargeLandpad.setSelected(filter.has(SERVICE_TYPE.LARGE_LANDPAD)); + excludes.setItems(BindingsHelper.observableList(filter.getExcludes(), market.getModeler()::get)); + } + + private void save() { + SystemModel s = center.getValue(); + LOG.trace("Old filter", filter); + filter.setCenter(s == ModelFabric.NONE_SYSTEM ? null : market.getModeler().get(s)); + filter.setRadius(radius.getValue().doubleValue()); + filter.setDistance(distance.getValue().doubleValue()); + if (cbMarket.isSelected()) filter.add(SERVICE_TYPE.MARKET); else filter.remove(SERVICE_TYPE.MARKET); + if (cbBlackMarket.isSelected()) filter.add(SERVICE_TYPE.BLACK_MARKET); else filter.remove(SERVICE_TYPE.BLACK_MARKET); + if (cbMunition.isSelected()) filter.add(SERVICE_TYPE.MUNITION); else filter.remove(SERVICE_TYPE.MUNITION); + if (cbRepair.isSelected()) filter.add(SERVICE_TYPE.REPAIR); else filter.remove(SERVICE_TYPE.REPAIR); + if (cbOutfit.isSelected()) filter.add(SERVICE_TYPE.OUTFIT); else filter.remove(SERVICE_TYPE.OUTFIT); + if (cbShipyard.isSelected()) filter.add(SERVICE_TYPE.SHIPYARD); else filter.remove(SERVICE_TYPE.SHIPYARD); + if (cbMediumLandpad.isSelected()) filter.add(SERVICE_TYPE.MEDIUM_LANDPAD); else filter.remove(SERVICE_TYPE.MEDIUM_LANDPAD); + if (cbLargeLandpad.isSelected()) filter.add(SERVICE_TYPE.LARGE_LANDPAD); else filter.remove(SERVICE_TYPE.LARGE_LANDPAD); + filter.clearExcludes(); + excludes.getItems().forEach(st -> filter.addExclude(market.getModeler().get(st))); + LOG.trace("New filter", filter); + } + + public Action showDialog(Parent parent, Parent content){ + return showDialog(parent, content, new MarketFilter()); + } + + public Action showDialog(Parent parent, Parent content, MarketFilter filter){ + Dialog dlg = new Dialog(parent, Localization.getString("filter.title")); + dlg.setContent(content); + dlg.getActions().addAll(actSave, Dialog.ACTION_CANCEL); + dlg.setResizable(false); + fill(filter); + return dlg.show(); + } + + public void add(ActionEvent actionEvent) { + SystemModel s = system.getValue(); + if (s != null){ + StationModel st = station.getValue(); + if (st != null && st != ModelFabric.NONE_STATION){ + excludes.getItems().add(st); + } else { + excludes.getItems().addAll(s.getStations()); + } + } + } + + public void remove(ActionEvent actionEvent) { + int index = excludes.getSelectionModel().getSelectedIndex(); + if (index >= 0){ + excludes.getItems().remove(index); + } + } + + public void clean(ActionEvent actionEvent) { + excludes.getItems().clear(); + } + + public MarketFilter getFilter(){ + return filter; + } +} diff --git a/client/src/main/java/ru/trader/controllers/MainController.java b/client/src/main/java/ru/trader/controllers/MainController.java index ec05a06..2120ae4 100644 --- a/client/src/main/java/ru/trader/controllers/MainController.java +++ b/client/src/main/java/ru/trader/controllers/MainController.java @@ -177,6 +177,10 @@ public class MainController { Screeners.showSettings(); } + public void editFilter(){ + Screeners.showFilter(market.getAnalyzer().getFilter()); + } + private void reload(){ world = new MarketModel(World.getMarket()); market = world; diff --git a/client/src/main/java/ru/trader/controllers/Screeners.java b/client/src/main/java/ru/trader/controllers/Screeners.java index 538aec8..b8a190f 100644 --- a/client/src/main/java/ru/trader/controllers/Screeners.java +++ b/client/src/main/java/ru/trader/controllers/Screeners.java @@ -9,6 +9,8 @@ import javafx.stage.Stage; import org.controlsfx.control.action.Action; import org.controlsfx.dialog.Dialogs; import ru.trader.EMDNUpdater; +import ru.trader.Main; +import ru.trader.core.MarketFilter; import ru.trader.model.*; import ru.trader.view.support.CustomBuilderFactory; import ru.trader.view.support.Localization; @@ -29,6 +31,7 @@ public class Screeners { private static Parent pathsScreen; private static Parent settingsScreen; private static Parent sEditorScreen; + private static Parent filterScreen; private static MainController mainController; private static ItemDescController itemDescController; @@ -39,6 +42,7 @@ public class Screeners { private static PathsController pathsController; private static SettingsController settingsController; private static SystemsEditorController systemsEditorController; + private static FilterController filterController; private static FXMLLoader initLoader(URL url){ FXMLLoader loader = new FXMLLoader(url, Localization.getResources()); @@ -127,6 +131,12 @@ public class Screeners { systemsEditorController = loader.getController(); } + public static void loadFilterStage(URL fxml) throws IOException { + FXMLLoader loader = initLoader(fxml); + filterScreen = loader.load(); + addStylesheet(filterScreen); + filterController = loader.getController(); + } public static void show(Node node){ mainController.getMainPane().setCenter(node); @@ -195,8 +205,21 @@ public class Screeners { settingsController.showDialog(mainScreen, settingsScreen); } + public static MarketFilter showFilter() { + Action res = filterController.showDialog(mainScreen, filterScreen); + return res == filterController.actSave ? filterController.getFilter() : null; + } + + public static void showFilter(MarketFilter filter) { + Action res = filterController.showDialog(mainScreen, filterScreen, filter); + if (res == filterController.actSave){ + Main.SETTINGS.setFilter(filter); + } + } + public static void reinitAll() { mainController.init(); + filterController.init(); EMDNUpdater.setMarket(MainController.getMarket()); } } diff --git a/client/src/main/java/ru/trader/model/ModelFabric.java b/client/src/main/java/ru/trader/model/ModelFabric.java index 13076b6..1a1d55b 100644 --- a/client/src/main/java/ru/trader/model/ModelFabric.java +++ b/client/src/main/java/ru/trader/model/ModelFabric.java @@ -45,6 +45,9 @@ public class ModelFabric { return res; } + public Place get(SystemModel model){ + return model.getSystem(); + } public StationModel get(Vendor station){ if (station == null) return NONE_STATION; @@ -60,6 +63,9 @@ public class ModelFabric { return res; } + public Vendor get(StationModel model){ + return model.getStation(); + } public ItemModel get(Item item){ if (item == null) return null; diff --git a/client/src/main/java/ru/trader/view/support/cells/CustomListCell.java b/client/src/main/java/ru/trader/view/support/cells/CustomListCell.java new file mode 100644 index 0000000..70c550b --- /dev/null +++ b/client/src/main/java/ru/trader/view/support/cells/CustomListCell.java @@ -0,0 +1,33 @@ +package ru.trader.view.support.cells; + +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.util.Callback; + +import java.util.function.Function; + +public class CustomListCell implements Callback, ListCell> { + + private final Function toString; + + public CustomListCell(Function toString) { + this.toString = toString; + } + + @Override + public ListCell call(ListView param){ + return new ListCell(){ + @Override + public void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty){ + setText(toString.apply(item)); + setGraphic(null); + } else { + setText(null); + setGraphic(null); + } + } + }; + } +} diff --git a/client/src/main/resources/lang/locale_en_US.properties b/client/src/main/resources/lang/locale_en_US.properties index d6ec54f..56dac4e 100644 --- a/client/src/main/resources/lang/locale_en_US.properties +++ b/client/src/main/resources/lang/locale_en_US.properties @@ -57,6 +57,7 @@ main.menu.settings=Settings main.menu.settings.language=Language main.menu.settings.language.item=English main.menu.settings.parameters=Preferences +main.menu.settings.filter=Filter # add item dialog dialog.addItem.title=Adding new commodity @@ -123,3 +124,11 @@ settings.performance.limit=Routes count: sEditor.title=Star systems editor sEditor.text.orientates=Landmarks: sEditor.table.distance=Distance (LY) + +# filter.fxml +filter.title=Edit Filter +filter.center=Center: +filter.radius=Rsdius(LY): +filter.distance=Distance to station(Ls): +filter.services=Services: +filter.excludes=Excludes stations: \ No newline at end of file diff --git a/client/src/main/resources/lang/locale_ru_RU.properties b/client/src/main/resources/lang/locale_ru_RU.properties index be6658f..ba27e52 100644 --- a/client/src/main/resources/lang/locale_ru_RU.properties +++ b/client/src/main/resources/lang/locale_ru_RU.properties @@ -58,7 +58,7 @@ main.menu.settings=\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0438 main.menu.settings.language=\u042F\u0437\u044B\u043A main.menu.settings.language.item=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 main.menu.settings.parameters=\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B - +main.menu.settings.filter=\u0424\u0438\u043B\u044C\u0442\u0440 # add item dialog dialog.addItem.title=\u0414\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u0435 \u043D\u043E\u0432\u043E\u0433\u043E \u0442\u043E\u0432\u0430\u0440\u0430 @@ -124,3 +124,11 @@ settings.performance.limit=\u041A\u043E\u043B-\u0432\u043E \u043C\u0430\u0440\u0 sEditor.title=\u0420\u0435\u0434\u0430\u043A\u0442\u043E\u0440 \u0437\u0432\u0435\u0437\u0434\u043D\u044B\u0445 \u0441\u0438\u0441\u0442\u0435\u043C sEditor.text.orientates=\u041E\u0440\u0438\u0435\u043D\u0442\u0438\u0440\u044B: sEditor.table.distance=\u0414\u0438\u0441\u0442\u0430\u043D\u0446\u0438\u044F (LY) + +# filter.fxml +filter.title=\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u0444\u0438\u043B\u044C\u0442\u0440\u0430 +filter.center=\u0426\u0435\u043D\u0442\u0440: +filter.radius=\u0420\u0430\u0434\u0438\u0443\u0441(LY): +filter.distance=\u0414\u0438\u0441\u0442\u0430\u043D\u0446\u0438\u044F \u0434\u043E \u0441\u0442\u0430\u043D\u0446\u0438\u0438(Ls): +filter.services=\u0421\u0435\u0440\u0432\u0438\u0441\u044B: +filter.excludes=\u0418\u0441\u043A\u043B\u044E\u0447\u0430\u0435\u043C\u044B\u0435 \u0441\u0442\u0430\u043D\u0446\u0438\u0438: \ No newline at end of file diff --git a/client/src/main/resources/view/filter.fxml b/client/src/main/resources/view/filter.fxml new file mode 100644 index 0000000..797217e --- /dev/null +++ b/client/src/main/resources/view/filter.fxml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/main/resources/view/main.fxml b/client/src/main/resources/view/main.fxml index 7599965..8e90b3d 100644 --- a/client/src/main/resources/view/main.fxml +++ b/client/src/main/resources/view/main.fxml @@ -22,6 +22,7 @@ + diff --git a/core/src/main/java/ru/trader/core/Market.java b/core/src/main/java/ru/trader/core/Market.java index 37912c2..2baa347 100644 --- a/core/src/main/java/ru/trader/core/Market.java +++ b/core/src/main/java/ru/trader/core/Market.java @@ -1,12 +1,17 @@ package ru.trader.core; import java.util.Collection; +import java.util.Optional; public interface Market { void add(Place place); Place addPlace(String name, double x, double y, double z); void remove(Place place); + default Place get(String name){ + Optional place = get().stream().filter(p -> name.equals(p.getName())).findFirst(); + return place.isPresent() ? place.get() : null; + } void add(Group group); Group addGroup(String name, GROUP_TYPE type); diff --git a/core/src/main/java/ru/trader/core/MarketAnalyzer.java b/core/src/main/java/ru/trader/core/MarketAnalyzer.java index 81e7007..f4d039a 100644 --- a/core/src/main/java/ru/trader/core/MarketAnalyzer.java +++ b/core/src/main/java/ru/trader/core/MarketAnalyzer.java @@ -9,7 +9,8 @@ import java.util.*; public class MarketAnalyzer { private final static Logger LOG = LoggerFactory.getLogger(MarketAnalyzer.class); - private Market market; + private final Market market; + private MarketFilter filter; private double tank; private double maxDistance; private int segmentSize; @@ -25,221 +26,10 @@ public class MarketAnalyzer { this.segmentSize = 0; } - public Collection getTop(double balance){ - LOG.debug("Get top {}", limit); - Iterable places = market.get(); - List top = new ArrayList<>(limit); - for (Place place : places) { - LOG.trace("Check place {}", place); - Collection orders = getOrders(place, balance, top.isEmpty() ? 0 : top.get(top.size()-1).getProfit()); - TopList.addAllToTop(top, orders, limit, orderComparator); - } - return top; + public void setFilter(MarketFilter filter) { + this.filter = filter; } - public Collection getOrders(Vendor vendor, double balance) { - Collection places = market.get(); - Graph graph = new Graph(vendor.getPlace(), places, tank, maxDistance, true, jumps, Path::new); - return getOrders(graph, Collections.singleton(vendor), balance, 0); - } - - public Collection getOrders(Place place, double balance) { - return getOrders(place, balance, 0); - } - - private Collection getOrders(Place place, double balance, double lowProfit) { - Collection places = market.get(); - Graph graph = new Graph<>(place, places, tank, maxDistance, true, jumps, Path::new); - return getOrders(graph, place.get(), balance, lowProfit); - } - - private Collection getOrders(Graph graph, Collection sellers, double balance, double lowProfit) { - List res = new ArrayList<>(20); - for (Vendor vendor : sellers) { - for (Offer sell : vendor.getAllSellOffers()) { - LOG.trace("Sell offer {}", sell); - if (sell.getCount() == 0) continue; - long count = Order.getMaxCount(sell, balance, cargo); - LOG.trace("count = {}", count); - if (count == 0) continue; - Iterator buyers = market.getStatBuy(sell.getItem()).getOffers().descendingIterator(); - while (buyers.hasNext()){ - Offer buy = buyers.next(); - if (!graph.isAccessible(buy.getVendor().getPlace())){ - LOG.trace("Is inaccessible buyer, skip"); - continue; - } - Order order = new Order(sell, buy, count); - LOG.trace("Buy offer {} profit = {}", buy, order.getProfit()); - if (order.getProfit() <= 0 && order.getCount() > 0) break; - if (order.getProfit() < lowProfit && order.getCount() == count) { - LOG.trace("Is low profit, skip"); - break; - } - res.add(order); - } - } - } - res.sort(orderComparator); - return res; - } - - private Collection getOrders(Collection sellers, Collection buyers, double balance, double lowProfit) { - List res = new ArrayList<>(); - for (Vendor seller : sellers) { - for (Offer sell : seller.getAllSellOffers()) { - if (sell.getCount() == 0) continue; - long count = Order.getMaxCount(sell, balance, cargo); - LOG.trace("Sell offer {}, count = {}", sell, count); - if (count == 0) continue; - for (Vendor buyer : buyers) { - Offer buy = buyer.getBuy(sell.getItem()); - if (buy != null){ - Order order = new Order(sell, buy, count); - LOG.trace("Buy offer {} profit = {}", buy, order.getProfit()); - if (order.getProfit() < lowProfit) { - LOG.trace("Is low profit, skip"); - continue; - } - res.add(order); - } - } - } - } - res.sort(orderComparator); - return res; - } - - public Collection getOrders(Vendor from, Vendor to, double balance) { - Graph graph = new Graph(from.getPlace(), market.get(), tank, maxDistance, true, jumps, Path::new); - if (!graph.isAccessible(to.getPlace())){ - LOG.trace("Is inaccessible buyer"); - return Collections.emptyList(); - } - return getOrders(Collections.singleton(from), Collections.singleton(to), balance, 0); - } - - public Collection getOrders(Place from, Place to, double balance) { - Graph graph = new Graph(from, market.get(), tank, maxDistance, true, jumps, Path::new); - if (!graph.isAccessible(to)){ - LOG.trace("Is inaccessible buyer"); - return Collections.emptyList(); - } - return getOrders(from.get(), to.get(), balance, 0); - } - - - public Collection getOrders(Vendor from, Place to, double balance) { - Graph graph = new Graph(from.getPlace(), market.get(), tank, maxDistance, true, jumps, Path::new); - if (!graph.isAccessible(to)){ - LOG.trace("Is inaccessible buyer"); - return Collections.emptyList(); - } - return getOrders(Collections.singleton(from), to.get(), balance, 0); - } - - public Collection getOrders(Place from, Vendor to, double balance) { - Graph graph = new Graph(from, market.get(), tank, maxDistance, true, jumps, Path::new); - if (!graph.isAccessible(to.getPlace())){ - LOG.trace("Is inaccessible buyer"); - return Collections.emptyList(); - } - return getOrders(from.get(), Collections.singleton(to), balance, 0); - } - - - public Collection> getPaths(Place from, Place to){ - Graph graph = new Graph(from, market.get(), tank, maxDistance, true, jumps, Path::new); - return graph.getPathsTo(to); - } - - public Path getPath(Place from, Place to){ - Graph graph = new Graph(from, market.get(), tank, maxDistance, true, jumps, Path::new); - return graph.getFastPathTo(to); - } - - public PathRoute getPath(Vendor from, Vendor to){ - RouteGraph graph = new RouteGraph(from, market.getVendors(), tank, maxDistance, true, jumps); - return (PathRoute)graph.getFastPathTo(to); - } - - public Collection getPaths(Vendor from, double balance){ - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); - Collection vendors = market.getVendors(); - return searcher.getPaths(from, vendors, jumps, balance, cargo, limit); - } - - public Collection getPaths(Place from, double balance){ - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); - Collection vendors = market.getVendors(); - for (Vendor vendor : from.get()) { - Collection paths = searcher.getPaths(vendor, vendors, jumps, balance, cargo, limit); - if (paths.size()>0){ - return paths; - } - } - return Collections.emptyList(); - } - - public Collection getPaths(Vendor from, Vendor to, double balance){ - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); - return searcher.getPaths(from, to, market.getVendors(), jumps, balance, cargo, limit); - } - - public Collection getPaths(Place from, Place to, double balance){ - List top = new ArrayList<>(limit); - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); - Collection vendors = market.getVendors(); - Collection fVendors = from.get(); - Collection toVendors = to.get(); - int count = (int) Math.ceil(limit / fVendors.size()); - for (Vendor fromVendor : fVendors) { - for (Vendor toVendor : toVendors) { - Collection paths = searcher.getPaths(fromVendor, toVendor, vendors, jumps, balance, cargo, count); - TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); - } - } - return top; - } - - public Collection getPaths(Vendor from, Place to, double balance){ - List top = new ArrayList<>(limit); - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); - Collection vendors = market.getVendors(); - Collection toVendors = to.get(); - int count = (int) Math.ceil(limit / toVendors.size()); - for (Vendor toVendor : toVendors) { - Collection paths = searcher.getPaths(from, toVendor, vendors, jumps, balance, cargo, count); - TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); - } - return top; - } - - public Collection getPaths(Place from, Vendor to, double balance){ - List top = new ArrayList<>(limit); - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); - Collection vendors = market.getVendors(); - Collection fVendors = from.get(); - int count = (int) Math.ceil(limit / fVendors.size()); - for (Vendor fromVendor : fVendors) { - Collection paths = searcher.getPaths(fromVendor, to, vendors, jumps, balance, cargo, count); - TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); - } - return top; - } - - public Collection getTopPaths(double balance){ - List top = new ArrayList<>(limit); - RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); - Collection vendors = new PlacesWrapper(market.get()); - for (Vendor vendor : vendors) { - Collection paths = searcher.getPaths(vendor, vendor, vendors, jumps, balance, cargo, 3); - TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); - } - return top; - } - - public void setTank(double tank) { this.tank = tank; } @@ -263,4 +53,279 @@ public class MarketAnalyzer { public void setPathsCount(int count) { this.limit = count; } + + public Collection getTop(double balance){ + LOG.debug("Get top {}", limit); + Iterable places = getPlaces(); + List top = new ArrayList<>(limit); + for (Place place : places) { + LOG.trace("Check place {}", place); + Collection orders = getOrders(place, balance, top.isEmpty() ? 0 : top.get(top.size()-1).getProfit()); + TopList.addAllToTop(top, orders, limit, orderComparator); + } + return top; + } + + public Collection getOrders(Vendor vendor, double balance) { + Collection places = getPlaces(); + Graph graph = new Graph(vendor.getPlace(), places, tank, maxDistance, true, jumps, Path::new); + return getOrders(graph, Collections.singleton(vendor), balance, 0); + } + + public Collection getOrders(Place place, double balance) { + return getOrders(place, balance, 0); + } + + private Collection getOrders(Place place, double balance, double lowProfit) { + Collection places = getPlaces(); + Graph graph = new Graph<>(place, places, tank, maxDistance, true, jumps, Path::new); + return getOrders(graph, place.get(), balance, lowProfit); + } + + private Collection getOrders(Graph graph, Collection sellers, double balance, double lowProfit) { + List res = new ArrayList<>(20); + for (Vendor vendor : sellers) { + if (isFiltered(vendor)){ + LOG.trace("Is filtered, skip"); + continue; + } + for (Offer sell : vendor.getAllSellOffers()) { + LOG.trace("Sell offer {}", sell); + if (sell.getCount() == 0) continue; + long count = Order.getMaxCount(sell, balance, cargo); + LOG.trace("count = {}", count); + if (count == 0) continue; + Iterator buyers = market.getStatBuy(sell.getItem()).getOffers().descendingIterator(); + while (buyers.hasNext()){ + Offer buy = buyers.next(); + if (isFiltered(buy.getVendor())){ + LOG.trace("Is filtered, skip"); + continue; + } + if (!graph.isAccessible(buy.getVendor().getPlace())){ + LOG.trace("Is inaccessible buyer, skip"); + continue; + } + Order order = new Order(sell, buy, count); + LOG.trace("Buy offer {} profit = {}", buy, order.getProfit()); + if (order.getProfit() <= 0 && order.getCount() > 0) break; + if (order.getProfit() < lowProfit && order.getCount() == count) { + LOG.trace("Is low profit, skip"); + break; + } + res.add(order); + } + } + } + res.sort(orderComparator); + return res; + } + + private Collection getOrders(Collection sellers, Collection buyers, double balance, double lowProfit) { + List res = new ArrayList<>(); + for (Vendor seller : sellers) { + if (isFiltered(seller)){ + LOG.trace("Is filtered, skip"); + continue; + } + for (Offer sell : seller.getAllSellOffers()) { + if (sell.getCount() == 0) continue; + long count = Order.getMaxCount(sell, balance, cargo); + LOG.trace("Sell offer {}, count = {}", sell, count); + if (count == 0) continue; + for (Vendor buyer : buyers) { + if (isFiltered(buyer)){ + LOG.trace("Is filtered, skip"); + continue; + } + Offer buy = buyer.getBuy(sell.getItem()); + if (buy != null){ + Order order = new Order(sell, buy, count); + LOG.trace("Buy offer {} profit = {}", buy, order.getProfit()); + if (order.getProfit() < lowProfit) { + LOG.trace("Is low profit, skip"); + continue; + } + res.add(order); + } + } + } + } + res.sort(orderComparator); + return res; + } + + public Collection getOrders(Vendor from, Vendor to, double balance) { + Graph graph = new Graph(from.getPlace(), getPlaces(), tank, maxDistance, true, jumps, Path::new); + if (!graph.isAccessible(to.getPlace())){ + LOG.trace("Is inaccessible buyer"); + return Collections.emptyList(); + } + return getOrders(Collections.singleton(from), Collections.singleton(to), balance, 0); + } + + public Collection getOrders(Place from, Place to, double balance) { + Graph graph = new Graph(from, getPlaces(), tank, maxDistance, true, jumps, Path::new); + if (!graph.isAccessible(to)){ + LOG.trace("Is inaccessible buyer"); + return Collections.emptyList(); + } + return getOrders(from.get(), to.get(), balance, 0); + } + + + public Collection getOrders(Vendor from, Place to, double balance) { + Graph graph = new Graph(from.getPlace(), getPlaces(), tank, maxDistance, true, jumps, Path::new); + if (!graph.isAccessible(to)){ + LOG.trace("Is inaccessible buyer"); + return Collections.emptyList(); + } + return getOrders(Collections.singleton(from), to.get(), balance, 0); + } + + public Collection getOrders(Place from, Vendor to, double balance) { + Graph graph = new Graph(from, getPlaces(), tank, maxDistance, true, jumps, Path::new); + if (!graph.isAccessible(to.getPlace())){ + LOG.trace("Is inaccessible buyer"); + return Collections.emptyList(); + } + return getOrders(from.get(), Collections.singleton(to), balance, 0); + } + + + public Collection> getPaths(Place from, Place to){ + Graph graph = new Graph(from, getPlaces(), tank, maxDistance, true, jumps, Path::new); + return graph.getPathsTo(to); + } + + public Path getPath(Place from, Place to){ + Graph graph = new Graph(from, getPlaces(), tank, maxDistance, true, jumps, Path::new); + return graph.getFastPathTo(to); + } + + public PathRoute getPath(Vendor from, Vendor to){ + RouteGraph graph = new RouteGraph(from, getVendors(), tank, maxDistance, true, jumps); + return (PathRoute)graph.getFastPathTo(to); + } + + public Collection getPaths(Vendor from, double balance){ + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + Collection vendors = getVendors(); + return searcher.getPaths(from, vendors, jumps, balance, cargo, limit); + } + + public Collection getPaths(Place from, double balance){ + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + Collection vendors = getVendors(); + for (Vendor vendor : from.get()) { + if (isFiltered(vendor)){ + LOG.trace("Is filtered, skip"); + continue; + } + Collection paths = searcher.getPaths(vendor, vendors, jumps, balance, cargo, limit); + if (paths.size()>0){ + return paths; + } + } + return Collections.emptyList(); + } + + public Collection getPaths(Vendor from, Vendor to, double balance){ + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + return searcher.getPaths(from, to, getVendors(), jumps, balance, cargo, limit); + } + + public Collection getPaths(Place from, Place to, double balance){ + List top = new ArrayList<>(limit); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + Collection vendors = getVendors(); + Collection fVendors = from.get(); + Collection toVendors = to.get(); + int count = (int) Math.ceil(limit / fVendors.size()); + for (Vendor fromVendor : fVendors) { + if (isFiltered(fromVendor)){ + LOG.trace("Is filtered, skip"); + continue; + } + for (Vendor toVendor : toVendors) { + if (isFiltered(toVendor)){ + LOG.trace("Is filtered, skip"); + continue; + } + Collection paths = searcher.getPaths(fromVendor, toVendor, vendors, jumps, balance, cargo, count); + TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); + } + } + return top; + } + + public Collection getPaths(Vendor from, Place to, double balance){ + List top = new ArrayList<>(limit); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + Collection vendors = getVendors(); + Collection toVendors = to.get(); + int count = (int) Math.ceil(limit / toVendors.size()); + for (Vendor toVendor : toVendors) { + if (isFiltered(toVendor)){ + LOG.trace("Is filtered, skip"); + continue; + } + Collection paths = searcher.getPaths(from, toVendor, vendors, jumps, balance, cargo, count); + TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); + } + return top; + } + + public Collection getPaths(Place from, Vendor to, double balance){ + List top = new ArrayList<>(limit); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + Collection vendors = getVendors(); + Collection fVendors = from.get(); + int count = (int) Math.ceil(limit / fVendors.size()); + for (Vendor fromVendor : fVendors) { + if (isFiltered(fromVendor)){ + LOG.trace("Is filtered, skip"); + continue; + } + Collection paths = searcher.getPaths(fromVendor, to, vendors, jumps, balance, cargo, count); + TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); + } + return top; + } + + public Collection getTopPaths(double balance){ + List top = new ArrayList<>(limit); + RouteSearcher searcher = new RouteSearcher(maxDistance, tank, segmentSize); + Collection vendors = getVendors(); + for (Vendor vendor : vendors) { + Collection paths = searcher.getPaths(vendor, vendor, vendors, jumps, balance, cargo, 3); + TopList.addAllToTop(top, paths, limit, RouteGraph.byProfitComparator); + } + return top; + } + + private Collection getPlaces(){ + if (filter != null){ + return filter.filtered(market.get()); + } else { + return market.get(); + } + } + + private Collection getVendors(){ + if (filter != null){ + Collection vendors = new PlacesWrapper(getPlaces()); + return filter.filteredVendors(vendors); + } else { + return market.getVendors(); + } + } + + public MarketFilter getFilter() { + return filter; + } + + private boolean isFiltered(Vendor vendor){ + return filter != null && (filter.isFiltered(vendor.getPlace()) || filter.isFiltered(vendor)); + } } diff --git a/core/src/main/java/ru/trader/core/MarketFilter.java b/core/src/main/java/ru/trader/core/MarketFilter.java new file mode 100644 index 0000000..4625105 --- /dev/null +++ b/core/src/main/java/ru/trader/core/MarketFilter.java @@ -0,0 +1,169 @@ +package ru.trader.core; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Properties; +import java.util.stream.Collectors; + +public class MarketFilter { + private final static Logger LOG = LoggerFactory.getLogger(MarketFilter.class); + + private Place center; + private double radius; + private double distance; + private final EnumSet services; + private final Collection excludes; + + public MarketFilter() { + services = EnumSet.noneOf(SERVICE_TYPE.class); + excludes = new ArrayList<>(); + } + + public Place getCenter() { + return center; + } + + public void setCenter(Place center) { + this.center = center; + } + + public double getRadius() { + return radius; + } + + public void setRadius(double radius) { + this.radius = radius; + } + + public double getDistance() { + return distance; + } + + public void setDistance(double distance) { + this.distance = distance; + } + + public void add(SERVICE_TYPE service){ + services.add(service); + } + + public void addAll(Collection service){ + services.addAll(service); + } + + public void remove(SERVICE_TYPE service){ + services.remove(service); + } + + public boolean has(SERVICE_TYPE service){ + return services.contains(service); + } + + public void addExclude(Vendor vendor){ + excludes.add(vendor); + } + + public void removeExclude(Vendor vendor){ + excludes.remove(vendor); + } + + public void clearExcludes(){ + excludes.clear(); + } + + public Collection getExcludes(){ + return excludes; + } + + public boolean isFiltered(Place place){ + return center != null && center.getDistance(place) > radius; + } + + public boolean isFiltered(Vendor vendor){ + if (distance > 0 && vendor.getDistance() > distance) return true; + if (excludes.contains(vendor)) return true; + for (SERVICE_TYPE service : services) { + if (!vendor.has(service)) return true; + } + return false; + } + + public Collection filtered(Collection places){ + return places.parallelStream().filter(p -> !isFiltered(p)).collect(Collectors.toList()); + } + + public Collection filteredVendors(Collection vendors){ + return vendors.parallelStream().filter(v -> !isFiltered(v)).collect(Collectors.toList()); + } + + + public static MarketFilter buildFilter(Properties values, Market market){ + MarketFilter filter = new MarketFilter(); + String v = values.getProperty("filter.center", null); + if (v != null){ + filter.setCenter(market.get(v)); + } + filter.setRadius(Double.valueOf(values.getProperty("filter.radius","0"))); + filter.setDistance(Double.valueOf(values.getProperty("filter.distance", "0"))); + v = values.getProperty("filter.services", ""); + if (v.length() > 0){ + for (String s : v.split(",")) { + filter.add(SERVICE_TYPE.valueOf(s)); + } + } + v = values.getProperty("filter.excludes", ""); + if (v.length() > 0){ + for (String s : v.split(",")) { + String[] st = s.split("\\|"); + Place place = market.get(st[0]); + if (place != null) { + Vendor vendor = place.get(st[1]); + if (vendor != null) { + filter.addExclude(vendor); + } else { + LOG.warn("Not found vendor {}", st[1]); + } + } else { + LOG.warn("Not found place {}", st[0]); + } + } + } + return filter; + } + + public void writeTo(Properties properties){ + properties.setProperty("filter.center", center != null ? center.getName() : ""); + properties.setProperty("filter.radius", String.valueOf(radius)); + properties.setProperty("filter.distance", String.valueOf(distance)); + + StringBuilder s = new StringBuilder(); + for (SERVICE_TYPE service: services) { + if (s.length() > 0) s.append(","); + s.append(service); + } + properties.setProperty("filter.services", s.toString()); + s = new StringBuilder(); + for (Vendor vendor : excludes) { + if (s.length() > 0) s.append(","); + s.append(vendor.getPlace().getName()); + s.append("|"); + s.append(vendor.getName()); + } + properties.setProperty("filter.excludes", s.toString()); + } + + @Override + public String toString() { + return "{" + + "center=" + center + + ", radius=" + radius + + ", distance=" + distance + + ", services=" + services + + ", excludes=" + excludes + + '}'; + } +} diff --git a/core/src/main/java/ru/trader/core/Place.java b/core/src/main/java/ru/trader/core/Place.java index 7a9e454..45c8402 100644 --- a/core/src/main/java/ru/trader/core/Place.java +++ b/core/src/main/java/ru/trader/core/Place.java @@ -3,6 +3,7 @@ package ru.trader.core; import ru.trader.graph.Connectable; import java.util.Collection; +import java.util.Optional; public interface Place extends Connectable { @@ -15,6 +16,10 @@ public interface Place extends Connectable { void setPosition(double x, double y, double z); Collection get(); + default Vendor get(String name){ + Optional vendor = get().stream().filter(p -> name.equals(p.getName())).findFirst(); + return vendor.isPresent() ? vendor.get() : null; + } void add(Vendor vendor); Vendor addVendor(String name); void remove(Vendor vendor);