diff --git a/Noisily sorted.txt b/Noisily sorted.txt new file mode 100644 index 00000000..15f723e9 --- /dev/null +++ b/Noisily sorted.txt @@ -0,0 +1,5 @@ +2 2.0 +N true RANDOM true 0 1 +N false SORTED 1 -1 +C 0 1 +C 1 2 diff --git a/backwards slightly random.txt b/backwards slightly random.txt new file mode 100644 index 00000000..a1ca0b00 --- /dev/null +++ b/backwards slightly random.txt @@ -0,0 +1,4 @@ +N false REVERSE 25 15 0 1 +N false ALMOST 275 15 1 -1 +C 0 1 +C 1 2 diff --git a/src/dialogs/CustomImageDialog.java b/src/dialogs/CustomImageDialog.java index e937a34f..ef4b812f 100644 --- a/src/dialogs/CustomImageDialog.java +++ b/src/dialogs/CustomImageDialog.java @@ -14,16 +14,16 @@ public CustomImageDialog() { FileNameExtensionFilter webmpImages = new FileNameExtensionFilter("WEBMP Images", "wbmp"); this.removeAllFilesOption(); - this.fileDialog.addChoosableFileFilter(allImages); - this.fileDialog.addChoosableFileFilter(jpegImages); - this.fileDialog.addChoosableFileFilter(pngImages); - this.fileDialog.addChoosableFileFilter(gifImages); - this.fileDialog.addChoosableFileFilter(bmpImages); - this.fileDialog.addChoosableFileFilter(webmpImages); + fileDialog.addChoosableFileFilter(allImages); + fileDialog.addChoosableFileFilter(jpegImages); + fileDialog.addChoosableFileFilter(pngImages); + fileDialog.addChoosableFileFilter(gifImages); + fileDialog.addChoosableFileFilter(bmpImages); + fileDialog.addChoosableFileFilter(webmpImages); - this.fileDialog.setDialogTitle("Choose an image..."); + fileDialog.setDialogTitle("Choose an image..."); - this.fileDialog.showDialog(null, "Select"); - this.file = this.fileDialog.getSelectedFile(); + fileDialog.showOpenDialog(null); + this.file = fileDialog.getSelectedFile(); } } \ No newline at end of file diff --git a/src/dialogs/ExportShuffleDialog.java b/src/dialogs/ExportShuffleDialog.java new file mode 100644 index 00000000..e70ca657 --- /dev/null +++ b/src/dialogs/ExportShuffleDialog.java @@ -0,0 +1,12 @@ +package dialogs; + +final public class ExportShuffleDialog extends FileDialog { + public ExportShuffleDialog() { + super(); + + fileDialog.setDialogTitle("Choose where to export the current shuffle graph..."); + + fileDialog.showSaveDialog(null); + this.file = fileDialog.getSelectedFile(); + } +} \ No newline at end of file diff --git a/src/dialogs/ImportShuffleDialog.java b/src/dialogs/ImportShuffleDialog.java new file mode 100644 index 00000000..f1d2ae2a --- /dev/null +++ b/src/dialogs/ImportShuffleDialog.java @@ -0,0 +1,12 @@ +package dialogs; + +final public class ImportShuffleDialog extends FileDialog { + public ImportShuffleDialog() { + super(); + + fileDialog.setDialogTitle("Choose where to import the current shuffle graph from..."); + + fileDialog.showOpenDialog(null); + this.file = fileDialog.getSelectedFile(); + } +} \ No newline at end of file diff --git a/src/dialogs/ImportSortDialog.java b/src/dialogs/ImportSortDialog.java index b2e0e941..9587b28a 100644 --- a/src/dialogs/ImportSortDialog.java +++ b/src/dialogs/ImportSortDialog.java @@ -8,11 +8,11 @@ public ImportSortDialog() { FileNameExtensionFilter javaFiles = new FileNameExtensionFilter("Java Source Files (.java)", "java"); this.removeAllFilesOption(); - this.fileDialog.addChoosableFileFilter(javaFiles); + fileDialog.addChoosableFileFilter(javaFiles); - this.fileDialog.setDialogTitle("Choose a sort file to import..."); + fileDialog.setDialogTitle("Choose a sort file to import..."); - this.fileDialog.showDialog(null, "Select"); - this.file = this.fileDialog.getSelectedFile(); + fileDialog.showOpenDialog(null); + this.file = fileDialog.getSelectedFile(); } } \ No newline at end of file diff --git a/src/dialogs/LoadCustomDistributionDialog.java b/src/dialogs/LoadCustomDistributionDialog.java index 47c7d24b..23c0e1e1 100644 --- a/src/dialogs/LoadCustomDistributionDialog.java +++ b/src/dialogs/LoadCustomDistributionDialog.java @@ -4,9 +4,9 @@ final public class LoadCustomDistributionDialog extends FileDialog { public LoadCustomDistributionDialog() { super(); - this.fileDialog.setDialogTitle("Choose a distribution file..."); + fileDialog.setDialogTitle("Choose a distribution file..."); - this.fileDialog.showDialog(null, "Select"); - this.file = this.fileDialog.getSelectedFile(); + fileDialog.showDialog(null, "Select"); + this.file = fileDialog.getSelectedFile(); } } \ No newline at end of file diff --git a/src/dialogs/RunScriptDialog.java b/src/dialogs/RunScriptDialog.java index 72e648b2..19dce4bc 100644 --- a/src/dialogs/RunScriptDialog.java +++ b/src/dialogs/RunScriptDialog.java @@ -4,9 +4,9 @@ final public class RunScriptDialog extends FileDialog { public RunScriptDialog() { super(); - this.fileDialog.setDialogTitle("Choose a script file..."); + fileDialog.setDialogTitle("Choose a script file..."); - this.fileDialog.showDialog(null, "Select"); - this.file = this.fileDialog.getSelectedFile(); + fileDialog.showDialog(null, "Select"); + this.file = fileDialog.getSelectedFile(); } } \ No newline at end of file diff --git a/src/dialogs/SaveArrayDialog.java b/src/dialogs/SaveArrayDialog.java index 38ba1860..01430984 100644 --- a/src/dialogs/SaveArrayDialog.java +++ b/src/dialogs/SaveArrayDialog.java @@ -1,14 +1,12 @@ package dialogs; -import javax.swing.filechooser.FileNameExtensionFilter; - final public class SaveArrayDialog extends FileDialog { public SaveArrayDialog() { super(); - this.fileDialog.setDialogTitle("Choose where to save the contents of the main array..."); + fileDialog.setDialogTitle("Choose where to save the contents of the main array..."); - this.fileDialog.showSaveDialog(null); - this.file = this.fileDialog.getSelectedFile(); + fileDialog.showSaveDialog(null); + this.file = fileDialog.getSelectedFile(); } } \ No newline at end of file diff --git a/src/dialogs/ShuffleDialog.java b/src/dialogs/ShuffleDialog.java new file mode 100644 index 00000000..01c21e41 --- /dev/null +++ b/src/dialogs/ShuffleDialog.java @@ -0,0 +1,518 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package dialogs; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import javax.swing.GroupLayout; +import javax.swing.JFrame; +import javax.swing.JOptionPane; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import frames.AppFrame; +import frames.UtilFrame; +import main.ArrayManager; +import panels.ShufflePanel; +import panes.JErrorPane; +import utils.Distributions; +import utils.ShuffleGraph; +import utils.ShuffleInfo; +import utils.Shuffles; +import utils.shuffle_utils.GraphReader; +import utils.shuffle_utils.GraphWriter; +import utils.shuffle_utils.GraphReader.MalformedGraphFileException; + +/* + * +MIT License + +Copyright (c) 2019 w0rthy +Copyright (c) 2021 ArrayV 4.0 Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + * + */ + +/** + * + * @author S630690 + */ +final public class ShuffleDialog extends javax.swing.JDialog implements AppFrame { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private ArrayManager ArrayManager; + private JFrame Frame; + private UtilFrame UtilFrame; + private List distributions; + private static boolean perShuffleDelay = false; + + private boolean bypassEvents; + + /** + * Creates new form SortPrompt + */ + @SuppressWarnings("unchecked") + public ShuffleDialog(ArrayManager ArrayManager, JFrame frame, UtilFrame utilFrame) { + super(frame, "ArrayV Advanced Shuffle Editor", true); + + this.ArrayManager = ArrayManager; + this.Frame = frame; + this.UtilFrame = utilFrame; + + initComponents(); + + bypassEvents = true; + this.shuffleEditor.graph = ArrayManager.getShuffle(); + jList4.setListData(ArrayManager.getDistributionIDs()); + for(int i = 0; i < ArrayManager.getDistributions().length; i++) { + if(ArrayManager.getDistribution().equals(ArrayManager.getDistributions()[i])) { + jList4.setSelectedIndex(i); + break; + } + } + + distributions = Arrays.stream(ArrayManager.getDistributions()) + .filter(dist -> dist.getName() != "Custom") + .collect(Collectors.toList()); + Object[] distributionNames = distributions.stream() + .map(Distributions::getName) + .collect(Collectors.toList()) + .toArray(); + jList1.setListData(distributionNames); + jList3.setListData(distributionNames); + + jList2.setListData(ArrayManager.getShuffleIDs()); + + jTextField1.setText(Double.toString( + perShuffleDelay ? + shuffleEditor.graph.sleepRatio / shuffleEditor.graph.size() : + shuffleEditor.graph.sleepRatio + )); + jCheckBox1.setSelected(perShuffleDelay); + bypassEvents = false; + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + if (perShuffleDelay = jCheckBox1.isSelected()) { + shuffleEditor.graph.sleepRatio *= shuffleEditor.graph.size(); + } + } + }); + + setMinimumSize(new Dimension(765, 310)); + setAlwaysOnTop(false); + reposition(); + setVisible(true); + } + + @Override + public void reposition() { + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings({ "rawtypes" }) + // //GEN-BEGIN:initComponents + private void initComponents() { + + this.shuffleEditor = new ShufflePanel(); + + this.jButton1 = new javax.swing.JButton(); + this.jButton2 = new javax.swing.JButton(); + this.jButton3 = new javax.swing.JButton(); + + this.jTextField1 = new javax.swing.JTextField(); + this.jLabel5 = new javax.swing.JLabel(); + this.jCheckBox1 = new javax.swing.JCheckBox(); + + this.jScrollPane4 = new javax.swing.JScrollPane(); + this.jList4 = new javax.swing.JList(); + this.jLabel4 = new javax.swing.JLabel(); + + this.jScrollPane1 = new javax.swing.JScrollPane(); + this.jList1 = new javax.swing.JList(); + this.jLabel1 = new javax.swing.JLabel(); + + this.jScrollPane3 = new javax.swing.JScrollPane(); + this.jList3 = new javax.swing.JList(); + this.jLabel3 = new javax.swing.JLabel(); + + this.jScrollPane2 = new javax.swing.JScrollPane(); + this.jList2 = new javax.swing.JList(); + this.jLabel2 = new javax.swing.JLabel(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + jButton1.setText("Import..."); + jButton1.addActionListener(new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton1ActionPerformed(); + } + }); + + jButton2.setText("Export..."); + jButton2.addActionListener(new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton2ActionPerformed(); + } + }); + + jButton3.setText("Clear Disconnected Nodes"); + jButton3.addActionListener(new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton3ActionPerformed(); + } + }); + + jLabel5.setText("Sleep Ratio"); + + jTextField1.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + jTextField1TextChanged(e); + } + @Override + public void removeUpdate(DocumentEvent e) { + jTextField1TextChanged(e); + } + @Override + public void changedUpdate(DocumentEvent e) { + jTextField1TextChanged(e); + } + }); + + jCheckBox1.setText("Time per sub-shuffle"); + + jScrollPane4.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + jScrollPane4.setViewportView(this.jList4); + + jScrollPane1.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + jScrollPane1.setViewportView(this.jList1); + + jScrollPane3.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + jScrollPane3.setViewportView(this.jList3); + + jScrollPane2.setViewportView(this.jList2); + jScrollPane2.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + jList4.addListSelectionListener(new javax.swing.event.ListSelectionListener() { + @Override + public void valueChanged(javax.swing.event.ListSelectionEvent evt) { + try { + jList4ValueChanged(evt); + } catch (Exception e) { + JErrorPane.invokeErrorMessage(e); + } + } + }); + + jLabel4.setText("Base Distribution"); + + jList1.addListSelectionListener(new javax.swing.event.ListSelectionListener() { + @Override + public void valueChanged(javax.swing.event.ListSelectionEvent evt) { + try { + jList1ValueChanged(evt); + } catch (Exception e) { + JErrorPane.invokeErrorMessage(e); + } + } + }); + + jLabel1.setText("Distribution Stretch"); + + jList3.addListSelectionListener(new javax.swing.event.ListSelectionListener() { + @Override + public void valueChanged(javax.swing.event.ListSelectionEvent evt) { + try { + jList3ValueChanged(evt); + } catch (Exception e) { + JErrorPane.invokeErrorMessage(e); + } + } + }); + + jLabel3.setText("Distribution Warp"); + + jList2.addListSelectionListener(new javax.swing.event.ListSelectionListener() { + @Override + public void valueChanged(javax.swing.event.ListSelectionEvent evt) { + try { + jList2ValueChanged(evt); + } catch (Exception e) { + JErrorPane.invokeErrorMessage(e); + } + } + }); + + jLabel2.setText("Shuffle"); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createSequentialGroup() + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(this.jLabel4) + .addComponent(this.jScrollPane4, 175, 175, 175) + .addComponent(this.jButton3) + .addComponent(this.jLabel5) + .addComponent(this.jTextField1) + .addComponent(this.jCheckBox1)) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(this.shuffleEditor) + .addGap(10, 10, 10)) + .addGroup(layout.createSequentialGroup() + .addGap(10, 75, 75) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(this.jLabel1) + .addComponent(this.jScrollPane1, 175, 175, 175)) + .addGap(10, 75, 75) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(this.jLabel3) + .addComponent(this.jScrollPane3, 175, 175, 175)) + .addGap(10, 75, 75) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(this.jLabel2) + .addComponent(this.jScrollPane2, 175, 175, 175)) + .addGap(10, 75, 75)) + .addGroup(layout.createSequentialGroup() + .addGap(150, 150, 150) + .addComponent(this.jButton1) + .addGap(20, 20, 20) + .addComponent(this.jButton2) + .addGap(150, 150, 150))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(10, 30, 30) + .addComponent(this.jLabel4) + .addComponent(this.jScrollPane4, 175, 175, 175) + .addGap(20, 20, 20) + .addComponent(this.jButton3) + .addGap(20, 20, 20) + .addComponent(this.jLabel5) + .addComponent(this.jTextField1, 20, 20, 20) + .addComponent(this.jCheckBox1)) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(this.shuffleEditor) + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addGroup(layout.createSequentialGroup() + .addComponent(this.jLabel1) + .addComponent(this.jScrollPane1, 175, 175, 175)) + .addGroup(layout.createSequentialGroup() + .addComponent(this.jLabel3) + .addComponent(this.jScrollPane3, 175, 175, 175)) + .addGroup(layout.createSequentialGroup() + .addComponent(this.jLabel2) + .addComponent(this.jScrollPane2, 175, 175, 175))) + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(GroupLayout.Alignment.CENTER) + .addComponent(this.jButton1) + .addComponent(this.jButton2)) + .addGap(10, 10, 10)) + ); + pack(); + }// //GEN-END:initComponents + + private void jButton1ActionPerformed() {//GEN-FIRST:event_jButton1ActionPerformed + FileDialog fileDialog = new ImportShuffleDialog(); + ShuffleGraph newShuffle; + try { + newShuffle = new GraphReader().read(fileDialog.file); + } catch (IOException e) { + e.printStackTrace(); + JErrorPane.invokeCustomErrorMessage("IO Error: " + e.getMessage()); + return; + } catch (MalformedGraphFileException e) { + e.printStackTrace(); + JErrorPane.invokeCustomErrorMessage("Error Parsing File: " + e.getMessage()); + return; + } + ArrayManager.setShuffle(newShuffle); + if (jCheckBox1.isSelected()) { + shuffleEditor.graph.sleepRatio /= shuffleEditor.graph.size(); + } + jTextField1.setForeground(Color.BLACK); + jTextField1.setText(Double.toString(newShuffle.sleepRatio)); + this.shuffleEditor.graph = newShuffle; + this.shuffleEditor.repaint(); + }//GEN-LAST:event_jButton1ActionPerformed + + private void jButton2ActionPerformed() {//GEN-FIRST:event_jButton1ActionPerformed + FileDialog fileDialog = new ExportShuffleDialog(); + double oldSleepRatio = shuffleEditor.graph.sleepRatio; + if (jCheckBox1.isSelected()) { + shuffleEditor.graph.sleepRatio *= shuffleEditor.graph.size(); + } + try { + new GraphWriter(shuffleEditor.graph).write(fileDialog.file); + } catch (IOException e) { + shuffleEditor.graph.sleepRatio = oldSleepRatio; + e.printStackTrace(); + JErrorPane.invokeCustomErrorMessage("IO Error: " + e.getMessage()); + return; + } + shuffleEditor.graph.sleepRatio = oldSleepRatio; + JOptionPane.showMessageDialog(null, + "Successfully exported current shuffle to file \"" + fileDialog.file.getAbsolutePath() + "\"", + "Advanced Shuffle Editor", JOptionPane.INFORMATION_MESSAGE); + }//GEN-LAST:event_jButton1ActionPerformed + + private void jButton3ActionPerformed() {//GEN-FIRST:event_jButton1ActionPerformed + shuffleEditor.graph.removeAllDisconnected(); + shuffleEditor.repaint(); + }//GEN-LAST:event_jButton1ActionPerformed + + private void jTextField1TextChanged(DocumentEvent e) {//GEN-FIRST:event_jList1ValueChanged + String text = jTextField1.getText(); + if (text.length() == 0) return; + double sleepRatio; + try { + sleepRatio = Double.parseDouble(text); + } catch (NumberFormatException ex) { + jTextField1.setForeground(new Color(204, 0, 0)); + return; + } + jTextField1.setForeground(Color.BLACK); + shuffleEditor.graph.sleepRatio = sleepRatio; + }//GEN-LAST:event_jList1ValueChanged + + private void addToGraph(ShuffleInfo shuffle) { + Point safePos = shuffleEditor.graph.findSafeCoordinates(100, 100, 20, 20); + shuffleEditor.graph.addDisconnected(shuffle, safePos.x, safePos.y); + } + + private void jList4ValueChanged(javax.swing.event.ListSelectionEvent evt) throws Exception {//GEN-FIRST:event_jList1ValueChanged + // TODO add your handling code here: + if (bypassEvents) + return; + int selection = jList4.getSelectedIndex(); + Distributions[] distributions = ArrayManager.getDistributions(); + if (selection >= 0 && selection < distributions.length) + ArrayManager.setDistribution(distributions[selection]); + }//GEN-LAST:event_jList1ValueChanged + + private void jList1ValueChanged(javax.swing.event.ListSelectionEvent evt) throws Exception {//GEN-FIRST:event_jList1ValueChanged + // TODO add your handling code here: + if (bypassEvents) + return; + String selection = (String)jList1.getSelectedValue(); + Distributions distribution = distributions.stream() + .filter(d -> d.getName().equals(selection)) + .findFirst() + .orElse(null); + if (distribution != null) + addToGraph(new ShuffleInfo(distribution, false)); + shuffleEditor.repaint(); + bypassEvents = true; + jList1.clearSelection(); + bypassEvents = false; + }//GEN-LAST:event_jList1ValueChanged + + private void jList3ValueChanged(javax.swing.event.ListSelectionEvent evt) throws Exception {//GEN-FIRST:event_jList1ValueChanged + // TODO add your handling code here: + if (bypassEvents) + return; + String selection = (String)jList3.getSelectedValue(); + Distributions distribution = distributions.stream() + .filter(d -> d.getName().equals(selection)) + .findFirst() + .orElse(null); + if (distribution != null) + addToGraph(new ShuffleInfo(distribution, true)); + shuffleEditor.repaint(); + bypassEvents = true; + jList3.clearSelection(); + bypassEvents = false; + }//GEN-LAST:event_jList1ValueChanged + + private void jList2ValueChanged(javax.swing.event.ListSelectionEvent evt) throws Exception {//GEN-FIRST:event_jList1ValueChanged + // TODO add your handling code here: + if (bypassEvents) + return; + int selection = jList2.getSelectedIndex(); + Shuffles[] shuffles = ArrayManager.getShuffles(); + if (selection >= 0 && selection < shuffles.length) + addToGraph(new ShuffleInfo(shuffles[selection])); + shuffleEditor.repaint(); + bypassEvents = true; + jList2.clearSelection(); + bypassEvents = false; + }//GEN-LAST:event_jList1ValueChanged + + // Variables declaration - do not modify//GEN-BEGIN:variables + private ShufflePanel shuffleEditor; + + private javax.swing.JButton jButton1; + private javax.swing.JButton jButton2; + private javax.swing.JButton jButton3; + + private javax.swing.JTextField jTextField1; + private javax.swing.JLabel jLabel5; + private javax.swing.JCheckBox jCheckBox1; + + @SuppressWarnings("rawtypes") + private javax.swing.JList jList4; + private javax.swing.JScrollPane jScrollPane4; + private javax.swing.JLabel jLabel4; + + @SuppressWarnings("rawtypes") + private javax.swing.JList jList1; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JLabel jLabel1; + + @SuppressWarnings("rawtypes") + private javax.swing.JList jList3; + private javax.swing.JScrollPane jScrollPane3; + private javax.swing.JLabel jLabel3; + + @SuppressWarnings("rawtypes") + private javax.swing.JList jList2; + private javax.swing.JScrollPane jScrollPane2; + private javax.swing.JLabel jLabel2; + // End of variables declaration//GEN-END:variables +} \ No newline at end of file diff --git a/src/dialogs/SoundbankDialog.java b/src/dialogs/SoundbankDialog.java index 7d7da77b..58196aad 100644 --- a/src/dialogs/SoundbankDialog.java +++ b/src/dialogs/SoundbankDialog.java @@ -12,14 +12,14 @@ public SoundbankDialog() { FileNameExtensionFilter generalMIDI = new FileNameExtensionFilter("General MIDI (.gm)", "gm"); this.removeAllFilesOption(); - this.fileDialog.addChoosableFileFilter(allSoundbanks); - this.fileDialog.addChoosableFileFilter(soundfonts); - this.fileDialog.addChoosableFileFilter(downloadableSounds); - this.fileDialog.addChoosableFileFilter(generalMIDI); + fileDialog.addChoosableFileFilter(allSoundbanks); + fileDialog.addChoosableFileFilter(soundfonts); + fileDialog.addChoosableFileFilter(downloadableSounds); + fileDialog.addChoosableFileFilter(generalMIDI); - this.fileDialog.setDialogTitle("Choose a MIDI soundbank..."); + fileDialog.setDialogTitle("Choose a MIDI soundbank..."); - this.fileDialog.showDialog(null, "Select"); - this.file = this.fileDialog.getSelectedFile(); + fileDialog.showDialog(null, "Select"); + this.file = fileDialog.getSelectedFile(); } } \ No newline at end of file diff --git a/src/main/ArrayManager.java b/src/main/ArrayManager.java index f0107d54..0bc00946 100644 --- a/src/main/ArrayManager.java +++ b/src/main/ArrayManager.java @@ -1,17 +1,14 @@ package main; import java.util.Arrays; -import java.util.Comparator; -import java.util.Hashtable; -import java.util.Random; -import java.util.function.IntConsumer; import panes.JErrorPane; import utils.Delays; import utils.Highlights; +import utils.ShuffleGraph; +import utils.ShuffleInfo; import utils.Shuffles; import utils.Distributions; -import utils.Statistics; import utils.Writes; /* @@ -54,17 +51,17 @@ final public class ArrayManager { private ArrayVisualizer ArrayVisualizer; private Delays Delays; private Highlights Highlights; - private Shuffles Shuffles; - private Distributions Distributions; + private ShuffleGraph shuffle; + private Distributions distribution; private Writes Writes; public ArrayManager(ArrayVisualizer arrayVisualizer) { this.ArrayVisualizer = arrayVisualizer; - this.Shuffles = utils.Shuffles.RANDOM; - this.Distributions = utils.Distributions.LINEAR; - this.shuffleTypes = utils.Shuffles.values(); - this.distributionTypes = utils.Distributions.values(); + this.shuffle = ShuffleGraph.single(Shuffles.RANDOM); + this.distribution = Distributions.LINEAR; + this.shuffleTypes = Shuffles.values(); + this.distributionTypes = Distributions.values(); hadDistributionAllocationError = false; @@ -108,7 +105,7 @@ public void initializeArray(int[] array) { hadDistributionAllocationError = true; temp = array; } - Distributions.initializeArray(temp, this.ArrayVisualizer); + distribution.initializeArray(temp, this.ArrayVisualizer); double uniqueFactor = (double)currentLen/ArrayVisualizer.getUniqueItems(); for(int i = 0; i < currentLen; i++) @@ -124,11 +121,31 @@ public String[] getShuffleIDs() { public Shuffles[] getShuffles() { return this.shuffleTypes; } - public Shuffles getShuffle() { - return this.Shuffles; + public ShuffleGraph getShuffle() { + return this.shuffle; } + + /** + * @deprecated This method is deprecatated. Please use {@link #setShuffleSingle(Shuffles)} or {@link #setShuffle(ShuffleGraph)} instead. + * @see #setShuffleSingle(Shuffles) + * @see #setShuffle(ShuffleGraph) + */ public void setShuffle(Shuffles choice) { - this.Shuffles = choice; + this.setShuffleSingle(choice); + } + + public void setShuffle(ShuffleGraph graph) { + this.shuffle = graph; + } + + public void setShuffleSingle(Shuffles shuffle) { + this.setShuffle(ShuffleGraph.single(shuffle)); + } + public void setShuffleSingle(Distributions distribution) { + this.setShuffle(ShuffleGraph.single(distribution)); + } + public void setShuffleSingle(Distributions distribution, boolean warped) { + this.setShuffle(ShuffleGraph.single(distribution, warped)); } public String[] getDistributionIDs() { @@ -138,15 +155,19 @@ public Distributions[] getDistributions() { return this.distributionTypes; } public Distributions getDistribution() { - return this.Distributions; + return this.distribution; } public void setDistribution(Distributions choice) { - this.Distributions = choice; - this.Distributions.selectDistribution(ArrayVisualizer.getArray(), ArrayVisualizer); + this.distribution = choice; + this.distribution.selectDistribution(ArrayVisualizer.getArray(), ArrayVisualizer); if (!ArrayVisualizer.isActive()) this.initializeArray(ArrayVisualizer.getArray()); } + public boolean containsShuffle(Shuffles shuffle) { + return this.shuffle.contains(new ShuffleInfo(shuffle)); + } + public void shuffleArray(int[] array, int currentLen, ArrayVisualizer ArrayVisualizer) { this.initializeArray(array); @@ -157,14 +178,11 @@ public void shuffleArray(int[] array, int currentLen, ArrayVisualizer ArrayVisua if(ArrayVisualizer.isActive()) { double sleepRatio = ArrayVisualizer.getCurrentLength()/1024d; + sleepRatio *= shuffle.sleepRatio; Delays.setSleepRatio(sleepRatio); } - Shuffles tempShuffle = this.Shuffles; - if(Distributions == Distributions.RANDOM || Distributions == Distributions.EQUAL) - this.Shuffles = Shuffles.ALREADY; - Shuffles.shuffleArray(array, this.ArrayVisualizer, Delays, Highlights, Writes); - this.Shuffles = tempShuffle; + shuffle.shuffleArray(array, this.ArrayVisualizer); Delays.setSleepRatio(speed); diff --git a/src/main/ArrayVisualizer.java b/src/main/ArrayVisualizer.java index fcad3640..e8c55783 100644 --- a/src/main/ArrayVisualizer.java +++ b/src/main/ArrayVisualizer.java @@ -22,8 +22,6 @@ import java.awt.event.KeyListener; import java.awt.event.WindowEvent; import java.io.File; -import java.io.FileWriter; -import java.io.IOException; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -1216,7 +1214,7 @@ private void drawWindows() { this.window.setLocation(0, 0); this.window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - this.window.setTitle("w0rthy's Array Visualizer - " + (this.ComparisonSorts.length + this.DistributionSorts.length) + " Sorts, 15 Visual Styles, and " + (Distributions.values().length * Shuffles.values().length) + " Inputs to Sort"); + this.window.setTitle("w0rthy's Array Visualizer - " + (this.ComparisonSorts.length + this.DistributionSorts.length) + " Sorts, 15 Visual Styles, and Infinite Inputs to Sort"); this.window.setBackground(Color.BLACK); this.window.setIgnoreRepaint(true); diff --git a/src/panels/ShufflePanel.java b/src/panels/ShufflePanel.java new file mode 100644 index 00000000..bc490c02 --- /dev/null +++ b/src/panels/ShufflePanel.java @@ -0,0 +1,96 @@ +package panels; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Point; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +import utils.ShuffleGraph; + +public class ShufflePanel extends JPanel implements KeyListener { + int camX = 0, camY = 0; + int prevX = 0, prevY = 0; + public ShuffleGraph graph; + + public ShufflePanel() { + setPreferredSize(new Dimension(700, 450)); + MouseHandler handler = new MouseHandler(); + addMouseListener(handler); + addMouseMotionListener(handler); + addKeyListener(this); + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D)g; + g.setFont(new Font("ariel", Font.PLAIN, 24)); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(new Color(128, 128, 128)); + g.fillRect(0, 0, getWidth(), getHeight()); + g.translate(camX, camY); + graph.draw(g2d); + } + + @Override + public void keyTyped(KeyEvent e) {} + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_DELETE) { + graph.deleteNode(); + repaint(); + } + } + + @Override + public void keyReleased(KeyEvent e) {} + + protected class MouseHandler extends MouseAdapter { + @Override + public void mousePressed(MouseEvent e) { + requestFocus(); // Java doesn't handle this for us? + prevX = e.getX(); + prevY = e.getY(); + if (SwingUtilities.isLeftMouseButton(e)) { + Point grab = new Point(e.getX() - camX, e.getY() - camY); + graph.select(grab); + repaint(); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (SwingUtilities.isLeftMouseButton(e)) { + graph.endConnection(); + repaint(); + } + } + + @Override + public void mouseDragged(MouseEvent e) { + if (SwingUtilities.isMiddleMouseButton(e)) { + camX += e.getX() - prevX; + camY += e.getY() - prevY; + if (camX > 0) { + camX = 0; + } + repaint(); + } else if (SwingUtilities.isLeftMouseButton(e)) { + graph.drag(new Point(e.getX() - prevX, e.getY() - prevY)); + repaint(); + } + prevX = e.getX(); + prevY = e.getY(); + } + } +} diff --git a/src/prompts/ShufflePrompt.java b/src/prompts/ShufflePrompt.java index bb6e013b..09a7b0bd 100644 --- a/src/prompts/ShufflePrompt.java +++ b/src/prompts/ShufflePrompt.java @@ -4,9 +4,12 @@ */ package prompts; +import java.util.Arrays; + +import javax.swing.DefaultListModel; import javax.swing.JFrame; -import javax.swing.JOptionPane; +import dialogs.ShuffleDialog; import frames.AppFrame; import frames.UtilFrame; import main.ArrayManager; @@ -19,6 +22,7 @@ MIT License Copyright (c) 2019 w0rthy +Copyright (c) 2021 ArrayV 4.0 Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -55,6 +59,7 @@ final public class ShufflePrompt extends javax.swing.JFrame implements AppFrame private JFrame Frame; private UtilFrame UtilFrame; + private DefaultListModel shuffleModel; private boolean initializing; /** @@ -78,11 +83,22 @@ public ShufflePrompt(ArrayManager ArrayManager, JFrame frame, UtilFrame utilFram break; } } - jList2.setListData(ArrayManager.getShuffleIDs()); - for(int i = 0; i < ArrayManager.getShuffles().length; i++) { - if(ArrayManager.getShuffle().equals(ArrayManager.getShuffles()[i])) { - jList2.setSelectedIndex(i); - break; + shuffleModel = new DefaultListModel<>(); + jList2.setModel(shuffleModel); + Arrays.stream(ArrayManager.getShuffleIDs()).forEach(shuffleModel::addElement); + if (ArrayManager.getShuffle().size() > 1) { + shuffleModel.add(0, "Advanced"); + jList2.setSelectedIndex(0); + } else { + for(int i = 0; i < ArrayManager.getShuffles().length; i++) { + if(ArrayManager.containsShuffle(ArrayManager.getShuffles()[i])) { + jList2.setSelectedIndex(i); + break; + } + } + if (jList2.getSelectedIndex() == -1) { + shuffleModel.add(0, "Advanced"); + jList2.setSelectedIndex(0); } } initializing = false; @@ -106,6 +122,8 @@ public void reposition() { // //GEN-BEGIN:initComponents private void initComponents() { + this.jButton1 = new javax.swing.JButton(); + this.jLabel1 = new javax.swing.JLabel(); this.jScrollPane1 = new javax.swing.JScrollPane(); this.jList1 = new javax.swing.JList(); @@ -116,6 +134,14 @@ private void initComponents() { setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + jButton1.setText("Open Advanced Editor"); + jButton1.addActionListener(new java.awt.event.ActionListener() { + @Override + public void actionPerformed(java.awt.event.ActionEvent evt) { + jButton1ActionPerformed(); + } + }); + jLabel1.setText("What shape do you want the array to have?"); jLabel2.setText("How do you want the array to be shuffled?"); @@ -168,7 +194,9 @@ public void valueChanged(javax.swing.event.ListSelectionEvent evt) { .addGap(5, 5, 5)) .addGroup(layout.createSequentialGroup() .addComponent(this.jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 175, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(20, 20, 20))) + .addGap(20, 20, 20)) + .addGroup(javax.swing.GroupLayout.Alignment.CENTER, layout.createSequentialGroup() + .addComponent(this.jButton1))) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -187,12 +215,20 @@ public void valueChanged(javax.swing.event.ListSelectionEvent evt) { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, true) .addComponent(this.jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 175, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(20, 20, 20)) + .addGap(10, 10, 10) + .addComponent(this.jButton1) + .addGap(15, 15, 15)) ); pack(); }// //GEN-END:initComponents + private void jButton1ActionPerformed() {//GEN-FIRST:event_jList1ValueChanged + UtilFrame.jButton6ResetText(); + dispose(); + new ShuffleDialog(ArrayManager, this, UtilFrame); + }//GEN-LAST:event_jList1ValueChanged + private void jList1ValueChanged(javax.swing.event.ListSelectionEvent evt) throws Exception {//GEN-FIRST:event_jList1ValueChanged // TODO add your handling code here: if (initializing) @@ -208,12 +244,19 @@ private void jList2ValueChanged(javax.swing.event.ListSelectionEvent evt) throws if (initializing) return; int selection = jList2.getSelectedIndex(); + if (shuffleModel.getElementAt(0).equals("Advanced")) { + if (selection == 0) return; + shuffleModel.remove(0); + selection--; + } Shuffles[] shuffles = ArrayManager.getShuffles(); if (selection >= 0 && selection < shuffles.length) - ArrayManager.setShuffle(shuffles[selection]); + ArrayManager.setShuffleSingle(shuffles[selection]); }//GEN-LAST:event_jList1ValueChanged // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButton1; + private javax.swing.JLabel jLabel1; @SuppressWarnings("rawtypes") private javax.swing.JList jList1; diff --git a/src/threads/RunExchangeSorts.java b/src/threads/RunExchangeSorts.java index c26e9df2..c1b6dfb4 100644 --- a/src/threads/RunExchangeSorts.java +++ b/src/threads/RunExchangeSorts.java @@ -177,7 +177,7 @@ protected synchronized void executeSortList(int[] array) throws Exception { RunExchangeSorts.this.runIndividualSort(CircleSortIterative, 0, array, 1024, 1, false); RunExchangeSorts.this.runIndividualSort(CircleMergeSort, 0, array, 1024, 0.75, false); RunExchangeSorts.this.runIndividualSort(PseudoHeapSort, 0, array, 1024, 1.5, false); - RunExchangeSorts.this.runIndividualSort(LLQuickSort, 0, array, 2048, arrayManager.getShuffle() == Shuffles.RANDOM ? 1.5 : 5, false); + RunExchangeSorts.this.runIndividualSort(LLQuickSort, 0, array, 2048, arrayManager.containsShuffle(Shuffles.RANDOM) ? 1.5 : 5, false); RunExchangeSorts.this.runIndividualSort(LLQuickSortMiddlePivot, 0, array, 2048, 1.5, false); RunExchangeSorts.this.runIndividualSort(LRQuickSort, 0, array, 2048, 1, false); RunExchangeSorts.this.runIndividualSort(LRQuickSortParallel, 0, array, 2048, 1, false); @@ -185,7 +185,7 @@ protected synchronized void executeSortList(int[] array) throws Exception { RunExchangeSorts.this.runIndividualSort(StacklessQuickSort, 0, array, 2048, 1, false); RunExchangeSorts.this.runIndividualSort(IterativeQuickSort, 0, array, 2048, 1, false); // RunExchangeSorts.this.runIndividualSort(MeanQuickSort, 0, array, 2048, 1, false); - RunExchangeSorts.this.runIndividualSort(StableQuickSort, 0, array, 2048, arrayManager.getShuffle() == Shuffles.RANDOM ? 1 : 6.5, false); + RunExchangeSorts.this.runIndividualSort(StableQuickSort, 0, array, 2048, arrayManager.containsShuffle(Shuffles.RANDOM) ? 1 : 6.5, false); RunExchangeSorts.this.runIndividualSort(StableQuickSortMiddlePivot, 0, array, 2048, 1, false); RunExchangeSorts.this.runIndividualSort(ooPQuickSort, 0, array, 2048, 1, false); RunExchangeSorts.this.runIndividualSort(StableQuickSortParallel, 0, array, 2048, 1, false); diff --git a/src/threads/RunHybridSorts.java b/src/threads/RunHybridSorts.java index 08190a90..a74d593f 100644 --- a/src/threads/RunHybridSorts.java +++ b/src/threads/RunHybridSorts.java @@ -153,10 +153,10 @@ protected synchronized void executeSortList(int[] array) throws Exception { RunHybridSorts.this.runIndividualSort(QuickSPSort, 0, array, 512, 0.4, false); RunHybridSorts.this.runIndividualSort(BinaryMergeSort, 0, array, 2048, 1, false); RunHybridSorts.this.runIndividualSort(MergeInsertionSort, 0, array, 2048, 1.75, false); - RunHybridSorts.this.runIndividualSort(SwapMergeSort, 0, array, 2048, arrayManager.getShuffle() == Shuffles.RANDOM ? 1.65 : 6.5, false); + RunHybridSorts.this.runIndividualSort(SwapMergeSort, 0, array, 2048, arrayManager.containsShuffle(Shuffles.RANDOM) ? 1.65 : 6.5, false); RunHybridSorts.this.runIndividualSort(BaseNMergeSort, 4, array, 2048, 1, false); - RunHybridSorts.this.runIndividualSort(WeaveMergeSort, 0, array, 2048, arrayManager.getShuffle() == Shuffles.RANDOM ? 1.65 : 6.5, false); - RunHybridSorts.this.runIndividualSort(ImprovedWeaveMergeSort, 0, array, 2048, arrayManager.getShuffle() == Shuffles.RANDOM ? 1.65 : 6.5, false); + RunHybridSorts.this.runIndividualSort(WeaveMergeSort, 0, array, 2048, arrayManager.containsShuffle(Shuffles.RANDOM) ? 1.65 : 6.5, false); + RunHybridSorts.this.runIndividualSort(ImprovedWeaveMergeSort, 0, array, 2048, arrayManager.containsShuffle(Shuffles.RANDOM) ? 1.65 : 6.5, false); RunHybridSorts.this.runIndividualSort(TimSort, 0, array, 2048, 1, false); RunHybridSorts.this.runIndividualSort(CocktailMergeSort, 0, array, 2048, 1, false); RunHybridSorts.this.runIndividualSort(BubbleMergeSort, 0, array, 2048, 1, false); diff --git a/src/threads/RunInsertionSorts.java b/src/threads/RunInsertionSorts.java index aa5ae2e1..5bec4a3b 100644 --- a/src/threads/RunInsertionSorts.java +++ b/src/threads/RunInsertionSorts.java @@ -109,8 +109,8 @@ protected synchronized void executeSortList(int[] array) throws Exception { RunInsertionSorts.this.runIndividualSort(LibrarySort, 0, array, 2048, 1, false); RunInsertionSorts.this.runIndividualSort(ClassicLibrarySort, 0, array, 2048, 1, false); RunInsertionSorts.this.runIndividualSort(PatienceSort, 0, array, 2048, 1, false); - RunInsertionSorts.this.runIndividualSort(ClassicTreeSort, 0, array, 2048, arrayManager.getShuffle() == Shuffles.RANDOM ? 1 : 5, false); - RunInsertionSorts.this.runIndividualSort(TreeSort, 0, array, 2048, arrayManager.getShuffle() == Shuffles.RANDOM ? 1 : 5, false); + RunInsertionSorts.this.runIndividualSort(ClassicTreeSort, 0, array, 2048, arrayManager.containsShuffle(Shuffles.RANDOM) ? 1 : 5, false); + RunInsertionSorts.this.runIndividualSort(TreeSort, 0, array, 2048, arrayManager.containsShuffle(Shuffles.RANDOM) ? 1 : 5, false); RunInsertionSorts.this.runIndividualSort(ShuffledTreeSort, 0, array, 2048, 1, false); RunInsertionSorts.this.runIndividualSort(AATreeSort, 0, array, 2048, 1, false); RunInsertionSorts.this.runIndividualSort(AVLTreeSort, 0, array, 2048, 1, false); diff --git a/src/threads/RunQuickSorts.java b/src/threads/RunQuickSorts.java index 8016d70b..4613ccc5 100644 --- a/src/threads/RunQuickSorts.java +++ b/src/threads/RunQuickSorts.java @@ -45,7 +45,7 @@ public RunQuickSorts(ArrayVisualizer arrayVisualizer) { @Override protected synchronized void executeSortList(int[] array) throws Exception { - RunQuickSorts.this.runIndividualSort(CubeRootQuickSort, 0, array, 2048, arrayManager.getShuffle() == Shuffles.RANDOM ? 1 : 6.5, false); + RunQuickSorts.this.runIndividualSort(CubeRootQuickSort, 0, array, 2048, arrayManager.containsShuffle(Shuffles.RANDOM) ? 1 : 6.5, false); } @Override diff --git a/src/utils/Distributions.java b/src/utils/Distributions.java index 78c15aca..dd26c35b 100644 --- a/src/utils/Distributions.java +++ b/src/utils/Distributions.java @@ -2,7 +2,6 @@ import java.io.File; import java.io.FileNotFoundException; -import java.util.Arrays; import java.util.Random; import java.util.Scanner; diff --git a/src/utils/ShuffleGraph.java b/src/utils/ShuffleGraph.java new file mode 100644 index 00000000..4059b0a5 --- /dev/null +++ b/src/utils/ShuffleGraph.java @@ -0,0 +1,429 @@ +package utils; + +import java.awt.Graphics2D; +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import main.ArrayVisualizer; +import utils.shuffle_utils.ShuffleConnection; +import utils.shuffle_utils.ShuffleNode; + +public class ShuffleGraph implements Collection { + public List nodes; + public List connections; + public ShuffleNode selected; + public ShuffleConnection dragging; + public ShuffleNode dragCandidate; + public double sleepRatio; + + final static int DEFAULT_TEXT_SIZE = 24; + Map textSizes = new HashMap<>(); + + public ShuffleGraph() { + this(new ShuffleInfo[0]); + } + + public ShuffleGraph(ShuffleInfo[] shuffles) { + this.nodes = new ArrayList<>(); + this.nodes.add(new ShuffleNode(null, this, -ShuffleNode.WIDTH, 15)); + for (ShuffleInfo shuffle : shuffles) { + this.nodes.add(new ShuffleNode(shuffle, this)); + } + this.connections = new ArrayList<>(); + this.selected = null; + this.dragging = null; + this.dragCandidate = null; + this.sleepRatio = 1; + } + + public static ShuffleGraph single(ShuffleInfo shuffle) { + ShuffleGraph graph = new ShuffleGraph(); + graph.add(shuffle); + return graph; + } + + public static ShuffleGraph single(Shuffles shuffle) { + return single(new ShuffleInfo(shuffle)); + } + + public static ShuffleGraph single(Distributions distribution) { + return single(new ShuffleInfo(distribution)); + } + + public static ShuffleGraph single(Distributions distribution, boolean warped) { + return single(new ShuffleInfo(distribution, warped)); + } + + public void shuffleArray(int[] array, ArrayVisualizer arrayVisualizer) { + for (ShuffleInfo shuffle : this) { + shuffle.shuffle(array, arrayVisualizer); + } + } + + public void addDisconnected(ShuffleInfo shuffle) { + this.nodes.add(new ShuffleNode(shuffle, this)); + } + + public void addDisconnected(ShuffleInfo shuffle, int x, int y) { + this.nodes.add(new ShuffleNode(shuffle, this, x, y)); + } + + public Point findSafeCoordinates(int baseX, int baseY, int offsetX, int offsetY) { + Point p = new Point(baseX, baseY); + while (this.nodes.stream().anyMatch(node -> node.x == p.x && node.y == p.y)) { + p.x += offsetX; + p.y += offsetY; + } + return p; + } + + public int removeAllDisconnected() { + Set toKeep = new HashSet<>(); + toKeep.add(this.nodes.get(0)); + for (ShuffleNode connected : connectedNodesIterable()) { + toKeep.add(connected); + } + int removed = this.nodes.size() - toKeep.size(); + this.nodes.retainAll(toKeep); + return removed; + } + + public Iterator iterateConnectedNodes() { + return new NodeIterator(this); + } + + public Iterable connectedNodesIterable() { + return new Iterable() { + public Iterator iterator() { + return ShuffleGraph.this.iterateConnectedNodes(); + } + }; + } + + public void draw(Graphics2D g) { + for (ShuffleConnection connection : this.connections) { + connection.draw(g); + } + for (ShuffleNode node : this.nodes) { + node.draw(g); + } + } + + public void drag(Point rel) { + if (this.selected != null) { + this.selected.drag(rel); + } else if (this.dragging != null) { + Point pos = this.dragging.currentDragPos; + pos.translate(rel.x, rel.y); + boolean foundCandidate = false; + ListIterator it = this.nodes.listIterator(this.nodes.size()); + while (it.hasPrevious()) { + ShuffleNode node = it.previous(); + if (node.inArea(pos)) { + this.dragCandidate = node; + foundCandidate = true; + break; + } + } + if (!foundCandidate) { + this.dragCandidate = null; + } + } + } + + public void select(Point pos) { + ListIterator it = this.nodes.listIterator(this.nodes.size()); + while (it.hasPrevious()) { + ShuffleNode node = it.previous(); + if (node.inArea(pos)) { + this.selected = node; + return; + } else if (node.inStartDrag(pos)) { + ShuffleConnection newConnection = new ShuffleConnection(node, pos); + int removed = 0; + for (int i = 0; i < this.connections.size(); i++) { + ShuffleConnection conn = this.connections.get(i - removed); + if (conn.from == node) { + this.connections.remove(i - removed); + conn.remove(); + removed++; + } + } + this.connections.add(newConnection); + this.dragging = newConnection; + node.postConnection = newConnection; + this.selected = null; + return; + } + } + this.selected = null; + } + + public void endConnection() { + if (this.dragging == null) { + return; + } + if (this.dragCandidate != null) { + this.dragging.finishDragging(this.dragCandidate); + } else { + this.connections.remove(this.dragging); + this.dragging.remove(); + } + this.dragging = null; + this.dragCandidate = null; + } + + public void deleteNode() { + if (this.selected != null) { + this.selected.delete(); + } + } + + public void calcTextSize(String text, int fit, Graphics2D g) { + if (textSizes.containsKey(text)) { + g.setFont(g.getFont().deriveFont((float)textSizes.get(text))); + return; + } + int size = DEFAULT_TEXT_SIZE; + g.setFont(g.getFont().deriveFont((float)size)); + while (g.getFontMetrics().stringWidth(text) >= fit) { + size--; + g.setFont(g.getFont().deriveFont((float)size)); + } + textSizes.put(text, size); + } + + + // Collection code + public int size() { + int size = 0; + ShuffleNode node = this.nodes.get(0); + while (node != null) { + size++; + ShuffleConnection connect = node.postConnection; + if (connect == null) { + break; + } + node = connect.to; + } + return size - 1; + } + + public void clear() { + this.nodes.subList(1, this.nodes.size()).clear(); + this.nodes.get(0).postConnection = null; + } + + public ShuffleNode findLast() { + ShuffleNode previous = null; + ShuffleNode node = this.nodes.get(0); + while (node != null) { + ShuffleConnection connect = node.postConnection; + if (connect == null) { + previous = node; + node = null; + break; + } + previous = node; + node = connect.to; + } + return previous; + } + + public boolean add(ShuffleInfo shuffle) { + add(shuffle, findLast()); + return true; + } + + public boolean addAll(Collection c) { + ShuffleNode after = findLast(); + for (ShuffleInfo shuffle : c) { + after = add(shuffle, after); + } + return true; + } + + ShuffleNode add(ShuffleInfo shuffle, ShuffleNode after) { + ShuffleNode newNode = new ShuffleNode(shuffle, this, after.x + ShuffleNode.WIDTH + 15, after.y); + if (after.postConnection == null) { + after.postConnection = new ShuffleConnection(after, newNode); + this.connections.add(after.postConnection); + } else { + if (after.postConnection.to != null) { + after.postConnection.to.preConnection = null; + } + after.postConnection.to = newNode; + } + newNode.preConnection = after.postConnection; + this.nodes.add(newNode); + return newNode; + } + + ShuffleNode find(Object o) { + if (o == null) { + return null; + } + ShuffleNode node = this.nodes.get(0); + while (node != null) { + ShuffleConnection connect = node.postConnection; + if (connect == null) { + break; + } + node = connect.to; + if (node == null) { + break; + } + if (node.equals(o)) { + return node; + } + } + return null; + } + + public boolean contains(Object o) { + return find(o) != null; + } + + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!contains(o)) { + return false; + } + } + return true; + } + + public boolean isEmpty() { + return iterator().hasNext(); + } + + public boolean remove(Object o) { + ShuffleNode found = find(o); + if (found == null) { + return false; + } + found.delete(); + return true; + } + + public boolean removeAll(Collection c) { + boolean affected = false; + for (Object o : c) { + affected = remove(o) || affected; + } + return affected; + } + + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public Object[] toArray() { + List result = new ArrayList<>(); + for (ShuffleInfo shuffle : this) { + result.add(shuffle); + } + return result.toArray(); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) { + Class type = a.getClass().getComponentType(); + if (type == ShuffleInfo.class || type == Object.class) { + int mySize = size(); + if (mySize > a.length) { + return (T[])toArray(); + } + ShuffleInfo[] result = (ShuffleInfo[])a; + int i = 0; + ShuffleNode node = this.nodes.get(0); + while (node != null) { + ShuffleConnection connect = node.postConnection; + if (connect == null) { + break; + } + node = connect.to; + result[i++] = node.getValue(); + } + if (i < a.length - 1) { + a[i] = null; + } + return a; + } else if (type == ShuffleNode.class) { + return this.nodes.toArray(a); + } else { + throw new ArrayStoreException(); + } + } + + public Iterator iterator() { + return new GraphIterator(this); + } + + protected class GraphIterator implements Iterator { + NodeIterator it; + + GraphIterator(ShuffleGraph graph) { + this.it = new NodeIterator(graph); + } + + public boolean hasNext() { + return this.it.hasNext(); + } + + public ShuffleInfo next() { + return this.it.next().getValue(); + } + + public void remove() { + this.it.remove();; + } + } + + protected class NodeIterator implements Iterator { + ShuffleNode currentNode, nextNode; + + NodeIterator(ShuffleGraph graph) { + this.currentNode = graph.nodes.get(0); + this.nextNode = findNext(); + } + + ShuffleNode findNext() { + ShuffleConnection connect = this.currentNode.postConnection; + if (connect != null) { + ShuffleNode next = connect.to; + if (next != null) { + return next; + } + } + return null; + } + + public boolean hasNext() { + return this.nextNode != null; + } + + public ShuffleNode next() { + if (this.nextNode == null) { + throw new NoSuchElementException(); + } + this.currentNode = this.nextNode; + this.nextNode = findNext(); + return this.currentNode; + } + + public void remove() { + this.nextNode.delete(); + this.nextNode = findNext(); + } + } +} diff --git a/src/utils/ShuffleInfo.java b/src/utils/ShuffleInfo.java new file mode 100644 index 00000000..1bcd7acd --- /dev/null +++ b/src/utils/ShuffleInfo.java @@ -0,0 +1,120 @@ +package utils; + +import java.util.ArrayList; +import java.util.List; + +import main.ArrayVisualizer; + +final public class ShuffleInfo { + final boolean isDistribution; + final Distributions distribution; + final Shuffles shuffle; + final boolean warpDistribution; + + public ShuffleInfo(Distributions distribution, boolean warpDistribution) { + this.isDistribution = true; + this.distribution = distribution; + this.shuffle = null; + this.warpDistribution = warpDistribution; + } + + public ShuffleInfo(Distributions distribution) { + this(distribution, false); + } + + public ShuffleInfo(Shuffles shuffle) { + this.isDistribution = false; + this.distribution = null; + this.shuffle = shuffle; + this.warpDistribution = false; + } + + public static ShuffleInfo[] fromDistributionIterable(Iterable iterable, boolean warped) { + List result = new ArrayList<>(); + iterable.forEach(dist -> result.add(new ShuffleInfo(dist, warped))); + return result.toArray(new ShuffleInfo[0]); + } + + public static ShuffleInfo[] fromDistributionIterable(Iterable iterable) { + return fromDistributionIterable(iterable, false); + } + + public static ShuffleInfo[] fromShuffleIterable(Iterable iterable) { + List result = new ArrayList<>(); + iterable.forEach(shuff -> result.add(new ShuffleInfo(shuff))); + return result.toArray(new ShuffleInfo[0]); + } + + public boolean isDistribution() { + return this.isDistribution; + } + + public Distributions getDistribution() { + return this.distribution; + } + + public Shuffles getShuffle() { + return this.shuffle; + } + + public boolean isDistributionWarped() { + return this.warpDistribution; + } + + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o instanceof ShuffleInfo) { + ShuffleInfo other = (ShuffleInfo)o; + if (this.isDistribution != other.isDistribution) { + return false; + } + if (isDistribution) { + return this.distribution == other.distribution && this.warpDistribution == other.warpDistribution; + } else { + return this.shuffle == other.shuffle; + } + } else if (o instanceof Shuffles) { + return !this.isDistribution && this.shuffle == o; + } else if (o instanceof Distributions) { + return this.isDistribution && this.distribution == o; + } + return false; + } + + public String getName() { + if (this.isDistribution) { + if (warpDistribution) + return "Warped " + this.distribution.getName(); + return this.distribution.getName(); + } + return this.shuffle.getName(); + } + + public void shuffle(int[] array, ArrayVisualizer arrayVisualizer) { + if (this.isDistribution) { + Writes Writes = arrayVisualizer.getWrites(); + int currentLen = arrayVisualizer.getCurrentLength(); + double sleep = arrayVisualizer.shuffleEnabled() ? 1 : 0; + int[] copy = new int[currentLen]; + int[] tmp = new int[currentLen]; + Writes.arraycopy(array, 0, copy, 0, currentLen, sleep, true, true); + this.distribution.initializeArray(tmp, arrayVisualizer); + if (warpDistribution) { + for (int i = 0; i < currentLen; i++) { + Writes.write(array, i, copy[tmp[i]], sleep, true, false); + } + } else { + for (int i = 0; i < currentLen; i++) { + Writes.write(array, i, tmp[copy[i]], sleep, true, false); + } + } + } else { + Delays Delays = arrayVisualizer.getDelays(); + Highlights Highlights = arrayVisualizer.getHighlights(); + Writes Writes = arrayVisualizer.getWrites(); + this.shuffle.shuffleArray(array, arrayVisualizer, Delays, Highlights, Writes); + } + } +} diff --git a/src/utils/Shuffles.java b/src/utils/Shuffles.java index 7ed8a549..020229b0 100644 --- a/src/utils/Shuffles.java +++ b/src/utils/Shuffles.java @@ -106,18 +106,6 @@ public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays De this.sort(array, 0, currentLen, delay ? 1 : 0, Writes); } }, - REV_SORTED { - public String getName() { - return "Reverse Sorted"; - } - @Override - public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = ArrayVisualizer.getCurrentLength(); - boolean delay = ArrayVisualizer.shuffleEnabled(); - this.sort(array, 0, currentLen, delay ? 1 : 0, Writes); - Writes.reversal(array, 0, currentLen-1, delay ? 1 : 0, true, false); - } - }, NAIVE { public String getName() { return "Naive Randomly"; @@ -140,11 +128,19 @@ public String getName() { public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { int currentLen = ArrayVisualizer.getCurrentLength(); boolean delay = ArrayVisualizer.shuffleEnabled(); - int len = Math.max(1, currentLen/7); - - this.shuffle(array, 0, currentLen, delay ? 0.5 : 0, Writes); - Highlights.clearMark(2); - this.sort(array, 0, currentLen-len, delay ? 0.5 : 0, Writes); + + Random random = new Random(); + int[] aux = new int[currentLen]; + int i = 0, j = 0, k = 0; + while (i < currentLen) { + Highlights.markArray(2, i); + if (random.nextDouble() < 1/7d) + Writes.write(aux, k++, array[i++], delay ? 1 : 0, false, true); + else + Writes.write(array, j++, array[i++], delay ? 1 : 0, true, false); + } + Writes.arraycopy(aux, 0, array, j, k, delay ? 1 : 0, true, false); + shuffle(array, j, currentLen, delay ? 2 : 0, Writes); } }, SHUFFLED_HEAD { @@ -155,11 +151,19 @@ public String getName() { public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { int currentLen = ArrayVisualizer.getCurrentLength(); boolean delay = ArrayVisualizer.shuffleEnabled(); - int len = Math.max(1, currentLen/7); - this.shuffle(array, 0, currentLen, delay ? 0.5 : 0, Writes); - Highlights.clearMark(2); - this.sort(array, len, currentLen, delay ? 0.5 : 0, Writes); + Random random = new Random(); + int[] aux = new int[currentLen]; + int i = currentLen - 1, j = currentLen - 1, k = 0; + while (i >= 0) { + Highlights.markArray(2, i); + if (random.nextDouble() < 1/7d) + Writes.write(aux, k++, array[i--], delay ? 1 : 0, false, true); + else + Writes.write(array, j--, array[i--], delay ? 1 : 0, true, false); + } + Writes.reversearraycopy(aux, 0, array, 0, k, delay ? 1 : 0, true, false); + shuffle(array, 0, j, delay ? 2 : 0, Writes); } }, MOVED_ELEMENT { @@ -301,52 +305,6 @@ public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays De Writes.write(array, i, temp[i], delay ? 1 : 0, true, false); } }, - REV_FINAL_MERGE { - public String getName() { - return "Reversed Final Merge"; - } - @Override - public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = ArrayVisualizer.getCurrentLength(); - boolean delay = ArrayVisualizer.shuffleEnabled(); - int count = 2; - - int k = 0; - int[] temp = new int[currentLen]; - - for(int j = 0; j < count; j++) - for(int i = j; i < currentLen; i+=count) - Writes.write(temp, k++, array[i], 0, false, true); - - for(int i = 0; i < currentLen; i++) - Writes.write(array, i, temp[i], delay ? 1 : 0, true, false); - - Writes.reversal(array, 0, currentLen-1, delay ? 1 : 0, true, false); - } - }, - REV_SAWTOOTH { - public String getName() { - return "Reversed Sawtooth"; - } - @Override - public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { - int currentLen = ArrayVisualizer.getCurrentLength(); - boolean delay = ArrayVisualizer.shuffleEnabled(); - int count = 4; - - int k = 0; - int[] temp = new int[currentLen]; - - for(int j = 0; j < count; j++) - for(int i = j; i < currentLen; i+=count) - Writes.write(temp, k++, array[i], 0, false, true); - - for(int i = 0; i < currentLen; i++) - Writes.write(array, i, temp[i], delay ? 1 : 0, true, false); - - Writes.reversal(array, 0, currentLen-1, delay ? 1 : 0, true, false); - } - }, ORGAN { public String getName() { return "Pipe Organ"; @@ -638,30 +596,28 @@ public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays De heapSort.makeHeap(array, 0, currentLen, delay ? 1 : 0); } }, - REV_SMOOTH { + SMOOTH { public String getName() { - return "Reversed Smoothified"; + return "Smoothified"; } @Override public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { int currentLen = ArrayVisualizer.getCurrentLength(); boolean delay = ArrayVisualizer.shuffleEnabled(); - - Writes.reversal(array, 0, currentLen-1, delay ? 1 : 0, true, false); + SmoothSort smoothSort = new SmoothSort(ArrayVisualizer); smoothSort.smoothHeapify(array, currentLen); } }, - REV_POPLAR { + POPLAR { public String getName() { - return "Reversed Poplarified"; + return "Poplarified"; } @Override public void shuffleArray(int[] array, ArrayVisualizer ArrayVisualizer, Delays Delays, Highlights Highlights, Writes Writes) { int currentLen = ArrayVisualizer.getCurrentLength(); boolean delay = ArrayVisualizer.shuffleEnabled(); - - Writes.reversal(array, 0, currentLen-1, delay ? 1 : 0, true, false); + PoplarHeapSort poplarHeapSort = new PoplarHeapSort(ArrayVisualizer); poplarHeapSort.poplarHeapify(array, 0, currentLen); } diff --git a/src/utils/shuffle_utils/AWTUtils.java b/src/utils/shuffle_utils/AWTUtils.java new file mode 100644 index 00000000..d94cff2f --- /dev/null +++ b/src/utils/shuffle_utils/AWTUtils.java @@ -0,0 +1,33 @@ +package utils.shuffle_utils; + +import java.awt.BasicStroke; +import java.awt.Graphics2D; +import java.awt.Stroke; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.Rectangle2D; + +public class AWTUtils { + public static void drawCircle(Graphics2D g, int x, int y, int r) { + x = x - r; + y = y - r; + g.fillOval(x, y, r * 2, r * 2); + } + + public static void drawBorderRect(Graphics2D g, int x, int y, int width, int height, int thickness) { + Stroke oldStroke = g.getStroke(); + g.setStroke(new BasicStroke(thickness)); + g.drawRect(x, y, width, height); + g.setStroke(oldStroke); + } + + public static Rectangle2D getStringBounds(Graphics2D g, String str, float x, float y) { + FontRenderContext frc = g.getFontRenderContext(); + GlyphVector gv = g.getFont().createGlyphVector(frc, str); + return gv.getPixelBounds(null, x, y); + } + + public static Rectangle2D getStringBounds(Graphics2D g, String str) { + return getStringBounds(g, str, 0, 0); + } +} diff --git a/src/utils/shuffle_utils/GraphReader.java b/src/utils/shuffle_utils/GraphReader.java new file mode 100644 index 00000000..7ec18683 --- /dev/null +++ b/src/utils/shuffle_utils/GraphReader.java @@ -0,0 +1,261 @@ +package utils.shuffle_utils; + +import java.awt.Point; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; +import java.util.Set; +import java.util.stream.Collectors; + +import utils.Distributions; +import utils.ShuffleGraph; +import utils.ShuffleInfo; +import utils.Shuffles; + +public final class GraphReader { + public final class MalformedGraphFileException extends Exception { + public MalformedGraphFileException() { + super(); + } + + public MalformedGraphFileException(String message) { + super(message); + } + } + + private final class PartialElement { + int left, right; + + public PartialElement(int left, int right) { + this.left = left; + this.right = right; + } + } + + public final static int[] COMPATIBLE_VERSIONS = {0, 1, 2}; + static Set compatibleVersionsSet; + + Scanner scanner; + ShuffleGraph result; + List partialNodes; + int version; + + public GraphReader() { + result = null; + if (compatibleVersionsSet == null) { + compatibleVersionsSet = new HashSet<>( + Arrays.stream(COMPATIBLE_VERSIONS) + .boxed() + .collect(Collectors.toList()) + ); + } + } + + public ShuffleGraph getResult() { + return result; + } + + public ShuffleGraph read(String fileName) throws IOException, MalformedGraphFileException { + scanner = new Scanner(fileName); + try { + read(); + } finally { + scanner.close(); + } + return result; + } + + public ShuffleGraph read(File file) throws IOException, MalformedGraphFileException { + scanner = new Scanner(file); + try { + read(); + } finally { + scanner.close(); + } + return result; + } + + public ShuffleGraph read(Scanner scanner) throws IOException, MalformedGraphFileException { + this.scanner = scanner; + try { + read(); + } finally { + scanner.close(); + } + return result; + } + + private void read() throws IOException, MalformedGraphFileException { + version = scanner.hasNextInt() ? scanner.nextInt() : 0; + if (!compatibleVersionsSet.contains(version)) { + throw new MalformedGraphFileException("Unsupported version for reading: " + version + " (Supported versions: " + + Arrays.stream(COMPATIBLE_VERSIONS) + .mapToObj(String::valueOf) + .collect(Collectors.joining(", ", "{", "}")) + ")"); + } + result = new ShuffleGraph(); + partialNodes = new ArrayList<>(); + + if (version >= 2 && scanner.hasNextDouble()) { + result.sleepRatio = scanner.nextDouble(); + } + + while (scanner.hasNext()) { + String identifier = scanner.next().toUpperCase(); + switch (identifier) { + case "N": + readNode(); + break; + case "C": + readConnection(); + break; + default: + throw new MalformedGraphFileException("Invalid element type \"" + identifier + "\""); + } + } + + for (int i = 1; i < result.nodes.size(); i++) { + ShuffleNode node = result.nodes.get(i); + PartialElement partial = partialNodes.get(i - 1); + try { + node.preConnection = partial.left == -1 ? null : result.connections.get(partial.left); + node.postConnection = partial.right == -1 ? null : result.connections.get(partial.right); + } catch (IndexOutOfBoundsException e) { + String message = e.getMessage(); + int id = Integer.parseInt(message.split(" ", 3)[1]); + MalformedGraphFileException newError = new MalformedGraphFileException("No connection with the ID " + id); + newError.initCause(e); + throw newError; + } + } + + if (version >= 2) { + for (int i = 1; i < result.nodes.size(); i++) { + ShuffleNode node = result.nodes.get(i); + if (node.x == Integer.MIN_VALUE) { // coordinates not specified + if (node.preConnection == null || node.preConnection.from == null) { + Point safePos = result.findSafeCoordinates(100, 100, 20, 20); + node.x = safePos.x; + node.y = safePos.y; + } else { + ShuffleNode previous = node.preConnection.from; + node.x = previous.x + ShuffleNode.WIDTH + 15; + node.y = previous.y; + } + } + } + } + + partialNodes = null; + } + + private void readNode() throws MalformedGraphFileException { + if (!scanner.hasNextBoolean()) { + throw new MalformedGraphFileException("Expected isDistribution in node declaration"); + } + boolean isDistribution = scanner.nextBoolean(); + if (!scanner.hasNext()) { + throw new MalformedGraphFileException("Unexpected EOF during node parsing"); + } + String name = scanner.next(); + ShuffleInfo shuffleInfo; + try { + if (isDistribution) { + boolean isDistributionWarped = false; + if (version > 0) { + if (!scanner.hasNextBoolean()) { + throw new MalformedGraphFileException("Expected isDistributionWarped in node declaration"); + } + isDistributionWarped = scanner.nextBoolean(); + } + Distributions distribution = Distributions.valueOf(name); + shuffleInfo = new ShuffleInfo(distribution, isDistributionWarped); + } else { + Shuffles shuffle = Shuffles.valueOf(name); + shuffleInfo = new ShuffleInfo(shuffle); + } + } catch (IllegalArgumentException e) { + String message = e.getMessage(); + if (message.startsWith("No enum constant utils.")) { + message = message.substring("No enum constant utils.".length()); + if (message.startsWith("Shuffles.")) { + message = "No shuffle with the ID \"" + message.substring("Shuffles.".length()) + "\""; + } else if (message.startsWith("Distributions.")) { + message = "No distribution with the ID \"" + message.substring("Distributions.".length()) + "\""; + } else { + throw e; + } + MalformedGraphFileException newError = new MalformedGraphFileException(message); + newError.initCause(e); + throw newError; + } + throw e; + } + if (!scanner.hasNextInt()) { // x + throw new MalformedGraphFileException("Expected X coordinate in node declaration"); + } + int x = scanner.nextInt(); + if (!scanner.hasNextInt()) { // y + throw new MalformedGraphFileException("Expected Y coordinate in node declaration"); + } + int y = scanner.nextInt(); + int preConnectionID, postConnectionID; + if (version < 2) { + if (!scanner.hasNextInt()) { // preConnectionID + throw new MalformedGraphFileException("Expected preConnection ID in node declaration"); + } + preConnectionID = scanner.nextInt(); + if (!scanner.hasNextInt()) { // postConnectionID + throw new MalformedGraphFileException("Expected postConnection ID in node declaration"); + } + postConnectionID = scanner.nextInt(); + } else { + if (!scanner.hasNextInt()) { + preConnectionID = x; + postConnectionID = y; + x = Integer.MIN_VALUE; + } else { + preConnectionID = scanner.nextInt(); + if (!scanner.hasNextInt()) { // postConnectionID + throw new MalformedGraphFileException("Expected postConnection ID in node declaration"); + } + postConnectionID = scanner.nextInt(); + } + } + + result.nodes.add(new ShuffleNode(shuffleInfo, result, x, y)); + partialNodes.add(new PartialElement(preConnectionID, postConnectionID)); + } + + private void readConnection() throws MalformedGraphFileException { + if (!scanner.hasNextInt()) { + throw new MalformedGraphFileException("Expected fromNode ID in connection declaration"); + } + int fromNodeID = scanner.nextInt(); + if (!scanner.hasNextInt()) { + throw new MalformedGraphFileException("Expected toNode ID in connection declaration"); + } + int toNodeID = scanner.nextInt(); + + ShuffleNode fromNode = null, toNode = null; + try { + fromNode = fromNodeID == -1 ? null : result.nodes.get(fromNodeID); + toNode = toNodeID == -1 ? null : result.nodes.get(toNodeID); + } catch (IndexOutOfBoundsException e) { + String message = e.getMessage(); + int id = Integer.parseInt(message.split(" ", 3)[1]); + MalformedGraphFileException newError = new MalformedGraphFileException("No node with the ID " + id); + newError.initCause(e); + throw newError; + } + ShuffleConnection connection = new ShuffleConnection(fromNode, toNode); + result.connections.add(connection); + if (fromNodeID == 0) { + fromNode.postConnection = connection; + } + } +} diff --git a/src/utils/shuffle_utils/GraphWriter.java b/src/utils/shuffle_utils/GraphWriter.java new file mode 100644 index 00000000..b034e1ca --- /dev/null +++ b/src/utils/shuffle_utils/GraphWriter.java @@ -0,0 +1,76 @@ +package utils.shuffle_utils; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import utils.ShuffleGraph; + +public final class GraphWriter { + public static final int VERSION = 2; + + ShuffleGraph graph; + + public GraphWriter(ShuffleGraph graph) { + this.graph = graph; + } + + public void write(String fileName) throws IOException { + FileWriter writer = new FileWriter(fileName); + write(writer); + } + + public void write(File file) throws IOException { + FileWriter writer = new FileWriter(file); + write(writer); + } + + public void write(FileWriter writer) throws IOException { + Map nodeMap = new HashMap<>(); + Map connectionMap = new HashMap<>(); + for (int i = 0; i < graph.nodes.size(); i++) { + ShuffleNode node = graph.nodes.get(i); + nodeMap.put(node, i); + } + nodeMap.put(null, -1); + for (int i = 0; i < graph.connections.size(); i++) { + ShuffleConnection conn = graph.connections.get(i); + connectionMap.put(conn, i); + } + connectionMap.put(null, -1); + + // Metadata + writer.write(VERSION + " "); + writer.write(graph.sleepRatio + "\n"); + + // Nodes + for (int i = 1; i < graph.nodes.size(); i++) { + ShuffleNode node = graph.nodes.get(i); + writer.write("N "); + if (node.getValue().isDistribution()) { + writer.write("true "); + writer.write(node.getValue().getDistribution().name() + " "); + writer.write(node.getValue().isDistributionWarped() + " "); + } else { + writer.write("false "); + writer.write(node.getValue().getShuffle().name() + " "); + } + writer.write(node.x + " "); + writer.write(node.y + " "); + writer.write(connectionMap.get(node.preConnection) + " "); + writer.write(connectionMap.get(node.postConnection) + "\n"); + } + + // Connections + for (int i = 0; i < graph.connections.size(); i++) { + ShuffleConnection conn = graph.connections.get(i); + writer.write("C "); + writer.write(nodeMap.get(conn.from) + " "); + writer.write(nodeMap.get(conn.to) + "\n"); + } + + writer.close(); + } +} diff --git a/src/utils/shuffle_utils/ShuffleConnection.java b/src/utils/shuffle_utils/ShuffleConnection.java new file mode 100644 index 00000000..f8c6d15b --- /dev/null +++ b/src/utils/shuffle_utils/ShuffleConnection.java @@ -0,0 +1,76 @@ +package utils.shuffle_utils; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Stroke; +import java.awt.Point; +import java.util.List; + +public class ShuffleConnection { + public ShuffleNode from, to; + public Point currentDragPos; + + public ShuffleConnection(ShuffleNode from, ShuffleNode to) { + this.from = from; + this.to = to; + this.currentDragPos = new Point(); + } + + public ShuffleConnection(ShuffleNode from, Point to) { + this.from = from; + this.to = null; + this.currentDragPos = to; + } + + public void draw(Graphics2D g) { + Point fromPos = this.from.getPos(); + fromPos = new Point(fromPos.x + ShuffleNode.WIDTH + 10, fromPos.y + ShuffleNode.HEIGHT / 2); + Point endPos; + if (this.to == null) { + endPos = new Point(this.currentDragPos.x - 10, this.currentDragPos.y); + } else { + Point toPos = this.to.getPos(); + endPos = new Point(toPos.x, toPos.y + ShuffleNode.HEIGHT / 2); + } + Point midStart = new Point(fromPos.x + 15, fromPos.y); + Point midEnd = new Point(endPos.x - 15, endPos.y); + Stroke oldStroke = g.getStroke(); + g.setStroke(new BasicStroke(5)); + g.setColor(Color.BLACK); + g.drawLine(fromPos.x, fromPos.y, midStart.x, midStart.y); + g.drawLine(midStart.x, midStart.y, midEnd.x, midEnd.y); + g.drawLine(midEnd.x, midEnd.y, endPos.x, endPos.y); + g.setStroke(oldStroke); + if (this.to == null) { + AWTUtils.drawCircle(g, endPos.x + 10, endPos.y, 10); + } + } + + public void remove() { + if (this.from != null) { + this.from.postConnection = null; + } + if (this.to != null) { + this.to.preConnection = null; + } + } + + public void finishDragging(ShuffleNode other) { + this.to = other; + other.preConnection = this; + int removed = 0; + List connections = other.graph.connections; + for (int i = 0; i < connections.size(); i++) { + ShuffleConnection conn = connections.get(i - removed); + if (conn == this) { + continue; + } + if (conn.to == other) { + connections.remove(i - removed); + conn.remove(); + removed++; + } + } + } +} diff --git a/src/utils/shuffle_utils/ShuffleNode.java b/src/utils/shuffle_utils/ShuffleNode.java new file mode 100644 index 00000000..3eb1c75c --- /dev/null +++ b/src/utils/shuffle_utils/ShuffleNode.java @@ -0,0 +1,125 @@ +package utils.shuffle_utils; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.geom.Rectangle2D; + +import utils.ShuffleGraph; +import utils.ShuffleInfo; + +public class ShuffleNode { + public static final int WIDTH = 250; + public static final int HEIGHT = 50; + + public ShuffleInfo shuffle; + public int x, y; + public ShuffleGraph graph; + public ShuffleConnection preConnection, postConnection; + + public ShuffleNode(ShuffleInfo shuffle, ShuffleGraph graph, int x, int y) { + this.shuffle = shuffle; + this.graph = graph; + this.x = x; + this.y = y; + this.preConnection = null; + this.postConnection = null; + } + + public ShuffleNode(ShuffleInfo shuffle, ShuffleGraph graph) { + this(shuffle, graph, 0, 0); + } + + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o instanceof ShuffleNode) { + ShuffleNode other = (ShuffleNode)o; + return this.x == other.x + && this.y == other.y + && this.graph == other.graph + && this.shuffle.equals(other.shuffle); + } else if (o instanceof ShuffleInfo) { + return this.shuffle.equals(o); + } else { + return false; + } + } + + protected String getShuffleName() { + if (this.shuffle == null) { + return ""; + } + return this.shuffle.getName(); + } + + public void draw(Graphics2D g) { + Color borderColor = this.graph.selected == this ? new Color(128, 128, 255) : new Color(0, 0, 0); + Color leftColor = this.graph.dragCandidate == this ? new Color(128, 128, 255) : borderColor; + g.setColor(leftColor); + AWTUtils.drawCircle(g, x, y + HEIGHT / 2, 10); + g.setColor(borderColor); + AWTUtils.drawCircle(g, x + WIDTH, y + HEIGHT / 2, 10); + g.setColor(Color.WHITE); + g.fillRect(x, y, WIDTH, HEIGHT); + g.setColor(borderColor); + AWTUtils.drawBorderRect(g, x, y, WIDTH, HEIGHT, 2); + g.setColor(Color.BLACK); + String text = getShuffleName(); + graph.calcTextSize(text, WIDTH, g); + Rectangle2D rect = AWTUtils.getStringBounds(g, text); + g.drawString( + text, + (int)(x + WIDTH / 2 - rect.getWidth() / 2), + (int)(y + HEIGHT / 2 + rect.getHeight() / 2) + ); + } + + public void drag(Point rel) { + this.x += rel.getX(); + if (this.x < -WIDTH / 2) { + this.x = -WIDTH / 2; + } + this.y += rel.getY(); + } + + public boolean inArea(Point pos) { + return (new Rectangle2D.Double(x - 25, y, WIDTH + 15, HEIGHT)).contains(pos); + } + + public boolean inStartDrag(Point pos) { + return (new Rectangle2D.Double(x + WIDTH - 15, y + HEIGHT / 2 - 10, 30, 20)).contains(pos); + } + + public void delete() { + if (this == this.graph.selected) { + this.graph.selected = null; + } + this.graph.nodes.remove(this); + if (this.preConnection != null) { + if (this.postConnection == null) { + this.graph.connections.remove(this.preConnection); + this.preConnection.remove(); + } else { + this.preConnection.to = this.postConnection.to; + } + } + if (this.postConnection != null) { + if (this.preConnection == null) { + this.graph.connections.remove(this.postConnection); + this.postConnection.remove(); + } else { + this.postConnection.from = this.preConnection.from; + } + } + } + + public Point getPos() { + return new Point(this.x, this.y); + } + + public ShuffleInfo getValue() { + return this.shuffle; + } +}