package games.strategy.triplea.ui.history; import java.awt.BorderLayout; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.Resource; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.engine.history.HistoryNode; import games.strategy.engine.history.Renderable; import games.strategy.engine.history.Round; import games.strategy.engine.history.Step; import games.strategy.engine.random.IRandomStats; import games.strategy.engine.random.RandomStatsDetails; import games.strategy.triplea.Constants; import games.strategy.triplea.attachments.TerritoryAttachment; import games.strategy.triplea.delegate.DiceRoll; import games.strategy.triplea.delegate.Matches; import games.strategy.triplea.delegate.MoveDelegate; import games.strategy.triplea.delegate.OriginalOwnerTracker; import games.strategy.triplea.delegate.dataObjects.MoveDescription; import games.strategy.triplea.formatter.MyFormatter; import games.strategy.util.IntegerMap; public class HistoryLog extends JFrame { private static final long serialVersionUID = 4880602702815333376L; private final JTextArea m_textArea; private final StringWriter m_stringWriter; private final PrintWriter m_printWriter; public HistoryLog() { m_textArea = new JTextArea(40, 80); m_textArea.setEditable(false); final JScrollPane scrollingArea = new JScrollPane(m_textArea); // ... Get the content pane, set layout, add to center final JPanel content = new JPanel(); content.setLayout(new BorderLayout()); content.add(scrollingArea, BorderLayout.CENTER); m_stringWriter = new StringWriter(); m_printWriter = new PrintWriter(m_stringWriter); // ... Set window characteristics. this.setContentPane(content); this.setTitle("History Log"); this.pack(); this.setLocationRelativeTo(null); } public PrintWriter getWriter() { return m_printWriter; } @Override public String toString() { return m_stringWriter.toString(); } public void clear() { m_stringWriter.getBuffer().delete(0, m_stringWriter.getBuffer().length()); m_textArea.setText(""); } public void printFullTurn(final GameData data, final boolean verbose, final Collection<PlayerID> playersAllowed) { final HistoryNode printNode = data.getHistory().getLastNode(); HistoryNode curNode = printNode; Step stepNode = null; Step turnStartNode = null; PlayerID curPlayer = null; final Collection<PlayerID> players = new HashSet<>(); if (playersAllowed != null) { players.addAll(playersAllowed); } // find Step node, if exists in this path while (curNode != null) { if (curNode instanceof Step) { stepNode = (Step) curNode; break; } curNode = (HistoryNode) curNode.getPreviousNode(); } if (stepNode != null) { curPlayer = stepNode.getPlayerID(); if (players.isEmpty()) { players.add(curPlayer); } // get first step for this turn while (stepNode != null) { turnStartNode = stepNode; stepNode = (Step) stepNode.getPreviousSibling(); if (stepNode == null) { break; } if (stepNode.getPlayerID() == null) { break; } if (!players.contains(stepNode.getPlayerID())) { break; } } printRemainingTurn(turnStartNode, verbose, data.getDiceSides(), players); } else { System.err.println("No Step node found!"); } } private static PlayerID getPlayerID(final HistoryNode printNode) { DefaultMutableTreeNode curNode = printNode; final TreePath parentPath = (new TreePath(printNode.getPath())).getParentPath(); PlayerID curPlayer = null; if (parentPath != null) { final Object[] pathToNode = parentPath.getPath(); for (final Object pathNode : pathToNode) { final HistoryNode node = (HistoryNode) pathNode; if (node instanceof Step) { curPlayer = ((Step) node).getPlayerID(); } } } do { final Enumeration<?> nodeEnum = curNode.preorderEnumeration(); while (nodeEnum.hasMoreElements()) { final HistoryNode node = (HistoryNode) nodeEnum.nextElement(); if (node instanceof Step) { final String title = node.getTitle(); final PlayerID playerId = ((Step) node).getPlayerID(); if (!title.equals("Initializing Delegates")) { if (playerId != null) { curPlayer = playerId; } } } } curNode = curNode.getNextSibling(); } while ((curNode instanceof Step) && ((Step) curNode).getPlayerID().equals(curPlayer)); return curPlayer; } @SuppressWarnings("unchecked") public void printRemainingTurn(final HistoryNode printNode, final boolean verbose, final int diceSides, final Collection<PlayerID> playersAllowed) { final PrintWriter logWriter = m_printWriter; final String moreIndent = " "; // print out the parent nodes DefaultMutableTreeNode curNode = printNode; final TreePath parentPath = (new TreePath(printNode.getPath())).getParentPath(); PlayerID currentPlayer = null; if (parentPath != null) { final Object[] pathToNode = parentPath.getPath(); for (final Object pathNode : pathToNode) { final HistoryNode node = (HistoryNode) pathNode; for (int i = 0; i < node.getLevel(); i++) { logWriter.print(moreIndent); } logWriter.println(node.getTitle()); if (node.getLevel() == 0) { logWriter.println(); } if (node instanceof Step) { currentPlayer = ((Step) node).getPlayerID(); } } } final Collection<PlayerID> players = new HashSet<>(); if (playersAllowed != null) { players.addAll(playersAllowed); } if (currentPlayer != null) { players.add(currentPlayer); } final List<String> moveList = new ArrayList<>(); boolean moving = false; do { // keep track of conquered territory during combat String conquerStr = ""; final Enumeration<?> nodeEnum = curNode.preorderEnumeration(); while (nodeEnum.hasMoreElements()) { final HistoryNode node = (HistoryNode) nodeEnum.nextElement(); final String title = node.getTitle(); String indent = ""; for (int i = 0; i < node.getLevel(); i++) { indent = indent + moreIndent; } // flush move list if (moving && !(node instanceof Renderable)) { final Iterator<String> moveIter = moveList.iterator(); while (moveIter.hasNext()) { logWriter.println(moveIter.next()); moveIter.remove(); } moving = false; } if (node instanceof Renderable) { final Object details = ((Renderable) node).getRenderingData(); if (details instanceof DiceRoll) { if (!verbose) { continue; } final String diceMsg1 = title.substring(0, title.indexOf(':') + 1); if (diceMsg1.equals("")) { // tech roll logWriter.println(indent + moreIndent + title); } else { // dice roll // Japanese roll dice for 1 armour in Russia, round 1 logWriter.print(indent + moreIndent + diceMsg1); final DiceRoll diceRoll = (DiceRoll) details; final int hits = diceRoll.getHits(); int rolls = 0; for (int i = 1; i <= diceSides; i++) { rolls += diceRoll.getRolls(i).size(); } logWriter.println(" " + hits + "/" + rolls + " hits"); } } else if (details instanceof MoveDescription) { // movement final Pattern p = Pattern.compile("\\w+ undo move (\\d+)."); final Matcher m = p.matcher(title); if (m.matches()) { moveList.remove(Integer.valueOf(m.group(1)) - 1); } else { moveList.add(indent + title); moving = true; } } else if (details instanceof Collection) { final Collection<Object> objects = (Collection<Object>) details; final Iterator<Object> objIter = objects.iterator(); if (objIter.hasNext()) { final Object obj = objIter.next(); if (obj instanceof Unit) { final Collection<Unit> allUnitsInDetails = (Collection<Unit>) details; // purchase/place units - don't need details Unit unit = (Unit) obj; if (title.matches("\\w+ buy .*")) { logWriter.println(indent + title); } else if (title.matches("\\w+ attack with .*")) { logWriter.println(indent + title); } else if (title.matches("\\w+ defend with .*")) { logWriter.println(indent + title); } else if (title.matches("\\d+ \\w+ owned by the .*? lost .*")) { if (!verbose) { continue; } logWriter.println(indent + moreIndent + title); } else if (title.matches("\\d+ \\w+ owned by the .*? lost")) { if (!verbose) { continue; } logWriter.println(indent + moreIndent + title); } else if (title.startsWith("Battle casualty summary:")) { // logWriter.println(indent+"CAS1: "+title); logWriter.println( indent + conquerStr + ". Battle score " + title.substring(title.indexOf("for attacker is"))); conquerStr = ""; // separate units by player and show casualty summary final IntegerMap<PlayerID> unitCount = new IntegerMap<>(); unitCount.add(unit.getOwner(), 1); while (objIter.hasNext()) { unit = (Unit) objIter.next(); unitCount.add(unit.getOwner(), 1); } for (final PlayerID player : unitCount.keySet()) { logWriter.println(indent + "Casualties for " + player.getName() + ": " + MyFormatter.unitsToTextNoOwner(allUnitsInDetails, player)); } } else if (title.matches(".*? placed in .*")) { logWriter.println(indent + title); } else if (title.matches(".* owned by the \\w+ retreated to .*")) { logWriter.println(indent + title); } else if (title.matches("\\w+ win")) { conquerStr = title + conquerStr + " with " + MyFormatter.unitsToTextNoOwner(allUnitsInDetails) + " remaining"; } else { logWriter.println(indent + title); } } else { // collection of unhandled objects logWriter.println(indent + title); } } else { // empty collection of something if (title.matches("\\w+ win")) { conquerStr = title + conquerStr + " with no units remaining"; } else { // empty collection of unhandled objects logWriter.println(indent + title); } } } else if (details instanceof Territory) { // territory details logWriter.println(indent + title); } else if (details == null) { if (title.equals("Adding original owners")) { // do nothing } else if (title.equals(MoveDelegate.CLEANING_UP_DURING_MOVEMENT_PHASE)) { // do nothing } else if (title.equals("Game Loaded")) { // do nothing } else if (title.contains("now being played by")) { // do nothing } else if (title.contains("Turn Summary") || title.contains("Move Summary")) { // do nothing } else if (title.contains("Setting uses for triggers used")) { // do nothing } else if (title.equals("Resetting and Giving Bonus Movement to Units")) { // do nothing } else if (title.equals("Recording Battle Statistics")) { // do nothing } else if (title.equals("Preparing Airbases for Possible Scrambling")) { // do nothing } else if (title.matches("\\w+ collect \\d+ PUs?.*")) { logWriter.println(indent + title); } else if (title.matches("\\w+ takes? .*? from \\w+")) { // British take Libya from Germans if (moving) { final String str = moveList.remove(moveList.size() - 1); moveList.add(str + "\n " + indent + title.replaceAll(" takes ", " take ")); } else { conquerStr += title.replaceAll("^\\w+ takes ", ", taking "); } } else if (title.matches("\\w+ spend \\d+ on tech rolls")) { logWriter.println(indent + title); } else if (title.startsWith("Rolls to resolve tech hits:")) { // do nothing } else if (title.matches("\\w+ discover .*")) { logWriter.println(indent + title); } else if (title.matches("AA raid costs .*")) { logWriter.println(indent + title); } else { // unhandled message with null details logWriter.println(indent + title); } } else { // unknown details object logWriter.println(indent + title); } } else if (node instanceof Step) { final PlayerID playerId = ((Step) node).getPlayerID(); if (!title.equals("Initializing Delegates")) { logWriter.println(); logWriter.print(indent + title); if (playerId != null) { currentPlayer = playerId; players.add(currentPlayer); logWriter.print(" - " + playerId.getName()); } logWriter.println(); } } else if (node instanceof Round) { logWriter.println(); logWriter.println(indent + title); } else if (title.equals("Game History")) { logWriter.println(indent + title); } else { // unknown node type logWriter.println(indent + title); } } // while (nodeEnum.hasMoreElements()) curNode = curNode.getNextSibling(); } while (curNode != null && (curNode instanceof Step) && players.contains(((Step) curNode).getPlayerID())); // if we are mid-phase, this might not get flushed if (moving && !moveList.isEmpty()) { final Iterator<String> moveIter = moveList.iterator(); while (moveIter.hasNext()) { logWriter.println(moveIter.next()); moveIter.remove(); } moving = false; } logWriter.println(); logWriter.println(); m_textArea.setText(m_stringWriter.toString()); } public void printTerritorySummary(final HistoryNode printNode, final GameData data) { Collection<Territory> territories; final PlayerID player = getPlayerID(printNode); data.acquireReadLock(); try { territories = data.getMap().getTerritories(); } finally { data.releaseReadLock(); } final Collection<PlayerID> players = new HashSet<>(); players.add(player); printTerritorySummary(players, territories); } public void printTerritorySummary(final GameData data) { Collection<Territory> territories; PlayerID player; data.acquireReadLock(); try { player = data.getSequence().getStep().getPlayerID(); territories = data.getMap().getTerritories(); } finally { data.releaseReadLock(); } final Collection<PlayerID> players = new HashSet<>(); players.add(player); printTerritorySummary(players, territories); } public void printTerritorySummary(final GameData data, final Collection<PlayerID> allowedPlayers) { if (allowedPlayers == null || allowedPlayers.isEmpty()) { printTerritorySummary(data); return; } Collection<Territory> territories; data.acquireReadLock(); try { territories = data.getMap().getTerritories(); } finally { data.releaseReadLock(); } printTerritorySummary(allowedPlayers, territories); } private void printTerritorySummary(final Collection<PlayerID> players, final Collection<Territory> territories) { if (players == null || players.isEmpty() || territories == null || territories.isEmpty()) { return; } final PrintWriter logWriter = m_printWriter; // print all units in all territories, including "flags" logWriter.println("Territory Summary for " + MyFormatter.defaultNamedToTextList(players) + " : \n"); for (final Territory t : territories) { final List<Unit> ownedUnits = t.getUnits().getMatches(Matches.unitIsOwnedByOfAnyOfThesePlayers(players)); // see if there's a flag final TerritoryAttachment ta = TerritoryAttachment.get(t); boolean hasFlag = false; if (ta == null) { hasFlag = false; } else { hasFlag = t.getOwner() != null && players.contains(t.getOwner()) && (ta.getOriginalOwner() == null || !players.contains(ta.getOriginalOwner())); } if (hasFlag || !ownedUnits.isEmpty()) { logWriter.print(" " + t.getName() + " : "); if (hasFlag && ownedUnits.isEmpty()) { logWriter.println("1 flag"); } else if (hasFlag) { logWriter.print("1 flag, "); } // else if (ownedUnits.isEmpty()) // logWriter.print("nothing"); if (!ownedUnits.isEmpty()) { logWriter.println(MyFormatter.unitsToTextNoOwner(ownedUnits)); } } } logWriter.println(); logWriter.println(); m_textArea.setText(m_stringWriter.toString()); } public void printDiceStatistics(final GameData data, final IRandomStats randomStats) { final PrintWriter logWriter = m_printWriter; final RandomStatsDetails stats = randomStats.getRandomStats(data.getDiceSides()); final String diceStats = stats.getAllStatsString(" "); if (diceStats.length() > 0) { logWriter.println(diceStats); logWriter.println(); logWriter.println(); } m_textArea.setText(m_stringWriter.toString()); } public void printProductionSummary(final GameData data) { final PrintWriter logWriter = m_printWriter; Collection<PlayerID> players; Resource pus; data.acquireReadLock(); try { pus = data.getResourceList().getResource(Constants.PUS); players = data.getPlayerList().getPlayers(); } finally { data.releaseReadLock(); } if (pus == null) { return; } logWriter.println("Production/PUs Summary :\n"); for (final PlayerID player : players) { final int PUs = player.getResources().getQuantity(pus); final int production = getProduction(player, data); logWriter.println(" " + player.getName() + " : " + production + " / " + PUs); } logWriter.println(); logWriter.println(); m_textArea.setText(m_stringWriter.toString()); } // copied from StatPanel private int getProduction(final PlayerID player, final GameData data) { int rVal = 0; final Iterator<Territory> iter = data.getMap().getTerritories().iterator(); while (iter.hasNext()) { boolean isConvoyOrLand = false; final Territory place = iter.next(); final TerritoryAttachment ta = TerritoryAttachment.get(place); if (!place.isWater()) { isConvoyOrLand = true; } else if (place.isWater() && ta != null && OriginalOwnerTracker.getOriginalOwner(place) != PlayerID.NULL_PLAYERID && OriginalOwnerTracker.getOriginalOwner(place) == player && place.getOwner().equals(player)) { isConvoyOrLand = true; } if (place.getOwner().equals(player) && isConvoyOrLand) { if (ta != null) { rVal += ta.getProduction(); } } } return rVal; } }