// // @(#)StateWriter.java 6/2003 // // Copyright 2003 Zachary DelProposto. All rights reserved. // Use is subject to license terms. // // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // Or from http://www.gnu.org/ // package dip.gui.report; import dip.gui.ClientFrame; import dip.gui.dialog.TextViewer; import dip.world.*; import dip.order.*; import dip.order.OrderFormatOptions; import dip.misc.Utils; import dip.misc.Help; import dip.process.Adjustment; import javax.swing.JScrollPane; import java.util.*; /** * * Writes the current game state as HTML. * <p> * Includes: * <ol> * <li>Current Phase * <li>Current Unit Positions * <li>Current Orders * <li>Current Supply Center owners * <li>Current Adjustments <b>or</b> Current Retreats * </ol> * */ public class StateWriter { // i18n constants private static final String HTML_TEMPLATE = "StateWriter.template"; private static final String MSG_NO_ORDERS_SUBMITTED = "StateWriter.order.notsubmitted"; private static final String MSG_POWER_ELIMINATED = "StateWriter.order.eliminated"; private static final String MSG_UNAVAILABLE = "StateWriter.order.unavailable"; private static final String MSG_NONE = "StateWriter.quantity.none"; private static final String DISLODGED_HEADER_TEXT = "StateWriter.header.dislodged"; private static final String ADJUSTMENT_HEADER_TEXT = "StateWriter.header.adjustment"; private static final String NO_DISLODGED_UNITS = "StateWriter.dislodged.none"; private static final String ADJ_BUILD_TEXT = "StateWriter.adjustment.text.build"; private static final String ADJ_REMOVE_TEXT = "StateWriter.adjustment.text.remove"; private static final String ADJ_BASIC_TEXT = "StateWriter.adjustment.text"; private static final String ADJ_NOCHANGE_TEXT = "StateWriter.adjustment.text.nochange"; private static final String ADJ_BLOCKED_BUILD_TEXT = "StateWriter.adjustment.text.blockedbuilds"; private static final String SC_NUM = "StateWriter.sc.number"; private static final String ORD_TOO_FEW = "StateWriter.order.toofew"; // i18n dialog constants private static final String DIALOG_TITLE = "StateWriter.dialog.title"; // instance constants private final Power[] displayablePowers; private final TurnState turnState; private final Power[] allPowers; private final java.util.Map powerMap; private final Adjustment.AdjustmentInfoMap adjMap; private final OrderFormatOptions ofo; /** * Displays a summary of the current game state as HTML. * Obeys the displayablePowers setting (obtained from * ClientFrame). If no ClientFrame supplied, all displayable * powers are shown. */ public static String stateToHTML(ClientFrame cf, TurnState ts) { StateWriter sw = new StateWriter(cf, ts); return sw.getStateAsHTML(); }// stateToHTML() /** * Returns the HTML-encoded current state inside a dialog. */ public static void displayDialog(final ClientFrame clientFrame, final TurnState ts) { final StringBuffer title = new StringBuffer(64); title.append(Utils.getLocalString(DIALOG_TITLE)); title.append(": "); title.append(ts.getPhase()); TextViewer tv = new TextViewer(clientFrame); tv.setEditable(false); tv.addSingleButton( tv.makeOKButton() ); tv.setTitle(title.toString()); tv.setHelpID(Help.HelpID.Dialog_StatusReport); tv.setHeaderVisible(false); tv.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); tv.lazyLoadDisplayDialog(new TextViewer.TVRunnable() { public void run() { setText(stateToHTML(clientFrame, ts)); } }); }// displayDialog() /** StateWriter constructor */ private StateWriter(ClientFrame cf, TurnState ts) { assert(cf != null); turnState = ts; allPowers = ts.getWorld().getMap().getPowers(); displayablePowers = (cf == null) ? allPowers : cf.getDisplayablePowers(); powerMap = getUnitsByPower(); adjMap = Adjustment.getAdjustmentInfo(turnState, turnState.getWorld().getRuleOptions(), allPowers); ofo = cf.getOFO(); }// StateWriter() /** Write state as HTML */ private String getStateAsHTML() { // get template String templateText = Utils.getText(Utils.getLocalString(HTML_TEMPLATE)); // write retreat or adjustment information, if appropriate. String header = ""; String info = ""; if(turnState.getPhase().getPhaseType() == Phase.PhaseType.RETREAT) { header = Utils.getLocalString(DISLODGED_HEADER_TEXT); info = getDislodgedInfo(); } else if(turnState.getPhase().getPhaseType() == Phase.PhaseType.ADJUSTMENT) { header = Utils.getLocalString(ADJUSTMENT_HEADER_TEXT); info = getAdjustmentInfo(); } // get template objects Object[] templateData = new Object[] { turnState.getPhase(), // {0} : Phase getUnitLocationTable(), // {1} : Unit Location Table getOrders(), // {2} : Orders, by power. Non-displayed powers listed as "unknown" getSCInfo(), // {3} : Supply Center Information header, // {4} : Dislodged/Adjustment header (or empty) info // {5} : Dislodged/Adjustment info (or empty) }; // format into template return Utils.format(templateText, templateData); }// getState() /** * Unit Location Table<p> * The positions of all units -- for all powers -- are always available. * A light highlight color for the rows is applied for readability. */ private String getUnitLocationTable() { StringBuffer sb = new StringBuffer(1024); int nRows = -1; // max # of rows sb.append("<table cellpadding=\"3\" cellspacing\"3\">"); // column headers (the power name) sb.append("<tr>"); for(int i=0; i<allPowers.length; i++) { // odd columns have bgcolor highlights if((i & 1) == 0) { sb.append("<th bgcolor=\"F0F8FF\">"); } else { sb.append("<th>"); } sb.append("<u>"); sb.append(allPowers[i]); sb.append("</u></th>"); // determine the maximum number of rows we will have (not including // the power name) List list = (List) powerMap.get(allPowers[i]); if(list.size() > nRows) { nRows = list.size(); } } sb.append("</tr>"); // column data (unit locations) for(int i=0; i<nRows; i++) { sb.append("<tr>"); for(int j=0; j<allPowers.length; j++) { // odd columns have bg color if((j & 1) == 0) { sb.append("<td bgcolor=\"F0F8FF\">"); } else { sb.append("<td>"); } List list = (List) powerMap.get(allPowers[j]); if(i < list.size()) { sb.append(list.get(i)); } sb.append("</td>"); } sb.append("</tr>"); } sb.append("</table>"); return sb.toString(); }// getUnitLocationTable() /** * Write order information. Doesn't show orders for * powers that are hidden (not displayable) */ private String getOrders() { StringBuffer sb = new StringBuffer(2048); Position position = turnState.getPosition(); for(int i=0; i<allPowers.length; i++) { // print power name sb.append("<div class=\"indent1cm\"><b>"); sb.append(allPowers[i]); sb.append("</b></div>"); // if power is not displayable, mention that. boolean canShow = false; for(int z=0; z<displayablePowers.length; z++) { if(allPowers[i] == displayablePowers[z]) { canShow = true; break; } } sb.append("<div class=\"indent2cm\">"); if(canShow) { // print submission/elimination information List orders = turnState.getOrders(allPowers[i]); if(orders.size() > 0) { Iterator iter = orders.iterator(); while(iter.hasNext()) { Order order = (Order) iter.next(); sb.append( order.toFormattedString(ofo) ); sb.append("<br>\n"); } // but do we have orders for all units? // indicate if we do not. // this is phase dependent Adjustment.AdjustmentInfo adjInfo = adjMap.get(allPowers[i]); int diff = 0; if(turnState.getPhase().getPhaseType() == Phase.PhaseType.RETREAT) { diff = adjInfo.getDislodgedUnitCount() - orders.size(); } else if(turnState.getPhase().getPhaseType() == Phase.PhaseType.ADJUSTMENT) { diff = Math.abs(adjInfo.getAdjustmentAmount()) - orders.size(); } else if(turnState.getPhase().getPhaseType() == Phase.PhaseType.MOVEMENT) { diff = adjInfo.getUnitCount() - orders.size(); } if( diff > 0 ) { sb.append(Utils.getLocalString(ORD_TOO_FEW, new Integer(diff))); sb.append("<br>\n"); } } else { // if no orders are submitted, we must mention that, unless power // has been eliminated.... if(position.isEliminated(allPowers[i])) { sb.append(Utils.getLocalString(MSG_POWER_ELIMINATED)); } else { sb.append(Utils.getLocalString(MSG_NO_ORDERS_SUBMITTED)); } sb.append("<br>\n"); } } else { // (not available), unless eliminated if(position.isEliminated(allPowers[i])) { sb.append(Utils.getLocalString(MSG_POWER_ELIMINATED)); } else { sb.append(Utils.getLocalString(MSG_UNAVAILABLE)); } sb.append("<br>\n"); } sb.append("</div>"); } return sb.toString(); }// getOrders() /** Write SC ownership information */ private String getSCInfo() { StringBuffer sb = new StringBuffer(1024); sb.append("<div class=\"indent1cm\">"); Position position = turnState.getPosition(); // we're going to do this the slow, but simple way for(int i=0; i<allPowers.length; i++) { // create a sorted list of owned supply centers for this power. Province[] ownedSCs = position.getOwnedSupplyCenters(allPowers[i]); Arrays.sort(ownedSCs); // print the power name sb.append("<b>"); sb.append(allPowers[i]); sb.append(":</b> "); // print out the provinces if(ownedSCs.length > 0) { for(int z=0; z<ownedSCs.length; z++) { sb.append( ownedSCs[z].getFullName() ); sb.append(", "); } // delete the last ", " sb.delete( sb.length() - 2, sb.length()); sb.append(" "); sb.append(Utils.getLocalString(SC_NUM, new Integer(ownedSCs.length))); } else { sb.append(Utils.getLocalString(MSG_NONE)); } sb.append('.'); sb.append("<br>\n"); } sb.append("</div>"); return sb.toString(); }// getSCInfo() /** Write dislodged unit information. */ private String getDislodgedInfo() { // write dislodged units / powers. if none are // dislodged, indicate. Not super-efficient // ordered by powers (like SC ownership) boolean anyDislodged = false; Position position = turnState.getPosition(); StringBuffer sb = new StringBuffer(1024); sb.append("<div class=\"indent1cm\">"); for(int i=0; i<allPowers.length; i++) { Province[] dislodged = position.getDislodgedUnitProvinces(allPowers[i]); if(dislodged.length > 0) { anyDislodged = true; // print power name sb.append("<b>"); sb.append(allPowers[i]); sb.append(":</b> "); // print unit information, for each unit. // comma-separate. for(int z=0; z<dislodged.length-1; z++) { Unit unit = position.getDislodgedUnit(dislodged[z]); sb.append(' '); sb.append(unit.getType().getFullName()); sb.append(' '); sb.append(dislodged[z].getFullName()); sb.append(','); } // print last (no comma afterwards) Unit unit = position.getDislodgedUnit(dislodged[dislodged.length-1]); sb.append(' '); sb.append(unit.getType().getFullName()); sb.append(' '); sb.append(dislodged[dislodged.length-1].getFullName()); // finish the line. sb.append('.'); sb.append("<br>\n"); } } if(!anyDislodged) { sb.append(Utils.getLocalString(NO_DISLODGED_UNITS)); } sb.append("</div>"); return sb.toString(); }// getDislodgedInfo() /** Write adjustment information. */ private String getAdjustmentInfo() { StringBuffer sb = new StringBuffer(1024); sb.append("<div class=\"indent1cm\">"); // format using format string // many args... for(int i=0; i<allPowers.length; i++) { Adjustment.AdjustmentInfo adjInfo = adjMap.get(allPowers[i]); // determine build/remove/nochange text, and blocked builds String adjustmentText = null; // never null after below String blockedBuildMessage = ""; // empty if no builds are blocked int adjAmount = adjInfo.getAdjustmentAmount(); if(adjAmount > 0) { adjustmentText = Utils.getLocalString(ADJ_BUILD_TEXT, new Integer(Math.abs(adjAmount))); } else if(adjAmount < 0) { adjustmentText = Utils.getLocalString(ADJ_REMOVE_TEXT, new Integer(Math.abs(adjAmount))); } else { adjustmentText = Utils.getLocalString(ADJ_NOCHANGE_TEXT); } // blocked builds? int shouldBuild = (adjInfo.getSupplyCenterCount() - adjInfo.getUnitCount()); if(adjAmount >=0 && shouldBuild > adjAmount) { blockedBuildMessage = Utils.getLocalString(ADJ_BLOCKED_BUILD_TEXT, new Integer(shouldBuild - adjAmount)); } Object[] args = new Object[] { allPowers[i], // {0} : Power new Integer(adjInfo.getSupplyCenterCount()), // {1} : # SC (including home SC) controlled new Integer(adjInfo.getUnitCount()), // {2} : # units controlled adjustmentText, // {3} : build or remove (or no change) message blockedBuildMessage, // {4} : misc text (blocked builds), or empty }; sb.append(Utils.getLocalString(ADJ_BASIC_TEXT, args)); sb.append("<br>\n"); } sb.append("</div>"); return sb.toString(); }// getAdjustmentInfo() /** * Returns a Map of Power=>(List of Unit location names) * Dislodged units are underlined. Abbreviations * for province names are always used. * */ private java.util.Map getUnitsByPower() { java.util.Map pmap = new HashMap(); for(int i=0; i<allPowers.length; i++) { pmap.put(allPowers[i], new LinkedList()); } Position position = turnState.getPosition(); Province[] provinces = position.getProvinces(); for(int i=0; i<provinces.length; i++) { Province province = provinces[i]; if(position.hasUnit(province)) { Unit unit = position.getUnit(province); List uList = (List) pmap.get( unit.getPower() ); StringBuffer sb = new StringBuffer(16); sb.append( unit.getType().getShortName() ); sb.append(' '); sb.append( province.getShortName() ); uList.add( sb.toString() ); } if(position.hasDislodgedUnit(province)) { // dislodged units are underlined Unit unit = position.getDislodgedUnit(province); List uList = (List) pmap.get( unit.getPower() ); StringBuffer sb = new StringBuffer(16); sb.append("<u>"); sb.append( unit.getType().getShortName() ); sb.append("</u> <u>"); sb.append( province.getShortName() ); sb.append("</u>"); uList.add( sb.toString() ); } } // sort the lists. for(int i=0; i<allPowers.length; i++) { List list = (List) pmap.get( allPowers[i] ); Collections.sort(list); } return pmap; }// getUnitsByPower() }// class StateWriter()