【问题标题】:Sudoku game serialization issue数独游戏连载问题
【发布时间】:2013-07-27 05:35:23
【问题描述】:

我正在创建数独游戏,并尝试提供保存、另存为和打开游戏的选项。我正在使用 JFileChooser 来执行此操作。我可以保存(或“另存为”),但是当我尝试打开保存的文件时,出现错误。我是编程新手,我希望有人能发现问题并教育我在保存时如何阅读数独板的内容(以及打开时如何处理重新创建数独板文件)。我听说有一种更简单的方法可以使用 InputStream/OutputStream 而不是 Reader/Writer...

这是我实现这个的内部类的代码(我不知道是否有办法在不超过此文本框字符限制的情况下发布我的整个类。):

编辑:

    // this inner class provides a JMenuBar object at the top of
  // the board
  class MenuAtTop extends JMenuBar implements ActionListener{

    // SudokuMain object we are dealing with
    private SudokuMain main;

    // the "File" menu
    private JMenu fileMenu;
    // the "New Game" option
    private JMenuItem newGame;
    // the "Open" option
    private JMenuItem open;
    // the "Save" option
    private JMenuItem save;
    // the "Save As" option
    private JMenuItem saveAs;
    // the "Reset" option
    private JMenuItem reset;
    // the "Quit" option
    private JMenuItem quit;

    // the ability to choose files
    private JFileChooser choose;

    // the saved file
//    // compiler would not allow "static" keyword
    private File fileSaved = null;

    private Object opener;

    // JDialog object to create a dialog box to prompt
    // user for new game information
    private JDialog createNewWin; 

    /**
     * Constructs MenuAtTop object.
     * 
     * @param m The SudokuMain object to be referred to.
     */
    public MenuAtTop(final SudokuMain m) {

      main = m;

      opener = null;
      choose = new JFileChooser();

      // instantiate and bind to reference
      fileMenu = new JMenu("File");
      add(fileMenu);

      // instantiate and bind to reference
      newGame = new JMenuItem("New Game");
      newGame.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N,
                                                    ActionEvent.CTRL_MASK));
      fileMenu.add(newGame);
      newGame.addActionListener(this);

      open = new JMenuItem("Open");
      open.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
                                                 ActionEvent.CTRL_MASK));
      fileMenu.add(open);
      // add action listener to "Open" option
      open.addActionListener(this);

      save = new JMenuItem("Save");
      save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
                                                 ActionEvent.CTRL_MASK));
      fileMenu.add(save);
      // add action listener to "Save" option
      save.addActionListener(this);

      saveAs = new JMenuItem("Save As");
      saveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A,
                                                   ActionEvent.CTRL_MASK));
      fileMenu.add(saveAs);
      // add action listener to "Save As" option
      saveAs.addActionListener(this);

      reset = new JMenuItem("Reset");
      reset.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R,
                                                  ActionEvent.CTRL_MASK));
      fileMenu.add(reset);
      // add action listener to "Reset" option
      reset.addActionListener(this);

      quit = new JMenuItem("Quit");
      quit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q,
                                                 ActionEvent.CTRL_MASK));
      fileMenu.add(quit);
      // add action listener to "Quit" option
      quit.addActionListener(this);

    }

    public void actionPerformed(ActionEvent e) {

      if(e.getSource().equals(newGame)) {

        setEnabled(false);
        // create dialog box prompting for the new board information
        createNewWin = new Dialog1(main, "Create New Board", true);
        // make it visible
        createNewWin.setVisible(true);

        fileSaved = null;

      } else if(e.getSource().equals(open)) {

        int returnVal = choose.showOpenDialog(main.win);
        if(returnVal == JFileChooser.APPROVE_OPTION) {
          boolean error = false;
          File openFile = choose.getSelectedFile();

          try {
            FileInputStream fis = new FileInputStream(openFile);
            ObjectInputStream ois = new ObjectInputStream(fis);
            opener = ois.readObject();
            SudokuBase sudoku = (SudokuBoard) opener;
            ois.close();
          } catch (Exception exc) {
            exc.printStackTrace();
            JOptionPane.showMessageDialog(main.win, "Error opening file.");
            error = true;
          }

          // "opener" reads something and it is of type SudokuBase
          if(opener != null && opener instanceof SudokuBase){

            main.north.remove(main.rowColRegStates);
            main.west.remove(main.symbols);
            main.east.remove(main.view);

            main.view =  new SudokuView((SudokuBase) opener);
            main.rowColRegStates = new ShowStates(main.view);
            main.symbols = new SetSymbols(main.view);

            main.north.add(main.rowColRegStates);
            main.west.add(main.symbols);
            main.east.add(main.view);

            main.win.requestFocus();

            fileSaved = openFile;
          } else {
            if(error) {
              JOptionPane.showMessageDialog(main.win, "Incorrect file type.");
            }
          }
        }

      } else if(e.getSource().equals(save)) {

        if(fileSaved == null) {
          saveAsPrompt();
        } else {
          try {
            FileOutputStream fos = new FileOutputStream(fileSaved);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            board.writeToStream(fos);
            oos.writeObject(board);
            oos.close();
          } catch (Exception exc) {
            JOptionPane.showMessageDialog(main.win, "Error saving file.");
          }
        }

      } else if(e.getSource().equals(saveAs)) {
        saveAsPrompt();
      } else if(e.getSource().equals(reset)) {

        int n = JOptionPane.showConfirmDialog(main.win, 
                                              "Any player values will" +
                                              " be lost. Proceed?",
                                              "Warning!", 2);
        if(n == JOptionPane.OK_OPTION) {
          main.board.reset();
          main.view.repaint();
        }

      } else if(e.getSource().equals(quit)) {
        closePrompt();
      }

    }

    // This method prompts the user to choose a file to save to,
    // and then saves the file.
    private int saveAsPrompt() {
      boolean saveError;
      int rtn = choose.showSaveDialog(main.win);

      if(rtn == JFileChooser.APPROVE_OPTION) {
        saveError = false;
        File fileSaveAs = choose.getSelectedFile();
        try {
          board.writeToStream(new FileOutputStream(fileSaveAs));
        } catch (Exception e) {
          JOptionPane.showMessageDialog(main.win, "Error saving file.");
          saveError = true;
        }

        if(!saveError) {
          fileSaved = fileSaveAs;
        }
      }

      return rtn;

    }

    // This method prompts the user whether they want to save before
    // closing, only if changes occurred.
    private void closePrompt() {
      if(true) {
        int n = JOptionPane.showConfirmDialog(main.win, "Save game?");
        if(n == JOptionPane.YES_OPTION) {
          int saved = saveAsPrompt();
          if(saved != JFileChooser.CANCEL_OPTION){
            main.win.dispose();
          }
        } else if(n == JOptionPane.NO_OPTION) {
          main.win.dispose();
        }
      }
      else { // no changes were made
        main.win.dispose();
      }
    }

  }

