-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTopLevelWindow.java
433 lines (387 loc) · 13.3 KB
/
TopLevelWindow.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
/**
* The TopLevelWindow class handles representation of the GameOfLife class. The
* application provides tools to change ticks, load and save grids, save desired
* ranges, and calculates changes in living. This application also supports
* color changes and output formatting.
*
* @author chinm3
*
*/
public class TopLevelWindow {
static GameOfLife game;
static JFrame frame;
static JPanel board;
protected static Cell[][] cells;
static JPanel stats;
static JLabel livetodead;
static JLabel tick;
static JLabel differences;
static JPanel loadSave;
protected static Color gridColor;
static int threadCount;
/**
* This constructs the main JFrame. Each component is initialized and added to
* the frame.
*/
public static void createWindow() {
board = new JPanel();
int rows = game.getRows();
int cols = game.getCols();
board.setLayout(new GridLayout(rows, cols));
//board.setPreferredSize(new Dimension(400, 400));
createBoard();
stats = new JPanel();
stats.setLayout(new BoxLayout(stats, BoxLayout.Y_AXIS));
stats.setPreferredSize(new Dimension(100, 100));
createStats();
loadSave = new JPanel();
loadSave.setPreferredSize(new Dimension(100, 100));
createLoadSave();
frame = new JFrame("Conway's Game of Life");
frame.setExtendedState( frame.getExtendedState()|JFrame.MAXIMIZED_BOTH );
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel direct = createDirection();
createMenu();
frame.getContentPane().add(loadSave, BorderLayout.LINE_START);
frame.getContentPane().add(board, BorderLayout.CENTER);
frame.getContentPane().add(direct, BorderLayout.NORTH);
frame.getContentPane().add(stats, BorderLayout.EAST);
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
game.saveOnExit();
}
}));
frame.validate();
frame.pack();
frame.setVisible(true);
}
/**
* This handles the Tools menu. It creates JMenuItems and adds them to a
* JMenuBar. The bar is then set to the JFrame.
*/
public static void createMenu() {
JMenuBar menuBar;
JMenu menu;
JMenuItem menuItem;
menuBar = new JMenuBar();
// Build the first menu.
menu = new JMenu("Tools");
menu.setMnemonic(KeyEvent.VK_A);
// menu.getAccessibleContext().setAccessibleDescription(
// "The only menu in this program that has menu items");
menuBar.add(menu);
// a group of JMenuItems
menuItem = new JMenuItem("Help", KeyEvent.VK_T);
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1, ActionEvent.ALT_MASK));
menuItem.getAccessibleContext().setAccessibleDescription("This doesn't really do anything");
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(frame,
"<html>USAGE<br>---------------------------------------------------------------------------<br>"
+ "Load a grid using the 'LOAD GRID' button on the GUI. The grid must be in Kuzmin's specified format. <br>Hit 'NEXT' to advance the game a tick, and 'PREV' to go back a tick. <br>Save the current tick using 'SAVE GRID'. Save a range of ticks up to the current using Alt+2 (Tools->Save range). <br>Set output pattern will save any new grid outputs named in the format 'inputstring'.txt</html>");
}
});
menu.add(menuItem);
menuItem = new JMenuItem("Set output pattern", null);
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_2, ActionEvent.ALT_MASK));
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
String out = (String) (JOptionPane.showInputDialog(frame, "Enter ouput pattern:",
"Customized Dialog", JOptionPane.PLAIN_MESSAGE, null, null, "0"));
game.setOutput(out);
} catch (Exception q) {
q.printStackTrace();
}
}
});
menu.add(menuItem);
menuItem = new JMenuItem("Generate large grid", null);
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_2, ActionEvent.ALT_MASK));
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
String newRows = (String) (JOptionPane.showInputDialog(frame, "Enter rows:", "Customized Dialog",
JOptionPane.PLAIN_MESSAGE, null, null, "0"));
String newCols = (String) (JOptionPane.showInputDialog(frame, "Enter columns:", "Customized Dialog",
JOptionPane.PLAIN_MESSAGE, null, null, "0"));
if(newRows.matches("\\d+") && newCols.matches("\\d+")) {
game.changeGridSize(Integer.parseInt(newRows), Integer.parseInt(newCols));
frame.dispose();
createWindow();
}else {
JOptionPane.showMessageDialog(frame, "Invalid dimension entered!");
return;
}
} catch (Exception q) {
q.printStackTrace();
}
}
});
menu.add(menuItem);
menuItem = new JMenuItem("Set thread count", null);
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_3, ActionEvent.ALT_MASK));
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Integer[] possibilities = new Integer[5];
possibilities[0] = 1;
possibilities[1] = 2;
possibilities[2] = 4;
possibilities[3] = 8;
possibilities[4] = 16;
try {
int threads = (int) (JOptionPane.showInputDialog(frame, "Enter number of threads:", "Customized Dialog",
JOptionPane.PLAIN_MESSAGE, null, possibilities, "1"));
game.setThreads(threads);
threadCount = threads;
} catch (Exception q) {
q.printStackTrace();
}
}
});
menu.add(menuItem);
menuItem = new JMenuItem("Save range", null);
menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_3, ActionEvent.ALT_MASK));
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Integer[] possibilities = new Integer[game.getTick()];
for (int i = 0; i < game.getTick(); i++) {
possibilities[i] = new Integer(i);
}
try {
int start = (int) (JOptionPane.showInputDialog(frame, "Enter start of range:", "Customized Dialog",
JOptionPane.PLAIN_MESSAGE, null, possibilities, "0"));
possibilities = new Integer[game.getTick() - start + 2];
for (int i = start + 1; i <= game.getTick(); i++) {
possibilities[i] = new Integer(i);
}
int end = (int) JOptionPane.showInputDialog(frame, "Enter end of range:", "Customized Dialog",
JOptionPane.PLAIN_MESSAGE, null, possibilities, game.getTick());
game.runSimulation(start, end);
} catch (Exception q) {
q.printStackTrace();
}
}
});
menu.add(menuItem);
frame.setJMenuBar(menuBar);
}
/**
* Handles stat initialization. JLabels are created and added to the JPanel
*/
public static void createStats() {
int live = game.getLive();
int dead = game.getRows() * game.getCols() - live;
livetodead = new JLabel("<html>LIVE: " + live + "<br>DEAD: " + dead + "<br>"
+ Character.toString((char) '\u0394') + " LIVE: " + 0 + "<br>" + Character.toString((char) '\u0394')
+ "% LIVE:<br>" + (double) Math.round(0 * 1000d) / 1000d);
tick = new JLabel("Tick: " + game.getTick());
stats.add(livetodead);
stats.add(tick);
}
/**
* Handles LIVE, DEAD, change in LIVE, and change in LIVE percentage. Sets
* JLabels text to display stats.
*/
public static void updateStats() {
String prevStat = livetodead.getText();
Matcher matcher = Pattern.compile("\\d+").matcher(prevStat);
matcher.find();
int prevLive = Integer.valueOf(matcher.group());
int total = game.getRows() * game.getCols();
double prevLivePercentage = (double) prevLive / total;
int live = game.getLive();
double currLivePercentage = (double) live / total;
double diff = currLivePercentage - prevLivePercentage;
int dead = total - live;
int liveDiff = live - prevLive;
livetodead.setText("<html>LIVE: " + live + "<br>DEAD: " + dead + "<br>" + Character.toString((char) '\u0394')
+ " LIVE: " + liveDiff + "<br>" + Character.toString((char) '\u0394') + "% LIVE:<br>"
+ (double) Math.round(diff * 1000d) / 1000d);
tick.setText("Tick: " + game.getTick());
}
/**
* This handles creation of the NEXT, PREV and CHANGE COLOR buttons. These are
* JButtons set with action listeners.
*
* @return JPanel containing the buttons NEXT, PREV, CHANGE COLOR
*/
public static JPanel createDirection() {
JPanel direction = new JPanel();
JButton next = new JButton();
JButton prev = new JButton();
prev.setBackground(Color.red);
next.setBackground(Color.green);
next.setText("NEXT");
prev.setText("PREV");
next.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
game.advanceTick();
//updateBoard();
updateStats();
}
});
prev.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
game.previousTick();
updateBoard();
updateStats();
}
});
JButton color = new JButton();
color.setText("CHANGE COLOR");
color.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
changeColor();
}
});
direction.add(prev);
direction.add(next);
direction.add(color);
direction.setPreferredSize(new Dimension(100, 100));
return direction;
}
/**
* Handles initialization of LOAD and SAVE GRID functions.
*
* @return JPanel component with load and save grid functions.
*/
public static JPanel createLoadSave() {
JButton load = new JButton();
load.setText("LOAD GRID");
load.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
loadFile();
}
});
JButton save = new JButton();
save.setText("SAVE GRID");
save.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (game.getTick() == 0) {
return;
}
saveGrid();
}
});
loadSave.add(load);
loadSave.add(save);
return loadSave;
}
/**
* Save grid used to output a grid at the current tick.
*/
public static void saveGrid() {
game.outputTickFile(game.getTick());
}
/**
* Updates board according to values in the GameOfLife instance.
*/
public static void updateBoard() {
int i, j;
for (i = 0; i < game.getRows(); i++) {
for (j = 0; j < game.getCols(); j++) {
if (game.getValueAt(i, j) == 1) {
cells[i][j].setLive(gridColor);
} else {
cells[i][j].setDead();
}
}
}
//frame.revalidate();
frame.repaint();
}
/**
* Handles creation of grid components. Initializes JButtons and colors then
* adds to the JPanel for the board.
*/
public static void createBoard() {
gridColor = Color.black;
int rows = game.getRows();
int cols = game.getCols();
cells = new Cell[rows][cols];
int i, j;
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
cells[i][j] = new Cell(Color.white);
if (game.getValueAt(i, j) == 1) {
cells[i][j].setLive(gridColor);
} else {
cells[i][j].setDead();
}
board.add(cells[i][j]);
}
}
}
/**
* Receives input of a JOptionPane and changes board color.
*/
public static void changeColor() {
Object[] possibilities = { "Black", "Red", "Green", "Blue" };
String s = (String) JOptionPane.showInputDialog(frame, "Select color", "Customized Dialog",
JOptionPane.PLAIN_MESSAGE, null, possibilities, "Black");
if (s.equals("Black")) {
gridColor = Color.black;
updateBoard();
} else if (s.equals("Red")) {
gridColor = Color.red;
updateBoard();
} else if (s.equals("Green")) {
gridColor = Color.green;
updateBoard();
} else if (s.equals("Blue")) {
gridColor = Color.blue;
updateBoard();
} else {
return;
}
}
/**
* Subroutine for LOAD GRID button. Allows user to input file path via
* JFileChooser.
*/
public static void loadFile() {
JFileChooser jfc = new JFileChooser(FileSystemView.getFileSystemView().getHomeDirectory());
File curr = new File(System.getProperty("user.dir"));
// jfc.setCurrentDirectory(curr);
int returnValue = jfc.showOpenDialog(null);
String path = "";
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = jfc.getSelectedFile();
path = selectedFile.getAbsolutePath();
}
try {
game.loadConfig(path);
} catch (Exception e) {
JOptionPane.showMessageDialog(frame, "Invalid file format!", "Error", JOptionPane.WARNING_MESSAGE);
}
frame.dispose();
createWindow();
}
public static void main(String[] args) {
game = new GameOfLife();
File file = new File("cfg.txt");
if (!file.exists()) {
System.out.println(
"No cfg.txt found. Loading default 'glider' pattern. One will be created at the end of this session");
game.loadConfig("glider");
}else {
try {
game.loadConfig("cfg.txt");
} catch (Exception e) {
e.printStackTrace();
}
}
createWindow();
}
}