/****************************/ /* 8 パズルの解法 */ /* Coded by Y.Suganuma */ /****************************/ import java.io.*; import java.awt.*; import javax.swing.*; import java.awt.event.*; public class Test { public static void main (String[] args) { S_Eight win = new S_Eight("8 パズルの解法( Swing )"); } } class S_Eight extends JFrame implements ItemListener { int MAX = 1000; // 最大試行回数 int g[][]; // 目標状態 int kodomo[][][]; // [i][0][j] : 適用した規則番号 // [i][1][j] : 適用後の状態 int n_o_pattern; // 展開された状態の数 int oya[]; // 親の状態番号 int pattern[][][]; // 現在の状態 int s_method = 1; // 探索方法 // =1 : 横型 // =2 : 縦型 // =3 : 深さ+目標状態と異なるコマの数 // =4 : h(n) = p(n) + 3s(n) // p(n) : 目標状態からの距離 // s(n) : =0 : 次の駒が正しい順序のとき(中心の駒を除く) // =2 : 次の駒が誤った順序のとき(中心の駒を除く) // =1 : 中心の駒 int tenkai[]; // =0 : 未展開(葉)の状態 // =1 : 展開済みの状態 double hukasa[][]; // [i][0] : 状態のの深さ // [i][1] : 評価関数の値 double max_depth = 10; // 最大探索深さ JComboBoxchoice; JTextField trial, depth; S_Eight dt = this; /******************/ /* コンストラクタ */ /******************/ S_Eight(String name) { // JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す) super(name); // Windowの大きさ setSize(300, 200); // レイアウトの変更 Container cp = getContentPane(); cp.setBackground(Color.white); JPanel jp = new JPanel(); jp.setLayout(new GridLayout(4, 2, 5, 10)); jp.setBackground(Color.white); cp.add(jp); Font f = new Font("TimesRoman", Font.BOLD, 20); // スクロールリスト & テキストフィールド JLabel fill1 = new JLabel("最大試行回数"); fill1.setFont(f); jp.add(fill1); trial = new JTextField("1000", 10); trial.setBackground(Color.white); trial.setFont(f); jp.add(trial); JLabel fill2 = new JLabel("探索方法"); fill2.setFont(f); jp.add(fill2); choice = new JComboBox (); choice.setBackground(Color.white); choice.setFont(f); choice.addItem("横型探索"); choice.addItem("縦型探索"); choice.addItem("評価関数1"); choice.addItem("評価関数2"); jp.add(choice); choice.addItemListener(this); JLabel fill3 = new JLabel("最大探索深さ"); fill3.setFont(f); jp.add(fill3); depth = new JTextField("10", 10); depth.setBackground(Color.white); depth.setFont(f); jp.add(depth); // OK ボタンの設定 JLabel fill4 = new JLabel(" スタート?"); fill4.setFont(f); jp.add(fill4); JButton bt = new JButton("OK"); bt.setFont(f); bt.addMouseListener(new ClickMouse()); jp.add(bt); // ウィンドウを表示 setVisible(true); // イベントアダプタ addWindowListener(new WinEnd()); } /******************************/ /* 上,左,下,右の余白の設定 */ /******************************/ public Insets getInsets() { return new Insets(50, 20, 20, 20); } /******************************/ /* チョイスコントロールの処理 */ /******************************/ public void itemStateChanged(ItemEvent e) { if (e.getItemSelectable() == choice) { s_method = choice.getSelectedIndex() + 1; } } /********************************/ /* OKボタンが押されたときの処理 */ /********************************/ class ClickMouse extends MouseAdapter { /************************************/ /* マウスがクリックされたときの処理 */ /************************************/ public void mouseClicked(MouseEvent e) { int i1; // 領域の確保 MAX = Integer.parseInt(trial.getText()); max_depth = Integer.parseInt(depth.getText()); g = new int [3][3]; kodomo = new int [MAX][2][4]; oya = new int [MAX]; pattern = new int [MAX][3][3]; tenkai = new int [MAX]; hukasa = new double [MAX][2]; // 初期設定 n_o_pattern = 0; hukasa[0][0] = 0.0; tenkai[0] = 0; oya[0] = 0; for (i1 = 0; i1 < 4; i1++) kodomo[0][0][i1] = 0; // ゴールの設定 g[0][0] = 1; g[0][1] = 2; g[0][2] = 3; g[1][0] = 8; g[1][1] = 0; g[1][2] = 4; g[2][0] = 7; g[2][1] = 6; g[2][2] = 5; // 初期状態の設定 S_Initial initial = new S_Initial(dt); } } /************/ /* 終了処理 */ /************/ class WinEnd extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } } /****************************/ /* クラスS_Initialの定義 */ /* (初期状態の入力) */ /****************************/ class S_Initial extends JFrame { int err = 0; // エラーの有無 private int n; // 行・列の数 private S_Eight dt; private S_Initial in = this; JTextField a[][]; JLabel f[]; Error_p ep; /*******************************************/ /* コンストラクタ */ /* dt_i : S_Eightクラスのオブジェクト */ /*******************************************/ S_Initial(S_Eight dt_i) { // Frameクラスのコンストラクタの呼び出し super("初期状態"); // レイアウトの変更 n = 3; dt = dt_i; a = new JTextField [n][n]; f = new JLabel [n-1]; Container cp = getContentPane(); cp.setBackground(Color.white); cp.setLayout(null); JPanel jp = new JPanel(); jp.setBackground(Color.white); cp.add(jp); jp.setLayout(new BorderLayout(5, 10)); JPanel pn = new JPanel(); pn.setBackground(Color.white); pn.setLayout(new GridLayout(n, n+1, 5, 10)); jp.add(pn, BorderLayout.NORTH); Font fn = new Font("TimesRoman", Font.BOLD, 20); // テキストフィールドの設定 int i1, i2; for (i1 = 0; i1 < n; i1++) { for (i2 = 0; i2 < n; i2++) { a[i1][i2] = new JTextField(2); a[i1][i2].setFont(fn); pn.add(a[i1][i2]); } if (i1 < n-1) { f[i1] = new JLabel(); pn.add(f[i1]); } } // OK ボタンの設定 JButton bt = new JButton("OK"); bt.setFont(fn); bt.addMouseListener(new ClickMouse()); pn.add(bt); // メッセージパネルの追加 ep = new Error_p(this); ep.setBackground(Color.white); jp.add(ep, BorderLayout.CENTER); // Windowサイズを設定 setSize(350, 250); // ウィンドウを表示 setVisible(true); Insets in = getInsets(); jp.setLocation(10, 10); jp.setSize(350-in.left-in.right-20, 250-in.top-in.bottom-20); // イベントアダプタ addWindowListener(new WinEnd()); } /********************************/ /* OKボタンが押されたときの処理 */ /********************************/ class ClickMouse extends MouseAdapter { /************************************/ /* マウスがクリックされたときの処理 */ /************************************/ public void mouseClicked(MouseEvent e) { int i1, i2, i3, i4, k, k1; err = 0; /* 入力データの処理 */ for (i1 = 0; i1 < n && err == 0; i1++) { for (i2 = 0; i2 < n && err == 0; i2++) { if ((a[i1][i2].getText()).length() <= 0) dt.pattern[0][i1][i2] = 0; else dt.pattern[0][i1][i2] = Integer.parseInt(a[i1][i2].getText()); if (dt.pattern[0][i1][i2] < 0 || dt.pattern[0][i1][i2] > 8) err = 1; else { for (i3 = 0; i3 <= i1 && err == 0; i3++) { k1 = (i3 == i1) ? i2 : n; for (i4 = 0; i4 < k1 && err == 0; i4++) { if (dt.pattern[0][i1][i2] == dt.pattern[0][i3][i4]) err = 1; } } } } } ep.repaint(); // 解を見つける if (err == 0) { in.setVisible(false); Eight eight = new Eight(dt); } } } /************/ /* 終了処理 */ /************/ class WinEnd extends WindowAdapter { public void windowClosing(WindowEvent e) { in.setVisible(false); } } } /**************************/ /* エラーメッセージの表示 */ /**************************/ class Error_p extends JPanel { S_Initial in; Error_p(S_Initial in1) { in = in1; } public void paintComponent (Graphics g) { super.paintComponent(g); // 親クラスの描画(必ず必要) // フォントの設定 Font f = new Font("TimesRoman", Font.BOLD, 20); g.setFont(f); // エラーメッセージの出力 int x = 10; int y = 25; if (in.err == 0) g.drawString(" ", x, y); else { g.setColor(Color.red); g.drawString("*** Error ***", x, y); g.setColor(Color.black); } } } /************************/ /* クラスEightの定義 */ /* (問題を解く) */ /************************/ class Eight extends JFrame { private S_Eight dt; private Eight et = this; private JTextArea text; /*******************************************/ /* コンストラクタ */ /* dt_i : S_Eightクラスのオブジェクト */ /*******************************************/ Eight(S_Eight dt_i) { // JFrameクラスのコンストラクタの呼び出し super("8パズル"); // テキストエリアの設定 dt = dt_i; Container cp = getContentPane(); cp.setBackground(Color.white); JPanel jp = new JPanel(); cp.setLayout(null); jp.setBackground(Color.white); cp.add(jp); Font f = new Font("TimesRoman", Font.BOLD, 20); text = new JTextArea("", 15, 25); text.setFont(f); JScrollPane sp = new JScrollPane(text); jp.add(sp); // Windowサイズを設定 setSize(470, 450); // ウィンドウを表示 setVisible(true); Insets in = getInsets(); jp.setLocation(10, 10); jp.setSize(470-in.left-in.right-20, 450-in.top-in.bottom-20); // イベントアダプタ addWindowListener(new WinEnd()); // 実行 solve(); } /********************/ /* 8-パズルを解く */ /********************/ public void solve() { int i1, i2, i3, pt = 0, rl = 0, sw = 0; int ptn[][] = new int [3][3]; int ptn1[][] = new int [3][3]; int rl1[] = new int [1]; double pf, pf1; /* 問題解決 */ while (sw == 0) { // 規則の探索 pf = 99999.0; for (i1 = 0; i1 <= dt.n_o_pattern; i1++) { if (dt.tenkai[i1] == 0) { pf1 = fire(i1, rl1, ptn1); // 適用規則と評価関数の計算 if (pf1 > 10000.0) dt.tenkai[i1] = 1; else { if (pf1 < pf) { // 最小評価関数規則の選択 */ pf = pf1; // 評価関数値 pt = i1; // 展開パターン rl = rl1[0]; // 適用規則 for (i2 = 0; i2 < 3; i2++) { // 規則適用後のパターン for (i3 = 0; i3 < 3; i3++) ptn[i2][i3] = ptn1[i2][i3]; } } } } } // 適用可能規則なし if (pf > 10000.0) { sw = 1; text.insert("解決できませんでした!\n", 0); } // 規則の適用 else { dt.n_o_pattern += 1; // 制限回数超過 if (dt.n_o_pattern >= dt.MAX) { sw = 1; text.insert("解決できませんでした!\n", 0); } // 制限回数以内 else { dt.oya[dt.n_o_pattern] = pt; dt.hukasa[dt.n_o_pattern][0] = dt.hukasa[pt][0] + 1.0; dt.hukasa[dt.n_o_pattern][1] = pf; dt.tenkai[dt.n_o_pattern] = 0; for (i1 = 0; i1 < 4; i1++) dt.kodomo[dt.n_o_pattern][0][i1] = 0; for (i1 = 0; i1 < 3; i1++) { for (i2 = 0; i2 < 3; i2++) dt.pattern[dt.n_o_pattern][i1][i2] = ptn[i1][i2]; } sw = 1; for (i1 = 0; i1 < 4 && sw > 0; i1++) { if (dt.kodomo[pt][0][i1] == 0) { sw = 0; dt.kodomo[pt][0][i1] = rl; dt.kodomo[pt][1][i1] = dt.n_o_pattern; } } // 目標達成のチェック sw = 1; for (i1 = 0; i1 < 3 && sw > 0; i1++) { for (i2 = 0; i2 < 3 && sw > 0; i2++) { if (ptn[i1][i2] != dt.g[i1][i2]) sw = 0; } } // 目標達成 if (sw > 0) { pt = dt.n_o_pattern; text.insert("成功!!\n", 0); while (pt > 0) { p_pattern(pt); pt = dt.oya[pt]; } p_pattern(0); } } } } } /*****************************************/ /* 適用可能な規則を選ぶ */ /* pt : パターン番号 */ /* rl : 適用規則番号 */ /* ptn[i][j] : 規則適用後のパターン */ /* return : 評価関数の値 */ /* 有効な規則の集合 */ /* 13 : IF [0][0]=0 THEN RIGHT */ /* 14 : IF [0][0]=0 THEN DOWN */ /* 21 : IF [0][1]=0 THEN LEFT */ /* 23 : IF [0][1]=0 THEN RIGHT */ /* 24 : IF [0][1]=0 THEN DOWN */ /* 31 : IF [0][2]=0 THEN LEFT */ /* 34 : IF [0][2]=0 THEN DOWN */ /* 42 : IF [1][0]=0 THEN UP */ /* 43 : IF [1][0]=0 THEN RIGHT */ /* 44 : IF [1][0]=0 THEN DOWN */ /* 51 : IF [1][1]=0 THEN LEFT */ /* 52 : IF [1][1]=0 THEN UP */ /* 53 : IF [1][1]=0 THEN RIGHT */ /* 54 : IF [1][1]=0 THEN DOWN */ /* 61 : IF [1][2]=0 THEN LEFT */ /* 62 : IF [1][2]=0 THEN UP */ /* 64 : IF [1][2]=0 THEN DOWN */ /* 72 : IF [2][0]=0 THEN UP */ /* 73 : IF [2][0]=0 THEN RIGHT */ /* 81 : IF [2][1]=0 THEN LEFT */ /* 82 : IF [2][1]=0 THEN UP */ /* 83 : IF [2][1]=0 THEN RIGHT */ /* 91 : IF [2][2]=0 THEN LEFT */ /* 92 : IF [2][2]=0 THEN UP */ /* 最初の数字:空白の位置 */ /* 次の数字 :空白を移動する方向 */ /*****************************************/ public double fire(int pt, int [] rl,int [][] ptn) { int i1, i2, i3, i4, k1, sw; int ptn1[][] = new int [3][3]; double pf; double pf1[] = new double [1]; pf = 99999.0; for (i1 = 1; i1 <= 9; i1++) { for (i2 = 1; i2 <= 4; i2++) { k1 = 10 * i1 + i2; sw = rule(pt, pf1, k1, ptn1); if (sw > 0) { if (pf1[0] < pf) { pf = pf1[0]; rl[0] = k1; for (i3 = 0; i3 < 3; i3++) { for (i4 = 0; i4 < 3; i4++) ptn[i3][i4] = ptn1[i3][i4]; } } } } } return pf; } /**********************************************************/ /* 規則が適用か否かを調べ、かつ、適用後の評価関数値を計算 */ /* pt : パターン番号 */ /* pf : 評価関数の値 */ /* rl : 適用規則番号 */ /* ptn[i][j] : 規則適用後のパターン */ /* return : =0 : 適用不可能 */ /* =1 : 適用可能 */ /**********************************************************/ public int rule(int pt, double [] pf, int rl, int [][] ptn) { int i1, i2, i3, k1, k2, k3, sw; double pi[] = new double [1]; sw = 1; /* 既に適用した規則か否かのチェック */ for (i1 = 0; i1 < 4 && sw > 0; i1++) { if (rl == dt.kodomo[pt][0][i1]) sw = 0; } /* 物理的適用可能性のチェック */ if (sw > 0) { k1 = rl / 10; k2 = rl % 10; sw = -1; for (i1 = 0; i1 < 3 && sw < 0; i1++) { for (i2 = 0; i2 < 3 && sw < 0; i2++) { if (dt.pattern[pt][i1][i2] == 0) { sw = 1; k3 = 3 * i1 + i2 + 1; if (k1 != k3) // 位置が不適当 sw = 0; else { // 移動方向のチェック if (i1 == 0 && k2 == 2) sw = 0; if (i1 == 2 && k2 == 4) sw = 0; if (i2 == 0 && k2 == 1) sw = 0; if (i2 == 2 && k2 == 3) sw = 0; } } } } /* 規則の適用 */ if (sw > 0) { apply(pt,rl,ptn); /* 既に展開したパターンに同じものがあるか否かのチェック */ for (i1 = 0; i1 <= dt.n_o_pattern && sw > 0; i1++) { sw = 0; for (i2 = 0; i2 < 3 && sw == 0; i2++) { for (i3 = 0; i3 < 3 && sw == 0; i3++) { if (ptn[i2][i3] != dt.pattern[i1][i2][i3]) sw = 1; } } if (sw == 0 && dt.s_method == 2) { if (dt.hukasa[i1][0] > dt.hukasa[pt][0]+1.0) sw = 1; } } /* 評価関数の計算 */ if (sw > 0) { sw = cal_pf(pt, pi, ptn); pf[0] = pi[0]; } } } return sw; } /*****************************************/ /* 与えられた規則を適用する */ /* pt : パターン番号 */ /* rl : 適用規則番号 */ /* ptn : 規則適用後のパターン */ /*****************************************/ public void apply(int pt, int rl, int [][] ptn) { int i1, i2, j1, j2, k1 = 0, k2 = 0, l1, l2; /* 適用前のパターン */ for (i1 = 0; i1 < 3; i1++) { for (i2 = 0; i2 < 3; i2++) ptn[i1][i2] = dt.pattern[pt][i1][i2]; } /* 規則適用後のパターン */ l1 = rl / 10; l2 = rl % 10; // 適用前の空白の位置 j1 = (l1 - 1) / 3; j2 = (l1 - 1) % 3; // 適用後の空白の位置 switch (l2) { case 1 : k1 = j1; k2 = j2 - 1; break; case 2 : k1 = j1 - 1; k2 = j2; break; case 3 : k1 = j1; k2 = j2 + 1; break; case 4 : k1 = j1 + 1; k2 = j2; break; } /* 適用後のパターン */ ptn[j1][j2] = ptn[k1][k2]; ptn[k1][k2] = 0; } /**************************************************************/ /* 評価関数の値を計算する */ /* pt : パターン番号 */ /* pf : 評価関数の値 */ /* ptn[i][j] : 規則適用後のパターン(縦型、横型では未使用)*/ /* return : =0 : 適用不可能(縦型探索で深さ制限を越えた時 */ /* =1 : 適用可能(その他) */ /**************************************************************/ public int cal_pf(int pt, double [] pf,int [][] ptn) { int i1, i2, i3, i4, j1, j2, kyori, pos, seq, ssw, sw; sw = 1; switch (dt.s_method) { /* 横型探索 */ case 1 : pf[0] = dt.hukasa[pt][0]; break; /* 縦型探索 */ case 2 : if (dt.hukasa[pt][0] < dt.max_depth) pf[0] = -dt.hukasa[pt][0]; else sw = 0; break; /* 深さ+目標状態と異なる位置の数 */ case 3 : pos = 0; for (i1 = 0; i1 < 3; i1++) { for (i2 = 0; i2 < 3; i2++) { if (ptn[i1][i2] != 0) { if (ptn[i1][i2] != dt.g[i1][i2]) pos += 1; } } } pf[0] = dt.hukasa[pt][0] + (double)pos; break; /* h(n) = p(n) + 3s(n) p(n) : 目標状態からの距離 s(n) : =0 : 次の駒が正しい順序の時(中心の駒を除く) =2 : 次の駒が誤った順序の時(中心の駒を除く) =1 : 中心の駒 */ case 4 : kyori = 0; for (i1 = 0; i1 < 3; i1++) { for (i2 = 0; i2 < 3; i2++) { if (ptn[i1][i2] != 0) { // 距離の計算 ssw = 0; for (i3 = 0; i3 < 3 && ssw == 0; i3++) { for (i4 = 0; i4 < 3 && ssw == 0; i4++) { if (ptn[i1][i2] == dt.g[i3][i4]) { kyori += (Math.abs(i3-i1) + Math.abs(i4-i2)); ssw = 1; } } } // 次の駒の位置のチェック seq = 2; if (i1 == 1 && i2 == 1) // 中心の駒 seq = 1; else { // 中心以外の駒 if (i1 == 0) { if (i2 == 2) { j1 = 1; j2 = 2; } else { j1 = i1; j2 = i2 + 1; } } else { if (i1 == 1) { if (i2 == 0) { j1 = 0; j2 = 0; } else { j1 = 2; j2 = 2; } } else { if (i2 == 0) { j1 = 1; j2 = 0; } else { j1 = i1; j2 = i2 - 1; } } } if (ptn[i1][i2] == 8) { if (ptn[j1][j2] == 1) seq = 0; } else { if (ptn[j1][j2] == ptn[i1][i2]+1) seq = 0; } } kyori += (3 * seq); } } } pf[0] = dt.hukasa[pt][0] + (double)kyori; break; /* その他 */ default : pf[0] = dt.hukasa[pt][0]; break; } return sw; } /****************************/ /* 与えられたパターンの出力 */ /* pt : パターン番号 */ /****************************/ public void p_pattern(int pt) { int i1, i2; String str; for (i1 = 2; i1 >= 0; i1--) { str = " "; for (i2 = 0; i2 < 3; i2++) str += Integer.toString(dt.pattern[pt][i1][i2]); str += "\n"; text.insert(str, 0); } str = "状態番号 "; str += Integer.toString(pt); str += " 深さ= "; str += Integer.toString((int)dt.hukasa[pt][0]); str += " 評価= "; str += Integer.toString((int)dt.hukasa[pt][1]); text.insert(str + "\n", 0); } /************/ /* 終了処理 */ /************/ class WinEnd extends WindowAdapter { public void windowClosing(WindowEvent e) { et.setVisible(false); } } }