From: Stefan F. <ste...@us...> - 2011-07-23 07:37:24
|
rails/game/GameManager.java | 23 +++++++++++++++++++++-- rails/game/GameManagerI.java | 28 +++++++++++++++++++++++++++- rails/game/Token.java | 12 ++++++------ rails/game/special/SpecialProperty.java | 9 ++------- 4 files changed, 56 insertions(+), 16 deletions(-) New commits: commit c9b5d4d6a3884eddf0546b69883e1a2c34bd1966 Author: Stefan Frey <ste...@we...> Date: Sat Jul 23 09:15:29 2011 +0200 Removed static class variables and moved that to a centralize storage in GameManager diff --git a/rails/game/GameManager.java b/rails/game/GameManager.java index 3e16978..56c72ad 100644 --- a/rails/game/GameManager.java +++ b/rails/game/GameManager.java @@ -67,7 +67,7 @@ public class GameManager implements ConfigurableComponentI, GameManagerI { // map of correctionManagers protected Map<CorrectionType, CorrectionManagerI> correctionManagers = new HashMap<CorrectionType, CorrectionManagerI>(); - + protected String gameName; protected Map<String, String> gameOptions; @@ -223,6 +223,11 @@ public class GameManager implements ConfigurableComponentI, GameManagerI { */ protected GameDef.OrStep skippedStep = null; + // storage to replace static class variables + // TODO: Move that to a better place + protected Map<Integer, Object> objectStorage = new HashMap<Integer, Object>(); + protected int storageId = 0; + protected static Logger log = Logger.getLogger(GameManager.class.getPackage().getName()); @@ -1866,6 +1871,20 @@ public class GameManager implements ConfigurableComponentI, GameManagerI { return players.get(0); } - + + public void resetStorage() { + objectStorage = new HashMap<Integer, Object>(); + storageId = 0; + } + + public int storeObject(Object object) { + objectStorage.put(storageId++, object); + return storageId; + } + + public Object retrieveObject(int id) { + return objectStorage.get(id); + } + } diff --git a/rails/game/GameManagerI.java b/rails/game/GameManagerI.java index 8555a07..e95c71f 100644 --- a/rails/game/GameManagerI.java +++ b/rails/game/GameManagerI.java @@ -215,5 +215,31 @@ public interface GameManagerI extends MoveableHolder, ConfigurableComponentI { public void setSkipDone (GameDef.OrStep step); public Player reorderPlayersByCash(boolean high); - //public void reorderPlayersByCash(boolean high); + + /** + * reset the storage for other elements like tokens, special property + * that a referred by unique ids + * TODO + */ + public void resetStorage(); + + /** + * store element in storage + * @param object to store + * @return unique id of the object in the storage + * TODO move to a better place + */ + public int storeObject(Object object); + + /** + * ask storage for object + * @param identifier in storage + * @return object stored under the id (null if none is stored) + * TODO move to a better place + */ + public Object retrieveObject(int id); + + + + } \ No newline at end of file diff --git a/rails/game/Token.java b/rails/game/Token.java index 97b19d1..8d21a70 100644 --- a/rails/game/Token.java +++ b/rails/game/Token.java @@ -21,20 +21,20 @@ public abstract class Token implements TokenI { protected TokenHolder holder = null; protected String description = ""; protected String uniqueId; - - private static Map<String, TokenI> tokenMap = new HashMap<String, TokenI>(); - private static int index = 0; + + // TODO: storing id in String is for legacy reasons + protected static String ID_PREFIX = "Token_"; protected static Logger log = Logger.getLogger(Token.class.getPackage().getName()); public Token() { - uniqueId = "Token_" + (index++); - tokenMap.put(uniqueId, this); + uniqueId = ID_PREFIX + GameManager.getInstance().storeObject(this); } public static TokenI getByUniqueId(String id) { - return tokenMap.get(id); + int i = Integer.valueOf(id.replace(ID_PREFIX, "")); + return (Token)GameManager.getInstance().retrieveObject(i); } public String getUniqueId() { diff --git a/rails/game/special/SpecialProperty.java b/rails/game/special/SpecialProperty.java index 0daeb23..a165f16 100644 --- a/rails/game/special/SpecialProperty.java +++ b/rails/game/special/SpecialProperty.java @@ -54,17 +54,12 @@ public abstract class SpecialProperty implements SpecialPropertyI { /** To give subclasses access to the various 'managers' */ protected GameManagerI gameManager; - protected static Map<Integer, SpecialPropertyI> spMap = new HashMap<Integer, SpecialPropertyI>(); - - protected static int lastIndex = 0; - protected static Logger log = Logger.getLogger(SpecialProperty.class.getPackage().getName()); public SpecialProperty() { - uniqueId = ++lastIndex; - spMap.put(uniqueId, this); gameManager = GameManager.getInstance(); + uniqueId = gameManager.storeObject(this); } public void configureFromXML(Tag tag) throws ConfigurationException { @@ -113,7 +108,7 @@ public abstract class SpecialProperty implements SpecialPropertyI { } public static SpecialPropertyI getByUniqueId(int i) { - return spMap.get(i); + return (SpecialPropertyI)GameManager.getInstance().retrieveObject(i); } public void setCompany(CompanyI company) { |
From: Stefan F. <ste...@us...> - 2011-07-23 08:14:13
|
rails/game/GameManager.java | 24 ++++++++++++++---------- rails/game/GameManagerI.java | 6 ++++-- rails/game/Token.java | 8 ++++---- rails/game/special/SpecialProperty.java | 6 ++++-- 4 files changed, 26 insertions(+), 18 deletions(-) New commits: commit d4b5d377e9240fc5bd3dd241fe29ccf9da0c1608 Author: Stefan Frey <ste...@we...> Date: Sat Jul 23 10:16:07 2011 +0200 Fixed problem in new storage mechanism diff --git a/rails/game/GameManager.java b/rails/game/GameManager.java index 56c72ad..77b2118 100644 --- a/rails/game/GameManager.java +++ b/rails/game/GameManager.java @@ -225,9 +225,9 @@ public class GameManager implements ConfigurableComponentI, GameManagerI { // storage to replace static class variables // TODO: Move that to a better place - protected Map<Integer, Object> objectStorage = new HashMap<Integer, Object>(); - protected int storageId = 0; - + protected Map<String, Object> objectStorage = new HashMap<String, Object>(); + protected Map<String, Integer> storageIds = new HashMap<String, Integer>(); + protected static Logger log = Logger.getLogger(GameManager.class.getPackage().getName()); @@ -1873,17 +1873,21 @@ public class GameManager implements ConfigurableComponentI, GameManagerI { } public void resetStorage() { - objectStorage = new HashMap<Integer, Object>(); - storageId = 0; + objectStorage = new HashMap<String, Object>(); + storageIds = new HashMap<String, Integer>(); } - public int storeObject(Object object) { - objectStorage.put(storageId++, object); - return storageId; + public int storeObject(String typeName, Object object) { + Integer id = storageIds.get(typeName); + if (id == null) id = 0; + objectStorage.put(typeName + id, object); + storageIds.put(typeName, id + 1); // store next id + log.debug("Stores " + typeName + " + on id " + id); + return id; } - public Object retrieveObject(int id) { - return objectStorage.get(id); + public Object retrieveObject(String typeName, int id) { + return objectStorage.get(typeName + id); } } diff --git a/rails/game/GameManagerI.java b/rails/game/GameManagerI.java index e95c71f..e9d5936 100644 --- a/rails/game/GameManagerI.java +++ b/rails/game/GameManagerI.java @@ -225,19 +225,21 @@ public interface GameManagerI extends MoveableHolder, ConfigurableComponentI { /** * store element in storage + * @param name to identify the type of the object to retrieve * @param object to store * @return unique id of the object in the storage * TODO move to a better place */ - public int storeObject(Object object); + public int storeObject(String typeName, Object object); /** * ask storage for object + * @param name to identify the type of the object to retrieve * @param identifier in storage * @return object stored under the id (null if none is stored) * TODO move to a better place */ - public Object retrieveObject(int id); + public Object retrieveObject(String typeName, int id); diff --git a/rails/game/Token.java b/rails/game/Token.java index 8d21a70..ee38386 100644 --- a/rails/game/Token.java +++ b/rails/game/Token.java @@ -23,18 +23,18 @@ public abstract class Token implements TokenI { protected String uniqueId; // TODO: storing id in String is for legacy reasons - protected static String ID_PREFIX = "Token_"; + protected static String STORAGE_NAME = "Token"; protected static Logger log = Logger.getLogger(Token.class.getPackage().getName()); public Token() { - uniqueId = ID_PREFIX + GameManager.getInstance().storeObject(this); + uniqueId = STORAGE_NAME + "_" + GameManager.getInstance().storeObject(STORAGE_NAME, this); } public static TokenI getByUniqueId(String id) { - int i = Integer.valueOf(id.replace(ID_PREFIX, "")); - return (Token)GameManager.getInstance().retrieveObject(i); + int i = Integer.valueOf(id.replace(STORAGE_NAME + "_", "")); + return (Token)GameManager.getInstance().retrieveObject(STORAGE_NAME, i); } public String getUniqueId() { diff --git a/rails/game/special/SpecialProperty.java b/rails/game/special/SpecialProperty.java index a165f16..8994da7 100644 --- a/rails/game/special/SpecialProperty.java +++ b/rails/game/special/SpecialProperty.java @@ -50,6 +50,8 @@ public abstract class SpecialProperty implements SpecialPropertyI { protected String description = ""; protected int uniqueId; + + protected static final String STORAGE_NAME = "SpecialProperty"; /** To give subclasses access to the various 'managers' */ protected GameManagerI gameManager; @@ -59,7 +61,7 @@ public abstract class SpecialProperty implements SpecialPropertyI { public SpecialProperty() { gameManager = GameManager.getInstance(); - uniqueId = gameManager.storeObject(this); + uniqueId = gameManager.storeObject(STORAGE_NAME, this); } public void configureFromXML(Tag tag) throws ConfigurationException { @@ -108,7 +110,7 @@ public abstract class SpecialProperty implements SpecialPropertyI { } public static SpecialPropertyI getByUniqueId(int i) { - return (SpecialPropertyI)GameManager.getInstance().retrieveObject(i); + return (SpecialPropertyI)GameManager.getInstance().retrieveObject(STORAGE_NAME, i); } public void setCompany(CompanyI company) { |
From: Stefan F. <ste...@us...> - 2011-07-23 08:39:13
|
rails/game/GameManager.java | 1 - rails/game/special/SpecialProperty.java | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) New commits: commit 8b9df8fe03b3dbfbda264eaa0db322c600b325bb Author: Stefan Frey <ste...@we...> Date: Sat Jul 23 10:41:11 2011 +0200 Fixed problem of unique ids related to Special Properties (they start at one instead of zero) diff --git a/rails/game/GameManager.java b/rails/game/GameManager.java index 77b2118..7c5a11a 100644 --- a/rails/game/GameManager.java +++ b/rails/game/GameManager.java @@ -1882,7 +1882,6 @@ public class GameManager implements ConfigurableComponentI, GameManagerI { if (id == null) id = 0; objectStorage.put(typeName + id, object); storageIds.put(typeName, id + 1); // store next id - log.debug("Stores " + typeName + " + on id " + id); return id; } diff --git a/rails/game/special/SpecialProperty.java b/rails/game/special/SpecialProperty.java index 8994da7..917b510 100644 --- a/rails/game/special/SpecialProperty.java +++ b/rails/game/special/SpecialProperty.java @@ -61,7 +61,9 @@ public abstract class SpecialProperty implements SpecialPropertyI { public SpecialProperty() { gameManager = GameManager.getInstance(); - uniqueId = gameManager.storeObject(STORAGE_NAME, this); + uniqueId = gameManager.storeObject(STORAGE_NAME, this) + 1; + // increase unique id to allow loading old save files (which increase by 1) + // TODO: remove that legacy issue } public void configureFromXML(Tag tag) throws ConfigurationException { @@ -109,8 +111,11 @@ public abstract class SpecialProperty implements SpecialPropertyI { return uniqueId; } - public static SpecialPropertyI getByUniqueId(int i) { - return (SpecialPropertyI)GameManager.getInstance().retrieveObject(STORAGE_NAME, i); + public static SpecialPropertyI getByUniqueId(int id) { + id -= 1; + // decrease retrieval id to allow loading old save files (which increase by 1) + // TODO: remove that legacy issue + return (SpecialPropertyI)GameManager.getInstance().retrieveObject(STORAGE_NAME, id); } public void setCompany(CompanyI company) { |
From: Stefan F. <ste...@us...> - 2011-08-11 10:00:38
|
rails/game/specific/_18TN/PublicCompany_18TN.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) New commits: commit 65f7ed334d271a38a138aeaa2e25ca5c36b9d759 Author: Stefan Frey <ste...@we...> Date: Thu Aug 11 12:02:39 2011 +0200 Fixed bug in 18TN modifier diff --git a/rails/game/specific/_18TN/PublicCompany_18TN.java b/rails/game/specific/_18TN/PublicCompany_18TN.java index b6fe19d..8b3869e 100644 --- a/rails/game/specific/_18TN/PublicCompany_18TN.java +++ b/rails/game/specific/_18TN/PublicCompany_18TN.java @@ -54,6 +54,9 @@ public class PublicCompany_18TN extends PublicCompany implements RevenueStaticMo */ public boolean modifyCalculator(RevenueAdapter revenueAdapter) { + // check first if it is the company for the revenue calculation + if (revenueAdapter.getCompany() != this) return false; + // check if it is civil war, otherwise no effect if (!isCivilWar()) return false; @@ -72,8 +75,5 @@ public class PublicCompany_18TN extends PublicCompany implements RevenueStaticMo public String prettyPrint(RevenueAdapter revenueAdapter) { return LocalText.getText("CivilWarActive"); } - - - } |
From: Erik V. <ev...@us...> - 2011-08-11 22:16:26
|
rails/game/OperatingRound.java | 4163 ++++++++++++++++++++--------------------- 1 file changed, 2110 insertions(+), 2053 deletions(-) New commits: commit 8d9ca907cc522935c710a8349d13a9f97a50eebf Author: Erik Vos <eri...@xs...> Date: Fri Aug 12 00:14:14 2011 +0200 Reorganized OperatingRound No code changes, but methods have been sorted into functional groups, each preceded by a header. Hopefully this class will now be easier to grasp. diff --git a/rails/game/OperatingRound.java b/rails/game/OperatingRound.java index 957203e..ec8de9c 100644 --- a/rails/game/OperatingRound.java +++ b/rails/game/OperatingRound.java @@ -3,9 +3,7 @@ package rails.game; import java.util.*; -import rails.common.DisplayBuffer; -import rails.common.GuiDef; -import rails.common.LocalText; +import rails.common.*; import rails.common.parser.GameOption; import rails.game.action.*; import rails.game.correct.ClosePrivate; @@ -82,9 +80,12 @@ public class OperatingRound extends Round implements Observer { protected TrainManager trainManager = gameManager.getTrainManager(); + /*======================================= + * 1. OR START and END + *=======================================*/ + /** * Constructor with no parameters, call the super Class (Round's) Constructor with no parameters - * */ public OperatingRound(GameManagerI gameManager) { super (gameManager); @@ -158,7 +159,56 @@ public class OperatingRound extends Round implements Observer { } - /*----- METHODS THAT PROCESS PLAYER ACTIONS -----*/ + @Override + public void resume() { + + if (savedAction instanceof BuyTrain) { + buyTrain ((BuyTrain)savedAction); + } else if (savedAction instanceof SetDividend) { + executeSetRevenueAndDividend ((SetDividend) savedAction); + } else if (savedAction instanceof RepayLoans) { + executeRepayLoans ((RepayLoans) savedAction); + } else if (savedAction == null) { + //nextStep(); + } + savedAction = null; + wasInterrupted.set(true); + + guiHints.setVisibilityHint(GuiDef.Panel.STOCK_MARKET, false); + guiHints.setVisibilityHint(GuiDef.Panel.STATUS, true); + guiHints.setActivePanel(GuiDef.Panel.MAP); + } + + protected void finishOR() { + + // Check if any privates must be closed + // (now only applies to 1856 W&SR) - no, that is at end of TURN + //for (PrivateCompanyI priv : gameManager.getAllPrivateCompanies()) { + // priv.checkClosingIfExercised(true); + //} + + ReportBuffer.add(" "); + ReportBuffer.add(LocalText.getText("EndOfOperatingRound", thisOrNumber)); + + // Update the worth increase per player + int orWorthIncrease; + for (Player player : gameManager.getPlayers()) { + player.setLastORWorthIncrease(); + orWorthIncrease = player.getLastORWorthIncrease().intValue(); + ReportBuffer.add(LocalText.getText("ORWorthIncrease", + player.getName(), + thisOrNumber, + Bank.format(orWorthIncrease))); + } + + // OR done. Inform GameManager. + finishRound(); + } + + /*======================================= + * 2. CENTRAL PROCESSING FUNCTIONS + * 2.1. PROCESS USER ACTION + *=======================================*/ @Override public boolean process(PossibleAction action) { @@ -236,10 +286,10 @@ public class OperatingRound extends Round implements Observer { } else if (selectedAction instanceof ClosePrivate) { result = executeClosePrivate((ClosePrivate)selectedAction); - + } else if (selectedAction instanceof UseSpecialProperty && ((UseSpecialProperty)selectedAction).getSpecialProperty() instanceof SpecialRight) { - + result = buyRight ((UseSpecialProperty)selectedAction); } else if (selectedAction instanceof NullAction) { @@ -275,1073 +325,1284 @@ public class OperatingRound extends Round implements Observer { return false; } - public boolean layTile(LayTile action) { + /*======================================= + * 2.2. PREPARE NEXT ACTION + *=======================================*/ - String errMsg = null; - int cost = 0; - SpecialTileLay stl = null; - boolean extra = false; + /** + * To be called after each change, to re-establish the currently allowed + * actions. (new method, intended to absorb code from several other + * methods). + * + */ + @Override + public boolean setPossibleActions() { - PublicCompanyI company = action.getCompany(); - String companyName = company.getName(); - TileI tile = action.getLaidTile(); - MapHex hex = action.getChosenHex(); - int orientation = action.getOrientation(); + /* Create a new list of possible actions for the UI */ + possibleActions.clear(); + selectedAction = null; - // Dummy loop to enable a quick jump out. - while (true) { - // Checks - // Must be correct company. - if (!companyName.equals(operatingCompany.get().getName())) { - errMsg = - LocalText.getText("WrongCompany", - companyName, - operatingCompany.get().getName() ); - break; - } - // Must be correct step - if (getStep() != GameDef.OrStep.LAY_TRACK) { - errMsg = LocalText.getText("WrongActionNoTileLay"); - break; + boolean forced = false; + doneAllowed = false; // set default (fix of bug 2954654) + + if (getStep() == GameDef.OrStep.INITIAL) { + initTurn(); + if (noMapMode) { + nextStep (GameDef.OrStep.LAY_TOKEN); + } else { + initNormalTileLays(); // new: only called once per turn ? + setStep (GameDef.OrStep.LAY_TRACK); } + } - if (tile == null) break; + GameDef.OrStep step = getStep(); + if (step == GameDef.OrStep.LAY_TRACK) { - if (!getCurrentPhase().isTileColourAllowed(tile.getColourName())) { - errMsg = - LocalText.getText("TileNotYetAvailable", - tile.getExternalId()); - break; - } - if (tile.countFreeTiles() == 0) { - errMsg = - LocalText.getText("TileNotAvailable", - tile.getExternalId()); - break; + if (!operatingCompany.get().hasLaidHomeBaseTokens()) { + // This can occur if the home hex has two cities and track, + // such as the green OO tile #59 + possibleActions.add(new LayBaseToken (operatingCompany.get().getHomeHexes())); + forced = true; + } else { + possibleActions.addAll(getNormalTileLays(true)); + possibleActions.addAll(getSpecialTileLays(true)); + possibleActions.add(new NullAction(NullAction.SKIP)); } - /* - * Check if the current tile is allowed via the LayTile allowance. - * (currently the set if tiles is always null, which means that this - * check is redundant. This may change in the future. - */ - if (action != null) { - List<TileI> tiles = action.getTiles(); - if (tiles != null && !tiles.isEmpty() && !tiles.contains(tile)) { - errMsg = - LocalText.getText( - "TileMayNotBeLaidInHex", - tile.getExternalId(), - hex.getName() ); - break; - } - stl = action.getSpecialProperty(); - if (stl != null) extra = stl.isExtra(); - } + } else if (step == GameDef.OrStep.LAY_TOKEN) { + setNormalTokenLays(); + setSpecialTokenLays(); + log.debug("Normal token lays: " + currentNormalTokenLays.size()); + log.debug("Special token lays: " + currentSpecialTokenLays.size()); - /* - * If this counts as a normal tile lay, check if the allowed number - * of normal tile lays is not exceeded. - */ - if (!extra && !validateNormalTileLay(tile)) { - errMsg = - LocalText.getText("NumberOfNormalTileLaysExceeded", - tile.getColourName()); - break; - } + possibleActions.addAll(currentNormalTokenLays); + possibleActions.addAll(currentSpecialTokenLays); + possibleActions.add(new NullAction(NullAction.SKIP)); + } else if (step == GameDef.OrStep.CALC_REVENUE) { + prepareRevenueAndDividendAction(); + if (noMapMode) + prepareNoMapActions(); + } else if (step == GameDef.OrStep.BUY_TRAIN) { + setBuyableTrains(); + // TODO Need route checking here. + // TEMPORARILY allow not buying a train if none owned + //if (!operatingCompany.getObject().mustOwnATrain() + // || operatingCompany.getObject().getPortfolio().getNumberOfTrains() > 0) { + doneAllowed = true; + //} + if (noMapMode && (operatingCompany.get().getLastRevenue() == 0)) + prepareNoMapActions(); - // Sort out cost - if (stl != null && stl.isFree()) { - cost = 0; - } else { - cost = hex.getTileCost(); - } + } else if (step == GameDef.OrStep.DISCARD_TRAINS) { - // Amount must be non-negative multiple of 10 - if (cost < 0) { - errMsg = - LocalText.getText("NegativeAmountNotAllowed", - Bank.format(cost)); - break; - } - if (cost % 10 != 0) { - errMsg = - LocalText.getText("AmountMustBeMultipleOf10", - Bank.format(cost)); - break; - } - // Does the company have the money? - if (cost > operatingCompany.get().getCash()) { - errMsg = - LocalText.getText("NotEnoughMoney", - companyName, - Bank.format(operatingCompany.get().getCash()), - Bank.format(cost) ); - break; - } - break; - } - if (errMsg != null) { - DisplayBuffer.add(LocalText.getText("CannotLayTileOn", - companyName, - tile.getExternalId(), - hex.getName(), - Bank.format(cost), - errMsg )); - return false; + forced = true; + setTrainsToDiscard(); } - /* End of validation, start of execution */ - moveStack.start(true); + // The following additional "common" actions are only available if the + // primary action is not forced. + if (!forced) { - if (tile != null) { - if (cost > 0) - new CashMove(operatingCompany.get(), bank, cost); - operatingCompany.get().layTile(hex, tile, orientation, cost); + setBonusTokenLays(); - if (cost == 0) { - ReportBuffer.add(LocalText.getText("LaysTileAt", - companyName, - tile.getExternalId(), - hex.getName(), - hex.getOrientationName(orientation))); - } else { - ReportBuffer.add(LocalText.getText("LaysTileAtFor", - companyName, - tile.getExternalId(), - hex.getName(), - hex.getOrientationName(orientation), - Bank.format(cost) )); - } - hex.upgrade(action); + setDestinationActions(); - // Was a special property used? - if (stl != null) { - stl.setExercised(); - //currentSpecialTileLays.remove(action); - log.debug("This was a special tile lay, " - + (extra ? "" : " not") + " extra"); + setGameSpecificPossibleActions(); + // Private Company manually closure + for (PrivateCompanyI priv: companyManager.getAllPrivateCompanies()) { + if (!priv.isClosed() && priv.closesManually()) + possibleActions.add(new ClosePrivate(priv)); } - if (!extra) { - log.debug("This was a normal tile lay"); - registerNormalTileLay(tile); - } - } - if (tile == null || !areTileLaysPossible()) { - nextStep(); - } - - return true; - } + // Can private companies be bought? + if (isPrivateSellingAllowed()) { - protected boolean validateNormalTileLay(TileI tile) { - return checkNormalTileLay(tile, false); - } + // Create a list of players with the current one in front + int currentPlayerIndex = operatingCompany.get().getPresident().getIndex(); + Player player; + int minPrice, maxPrice; + List<Player> players = getPlayers(); + int numberOfPlayers = getNumberOfPlayers(); + for (int i = currentPlayerIndex; i < currentPlayerIndex + + numberOfPlayers; i++) { + player = players.get(i % numberOfPlayers); + if (!maySellPrivate(player)) continue; + for (PrivateCompanyI privComp : player.getPortfolio().getPrivateCompanies()) { - protected void registerNormalTileLay(TileI tile) { - checkNormalTileLay(tile, true); - } + // check to see if the private can be sold to a company + if (!privComp.tradeableToCompany()) { + continue; + } - protected boolean checkNormalTileLay(TileI tile, boolean update) { + minPrice = getPrivateMinimumPrice (privComp); - // Unspecified tile (e.g. 1889 D private, which is free on mountains) - if (tile == null) { - return !tileLaysPerColour.isEmpty(); - } - - String colour = tile.getColourName(); - Integer oldAllowedNumberObject = tileLaysPerColour.get(colour); + maxPrice = getPrivateMaximumPrice (privComp); - if (oldAllowedNumberObject == null) return false; + possibleActions.add(new BuyPrivate(privComp, minPrice, + maxPrice)); + } + } + } - int oldAllowedNumber = oldAllowedNumberObject.intValue(); - if (oldAllowedNumber <= 0) return false; + if (operatingCompany.get().canUseSpecialProperties()) { - if (update) updateAllowedTileColours(colour, oldAllowedNumber); - return true; - } + // Are there any "common" special properties, + // i.e. properties that are available to everyone? + List<SpecialPropertyI> commonSP = gameManager.getCommonSpecialProperties(); + if (commonSP != null) { + SellBonusToken sbt; + loop: for (SpecialPropertyI sp : commonSP) { + if (sp instanceof SellBonusToken) { + sbt = (SellBonusToken) sp; + // Can't buy if already owned + if (operatingCompany.get().getBonuses() != null) { + for (Bonus bonus : operatingCompany.get().getBonuses()) { + if (bonus.getName().equals(sp.getName())) continue loop; + } + } + possibleActions.add (new BuyBonusToken (sbt)); + } + } + } - /* - * We will assume that in all cases the following assertions hold: 1. If - * the allowed number for the colour of the just laid tile reaches zero, - * all normal tile lays have been consumed. 2. If any colour is laid, no - * different colours may be laid. THIS MAY NOT BE TRUE FOR ALL GAMES! - */ + // Are there other step-independent special properties owned by the company? + List<SpecialPropertyI> orsps = operatingCompany.get().getPortfolio().getAllSpecialProperties(); + List<SpecialPropertyI> compsps = operatingCompany.get().getSpecialProperties(); + if (compsps != null) orsps.addAll(compsps); - protected void updateAllowedTileColours (String colour, int oldAllowedNumber) { - - if (oldAllowedNumber <= 1) { - tileLaysPerColour.clear(); - log.debug("No more normal tile lays allowed"); - //currentNormalTileLays.clear();// Shouldn't be needed anymore ?? - } else { - List<String> coloursToRemove = new ArrayList<String>(); - for (String key:tileLaysPerColour.viewKeySet()) { - if (colour.equals(key)) { - tileLaysPerColour.put(key, oldAllowedNumber-1); - } else { - coloursToRemove.add(key); + if (orsps != null) { + for (SpecialPropertyI sp : orsps) { + if (!sp.isExercised() && sp.isUsableIfOwnedByCompany() + && sp.isUsableDuringOR(step)) { + if (sp instanceof SpecialTokenLay) { + if (getStep() != GameDef.OrStep.LAY_TOKEN) { + possibleActions.add(new LayBaseToken((SpecialTokenLay)sp)); + } + } else { + possibleActions.add(new UseSpecialProperty(sp)); + } + } + } + } + // Are there other step-independent special properties owned by the president? + orsps = getCurrentPlayer().getPortfolio().getAllSpecialProperties(); + if (orsps != null) { + for (SpecialPropertyI sp : orsps) { + if (!sp.isExercised() && sp.isUsableIfOwnedByPlayer() + && sp.isUsableDuringOR(step)) { + if (sp instanceof SpecialTokenLay) { + if (getStep() != GameDef.OrStep.LAY_TOKEN) { + possibleActions.add(new LayBaseToken((SpecialTokenLay)sp)); + } + } else { + possibleActions.add(new UseSpecialProperty(sp)); + } + } + } } } - // Two-step removal to prevent ConcurrentModificatioonException. - for (String key : coloursToRemove) { - tileLaysPerColour.remove(key); - } - log.debug((oldAllowedNumber - 1) + " additional " + colour - + " tile lays allowed; no other colours"); } - } - public boolean layBaseToken(LayBaseToken action) { + if (doneAllowed) { + possibleActions.add(new NullAction(NullAction.DONE)); + } - String errMsg = null; - int cost = 0; - SpecialTokenLay stl = null; - boolean extra = false; + for (PossibleAction pa : possibleActions.getList()) { + try { + log.debug(operatingCompany.get().getName() + " may: " + pa.toString()); + } catch (Exception e) { + log.error("Error in toString() of " + pa.getClass(), e); + } + } - MapHex hex = action.getChosenHex(); - int station = action.getChosenStation(); - String companyName = operatingCompany.get().getName(); + return true; + } - // TEMPORARY FIX to enable fixing invalidated saved files - //if ("N11".equals(hex.getName()) && station == 2) { - // station = 1; - // action.setChosenStation(1); - //} + /** Stub, can be overridden by subclasses */ + protected void setGameSpecificPossibleActions() { - // Dummy loop to enable a quick jump out. - while (true) { + } - // Checks - // Must be correct step (exception: home base lay & some special token lay) - if (getStep() != GameDef.OrStep.LAY_TOKEN - && action.getType() != LayBaseToken.HOME_CITY - && action.getType() != LayBaseToken.SPECIAL_PROPERTY) { - errMsg = LocalText.getText("WrongActionNoTokenLay"); - break; - } + /*======================================= + * 2.3. TURN CONTROL + *=======================================*/ - if (operatingCompany.get().getNumberOfFreeBaseTokens() == 0) { - errMsg = LocalText.getText("HasNoTokensLeft", companyName); - break; - } + protected void initTurn() { + log.debug("Starting turn of "+operatingCompany.get().getName()); + ReportBuffer.add(" "); + ReportBuffer.add(LocalText.getText("CompanyOperates", + operatingCompany.get().getName(), + operatingCompany.get().getPresident().getName())); + setCurrentPlayer(operatingCompany.get().getPresident()); - if (!isTokenLayAllowed (operatingCompany.get(), hex, station)) { - errMsg = LocalText.getText("BaseTokenSlotIsReserved"); - break; + if (noMapMode && !operatingCompany.get().hasLaidHomeBaseTokens()){ + // Lay base token in noMapMode + BaseToken token = operatingCompany.get().getFreeToken(); + if (token == null) { + log.error("Company " + operatingCompany.get().getName() + " has no free token to lay base token"); + } else { + log.debug("Company " + operatingCompany.get().getName() + " lays base token in nomap mode"); + token.moveTo(bank.getUnavailable()); } + } + operatingCompany.get().initTurn(); + trainsBoughtThisTurn.clear(); + } - if (!hex.hasTokenSlotsLeft(station)) { - errMsg = LocalText.getText("CityHasNoEmptySlots"); - break; - } + protected void finishTurn() { - /* - * TODO: the below condition holds for 1830. in some games, separate - * cities on one tile may hold tokens of the same company; this case - * is not yet covered. - */ - if (hex.hasTokenOfCompany(operatingCompany.get())) { - errMsg = - LocalText.getText("TileAlreadyHasToken", - hex.getName(), - companyName ); - break; - } + if (!operatingCompany.get().isClosed()) { + operatingCompany.get().setOperated(); + companiesOperatedThisRound.add(operatingCompany.get()); - if (action != null) { - List<MapHex> locations = action.getLocations(); - if (locations != null && locations.size() > 0 - && !locations.contains(hex) && !locations.contains(null)) { - errMsg = - LocalText.getText("TokenLayingHexMismatch", - hex.getName(), - action.getLocationNameString() ); - break; - } - stl = action.getSpecialProperty(); - if (stl != null) extra = stl.isExtra(); + // Check if any privates must be closed (now only applies to 1856 W&SR) + // Copy list first to avoid concurrent modifications + for (PrivateCompanyI priv : + new ArrayList<PrivateCompanyI> (operatingCompany.get().getPortfolio().getPrivateCompanies())) { + priv.checkClosingIfExercised(true); } + } - cost = operatingCompany.get().getBaseTokenLayCost(hex); - if (stl != null && stl.isFree()) cost = 0; + if (!finishTurnSpecials()) return; - // Does the company have the money? - if (cost > operatingCompany.get().getCash()) { - errMsg = LocalText.getText("NotEnoughMoney", - companyName, - Bank.format(operatingCompany.get().getCash()), - Bank.format(cost)); - break; - } - break; - } - if (errMsg != null) { - DisplayBuffer.add(LocalText.getText("CannotLayBaseTokenOn", - companyName, - hex.getName(), - Bank.format(cost), - errMsg )); - return false; + if (setNextOperatingCompany(false)) { + setStep(GameDef.OrStep.INITIAL); + } else { + finishOR(); } + } - /* End of validation, start of execution */ - moveStack.start(true); + /** Stub, may be overridden in subclasses + * Return value: + * TRUE = normal turn end; + * FALSE = return immediately from finishTurn(). + */ + protected boolean finishTurnSpecials () { + return true; + } - if (hex.layBaseToken(operatingCompany.get(), station)) { - /* TODO: the false return value must be impossible. */ + protected boolean setNextOperatingCompany(boolean initial) { - operatingCompany.get().layBaseToken(hex, cost); - - // If this is a home base token lay, stop here - if (action.getType() == LayBaseToken.HOME_CITY) { - return true; - } - - if (cost > 0) { - new CashMove(operatingCompany.get(), bank, cost); - ReportBuffer.add(LocalText.getText("LAYS_TOKEN_ON", - companyName, - hex.getName(), - Bank.format(cost) )); + while (true) { + if (initial || operatingCompany.get() == null || operatingCompany == null) { + setOperatingCompany(operatingCompanies.get(0)); + initial = false; } else { - ReportBuffer.add(LocalText.getText("LAYS_FREE_TOKEN_ON", - companyName, - hex.getName() )); - } + int index = operatingCompanies.indexOf(operatingCompany.get()); + if (++index >= operatingCompanies.size()) { + return false; + } - // Was a special property used? - if (stl != null) { - stl.setExercised(); - currentSpecialTokenLays.remove(action); - log.debug("This was a special token lay, " - + (extra ? "" : " not") + " extra"); + // Check if the operating order has changed + List<PublicCompanyI> newOperatingCompanies + = setOperatingCompanies (operatingCompanies.viewList(), operatingCompany.get()); + PublicCompanyI company; + for (int i=0; i<newOperatingCompanies.size(); i++) { + company = newOperatingCompanies.get(i); + if (company != operatingCompanies.get(i)) { + log.debug("Company "+company.getName() + +" replaces "+operatingCompanies.get(i).getName() + +" in operating sequence"); + operatingCompanies.move(company, i); + } + } + setOperatingCompany(operatingCompanies.get(index)); } - // Jump out if we aren't in the token laying step - if (getStep() != GameDef.OrStep.LAY_TOKEN) return true; - - if (!extra) { - currentNormalTokenLays.clear(); - log.debug("This was a normal token lay"); - } + if (operatingCompany.get().isClosed()) continue; - if (currentNormalTokenLays.isEmpty()) { - log.debug("No more normal token lays are allowed"); - } else if (operatingCompany.get().getNumberOfFreeBaseTokens() == 0) { - log.debug("Normal token lay allowed by no more tokens"); - currentNormalTokenLays.clear(); - } else { - log.debug("A normal token lay is still allowed"); - } - setSpecialTokenLays(); - log.debug("There are now " + currentSpecialTokenLays.size() - + " special token lay objects"); - if (currentNormalTokenLays.isEmpty() - && currentSpecialTokenLays.isEmpty()) { - nextStep(); - } + return true; + } + } + protected void setOperatingCompany (PublicCompanyI company) { + if (operatingCompany == null) { + operatingCompany = + new GenericState<PublicCompanyI>("OperatingCompany", company); + } else { + operatingCompany.set(company); } + } - return true; + /** + * Get the public company that has the turn to operate. + * + * @return The currently operating company object. + */ + public PublicCompanyI getOperatingCompany() { + return operatingCompany.get(); } - public boolean layBonusToken(LayBonusToken action) { + public List<PublicCompanyI> getOperatingCompanies() { + return operatingCompanies.viewList(); + } - String errMsg = null; - int cost = 0; - SpecialTokenLay stl = null; - boolean extra = false; + public int getOperatingCompanyIndex() { + int index = operatingCompanies.indexOf(getOperatingCompany()); + return index; + } - MapHex hex = action.getChosenHex(); - BonusToken token = action.getToken(); - // Dummy loop to enable a quick jump out. - while (true) { + /*======================================= + * 2.4. STEP CONTROL + *=======================================*/ - // Checks - MapHex location = action.getChosenHex(); - if (location != hex) { - errMsg = - LocalText.getText("TokenLayingHexMismatch", - hex.getName(), - location.getName() ); - break; - } - stl = action.getSpecialProperty(); - if (stl != null) extra = stl.isExtra(); + /** + * Get the current operating round step (i.e. the next action). + * + * @return The number that defines the next action. + */ + public GameDef.OrStep getStep() { + return (GameDef.OrStep) stepObject.get(); + } - cost = 0; // Let's assume for now that bonus tokens are always - // free - if (stl != null && stl.isFree()) cost = 0; + /** + * Bypass normal order of operations and explicitly set round step. This + * should only be done for specific rails.game exceptions, such as forced + * train purchases. + * + * @param step + */ + protected void setStep(GameDef.OrStep step) { - // Does the company have the money? - if (cost > operatingCompany.get().getCash()) { - errMsg = - LocalText.getText("NotEnoughMoney", - operatingCompany.get().getName()); - break; - } - break; - } - if (errMsg != null) { - DisplayBuffer.add(LocalText.getText("CannotLayBonusTokenOn", - token.getName(), - hex.getName(), - Bank.format(cost), - errMsg )); - return false; - } + stepObject.set(step); - /* End of validation, start of execution */ - moveStack.start(true); + } - if (hex.layBonusToken(token, gameManager.getPhaseManager())) { - /* TODO: the false return value must be impossible. */ + /** + * Internal method: change the OR state to the next step. If the currently + * Operating Company is done, notify this. + * + * @param company The current company. + */ + protected void nextStep() { + nextStep(getStep()); + } - operatingCompany.get().addBonus(new Bonus(operatingCompany.get(), - token.getName(), - token.getValue(), Collections.singletonList(hex))); - token.setUser(operatingCompany.get()); + /** Take the next step after a given one (see nextStep()) */ + protected void nextStep(GameDef.OrStep step) { - ReportBuffer.add(LocalText.getText("LaysBonusTokenOn", - operatingCompany.get().getName(), - token.getName(), - Bank.format(token.getValue()), - hex.getName() )); + PublicCompanyI company = operatingCompany.get(); - // Was a special property used? - if (stl != null) { - stl.setExercised(); - currentSpecialTokenLays.remove(action); - log.debug("This was a special token lay, " - + (extra ? "" : " not") + " extra"); + // Cycle through the steps until we reach one where a user action is + // expected. + int stepIndex; + for (stepIndex = 0; stepIndex < steps.length; stepIndex++) { + if (steps[stepIndex] == step) break; + } + while (++stepIndex < steps.length) { + step = steps[stepIndex]; + log.debug("Step " + step); + if (step == GameDef.OrStep.LAY_TOKEN + && company.getNumberOfFreeBaseTokens() == 0) { + continue; } - } + if (step == GameDef.OrStep.CALC_REVENUE) { - return true; - } + if (!company.canRunTrains()) { + // No trains, then the revenue is zero. + executeSetRevenueAndDividend ( + new SetDividend (0, false, new int[] {SetDividend.NO_TRAIN})); + // TODO: This probably does not handle share selling correctly + continue; + } + } - public boolean buyBonusToken(BuyBonusToken action) { + if (step == GameDef.OrStep.PAYOUT) { + // This step is now obsolete + continue; + } - String errMsg = null; - int cost; - SellBonusToken sbt = null; - CashHolder seller = null; + if (step == GameDef.OrStep.TRADE_SHARES) { - // Dummy loop to enable a quick jump out. - while (true) { + // Is company allowed to trade trasury shares? + if (!company.mayTradeShares() + || !company.hasOperated()) { + continue; + } - // Checks - sbt = action.getSpecialProperty(); - cost = sbt.getPrice(); - seller = sbt.getSeller(); + /* Check if any trading is possible. + * If not, skip this step. + * (but register a Done action for BACKWARDS COMPATIBILITY only) + */ + // Preload some expensive results + int ownShare = company.getPortfolio().getShare(company); + int poolShare = pool.getShare(company); // Expensive, do it once + // Can it buy? + boolean canBuy = + ownShare < getGameParameterAsInt (GameDef.Parm.TREASURY_SHARE_LIMIT) + && company.getCash() >= company.getCurrentSpace().getPrice() + && poolShare > 0; + // Can it sell? + boolean canSell = + company.getPortfolio().getShare(company) > 0 + && poolShare < getGameParameterAsInt (GameDef.Parm.POOL_SHARE_LIMIT); + // Above we ignore the possible existence of double shares (as in 1835). + + if (!canBuy && !canSell) { + // XXX For BACKWARDS COMPATIBILITY only, + // register a Done skip action during reloading. + if (gameManager.isReloading()) { + gameManager.setSkipDone(GameDef.OrStep.TRADE_SHARES); + log.debug("If the next saved action is 'Done', skip it"); + } + log.info("Skipping Treasury share trading step"); + continue; + } + + gameManager.startTreasuryShareTradingRound(); - // Does the company have the money? - if (cost > operatingCompany.get().getCash()) { - errMsg = - LocalText.getText("NotEnoughMoney", - operatingCompany.get().getName(), - Bank.format(operatingCompany.get().getCash()), - Bank.format(cost)); - break; } + + if (!gameSpecificNextStep (step)) continue; + + // No reason found to skip this step break; } - if (errMsg != null) { - DisplayBuffer.add(LocalText.getText("CannotBuyBonusToken", - operatingCompany.get().getName(), - sbt.getName(), - seller.getName(), - Bank.format(cost), - errMsg )); - return false; + + if (step == GameDef.OrStep.FINAL) { + finishTurn(); + } else { + setStep(step); } - /* End of validation, start of execution */ - moveStack.start(true); + } - new CashMove (operatingCompany.get(), seller, cost); - operatingCompany.get().addBonus(new Bonus(operatingCompany.get(), - sbt.getName(), - sbt.getValue(), - sbt.getLocations())); + /** Stub, can be overridden in subclasses to check for extra steps */ + protected boolean gameSpecificNextStep (GameDef.OrStep step) { + return true; + } - ReportBuffer.add(LocalText.getText("BuysBonusTokenFrom", - operatingCompany.get().getName(), - sbt.getName(), - Bank.format(sbt.getValue()), - seller.getName(), - Bank.format(sbt.getPrice()))); + /** + * This method is only called at the start of each step (unlike + * updateStatus(), which is called after each user action) + */ + protected void prepareStep() { + GameDef.OrStep step = stepObject.value(); - sbt.setExercised(); + if (step == GameDef.OrStep.LAY_TRACK) { + // getNormalTileLays(); + } else if (step == GameDef.OrStep.LAY_TOKEN) { - return true; - } + } else { + currentSpecialProperties = null; + } + } + /*======================================= + * 3. COMMON ACTIONS (not bound to steps) + * 3.1. NOOPS + *=======================================*/ - public boolean setRevenueAndDividend(SetDividend action) { + public void skip() { + log.debug("Skip step " + stepObject.value()); + moveStack.start(true); + nextStep(); + } - String errMsg = validateSetRevenueAndDividend (action); + /** + * The current Company is done operating. + * + * @param company Name of the company that finished operating. + * @return False if an error is found. + */ + public boolean done() { - if (errMsg != null) { - DisplayBuffer.add(LocalText.getText( - "CannotProcessRevenue", - Bank.format (action.getActualRevenue()), - action.getCompanyName(), - errMsg - )); + if (operatingCompany.get().getPortfolio().getNumberOfTrains() == 0 + && operatingCompany.get().mustOwnATrain()) { + // FIXME: Need to check for valid route before throwing an + // error. + /* Check TEMPORARILY disabled + errMsg = + LocalText.getText("CompanyMustOwnATrain", + operatingCompany.getObject().getName()); + setStep(STEP_BUY_TRAIN); + DisplayBuffer.add(errMsg); return false; + */ } - moveStack.start(true); + moveStack.start(false); - ReportBuffer.add(LocalText.getText("CompanyRevenue", - action.getCompanyName(), - Bank.format(action.getActualRevenue()))); + nextStep(); - int remainingAmount = checkForDeductions (action); - if (remainingAmount < 0) { - // A share selling round will be run to raise cash to pay debts - return true; + if (getStep() == GameDef.OrStep.FINAL) { + finishTurn(); } - executeSetRevenueAndDividend (action); - return true; - } - protected String validateSetRevenueAndDividend (SetDividend action) { + /*======================================= + * 3.2. DISCARDING TRAINS + *=======================================*/ + + public boolean discardTrain(DiscardTrain action) { + + TrainI train = action.getDiscardedTrain(); + PublicCompanyI company = action.getCompany(); + String companyName = company.getName(); String errMsg = null; - PublicCompanyI company; - String companyName; - int amount = 0; - int revenueAllocation = -1; // Dummy loop to enable a quick jump out. while (true) { - // Checks - // Must be correct company. - company = action.getCompany(); - companyName = company.getName(); - if (company != operatingCompany.get()) { - errMsg = - LocalText.getText("WrongCompany", - companyName, - operatingCompany.get().getName() ); - break; - } // Must be correct step - if (getStep() != GameDef.OrStep.CALC_REVENUE) { - errMsg = LocalText.getText("WrongActionNoRevenue"); + if (getStep() != GameDef.OrStep.BUY_TRAIN + && getStep() != GameDef.OrStep.DISCARD_TRAINS) { + errMsg = LocalText.getText("WrongActionNoDiscardTrain"); break; } - // Amount must be non-negative multiple of 10 - amount = action.getActualRevenue(); - if (amount < 0) { - errMsg = - LocalText.getText("NegativeAmountNotAllowed", - String.valueOf(amount)); - break; - } - if (amount % 10 != 0) { - errMsg = - LocalText.getText("AmountMustBeMultipleOf10", - String.valueOf(amount)); + if (train == null && action.isForced()) { + errMsg = LocalText.getText("NoTrainSpecified"); break; } - // Check chosen revenue distribution - if (amount > 0) { - // Check the allocation type index (see SetDividend for values) - revenueAllocation = action.getRevenueAllocation(); - if (revenueAllocation < 0 - || revenueAllocation >= SetDividend.NUM_OPTIONS) { - errMsg = - LocalText.getText("InvalidAllocationTypeIndex", - String.valueOf(revenueAllocation)); - break; - } - - // Validate the chosen allocation type - int[] allowedAllocations = - ((SetDividend) selectedAction).getAllowedAllocations(); - boolean valid = false; - for (int aa : allowedAllocations) { - if (revenueAllocation == aa) { - valid = true; - break; - } - } - if (!valid) { - errMsg = - LocalText.getText(SetDividend.getAllocationNameKey(revenueAllocation)); - break; - } - } else { - // If there is no revenue, use withhold. - action.setRevenueAllocation(SetDividend.WITHHOLD); - } + // Does the company own such a train? - if (amount == 0 && operatingCompany.get().getNumberOfTrains() == 0) { - DisplayBuffer.add(LocalText.getText("RevenueWithNoTrains", - operatingCompany.get().getName(), - Bank.format(0) )); + if (!company.getPortfolio().getTrainList().contains(train)) { + errMsg = + LocalText.getText("CompanyDoesNotOwnTrain", + company.getName(), + train.getName() ); + break; } break; } + if (errMsg != null) { + DisplayBuffer.add(LocalText.getText("CannotDiscardTrain", + companyName, + (train != null ?train.getName() : "?"), + errMsg )); + return false; + } - return errMsg; - } + /* End of validation, start of execution */ + moveStack.start(true); + // + if (action.isForced()) moveStack.linkToPreviousMoveSet(); - protected void executeSetRevenueAndDividend (SetDividend action) { + // Reset type of dual trains + if (train.getCertType().getPotentialTrainTypes().size() > 1) { + train.setType(null); + } - int amount = action.getActualRevenue(); - int revenueAllocation = action.getRevenueAllocation(); + train.moveTo(train.isObsolete() ? scrapHeap : pool); + ReportBuffer.add(LocalText.getText("CompanyDiscardsTrain", + companyName, + train.getName() )); - operatingCompany.get().setLastRevenue(amount); - operatingCompany.get().setLastRevenueAllocation(revenueAllocation); + // Check if any more companies must discard trains, + // otherwise continue train buying + if (!checkForExcessTrains()) { + // Trains may have been discarded by other players + setCurrentPlayer (operatingCompany.get().getPresident()); + stepObject.set(GameDef.OrStep.BUY_TRAIN); + } - // Pay any debts from treasury, revenue and/or president's cash - // The remaining dividend may be less that the original income - amount = executeDeductions (action); + //setPossibleActions(); - if (amount == 0) { + return true; + } - ReportBuffer.add(LocalText.getText("CompanyDoesNotPayDividend", - operatingCompany.get().getName())); - withhold(amount); + protected void setTrainsToDiscard() { - } else if (revenueAllocation == SetDividend.PAYOUT) { + // Scan the players in SR sequence, starting with the current player + Player player; + List<PublicCompanyI> list; + int currentPlayerIndex = getCurrentPlayerIndex(); + for (int i = currentPlayerIndex; i < currentPlayerIndex + + getNumberOfPlayers(); i++) { + player = gameManager.getPlayerByIndex(i); + if (excessTrainCompanies.containsKey(player)) { + setCurrentPlayer(player); + list = excessTrainCompanies.get(player); + for (PublicCompanyI comp : list) { + possibleActions.add(new DiscardTrain(comp, + comp.getPortfolio().getUniqueTrains(), true)); + // We handle one company at at time. + // We come back here until all excess trains have been + // discarded. + return; + } + } + } + } + /*======================================= + * 3.3. PRIVATES (BUYING, SELLING, CLOSING) + *=======================================*/ - ReportBuffer.add(LocalText.getText("CompanyPaysOutFull", - operatingCompany.get().getName(), Bank.format(amount) )); + public boolean buyPrivate(BuyPrivate action) { - payout(amount); + String errMsg = null; + PublicCompanyI publicCompany = action.getCompany(); + String publicCompanyName = publicCompany.getName(); + PrivateCompanyI privateCompany = action.getPrivateCompany(); + String privateCompanyName = privateCompany.getName(); + int price = action.getPrice(); + CashHolder owner = null; + Player player = null; + int upperPrice; + int lowerPrice; - } else if (revenueAllocation == SetDividend.SPLIT) { + // Dummy loop to enable a quick jump out. + while (true) { - ReportBuffer.add(LocalText.getText("CompanySplits", - operatingCompany.get().getName(), Bank.format(amount) )); + // Checks + // Does private exist? + if ((privateCompany = + companyManager.getPrivateCompany( + privateCompanyName)) == null) { + errMsg = + LocalText.getText("PrivateDoesNotExist", + privateCompanyName); + break; + } + // Is private still open? + if (privateCompany.isClosed()) { + errMsg = + LocalText.getText("PrivateIsAlreadyClosed", + privateCompanyName); + break; + } + // Is private owned by a player? + owner = privateCompany.getPortfolio().getOwner(); + if (!(owner instanceof Player)) { + errMsg = + LocalText.getText("PrivateIsNotOwnedByAPlayer", + privateCompanyName); + break; + } + player = (Player) owner; + upperPrice = privateCompany.getUpperPrice(); + lowerPrice = privateCompany.getLowerPrice(); - splitRevenue(amount); + // Is private buying allowed? + if (!isPrivateSellingAllowed()) { + errMsg = LocalText.getText("PrivateBuyingIsNotAllowed"); + break; + } - } else if (revenueAllocation == SetDividend.WITHHOLD) { + // Price must be in the allowed range + if (lowerPrice != PrivateCompanyI.NO_PRICE_LIMIT && price < lowerPrice) { + errMsg = + LocalText.getText("PriceBelowLowerLimit", + Bank.format(price), + Bank.format(lowerPrice), + privateCompanyName ); + break; + } + if (upperPrice != PrivateCompanyI.NO_PRICE_LIMIT && price > upperPrice) { + errMsg = + LocalText.getText("PriceAboveUpperLimit", + Bank.format(price), + Bank.format(lowerPrice), + privateCompanyName ); + break; + } + // Does the company have the money? + if (price > operatingCompany.get().getCash()) { + errMsg = + LocalText.getText("NotEnoughMoney", + publicCompanyName, + Bank.format(operatingCompany.get().getCash()), + Bank.format(price) ); + break; + } + break; + } + if (errMsg != null) { + if (owner != null) { + DisplayBuffer.add(LocalText.getText("CannotBuyPrivateFromFor", + publicCompanyName, + privateCompanyName, + owner.getName(), + Bank.format(price), + errMsg )); + } else { + DisplayBuffer.add(LocalText.getText("CannotBuyPrivateFor", + publicCompanyName, + privateCompanyName, + Bank.format(price), + errMsg )); + } + return false; + } - ReportBuffer.add(LocalText.getText("CompanyWithholds", - operatingCompany.get().getName(), - Bank.format(amount) )); + moveStack.start(true); - withhold(amount); + operatingCompany.get().buyPrivate(privateCompany, player.getPortfolio(), + price); + + return true; + + } + + protected boolean isPrivateSellingAllowed() { + return getCurrentPhase().isPrivateSellingAllowed(); + } + protected int getPrivateMinimumPrice (PrivateCompanyI privComp) { + int minPrice = privComp.getLowerPrice(); + if (minPrice == PrivateCompanyI.NO_PRICE_LIMIT) { + minPrice = 0; } + return minPrice; + } - // Rust any obsolete trains - operatingCompany.get().getPortfolio().rustObsoleteTrains(); + protected int getPrivateMaximumPrice (PrivateCompanyI privComp) { + int maxPrice = privComp.getUpperPrice(); + if (maxPrice == PrivateCompanyI.NO_PRICE_LIMIT) { + maxPrice = operatingCompany.get().getCash(); + } + return maxPrice; + } - // We have done the payout step, so continue from there - nextStep(GameDef.OrStep.PAYOUT); + protected boolean maySellPrivate (Player player) { + return true; } - /** - * Distribute the dividend amongst the shareholders. - * - * @param amount - */ - public void payout(int amount) { + protected boolean executeClosePrivate(ClosePrivate action) { - if (amount == 0) return; + PrivateCompanyI priv = action.getPrivateCompany(); - int part; - int shares; + log.debug("Executed close private action for private " + priv.getName()); - Map<CashHolder, Integer> sharesPerRecipient = countSharesPerRecipient(); + String errMsg = null; - // Calculate, round up, report and add the cash + if (priv.isClosed()) + errMsg = LocalText.getText("PrivateAlreadyClosed", priv.getName()); - // Define a precise sequence for the reporting - Set<CashHolder> recipientSet = sharesPerRecipient.keySet(); - for (CashHolder recipient : SequenceUtil.sortCashHolders(recipientSet)) { - if (recipient instanceof Bank) continue; - shares = (sharesPerRecipient.get(recipient)); - if (shares == 0) continue; - part = (int) Math.ceil(amount * shares * operatingCompany.get().getShareUnit() / 100.0); - ReportBuffer.add(LocalText.getText("Payout", - recipient.getName(), - Bank.format(part), - shares, - operatingCompany.get().getShareUnit())); - pay (bank, recipient, part); + if (errMsg != null) { + DisplayBuffer.add(errMsg); + return false; } - // Move the token - operatingCompany.get().payout(amount); + moveStack.start(true); - } + priv.setClosed(); - protected Map<CashHolder, Integer> countSharesPerRecipient () { + return true; + } - Map<CashHolder, Integer> sharesPerRecipient = new HashMap<CashHolder, Integer>(); + /*======================================= + * 3.4. DESTINATIONS + *=======================================*/ - // Changed to accomodate the CGR 5% share roundup rule. - // For now it is assumed, that actual payouts are always rounded up - // (the withheld half of split revenues is not handled here, see splitRevenue()). + /** Stub for applying any follow-up actions when + * a company reaches it destinations. + * Default version: no actions. + * @param company + */ + protected void reachDestination (PublicCompanyI company) { - // First count the shares per recipient - for (PublicCertificateI cert : operatingCompany.get().getCertificates()) { - CashHolder recipient = getBeneficiary(cert); - if (!sharesPerRecipient.containsKey(recipient)) { - sharesPerRecipient.put(recipient, cert.getShares()); - } else { - sharesPerRecipient.put(recipient, - sharesPerRecipient.get(recipient) + cert.getShares()); - } - } - return sharesPerRecipient; } - /** Who gets the per-share revenue? */ - protected CashHolder getBeneficiary(PublicCertificateI cert) { + public boolean reachDestinations (ReachDestinations action) { - Portfolio holder = cert.getPortfolio(); - CashHolder beneficiary = holder.getOwner(); - // Special cases apply if the holder is the IPO or the Pool - if (operatingCompany.get().paysOutToTreasury(cert)) { - beneficiary = operatingCompany.get(); + List<PublicCompanyI> destinedCompanies + = action.getReachedCompanies(); + if (destinedCompanies != null) { + for (PublicCompanyI company : destinedCompanies) { + if (company.hasDestination() + && !company.hasReachedDestination()) { + if (!moveStack.isOpen()) moveStack.start(true); + company.setReachedDestination(true); + ReportBuffer.add(LocalText.getText("DestinationReached", + company.getName(), + company.getDestinationHex().getName() + )); + // Process any consequences of reaching a destination + // (default none) + reachDestination (company); + } + } } - return beneficiary; + return true; } /** - * Withhold a given amount of revenue (and store it). - * - * @param The revenue amount. + * This is currently a stub, as it is unclear if there is a common + * rule for setting destination reaching options. + * See OperatingRound_1856 for a first implementation + * of such rules. */ - public void withhold(int amount) { + protected void setDestinationActions () { - PublicCompanyI company = operatingCompany.get(); + } - // Payout revenue to company - pay (bank, company, amount); + /*======================================= + * ... [truncated message content] |
From: Brett L. <wak...@us...> - 2011-09-21 13:10:41
|
rails/game/StartRound_1830.java | 4 ++++ 1 file changed, 4 insertions(+) New commits: commit 4a170a460e1adc1cc8c3882e6541d7b18a12f2ef Author: Bill Rosgen <ro...@gm...> Date: Wed Sep 21 16:25:08 2011 +0800 bug fix: 1830-stye initial auction should advance player after price is reduced on first item diff --git a/rails/game/StartRound_1830.java b/rails/game/StartRound_1830.java index eef05f1..517a470 100644 --- a/rails/game/StartRound_1830.java +++ b/rails/game/StartRound_1830.java @@ -354,6 +354,10 @@ public class StartRound_1830 extends StartRound { startPacket.getFirstItem(), 0, 0); gameManager.setPriorityPlayer(); // startPacket.getFirstItem().getName()); + } else { + //BR: If the first item's price is reduced, but not to 0, + // we still need to advance to the next player + setNextPlayer(); } } else { numPasses.set(0); |
From: Erik V. <ev...@us...> - 2011-09-30 11:54:31
|
rails/game/OperatingRound.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) New commits: commit 4ebc9085c07dccba67fc7f5b361b61bfbfb486d7 Author: Erik Vos <eri...@xs...> Date: Fri Sep 30 13:54:12 2011 +0200 Fix to allow laying THB home token if track exists. By Bill Rosgen diff --git a/rails/game/OperatingRound.java b/rails/game/OperatingRound.java index ec8de9c..f2ccfef 100644 --- a/rails/game/OperatingRound.java +++ b/rails/game/OperatingRound.java @@ -361,7 +361,13 @@ public class OperatingRound extends Round implements Observer { if (!operatingCompany.get().hasLaidHomeBaseTokens()) { // This can occur if the home hex has two cities and track, // such as the green OO tile #59 - possibleActions.add(new LayBaseToken (operatingCompany.get().getHomeHexes())); + + // BR: as this is a home token, need to call LayBaseToken with a MapHex, not a list + // to avoid the LayBaseToken action from being a regular token lay + // I am not sure that this will work with multiple home hexes. + for (MapHex home : operatingCompany.get().getHomeHexes()) { + possibleActions.add(new LayBaseToken (home) ); + } forced = true; } else { possibleActions.addAll(getNormalTileLays(true)); |
From: Erik V. <ev...@us...> - 2011-10-03 09:46:40
|
rails/game/action/LayBaseToken.java | 47 ++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 13 deletions(-) New commits: commit 9502e7165a2752f013bd2793f49c774ac4a83acf Author: Erik Vos <eri...@xs...> Date: Mon Oct 3 11:46:12 2011 +0200 Javadoc comments added to the LayBaseToken constructors. diff --git a/rails/game/action/LayBaseToken.java b/rails/game/action/LayBaseToken.java index 945a4d1..9abe174 100644 --- a/rails/game/action/LayBaseToken.java +++ b/rails/game/action/LayBaseToken.java @@ -40,22 +40,43 @@ public class LayBaseToken extends LayToken { public static final long serialVersionUID = 1L; /** - * Allow laying a base token on a given location. + * Lay a base token on one of a given list of locations. + * <p>This constructor is only intended to be used for normal lays of non-home tokens + * in the operating company LAY_TOKEN OR step. + * + * @param locations A list of valid locations (hexes) where the acting company can lay a base token.<br> + * <i>Note:</i> Currently, the game engine cannot yet provide such a list, as all knowledge about routes + * is contained in the user interface code. As a consequence, this constructor is only called + * with the value <b>null</b>, which allows laying a base token on <i>any</i> empty city slot. + * In fact, the UI will now apply the restriction to valid locations only. + * Over time, applying this restriction should be moved to the game engine. */ public LayBaseToken(List<MapHex> locations) { super(locations); type = LOCATION_SPECIFIC; } + /** Lay a base token as allowed via a Special Property. + * <p>The valid locations (hexes) of such a token should be defined inside the special property. + * Typically, such locations do not need to be connected to the existing network of a company. + * + * @param specialProperty The special property that allows laying an extra or unconnected base token. + */ public LayBaseToken(SpecialTokenLay specialProperty) { super(specialProperty); type = SPECIAL_PROPERTY; } + /** Lay a base token on a given location. + * <p> This constructor is specifically intended to allow the player to select a city for its <b>home</b> token + * on a multi-city hex or tile (e.g. an OO tile, such as the Erie in 1830 or the THB in 1856). + * + * @param hex The hex on which a city must be selected to lay a home token on. + */ public LayBaseToken (MapHex hex) { - super (hex); - setChosenHex (hex); - type = HOME_CITY; + super (hex); + setChosenHex (hex); + type = HOME_CITY; } public int getChosenStation() { @@ -75,9 +96,9 @@ public class LayBaseToken extends LayToken { if (!(action instanceof LayBaseToken)) return false; LayBaseToken a = (LayBaseToken) action; return (a.locationNames == null && locationNames == null || a.locationNames.equals(locationNames)) - && a.type == type - && a.company == company - && a.specialProperty == specialProperty; + && a.type == type + && a.company == company + && a.specialProperty == specialProperty; } @Override @@ -85,10 +106,10 @@ public class LayBaseToken extends LayToken { if (!(action instanceof LayBaseToken)) return false; LayBaseToken a = (LayBaseToken) action; return a.chosenHex == chosenHex - && a.chosenStation == chosenStation - && a.type == type - && a.company == company - && a.specialProperty == specialProperty; + && a.chosenStation == chosenStation + && a.type == type + && a.company == company + && a.specialProperty == specialProperty; } @Override @@ -106,7 +127,7 @@ public class LayBaseToken extends LayToken { /** Deserialize */ private void readObject(ObjectInputStream in) throws IOException, - ClassNotFoundException { + ClassNotFoundException { in.defaultReadObject(); @@ -120,7 +141,7 @@ public class LayBaseToken extends LayToken { if (specialPropertyId > 0) { specialProperty = - (SpecialTokenLay) SpecialProperty.getByUniqueId(specialPropertyId); + (SpecialTokenLay) SpecialProperty.getByUniqueId(specialPropertyId); } if (chosenHexName != null && chosenHexName.length() > 0) { chosenHex = mmgr.getHex(chosenHexName); |
From: Erik V. <ev...@us...> - 2011-10-13 10:55:06
|
rails/game/PublicCompany.java | 14 ++++++++------ rails/game/Stop.java | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) New commits: commit adfb01e778893115aec455fd3755bd0e1bad3502 Author: Erik Vos <eri...@xs...> Date: Thu Oct 13 12:54:13 2011 +0200 Some code cleanups diff --git a/rails/game/PublicCompany.java b/rails/game/PublicCompany.java index b7e8d50..0a0aeb9 100644 --- a/rails/game/PublicCompany.java +++ b/rails/game/PublicCompany.java @@ -144,7 +144,7 @@ public class PublicCompany extends Company implements PublicCompanyI { * A map per tile colour, holding the number of turns that the tile lay * number applies. The default number is always 1. */ - protected Map<String, Integer> turnsWithExtraTileLaysInit = null; + //protected Map<String, Integer> turnsWithExtraTileLaysInit = null; /** Copy of turnsWithExtraTileLaysInit, per company */ protected Map<String, IntegerState> turnsWithExtraTileLays = null; /** @@ -449,7 +449,7 @@ public class PublicCompany extends Company implements PublicCompanyI { } } - Tag tileLaysTag = tag.getChild("TileLays"); + /*Tag tileLaysTag = tag.getChild("TileLays"); if (tileLaysTag != null) { for (Tag numberTag : tileLaysTag.getChildren("Number")) { @@ -471,9 +471,9 @@ public class PublicCompany extends Company implements PublicCompanyI { String[] colours = colourString.split(","); HashMap<String, Integer> phaseMap; /** - * TODO: should not be necessary to specify all phases - * separately - */ + * TODO: should not be necessary to specify all phases + * separately + *//* String[] phases = phaseString.split(","); for (int i = 0; i < colours.length; i++) { if (extraTileLays == null) @@ -494,7 +494,7 @@ public class PublicCompany extends Company implements PublicCompanyI { } } } - } + }*/ int certIndex = 0; List<Tag> certificateTags = tag.getChildren("Certificate"); @@ -678,6 +678,7 @@ public class PublicCompany extends Company implements PublicCompanyI { stockMarket = gameManager.getStockMarket(); mapManager = gameManager.getMapManager(); + /* if (turnsWithExtraTileLaysInit != null) { turnsWithExtraTileLays = new HashMap<String, IntegerState>(); for (String colour : turnsWithExtraTileLaysInit.keySet()) { @@ -686,6 +687,7 @@ public class PublicCompany extends Company implements PublicCompanyI { turnsWithExtraTileLaysInit.get(colour))); } } + */ if (maxNumberOfLoans != 0) { currentNumberOfLoans = new IntegerState (name+"_Loans", 0); diff --git a/rails/game/Stop.java b/rails/game/Stop.java index d1e918c..401f18f 100644 --- a/rails/game/Stop.java +++ b/rails/game/Stop.java @@ -171,9 +171,9 @@ public class Stop implements TokenHolder { if (scoreType == null) scoreType = tileManager.getScoreTypeDefault(type); if (scoreType == null) scoreType = type.getDefaultScoreType(); - log.debug("+++ Hex="+mapHex.getName()+" tile="+tile.getId()+" city="+number - +": stopType="+type+" runTo="+runToAllowed+" runThrough="+runThroughAllowed - +" loop="+loopAllowed+" scoreType="+scoreType); + //log.debug("+++ Hex="+mapHex.getName()+" tile="+tile.getId()+" city="+number + // +": stopType="+type+" runTo="+runToAllowed+" runThrough="+runThroughAllowed + // +" loop="+loopAllowed+" scoreType="+scoreType); } public String getName() { |
From: Erik V. <ev...@us...> - 2011-10-30 20:24:09
|
rails/game/OperatingRound.java | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) New commits: commit 8dd8adf3e2da0056bb7b254f3a0d796288d73b1d Author: Erik Vos <eri...@xs...> Date: Sun Oct 30 21:16:37 2011 +0100 Fix: Buying Coalfields right is now refused if the company does not have enough cash. Reported by Charles Strong. This is the easy fix. In fact, the option to buy this right should not be offered at all if the company lacks cash, but fixing that is a bit more complex and omitted for now. diff --git a/rails/game/OperatingRound.java b/rails/game/OperatingRound.java index 23af287..458a6a7 100644 --- a/rails/game/OperatingRound.java +++ b/rails/game/OperatingRound.java @@ -1353,21 +1353,35 @@ public class OperatingRound extends Round implements Observer { protected boolean buyRight (UseSpecialProperty action) { String errMsg = null; + String rightName = ""; + String rightValue = ""; + int cost = 0; SpecialPropertyI sp = action.getSpecialProperty(); - if (!(sp instanceof SpecialRight)) { - errMsg = "Wrong right property class: "+sp.toString(); - } - SpecialRight right = (SpecialRight) sp; - String rightName = right.getName(); - String rightValue = right.getValue(); + while (true) { + if (!(sp instanceof SpecialRight)) { + errMsg = "Wrong right property class: "+sp.toString(); + break; + } + + SpecialRight right = (SpecialRight) sp; + rightName = right.getName(); + rightValue = right.getValue(); + cost = right.getCost(); + + if (cost > 0 && cost > operatingCompany.get().getCash()) { + errMsg = LocalText.getText("NoMoney"); + break; + } + break; + } if (errMsg != null) { DisplayBuffer.add(LocalText.getText("CannotBuyRight", action.getCompanyName(), rightName, - Bank.format(right.getCost()), + Bank.format(cost), errMsg)); return false; @@ -1376,12 +1390,12 @@ public class OperatingRound extends Round implements Observer { moveStack.start(true); operatingCompany.get().setRight(rightName, rightValue); - new CashMove (operatingCompany.get(), bank, right.getCost()); + if (cost > 0) new CashMove (operatingCompany.get(), bank, cost); ReportBuffer.add(LocalText.getText("BuysRight", operatingCompany.get().getName(), rightName, - Bank.format(right.getCost()))); + Bank.format(cost))); sp.setExercised(); |
From: Erik V. <ev...@us...> - 2011-11-23 16:21:39
|
rails/game/specific/_1835/PrussianFormationRound.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) New commits: commit 7f688a6ee82a52d57e6909303f91103016a29b24 Author: Erik Vos <eri...@xs...> Date: Wed Nov 23 17:20:57 2011 +0100 Fixed PR train discarding bugs. PR did not discard excess trains after forced merge at first 5-train, and would (probably) never discard more than one train, even if having two or more excess trains. diff --git a/rails/game/specific/_1835/PrussianFormationRound.java b/rails/game/specific/_1835/PrussianFormationRound.java index aa9a3b0..d31cd9f 100644 --- a/rails/game/specific/_1835/PrussianFormationRound.java +++ b/rails/game/specific/_1835/PrussianFormationRound.java @@ -92,7 +92,13 @@ public class PrussianFormationRound extends StockRound { } } executeExchange (foldables, false, true); - finishRound(); + + // Check if the PR must discard any trains + if (prussian.getNumberOfTrains() > prussian.getCurrentTrainLimit()) { + step = Step.DISCARD_TRAINS; + } else { + finishRound(); + } } else { findNextMergingPlayer(false); } @@ -118,6 +124,7 @@ public class PrussianFormationRound extends StockRound { } else if (step == Step.DISCARD_TRAINS) { if (prussian.getNumberOfTrains() > prussian.getCurrentTrainLimit()) { + log.debug("+++ PR has "+prussian.getNumberOfTrains()+", limit is "+prussian.getCurrentTrainLimit()); possibleActions.add(new DiscardTrain(prussian, prussian.getPortfolio().getUniqueTrains(), true)); } @@ -425,8 +432,13 @@ public class PrussianFormationRound extends StockRound { company.getName(), train.getName() )); - // This always finished this type of round - finishRound(); + // We still might have another excess train + // TODO: would be better to have DiscardTrain discard multiple trains + if (prussian.getNumberOfTrains() > prussian.getCurrentTrainLimit()) { + step = Step.DISCARD_TRAINS; + } else { + finishRound(); + } return true; } |
From: Erik V. <ev...@us...> - 2011-12-08 20:11:50
|
rails/game/specific/_1889/OperatingRound_1889.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) New commits: commit 60aa939317e6255ba8bd55bc37a4d7879c4232ba Author: Erik Vos <eri...@xs...> Date: Thu Dec 8 21:11:22 2011 +0100 1889: Laying port tile is only allowed action if done outside of own company OR. diff --git a/rails/game/specific/_1889/OperatingRound_1889.java b/rails/game/specific/_1889/OperatingRound_1889.java index 12d82bc..c7fdf52 100644 --- a/rails/game/specific/_1889/OperatingRound_1889.java +++ b/rails/game/specific/_1889/OperatingRound_1889.java @@ -60,7 +60,9 @@ public class OperatingRound_1889 extends OperatingRound { if (!activeSpPrivB.booleanValue()) possibleActions.add(new UseSpecialProperty(spPrivB)); else { + possibleActions.clear(); possibleActions.add(layTile); + possibleActions.add(new NullAction(NullAction.SKIP)); DisplayBuffer.add(LocalText.getText("1889PrivateBactive", privB.getPortfolio().getOwner())); } } @@ -75,7 +77,7 @@ public class OperatingRound_1889 extends OperatingRound { LayTile layTile = new LayTile(spPrivC); if (validateSpecialTileLay(layTile)) { possibleActions.clear(); - possibleActions.add(new LayTile(spPrivC)); + possibleActions.add(layTile); possibleActions.add(new NullAction(NullAction.SKIP)); DisplayBuffer.add(LocalText.getText("1889PrivateCactive", previousOwnerName)); } |
From: Erik V. <ev...@us...> - 2011-12-18 11:59:44
|
rails/game/StockRound.java | 51 +++++++++-------- rails/game/specific/_18EU/FinalMinorExchangeRound.java | 4 + 2 files changed, 31 insertions(+), 24 deletions(-) New commits: commit fa62b37076e0753f6fa32c2af220ac880c784be3 Author: Erik Vos <eri...@xs...> Date: Sun Dec 18 12:58:58 2011 +0100 Fixed 18EU bug: sold out companies could raise value at end of FME round. Raising stock value is now controlled by a new StockRound attribute named 'raiseIfSoldOut', which is normally true. This new attribute should be set to false at initiating any subclasses that call the generic finishRound() but where stock price should not change. This is now done in 18EU/FinalMinorExchangeRound. diff --git a/rails/game/StockRound.java b/rails/game/StockRound.java index 3bbc525..3c469c3 100644 --- a/rails/game/StockRound.java +++ b/rails/game/StockRound.java @@ -56,6 +56,7 @@ public class StockRound extends Round { /* Rules */ protected int sequenceRule; + protected boolean raiseIfSoldOut; /** * Constructor with the GameManager, will call super class (Round's) Constructor to initialize @@ -71,6 +72,8 @@ public class StockRound extends Round { sequenceRule = getGameParameterAsInt(GameDef.Parm.STOCK_ROUND_SEQUENCE); + raiseIfSoldOut = true; + guiHints.setVisibilityHint(GuiDef.Panel.MAP, true); guiHints.setVisibilityHint(GuiDef.Panel.STOCK_MARKET, true); guiHints.setActivePanel(GuiDef.Panel.STATUS); @@ -417,11 +420,11 @@ public class StockRound extends Round { // and double shares for now. choiceOfPresidentExchangeCerts = uniqueCertsCount[1] > 1 && uniqueCertsCount[2] > 0; - // If a presidency dump is possible, extra (single) share(s) may be sold - // that aren't even owned - extraSingleShares = Math.min( - presidentShare/shareUnit, - (maxShareToSell-dumpThreshold)/shareUnit+1); + // If a presidency dump is possible, extra (single) share(s) may be sold + // that aren't even owned + extraSingleShares = Math.min( + presidentShare/shareUnit, + (maxShareToSell-dumpThreshold)/shareUnit+1); } // What number of shares can we sell if we cannot dump? @@ -1344,24 +1347,26 @@ public class StockRound extends Round { ReportBuffer.add(LocalText.getText("END_SR", String.valueOf(getStockRoundNumber()))); - /* Check if any companies are sold out. */ - for (PublicCompanyI company : gameManager.getCompaniesInRunningOrder()) { - if (company.hasStockPrice() && company.isSoldOut()) { - StockSpaceI oldSpace = company.getCurrentSpace(); - stockMarket.soldOut(company); - StockSpaceI newSpace = company.getCurrentSpace(); - if (newSpace != oldSpace) { - ReportBuffer.add(LocalText.getText("SoldOut", - company.getName(), - Bank.format(oldSpace.getPrice()), - oldSpace.getName(), - Bank.format(newSpace.getPrice()), - newSpace.getName())); - } else { - ReportBuffer.add(LocalText.getText("SoldOutNoRaise", - company.getName(), - Bank.format(newSpace.getPrice()), - newSpace.getName())); + if (raiseIfSoldOut) { + /* Check if any companies are sold out. */ + for (PublicCompanyI company : gameManager.getCompaniesInRunningOrder()) { + if (company.hasStockPrice() && company.isSoldOut()) { + StockSpaceI oldSpace = company.getCurrentSpace(); + stockMarket.soldOut(company); + StockSpaceI newSpace = company.getCurrentSpace(); + if (newSpace != oldSpace) { + ReportBuffer.add(LocalText.getText("SoldOut", + company.getName(), + Bank.format(oldSpace.getPrice()), + oldSpace.getName(), + Bank.format(newSpace.getPrice()), + newSpace.getName())); + } else { + ReportBuffer.add(LocalText.getText("SoldOutNoRaise", + company.getName(), + Bank.format(newSpace.getPrice()), + newSpace.getName())); + } } } } diff --git a/rails/game/specific/_18EU/FinalMinorExchangeRound.java b/rails/game/specific/_18EU/FinalMinorExchangeRound.java index 79bb761..3e96542 100644 --- a/rails/game/specific/_18EU/FinalMinorExchangeRound.java +++ b/rails/game/specific/_18EU/FinalMinorExchangeRound.java @@ -23,6 +23,8 @@ public class FinalMinorExchangeRound extends StockRound_18EU { guiHints.setVisibilityHint(GuiDef.Panel.MAP, true); guiHints.setActivePanel(GuiDef.Panel.STATUS); + + raiseIfSoldOut = false; } public void start(Player playerToStartFMERound) { @@ -56,7 +58,7 @@ public class FinalMinorExchangeRound extends StockRound_18EU { } List<PublicCompanyI> comps = - companyManager.getAllPublicCompanies(); + companyManager.getAllPublicCompanies(); List<PublicCompanyI> minors = new ArrayList<PublicCompanyI>(); List<PublicCompanyI> targetCompanies = new ArrayList<PublicCompanyI>(); String type; |
From: Erik V. <ev...@us...> - 2011-12-19 12:01:22
|
rails/game/StockRound.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) New commits: commit db7c6ee7b80a57e88b8008ccec460fb91059ea87 Author: Erik Vos <eri...@xs...> Date: Mon Dec 19 12:59:29 2011 +0100 Fixed initialisation of raiseIfSoldOut attribute. diff --git a/rails/game/StockRound.java b/rails/game/StockRound.java index 3c469c3..03bb17a 100644 --- a/rails/game/StockRound.java +++ b/rails/game/StockRound.java @@ -56,7 +56,7 @@ public class StockRound extends Round { /* Rules */ protected int sequenceRule; - protected boolean raiseIfSoldOut; + protected boolean raiseIfSoldOut = false; /** * Constructor with the GameManager, will call super class (Round's) Constructor to initialize @@ -72,13 +72,15 @@ public class StockRound extends Round { sequenceRule = getGameParameterAsInt(GameDef.Parm.STOCK_ROUND_SEQUENCE); - raiseIfSoldOut = true; - guiHints.setVisibilityHint(GuiDef.Panel.MAP, true); guiHints.setVisibilityHint(GuiDef.Panel.STOCK_MARKET, true); guiHints.setActivePanel(GuiDef.Panel.STATUS); } + /** Start the Stock Round. <p> + * Please note: subclasses that are NOT real stock rounds should NOT call this method + * (or set raiseIfSoldOut to false after calling this method). + */ public void start() { ReportBuffer.add(LocalText.getText("StartStockRound", @@ -91,6 +93,8 @@ public class StockRound extends Round { initPlayer(); + raiseIfSoldOut = true; + } /*----- General methods -----*/ |
From: Erik V. <ev...@us...> - 2012-02-07 22:02:17
|
rails/game/StartRound.java | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) New commits: commit c60ac2a448bd5b3d727673a23fa879da839c4d54 Author: Erik Vos <eri...@xs...> Date: Tue Feb 7 23:01:08 2012 +0100 Fix for 1835 bug: BY presidency not transferred during start round. Presidency check was missing and has now been added in the start round. diff --git a/rails/game/StartRound.java b/rails/game/StartRound.java index c9ab5b6..73008d1 100644 --- a/rails/game/StartRound.java +++ b/rails/game/StartRound.java @@ -3,9 +3,7 @@ package rails.game; import java.util.ArrayList; import java.util.List; -import rails.common.DisplayBuffer; -import rails.common.GuiDef; -import rails.common.LocalText; +import rails.common.*; import rails.common.parser.GameOption; import rails.game.action.*; import rails.game.model.ModelObject; @@ -18,7 +16,7 @@ public abstract class StartRound extends Round { protected int[] itemIndex; protected List<StartItem> itemsToSell = null; protected State auctionItemState = - new State("AuctionItem", StartItem.class); + new State("AuctionItem", StartItem.class); protected IntegerState numPasses = new IntegerState("StartRoundPasses"); protected int numPlayers; protected String variant; @@ -120,7 +118,7 @@ public abstract class StartRound extends Round { BuyStartItem buyAction = (BuyStartItem) startItemAction; if (buyAction.hasSharePriceToSet() - && buyAction.getAssociatedSharePrice() == 0) { + && buyAction.getAssociatedSharePrice() == 0) { // We still need a share price for this item startItemAction.getStartItem().setStatus( StartItem.NEEDS_SHARE_PRICE); @@ -138,7 +136,7 @@ public abstract class StartRound extends Round { DisplayBuffer.add(LocalText.getText("UnexpectedAction", action.toString())); } - + startPacketChecks(); if (startPacket.areAllSold()) { @@ -210,14 +208,14 @@ public abstract class StartRound extends Round { sharePrice = boughtItem.getAssociatedSharePrice(); if (sharePrice == 0) { errMsg = - LocalText.getText("NoSharePriceSet", shareCompName); + LocalText.getText("NoSharePriceSet", shareCompName); break; } if ((stockMarket.getStartSpace(sharePrice)) == null) { errMsg = - LocalText.getText("InvalidStartPrice", - Bank.format(sharePrice), - shareCompName ); + LocalText.getText("InvalidStartPrice", + Bank.format(sharePrice), + shareCompName ); break; } } @@ -302,7 +300,7 @@ public abstract class StartRound extends Round { if (comp.hasStarted() && !comp.hasFloated()) { checkFlotation(comp); } - + if (comp.hasStarted()) comp.checkPresidency(); // Needed for 1835 BY } } @@ -318,9 +316,9 @@ public abstract class StartRound extends Round { super.finishRound(); } - /*----- Setting up the UI for the next action -----*/ + /*----- Setting up the UI for the next action -----*/ - /** + /** * Get the currentPlayer index in the player list (starting at 0). * * @return The index of the current Player. |
From: Erik V. <ev...@us...> - 2012-04-01 11:56:03
|
rails/game/PublicCompany.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) New commits: commit a9a425403147d11fba8559e7798dde93a850d9aa Author: Erik Vos <eri...@xs...> Date: Sun Apr 1 13:55:40 2012 +0200 1830: fixed wrong worth calculation for Prussian shares. For PR, the market price applies to 2 (5%) share units. This (unique) difeerence was overlooked in teh worth calculation. diff --git a/rails/game/PublicCompany.java b/rails/game/PublicCompany.java index 48c56ed..92c045e 100644 --- a/rails/game/PublicCompany.java +++ b/rails/game/PublicCompany.java @@ -792,7 +792,7 @@ public class PublicCompany extends Company implements PublicCompanyI { name + "_" + colour + "_ExtraTileTurns", turns)); } - + /** Reset turn objects */ public void initTurn() { @@ -1024,7 +1024,7 @@ public class PublicCompany extends Company implements PublicCompanyI { public boolean hasFloated() { return hasFloated.booleanValue(); } - + public ModelObject getFloatedModel() { return hasFloated; } @@ -1160,7 +1160,7 @@ public class PublicCompany extends Company implements PublicCompanyI { * @return */ public int getGameEndPrice() { - return getMarketPrice(); + return getMarketPrice()/getShareUnitsForSharePrice(); } /** @@ -1968,7 +1968,7 @@ public class PublicCompany extends Company implements PublicCompanyI { // New style int tileLays = phase.getTileLaysPerColour(getTypeName(), tileColour); - + if (tileLays <= 1) { extraTileLays = null; return tileLays; @@ -1979,8 +1979,8 @@ public class PublicCompany extends Company implements PublicCompanyI { if (turnsWithExtraTileLays != null) { extraTiles = turnsWithExtraTileLays.get(tileColour); } - - + + if (extraTiles != null) { if (extraTiles.intValue() == 0) { extraTiles = null; |
From: Erik V. <ev...@us...> - 2012-05-08 11:56:30
|
rails/game/PublicCompany.java | 26 +++++++++++++++++++++----- rails/game/PublicCompanyI.java | 7 +++---- 2 files changed, 24 insertions(+), 9 deletions(-) New commits: commit d136bf8dd11a71574fee182a00c2f1277370a39d Author: Erik Vos <eri...@xs...> Date: Tue May 8 13:55:25 2012 +0200 Companies with a fixed start price postpone laying the current price token until floating time. This applies to 1835 (bug reported by Volker Schnell) and 1825. Only defaults are set. For 1837, an (game or company) attribute must be added to set this rule also for the companies that don't ahve a fixed starting price. diff --git a/rails/game/PublicCompany.java b/rails/game/PublicCompany.java index 92c045e..1b589e2 100644 --- a/rails/game/PublicCompany.java +++ b/rails/game/PublicCompany.java @@ -29,10 +29,10 @@ public class PublicCompany extends Company implements PublicCompanyI { protected static int numberOfPublicCompanies = 0; - // Home base token lay times + // Home base & price token lay times protected static final int WHEN_STARTED = 0; protected static final int WHEN_FLOATED = 1; - protected static final int START_OF_FIRST_OR = 2; + protected static final int START_OF_FIRST_OR = 2; // Only applies to home base tokens // Base token lay cost calculation methods public static final String BASE_COST_SEQUENCE = "sequence"; @@ -235,6 +235,8 @@ public class PublicCompany extends Company implements PublicCompanyI { /*---- variables needed during initialisation -----*/ protected String startSpace = null; + protected int dropPriceToken = WHEN_STARTED; + protected int capitalisation = CAPITALISE_FULL; /** Fixed price (for a 1835-style minor) */ @@ -313,6 +315,11 @@ public class PublicCompany extends Company implements PublicCompanyI { floatPerc = tag.getAttributeAsInteger("floatPerc", floatPerc); startSpace = tag.getAttributeAsString("startspace"); + // Set the default price token drop time. + // Currently, no exceptions exist, so this value isn't changed anywhere yet. + // Any (future) games with exceptions to these defaults will require a separate XML attribute. + // Known games to have exceptions: 1837. + dropPriceToken = startSpace != null ? WHEN_FLOATED : WHEN_STARTED; fixedPrice = tag.getAttributeAsInteger("price", 0); @@ -934,8 +941,12 @@ public class PublicCompany extends Company implements PublicCompanyI { if (startSpace != null) { setParSpace(startSpace); - // The current price is set via the Stock Market - stockMarket.start(this, startSpace); + setCurrentSpace(startSpace); + + // Drop the current price token, if allowed at this point + if (dropPriceToken == WHEN_STARTED) { + stockMarket.start(this, startSpace); + } } @@ -1002,6 +1013,11 @@ public class PublicCompany extends Company implements PublicCompanyI { stockMarket.moveUp(this); } + // Drop the current price token, if allowed at this point + if (dropPriceToken == WHEN_FLOATED) { + stockMarket.start(this, getCurrentSpace()); + } + if (homeBaseTokensLayTime == WHEN_FLOATED) { layHomeBaseTokens(); } @@ -1170,7 +1186,7 @@ public class PublicCompany extends Company implements PublicCompanyI { * stock market. */ public void setCurrentSpace(StockSpaceI price) { - if (price != null) { + if (price != null && price != getCurrentSpace()) { currentPrice.setPrice(price); } } diff --git a/rails/game/PublicCompanyI.java b/rails/game/PublicCompanyI.java index ecd928f..7b28906 100644 --- a/rails/game/PublicCompanyI.java +++ b/rails/game/PublicCompanyI.java @@ -11,12 +11,11 @@ import rails.game.model.*; */ public interface PublicCompanyI extends CompanyI, CashHolder, TokenHolder { + /* Capitalisation options */ public static final int CAPITALISE_FULL = 0; - public static final int CAPITALISE_INCREMENTAL = 1; - public static final int CAPITALISE_WHEN_BOUGHT = 2; - + public void setIndex (int index); @@ -96,7 +95,7 @@ public interface PublicCompanyI extends CompanyI, CashHolder, TokenHolder { */ public boolean hasFloated(); - + public ModelObject getFloatedModel(); /** |
From: Erik V. <ev...@us...> - 2012-05-08 19:21:41
|
rails/game/StockRound.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) New commits: commit d84f8880851c489386b69eaac3adb93e590e3ae7 Author: Erik Vos <eri...@xs...> Date: Tue May 8 21:19:56 2012 +0200 Fixed 1835 bug where non-president 20% share was offered to be sold in parts. Reported by Mike Bourke diff --git a/rails/game/StockRound.java b/rails/game/StockRound.java index 27989f9..91a4c6e 100644 --- a/rails/game/StockRound.java +++ b/rails/game/StockRound.java @@ -410,6 +410,7 @@ public class StockRound extends Round { * If the current Player is president, check if he can dump the * presidency onto someone else. */ + dumpThreshold = 0; if (company.getPresident() == currentPlayer) { int presidentShare = company.getCertificates().get(0).getShare(); @@ -423,7 +424,6 @@ public class StockRound extends Round { List<Player> players = gameManager.getPlayers(); Player player; int dumpedPlayerShare = 0; - dumpThreshold = 0; for (playerIndex = (currentPlayerIndex+1)%numberOfPlayers; playerIndex != currentPlayerIndex; |
From: Erik V. <ev...@us...> - 2012-05-28 10:57:06
|
rails/game/MapManager.java | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) New commits: commit 1210bffe332f0f978f828b2e7e38e77cc7267cc4 Author: Erik Vos <eri...@xs...> Date: Mon May 28 12:55:02 2012 +0200 Fixed 1835 hex distance calculation bug. Impassable hex sides are no longer disregarded. diff --git a/rails/game/MapManager.java b/rails/game/MapManager.java index dcd9cad..6cd4307 100644 --- a/rails/game/MapManager.java +++ b/rails/game/MapManager.java @@ -325,6 +325,40 @@ public class MapManager implements ConfigurableComponentI { } } + /** Return the hex adjacent to a given hex in a particular direction. + * Return null if that hex does not exist. + * @param hex The hex object for which an adjacent one is searched. + * @param orientation The direction where to look (values 0-5); + * @return The found MapHex object, or null. + */ + public MapHex getAdjacentHex (MapHex hex, int orientation) { + + int x = hex.getX(); + int y = hex.getY(); + int xx = getAdjacentX (x, y, orientation); + int yy = getAdjacentY (x, y, orientation); + + if (xx >= minX && xx <= maxX && yy >= minY && yy <= maxY) { + return hexes[xx][yy]; // null if undefined + } + return null; //outside the map border + } + + /** Return a List of all hexes adjacent to a given hex. + * @param hex The hex object for which all adjacent hexes are searched. + * @return The found list of MapHex objects. Can be empty, not null. + */ + public List<MapHex> getAdjacentHexes (MapHex hex) { + + List<MapHex> adjacentHexes = new ArrayList<MapHex> (); + MapHex adjacentHex; + + for (int i=0; i<6; i++) { + if ((adjacentHex = getAdjacentHex (hex, i)) != null) adjacentHexes.add(adjacentHex); + } + return adjacentHexes; + } + /** * @return Returns the currentTileOrientation. */ @@ -443,7 +477,7 @@ public class MapManager implements ConfigurableComponentI { distances.get(hex1).put(hex2, depth); } - for (MapHex hex3 : hex2.getNeighbors()) { + for (MapHex hex3 : getAdjacentHexes(hex2)) { if (hex3 == null) continue; if (distances.get(hex1).get(hex3) == null) { calculateHexDistances (hex1, hex3, depth+1); @@ -494,7 +528,7 @@ public class MapManager implements ConfigurableComponentI { public int getMapXOffset() { return mapXOffset; } - + public int getMapYOffset() { return mapYOffset; } |
From: Erik V. <ev...@us...> - 2012-06-04 13:15:57
|
rails/game/StockRound.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) New commits: commit f654b2d5744b8048d6a78a04be9b760c8acc1631 Author: Erik Vos <eri...@xs...> Date: Mon Jun 4 15:15:11 2012 +0200 1856: allow selling a just bought share by the president. In theory a half-presidency share is then sold, which is allowed according to a clarification by Steve Thomas (backed by Bill Dixon). Apparently it does not matter if the presidency can actually be dumped. diff --git a/rails/game/StockRound.java b/rails/game/StockRound.java index 7f03de7..c4df970 100644 --- a/rails/game/StockRound.java +++ b/rails/game/StockRound.java @@ -479,7 +479,15 @@ public class StockRound extends Round { /* In some games (1856), a just bought share may not be sold */ // This code ignores the possibility of different share units if ((Boolean)gameManager.getGameParameter(GameDef.Parm.NO_SALE_OF_JUST_BOUGHT_CERT) - && company.equals(companyBoughtThisTurnWrapper.get())) { + && company.equals(companyBoughtThisTurnWrapper.get()) + /* An 1856 clarification by Steve Thomas (backed by Bill Dixon) states that + * in this situation a half-presidency may be sold + * (apparently even if a dump would otherwise not be allowed), + * as long as the number of shares does not become zero. + * So the rule "can't sell a just bought share" only means, + * that the number of shares may not be sold down to zero. + * Added 4jun2012 by EV */ + && number == ownedShare/shareUnit) { number--; } if (number <= 0) continue; |
From: Erik V. <ev...@us...> - 2012-06-05 20:07:08
|
rails/game/PublicCompany.java | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) New commits: commit ba0b55b7bf6e01dc671a578d9440bc17706481ba Author: John David Galt <jd...@di...> Date: Tue Jun 5 22:06:07 2012 +0200 Distance-dependent token lay costs can now have different base costs per laid token. Applies to 1837. diff --git a/rails/game/PublicCompany.java b/rails/game/PublicCompany.java index 7b38879..ba876c0 100644 --- a/rails/game/PublicCompany.java +++ b/rails/game/PublicCompany.java @@ -1761,21 +1761,25 @@ public class PublicCompany extends Company implements PublicCompanyI { if (baseTokenLayCost == null) return 0; - if (baseTokenLayCostMethod.equals(BASE_COST_SEQUENCE)) { - int index = getNumberOfLaidBaseTokens(); + /* Changed by JDG/EV: allow cost array for both calculation methods. + * In 1837, token lay cost per hex distance depends on + * the number of tokens laid before. */ + int index = getNumberOfLaidBaseTokens(); - if (index >= baseTokenLayCost.length) { - index = baseTokenLayCost.length - 1; - } else if (index < 0) { - index = 0; - } + if (index >= baseTokenLayCost.length) { + index = baseTokenLayCost.length - 1; + } else if (index < 0) { + index = 0; + } + + if (baseTokenLayCostMethod.equals(BASE_COST_SEQUENCE)) { return baseTokenLayCost[index]; } else if (baseTokenLayCostMethod.equals(BASE_COST_DISTANCE)) { if (hex == null) { - return baseTokenLayCost[0]; + return baseTokenLayCost[index]; } else { // WARNING: no provision yet for multiple home hexes. - return mapManager.getHexDistance(homeHexes.get(0), hex) * baseTokenLayCost[0]; + return mapManager.getHexDistance(homeHexes.get(0), hex) * baseTokenLayCost[index]; } } else { return 0; |
From: Erik V. <ev...@us...> - 2012-10-09 19:25:34
|
rails/game/OperatingRound.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) New commits: commit fa8142a7eec7a0ba5d2e5f795d0063908650db59 Author: Erik Vos <eri...@xs...> Date: Tue Oct 9 21:24:48 2012 +0200 When buying trains from other companies, include companies that have floated but do not operate in the current round for any reason. This fixes a bug reported by Volker Schnell where no train could be bought from the PR in the OR where the PR was formed. diff --git a/rails/game/OperatingRound.java b/rails/game/OperatingRound.java index d799ad9..4d77326 100644 --- a/rails/game/OperatingRound.java +++ b/rails/game/OperatingRound.java @@ -3062,7 +3062,9 @@ public class OperatingRound extends Round implements Observer { companiesPerPlayer.add(new ArrayList<PublicCompanyI>(4)); List<PublicCompanyI> companies; // Sort out which players preside over which companies. - for (PublicCompanyI c : getOperatingCompanies()) { + //for (PublicCompanyI c : getOperatingCompanies()) { + for (PublicCompanyI c : companyManager.getAllPublicCompanies()) { + if (!c.hasFloated()) continue; if (c.isClosed() || c == operatingCompany.get()) continue; p = c.getPresident(); index = p.getIndex(); |