这是保存数据的类(它由 SudokuBoard 扩展):

// Allow short name access to following classes
import java.util.Observable;
import java.io.InputStream;
import java.io.OutputStream;

public abstract class SudokuBase extends Observable {
  // rows per region
  private int rows;
  // columns per region
  private int columns;
  // size of a region (rows * columns)
  private int size;
  // array of each element of entire sudoku board
  private int[] grid;

  // the masked 8-bit "given" value constant
  private static final int GIVEN_MASK = 0x00000100;
  // the bitwise complement of the masked "given" constant,
  // which produces an unmasked constant
  private static final int GIVEN_UNMASK = ~ GIVEN_MASK;

  /** 
   * Enumerated type to store constants that indicate the "State" of
   * a specified row, column, or region.
   */
  public enum State {COMPLETE, INCOMPLETE, ERROR};

  /**
   * Constructs SudokuBase object.
   * 
   * @param layoutRows The number of rows per region.
   * @param layoutColumns The number of columns per region.
   */
  public SudokuBase(int layoutRows, int layoutColumns) {
    rows = layoutRows;
    columns = layoutColumns;
    size = columns * rows;
    grid = new int[size*size];
  }

  /**
   * Gets the number of rows per region.
   * 
   * @return The rows per region.
   */
  public int getRowsPerRegion() {
    return rows;
  }

  /**
   * Gets the number of columns per region.
   * 
   * @return The columns per region.
   */
  public int getColumnsPerRegion() {
    return columns;
  }

  /**
   * Gets the size of the region (rows * columns).
   * 
   * @return The size of the region.
   */
  public int getBoardSize() {
    return size;
  }

  // gets the index of the specified row and column for the grid
  private int getIndex(int row, int col) {
    // handle invalid arguments
    if(row < 0 || row >= size || col < 0 || col >= size) {
      String msg = "Error in location";
      throw new IllegalArgumentException(msg);
    }
    return row * size + col;
  }

