root / trunk / code / projects / colonet / ColonetGUI / Colonet.java @ 107
History | View | Annotate | Download (23.5 KB)
1 | 32 | gtress | //
|
---|---|---|---|
2 | // Colonet.java
|
||
3 | //
|
||
4 | |||
5 | import javax.swing.*; |
||
6 | import java.awt.*; |
||
7 | import java.awt.image.*; |
||
8 | import java.awt.event.*; |
||
9 | import java.net.*; |
||
10 | import java.io.*; |
||
11 | import java.util.Random; |
||
12 | import java.applet.*; |
||
13 | |||
14 | public class Colonet extends JApplet implements ActionListener, MouseListener, Runnable { |
||
15 | |||
16 | final int CANVAS_SIZE = 500; //don't make this too large, or the applet will be slow. |
||
17 | final int BUFFER = 50; |
||
18 | final int RADIUS = 30; |
||
19 | |||
20 | // Connection
|
||
21 | JTextField txtHost;
|
||
22 | JTextField txtPort;
|
||
23 | JButton btnConnect;
|
||
24 | JButton btnGraph;
|
||
25 | JLabel lblConnectionStatus;
|
||
26 | 76 | gtress | JTextArea txtMatrix;
|
27 | 32 | gtress | JTextArea txtInfo;
|
28 | JPanel panelConnect;
|
||
29 | JPanel panelServerInterface;
|
||
30 | |||
31 | // Stats
|
||
32 | JLabel lblBattery;
|
||
33 | JLabel lblTokenPasses;
|
||
34 | JLabel lblHastToken;
|
||
35 | JPanel panelStats;
|
||
36 | |||
37 | // South
|
||
38 | JPanel panelSouth;
|
||
39 | JTextArea log;
|
||
40 | JScrollPane spLog;
|
||
41 | |||
42 | // Control
|
||
43 | JPanel panelControl;
|
||
44 | JTabbedPane tabPaneControl;
|
||
45 | JPanel panelRobotControl;
|
||
46 | JPanel panelRobotDirection;
|
||
47 | JPanel panelRobotCommands;
|
||
48 | JButton btnF, btnB, btnL, btnR, btnActivate;
|
||
49 | 67 | gtress | JComboBox cmbRobotNum;
|
50 | 32 | gtress | |
51 | // Task Manager
|
||
52 | JPanel panelTaskManager;
|
||
53 | JScrollPane spTaskManager;
|
||
54 | JPanel panelTaskManagerControls;
|
||
55 | JPanel panelTaskManagerControlsPriority;
|
||
56 | DefaultListModel taskListModel;
|
||
57 | JList taskList;
|
||
58 | JButton btnAddTask;
|
||
59 | JButton btnRemoveTask;
|
||
60 | JButton btnMoveTaskUp;
|
||
61 | JButton btnMoveTaskDown;
|
||
62 | |||
63 | // Graphics
|
||
64 | JPanel panel;
|
||
65 | GraphicsConfiguration gc;
|
||
66 | volatile BufferedImage image; |
||
67 | volatile Graphics2D canvas; |
||
68 | int cx, cy;
|
||
69 | |||
70 | Socket socket;
|
||
71 | OutputStreamWriter out; //TODO: add a BufferedWriter |
||
72 | 76 | gtress | DataUpdater dataUpdater; |
73 | 32 | gtress | |
74 | Font botFont;
|
||
75 | Random random = new Random(); |
||
76 | volatile int tokenLoc; //the token is currently here |
||
77 | volatile int numBots; |
||
78 | volatile int selectedBot; //the user has selected this bot |
||
79 | volatile Rectangle[] botRect; //contains boundary shapes around bots for click detection |
||
80 | |||
81 | Thread drawThread;
|
||
82 | 76 | gtress | Simulator simulator; |
83 | 32 | gtress | SelectionIndicator indicator; |
84 | PacketMonitor packetMonitor; |
||
85 | 35 | gtress | ColonetServerInterface csi; |
86 | 32 | gtress | |
87 | |||
88 | public void init () { |
||
89 | // set the default look and feel
|
||
90 | String laf = UIManager.getSystemLookAndFeelClassName(); |
||
91 | try {
|
||
92 | UIManager.setLookAndFeel(laf);
|
||
93 | } catch (UnsupportedLookAndFeelException exc) { |
||
94 | System.err.println ("Warning: UnsupportedLookAndFeel: " + laf); |
||
95 | } catch (Exception exc) { |
||
96 | System.err.println ("Error loading " + laf + ": " + exc); |
||
97 | } |
||
98 | // We should invoke and wait to avoid browser display difficulties
|
||
99 | Runnable r = new Runnable() { |
||
100 | public void run() { |
||
101 | createAndShowGUI(); |
||
102 | } |
||
103 | }; |
||
104 | try {
|
||
105 | SwingUtilities.invokeAndWait(r);
|
||
106 | } catch (InterruptedException e) { |
||
107 | //Not really sure why we would be in this situation
|
||
108 | System.out.println(e);
|
||
109 | } catch (java.lang.reflect.InvocationTargetException e) {
|
||
110 | //This should never happen. Seriously.
|
||
111 | System.out.println(e);
|
||
112 | } |
||
113 | } |
||
114 | |||
115 | public void destroy () { |
||
116 | try { drawThread.interrupt(); } catch (Exception e) { } |
||
117 | try { indicator.interrupt(); } catch (Exception e) { } |
||
118 | try { packetMonitor.interrupt(); } catch (Exception e) { } |
||
119 | } |
||
120 | |||
121 | private synchronized void createAndShowGUI () { |
||
122 | // init graphical elements
|
||
123 | panel = new JPanel(false); //set automatic double-buffering to false. we are doing it manually. |
||
124 | |||
125 | // Connection area
|
||
126 | 76 | gtress | txtMatrix = new JTextArea("- 9 3 - 1\n- - - 5 -\n4 - - - 2\n- - - - -\n1 - - 3 -"); |
127 | txtMatrix.setBorder(BorderFactory.createTitledBorder("Input Matrix")); |
||
128 | 32 | gtress | txtInfo = new JTextArea(); |
129 | txtInfo.setBorder(BorderFactory.createTitledBorder("Info")); |
||
130 | txtInfo.setEditable(false);
|
||
131 | btnGraph = new JButton("Run"); |
||
132 | txtHost = new JTextField("roboclub1.frc.ri.cmu.edu"); |
||
133 | txtHost.setBorder(BorderFactory.createTitledBorder("Host")); |
||
134 | txtPort = new JTextField("10123"); |
||
135 | txtPort.setBorder(BorderFactory.createTitledBorder("Port")); |
||
136 | btnConnect = new JButton("Connect"); |
||
137 | lblConnectionStatus = new JLabel("Status: Offline"); |
||
138 | panelConnect = new JPanel(); |
||
139 | panelConnect.setLayout(new GridLayout(6,1)); |
||
140 | panelConnect.add(lblConnectionStatus); |
||
141 | panelConnect.add(txtHost); |
||
142 | panelConnect.add(txtPort); |
||
143 | panelConnect.add(btnConnect); |
||
144 | panelConnect.add(txtInfo); |
||
145 | panelConnect.add(btnGraph); |
||
146 | panelServerInterface = new JPanel(); |
||
147 | panelServerInterface.setLayout(new GridLayout(2,1)); |
||
148 | panelServerInterface.add(panelConnect); |
||
149 | 76 | gtress | panelServerInterface.add(txtMatrix); |
150 | 32 | gtress | |
151 | // Status Elements
|
||
152 | lblTokenPasses = new JLabel(); |
||
153 | lblBattery = new JLabel("???"); |
||
154 | panelStats = new JPanel(); |
||
155 | panelStats.setLayout(new GridLayout(4,2)); |
||
156 | panelStats.add(new JLabel("Token Passes / sec ")); |
||
157 | panelStats.add(lblTokenPasses); |
||
158 | panelStats.add(new JLabel("Battery ")); |
||
159 | panelStats.add(lblBattery); |
||
160 | panelStats.add(new JLabel("Token Passes / sec ")); |
||
161 | panelStats.add(lblTokenPasses); |
||
162 | |||
163 | 72 | gtress | //TODO: add panelStats somewhere?
|
164 | 32 | gtress | |
165 | // Robot direction panel
|
||
166 | panelRobotDirection = new JPanel(); |
||
167 | btnF = new JButton("^"); |
||
168 | btnB = new JButton("v"); |
||
169 | btnL = new JButton("<"); |
||
170 | btnR = new JButton(">"); |
||
171 | btnActivate = new JButton("o"); |
||
172 | panelRobotDirection = new JPanel(); |
||
173 | panelRobotDirection.setLayout(new GridLayout(3,3)); |
||
174 | panelRobotDirection.add(new JLabel("")); |
||
175 | panelRobotDirection.add(btnF); |
||
176 | panelRobotDirection.add(new JLabel("")); |
||
177 | panelRobotDirection.add(btnL); |
||
178 | panelRobotDirection.add(btnActivate); |
||
179 | panelRobotDirection.add(btnR); |
||
180 | panelRobotDirection.add(new JLabel("")); |
||
181 | panelRobotDirection.add(btnB); |
||
182 | panelRobotDirection.add(new JLabel("")); |
||
183 | |||
184 | // Robot Control and Commands
|
||
185 | panelRobotCommands = new JPanel(); |
||
186 | panelRobotCommands.setLayout(new FlowLayout()); |
||
187 | 67 | gtress | cmbRobotNum = new JComboBox(); |
188 | panelRobotCommands.add(cmbRobotNum); |
||
189 | 32 | gtress | panelRobotCommands.add(new JLabel("Commands go here")); |
190 | panelRobotControl = new JPanel(); |
||
191 | panelRobotControl.setLayout(new GridLayout(2,1)); |
||
192 | panelRobotControl.add(panelRobotDirection); |
||
193 | panelRobotControl.add(panelRobotCommands); |
||
194 | |||
195 | // Task Manager
|
||
196 | panelTaskManager = new JPanel(); |
||
197 | panelTaskManager.setLayout(new BorderLayout()); |
||
198 | taskListModel = new DefaultListModel(); |
||
199 | taskListModel.addElement("Map the Environment");
|
||
200 | taskListModel.addElement("Clean Up Chemical Spill");
|
||
201 | taskListModel.addElement("Grow Plants");
|
||
202 | taskListModel.addElement("Save the Cheerleader");
|
||
203 | taskListModel.addElement("Save the World");
|
||
204 | taskList = new JList(taskListModel); |
||
205 | taskList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||
206 | taskList.setSelectedIndex(0);
|
||
207 | spTaskManager = new JScrollPane(taskList); |
||
208 | panelTaskManagerControls = new JPanel(); |
||
209 | panelTaskManagerControls.setLayout(new GridLayout(1,3)); |
||
210 | panelTaskManagerControlsPriority = new JPanel(); |
||
211 | panelTaskManagerControlsPriority.setLayout(new GridLayout(1,2)); |
||
212 | btnAddTask = new JButton("Add..."); |
||
213 | btnRemoveTask = new JButton("Remove"); |
||
214 | btnMoveTaskUp = new JButton("^"); |
||
215 | btnMoveTaskDown = new JButton("v"); |
||
216 | panelTaskManagerControlsPriority.add(btnMoveTaskUp); |
||
217 | panelTaskManagerControlsPriority.add(btnMoveTaskDown); |
||
218 | panelTaskManagerControls.add(btnAddTask); |
||
219 | panelTaskManagerControls.add(btnRemoveTask); |
||
220 | panelTaskManagerControls.add(panelTaskManagerControlsPriority); |
||
221 | panelTaskManager.add(spTaskManager, BorderLayout.CENTER);
|
||
222 | panelTaskManager.add(panelTaskManagerControls, BorderLayout.SOUTH);
|
||
223 | panelTaskManager.add(new JLabel("Current Task Queue"), BorderLayout.NORTH); |
||
224 | |||
225 | // Message log
|
||
226 | log = new JTextArea(); |
||
227 | spLog = new JScrollPane(log, |
||
228 | ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
|
||
229 | ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||
230 | spLog.setBorder(BorderFactory.createTitledBorder("Log")); |
||
231 | spLog.setPreferredSize(new Dimension(0, 150)); |
||
232 | log.setEditable(false);
|
||
233 | |||
234 | // Main control mechanism
|
||
235 | panelControl = new JPanel(); |
||
236 | panelControl.setLayout(new GridLayout(1,1)); |
||
237 | tabPaneControl = new JTabbedPane(JTabbedPane.TOP); |
||
238 | tabPaneControl.setPreferredSize(new Dimension(300, 0)); |
||
239 | tabPaneControl.addTab("Connection", panelServerInterface);
|
||
240 | tabPaneControl.addTab("Robots", panelRobotControl);
|
||
241 | tabPaneControl.addTab("Tasks", panelTaskManager);
|
||
242 | panelControl.add(tabPaneControl); |
||
243 | |||
244 | // Set up elements in the south
|
||
245 | panelSouth = new JPanel(); |
||
246 | panelSouth.setLayout(new GridLayout(1,2)); |
||
247 | panelSouth.add(spLog); |
||
248 | |||
249 | this.getContentPane().setLayout(new BorderLayout()); |
||
250 | this.getContentPane().add(panel, BorderLayout.CENTER); |
||
251 | this.getContentPane().add(panelSouth, BorderLayout.SOUTH); |
||
252 | this.getContentPane().add(panelControl, BorderLayout.EAST); |
||
253 | this.setVisible(true); |
||
254 | |||
255 | 72 | gtress | /* Add all listeners here */
|
256 | // Task Management
|
||
257 | 32 | gtress | btnAddTask.addActionListener(this);
|
258 | btnRemoveTask.addActionListener(this);
|
||
259 | btnMoveTaskUp.addActionListener(this);
|
||
260 | btnMoveTaskDown.addActionListener(this);
|
||
261 | 72 | gtress | // Robot Control
|
262 | btnF.addActionListener(this);
|
||
263 | btnB.addActionListener(this);
|
||
264 | btnL.addActionListener(this);
|
||
265 | btnR.addActionListener(this);
|
||
266 | btnActivate.addActionListener(this);
|
||
267 | // Other
|
||
268 | 32 | gtress | btnGraph.addActionListener(this);
|
269 | btnConnect.addActionListener(this);
|
||
270 | panel.addMouseListener(this);
|
||
271 | |||
272 | |||
273 | // Get the graphics configuration of the screen to create a buffer
|
||
274 | gc = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
||
275 | .getDefaultScreenDevice().getDefaultConfiguration(); |
||
276 | image = gc.createCompatibleImage(CANVAS_SIZE,CANVAS_SIZE); |
||
277 | canvas = image.createGraphics(); |
||
278 | canvas.setStroke(new BasicStroke(2)); //set pen width |
||
279 | |||
280 | // Calculate center of canvas
|
||
281 | cx = image.getWidth() / 2;
|
||
282 | cy = image.getHeight() / 2;
|
||
283 | |||
284 | botFont = new Font("Arial", Font.PLAIN, 30); |
||
285 | tokenLoc = 0;
|
||
286 | numBots = 0;
|
||
287 | selectedBot = 0;
|
||
288 | |||
289 | // Set up dependent threads
|
||
290 | indicator = new SelectionIndicator(canvas);
|
||
291 | indicator.setRadius(RADIUS+3, 15); //a tad more than the bot radius |
||
292 | packetMonitor = new PacketMonitor();
|
||
293 | 76 | gtress | simulator = new Simulator();
|
294 | 35 | gtress | |
295 | 76 | gtress | csi = new ColonetServerInterface(log, txtMatrix);
|
296 | 32 | gtress | |
297 | } |
||
298 | |||
299 | 35 | gtress | public synchronized void paint (Graphics g) { |
300 | 32 | gtress | /* First, redraw the graphical components in the applet.
|
301 | This paint method overrides the built-in paint of the
|
||
302 | JApplet, and we don't want to deal with redrawing the
|
||
303 | components manually. Fuck that shit. */
|
||
304 | super.paint(g);
|
||
305 | |||
306 | // Place the buffered image on the screen, inside the panel
|
||
307 | panel.getGraphics().drawImage(image, 0, 0, Color.WHITE, this); |
||
308 | |||
309 | } |
||
310 | |||
311 | 35 | gtress | public synchronized void update (Graphics g) { |
312 | 32 | gtress | paint(g); |
313 | } |
||
314 | |||
315 | 35 | gtress | public void actionPerformed (ActionEvent e) { |
316 | 32 | gtress | Object source = e.getSource();
|
317 | if (source == btnGraph) {
|
||
318 | btnGraph.setEnabled(false);
|
||
319 | lblConnectionStatus.setText("Simulating");
|
||
320 | |||
321 | //Start dependent threads
|
||
322 | drawThread = new Thread(this, "drawThread"); |
||
323 | drawThread.start(); |
||
324 | indicator.start(); |
||
325 | packetMonitor.start(); |
||
326 | 76 | gtress | simulator.start(); |
327 | 32 | gtress | } else if (source == btnConnect) { |
328 | doSocket(); |
||
329 | 76 | gtress | dataUpdater = new DataUpdater();
|
330 | dataUpdater.start(); |
||
331 | 32 | gtress | } else if (source == btnRemoveTask) { |
332 | try {
|
||
333 | taskListModel.remove(taskList.getSelectedIndex()); |
||
334 | } catch (ArrayIndexOutOfBoundsException ex) { |
||
335 | } |
||
336 | 72 | gtress | |
337 | // Robot controls
|
||
338 | } else if (source == btnF) { |
||
339 | csi.sendData(ColonetServerInterface.MOTOR1_SET + " 0 200", ColonetServerInterface.GLOBAL_DEST);
|
||
340 | csi.sendData(ColonetServerInterface.MOTOR2_SET + " 0 200", ColonetServerInterface.GLOBAL_DEST);
|
||
341 | } else if (source == btnB) { |
||
342 | csi.sendData(ColonetServerInterface.MOTOR1_SET + " 1 200", ColonetServerInterface.GLOBAL_DEST);
|
||
343 | csi.sendData(ColonetServerInterface.MOTOR2_SET + " 1 200", ColonetServerInterface.GLOBAL_DEST);
|
||
344 | } else if (source == btnL) { |
||
345 | csi.sendData(ColonetServerInterface.MOTOR1_SET + " 1 200", ColonetServerInterface.GLOBAL_DEST);
|
||
346 | csi.sendData(ColonetServerInterface.MOTOR2_SET + " 0 200", ColonetServerInterface.GLOBAL_DEST);
|
||
347 | } else if (source == btnR) { |
||
348 | csi.sendData(ColonetServerInterface.MOTOR1_SET + " 0 200", ColonetServerInterface.GLOBAL_DEST);
|
||
349 | csi.sendData(ColonetServerInterface.MOTOR2_SET + " 1 200", ColonetServerInterface.GLOBAL_DEST);
|
||
350 | } else if (source == btnActivate) { |
||
351 | csi.sendData(ColonetServerInterface.MOTOR1_SET + " 0 0", ColonetServerInterface.GLOBAL_DEST);
|
||
352 | csi.sendData(ColonetServerInterface.MOTOR2_SET + " 0 0", ColonetServerInterface.GLOBAL_DEST);
|
||
353 | 107 | gtress | |
354 | |||
355 | // Queue Management
|
||
356 | } else if (source == btnAddTask) { |
||
357 | String description = JOptionPane.showInputDialog("Description of new task"); |
||
358 | String data = JOptionPane.showInputDialog("Command and data for new task"); |
||
359 | csi.sendQueueAdd(0, data, description);
|
||
360 | } else if (source == btnRemoveTask) { |
||
361 | csi.sendQueueRemove(0);
|
||
362 | } else if (source == btnMoveTaskUp) { |
||
363 | csi.sendQueueReorder(0, 1); |
||
364 | } else if (source == btnMoveTaskDown) { |
||
365 | |||
366 | |||
367 | 32 | gtress | } |
368 | } |
||
369 | |||
370 | 35 | gtress | private void randomize () { |
371 | 32 | gtress | Random r = new Random(); |
372 | StringBuilder s = new StringBuilder(); |
||
373 | |||
374 | int count = r.nextInt(8) + 1; |
||
375 | 35 | gtress | for (int i = 0; i < count; i++) { |
376 | for (int j = 0; j < count; j++) { |
||
377 | 32 | gtress | if (r.nextBoolean())
|
378 | s.append("" + (r.nextInt(16) + 1)); |
||
379 | else
|
||
380 | s.append("-");
|
||
381 | if (j != count-1) |
||
382 | s.append(" ");
|
||
383 | } |
||
384 | if (i != count-1) s.append("\n"); |
||
385 | } |
||
386 | |||
387 | 76 | gtress | txtMatrix.setText(s.toString()); |
388 | 32 | gtress | } |
389 | |||
390 | 35 | gtress | private void doSocket () { |
391 | csi.connect(txtHost.getText(), txtPort.getText()); |
||
392 | 32 | gtress | } |
393 | |||
394 | 35 | gtress | public void drawRobot (int id, int x, int y) { |
395 | 32 | gtress | //save the bot in memory, so we can tell if we click on it later
|
396 | botRect[id] = new Rectangle(x-RADIUS, y-RADIUS, 2*RADIUS, 2*RADIUS); |
||
397 | |||
398 | //draw the bot on the canvas
|
||
399 | canvas.setColor(Color.BLACK);
|
||
400 | canvas.drawOval(x-RADIUS, y-RADIUS, RADIUS*2, RADIUS*2); |
||
401 | |||
402 | //draw the label
|
||
403 | canvas.setFont(botFont); |
||
404 | canvas.drawString("" + id, x-10, y+10); |
||
405 | } |
||
406 | |||
407 | 35 | gtress | public void drawConnection (int start, int end, int radius, Color color) { |
408 | 32 | gtress | final int ARROW_LENGTH = 18; |
409 | |||
410 | double angle = 2.0 * Math.PI / numBots; |
||
411 | int startx, starty, endx, endy;
|
||
412 | startx = cx - (int)(radius * Math.cos(start * angle)); |
||
413 | starty = cy - (int)(radius * Math.sin(start * angle)); |
||
414 | endx = cx - (int)(radius * Math.cos(end * angle)); |
||
415 | endy = cy - (int)(radius * Math.sin(end * angle)); |
||
416 | canvas.setColor(color); |
||
417 | canvas.drawLine(startx, starty, endx, endy); |
||
418 | |||
419 | //create arrow
|
||
420 | if (color.equals(Color.BLACK)) return; |
||
421 | int big_dy = starty - endy;
|
||
422 | int big_dx = endx - startx;
|
||
423 | double theta = 0; |
||
424 | if (big_dx == 0 && starty > endy) //pointing up |
||
425 | theta = Math.PI/2; |
||
426 | else if (big_dx == 0 && starty < endy) //pointing down |
||
427 | theta = 3*Math.PI/2; |
||
428 | else if (big_dy == 0 && startx > endx) //pointing left |
||
429 | theta = Math.PI;
|
||
430 | else if (big_dy == 0 && startx < endx) //pointing right |
||
431 | theta = 0;
|
||
432 | else
|
||
433 | theta = Math.atan(1.0 * big_dy / big_dx); |
||
434 | |||
435 | //create ploygon
|
||
436 | Polygon poly = new Polygon(); |
||
437 | int dx_arrow = Math.abs((int)(ARROW_LENGTH * Math.cos(theta))); |
||
438 | int dy_arrow = Math.abs((int)(ARROW_LENGTH * Math.sin(theta))); |
||
439 | int dy_half = (int)(ARROW_LENGTH/2 * Math.cos(theta)); |
||
440 | int dx_half = (int)(ARROW_LENGTH/2 * Math.sin(theta)); |
||
441 | int rx = (big_dx > 0) ? endx - dx_arrow : endx + dx_arrow; |
||
442 | int ry = (big_dy > 0) ? endy + dy_arrow : endy - dy_arrow; |
||
443 | poly.addPoint(endx, endy); |
||
444 | poly.addPoint(rx - dx_half, ry - dy_half); |
||
445 | poly.addPoint(rx + dx_half, ry + dy_half); |
||
446 | canvas.fillPolygon(poly); |
||
447 | } |
||
448 | |||
449 | 35 | gtress | public void run () { |
450 | while (true) { |
||
451 | 32 | gtress | step(); |
452 | repaint(); |
||
453 | 35 | gtress | try {
|
454 | Thread.sleep(90); |
||
455 | } catch (InterruptedException e) { |
||
456 | return;
|
||
457 | } |
||
458 | 32 | gtress | } |
459 | } |
||
460 | |||
461 | 35 | gtress | public void step () { |
462 | 32 | gtress | final int DIAMETER = image.getWidth() - 2*BUFFER; |
463 | final int BIGRADIUS = DIAMETER / 2; |
||
464 | final int TOKENRADIUS = 40; |
||
465 | boolean valid;
|
||
466 | |||
467 | // clear image
|
||
468 | canvas.setColor(Color.WHITE);
|
||
469 | canvas.fillRect(0, 0, image.getWidth(), image.getHeight()); |
||
470 | |||
471 | // parse the matrix, to see what robots exist
|
||
472 | 76 | gtress | String [] rows = txtMatrix.getText().split("\n"); |
473 | 32 | gtress | numBots = rows.length; |
474 | String [][] entries = new String[numBots][numBots]; |
||
475 | valid = true;
|
||
476 | 35 | gtress | for (int i = 0; i < numBots; i++) { |
477 | 32 | gtress | entries[i] = rows[i].split(" ");
|
478 | if (entries[i].length != rows.length) valid = false; |
||
479 | } |
||
480 | |||
481 | 35 | gtress | if (valid) {
|
482 | 38 | gtress | this.showStatus("Running"); |
483 | 32 | gtress | |
484 | // draw robots and find which one is seleced
|
||
485 | double angle = 2.0 * Math.PI / numBots; |
||
486 | canvas.setColor(Color.BLACK);
|
||
487 | botRect = new Rectangle[numBots]; |
||
488 | int x, y;
|
||
489 | if (selectedBot >= numBots) selectedBot = 0; |
||
490 | 35 | gtress | for (int i = 0; i < numBots; i++) { |
491 | 32 | gtress | x = cx - (int)(BIGRADIUS * Math.cos(i * angle)); |
492 | y = cy - (int)(BIGRADIUS * Math.sin(i * angle)); |
||
493 | drawRobot(i, x, y); |
||
494 | if (i == selectedBot) indicator.setCenter(x, y);
|
||
495 | } |
||
496 | |||
497 | // draw token marker
|
||
498 | int tokenx, tokeny;
|
||
499 | int tokenNum = tokenLoc;
|
||
500 | tokenx = cx - (int)(BIGRADIUS * Math.cos(tokenNum * angle)); |
||
501 | tokeny = cy - (int)(BIGRADIUS * Math.sin(tokenNum * angle)); |
||
502 | canvas.setColor(Color.RED);
|
||
503 | canvas.drawOval(tokenx-TOKENRADIUS, tokeny-TOKENRADIUS, 2*TOKENRADIUS, 2*TOKENRADIUS); |
||
504 | |||
505 | // create an inner circle along which the connections are made.
|
||
506 | // let the diameter of this circle be 2*RADIUS less than the outerDiameter.
|
||
507 | // see what connections exist
|
||
508 | 35 | gtress | for (int row = 0; row < numBots; row++) { |
509 | for(int col = 0; col < numBots; col++) { |
||
510 | if (!entries[row][col].equals("-") && entries[col][row].equals("-") && row != col) { |
||
511 | //TODO: Make a standard gray
|
||
512 | 32 | gtress | drawConnection(row, col, BIGRADIUS-RADIUS, new Color(200,200,200)); |
513 | 38 | gtress | } else if (!entries[row][col].equals("-") && ! entries[col][row].equals("-") && row != col) { |
514 | 32 | gtress | drawConnection(row, col, BIGRADIUS-RADIUS, Color.BLACK);
|
515 | } |
||
516 | } |
||
517 | } |
||
518 | |||
519 | // draw the selection indicator
|
||
520 | indicator.draw(); |
||
521 | |||
522 | 35 | gtress | } else {// if matrix is not valid |
523 | 32 | gtress | this.showStatus("Error: Invalid matrix"); |
524 | } |
||
525 | |||
526 | } |
||
527 | |||
528 | /* At this point, moveToken is only called by the simulator.
|
||
529 | * In the future, it can be rewritten to account for non-standard
|
||
530 | * token passing or deleted if the information can be retrieved
|
||
531 | * directly from the Colonet server instead.
|
||
532 | */
|
||
533 | 35 | gtress | public void moveToken () { |
534 | try {
|
||
535 | tokenLoc = (tokenLoc+1)%numBots;
|
||
536 | } catch (ArithmeticException e) { // in case numRobots is zero |
||
537 | } |
||
538 | 32 | gtress | |
539 | packetMonitor.addTokenPass(); |
||
540 | } |
||
541 | |||
542 | //
|
||
543 | // MouseEvent methods
|
||
544 | //
|
||
545 | public void mouseExited(MouseEvent e) {} |
||
546 | public void mouseEntered(MouseEvent e) {} |
||
547 | public void mouseReleased(MouseEvent e) {} |
||
548 | public void mouseClicked(MouseEvent e) {} |
||
549 | 35 | gtress | public void mousePressed(MouseEvent e) { |
550 | 32 | gtress | try {
|
551 | 35 | gtress | for (int i = 0; i < numBots; i++) { |
552 | 32 | gtress | if (botRect[i].contains(e.getPoint()))
|
553 | selectedBot = i; |
||
554 | } |
||
555 | } catch (Exception ex) { |
||
556 | System.out.println(e);
|
||
557 | } |
||
558 | |||
559 | } |
||
560 | |||
561 | /*
|
||
562 | * SelectionIndicator thread.
|
||
563 | * Graphical representation of the selection marker
|
||
564 | *
|
||
565 | * step() and draw() are synchronized methods. step() is private and
|
||
566 | * used to update the position of the crosshairs. draw() is called
|
||
567 | * externally and should only run if all calculations in step() have
|
||
568 | * been completed.
|
||
569 | */
|
||
570 | 35 | gtress | private class SelectionIndicator extends Thread { |
571 | 32 | gtress | |
572 | final int INDICATOR_DELAY = 100; |
||
573 | final double DTHETA = 0.3; //larger values make the marker rotate faster |
||
574 | Graphics2D g; //canvas to draw on |
||
575 | boolean running;
|
||
576 | |||
577 | int sx, sy; //center |
||
578 | int r, dr; //radius and width of marker |
||
579 | double theta; //current angle |
||
580 | |||
581 | volatile Polygon poly1, poly2, poly3, poly4; |
||
582 | |||
583 | int px1, py1;
|
||
584 | int rx1, ry1;
|
||
585 | int px2, py2;
|
||
586 | int rx2, ry2;
|
||
587 | int px3, py3;
|
||
588 | int rx3, ry3;
|
||
589 | int px4, py4;
|
||
590 | int rx4, ry4;
|
||
591 | |||
592 | int steps;
|
||
593 | |||
594 | 35 | gtress | public SelectionIndicator (Graphics2D g) { |
595 | 32 | gtress | super("SelectionIndicator"); |
596 | this.g = g;
|
||
597 | running = false;
|
||
598 | steps = 0;
|
||
599 | |||
600 | theta = 0;
|
||
601 | rx1 = 0; ry1 = 0; |
||
602 | px1 = 0; py1 = 0; |
||
603 | rx2 = 0; ry2 = 0; |
||
604 | px2 = 0; py2 = 0; |
||
605 | rx3 = 0; ry3 = 0; |
||
606 | px3 = 0; py3 = 0; |
||
607 | rx4 = 0; ry4 = 0; |
||
608 | px4 = 0; py4 = 0; |
||
609 | } |
||
610 | |||
611 | 35 | gtress | public synchronized void setCenter (int sx, int sy) { |
612 | 32 | gtress | if (sx == this.sx && sy == this.sy) return; |
613 | this.sx = sx;
|
||
614 | this.sy = sy;
|
||
615 | steps = 0;
|
||
616 | } |
||
617 | |||
618 | 35 | gtress | public synchronized void setRadius (int r, int dr) { |
619 | 32 | gtress | this.r = r;
|
620 | this.dr = dr;
|
||
621 | steps = 0;
|
||
622 | } |
||
623 | |||
624 | 35 | gtress | public void run () { |
625 | 32 | gtress | running = true;
|
626 | 35 | gtress | while (running) {
|
627 | 32 | gtress | step(); |
628 | 35 | gtress | try {
|
629 | Thread.sleep(INDICATOR_DELAY);
|
||
630 | } catch (InterruptedException e) { |
||
631 | running = false;
|
||
632 | return;
|
||
633 | } |
||
634 | 32 | gtress | } |
635 | } |
||
636 | |||
637 | 35 | gtress | private synchronized void step () { |
638 | 32 | gtress | Polygon poly1_new = new Polygon(); |
639 | Polygon poly2_new = new Polygon(); |
||
640 | Polygon poly3_new = new Polygon(); |
||
641 | Polygon poly4_new = new Polygon(); |
||
642 | |||
643 | //the step
|
||
644 | theta = (theta + DTHETA/Math.PI) % (Math.PI); |
||
645 | |||
646 | //the calculation
|
||
647 | //let p be the point of the pointy thing toward the center
|
||
648 | //let r be the point at the opposite side
|
||
649 | |||
650 | //recalculate radius, if it will look cool, lolz
|
||
651 | int newr = r;
|
||
652 | 38 | gtress | if (steps < 100) |
653 | 107 | gtress | newr = (int)( r + 200/(steps+1) ); |
654 | 32 | gtress | |
655 | //precompute values for dx and dy
|
||
656 | int dx_inner = (int)(newr * Math.cos(theta)); |
||
657 | int dy_inner = (int)(newr * Math.sin(theta)); |
||
658 | int dx_outer = (int)((newr+dr) * Math.cos(theta)); |
||
659 | int dy_outer = (int)((newr+dr) * Math.sin(theta)); |
||
660 | |||
661 | //calculate polygon constants
|
||
662 | int dy_poly = (int)(dr/2 * Math.cos(theta)); |
||
663 | int dx_poly = (int)(dr/2 * Math.sin(theta)); |
||
664 | |||
665 | //determine critical points
|
||
666 | 35 | gtress | //kansas city shuffle!
|
667 | 32 | gtress | px1 = sx + dx_inner; |
668 | py1 = sy - dy_inner; |
||
669 | rx1 = sx + dx_outer; |
||
670 | ry1 = sy - dy_outer; |
||
671 | px2 = sx - dx_inner; |
||
672 | py2 = sy + dy_inner; |
||
673 | rx2 = sx - dx_outer; |
||
674 | ry2 = sy + dy_outer; |
||
675 | px3 = sx - dy_inner; |
||
676 | py3 = sy - dx_inner; |
||
677 | rx3 = sx - dy_outer; |
||
678 | ry3 = sy - dx_outer; |
||
679 | px4 = sx + dy_inner; |
||
680 | py4 = sy + dx_inner; |
||
681 | rx4 = sx + dy_outer; |
||
682 | ry4 = sy + dx_outer; |
||
683 | |||
684 | //create polygons
|
||
685 | poly1_new.addPoint(px1, py1); |
||
686 | poly1_new.addPoint(rx1+dx_poly, ry1+dy_poly); |
||
687 | poly1_new.addPoint(rx1-dx_poly, ry1-dy_poly); |
||
688 | poly2_new.addPoint(px2, py2); |
||
689 | poly2_new.addPoint(rx2+dx_poly, ry2+dy_poly); |
||
690 | poly2_new.addPoint(rx2-dx_poly, ry2-dy_poly); |
||
691 | poly3_new.addPoint(px3, py3); |
||
692 | poly3_new.addPoint(rx3-dy_poly, ry3+dx_poly); |
||
693 | poly3_new.addPoint(rx3+dy_poly, ry3-dx_poly); |
||
694 | poly4_new.addPoint(px4, py4); |
||
695 | poly4_new.addPoint(rx4-dy_poly, ry4+dx_poly); |
||
696 | poly4_new.addPoint(rx4+dy_poly, ry4-dx_poly); |
||
697 | |||
698 | //reassign updated polygons
|
||
699 | poly1 = poly1_new; |
||
700 | poly2 = poly2_new; |
||
701 | poly3 = poly3_new; |
||
702 | poly4 = poly4_new; |
||
703 | |||
704 | if (steps < 300) steps++; |
||
705 | } |
||
706 | |||
707 | 35 | gtress | public synchronized void draw () { |
708 | 32 | gtress | if (!running) return; |
709 | g.setColor(Color.GRAY);
|
||
710 | //draw polygons
|
||
711 | g.fillPolygon(poly1); |
||
712 | g.fillPolygon(poly2); |
||
713 | g.fillPolygon(poly3); |
||
714 | g.fillPolygon(poly4); |
||
715 | } |
||
716 | |||
717 | } |
||
718 | |||
719 | /*
|
||
720 | * Simulator thread.
|
||
721 | *
|
||
722 | */
|
||
723 | 35 | gtress | private class Simulator extends Thread { |
724 | 32 | gtress | final int SIMULATOR_DELAY = 300; |
725 | boolean running;
|
||
726 | |||
727 | 35 | gtress | public Simulator () {
|
728 | 32 | gtress | super("Simulator"); |
729 | running = false;
|
||
730 | } |
||
731 | |||
732 | 35 | gtress | public void run () { |
733 | 32 | gtress | running = true;
|
734 | 35 | gtress | while (running) {
|
735 | 32 | gtress | step(); |
736 | 35 | gtress | try {
|
737 | Thread.sleep(SIMULATOR_DELAY);
|
||
738 | } catch (InterruptedException e) { |
||
739 | running = false;
|
||
740 | return;
|
||
741 | } |
||
742 | 32 | gtress | } |
743 | } |
||
744 | |||
745 | 35 | gtress | private void step () { |
746 | 32 | gtress | // simulate passing the token
|
747 | moveToken(); |
||
748 | } |
||
749 | |||
750 | } |
||
751 | |||
752 | /*
|
||
753 | * PacketMonitor thread.
|
||
754 | *
|
||
755 | * Currently, this counts the rate of token passes but will eventually
|
||
756 | * be modified to keep more important statistics.
|
||
757 | */
|
||
758 | 35 | gtress | private class PacketMonitor extends Thread { |
759 | 32 | gtress | final int PACKETMONITOR_DELAY = 1000; |
760 | |||
761 | boolean running;
|
||
762 | int tokenPasses;
|
||
763 | |||
764 | 35 | gtress | public PacketMonitor () {
|
765 | 32 | gtress | super("PacketMonitor"); |
766 | running = false;
|
||
767 | tokenPasses = 0;
|
||
768 | } |
||
769 | |||
770 | 35 | gtress | public void run () { |
771 | 32 | gtress | running = true;
|
772 | 35 | gtress | while (running) {
|
773 | 32 | gtress | displayTokenPasses(); |
774 | 35 | gtress | try {
|
775 | Thread.sleep(PACKETMONITOR_DELAY);
|
||
776 | } catch (InterruptedException e) { |
||
777 | running = false;
|
||
778 | return;
|
||
779 | } |
||
780 | 32 | gtress | } |
781 | } |
||
782 | |||
783 | 35 | gtress | public synchronized void addTokenPass () { |
784 | 32 | gtress | tokenPasses++; |
785 | } |
||
786 | |||
787 | 35 | gtress | public synchronized void displayTokenPasses () { |
788 | 32 | gtress | lblTokenPasses.setText("" + tokenPasses);
|
789 | tokenPasses = 0;
|
||
790 | } |
||
791 | |||
792 | } |
||
793 | |||
794 | /*
|
||
795 | 67 | gtress | * DataUpdater thread.
|
796 | 76 | gtress | * The purpose of this thread is to request data from the server at regular intervals.
|
797 | 32 | gtress | *
|
798 | */
|
||
799 | 67 | gtress | class DataUpdater extends Thread { |
800 | 76 | gtress | final int DATAUPDATER_DELAY = 1100; |
801 | 32 | gtress | |
802 | 67 | gtress | public DataUpdater () {
|
803 | super("Colonet DataUpdater"); |
||
804 | 32 | gtress | } |
805 | |||
806 | 35 | gtress | public void run () { |
807 | 32 | gtress | String line;
|
808 | 35 | gtress | while (true) { |
809 | 32 | gtress | try {
|
810 | 76 | gtress | //request more data
|
811 | if (csi.isReady())
|
||
812 | 107 | gtress | csi.sendSensorDataRequest(); |
813 | 67 | gtress | Thread.sleep(DATAUPDATER_DELAY);
|
814 | 32 | gtress | } catch (InterruptedException e) { |
815 | return;
|
||
816 | 39 | gtress | } |
817 | 32 | gtress | } |
818 | } |
||
819 | |||
820 | } |
||
821 | |||
822 | } |