James H has helpfully provided yet more silly operators for the -A
[sgt/puzzles] / PuzzleApplet.java
CommitLineData
2c930807 1/*
2 * PuzzleApplet.java: NestedVM applet for the puzzle collection
3 */
4import java.awt.*;
5import java.awt.event.*;
6import java.awt.image.BufferedImage;
7import java.util.*;
8import javax.swing.*;
9import javax.swing.border.BevelBorder;
10import javax.swing.Timer;
11import java.util.List;
12
13import org.ibex.nestedvm.Runtime;
14
15public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
16
17 private static final long serialVersionUID = 1L;
18
19 private static final int CFG_SETTINGS = 0, CFG_SEED = 1, CFG_DESC = 2,
20 LEFT_BUTTON = 0x0200, MIDDLE_BUTTON = 0x201, RIGHT_BUTTON = 0x202,
21 LEFT_DRAG = 0x203, MIDDLE_DRAG = 0x204, RIGHT_DRAG = 0x205,
22 LEFT_RELEASE = 0x206, CURSOR_UP = 0x209, CURSOR_DOWN = 0x20a,
23 CURSOR_LEFT = 0x20b, CURSOR_RIGHT = 0x20c, MOD_CTRL = 0x1000,
24 MOD_SHFT = 0x2000, MOD_NUM_KEYPAD = 0x4000, ALIGN_VCENTRE = 0x100,
25 ALIGN_HCENTRE = 0x001, ALIGN_HRIGHT = 0x002, C_STRING = 0,
26 C_CHOICES = 1, C_BOOLEAN = 2;
27
28 private JFrame mainWindow;
29
30 private JMenu typeMenu;
31 private JMenuItem solveCommand;
32 private Color[] colors;
33 private JLabel statusBar;
34 private PuzzlePanel pp;
35 private Runtime runtime;
36 private Graphics2D gg;
37 private Timer timer;
38 private int xarg1, xarg2, xarg3;
39 private int[] xPoints, yPoints;
40 private BufferedImage[] blitters = new BufferedImage[512];
41 private ConfigDialog dlg;
42
43 static {
44 try {
45 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
46 } catch (Exception ex) {
47 ex.printStackTrace();
48 }
49 }
50
51 public void init() {
52 try {
53 Container cp = getContentPane();
54 cp.setLayout(new BorderLayout());
55 runtime = (Runtime) Class.forName("PuzzleEngine").newInstance();
56 runtime.setCallJavaCB(this);
57 JMenuBar menubar = new JMenuBar();
58 JMenu jm;
59 menubar.add(jm = new JMenu("Game"));
60 addMenuItemWithKey(jm, "New", 'n');
61 addMenuItemCallback(jm, "Restart", "jcallback_restart_event");
62 addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC);
63 addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED);
64 jm.addSeparator();
65 addMenuItemWithKey(jm, "Undo", 'u');
66 addMenuItemWithKey(jm, "Redo", 'r');
67 jm.addSeparator();
68 solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event");
69 solveCommand.setEnabled(false);
70 if (mainWindow != null) {
71 jm.addSeparator();
72 addMenuItemWithKey(jm, "Exit", 'q');
73 }
74 menubar.add(typeMenu = new JMenu("Type"));
75 typeMenu.setVisible(false);
76 menubar.add(jm = new JMenu("Help"));
77 addMenuItemCallback(jm, "About", "jcallback_about_event");
78 setJMenuBar(menubar);
79 cp.add(pp = new PuzzlePanel(), BorderLayout.CENTER);
80 pp.addKeyListener(new KeyAdapter() {
81 public void keyPressed(KeyEvent e) {
82 int key = -1;
83 int shift = e.isShiftDown() ? MOD_SHFT : 0;
84 int ctrl = e.isControlDown() ? MOD_CTRL : 0;
85 switch (e.getKeyCode()) {
86 case KeyEvent.VK_LEFT:
87 case KeyEvent.VK_KP_LEFT:
88 key = shift | ctrl | CURSOR_LEFT;
89 break;
90 case KeyEvent.VK_RIGHT:
91 case KeyEvent.VK_KP_RIGHT:
92 key = shift | ctrl | CURSOR_RIGHT;
93 break;
94 case KeyEvent.VK_UP:
95 case KeyEvent.VK_KP_UP:
96 key = shift | ctrl | CURSOR_UP;
97 break;
98 case KeyEvent.VK_DOWN:
99 case KeyEvent.VK_KP_DOWN:
100 key = shift | ctrl | CURSOR_DOWN;
101 break;
102 case KeyEvent.VK_PAGE_UP:
103 key = shift | ctrl | MOD_NUM_KEYPAD | '9';
104 break;
105 case KeyEvent.VK_PAGE_DOWN:
106 key = shift | ctrl | MOD_NUM_KEYPAD | '3';
107 break;
108 case KeyEvent.VK_HOME:
109 key = shift | ctrl | MOD_NUM_KEYPAD | '7';
110 break;
111 case KeyEvent.VK_END:
112 key = shift | ctrl | MOD_NUM_KEYPAD | '1';
113 break;
114 default:
115 if (e.getKeyCode() >= KeyEvent.VK_NUMPAD0 && e.getKeyCode() <=KeyEvent.VK_NUMPAD9) {
116 key = MOD_NUM_KEYPAD | (e.getKeyCode() - KeyEvent.VK_NUMPAD0+'0');
117 }
118 break;
119 }
120 if (key != -1) {
121 runtimeCall("jcallback_key_event", new int[] {0, 0, key});
122 }
123 }
124 public void keyTyped(KeyEvent e) {
125 runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()});
126 }
127 });
128 pp.addMouseListener(new MouseAdapter() {
129 public void mouseReleased(MouseEvent e) {
130 mousePressedReleased(e, true);
131 }
132 public void mousePressed(MouseEvent e) {
133 pp.requestFocus();
134 mousePressedReleased(e, false);
135 }
136 private void mousePressedReleased(MouseEvent e, boolean released) {
137 int button;
138 if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
139 button = MIDDLE_BUTTON;
140 else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
141 button = RIGHT_BUTTON;
142 else if ((e.getModifiers() & (InputEvent.BUTTON1_MASK)) != 0)
143 button = LEFT_BUTTON;
144 else
145 return;
146 if (released)
147 button += LEFT_RELEASE - LEFT_BUTTON;
148 runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
149 }
150 });
151 pp.addMouseMotionListener(new MouseMotionAdapter() {
152 public void mouseDragged(MouseEvent e) {
153 int button;
154 if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0)
155 button = MIDDLE_DRAG;
156 else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0)
157 button = RIGHT_DRAG;
158 else
159 button = LEFT_DRAG;
160 runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button});
161 }
162 });
163 pp.addComponentListener(new ComponentAdapter() {
164 public void componentResized(ComponentEvent e) {
165 handleResized();
166 }
167 });
168 pp.setFocusable(true);
169 pp.requestFocus();
170 timer = new Timer(20, new ActionListener() {
171 public void actionPerformed(ActionEvent e) {
172 runtimeCall("jcallback_timer_func", new int[0]);
173 }
174 });
175 SwingUtilities.invokeLater(new Runnable() {
176 public void run() {
177 runtime.start();
178 runtime.execute();
179 }
180 });
181 } catch (Exception ex) {
182 ex.printStackTrace();
183 }
184 }
185
186 public void destroy() {
187 SwingUtilities.invokeLater(new Runnable() {
188 public void run() {
189 runtime.execute();
190 if (mainWindow != null) {
191 mainWindow.dispose();
192 System.exit(0);
193 }
194 }
195 });
196 }
197
198 protected void handleResized() {
199 pp.createBackBuffer(pp.getWidth(), pp.getHeight(), colors[0]);
200 runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()});
201 }
202
203 private void addMenuItemWithKey(JMenu jm, String name, int key) {
204 addMenuItemCallback(jm, name, "jcallback_menu_key_event", key);
205 }
206
207 private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) {
208 return addMenuItemCallback(jm, name, callback, new int[] {arg});
209 }
210
211 private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback) {
212 return addMenuItemCallback(jm, name, callback, new int[0]);
213 }
214
215 private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int[] args) {
216 JMenuItem jmi;
217 if (jm == typeMenu)
218 typeMenu.add(jmi = new JCheckBoxMenuItem(name));
219 else
220 jm.add(jmi = new JMenuItem(name));
221 jmi.addActionListener(new ActionListener() {
222 public void actionPerformed(ActionEvent e) {
223 runtimeCall(callback, args);
224 }
225 });
226 return jmi;
227 }
228
229 protected void runtimeCall(String func, int[] args) {
230 if (runtimeCallWithResult(func, args) == 42 && mainWindow != null) {
231 destroy();
232 }
233 }
234
235 protected int runtimeCallWithResult(String func, int[] args) {
236 try {
237 return runtime.call(func, args);
238 } catch (Runtime.CallException ex) {
239 ex.printStackTrace();
240 return 42;
241 }
242 }
243
244 private void buildConfigureMenuItem() {
245 if (typeMenu.isVisible()) {
246 typeMenu.addSeparator();
247 } else {
248 typeMenu.setVisible(true);
249 }
250 addMenuItemCallback(typeMenu, "Custom...", "jcallback_config_event", CFG_SETTINGS);
251 }
252
253 private void addTypeItem(String name, final int ptrGameParams) {
254 typeMenu.setVisible(true);
255 addMenuItemCallback(typeMenu, name, "jcallback_preset_event", ptrGameParams);
256 }
257
258 public int call(int cmd, int arg1, int arg2, int arg3) {
259 try {
260 switch(cmd) {
261 case 0: // initialize
262 if (mainWindow != null) mainWindow.setTitle(runtime.cstring(arg1));
263 if ((arg2 & 1) != 0) buildConfigureMenuItem();
264 if ((arg2 & 2) != 0) addStatusBar();
265 if ((arg2 & 4) != 0) solveCommand.setEnabled(true);
266 colors = new Color[arg3];
267 return 0;
268 case 1: // Type menu item
269 addTypeItem(runtime.cstring(arg1), arg2);
270 return 0;
271 case 2: // MessageBox
272 JOptionPane.showMessageDialog(this, runtime.cstring(arg2), runtime.cstring(arg1), arg3 == 0 ? JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE);
273 return 0;
274 case 3: // Resize
275 pp.setPreferredSize(new Dimension(arg1, arg2));
276 if (mainWindow != null) mainWindow.pack();
277 handleResized();
278 if (mainWindow != null) mainWindow.setVisible(true);
279 return 0;
280 case 4: // drawing tasks
281 switch(arg1) {
282 case 0:
283 String text = runtime.cstring(arg2);
284 if (text.equals("")) text = " ";
285 System.out.println("status '" + text + "'");
286 statusBar.setText(text); break;
287 case 1:
288 gg = pp.backBuffer.createGraphics();
289 if (arg2 != 0 || arg3 != 0) {
290 gg.setColor(Color.black);
291 gg.fillRect(0, 0, arg2, getHeight());
292 gg.fillRect(0, 0, getWidth(), arg3);
293 gg.fillRect(getWidth() - arg2, 0, arg2, getHeight());
294 gg.fillRect(0, getHeight() - arg3, getWidth(), arg3);
295 gg.setClip(arg2, arg3, getWidth()-2*arg2, getHeight()-2*arg3);
296 }
297 break;
298 case 2: gg.dispose(); pp.repaint(); break;
299 case 3: gg.setClip(arg2, arg3, xarg1, xarg2); break;
300 case 4:
301 if (arg2 == 0 && arg3 == 0) {
302 gg.fillRect(0, 0, getWidth(), getHeight());
303 } else {
304 gg.setClip(arg2, arg3, getWidth()-2*arg2, getHeight()-2*arg3);
305 }
306 break;
307 case 5:
308 gg.setColor(colors[xarg3]);
309 gg.fillRect(arg2, arg3, xarg1, xarg2);
310 break;
311 case 6:
312 gg.setColor(colors[xarg3]);
313 gg.drawLine(arg2, arg3, xarg1, xarg2);
314 break;
315 case 7:
316 xPoints = new int[arg2];
317 yPoints = new int[arg2];
318 break;
319 case 8:
320 if (arg3 != -1) {
321 gg.setColor(colors[arg3]);
322 gg.fillPolygon(xPoints, yPoints, xPoints.length);
323 }
324 gg.setColor(colors[arg2]);
325 gg.drawPolygon(xPoints, yPoints, xPoints.length);
326 break;
327 case 9:
328 if (arg3 != -1) {
329 gg.setColor(colors[arg3]);
330 gg.fillOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
331 }
332 gg.setColor(colors[arg2]);
333 gg.drawOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2);
334 break;
335 case 10:
336 for(int i=0; i<blitters.length; i++) {
337 if (blitters[i] == null) {
338 blitters[i] = new BufferedImage(arg2, arg3, BufferedImage.TYPE_3BYTE_BGR);
339 return i;
340 }
341 }
342 throw new RuntimeException("No free blitter found!");
343 case 11: blitters[arg2] = null; break;
344 case 12:
345 timer.start(); break;
346 case 13:
347 timer.stop(); break;
348 }
349 return 0;
350 case 5: // more arguments
351 xarg1 = arg1;
352 xarg2 = arg2;
353 xarg3 = arg3;
354 return 0;
355 case 6: // polygon vertex
356 xPoints[arg1]=arg2;
357 yPoints[arg1]=arg3;
358 return 0;
359 case 7: // string
360 gg.setColor(colors[arg2]);
361 {
362 String text = runtime.cstring(arg3);
363 Font ft = new Font((xarg3 & 0x10) != 0 ? "Monospaced" : "Dialog",
364 Font.PLAIN, 100);
365 int height100 = this.getFontMetrics(ft).getHeight();
366 ft = ft.deriveFont(arg1 * 100 / (float)height100);
367 FontMetrics fm = this.getFontMetrics(ft);
368 int asc = fm.getAscent(), desc = fm.getDescent();
369 if ((xarg3 & ALIGN_VCENTRE) != 0)
370 xarg2 += asc - (asc+desc)/2;
371 else
372 xarg2 += asc;
373 int wid = fm.stringWidth(text);
374 if ((xarg3 & ALIGN_HCENTRE) != 0)
375 xarg1 -= wid / 2;
376 else if ((xarg3 & ALIGN_HRIGHT) != 0)
377 xarg1 -= wid;
378 gg.setFont(ft);
379 gg.drawString(text, xarg1, xarg2);
380 }
381 return 0;
382 case 8: // blitter_save
383 Graphics g2 = blitters[arg1].createGraphics();
384 g2.drawImage(pp.backBuffer, 0, 0, blitters[arg1].getWidth(), blitters[arg1].getHeight(),
385 arg2, arg3, arg2 + blitters[arg1].getWidth(), arg3 + blitters[arg1].getHeight(), this);
386 g2.dispose();
387 return 0;
388 case 9: // blitter_load
389 gg.drawImage(blitters[arg1], arg2, arg3, this);
390 return 0;
391 case 10: // dialog_init
392 dlg= new ConfigDialog(this, runtime.cstring(arg1));
393 return 0;
394 case 11: // dialog_add_control
395 {
396 int sval_ptr = arg1;
397 int ival = arg2;
398 int ptr = xarg1;
399 int type=xarg2;
400 String name = runtime.cstring(xarg3);
401 switch(type) {
402 case C_STRING:
403 dlg.addTextBox(ptr, name, runtime.cstring(sval_ptr));
404 break;
405 case C_BOOLEAN:
406 dlg.addCheckBox(ptr, name, ival != 0);
407 break;
408 case C_CHOICES:
409 dlg.addComboBox(ptr, name, runtime.cstring(sval_ptr), ival);
410 }
411 }
412 return 0;
413 case 12:
414 dlg.finish();
415 dlg = null;
416 return 0;
417 case 13: // tick a menu item
418 if (arg1 < 0) arg1 = typeMenu.getItemCount() - 1;
419 for (int i = 0; i < typeMenu.getItemCount(); i++) {
420 if (typeMenu.getMenuComponent(i) instanceof JCheckBoxMenuItem) {
421 ((JCheckBoxMenuItem)typeMenu.getMenuComponent(i)).setSelected(arg1 == i);
422 }
423 }
424 return 0;
425 default:
426 if (cmd >= 1024 && cmd < 2048) { // palette
427 colors[cmd-1024] = new Color(arg1, arg2, arg3);
428 }
429 if (cmd == 1024) {
430 pp.setBackground(colors[0]);
431 if (statusBar != null) statusBar.setBackground(colors[0]);
432 this.setBackground(colors[0]);
433 }
434 return 0;
435 }
436 } catch (Throwable ex) {
437 ex.printStackTrace();
438 System.exit(-1);
439 return 0;
440 }
441 }
442
443 private void addStatusBar() {
444 statusBar = new JLabel("test");
445 statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED));
446 getContentPane().add(BorderLayout.SOUTH,statusBar);
447 }
448
449 // Standalone runner
450 public static void main(String[] args) {
451 final PuzzleApplet a = new PuzzleApplet();
452 JFrame jf = new JFrame("Loading...");
453 jf.getContentPane().setLayout(new BorderLayout());
454 jf.getContentPane().add(a, BorderLayout.CENTER);
455 a.mainWindow=jf;
456 a.init();
457 a.start();
458 jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
459 jf.addWindowListener(new WindowAdapter() {
460 public void windowClosing(WindowEvent e) {
461 a.stop();
462 a.destroy();
463 }
464 });
465 jf.setVisible(true);
466 }
467
468 public static class PuzzlePanel extends JPanel {
469
470 private static final long serialVersionUID = 1L;
471 protected BufferedImage backBuffer;
472
473 public PuzzlePanel() {
474 setPreferredSize(new Dimension(100,100));
475 createBackBuffer(100,100, Color.black);
476 }
477
478 public void createBackBuffer(int w, int h, Color bg) {
479 backBuffer = new BufferedImage(w,h, BufferedImage.TYPE_3BYTE_BGR);
480 Graphics g = backBuffer.createGraphics();
481 g.setColor(bg);
482 g.fillRect(0, 0, w, h);
483 g.dispose();
484 }
485
486 protected void paintComponent(Graphics g) {
487 g.drawImage(backBuffer, 0, 0, this);
488 }
489 }
490
491 public static class ConfigComponent {
492 public int type;
493 public int configItemPointer;
494 public JComponent component;
495
496 public ConfigComponent(int type, int configItemPointer, JComponent component) {
497 this.type = type;
498 this.configItemPointer = configItemPointer;
499 this.component = component;
500 }
501 }
502
503 public class ConfigDialog extends JDialog {
504
505 private GridBagConstraints gbcLeft = new GridBagConstraints(
506 GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1,
507 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE,
508 new Insets(0, 0, 0, 0), 0, 0);
509 private GridBagConstraints gbcRight = new GridBagConstraints(
510 GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
511 GridBagConstraints.REMAINDER, 1, 1.0, 0,
512 GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL,
513 new Insets(5, 5, 5, 5), 0, 0);
514 private GridBagConstraints gbcBottom = new GridBagConstraints(
515 GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE,
516 GridBagConstraints.REMAINDER, GridBagConstraints.REMAINDER,
517 1.0, 1.0, GridBagConstraints.CENTER,
518 GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0);
519
520 private static final long serialVersionUID = 1L;
521 private List components = new ArrayList();
522
523 public ConfigDialog(JApplet parent, String title) {
524 super(JOptionPane.getFrameForComponent(parent), title, true);
525 getContentPane().setLayout(new GridBagLayout());
526 }
527
528 public void addTextBox(int ptr, String name, String value) {
529 getContentPane().add(new JLabel(name), gbcLeft);
530 JComponent c = new JTextField(value, 25);
531 getContentPane().add(c, gbcRight);
532 components.add(new ConfigComponent(C_STRING, ptr, c));
533 }
534
535
536 public void addCheckBox(int ptr, String name, boolean selected) {
537 JComponent c = new JCheckBox(name, selected);
538 getContentPane().add(c, gbcRight);
539 components.add(new ConfigComponent(C_BOOLEAN, ptr, c));
540 }
541
542 public void addComboBox(int ptr, String name, String values, int selected) {
543 getContentPane().add(new JLabel(name), gbcLeft);
544 StringTokenizer st = new StringTokenizer(values.substring(1), values.substring(0,1));
545 JComboBox c = new JComboBox();
546 c.setEditable(false);
547 while(st.hasMoreTokens())
548 c.addItem(st.nextToken());
549 c.setSelectedIndex(selected);
550 getContentPane().add(c, gbcRight);
551 components.add(new ConfigComponent(C_CHOICES, ptr, c));
552 }
553
554 public void finish() {
555 JPanel buttons = new JPanel(new GridLayout(1, 2, 5, 5));
556 getContentPane().add(buttons, gbcBottom);
557 JButton b;
558 buttons.add(b=new JButton("OK"));
559 b.addActionListener(new ActionListener() {
560 public void actionPerformed(ActionEvent e) {
561 save();
562 dispose();
563 }
564 });
565 getRootPane().setDefaultButton(b);
566 buttons.add(b=new JButton("Cancel"));
567 b.addActionListener(new ActionListener() {
568 public void actionPerformed(ActionEvent e) {
569 dispose();
570 }
571 });
572 setDefaultCloseOperation(DISPOSE_ON_CLOSE);
573 pack();
574 setLocationRelativeTo(null);
575 setVisible(true);
576 }
577 private void save() {
578 for (int i = 0; i < components.size(); i++) {
579 ConfigComponent cc = (ConfigComponent) components.get(i);
580 switch(cc.type) {
581 case C_STRING:
582 JTextField jtf = (JTextField)cc.component;
583 runtimeCall("jcallback_config_set_string", new int[] {cc.configItemPointer, runtime.strdup(jtf.getText())});
584 break;
585 case C_BOOLEAN:
586 JCheckBox jcb = (JCheckBox)cc.component;
587 runtimeCall("jcallback_config_set_boolean", new int[] {cc.configItemPointer, jcb.isSelected()?1:0});
588 break;
589 case C_CHOICES:
590 JComboBox jcm = (JComboBox)cc.component;
591 runtimeCall("jcallback_config_set_boolean", new int[] {cc.configItemPointer, jcm.getSelectedIndex()});
592 break;
593 }
594 }
595 runtimeCall("jcallback_config_ok", new int[0]);
596 }
597 }
598}