# 一直想寫的數獨程式

這個是在我期末那周拚出來的數獨遊戲
其實很久以前就很想寫樹讀了
但是一來沒有什麼壓力
二來C++也不是合寫這種
三來我Visual C++不熟悉,寫不出這種東西
所以一直拖到了現在才寫出數獨遊戲!!
另外 裡面還有一些附屬檔案
我就放到下面的這個連結給大家抓吧

http://dl.dropbox.com/u/12113131/program/sudoku.rar (opens new window)

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.util.*;
public class sudoku extends JFrame // implements ActionListener implements KeyListener
{
  private JPanel gameBoard,settingBoard,tempPanel[];
  private JButton btn[],loadbtn,designbtn;
  private JTextField filename;
  private String columnSpace,rowSpace; 
  private Font btnFont;                                  // 字形
  private int SIZE = 600, win;
  private FileAction fileAction;
  private GameAction gameAction;
  private Color warnningColor;
  private int checkSet[][];
  public static void main(String[]args)
  {
    sudoku new_game = new sudoku();
    new_game.setVisible(true);
  }
  public sudoku()
  {
    super("數獨遊戲");      // 視窗標題
    btnFont = new Font(null,Font.BOLD,30);                                    // 設定字形大小gameBoard = new JPanel();
    setSize(SIZE + 17, SIZE + 57);                                            // 設定視窗大小
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);                           // 設定右上方的 X 可關閉程式
    setLayout(new BorderLayout());                                            // 設定版面 (BorderLayout 是有五個方向的排版方式)
    setResizable(false);                                                      // 設定不可變更視窗大小
    columnSpace=" ";
    rowSpace="   ";
    tempPanel = new JPanel[2];
    add(new JLabel(rowSpace),BorderLayout.WEST);
    add(new JLabel(columnSpace),BorderLayout.NORTH);
    
    settingBoard = new JPanel();
    settingBoard.setLayout(new FlowLayout());
    settingBoard.add(new JLabel("檔案名稱:"));
    filename = new JTextField("save.txt",20);
    settingBoard.add(filename);
    fileAction = new FileAction();
    loadbtn = new JButton("Load");
    loadbtn.addActionListener(fileAction);
    settingBoard.add(loadbtn);
    designbtn = new JButton("Design");
    designbtn.addActionListener(fileAction);
    settingBoard.add(designbtn);
    
    add(settingBoard,BorderLayout.SOUTH);
    
    gameBoard = new JPanel();
    gameAction = new GameAction();
    gameBoard.setLayout(new GridLayout(3,3));                                 // 設定排版為九個格子 (GridLayout 是很像畫格子的排版方式)
    btn = new JButton[81];
    for(int i=0;i<9;i++)
    {
      tempPanel[0] = new JPanel();
      tempPanel[0].setLayout(new BorderLayout());
      tempPanel[0].add(new JLabel(rowSpace),BorderLayout.EAST);
      tempPanel[0].add(new JLabel(columnSpace),BorderLayout.SOUTH);
      tempPanel[1] = new JPanel();
      tempPanel[1].setLayout(new GridLayout(3,3));
      for(int j=0;j<9;j++)
      {
        int index = i/3*27 + i%3*3 + j/3*9 + j%3;
        btn[index] = new JButton("");          // 設定按鈕文字
        btn[index].addActionListener(gameAction);    // 設定按鈕的動作
        btn[index].setActionCommand(""+index);     // 設定按鈕的指令 (將按鈕的編號轉成 String 設定給 ActionCommand)
        btn[index].setFont(btnFont);           // 設定按鈕的字形
        tempPanel[1].add(btn[index]);             // 將按鈕加進 gameBoard
      }
      tempPanel[0].add(tempPanel[1],BorderLayout.CENTER);
      gameBoard.add(tempPanel[0]);
    }
    