  /**
   * Gets the value of the element at the specified row
   * and column on the grid.
   * 
   * @param row The specified row.
   * @param col The specified column.
   * @return The value of the element at the specified row and column.
   */
  public int getValue(int row, int col) {
    return grid[getIndex(row, col)] & GIVEN_UNMASK;
  }

  /**
   * Sets the desired value at the specified row and column.
   * 
   * @param row The specified row.
   * @param col The specified column.
   * @param value The specified value to be set.
   */
  public void setValue(int row, int col, int value) {
    // handle invalid argument
    if(value < 0 || value > size) {
      String msg = "Value out of range: " + value;
      throw new IllegalArgumentException(msg);
    }
    // handle attempt to set a value for a "given" location
    if(isGiven(row, col)) {
      String msg = "Cannot set given location: " + row + ", " + col;
      throw new IllegalStateException(msg);
    }
    grid[getIndex(row, col)] = value;
    setChanged();
    notifyObservers();
  }

  /**
   * This method checks the status of the "givens" bit.
   * 
   * @param row The specified row.
   * @param col The specified column.
   * @return Whether or not the specified location is a "given" value.
   */
  public boolean isGiven(int row, int col) {
    return (grid[getIndex(row, col)] & GIVEN_MASK) == GIVEN_MASK;
  }

  /**
   * This method sets non-zero values on the Sudoku board with the
   * "givens" bit.
   */
  public void fixGivens() {
    for(int i = 0; i < grid.length; i++)
      if(grid[i] != 0)
      grid[i] |= GIVEN_MASK;
    setChanged();
    notifyObservers();
  }

  /**
   * This abstract method gets the "State" (COMPLETE, INCOMPLETE,
   * or ERROR) of the specified row.
   *
   * @param n The specified row.
   * @return The "State" of the row.
   */
  public abstract State getRowState(int n);

  /**
   * This abstract method gets the "State" (COMPLETE, INCOMPLETE,
   * or ERROR) of the specified column.
   *
   * @param n The specified column.
   * @return The "State" of the column.
   */
  public abstract State getColumnState(int n);

  /**
   * This abstract method gets the "State" (COMPLETE, INCOMPLETE,
   * or ERROR) of the specified region.
   *
   * @param n The specified region.
   * @return The "State" of the region.
   */
  public abstract State getRegionState(int n);

  /**
   * Represents the Sudoku board as a grid of appropriate characters.
   * 
   * @return The string representation of the Sudoku board.
   */
  public String toString() {
    String board = "";
    for(int i = 0; i < size; i ++) {
      for(int j = 0; j < size; j ++)
        board += charFor(i, j) + " ";
      board += "\n";
    }
    return board;
  }

  // this method provides a character for all possible values encountered on the
  // Sudoku board, to be utilized in "toString()"
  private String charFor(int i, int j) {
    int v = getValue(i, j);
    // negative value (invalid)
    if(v < 0) {
      return "?";
    } else if(v == 0) {  // blank or zero value
      return ".";
    } else if(v < 36) {  // value from 1 to (size * size)
      return Character.toString(Character.forDigit(v, 36)).toUpperCase();
    } else {  // non-numeric input or v >= size * size (both invalid)
      return "?";
    }
  }

  /**
   * This method reads from an input stream.
   * 
   * @param is The input stream to read from.
   */
  protected void readFromStream(InputStream is) {
  }

  /**
   * This method writes to an output stream.
   * 
   * @param os The output stream to write to.
   */
    protected void writeToStream(OutputStream os) {
    try {
     ObjectOutputStream oos = new ObjectOutputStream(os);
     oos.close();
    } catch(IOException e) {
    }

  }

  /**
   * Gets the "raw" value directly, not having checked whether there is an
   * unfixed error message.
   * 
   * @param row The row where the raw value is located.
   * @param col The column where the raw value is located.
   * @return The raw value.
   */
  protected int getRawValue(int row, int col) {
    return grid[getIndex(row, col)];
  }

  /**
   * Sets the raw value directly, not having checked whether there is an
   * unfixed error message.
   * 
   * @param row The row where the raw value is to be located.
   * @param col The column where the raw value is to be located.
   */
  protected void setRawValue(int row, int col, int value) {
    grid[getIndex(row, col)] = value;
  }

  protected void reset() {
    for(int row = 0; row < rows; row++) {
      for(int col = 0; col < columns; col++) {
        if(!isGiven(row, col)) {
          grid[getIndex(row, col)] = 0;
        }
      }
    }

  }

}

