2
* This program is free software; you can redistribute it and/or modify
3
* it under the terms of the GNU General Public License as published by
4
* the Free Software Foundation; either version 2 of the License, or
5
* (at your option) any later version.
6
* This program is distributed in the hope that it will be useful,
7
* but WITHOUT ANY WARRANTY; without even the implied warranty of
8
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9
* GNU General Public License for more details.
10
* You should have received a copy of the GNU General Public License
11
* along with this program; if not, write to the Free Software
12
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
* Created on January 6, 2002, 4:07 PM
21
package games.strategy.triplea.ui;
23
import games.strategy.engine.data.Change;
24
import games.strategy.engine.data.GameData;
25
import games.strategy.engine.data.GameStep;
26
import games.strategy.engine.data.PlayerID;
27
import games.strategy.engine.data.Resource;
28
import games.strategy.engine.data.Territory;
29
import games.strategy.engine.data.Unit;
30
import games.strategy.engine.data.UnitType;
31
import games.strategy.engine.data.events.GameDataChangeListener;
32
import games.strategy.engine.stats.AbstractStat;
33
import games.strategy.engine.stats.IStat;
34
import games.strategy.triplea.Constants;
35
import games.strategy.triplea.Properties;
36
import games.strategy.triplea.attatchments.PlayerAttachment;
37
import games.strategy.triplea.attatchments.TerritoryAttachment;
38
import games.strategy.triplea.delegate.BattleCalculator;
39
import games.strategy.triplea.delegate.Matches;
40
import games.strategy.triplea.delegate.OriginalOwnerTracker;
41
import games.strategy.triplea.delegate.TechAdvance;
42
import games.strategy.triplea.delegate.TechTracker;
43
import games.strategy.util.IntegerMap;
44
import games.strategy.util.Match;
46
import java.awt.Graphics;
47
import java.awt.GridLayout;
48
import java.awt.Image;
49
import java.util.ArrayList;
50
import java.util.Arrays;
51
import java.util.Collection;
52
import java.util.Collections;
53
import java.util.Comparator;
54
import java.util.HashMap;
55
import java.util.Iterator;
56
import java.util.List;
58
import java.util.TreeSet;
60
import javax.swing.JPanel;
61
import javax.swing.JScrollPane;
62
import javax.swing.JTable;
63
import javax.swing.SwingUtilities;
64
import javax.swing.table.AbstractTableModel;
65
import javax.swing.table.TableColumn;
66
import javax.swing.table.TableModel;
70
* @author Sean Bridges
72
public class StatPanel extends JPanel
74
private final StatTableModel m_dataModel;
75
private final TechTableModel m_techModel;
76
private IStat[] m_stats = new IStat[] {new PUStat(), new ProductionStat(), new UnitsStat(), new TUVStat()};
77
private GameData m_data;
78
private JTable m_statsTable;
79
private Image m_statsImage;
81
//sort based on first step
82
private final Comparator<PlayerID> m_playerOrderComparator = new Comparator<PlayerID>()
84
public int compare(PlayerID p1, PlayerID p2)
87
Iterator iter = m_data.getSequence().iterator();
91
GameStep s = (GameStep) iter.next();
93
if(s.getPlayerID() == null)
96
if(s.getPlayerID().equals(p1))
98
else if(s.getPlayerID().equals(p2))
106
/** Creates a new instance of InfoPanel */
107
public StatPanel(GameData data)
110
//only add the vc stat if we have some victory cities
111
if(Match.someMatch(data.getMap().getTerritories(), Matches.TerritoryIsVictoryCity))
113
List<IStat> stats = new ArrayList<IStat>(Arrays.asList(m_stats));
114
stats.add(new VictoryCityStat());
115
m_stats = stats.toArray(new IStat[stats.size()]);
117
//only add the vps in pacific
118
if(data.getProperties().get(Constants.PACIFIC_THEATER, false))
120
List<IStat> stats = new ArrayList<IStat>(Arrays.asList(m_stats));
121
stats.add(new VPStat());
122
m_stats = stats.toArray(new IStat[stats.size()]);
125
setLayout(new GridLayout(2, 1));
127
m_dataModel = new StatTableModel();
128
m_techModel = new TechTableModel();
130
m_statsTable = new JTable(m_dataModel) {
132
public void print(Graphics g)
134
if(m_statsImage != null)
135
g.drawImage(m_statsImage, 0, 0, null, null);
139
JTable table = m_statsTable;
140
// Strangely, this is enabled by default
141
table.getTableHeader().setReorderingAllowed(false);
143
// Set width of country column
144
TableColumn column = table.getColumnModel().getColumn(0);
145
column.setPreferredWidth(175);
147
JScrollPane scroll = new JScrollPane(table);
148
// add(scroll, BorderLayout.NORTH);
151
table = new JTable(m_techModel);
152
// Strangely, this is enabled by default
153
table.getTableHeader().setReorderingAllowed(false);
155
// Make the technology column big. Value chosen by trial and error
156
// The right way to do this is probably to get a FontMetrics object
157
// and measure the pixel width of the longest technology name in the
159
column = table.getColumnModel().getColumn(0);
160
column.setPreferredWidth(500);
162
scroll = new JScrollPane(table);
163
// add(scroll, BorderLayout.SOUTH);
169
public void setGameData(GameData data)
172
m_dataModel.setGameData(data);
173
m_techModel.setGameData(data);
174
m_dataModel.gameDataChanged(null);
175
m_techModel.gameDataChanged(null);
179
public void setStatsBgImage(Image image)
181
m_statsImage = image;
184
public StatTableModel getStatsModel()
189
public JTable getStatsTable()
194
public TableModel getTechModel()
201
* @return all the alliances with more than one player.
203
public Collection<String> getAlliances()
205
Iterator allAlliances = m_data.getAllianceTracker().getAlliances().iterator();
206
//order the alliances use a Tree Set
207
Collection<String> rVal = new TreeSet<String>();
209
while (allAlliances.hasNext())
211
String alliance = (String) allAlliances.next();
212
if (m_data.getAllianceTracker().getPlayersInAlliance(alliance).size() > 1)
221
public List<PlayerID> getPlayers()
223
List<PlayerID> players = new ArrayList<PlayerID>( m_data.getPlayerList().getPlayers());
224
Collections.sort(players,m_playerOrderComparator);
229
public IStat[] getStats()
237
* Custom table model.
239
* This model is thread safe.
241
class StatTableModel extends AbstractTableModel implements GameDataChangeListener
243
/* Flag to indicate whether data needs to be recalculated */
244
private boolean m_isDirty = true;
245
/* Column Header Names */
248
/* Underlying data for the table */
249
private String[][] m_collectedData;
252
public StatTableModel()
254
m_data.addDataChangeListener(this);
259
private synchronized void loadData()
261
m_data.acquireReadLock();
264
List players = getPlayers();
265
Collection<String> alliances = getAlliances();
267
m_collectedData = new String[players.size() + alliances.size()][m_stats.length + 1];
270
Iterator playerIter = players.iterator();
271
while (playerIter.hasNext())
273
PlayerID player = (PlayerID) playerIter.next();
275
m_collectedData[row][0] = player.getName();
276
for(int i = 0; i < m_stats.length; i++)
278
m_collectedData[row][i+1] = m_stats[i].getFormatter().format(m_stats[i].getValue(player, m_data));
282
Iterator<String> allianceIterator = alliances.iterator();
283
while (allianceIterator.hasNext())
285
String alliance = allianceIterator.next();
287
m_collectedData[row][0] = alliance;
288
for(int i = 0; i < m_stats.length; i++)
290
m_collectedData[row][i+1] = m_stats[i].getFormatter().format(m_stats[i].getValue(alliance, m_data));
297
m_data.releaseReadLock();
304
public void gameDataChanged(Change aChange)
311
SwingUtilities.invokeLater(new Runnable()
323
* Recalcs the underlying data in a lazy manner Limitation: This is not
324
* a threadsafe implementation
326
public synchronized Object getValueAt(int row, int col)
334
return m_collectedData[row][col];
337
// Trivial implementations of required methods
338
public String getColumnName(int col)
343
m_stats[col -1].getName();
346
public int getColumnCount()
348
return m_stats.length + 1;
351
public synchronized int getRowCount()
354
return m_collectedData.length;
357
//no need to recalculate all the stats just to get the row count
358
//getting the row count is a fairly frequent operation, and will
359
//happen even if we are not displayed!
360
m_data.acquireReadLock();
363
return m_data.getPlayerList().size() + getAlliances().size();
368
m_data.releaseReadLock();
373
public synchronized void setGameData(GameData data)
377
m_data.removeDataChangeListener(this);
379
m_data.addDataChangeListener(this);
387
class TechTableModel extends AbstractTableModel implements GameDataChangeListener
389
/* Flag to indicate whether data needs to be recalculated */
390
private boolean isDirty = true;
391
/* Column Header Names */
393
/* Row Header Names */
394
private String[] colList;
395
/* Underlying data for the table */
396
private String[][] data;
397
/* Convenience mapping of country names -> col */
398
private Map<String, Integer> colMap = null;
399
/* Convenience mapping of technology names -> row */
400
private Map<String, Integer> rowMap = null;
402
public TechTableModel()
404
m_data.addDataChangeListener(this);
408
/* Load the country -> col mapping */
409
colMap = new HashMap<String, Integer>();
410
for (int i = 0; i < colList.length; i++)
412
colMap.put(colList[i], new Integer(i + 1));
416
* .size()+1 added to stop index out of bounds errors when using an
419
boolean useTech = false;
420
if(m_data.getResourceList().getResource(Constants.TECH_TOKENS) != null)
423
data = new String[TechAdvance.getTechAdvances(m_data,null).size()+1][colList.length + 2];
427
data = new String[TechAdvance.getTechAdvances(m_data,null).size()][colList.length + 1];
430
/* Load the technology -> row mapping */
431
rowMap = new HashMap<String, Integer>();
432
Iterator iter = TechAdvance.getTechAdvances(m_data,null).iterator();
437
rowMap.put("Tokens", new Integer(row));
438
data[row][0] = "Tokens";
442
while (iter.hasNext())
444
TechAdvance tech = (TechAdvance) iter.next();
445
rowMap.put((tech).getName(), new Integer(row));
446
data[row][0] = tech.getName();
453
private void clearAdvances()
456
/* Initialize the table with the tech names */
457
for (int i = 0; i < data.length; i++)
459
for (int j = 1; j <= colList.length; j++)
467
private void initColList()
469
java.util.List<PlayerID> players = new ArrayList<PlayerID>(m_data.getPlayerList().getPlayers());
471
colList = new String[players.size()];
473
for (int i = 0; i < players.size(); i++)
475
colList[i] = players.get(i).getName();
478
Arrays.sort(colList, 0, players.size());
484
//copy so aquire/release read lock are on the same object!
485
final GameData gameData = m_data;
487
gameData.acquireReadLock();
490
Iterator playerIter = gameData.getPlayerList().getPlayers().iterator();
491
while (playerIter.hasNext())
493
PlayerID pid = (PlayerID) playerIter.next();
494
if (colMap.get(pid.getName()) == null)
495
throw new IllegalStateException("Unexpected player in GameData.getPlayerList()" + pid.getName());
497
int col = colMap.get(pid.getName()).intValue();
500
boolean useTokens = false;
501
if(m_data.getResourceList().getResource(Constants.TECH_TOKENS) != null)
504
Integer tokens = pid.getResources().getQuantity(Constants.TECH_TOKENS);
505
data[row][col] = tokens.toString();
508
Iterator advances = TechTracker.getTechAdvances(pid,m_data).iterator();
510
while (advances.hasNext())
513
TechAdvance advance = (TechAdvance) advances.next();
514
row = rowMap.get(advance.getName()).intValue();
515
// System.err.println("(" + row + ", " + col + ")");
516
data[row][col] = "X";
517
// data[row][col] = colList[col].substring(0, 1);
519
advances = TechAdvance.getTechAdvances(m_data,null).iterator();
520
List<TechAdvance> has = TechAdvance.getTechAdvances(m_data,pid);
521
while (advances.hasNext())
523
TechAdvance advance = (TechAdvance) advances.next();
524
//if(!pid.getTechnologyFrontierList().getAdvances().contains(advance)){
525
if(!has.contains(advance)) {
526
row = rowMap.get(advance.getName()).intValue();
527
data[row][col] = "-";
534
gameData.releaseReadLock();
538
public String getColumnName(int col)
542
return colList[col - 1].substring(0, 1);
546
* Recalcs the underlying data in a lazy manner Limitation: This is not
547
* a threadsafe implementation
549
public Object getValueAt(int row, int col)
557
return data[row][col];
560
// Trivial implementations of required methods
561
public int getColumnCount()
563
return colList.length + 1;
566
public int getRowCount()
571
public void gameDataChanged(Change aChange)
575
SwingUtilities.invokeLater(new Runnable()
586
public void setGameData(GameData data)
588
m_data.removeDataChangeListener(this);
590
m_data.addDataChangeListener(this);
598
class ProductionStat extends AbstractStat
601
public String getName()
606
public double getValue(PlayerID player, GameData data)
609
Iterator iter = data.getMap().getTerritories().iterator();
610
while (iter.hasNext())
612
boolean isOwnedConvoyOrLand = false;
613
Territory place = (Territory) iter.next();
614
OriginalOwnerTracker origOwnerTracker = new OriginalOwnerTracker();
615
TerritoryAttachment ta = TerritoryAttachment.get(place);
617
/* Check if terr is a Land Convoy Route and check ownership of neighboring Sea Zone*/
620
//if it's water, it is a Convoy Center
623
//Preset the original owner
624
PlayerID origOwner = ta.getOccupiedTerrOf();
625
if(origOwner == null)
626
origOwner = origOwnerTracker.getOriginalOwner(place);
628
//Can't get PUs for capturing a CC, only original owner can get them.
629
if (origOwner != PlayerID.NULL_PLAYERID && origOwner == player)
630
isOwnedConvoyOrLand = true;
632
if(origOwner == null)
633
isOwnedConvoyOrLand = true;
637
//if it's a convoy route
638
if (TerritoryAttachment.get(place).isConvoyRoute())
640
//Determine if both parts of the convoy route are owned by the attacker or allies
641
boolean ownedConvoyRoute = data.getMap().getNeighbors(place, Matches.territoryHasConvoyOwnedBy(player, data, place)).size() > 0;
643
isOwnedConvoyOrLand = true;
645
//it's a regular land territory
648
isOwnedConvoyOrLand = true;
653
if(place.getOwner().equals(player) && isOwnedConvoyOrLand)
655
rVal += ta.getProduction();
659
rVal*= Properties.getPU_Multiplier(data);
665
class PUStat extends AbstractStat
668
public String getName()
673
public double getValue(PlayerID player, GameData data)
675
return player.getResources().getQuantity(Constants.PUS);
680
class UnitsStat extends AbstractStat
683
public String getName()
688
public double getValue(PlayerID player, GameData data)
691
Match<Unit> ownedBy = Matches.unitIsOwnedBy(player);
692
Iterator iter = data.getMap().getTerritories().iterator();
693
while (iter.hasNext())
695
Territory place = (Territory) iter.next();
696
rVal += place.getUnits().countMatches(ownedBy);
703
class TUVStat extends AbstractStat
706
public String getName()
711
public double getValue(PlayerID player, GameData data)
713
IntegerMap<UnitType> costs = BattleCalculator.getCosts(player, data);
715
Match<Unit> unitIsOwnedBy = Matches.unitIsOwnedBy(player);
718
Iterator iter = data.getMap().getTerritories().iterator();
719
while (iter.hasNext())
721
Territory place = (Territory) iter.next();
722
Collection<Unit> owned = place.getUnits().getMatches(unitIsOwnedBy);
723
rVal += BattleCalculator.getTUV(owned, costs);
729
class VictoryCityStat extends AbstractStat
731
public String getName()
736
public double getValue(PlayerID player, GameData data)
739
Iterator iter = data.getMap().getTerritories().iterator();
740
while (iter.hasNext())
742
Territory place = (Territory) iter.next();
743
if(!place.getOwner().equals(player))
746
TerritoryAttachment ta = TerritoryAttachment.get(place);
750
if(ta.isVictoryCity())
757
class VPStat extends AbstractStat
759
public String getName()
764
public double getValue(PlayerID player, GameData data)
766
PlayerAttachment pa = PlayerAttachment.get(player);
768
return Double.parseDouble(pa.getVps());