From: Stefan F. <ste...@us...> - 2010-04-12 17:37:46
|
Update of /cvsroot/rails/18xx/rails/algorithms In directory sfp-cvsdas-4.v30.ch3.sourceforge.com:/tmp/cvs-serv14735/rails/algorithms Modified Files: NetworkEdge.java NetworkVertex.java NetworkIterator.java NetworkGraphBuilder.java Added Files: NetworkTrain.java RevenueCalculator.java RevenueAdapter.java Log Message: Initial experimental version of revenue calculation --- NEW FILE: RevenueCalculator.java --- package rails.algorithms; import java.util.Arrays; import org.apache.log4j.Logger; final class RevenueCalculator { private final int nbVertexes; private final int maxNeighbors; private final int nbTrains; // static vertex data private final int[] vertexValue; private final boolean[] vertexCity; private final boolean[] vertexTown; private final int[][] vertexNeighbors; // start vertexes private int[] startVertexes; // static edge data private final boolean[][] edgeGreedy; private final int[][] edgeDistance; // dynamic edge data private final boolean[][] edgeUsed; // static train data private final int[] trainMaxCities; private final int[] trainMaxCombined; private final boolean[] trainTownsCostNothing; private final int[] trainMultiplyCities; private final int[] trainMultiplyTowns; // dynamic train data private final int[] trainCityValue; private final int[] trainTownValue; private final int[] trainCities; private final int[] trainTowns; private final boolean[][] trainVisited; private final int[][] trainVertexStack; private final int[] trainStackPos; private final int [] trainBottomPos; private final int [] trainStartEdge; // dynamic run data private int finalTrain; private int currentBestValue; private final int [][] currentBestRun; private int countVisits; private int countEdges; protected static Logger log = Logger.getLogger(RevenueCalculator.class.getPackage().getName()); public RevenueCalculator (int nbVertexes, int maxNeighbors, int nbTrains) { this.nbVertexes = nbVertexes; this.maxNeighbors = maxNeighbors; this.nbTrains = nbTrains; log.debug("RC defined: nbVertexes = " + nbVertexes + ", maxNeighbors = " + maxNeighbors + ", nbTrains = " + nbTrains); // initialize all required variables vertexValue = new int[nbVertexes]; vertexCity = new boolean[nbVertexes]; vertexTown = new boolean[nbVertexes]; vertexNeighbors = new int[nbVertexes][maxNeighbors]; edgeGreedy = new boolean[nbVertexes][nbVertexes]; edgeDistance = new int[nbVertexes][nbVertexes]; edgeUsed = new boolean[nbVertexes][nbVertexes]; trainCities = new int[nbTrains]; trainTowns = new int[nbTrains]; trainTownsCostNothing = new boolean[nbTrains]; trainMultiplyCities = new int[nbTrains]; trainMultiplyTowns = new int[nbTrains]; trainCityValue = new int[nbTrains]; trainTownValue = new int[nbTrains]; trainMaxCities = new int[nbTrains]; trainMaxCombined = new int[nbTrains]; trainVisited = new boolean[nbTrains][nbVertexes]; trainVertexStack = new int[nbTrains][nbVertexes]; trainStackPos = new int[nbTrains]; trainBottomPos = new int[nbTrains]; trainStartEdge = new int[nbTrains]; currentBestRun = new int[nbTrains][nbVertexes]; } void setVertex(int id, int value, boolean city, boolean town, int[]neighbors) { vertexValue[id] = value; vertexCity[id] = city; vertexTown[id] = town; vertexNeighbors[id] = neighbors; } void setStartVertexes(int[] startVertexes) { this.startVertexes = startVertexes; } void setEdge(int vertexLo, int vertexHi, boolean greedy, int distance) { edgeGreedy[vertexLo][vertexHi] = greedy; edgeDistance[vertexLo][vertexHi] = distance; } void setTrain(int id, int cities, int towns, boolean townsCostNothing, int multiplyCities, int multiplyTowns) { trainMaxCities[id] = cities; trainMaxCombined[id] = cities + towns; trainTownsCostNothing[id] = townsCostNothing; trainMultiplyCities[id] = multiplyCities; trainMultiplyTowns[id] = multiplyTowns; } int[][] getOptimalRun() { return currentBestRun; } int calculateRevenue(int startTrain, int finalTrain) { log.debug("RC: calculateRevenue trains from " + startTrain + " to " + finalTrain); this.finalTrain = finalTrain; runTrain(startTrain); return currentBestValue; } private void runTrain(int trainId) { log.debug("RC: startTrain " + trainId); // initialize the positions trainStackPos[trainId] = 0; trainBottomPos[trainId] = 0; // try all startVertexes for (int i=0; i < startVertexes.length; i++) { int vertexId = startVertexes[i]; log.debug("RC: Using startVertex nr. " + i + " for train " + trainId); encounterVertex(trainId, startVertexes[i], true); // and all edges of it boolean evaluateResult = true; for (int j = 0; j < maxNeighbors; j++) { int neighborId = vertexNeighbors[vertexId][j]; log.debug("RC: Testing Neighbor Nr. " + j + " of startVertex is " + neighborId); if (neighborId == -1) break; // no more neighbors if (travelEdge(vertexId, neighborId, true)) { evaluateResult = false; trainStartEdge[trainId] = j; // store edge nextVertex(trainId, neighborId, vertexId); } } // no more edges to find finalizeVertex(trainId, evaluateResult); encounterVertex(trainId, startVertexes[i], false); log.debug("RC: finished startVertex " + vertexId + " for train " +trainId); } log.debug("RC: finishTrain " + trainId); } private void startBottom(int trainId) { log.debug("RC: startBottom " +trainId); trainBottomPos[trainId] = trainStackPos[trainId]; // store the stack position where bottom starts log.debug("RC: Restart at bottom at stack position " + trainBottomPos[trainId]); // use startvertex int vertexId = trainVertexStack[trainId][0]; trainVertexStack[trainId][trainStackPos[trainId]++] = vertexId; // push to stack for (int j = trainStartEdge[trainId] + 1; j < maxNeighbors; j++) { int neighborId = vertexNeighbors[vertexId][j]; log.debug("RC: Testing Neighbor Nr. " + j + " of bottomVertex is " + neighborId); if (neighborId == -1) break; // no more neighbors if (trainVisited[trainId][neighborId]) { log.debug(" RC: Hex already visited"); continue; } if (travelEdge(vertexId, neighborId, true)) { nextVertex(trainId, neighborId, vertexId); } } // no more edges to find // finalizeVertex(trainId); trainStackPos[trainId]--; // pull from stack trainBottomPos[trainId] = 0; log.debug("RC: finished bottom of " + trainId); } /** * arrives at an unvisited vertex */ private void nextVertex(int trainId, int vertexId, int previousId) { // 1. add vertex to path and returns true if train terminated (if start = 0, otherwise it is a revisit of the start) encounterVertex(trainId, vertexId, true); boolean trainTerminated = trainTerminated(trainId); // 2a. visit neighbors, if train has not terminated boolean evaluateResult = true; if (!trainTerminated) { for (int j = 0; j < maxNeighbors; j++) { int neighborId = vertexNeighbors[vertexId][j]; log.debug("RC: Testing Neighbor Nr. " + j + " of " + vertexId + " is " + neighborId); if (neighborId == -1) break; // no more neighbors if (trainVisited[trainId][neighborId]) { log.debug("RC: Hex already visited"); continue; } if (travelEdge(vertexId, neighborId, edgeGreedy[previousId][vertexId])) { evaluateResult = false; nextVertex(trainId, neighborId, vertexId); } } // 2b. restart at startVertex for bottom part if (trainBottomPos[trainId] == 0 && (vertexCity[vertexId] || vertexTown[vertexId])){ startBottom(trainId); } } // 3. no more edges to visit from here => evaluate or start new train finalizeVertex(trainId, evaluateResult); // 4. then leave that vertex encounterVertex(trainId, vertexId, false); returnEdge(trainId); } private void encounterVertex(int trainId, int vertexId, boolean arrive) { log.debug("RC: EncounterVertex, trainId = " + trainId + " vertexId = " + vertexId + " arrive = " + arrive); // set visit to true if arriving, otherwise you leave trainVisited[trainId][vertexId] = arrive; if (arrive) { if (vertexCity[vertexId]) { trainCities[trainId]++; trainCityValue[trainId] += vertexValue[vertexId]; } else if (vertexTown[vertexId]) { trainTowns[trainId]++; trainTownValue[trainId] += vertexValue[vertexId]; } trainVertexStack[trainId][trainStackPos[trainId]++] = vertexId; // push to stack countVisits++; } else { if (vertexCity[vertexId]) { trainCities[trainId]--; trainCityValue[trainId] -= vertexValue[vertexId]; } else if (vertexTown[vertexId]) { trainTowns[trainId]--; trainTownValue[trainId] -= vertexValue[vertexId]; } trainStackPos[trainId]--; // pull from stack countVisits--; } log.debug("RC: Count Visits = " + countVisits); } private boolean travelEdge(int startVertex, int endVertex, boolean previousGreedy) { if (edgeUsed[startVertex][endVertex]) { log.debug("RC: Edge from " + startVertex + " to " + endVertex + " already used" ); return false; } else if (previousGreedy || edgeGreedy[startVertex][endVertex]) { log.debug("RC: Travel edge from " + startVertex + " to " + endVertex ); edgeUsed[startVertex][endVertex] = true; edgeUsed[endVertex][startVertex] = true; countEdges++; log.debug("RC: Count Edges = " + countEdges); return true; } else { log.debug("RC: Cannot travel from " + startVertex + " to " + endVertex + ", because of greedy rule"); return false; } } private void returnEdge(int trainId) { int stackPos = trainStackPos[trainId]; log.debug("RC: Tries to clear edge at stack position " + stackPos + " of train " + trainId); if (stackPos == 0) { log.debug("RC: Position zero has not to be cleared"); return; } if (stackPos == trainBottomPos[trainId]) { log.debug("RC: Replace start Vertex for bottom position"); } int startVertex = trainVertexStack[trainId][stackPos]; int endVertex = trainVertexStack[trainId][stackPos - 1]; if (edgeUsed[startVertex][endVertex]) { edgeUsed[startVertex][endVertex] = false; edgeUsed[endVertex][startVertex] = false; countEdges--; log.debug("RC: Cleared edge from " + startVertex + " to " + endVertex); log.debug("RC: Count Edges = " + countEdges); } else { log.debug ("RC: Error return edge not used: " + startVertex + " to " + endVertex); } } private boolean trainTerminated(int trainId) { boolean terminated; if (trainTownsCostNothing[trainId]) { terminated = trainCities[trainId] == trainMaxCities[trainId]; } else { terminated = trainCities[trainId] > trainMaxCities[trainId] || (trainCities[trainId] + trainTowns[trainId] == trainMaxCombined[trainId]); } if (terminated) { log.debug ("RC: Train " + trainId + " has terminated: " + "cities = " + trainCities[trainId] + " towns = " + trainTowns[trainId] + "maxCities = " + trainMaxCities[trainId] + "maxCombined = " + trainMaxCombined[trainId]); } return terminated; } private void finalizeVertex(int trainId, boolean evaluate) { log.debug("RC: No more edges found for " + trainId); if (trainId == finalTrain) { if (evaluate) evaluateResults(); } else { runTrain(trainId + 1); } } private void evaluateResults() { // sum to total value int totalValue = 0; for (int j = 0; j <= finalTrain; j++) { int trainValue; if (trainCities[j] + trainTowns[j] <= 1) { trainValue = 0; // one station is not enough } else { trainValue = trainCityValue[j] * trainMultiplyCities[j] + trainTownValue[j] * trainMultiplyTowns[j]; } totalValue += trainValue; log.debug("RC: Train " + j + " has value of " + trainValue); } // compare to current best result if (totalValue > currentBestValue) { currentBestValue = totalValue; // exceed thus deep copy of vertex stack for (int j = 0; j <= finalTrain; j++) for (int v = 0; v < nbVertexes; v++) if (v < trainStackPos[j]) currentBestRun[j][v] = trainVertexStack[j][v]; else { currentBestRun[j][v] = -1; // terminator break; } log.info("RC: Found better run with " + totalValue); } } @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("vertexValue:" + Arrays.toString(vertexValue)); buffer.append("vertexCity:" + Arrays.toString(vertexCity)); buffer.append("vertexTown:" + Arrays.toString(vertexTown)); buffer.append("vertexEdges:" + Arrays.deepToString(vertexNeighbors)); buffer.append("edgeGreedy:" + Arrays.deepToString(edgeGreedy)); buffer.append("edgeDistance:" + Arrays.deepToString(edgeDistance)); buffer.append("startVertexes:" + Arrays.toString(startVertexes)); return buffer.toString(); } } Index: NetworkIterator.java =================================================================== RCS file: /cvsroot/rails/18xx/rails/algorithms/NetworkIterator.java,v retrieving revision 1.2 retrieving revision 1.3 diff -C2 -d -r1.2 -r1.3 *** NetworkIterator.java 9 Apr 2010 17:49:31 -0000 1.2 --- NetworkIterator.java 12 Apr 2010 17:37:32 -0000 1.3 *************** *** 185,189 **** for (NetworkEdge edge : graph.edgesOf(vertex)) { ! if (previousColor == VisitColor.WHITE || edge.isAutoEdge()) { NetworkVertex oppositeV = Graphs.getOppositeVertex(graph, edge, vertex); --- 185,189 ---- for (NetworkEdge edge : graph.edgesOf(vertex)) { ! if (previousColor == VisitColor.WHITE || edge.isGreedy()) { NetworkVertex oppositeV = Graphs.getOppositeVertex(graph, edge, vertex); *************** *** 207,211 **** private void encounterVertex(NetworkVertex vertex, NetworkEdge edge) { VisitColor color = VisitColor.WHITE; ! if (vertex.isSide() && !edge.isAutoEdge()) color = VisitColor.YELLOW; putSeenData(vertex, color); --- 207,211 ---- private void encounterVertex(NetworkVertex vertex, NetworkEdge edge) { VisitColor color = VisitColor.WHITE; ! if (vertex.isSide() && !edge.isGreedy()) color = VisitColor.YELLOW; putSeenData(vertex, color); Index: NetworkVertex.java =================================================================== RCS file: /cvsroot/rails/18xx/rails/algorithms/NetworkVertex.java,v retrieving revision 1.3 retrieving revision 1.4 diff -C2 -d -r1.3 -r1.4 *** NetworkVertex.java 9 Apr 2010 17:49:31 -0000 1.3 --- NetworkVertex.java 12 Apr 2010 17:37:32 -0000 1.4 *************** *** 8,11 **** --- 8,12 ---- import rails.game.City; import rails.game.MapHex; + import rails.game.PhaseI; import rails.game.PublicCompanyI; import rails.game.Station; *************** *** 97,100 **** --- 98,114 ---- } + public boolean isTownType(){ + return isStation() && station.getType().equals(Station.TOWN); + } + + public boolean isCityType(){ + return isStation() && + (station.getType().equals(Station.CITY) || station.getType().equals(Station.OFF_MAP_AREA)); + } + + public boolean isOffBoardType() { + return isStation() && station.getType().equals(Station.OFF_MAP_AREA); + } + public MapHex getHex(){ return hex; *************** *** 108,111 **** --- 122,136 ---- return side; } + + public int getValue(PhaseI phase){ + if (isOffBoardType()) { + return hex.getCurrentOffBoardValue(phase); + } else if (isStation()) { + return station.getValue(); + } else { + return 0; + } + } + /** * Checks if a vertex is fully tokened --- NEW FILE: RevenueAdapter.java --- package rails.algorithms; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jgrapht.Graph; import org.jgrapht.Graphs; import rails.game.PhaseI; import rails.game.PublicCompanyI; import rails.game.TrainI; public class RevenueAdapter { private Graph<NetworkVertex, NetworkEdge> graph; private RevenueCalculator rc; private int maxNeighbors; private List<NetworkVertex> vertexes; private List<NetworkEdge> edges; private List<NetworkTrain> trains; public RevenueAdapter(Graph<NetworkVertex, NetworkEdge> graph){ this.graph = graph; this.vertexes = new ArrayList<NetworkVertex>(graph.vertexSet()); this.edges = new ArrayList<NetworkEdge>(graph.edgeSet()); this.trains = new ArrayList<NetworkTrain>(); } public void initRevenueCalculator(){ if (rc == null) { // define the maximum number of vertexes maxNeighbors = 0; for (NetworkVertex vertex:vertexes) if (!vertex.isHQ()) maxNeighbors = Math.max(maxNeighbors, graph.edgesOf(vertex).size()); this.rc = new RevenueCalculator(vertexes.size(), ++maxNeighbors, trains.size()); // increase maxEdges to allow for cutoff } } public void refreshRevenueCalculator() { rc = null; } public void populateRevenueCalculator(PublicCompanyI company, PhaseI phase){ if (rc == null) initRevenueCalculator(); // set vertexes for (int id=0; id < vertexes.size(); id++){ NetworkVertex v = vertexes.get(id); if (v.isHQ()) { // HQ is not added to list, but used to assign startVertexes List<NetworkVertex> hqNeighbors = Graphs.neighborListOf(graph, v); int[] startVertexes = new int[hqNeighbors.size()]; for (int j=0; j < hqNeighbors.size(); j++) { startVertexes[j] = vertexes.lastIndexOf(hqNeighbors.get(j)); } rc.setStartVertexes(startVertexes); } else { // prepare values int value = v.getValue(phase); boolean city = v.isCityType(); boolean town = v.isTownType(); int j = 0, e[] = new int[maxNeighbors]; if (v.canCompanyRunThrough(company)) { for (NetworkVertex n:Graphs.neighborListOf(graph, v)){ if (!n.isHQ()) { e[j++] = vertexes.lastIndexOf(n); } } } e[j] = -1; // stop rc.setVertex(id, value, city, town, e); } } // set edges for (int id=0; id < edges.size(); id++) { // prepare values NetworkEdge e = edges.get(id); int vA = vertexes.lastIndexOf(e.getSource()); int vB = vertexes.lastIndexOf(e.getTarget()); boolean greedy = e.isGreedy(); int distance = e.getDistance(); rc.setEdge(vA, vB, greedy, distance); rc.setEdge(vB, vA, greedy, distance); } // set trains for (int id=0; id < trains.size(); id++) { NetworkTrain train = trains.get(id); train.addToRevenueCalculator(rc, id); } } public void addDefaultTrain(int cities) { String trainName = Integer.valueOf(cities).toString(); NetworkTrain train =new NetworkTrain(cities, 0, false, 1, 1, trainName); trains.add(train); } public void addTrain(TrainI railsTrain){ int cities = railsTrain.getMajorStops(); int towns = railsTrain.getMinorStops(); boolean townsCostNothing = (railsTrain.getTownCountIndicator() == 0); int multiplyCities = railsTrain.getCityScoreFactor(); int multiplyTowns = railsTrain.getTownScoreFactor(); String trainName = railsTrain.getName(); NetworkTrain networkTrain = new NetworkTrain(cities, towns, townsCostNothing, multiplyCities, multiplyTowns, trainName); trains.add(networkTrain); } public void addTrainByString(String trainString) { String t = trainString.trim(); int cities = 0; int towns = 0; boolean townsCostNothing = false; int multiplyCities = 1; int multiplyTowns = 1; if (t.equals("D")) { cities = 99; // diesel } else if (t.contains("+")) { cities = Integer.parseInt(t.split("\\+")[0]); // + train towns = Integer.parseInt(t.split("\\+")[1]); } else if (t.contains("E")) { // express train cities = Integer.parseInt(t.replace("E", "")); townsCostNothing = true; multiplyTowns = 0; } else if (t.contains("D")) { // double (express) train cities = Integer.parseInt(t.replace("D", "")); townsCostNothing = true; multiplyCities = 2; multiplyTowns = 0; } else { // default train cities = Integer.parseInt(t); } NetworkTrain networkTrain = new NetworkTrain(cities, towns, townsCostNothing, multiplyCities, multiplyTowns, t); trains.add(networkTrain); } public Map<NetworkTrain, List<NetworkVertex>> getOptimalRun() { int[][] optimalRunRaw = rc.getOptimalRun(); Map<NetworkTrain, List<NetworkVertex>> optimalRun = new HashMap<NetworkTrain, List<NetworkVertex>>(); for (int j=0; j < optimalRunRaw.length; j++) { List<NetworkVertex> runList = new ArrayList<NetworkVertex>(); for (int v=0; v < optimalRunRaw[j].length; v++) { int vertexId = optimalRunRaw[j][v]; if (vertexId == -1) break; runList.add(vertexes.get(vertexId)); } optimalRun.put(trains.get(j), runList); } return optimalRun; } public int calculateRevenue() { return calculateRevenue(0, trains.size() - 1); } public int calculateRevenue(int startTrain, int finalTrain) { if (startTrain < 0 || finalTrain >= trains.size() || startTrain > finalTrain) return -1; return rc.calculateRevenue(startTrain, finalTrain); } @Override public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("RevenueCalculator:" + rc); buffer.append("Vertexes:" + vertexes); buffer.append("Edges:" + edges); return buffer.toString(); } } Index: NetworkGraphBuilder.java =================================================================== RCS file: /cvsroot/rails/18xx/rails/algorithms/NetworkGraphBuilder.java,v retrieving revision 1.4 retrieving revision 1.5 diff -C2 -d -r1.4 -r1.5 *** NetworkGraphBuilder.java 9 Apr 2010 17:49:31 -0000 1.4 --- NetworkGraphBuilder.java 12 Apr 2010 17:37:32 -0000 1.5 *************** *** 254,260 **** NetworkVertex firstVertex = Graphs.getOppositeVertex(graph, edges[0], vertex); NetworkVertex secondVertex = Graphs.getOppositeVertex(graph, edges[1], vertex); ! boolean autoEdge = edges[0].isAutoEdge() || edges[1].isAutoEdge(); graph.addEdge(firstVertex, secondVertex, ! new NetworkEdge(firstVertex, secondVertex, autoEdge)); // remove vertex graph.removeVertex(vertex); --- 254,261 ---- NetworkVertex firstVertex = Graphs.getOppositeVertex(graph, edges[0], vertex); NetworkVertex secondVertex = Graphs.getOppositeVertex(graph, edges[1], vertex); ! boolean greedy = edges[0].isGreedy() || edges[1].isGreedy(); ! int distance = edges[0].getDistance() + edges[1].getDistance(); graph.addEdge(firstVertex, secondVertex, ! new NetworkEdge(firstVertex, secondVertex, greedy, distance)); // remove vertex graph.removeVertex(vertex); Index: NetworkEdge.java =================================================================== RCS file: /cvsroot/rails/18xx/rails/algorithms/NetworkEdge.java,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** NetworkEdge.java 4 Apr 2010 22:02:53 -0000 1.1 --- NetworkEdge.java 12 Apr 2010 17:37:32 -0000 1.2 *************** *** 7,16 **** private final NetworkVertex target; ! private final boolean autoEdge; ! public NetworkEdge(NetworkVertex source, NetworkVertex target, boolean autoEdge) { this.source = source; this.target = target; ! this.autoEdge = autoEdge; } --- 7,29 ---- private final NetworkVertex target; ! private final boolean greedy; ! private final int distance; ! ! public NetworkEdge(NetworkVertex source, NetworkVertex target, boolean greedy) { this.source = source; this.target = target; ! this.greedy = greedy; ! if (greedy) ! this.distance = 1; ! else ! this.distance = 0; ! } ! ! public NetworkEdge(NetworkVertex source, NetworkVertex target, boolean greedy, int distance) { ! this.source = source; ! this.target = target; ! this.greedy = greedy; ! this.distance = distance; } *************** *** 23,30 **** } ! public boolean isAutoEdge() { ! return autoEdge; } public String getConnection() { return source + " - >" + target; --- 36,47 ---- } ! public boolean isGreedy() { ! return greedy; } + public int getDistance() { + return distance; + } + public String getConnection() { return source + " - >" + target; *************** *** 34,41 **** // set to "" to faciltate visual graph public String toString() { ! if (!autoEdge) ! return "***"; else ! return ""; } } --- 51,59 ---- // set to "" to faciltate visual graph public String toString() { ! if (!greedy) ! return "*** / " + distance; else ! return "" + distance; } + } --- NEW FILE: NetworkTrain.java --- package rails.algorithms; public final class NetworkTrain { private final int cities; private final int towns; private final boolean townsCostNothing; private final int multiplyCities; private final int multiplyTowns; private final String trainName; NetworkTrain(int cities, int towns, boolean townsCostNothing, int multiplyCities, int multiplyTowns, String trainName) { this.cities = cities; this.towns = towns; this.townsCostNothing = townsCostNothing; this.multiplyCities = multiplyCities; this.multiplyTowns = multiplyTowns; this.trainName = trainName; } void addToRevenueCalculator(RevenueCalculator rc, int trainId) { rc.setTrain(trainId, cities, towns, townsCostNothing, multiplyCities, multiplyTowns); } public String toString() { return trainName; } } |