【问题讨论】:

    标签: java serialization sudoku


    【解决方案1】:

    好吧,我无法给出完整的答案,也不想浏览完整的源代码。但是有一些提示可以帮助您找到一些解决方案:

    在开发应用程序时永远不要捕获这样的异常:

    } catch (Exception e) {
          JOptionPane.showMessageDialog(main.win, "Error saving file.");
          saveError = true;
    }
    

    这样,您就完全失去了检测错误的机会。至少在您的异常处理中添加以下行:

    e.printStackTrace();
    

    通常您会记录异常等,但是您会在控制台上看到错误的来源。总比没有好。

    对于您更具体的问题: 您似乎将一个对象写入包含所有配置的文件。在您的读取方法中出现问题。可能您不会读取与写入相同的对象或类似的对象。没有任何代码很难说。尝试获取异常堆栈跟踪并找出问题所在。如果您无法弄清楚,请使用更具体的信息编辑您的问题,我会尝试提供更好的指导。

    编辑: 下面是一个小例子,展示了类似数独游戏的对象序列化:

    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    
    public class SerializationExample {
    
      public static void main(String args[]) throws IOException, ClassNotFoundException {
        final File target = new File(System.getProperty("java.io.tmp"), "mySerializedObject.txt");
    
        Map<Integer, Integer> initialState = new HashMap<Integer, Integer>();
        initialState.put(1, 1);
        initialState.put(21, 3);
        // ...
    
        GameState state = new GameState(10, initialState);
        state.setField(2, 2);
        state.setField(3, 8);
        System.out.println("Game state before writing to file: " + state);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(target));
        out.writeObject(state);
        out.close();
    
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(target));
        Object gameStateReadFromFile = in.readObject();
        GameState readGameState = (GameState)gameStateReadFromFile;
        System.out.println("Read from file: " + readGameState);
      }
    
      private static class GameState implements Serializable {
        private int[] fields;
    
        private int boardSize;
    
        private int[] fixedFields;
    
        public GameState(int boardSize, Map<Integer, Integer> initialState) {
          this.boardSize = boardSize;
          this.fields = new int[boardSize * boardSize];
          this.fixedFields = new int[this.fields.length];
    
          for (Entry<Integer, Integer> entry : initialState.entrySet()) {
            this.fixedFields[entry.getKey()] = entry.getValue();
          }
        }
    
        public void setField(int index, int value) {
          this.fields[index] = value;
        }
    
        @Override
        public String toString() {
          StringBuilder builder = new StringBuilder();
          builder.append("\nFixed fields: ");
          appendArray(builder, this.fixedFields);
          builder.append("\nSet fields: ");
          appendArray(builder, this.fields);
          return builder.toString();
        }
    
        private void appendArray(StringBuilder builder, int[] fieldArray) {
          for (int i = 0; i < fieldArray.length; ++i) {
            if (fieldArray[i] != 0) {
              builder.append("row ").append(i / this.boardSize).append(" column ").append(i % this.boardSize)
                  .append(" has value ")
                  .append(fieldArray[i]).append(",");
            }
          }
        }
      }
    }
    

    【讨论】:

    • 所以对于数独游戏,我需要保存 int 值。由于一些值是给定的(在游戏前设置),一些是可玩的值(可以更改),我计划通过值网格并确定哪些是“给定”值(我有一个方法可以做到这一点) 并存储它们。然后我会再次遍历网格并存储可播放的值。
    • 您可以通过多种方式做到这一点。但是,您的源代码表明,您正在存储一个对象并希望稍后读取一个对象。按照这个策略,最好有一个对象来代表你的游戏状态(也许是带有网格的数独板加上给出的值的信息)。如果这个对象(和所有包含的对象)实现了 Serializable 接口,你应该是黄金。
    • 所以对于数独游戏,我有需要保存和恢复的 int 值。由于一些值是给定的(在游戏前设置),一些是可玩的值(可以更改),我计划通过值网格并确定哪些是“给定”值(我有一个方法可以做到这一点) 并存储每个的行、列和值信息。然后我会再次遍历网格并存储可播放值的行、列和值信息。当我打开一个文件时,我想恢复保存的电路板。我想我正试图弄清楚如何去做?
    • 我想我之前按回车键有点早。您认为您可以创建一个类似于我正在尝试做的简单示例吗?我正在尝试了解如何处理对象(SudokuBoard)以及如何提取必要的信息。
    • 在我的回答中添加了序列化示例。我想你可以把它适应你的应用程序
    猜你喜欢
    • 2013-07-25
    • 2012-01-31
    • 1970-01-01
    • 2021-01-31
    • 1970-01-01
    • 2021-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多