    warnningColor = Color.RED;
    win = 0;
    checkSet = new int[27][9];
    for(int i=0;i<9;i++)
      for(int j=0;j<9;j++)
      {
        checkSet[i][j]= i *9 + j; // 水平
        checkSet[9+i][j]= j *9 + i; // 垂直
        checkSet[18+i][j]=(i/3*3+j/3) *9 + (i%3*3+j%3); // 九宮格
      }
      add(gameBoard, BorderLayout.CENTER);   // 將 gameBoard 加到視窗的中央
  }
  private int check()
  {
    //if(win == 1)return win;
    win = 1;
    clear_btn_background();
    for(int i=0;i<checkSet.length;i++)
      if(check_paint(checkSet[i]))win = -1;
    for(int i=0;i<81 && win==1;i++)
      if(btn[i].getText().equals(""))win = 0;
    return win;
  }
  private boolean check_paint(int checkArr[])
  {
    boolean paint = false;
    int data[] = new int[10];
    for(int i=0;i<data.length;i++)
      data[i]=0;
    for(int i=0;i<checkArr.length;i++)
    {
      if( btn[ checkArr[i] ].getText().equals(""))continue;
      int temp = Integer.parseInt( btn[ checkArr[i] ].getText() );
      if(data[temp]!=0)
      {
        if(data[temp]<0)data[temp]=-data[temp];
        btn[data[temp]-1].setBackground(warnningColor);
        paint = true;
        data[temp]=-checkArr[i]-1;
      }
      else data[temp]=checkArr[i]+1;
    }
    for(int i=0;i<data.length;i++)
      if(data[i]<0)
      {
        btn[-data[i]-1].setBackground(warnningColor);
        paint = true;
      }
    return paint;
  }
  private void clear_btn()
  {
    for(int i=0;i<81;i++)
    {
      btn[i].setText("");
      btn[i].setActionCommand(""+i);
    }
    designbtn.setText("Design");
    designbtn.setActionCommand("Design");
    win = 0;
    clear_btn_background();
  }
  private void clear_btn_background()
  {
    for(int i=0;i<81;i++)
      btn[i].setBackground(null);
  }
  private int next_val(int temp,int val)
  {
    boolean num[] = {false,false,false,false,false,false,false,false,false,false};
    int ia;
    for(int i=0;i<9;i++)
    {
      ia=temp/9*9+i;
      if(btn[ia].getActionCommand().length()>2)num[getVal(ia)]=true;
      ia=i*9+temp%9;
      if(btn[ia].getActionCommand().length()>2)num[getVal(ia)]=true;
      ia= temp/27*27 + temp%9/3*3 + i/3*9 + i%3 ;
      if(btn[ia].getActionCommand().length()>2)num[getVal(ia)]=true;
    }
    for(int i=val+1;i<10;i++)  
      if(!num[i])return i;
    return 0;
  }
  private int getVal(int temp)
  {
    if(!(btn[temp].getText().equals("")))           
      return Integer.parseInt(btn[temp].getText());   // 若該按鈕有文字則將該string用int 傳回
    return 0;                                         // 否則傳回0
  }
  private void setVal(int temp,int val)
  {
    if(val!=0)btn[temp].setText(""+val);
    else btn[temp].setText("");
  }
  private class GameAction implements ActionListener
  {
    public void actionPerformed(ActionEvent e)
    {
      int index = Integer.parseInt(e.getActionCommand());
      if(index>99)return;
      index %= 100;
      int val = getVal(index);
      if( designbtn.getText().equals("Design") ) // 開始設計了,將按鈕變成 Save
      {
        designbtn.setText("Save");
        designbtn.setActionCommand("Save");
      }
      if( designbtn.getText().equals("Save")) // 設計功能下,沒有辦法按下 New
        val=(val+1)%10;
      else if( designbtn.getText().equals("New"))
        val=next_val(index,val);  //  可替換成搜尋函式
      setVal(index,val);
      check();
      if(win==1)
      {
        JOptionPane.showMessageDialog(null,"恭喜你解開這個數獨啦!!\n你可以按 New 進行設計新的題目\n或按 Load 來讀取"+filename.getText());
      }
    }
  }
  private class FileAction implements ActionListener
  {
    public void actionPerformed(ActionEvent e)
    {
      if(e.getActionCommand().equals("Load"))
      {
        Scanner fin = null;
        try
        {
          fin = new Scanner(new FileInputStream( filename.getText() ));
        }
        catch(Exception error)
        {
          JOptionPane.showMessageDialog(null,"無法讀取 "+filename.getText()+"\n請確認該檔案是否存在。");
          clear_btn();
          return ;
        }
        if( !(designbtn.getText().equals("Design")) &&
          win != 1 &&
          JOptionPane.showConfirmDialog(null,
          "目前的遊戲進度將會被覆蓋,是否繼續?",
          "檔案讀取",
          JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) return;
        for(int i=0;i<81 && fin.hasNextInt();i++)
        {
          int val = fin.nextInt();
          if(val!=0)
          {
            btn[i].setText(""+val);
            btn[i].setActionCommand(""+(i+100));
          }
          else
          {
            btn[i].setText("");
            btn[i].setActionCommand(""+i);
          }
        }
        fin.close();
        win = 0;
        check();
        if(win==-1)JOptionPane.showMessageDialog(null,"讀入的題目不符數獨規則,\n請手動在該檔案內更改完成後重新讀入。");
        else if(win==1)JOptionPane.showMessageDialog(null,filename.getText()+" 內的題目已經獲勝。");
        designbtn.setText("New");
        designbtn.setActionCommand("New");
      }
      else if(e.getActionCommand().equals("Save"))
      {
        if(win==-1)JOptionPane.showMessageDialog(null,"目前設計的題目不符數獨規則,\n請在紅色色塊處更改完成後再按 Save。");
        else if(win==1)JOptionPane.showMessageDialog(null,"目前設計的題目已經獲勝,無法儲存為題目。");
        if(win!=0)return;
        PrintWriter fout = null;
        try
        {
          fout = new PrintWriter(new FileOutputStream( filename.getText() ));
        }
        catch(Exception error)
        {
          filename.setText("Can Read The File.");
          return;
        }
        if( JOptionPane.showConfirmDialog(null,
          filename.getText()+" 將會被覆蓋,是否繼續?",
          "盤面設計儲存",
          JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) return;
        for(int i=0;i<81;i++)
        {
          int val=getVal(i);
          fout.printf("%4d",val);
          if(i%9==8)fout.println();
          else fout.print(" ");
        }
        fout.close();
        clear_btn();
      }
      else if(e.getActionCommand().equals("New"))
      {
        if( win != 1 &&
          JOptionPane.showConfirmDialog(null,
          "遊戲盤面將會被清空,是否繼續?",
          "開新遊戲",
          JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) return;
        clear_btn();
      }
      else if(e.getActionCommand().equals("Design"))
      {
        designbtn.setText("Design");
        designbtn.setActionCommand("Design");
        JOptionPane.showMessageDialog(null,"現在可以進行數獨的題目設計,設計完後請按 Save。\n若想開始遊戲請輸入正確的檔名之後按下 Load。");
      }
    }
  }
}