diff options
| author | Franklin Wei <frankhwei536@gmail.com> | 2016-11-20 15:16:41 -0500 |
|---|---|---|
| committer | Franklin Wei <frankhwei536@gmail.com> | 2016-11-24 16:23:09 -0500 |
| commit | 56c9984511f016eab7e1278ba9e40d88bb59a162 (patch) | |
| tree | 1bfa6d3aeb3bf2a6ffec71387ac073cd0b8b2a51 /apps/plugins/puzzles/PuzzleApplet.java | |
| parent | 29648f817677b84c03c2bcfe89eb8cf53653e7db (diff) | |
| download | rockbox-puzzles.zip rockbox-puzzles.tar.gz rockbox-puzzles.tar.bz2 rockbox-puzzles.tar.xz | |
[WIP] Port of Simon Tatham's Puzzle Collectionpuzzles
Original revision: 5123b1bf68777ffa86e651f178046b26a87cf2d9
MIT Licensed. Some games still crash and others are unplayable due to
issues with controls. Still need a "real" polygon filling algorithm.
The following games are at least partially broken for various reasons:
Cube: crash with certain settings
Galaxies: crash
Inertia: crash
Keen: input issues
Loopy: weird stuff happens
Map: crash on input
Mines: weird stuff happens on target
Palisade: input issues
Signpost: crash on input
Solo: input issues
Towers: input and drawing issues
Train Tracks: drawing issues
Twiddle: weird animation on target
Undead: input and drawing issues
Unequal: input and drawing issues
Untangle: input issues
All in all, about 40% of the games are at least partially broken.
Change-Id: I7c69b6860ab115f973c8d76799502e9bb3d52368
Diffstat (limited to '')
| -rw-r--r-- | apps/plugins/puzzles/PuzzleApplet.java | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/PuzzleApplet.java b/apps/plugins/puzzles/PuzzleApplet.java new file mode 100644 index 0000000..0b0648c --- /dev/null +++ b/apps/plugins/puzzles/PuzzleApplet.java @@ -0,0 +1,617 @@ +/* + * PuzzleApplet.java: NestedVM applet for the puzzle collection + */ +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.util.*; +import javax.swing.*; +import javax.swing.border.BevelBorder; +import javax.swing.Timer; +import java.util.List; + +import org.ibex.nestedvm.Runtime; + +public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB { + + private static final long serialVersionUID = 1L; + + private static final int CFG_SETTINGS = 0, CFG_SEED = 1, CFG_DESC = 2, + LEFT_BUTTON = 0x0200, MIDDLE_BUTTON = 0x201, RIGHT_BUTTON = 0x202, + LEFT_DRAG = 0x203, MIDDLE_DRAG = 0x204, RIGHT_DRAG = 0x205, + LEFT_RELEASE = 0x206, CURSOR_UP = 0x209, CURSOR_DOWN = 0x20a, + CURSOR_LEFT = 0x20b, CURSOR_RIGHT = 0x20c, MOD_CTRL = 0x1000, + MOD_SHFT = 0x2000, MOD_NUM_KEYPAD = 0x4000, ALIGN_VCENTRE = 0x100, + ALIGN_HCENTRE = 0x001, ALIGN_HRIGHT = 0x002, C_STRING = 0, + C_CHOICES = 1, C_BOOLEAN = 2; + + private JFrame mainWindow; + + private JMenu typeMenu; + private JMenuItem solveCommand; + private Color[] colors; + private JLabel statusBar; + private PuzzlePanel pp; + private Runtime runtime; + private String[] puzzle_args; + private Graphics2D gg; + private Timer timer; + private int xarg1, xarg2, xarg3; + private int[] xPoints, yPoints; + private BufferedImage[] blitters = new BufferedImage[512]; + private ConfigDialog dlg; + + static { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void init() { + try { + Container cp = getContentPane(); + cp.setLayout(new BorderLayout()); + runtime = (Runtime) Class.forName("PuzzleEngine").newInstance(); + runtime.setCallJavaCB(this); + JMenuBar menubar = new JMenuBar(); + JMenu jm; + menubar.add(jm = new JMenu("Game")); + addMenuItemWithKey(jm, "New", 'n'); + addMenuItemCallback(jm, "Restart", "jcallback_restart_event"); + addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC); + addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED); + jm.addSeparator(); + addMenuItemWithKey(jm, "Undo", 'u'); + addMenuItemWithKey(jm, "Redo", 'r'); + jm.addSeparator(); + solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event"); + solveCommand.setEnabled(false); + if (mainWindow != null) { + jm.addSeparator(); + addMenuItemWithKey(jm, "Exit", 'q'); + } + menubar.add(typeMenu = new JMenu("Type")); + typeMenu.setVisible(false); + menubar.add(jm = new JMenu("Help")); + addMenuItemCallback(jm, "About", "jcallback_about_event"); + setJMenuBar(menubar); + cp.add(pp = new PuzzlePanel(), BorderLayout.CENTER); + pp.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + int key = -1; + int shift = e.isShiftDown() ? MOD_SHFT : 0; + int ctrl = e.isControlDown() ? MOD_CTRL : 0; + switch (e.getKeyCode()) { + case KeyEvent.VK_LEFT: + case KeyEvent.VK_KP_LEFT: + key = shift | ctrl | CURSOR_LEFT; + break; + case KeyEvent.VK_RIGHT: + case KeyEvent.VK_KP_RIGHT: + key = shift | ctrl | CURSOR_RIGHT; + break; + case KeyEvent.VK_UP: + case KeyEvent.VK_KP_UP: + key = shift | ctrl | CURSOR_UP; + break; + case KeyEvent.VK_DOWN: + case KeyEvent.VK_KP_DOWN: + key = shift | ctrl | CURSOR_DOWN; + break; + case KeyEvent.VK_PAGE_UP: + key = shift | ctrl | MOD_NUM_KEYPAD | '9'; + break; + case KeyEvent.VK_PAGE_DOWN: + key = shift | ctrl | MOD_NUM_KEYPAD | '3'; + break; + case KeyEvent.VK_HOME: + key = shift | ctrl | MOD_NUM_KEYPAD | '7'; + break; + case KeyEvent.VK_END: + key = shift | ctrl | MOD_NUM_KEYPAD | '1'; + break; + default: + if (e.getKeyCode() >= KeyEvent.VK_NUMPAD0 && e.getKeyCode() <=KeyEvent.VK_NUMPAD9) { + key = MOD_NUM_KEYPAD | (e.getKeyCode() - KeyEvent.VK_NUMPAD0+'0'); + } + break; + } + if (key != -1) { + runtimeCall("jcallback_key_event", new int[] {0, 0, key}); + } + } + public void keyTyped(KeyEvent e) { + runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()}); + } + }); + pp.addMouseListener(new MouseAdapter() { + public void mouseReleased(MouseEvent e) { + mousePressedReleased(e, true); + } + public void mousePressed(MouseEvent e) { + pp.requestFocus(); + mousePressedReleased(e, false); + } + private void mousePressedReleased(MouseEvent e, boolean released) { + int button; + if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0) + button = MIDDLE_BUTTON; + else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0) + button = RIGHT_BUTTON; + else if ((e.getModifiers() & (InputEvent.BUTTON1_MASK)) != 0) + button = LEFT_BUTTON; + else + return; + if (released) + button += LEFT_RELEASE - LEFT_BUTTON; + runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button}); + } + }); + pp.addMouseMotionListener(new MouseMotionAdapter() { + public void mouseDragged(MouseEvent e) { + int button; + if ((e.getModifiers() & (InputEvent.BUTTON2_MASK | InputEvent.SHIFT_MASK)) != 0) + button = MIDDLE_DRAG; + else if ((e.getModifiers() & (InputEvent.BUTTON3_MASK | InputEvent.ALT_MASK)) != 0) + button = RIGHT_DRAG; + else + button = LEFT_DRAG; + runtimeCall("jcallback_key_event", new int[] {e.getX(), e.getY(), button}); + } + }); + pp.addComponentListener(new ComponentAdapter() { + public void componentResized(ComponentEvent e) { + handleResized(); + } + }); + pp.setFocusable(true); + pp.requestFocus(); + timer = new Timer(20, new ActionListener() { + public void actionPerformed(ActionEvent e) { + runtimeCall("jcallback_timer_func", new int[0]); + } + }); + String gameid; + try { + gameid = getParameter("game_id"); + } catch (java.lang.NullPointerException ex) { + gameid = null; + } + if (gameid == null) { + puzzle_args = null; + } else { + puzzle_args = new String[2]; + puzzle_args[0] = "puzzle"; + puzzle_args[1] = gameid; + } + SwingUtilities.invokeLater(new Runnable() { + public void run() { + runtime.start(puzzle_args); + runtime.execute(); + } + }); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public void destroy() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + runtime.execute(); + if (mainWindow != null) { + mainWindow.dispose(); + System.exit(0); + } + } + }); + } + + protected void handleResized() { + pp.createBackBuffer(pp.getWidth(), pp.getHeight(), colors[0]); + runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()}); + } + + private void addMenuItemWithKey(JMenu jm, String name, int key) { + addMenuItemCallback(jm, name, "jcallback_menu_key_event", key); + } + + private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) { + return addMenuItemCallback(jm, name, callback, new int[] {arg}); + } + + private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback) { + return addMenuItemCallback(jm, name, callback, new int[0]); + } + + private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int[] args) { + JMenuItem jmi; + if (jm == typeMenu) + typeMenu.add(jmi = new JCheckBoxMenuItem(name)); + else + jm.add(jmi = new JMenuItem(name)); + jmi.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + runtimeCall(callback, args); + } + }); + return jmi; + } + + protected void runtimeCall(String func, int[] args) { + if (runtimeCallWithResult(func, args) == 42 && mainWindow != null) { + destroy(); + } + } + + protected int runtimeCallWithResult(String func, int[] args) { + try { + return runtime.call(func, args); + } catch (Runtime.CallException ex) { + ex.printStackTrace(); + return 42; + } + } + + private void buildConfigureMenuItem() { + if (typeMenu.isVisible()) { + typeMenu.addSeparator(); + } else { + typeMenu.setVisible(true); + } + addMenuItemCallback(typeMenu, "Custom...", "jcallback_config_event", CFG_SETTINGS); + } + + private void addTypeItem(String name, final int ptrGameParams) { + typeMenu.setVisible(true); + addMenuItemCallback(typeMenu, name, "jcallback_preset_event", ptrGameParams); + } + + public int call(int cmd, int arg1, int arg2, int arg3) { + try { + switch(cmd) { + case 0: // initialize + if (mainWindow != null) mainWindow.setTitle(runtime.cstring(arg1)); + if ((arg2 & 1) != 0) buildConfigureMenuItem(); + if ((arg2 & 2) != 0) addStatusBar(); + if ((arg2 & 4) != 0) solveCommand.setEnabled(true); + colors = new Color[arg3]; + return 0; + case 1: // Type menu item + addTypeItem(runtime.cstring(arg1), arg2); + return 0; + case 2: // MessageBox + JOptionPane.showMessageDialog(this, runtime.cstring(arg2), runtime.cstring(arg1), arg3 == 0 ? JOptionPane.INFORMATION_MESSAGE : JOptionPane.ERROR_MESSAGE); + return 0; + case 3: // Resize + pp.setPreferredSize(new Dimension(arg1, arg2)); + if (mainWindow != null) mainWindow.pack(); + handleResized(); + if (mainWindow != null) mainWindow.setVisible(true); + return 0; + case 4: // drawing tasks + switch(arg1) { + case 0: + String text = runtime.cstring(arg2); + if (text.equals("")) text = " "; + statusBar.setText(text); + break; + case 1: + gg = pp.backBuffer.createGraphics(); + if (arg2 != 0 || arg3 != 0 || + arg2 + xarg2 != getWidth() || + arg3 + xarg3 != getHeight()) { + int left = arg2, right = arg2 + xarg2; + int top = arg3, bottom = arg3 + xarg3; + int width = getWidth(), height = getHeight(); + gg.setColor(colors != null ? colors[0] : Color.black); + gg.fillRect(0, 0, left, height); + gg.fillRect(right, 0, width-right, height); + gg.fillRect(0, 0, width, top); + gg.fillRect(0, bottom, width, height-bottom); + gg.setClip(left, top, right-left, bottom-top); + } + break; + case 2: gg.dispose(); pp.repaint(); break; + case 3: gg.setClip(arg2, arg3, xarg1, xarg2); break; + case 4: + if (arg2 == 0 && arg3 == 0) { + gg.setClip(0, 0, getWidth(), getHeight()); + } else { + gg.setClip(arg2, arg3, getWidth()-2*arg2, getHeight()-2*arg3); + } + break; + case 5: + gg.setColor(colors[xarg3]); + gg.fillRect(arg2, arg3, xarg1, xarg2); + break; + case 6: + gg.setColor(colors[xarg3]); + gg.drawLine(arg2, arg3, xarg1, xarg2); + break; + case 7: + xPoints = new int[arg2]; + yPoints = new int[arg2]; + break; + case 8: + if (arg3 != -1) { + gg.setColor(colors[arg3]); + gg.fillPolygon(xPoints, yPoints, xPoints.length); + } + gg.setColor(colors[arg2]); + gg.drawPolygon(xPoints, yPoints, xPoints.length); + break; + case 9: + if (arg3 != -1) { + gg.setColor(colors[arg3]); + gg.fillOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2); + } + gg.setColor(colors[arg2]); + gg.drawOval(xarg1-xarg3, xarg2-xarg3, xarg3*2, xarg3*2); + break; + case 10: + for(int i=0; i<blitters.length; i++) { + if (blitters[i] == null) { + blitters[i] = new BufferedImage(arg2, arg3, BufferedImage.TYPE_3BYTE_BGR); + return i; + } + } + throw new RuntimeException("No free blitter found!"); + case 11: blitters[arg2] = null; break; + case 12: + timer.start(); break; + case 13: + timer.stop(); break; + } + return 0; + case 5: // more arguments + xarg1 = arg1; + xarg2 = arg2; + xarg3 = arg3; + return 0; + case 6: // polygon vertex + xPoints[arg1]=arg2; + yPoints[arg1]=arg3; + return 0; + case 7: // string + gg.setColor(colors[arg2]); + { + String text = runtime.utfstring(arg3); + Font ft = new Font((xarg3 & 0x10) != 0 ? "Monospaced" : "Dialog", + Font.PLAIN, 100); + int height100 = this.getFontMetrics(ft).getHeight(); + ft = ft.deriveFont(arg1 * 100 / (float)height100); + FontMetrics fm = this.getFontMetrics(ft); + int asc = fm.getAscent(), desc = fm.getDescent(); + if ((xarg3 & ALIGN_VCENTRE) != 0) + xarg2 += asc - (asc+desc)/2; + int wid = fm.stringWidth(text); + if ((xarg3 & ALIGN_HCENTRE) != 0) + xarg1 -= wid / 2; + else if ((xarg3 & ALIGN_HRIGHT) != 0) + xarg1 -= wid; + gg.setFont(ft); + gg.drawString(text, xarg1, xarg2); + } + return 0; + case 8: // blitter_save + Graphics g2 = blitters[arg1].createGraphics(); + g2.drawImage(pp.backBuffer, 0, 0, blitters[arg1].getWidth(), blitters[arg1].getHeight(), + arg2, arg3, arg2 + blitters[arg1].getWidth(), arg3 + blitters[arg1].getHeight(), this); + g2.dispose(); + return 0; + case 9: // blitter_load + gg.drawImage(blitters[arg1], arg2, arg3, this); + return 0; + case 10: // dialog_init + dlg= new ConfigDialog(this, runtime.cstring(arg1)); + return 0; + case 11: // dialog_add_control + { + int sval_ptr = arg1; + int ival = arg2; + int ptr = xarg1; + int type=xarg2; + String name = runtime.cstring(xarg3); + switch(type) { + case C_STRING: + dlg.addTextBox(ptr, name, runtime.cstring(sval_ptr)); + break; + case C_BOOLEAN: + dlg.addCheckBox(ptr, name, ival != 0); + break; + case C_CHOICES: + dlg.addComboBox(ptr, name, runtime.cstring(sval_ptr), ival); + } + } + return 0; + case 12: + dlg.finish(); + dlg = null; + return 0; + case 13: // tick a menu item + if (arg1 < 0) arg1 = typeMenu.getItemCount() - 1; + for (int i = 0; i < typeMenu.getItemCount(); i++) { + if (typeMenu.getMenuComponent(i) instanceof JCheckBoxMenuItem) { + ((JCheckBoxMenuItem)typeMenu.getMenuComponent(i)).setSelected(arg1 == i); + } + } + return 0; + default: + if (cmd >= 1024 && cmd < 2048) { // palette + colors[cmd-1024] = new Color(arg1, arg2, arg3); + } + if (cmd == 1024) { + pp.setBackground(colors[0]); + if (statusBar != null) statusBar.setBackground(colors[0]); + this.setBackground(colors[0]); + } + return 0; + } + } catch (Throwable ex) { + ex.printStackTrace(); + System.exit(-1); + return 0; + } + } + + private void addStatusBar() { + statusBar = new JLabel("test"); + statusBar.setBorder(new BevelBorder(BevelBorder.LOWERED)); + getContentPane().add(BorderLayout.SOUTH,statusBar); + } + + // Standalone runner + public static void main(String[] args) { + final PuzzleApplet a = new PuzzleApplet(); + JFrame jf = new JFrame("Loading..."); + jf.getContentPane().setLayout(new BorderLayout()); + jf.getContentPane().add(a, BorderLayout.CENTER); + a.mainWindow=jf; + a.init(); + a.start(); + jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + jf.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + a.stop(); + a.destroy(); + } + }); + jf.setVisible(true); + } + + public static class PuzzlePanel extends JPanel { + + private static final long serialVersionUID = 1L; + protected BufferedImage backBuffer; + + public PuzzlePanel() { + setPreferredSize(new Dimension(100,100)); + createBackBuffer(100,100, Color.black); + } + + public void createBackBuffer(int w, int h, Color bg) { + if (w > 0 && h > 0) { + backBuffer = new BufferedImage(w,h, BufferedImage.TYPE_3BYTE_BGR); + Graphics g = backBuffer.createGraphics(); + g.setColor(bg); + g.fillRect(0, 0, w, h); + g.dispose(); + } + } + + protected void paintComponent(Graphics g) { + g.drawImage(backBuffer, 0, 0, this); + } + } + + public static class ConfigComponent { + public int type; + public int configItemPointer; + public JComponent component; + + public ConfigComponent(int type, int configItemPointer, JComponent component) { + this.type = type; + this.configItemPointer = configItemPointer; + this.component = component; + } + } + + public class ConfigDialog extends JDialog { + + private GridBagConstraints gbcLeft = new GridBagConstraints( + GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, 1, 1, + 0, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + private GridBagConstraints gbcRight = new GridBagConstraints( + GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, + GridBagConstraints.REMAINDER, 1, 1.0, 0, + GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(5, 5, 5, 5), 0, 0); + private GridBagConstraints gbcBottom = new GridBagConstraints( + GridBagConstraints.RELATIVE, GridBagConstraints.RELATIVE, + GridBagConstraints.REMAINDER, GridBagConstraints.REMAINDER, + 1.0, 1.0, GridBagConstraints.CENTER, + GridBagConstraints.HORIZONTAL, new Insets(5, 5, 5, 5), 0, 0); + + private static final long serialVersionUID = 1L; + private List components = new ArrayList(); + + public ConfigDialog(JApplet parent, String title) { + super(JOptionPane.getFrameForComponent(parent), title, true); + getContentPane().setLayout(new GridBagLayout()); + } + + public void addTextBox(int ptr, String name, String value) { + getContentPane().add(new JLabel(name), gbcLeft); + JComponent c = new JTextField(value, 25); + getContentPane().add(c, gbcRight); + components.add(new ConfigComponent(C_STRING, ptr, c)); + } + + + public void addCheckBox(int ptr, String name, boolean selected) { + JComponent c = new JCheckBox(name, selected); + getContentPane().add(c, gbcRight); + components.add(new ConfigComponent(C_BOOLEAN, ptr, c)); + } + + public void addComboBox(int ptr, String name, String values, int selected) { + getContentPane().add(new JLabel(name), gbcLeft); + StringTokenizer st = new StringTokenizer(values.substring(1), values.substring(0,1)); + JComboBox c = new JComboBox(); + c.setEditable(false); + while(st.hasMoreTokens()) + c.addItem(st.nextToken()); + c.setSelectedIndex(selected); + getContentPane().add(c, gbcRight); + components.add(new ConfigComponent(C_CHOICES, ptr, c)); + } + + public void finish() { + JPanel buttons = new JPanel(new GridLayout(1, 2, 5, 5)); + getContentPane().add(buttons, gbcBottom); + JButton b; + buttons.add(b=new JButton("OK")); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + save(); + dispose(); + } + }); + getRootPane().setDefaultButton(b); + buttons.add(b=new JButton("Cancel")); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dispose(); + } + }); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + pack(); + setLocationRelativeTo(null); + setVisible(true); + } + private void save() { + for (int i = 0; i < components.size(); i++) { + ConfigComponent cc = (ConfigComponent) components.get(i); + switch(cc.type) { + case C_STRING: + JTextField jtf = (JTextField)cc.component; + runtimeCall("jcallback_config_set_string", new int[] {cc.configItemPointer, runtime.strdup(jtf.getText())}); + break; + case C_BOOLEAN: + JCheckBox jcb = (JCheckBox)cc.component; + runtimeCall("jcallback_config_set_boolean", new int[] {cc.configItemPointer, jcb.isSelected()?1:0}); + break; + case C_CHOICES: + JComboBox jcm = (JComboBox)cc.component; + runtimeCall("jcallback_config_set_choice", new int[] {cc.configItemPointer, jcm.getSelectedIndex()}); + break; + } + } + runtimeCall("jcallback_config_ok", new int[0]); + } + } +} |