情報学部 菅沼ホーム Java 目次 索引

ナンプレ

  1. ステップ1: ゲーム枠の作成

      ナンプレ( Number Place )は,下の図に示すように,3 × 3 のマス目からなるブロックが,3 × 3 の状態に並べられており,空白部分に数字を埋めていくゲームです.通常,下の図の左に示すように,初期状態ではいくつかの数字が既に設定されており(これが,問題になります),残りの空白に数字を埋めていくことになります(下の図の右が解答結果になります).数字を埋めるルールは非常に簡単です,「各ブロック,各行( 9 マスからなる),及び,各列( 9 マスからなる)に 1 ~ 9 までの数字が 1 回だけ出現しなければならない」ということだけです.
      以下に示すプログラムは,問題を自分で入力し(コンピュータによって,問題を設定することも可能),その問題を自分で解くように作成されています.プログラムを作成する順序としてステップ1からステップ5までに分けて記述されていますが,各ステップで作成したプログラムはそのまま実行可能です.上位のステップになるほど,以下に示すように,問題を解くためのコンピュータによる支援が付加されていきます.

    • ルール違反のチェック(ステップ2)
    • 使用した数字の数を表示(ステップ3)
    • 指定された数字を設定することが可能な場所の表示(ステップ4)
    • 現在の状態を記憶し,後ほど,その状態に再び戻す(ステップ5)

      基本的に,「ゲーム枠の作成」で説明した方法とほぼ同じ方法で作成します.ただし,パネルのサイズは変更しています.また,ゲームクリア及びゲームオーバーの画面は存在しません.以下,各クラスに対して,「ゲーム枠の作成」の場合との違いについて説明していきます.

    1. Game クラス

        「ゲーム枠の作成」における Game クラスと全く同じプログラムです( Game.java ).

      /*************************/
      /* ナンプレ              */
      /*   coded by Y.Suganuma */
      /*************************/
      import java.awt.*;
      import java.awt.event.*;
      import javax.swing.*;
      import main.*;
      
      public class Game {
      	public static void main (String[] args)
      	{
      		Win win = new Win("ブロック崩し");
      	}
      }
      
      class Win extends JFrame
      {
      	/******************/
      	/* コンストラクタ */
      	/******************/
      	Win(String name)
      	{
      					// JFrameクラスのコンストラクタ(Windowのタイトルを引き渡す)
      		super(name);
      					// Windowの大きさ
      		setSize(560, 690);   // 40+20, 70+20
      					// MainPanel の大きさを決定
      		Dimension size = getSize();
      		size.width  -=60;
      		size.height -=90;
      					// ContentPain を取得し,設定
      		Container CP = getContentPane();   // ContentPane を取得
      		CP.setLayout(null);   // レイアウトマネージャを停止
      		CP.setBackground(new Color(220, 255, 220));   // 背景色
      					// MainPanel を追加し,設定
      		MainPanel pn = new MainPanel(size);   // MainPanel オブジェクトの生成
      		CP.add(pn);   // MainPanel オブジェクトを ContentPane に追加
      		pn.setSize(size.width, size.height);
      		pn.setLocation(10, 10);
      					// ウィンドウを表示
      		setVisible(true);
      					// イベントリスナ
      		addWindowListener(new WinEnd());
      	}
      
      	/******************************/
      	/* 上,左,下,右の余白の設定 */
      	/******************************/
      	public Insets getInsets()
      	{
      		return new Insets(50, 20, 20, 20);
      	}
      
      	/************/
      	/* 終了処理 */
      	/************/
      	class WinEnd extends WindowAdapter
      	{
      		public void windowClosing(WindowEvent e) {
      			System.exit(0);
      		}
      	}
      }
      				

    2. MainPanel クラス

        「ゲーム枠の作成」における MainPanel クラスとほぼ同じですが,ゲームクリア及びゲームオーバーの状態やゲームレベルを無くしている部分が異なっています.また,問題の状態を表す 3 次元配列 pr を定義しています( MainPanel.java ).3 × 3 の小さな正方形のテキストフィールドから構成されている塊を,今後ボードと呼びます.16,17 行目に説明してありますように,pr[i][j][k] の i は,ボード番号を指しています.一番左上のボードが 0,一番右下のボードが 8 になります.pr[1][2][0] = 5 とは,2 番目のボード(一番上の中央のボード)の 3 行 1 列にあるテキストフィールドに 5 を設定することを意味します.

        一般に,多次元配列は,配列の各要素を配列として定義することによって可能です.例えば,2 行 3 列の配列 x は,
      int x[][] = new int [2][3];				
      または,
      int x[][] = new int [2][];
      x[0] = new int [3];   // 以下,繰り返し文を使用可能
      x[1] = new int [3];				
      のような形で定義可能です.初期設定を行いたい場合は,
      int x[][] = {{1, 2, 3}, {4, 5, 6}};   // 内側の括弧が各行に相当				
      または,
      int x1[] = {1, 2, 3};
      int x2[] = {4, 5, 6};
      int x[][] = {x1, x2};				
      のような方法で行います.

      01	package main;
      02	
      03	import java.awt.*;
      04	import java.awt.event.*;
      05	import javax.swing.*;
      06	import start.*;
      07	import game.*;
      08	
      09	public class MainPanel extends JPanel implements Runnable
      10	{
      11		Dimension size;   // パネルの大きさ
      12		boolean in_game = true;   // ゲーム実行中はtrue
      13		public int state = 0;   // ゲーム状態(0:表紙,1:ゲーム,2:終了)
      14		int old_state = 0;   // 直前のゲーム状態
      15		public int pr[][][] = new int [9][3][3];   // 問題
      16				// pr[i][j][k] : [i] : ブロック番号.左から右,上から下へ番号付け(i=0~9)
      17				//             : [j][k] : 各ブロックのj行k列(j,k=0~2)
      18		StartPanel sp;
      19		GamePanel gp;
      20		Thread td;
      21				//
      22				// コンストラクタ
      23				//
      24		public MainPanel(Dimension size1)
      25		{
      26			size = size1;
      27						// グリッドレイアウト
      28			setLayout(new GridLayout(1, 1, 0, 0));
      29						// ゲームパネルの生成
      30			sp = new StartPanel(size, this);   // スタート(タイトル)
      31			add(sp);
      32						// スレッドの生成
      33			td = new Thread(this);
      34			td.start();
      35		}
      36				//
      37				// ゲームの状態を変更
      38				//
      39		public void run()
      40		{
      41			while (in_game) {
      42				try {
      43					td.sleep(100);   // 100 ms 毎の実施
      44				}
      45				catch (InterruptedException e) {}
      46				if (state != old_state) {
      47								// 前のパネルの削除
      48					if (old_state == 0)
      49						remove(sp);
      50					else
      51						remove(gp);
      52								// 新しいパネルの追加
      53					if (state == 2)   // ゲーム終了
      54						in_game = false;
      55					else {
      56						if (state == 0) {   // StartPanel
      57							sp = new StartPanel(size, this);
      58							add(sp);
      59						}
      60						else {   // GamePanel
      61							gp = new GamePanel(size, this);
      62							add(gp);
      63						}
      64						validate();
      65						old_state = state;
      66					}
      67				}
      68			}
      69		}
      70	}
      				

    3. StartPanel クラス

        「ゲーム枠の作成」における StartPanel クラスとは,大きく異なっています.ナンプレの盤面を表示し,問題を入力できるようにしています.そのため,StartPanel クラス( StartPanel.java )に加え,盤面の各ブロックに対応する Bord クラス( Bord.java )を作成しています.以下,StartPanel クラスから順に説明していきます.

      001	package start;
      002	
      003	import java.awt.*;
      004	import java.awt.event.*;
      005	import javax.swing.*;
      006	import main.*;
      007	
      008	public class StartPanel extends JPanel implements ActionListener
      009	{
      010		Bord bd[] = new Bord [9];
      011		Dimension size;   // パネルの大きさ
      012		MainPanel mp;
      013		JButton bt1, bt2, bt3;
      014				//
      015				// コンストラクタ
      016				//
      017		public StartPanel(Dimension size1, MainPanel mp1)
      018		{
      019			size = size1;
      020			mp   = mp1;
      021						// レイアウトマネージャの停止
      022			setLayout(null);
      023						// 背景色の設定
      024			setBackground(new Color(220, 255, 220));   // 背景色
      025						// パネルの配置
      026			JPanel jp = new JPanel();
      027			jp.setSize(size.width, size.width);
      028			jp.setLocation(0, 0);
      029			jp.setBackground(Color.green);
      030			add(jp);
      031								// グリッドレイアウト
      032			jp.setLayout(new GridLayout(3, 3, 5, 5));
      033								// ボードの配置
      034			for (int i1 = 0; i1 < 9; i1++) {
      035				bd[i1] = new Bord();
      036				jp.add(bd[i1]);
      037			}
      038						// ボタンの配置
      039			Font f = new Font("SansSerif", Font.BOLD, 20);
      040			bt1 = new JButton("遊び方");
      041			bt1.setFont(f);
      042			bt1.setSize(120, 40);
      043			bt1.setLocation(140, 530);
      044			bt1.addActionListener(this);
      045			add(bt1);
      046	
      047			bt2 = new JButton("実行");
      048			bt2.setFont(f);
      049			bt2.setSize(80, 40);
      050			bt2.setLocation(290, 530);
      051			bt2.addActionListener(this);
      052			add(bt2);
      053	
      054			bt3 = new JButton("問題");
      055			bt3.setFont(f);
      056			bt3.setSize(80, 40);
      057			bt3.setLocation(400, 530);
      058			bt3.addActionListener(this);
      059			add(bt3);
      060		}
      061				//
      062				// ボタンがクリックされたときの処理
      063				//
      064		public void actionPerformed(ActionEvent e)
      065		{
      066						// 遊び方
      067			if (e.getSource() == bt1) {
      068				Method db = new Method();
      069				db.setVisible(true);
      070				requestFocusInWindow();
      071			}
      072						// 実行
      073			else if (e.getSource() == bt2) {
      074				for (int i1 = 0; i1 < 9; i1++) {
      075					for (int i2 = 0; i2 < 3; i2++) {
      076						for (int i3 = 0; i3 < 3; i3++) {
      077							String str = bd[i1].tx[i2][i3].getText();
      078							if (str.length() > 0) {
      079								int k;
      080								try {
      081									k = Integer.parseInt(str);
      082									if (k <= 0 || k >= 10)
      083										k = -1;
      084								}
      085								catch (NumberFormatException ne)
      086								{
      087									k = -1;
      088								}
      089								mp.pr[i1][i2][i3] = k;
      090							}
      091							else
      092								mp.pr[i1][i2][i3] = 0;
      093						}
      094					}
      095				}
      096				mp.state = 1;
      097			}
      098						// 問題
      099			else {
      100				Problem win = new Problem("問題作成", bd);
      101			}
      102		}
      103	}
      104	
      105	/******************/
      106	/* ゲームの遊び方 */
      107	/******************/
      108	class Method extends JDialog
      109	{
      110				// コンストラクタ
      111		Method()
      112		{
      113			setTitle("ゲームの遊び方");
      114					// ContetPain
      115			Container cp = getContentPane();
      116			cp.setLayout(new FlowLayout(FlowLayout.CENTER));
      117			cp.setBackground(new Color(220, 255, 220));   // 背景色
      118			Font f = new Font("MS 明朝", Font.PLAIN, 20);
      119			setSize(850, 370);
      120					// TextArea の追加
      121			JTextArea ta = new JTextArea(15, 80);
      122			ta.setFont(f);
      123			ta.setEditable(false);
      124			ta.setLineWrap(true);
      125			ta.setText("1.スタート画面\n");
      126			ta.append("\n");
      127			ta.append(" a.味方1: 配置したい位置をマウスでクリックした後,1 のキーを押せば( 3 のキーを押せばキャンセル)配置され,左方向に動き出す.敵と接触することによって敵を消滅させることができる.配置できる上限数のデフォルトは 10 である.\n");
      128			ta.append("\n");
      129			ta.append(" b.味方2: 配置したい位置をマウスでクリックした後,2 のキーを押せば( 3 のキーを押せばキャンセル)配置される.その位置に停止したままであるが,その近傍を通過した敵を消滅させることができる.配置できる上限数のデフォルトは 4 である.\n");
      130			ta.append("\n");
      131			ta.append("2.ゲーム画面\n");
      132			ta.append("\n");
      133			ta.append(" a.最初から決まっている数字に対しては,背景色が薄い緑になり,修正することができません.それ以外の空いている箇所に数字を入力し,「return」キーをクリックすることによって数字を設定していきます.( step1 )\n");
      134			ta.append("\n");
      135			ta.append(" b.数字を入力する際,ルール違反が起こっている場合は,数字色が赤になります.( step2 )\n");
      136			ta.append("\n");
      137			ta.append(" c.1 ~ 9 までの番号が付けられているボタン(「数字」ボタンと呼ぶ)の下にあるテキストフィールドには,各数字に対する出現回数が表示されます.この値が 9 になるとテキストフィールドの背景色が赤になります.上に示すゲーム版の数字色がすべて黒で,かつ,すべてのテキストフィールドの背景色が赤になった時点でゲーム終了となります.( step3 )\n");
      138			ta.append("\n");
      139			ta.append(" d.「数字」ボタンをクリックすると,ゲーム版の対応する数字を入力可能な場所の背景色がピンクになります.( step4 )\n");
      140			ta.append("\n");
      141			ta.append(" e.「記憶」ボタンをクリックするとことによって,現在の状態を記憶することが出来ます.また,「戻る」ボタンをクリックすることによって,最後に記憶した状態まで順番に戻すことが出来ます.なお,「記憶」ボタンは,「return」キーによって入力する数字を確定した後にクリックして下さい.( step5 )\n");
      142			JScrollPane scroll = new JScrollPane(ta);
      143			cp.add(scroll);
      144					// Window を閉じるため
      145			addWindowListener(new WinEnd());
      146		}
      147					// 終了処理
      148		class WinEnd extends WindowAdapter
      149		{
      150			public void windowClosing(WindowEvent e) {
      151				setVisible(false);
      152			}
      153		}
      154	}
      				
      010 行目

        9 つの Bord クラスのオブジェクト(各ブロックに対応)を保存するための配列を定義しています.

      026 行目~ 037 行目

        背景色が緑のパネルを貼り付け,そのパネルを 3 × 3 のグリッドレイアウトに設定し,各位置に Bord クラスのオブジェクトを貼り付けています.bd[0] が一番上の左側のボード,bd[1] が一番上の中央のボード,・・・,bd[8] が一番下の右側のボードになります.032 行目におけるグリッドレイアウトの設定の際,要素間の隙間を 5 ピクセルに設定してあるため,各ブロックは,幅 5 ピクセルの緑色の線で分割されることになります.

      039 行目~ 059 行目

        「遊び方」ボタン,「実行」ボタン,「問題」ボタンを貼り付けています.

      073 行目~ 097 行目

        「実行」ボタンがクリックされた時の処理です.074 行目~ 095 行目において,各ブロックのテキストフィールドに入力された値を,JTextField クラスのメソッド getText によって取得し,Integer クラスのスタティックメソッド parseInt (実数に変換したい場合は,Double クラスparseDouble を使用)によって整数に変換し,3 次元配列 pr の対応する位置に代入しています.数字以外の文字や 1 ~ 9 以外が入力された場合は,-1 を設定しています(実行画面では何も表示されません).また,096 行目の記述により,ゲームの実行に移ります.

        この例に示すように,スタティックメソッドスタティック変数は,「オブジェクト.メソッド名」「オブジェクト.変数名」ではなく,「クラス名.メソッド名」「クラス名.変数名」という形で参照することに注意してください.

      099 行目~ 101 行目

        「問題」ボタンがクリックされた時の処理です.問題を作成するための Window が開きます.問題作成に関しては,後ほど説明します.

        以下に示すのが,Bord クラスのソースプログラムです.3 行 3 列の 2 次元テキストフィールドを用いて,数字を入力する場所を確保しています.背景色を薄い緑色( RGB : 0xDCFFDC )に設定し,かつ,グリッドレイアウトにおける要素間の隙間を 2 ピクセルに設定しているため,テキストフィールドは,幅 2 ピクセルの薄い緑色の直線で分割されます.

      package start;
      
      import java.awt.*;
      import java.awt.event.*;
      import javax.swing.*;
      
      public class Bord extends JPanel
      {
      	JTextField tx[][] = new JTextField [3][3];
      			// コンストラクタ
      	public Bord()
      	{
      					// 背景色の設定
      		setBackground(new Color(220, 255, 220));
      					// グリッドレイアウト
      		setLayout(new GridLayout(3, 3, 2, 2));
      					// テキストフィールドの配置
      		Font f = new Font("SansSerif", Font.BOLD, 50);
      		for (int i1 = 0; i1 < 3; i1++) {
      			for (int i2 = 0; i2 < 3; i2++) {
      				tx[i1][i2] = new JTextField();
      				tx[i1][i2].setBackground(Color.white);
      				tx[i1][i2].setFont(f);
      				tx[i1][i2].setHorizontalAlignment(JTextField.CENTER);
      				add(tx[i1][i2]);
      			}
      		}
      	}
      }
      				

        「問題」ボタンをクリックすると,Problem クラスによって,問題を作成するためのページが開きます.ただし,以下に説明するように,この問題作成プログラムは完璧なものではありません.解の形を決定した後,適当に空白を作り,問題としています.そのため,解は必ず存在しますが,唯一解であるか否かのチェックは行っていません.解の形は,基本的に,以下に示すような方法で求めています.

      • 最初の 1 を,9 × 9 個のマスのいずれかにランダムに配置する.
      • 2 番目の 1 を,配置可能な位置からランダムに選択して配置する.
      • 3 番目の 1 を,配置可能な位置からランダムに選択して配置する.
      •   ・・・・・
      • 最初の 2 を,配置可能な位置からランダムに選択して配置する.
      • 2 番目の 2 を,配置可能な位置からランダムに選択して配置する.
      •   ・・・・・

        上の処理を続けると,いずれかの段階で配置可能な場所がなくなり,次に進むことができなくなるようなことがしばしば起こります.その場合は,最後に配置した数字を除き,その数字を配置可能な他の場所に配置し直します.このような候補がなくなった場合は,さらに前に戻り,同様な処理を繰り返します.つまり,バックトラック処理を行っています.しかし,

      • 組み合わせの問題から,バックトラック回数が膨大になる可能性がある.
      • ループに陥ることなく,バックトラックを実行するためには,過去の状態をすべて保存しておく必要がある.その結果,プログラムが複雑になると共に,膨大なメモリを必要とする可能性がある.

      などの理由から,バックトラックの回数が 1000 回に達すると,そこまでの状態をクリアし,1 の配置から再度実行するということを繰り返しています.Java コンソールに数字が表示されますが,この数字が問題生成に要した回数を表しています.例えば,15432 という数字は,1 から配置する方法を 16 回繰り返し,16 回目において,432 回のバックトラックによって解を得たことを表しています.以下に示すのが,Problem クラスのソースプログラムです.

      001	package start;
      002	
      003	import java.awt.*;
      004	import java.awt.event.*;
      005	import javax.swing.*;
      006	import java.util.*;
      007	
      008	public class Problem extends JFrame implements ActionListener
      009	{
      010		int pr[][] = new int[9][9];
      011		JTextField tx[][] = new JTextField[9][9];
      012		JButton bt1, bt2, bt3;
      013		JTextField sp;
      014		Bord bd[];
      015		Random rn;
      016				// 初期設定
      017		Problem(String name, Bord bd1[])
      018		{
      019						// Frameクラスのコンストラクタ(Windowのタイトルを引き渡す)
      020			super(name);
      021			bd = bd1;
      022						// Windowの大きさ
      023			setSize(400, 400);
      024						// 背景色の設定,レイアウト,フォント
      025			setBackground(new Color(220, 255, 220));   // 背景色
      026			setLayout(new BorderLayout(5, 5));
      027			Font f = new Font("SansSerif", Font.BOLD, 20);
      028						// 乱数
      029			rn = new Random();
      030						// 上のパネル
      031			JPanel jp1 = new JPanel();
      032			jp1.setBackground(new Color(220, 255, 220));
      033			add(jp1, BorderLayout.NORTH);;
      034	
      035			bt1 = new JButton("解");
      036			bt1.setFont(f);
      037			bt1.addActionListener(this);
      038			jp1.add(bt1);
      039	
      040			JLabel lb2 = new JLabel(" ");
      041			lb2.setFont(f);
      042			jp1.add(lb2);
      043	
      044			JLabel lb1 = new JLabel("空白");
      045			lb1.setFont(f);
      046			jp1.add(lb1);
      047	
      048			sp = new JTextField(2);
      049			sp.setFont(f);
      050			jp1.add(sp);
      051	
      052			bt2 = new JButton("Go");
      053			bt2.setFont(f);
      054			bt2.addActionListener(this);
      055			jp1.add(bt2);
      056	
      057			JLabel lb3 = new JLabel(" ");
      058			lb3.setFont(f);
      059			jp1.add(lb3);
      060	
      061			bt3 = new JButton("設定");
      062			bt3.setFont(f);
      063			bt3.addActionListener(this);
      064			jp1.add(bt3);
      065						// 下のパネル
      066			JPanel jp2 = new JPanel();
      067			add(jp2, BorderLayout.CENTER);;
      068			jp2.setLayout(new GridLayout(9, 9, 5, 5));
      069			jp2.setBackground(new Color(220, 255, 220));
      070			for (int i1 = 0; i1 < 9; i1++) {
      071				for (int i2 = 0; i2 < 9; i2++) {
      072					tx[i1][i2] = new JTextField(1);
      073					tx[i1][i2].setFont(f);
      074					jp2.add(tx[i1][i2]);
      075				}
      076			}
      077						// ウィンドウを表示
      078			setVisible(true);
      079						// 問題の作成
      080			create();
      081		}
      082				// ボタンがクリックされたときの処理
      083		public void actionPerformed(ActionEvent e)
      084		{
      085						// 解
      086			if (e.getSource() == bt1)
      087				create();
      088						// Go
      089			else if (e.getSource() == bt2)
      090				space();
      091						// 設定
      092			else if (e.getSource() == bt3) {
      093				set();
      094				setVisible(false);
      095			}
      096		}
      097				// 問題の作成
      098		void create()
      099		{
      100						// 初期設定
      101			int ct1 = 0, ct2 = 0, n1 = 1, n2 = 1, ft = 1;
      102								// ct1 : バックトラック回数
      103								// ct2 : バックトラック回数の合計
      104								// n1 : 配置する数字
      105								// n2 : その数字の何個目かを示す
      106								// ft : 一番最初の数字( =1 )か否か( =0 )
      107			for (int i1 = 0; i1 < 9; i1++) {
      108				for (int i2 = 0; i2 < 9; i2++)
      109					pr[i1][i2] = 0;
      110			}
      111			ArrayList <Pos> mp = new ArrayList <Pos> ();
      112						// 設定
      113			while (n1 <= 9) {
      114				while (n2 <= 9) {
      115								// 最初の数字
      116					if (ft > 0) {
      117						ft    = 0;
      118						int k = (int)(rn.nextDouble() * 81);
      119						if (k >= 81)
      120							k = 80;
      121						int k1     = k / 9;
      122						int k2     = k % 9;
      123						pr[k1][k2] = 1;
      124						ArrayList <Integer> row1 = new ArrayList <Integer> ();
      125						ArrayList <Integer> col1 = new ArrayList <Integer> ();
      126						for (int i1 = 0; i1 < 9; i1++) {
      127							for (int i2 = 0; i2 < 9; i2++) {
      128								if (i1 != k1 || i2 != k2) {
      129									row1.add(i1);
      130									col1.add(i2);
      131								}
      132							}
      133						}
      134						Pos ps = new Pos(k1, k2, 1, 1, row1, col1);
      135						mp.add(ps);
      136						n2++;
      137					}
      138								// 2番目以降の数字
      139					else {
      140						int p[][] = new int[9][9];
      141						select(n1, pr, p);
      142						ArrayList <Integer> row1 = new ArrayList <Integer> ();
      143						ArrayList <Integer> col1 = new ArrayList <Integer> ();
      144						for (int i1 = 0; i1 < 9; i1++) {
      145							for (int i2 = 0; i2 < 9; i2++) {
      146								if (p[i1][i2] > 0) {
      147									row1.add(i1);
      148									col1.add(i2);
      149								}
      150							}
      151						}
      152										// 設定する場所がある場合
      153						if (row1.size() > 0) {
      154							int k = (int)(rn.nextDouble() * row1.size());
      155							if (k >= row1.size())
      156								k = row1.size() - 1;
      157							int k1     = ((Integer)row1.get(k)).intValue();
      158							int k2     = ((Integer)col1.get(k)).intValue();
      159							pr[k1][k2] = n1;
      160							row1.remove(k);
      161							col1.remove(k);
      162							Pos ps = new Pos(k1, k2, n1, n2, row1, col1);
      163							mp.add(ps);
      164							n2++;
      165						}
      166										// 設定する場所がない場合
      167						else {
      168							int sw = 0;
      169							while (sw == 0 && ct1 < 1000) {
      170								ct1++;
      171								int n = mp.size() - 1;
      172								if (n < 0)
      173									break;
      174								else {
      175									Pos ps     = mp.get(n);
      176									int k1     = ps.r;
      177									int k2     = ps.c;
      178									pr[k1][k2] = 0;
      179									mp.remove(n);
      180									if (ps.row.size() > 0) {
      181										sw    = 1;
      182										n1    = ps.n1;
      183										n2    = ps.n2;
      184										row1  = ps.row;
      185										col1  = ps.col;
      186										int k = (int)(rn.nextDouble() * row1.size());
      187										if (k >= row1.size())
      188											k = row1.size() - 1;
      189										k1         = ((Integer)row1.get(k)).intValue();
      190										k2         = ((Integer)col1.get(k)).intValue();
      191										pr[k1][k2] = n1;
      192										row1.remove(k);
      193										col1.remove(k);
      194										ps = new Pos(k1, k2, n1, n2, row1, col1);
      195										mp.add(ps);
      196										n2++;
      197									}
      198								}
      199							}
      200							if (sw == 0) {
      201								ct2 += ct1;
      202								ct1  = 0;
      203								n1   = 0;
      204								n2   = 10;
      205								ft   = 1;
      206								mp.clear();
      207								for (int i1 = 0; i1 < 9; i1++) {
      208									for (int i2 = 0; i2 < 9; i2++)
      209										pr[i1][i2] = 0;
      210								}
      211							}
      212						}
      213					}
      214				}
      215				n1++;
      216				n2 = 1;
      217			}
      218						// 出力
      219			ct2 += ct1;
      220			System.out.println("result " + ct2);
      221			for (int i1 = 0; i1 < 9; i1++) {
      222				for (int i2 = 0; i2 < 9; i2++)
      223					tx[i1][i2].setText(Integer.toString(pr[i1][i2]));
      224			}
      225		}
      226				// 数字を配置できる場所の特定
      227				//   n : 数字
      228				//   pr[i][j] : 現在の状態
      229				//   p[i][j] : =0 : 配置不可能
      230				//             =1 : 配置可能
      231		void select(int n, int pr[][], int p[][])
      232		{
      233						// 初期設定
      234			for (int i1 = 0; i1 < 9; i1++) {
      235				for (int i2 = 0; i2 < 9; i2++) {
      236					if (pr[i1][i2] == 0)
      237						p[i1][i2] = 1;
      238					else
      239						p[i1][i2] = 0;
      240				}
      241			}
      242						// ブロック内
      243			for (int i1 = 0; i1 < 9; i1++) {
      244				int k1 = i1 / 3 * 3;
      245				int k2 = (i1 % 3) * 3;
      246				int sw = 0;
      247				for (int i2 = k1; i2 < k1+3 && sw == 0; i2++) {
      248					for (int i3 = k2; i3 < k2+3 && sw == 0; i3++) {
      249						if (pr[i2][i3] == n)
      250							sw = 1;
      251					}
      252				}
      253				if (sw > 0) {
      254					for (int i2 = k1; i2 < k1+3; i2++) {
      255						for (int i3 = k2; i3 < k2+3; i3++)
      256							p[i2][i3] = 0;
      257					}
      258				}
      259			}
      260						// 行内
      261			for (int i1 = 0; i1 < 9; i1++) {
      262				int sw = 0;
      263				for (int i2 = 0; i2 < 9 && sw == 0; i2++) {
      264					if (pr[i1][i2] == n)
      265						sw = 1;
      266				}
      267				if (sw > 0) {
      268					for (int i2 = 0; i2 < 9; i2++)
      269						p[i1][i2] = 0;
      270				}
      271			}
      272						// 列内
      273			for (int i1 = 0; i1 < 9; i1++) {
      274				int sw = 0;
      275				for (int i2 = 0; i2 < 9 && sw == 0; i2++) {
      276					if (pr[i2][i1] == n)
      277						sw = 1;
      278				}
      279				if (sw > 0) {
      280					for (int i2 = 0; i2 < 9; i2++)
      281						p[i2][i1] = 0;
      282				}
      283			}
      284		}
      285				// 空白の生成
      286		void space()
      287		{
      288			int n = (sp.getText().length() > 0) ? Integer.parseInt(sp.getText()) : 0;
      289			if (n > 0) {
      290				int k = 0;
      291				while (k < n) {
      292					int k1 = (int)(9 * rn.nextDouble());
      293					if (k1 > 8)
      294						k1 = 8;
      295					int k2 = (int)(9 * rn.nextDouble());
      296					if (k2 > 8)
      297						k2 = 8;
      298					if (pr[k1][k2] != 0) {
      299						tx[k1][k2].setText("");
      300						pr[k1][k2] = 0;
      301						k++;
      302					}
      303				}
      304			}
      305		}
      306				// 問題の設定
      307		void set()
      308		{
      309			for (int i1 = 0; i1 < 9; i1++) {
      310				for (int i2 = 0; i2 < 3; i2++) {
      311					for (int i3 = 0; i3 < 3; i3++) {
      312						int k1 = (i1 / 3) * 3 + i2;
      313						int k2 = (i1 % 3) * 3 + i3;
      314						bd[i1].tx[i2][i3].setText(tx[k1][k2].getText());
      315					}
      316				}
      317			}
      318		}
      319	}
      320	
      321	class Pos
      322	{
      323		int r, c;   // 設定した場所
      324		int n1;   // 数字
      325		int n2;   // 何個目の数字か
      326		ArrayList <Integer> row = new ArrayList <Integer> ();   // 設定可能場所(行)
      327		ArrayList <Integer> col = new ArrayList <Integer> ();   // 設定可能場所(列)
      328	
      329		Pos(int r1, int c1, int m1, int m2, ArrayList <Integer> row1, ArrayList <Integer> col1)
      330		{
      331			r   = r1;
      332			c   = c1;
      333			n1  = m1;
      334			n2  = m2;
      335			row = row1;
      336			col = col1;
      337		}
      338	}
      				
      026 行目

        BorderLayout に設定しています.

      031 行目~ 033 行目

        BorderLayout の NORTH にパネル jp1 を追加しています.

      035 行目~ 064 行目

        パネル jp1 に,ボタンやテキストフィールドを追加しています.

      066 行目~ 076 行目

        BorderLayout の CENTER にパネル jp2 を追加し,そのレイアウトを GridLayout に設定した後,( 9 × 9 )個の テキストフィールドを追加しています.

      080 行目

        メソッド create ( 098 行目~ 225 行目)によって,一つの解を生成しています.

      086,087 行目

        「解」ボタンがクリックされたときの処理です.Window が表示されたとき,一つの解が表示されていますが,このボタンをクリックすると,メソッド create によって,別の解が生成されます.

      089,090 行目

        「Go」ボタンがクリックされたときの処理です.空白表示の横のテキストフィールドに空白の数を入力し,このボタンをクリックすると,メソッド space ( 286 行目~ 305 行目)によって,ランダムに数字が空白に置き換わります.また,0 以下の値を設定しても,何も起こりません.なお,このボタンを使用せず,各テキストエリアを直接編集することも可能です.

      092 行目~ 095 行目

        「設定」ボタンがクリックされたときの処理です.このボタンをクリックすると,メソッド set ( 307 行目~ 318 行目)によって,問題がゲーム本体のボードに設定されます.その後,問題作成用の Window を非表示にしています( 094 行目).

      117 行目~ 136 行目

        ( 9 × 9 )個の場所からランダムに選択し,k1 行 k2 列に最初の 1 を設定しています( 118 行目~ 123 行目).118 行目では,実数として得られた数値を整数に変換しています(型変換( cast 演算子)).残りの( 9 × 9 - 1 )個の場所を,配置可能な候補として,ArrayList クラスのオブジェクト row1 と col1 に保存しています( 124 行目~ 133 行目).これらの情報に基づき,Pos クラスのオブジェクトを生成し( 134 行目),そのオブジェクトを ArrayList クラスのオブジェクト mp に記憶しています( 135 行目).Pos クラスは,321 行目~ 338 行目に記述してあるように,数字 n1 の n2 個目を,r 行 c 列に設定したとき,r 行 c 列以外の配置可能場所を,ArrayList クラスのオブジェクト row と col に記憶しています.

      140 行目~ 151 行目

        数字 n1 を配置可能な場所を調べ( 141 行目),その結果を,ArrayList クラスのオブジェクト row1 と col1 に保存しています.

      154 行目~ 164 行目

        配置可能な場所が存在した場合の処理です.配置可能な場所から配置場所をランダムに選択し( 154 行目~ 158 行目),その場所に数字 n1 を設定しています( 159 行目).配置した場所を配置可能な場所の候補から削除した後( 160.161 行目),Pos クラスのオブジェクトを生成し,そのオブジェクトを配列 mp に記憶しています.

      168 行目~ 211 行目

        配置可能な場所が存在しなかった場合の処理です.168 行目~ 199 行目が,バックトラックの実行部分です.配列 mp から,一つ前の情報を抜き出し,配置可能な場所が存在した場合は配置を実行し,存在しなければ,さらに前に戻るという処理を実行しています.また,200 行目~ 211 行目は,バックトラック回数が 1000 回に達した場合の処理であり,最初の 1 の配置に戻すための設定です.なお,173 行目の break 文が実行されると,break 文が入っている最も内側のループの外に出て(ループの処理を中止して),そのループの次の文が実行されます.

      219 行目~ 224 行目

        バックトラック回数を Java コンソールに出力すると共に,生成された問題を画面上に設定しています.ct2 に対する制限が付けてありませんので,113,114 行目の while 文によって無限ループに入る可能性もありますので,ご注意ください.

      231 行目~ 284 行目

        数字 n1 を配置可能な場所を調べ,その結果を配列 p に設定しています.

      286 行目~ 305 行目

        空白を作成するためのメソッドです.288 行目の ? : は,条件演算子と呼ばれ,一般的には以下のように記述されます.論理式が評価され,その結果が真であれば,式1を評価した結果が変数に代入され,偽であれば,式2を評価した結果が変数に代入されます.
      	変数 = (論理式) ? 式1 : 式2 					
      従って,288 行目は,データが入力されていれば,n にそれを整数に変換した値を代入し,そうでなければ,0 を代入するという意味になります.

      307 行目~ 318 行目

        作成した問題をゲーム本体に設定するためのメソッドです.

      321 行目~ 338 行目

        クラス Pos の定義です.

    4. GamePanel クラス

        GamePanel クラスは,実際のゲームを実現するクラスです.従って,「ゲーム枠の作成」における GamePanel クラスとは,ゲームの種類によってその内容は大きく異なります.今後,このクラスを完成させていくことになりますが,ここでは,GamePanel クラス( GamePanel.java )と Bord クラス( Bord.java )を使用して,StartPanel で作成した問題を再表示しています.ただし,StartPanel で入力された数字に対応するテキストフィールドの背景色を変更すると共に,その数字を編集できないようにしています.勿論,この状態でナンプレを実行することも可能です.以下,GamePanel クラスから順に説明していきます.

        GamePanel クラスは,以下に示すように,StartPanel クラスとほとんど同じ処理を行っています.ただし,ボタンとその機能が異なっています.「終了」ボタンをクリックするとゲームを終了し,また,「次の問題」ボタンをクリックすると StartPanel に戻ります.

      package game;
      
      import java.awt.*;
      import java.awt.event.*;
      import javax.swing.*;
      import main.*;
      
      public class GamePanel extends JPanel implements ActionListener
      {
      	MainPanel mp;
      	Bord bd[] = new Bord [9];
      	JButton next, end;
      			//
      			// コンストラクタ
      			//
      	public GamePanel(Dimension size, MainPanel mp1)
      	{
      		mp = mp1;
      					// レイアウトマネージャの停止
      		setLayout(null);
      					// 背景色の設定
      		setBackground(new Color(220, 255, 220));   // 背景色
      					// パネルの配置
      		JPanel jp = new JPanel();
      		jp.setSize(size.width, size.width);
      		jp.setLocation(0, 0);
      		jp.setBackground(Color.green);
      		add(jp);
      							// グリッドレイアウト
      		jp.setLayout(new GridLayout(3, 3, 5, 5));
      							// ボードの配置
      		for (int i1 = 0; i1 < 9; i1++) {
      			bd[i1] = new Bord(i1, mp);
      			jp.add(bd[i1]);
      		}
      					// 「次の問題」ボタンの配置
      		Font f = new Font("SansSerif", Font.BOLD, 20);
      		next = new JButton("次の問題");
      		next.setFont(f);
      		next.setSize(120, 40);
      		next.setLocation(140, 530);
      		next.addActionListener(this);
      		add(next);
      					// 「終了」ボタンの配置
      		end = new JButton("終了");
      		end.setFont(f);
      		end.setSize(80, 40);
      		end.setLocation(280, 530);
      		end.addActionListener(this);
      		add(end);
      	}
      			//
      			// ボタンがクリックされたときの処理
      			//
      	public void actionPerformed(ActionEvent e)
      	{
      					// 「終了」ボタン
      		if (e.getSource() == end)
      			mp.state = 2;
      					// 「次の問題」ボタン
      		else if (e.getSource() == next)
      			mp.state = 0;
      	}
      }
      				

        Bord クラスも,start パッケージ内の Bord クラスとほとんど同じです.ただし,26 行目~ 30 行目において,正の数値が設定されているテキストフィールドの背景色を変え( RGB : 0xC8FFC8 ),そのテキストフィールドの編集を不可能にしています.

      01	package game;
      02	
      03	import java.awt.*;
      04	import java.awt.event.*;
      05	import javax.swing.*;
      06	import main.*;
      07	
      08	public class Bord extends JPanel
      09	{
      10		JTextField tx[][] = new JTextField [3][3];
      11				// コンストラクタ
      12		public Bord(int n, MainPanel mp)
      13		{
      14						// 背景色の設定
      15			setBackground(new Color(220, 255, 220));
      16						// グリッドレイアウト
      17			setLayout(new GridLayout(3, 3, 2, 2));
      18						// テキストフィールドの配置
      19			Font f = new Font("SansSerif", Font.BOLD, 50);
      20			for (int i1 = 0; i1 < 3; i1++) {
      21				for (int i2 = 0; i2 < 3; i2++) {
      22					tx[i1][i2] = new JTextField();
      23					tx[i1][i2].setBackground(Color.white);
      24					tx[i1][i2].setFont(f);
      25					tx[i1][i2].setHorizontalAlignment(JTextField.CENTER);
      26					if (mp.pr[n][i1][i2] > 0) {
      27						tx[i1][i2].setText(Integer.toString(mp.pr[n][i1][i2]));
      28						tx[i1][i2].setEditable(false);
      29						tx[i1][i2].setBackground(new Color(200, 255, 200));
      30					}
      31					add(tx[i1][i2]);
      32				}
      33			}
      34		}
      35	}
      				

  2. ステップ2: ルール違反のチェック

      ステップ1の段階で,ゲームを実行することは可能ですが,ルール違反を起こしていないか否かをチェックすることはかなり面倒な作業になります.ここでは,ルール違反が起こった時それを警告する機能を付加します.修正するプログラムは,start パッケージ内の StartPanel クラス( StartPanel.java )と game パッケージ内の GamePanel クラス( GamePanel.java )です.まず,StartPanel クラスから説明していきます.StartPanel においては,「実行」ボタンがクリックされるとルール違反のチェックを行います.

    001	package start;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import main.*;
    007	
    008	public class StartPanel extends JPanel implements ActionListener
    009	{
    010		Bord bd[] = new Bord [9];
    011		Dimension size;   // パネルの大きさ
    012		MainPanel mp;
    013		JButton bt1, bt2, bt3;
    014				//
    015				// コンストラクタ
    016				//
    017		public StartPanel(Dimension size1, MainPanel mp1)
    018		{
    019			size = size1;
    020			mp   = mp1;
    021						// レイアウトマネージャの停止
    022			setLayout(null);
    023						// 背景色の設定
    024			setBackground(new Color(220, 255, 220));   // 背景色
    025						// パネルの配置
    026			JPanel jp = new JPanel();
    027			jp.setSize(size.width, size.width);
    028			jp.setLocation(0, 0);
    029			jp.setBackground(Color.green);
    030			add(jp);
    031								// グリッドレイアウト
    032			jp.setLayout(new GridLayout(3, 3, 5, 5));
    033								// ボードの配置
    034			for (int i1 = 0; i1 < 9; i1++) {
    035				bd[i1] = new Bord();
    036				jp.add(bd[i1]);
    037			}
    038						// ボタンの配置
    039			Font f = new Font("SansSerif", Font.BOLD, 20);
    040			bt1 = new JButton("遊び方");
    041			bt1.setFont(f);
    042			bt1.setSize(120, 40);
    043			bt1.setLocation(140, 530);
    044			bt1.addActionListener(this);
    045			add(bt1);
    046	
    047			bt2 = new JButton("実行");
    048			bt2.setFont(f);
    049			bt2.setSize(80, 40);
    050			bt2.setLocation(290, 530);
    051			bt2.addActionListener(this);
    052			add(bt2);
    053	
    054			bt3 = new JButton("問題");
    055			bt3.setFont(f);
    056			bt3.setSize(80, 40);
    057			bt3.setLocation(400, 530);
    058			bt3.addActionListener(this);
    059			add(bt3);
    060		}
    061				//
    062				// ボタンがクリックされたときの処理
    063				//
    064		public void actionPerformed(ActionEvent e)
    065		{
    066						// 遊び方
    067			if (e.getSource() == bt1) {
    068				Method db = new Method();
    069				db.setVisible(true);
    070				requestFocusInWindow();
    071			}
    072						// 実行
    073			else if (e.getSource() == bt2) {
    074				for (int i1 = 0; i1 < 9; i1++) {
    075					for (int i2 = 0; i2 < 3; i2++) {
    076						for (int i3 = 0; i3 < 3; i3++) {
    077							String str = bd[i1].tx[i2][i3].getText();
    078							if (str.length() > 0) {
    079								int k;
    080								try {
    081									k = Integer.parseInt(str);
    082									if (k == 0)
    083										k = -1;
    084								}
    085								catch (NumberFormatException ne)
    086								{
    087									k = -1;
    088								}
    089								mp.pr[i1][i2][i3] = k;
    090							}
    091							else
    092								mp.pr[i1][i2][i3] = 0;
    093						}
    094					}
    095				}
    096				boolean sw = check();
    097				if (sw)
    098					mp.state = 1;
    099			}
    100						// 問題
    101			else {
    102				Problem win = new Problem("問題作成", bd);
    103			}
    104		}
    105				//
    106				// データは適切か?
    107				//
    108		boolean check()
    109		{
    110			boolean sw = true;
    111						// 数字の適切性
    112			for (int i1 = 0; i1 < 9; i1++) {
    113				for (int i2 = 0; i2 < 3; i2++) {
    114					for (int i3 = 0; i3 < 3; i3++) {
    115						bd[i1].tx[i2][i3].setForeground(Color.black);
    116						if (mp.pr[i1][i2][i3] < 0 || mp.pr[i1][i2][i3] > 9) {
    117							sw = false;
    118							bd[i1].tx[i2][i3].setForeground(Color.red);
    119						}
    120					}
    121				}
    122			}
    123						// ブロック内
    124			if (sw) {
    125				for (int i1 = 0; i1 < 9; i1++) {
    126					for (int i2 = 0; i2 < 8; i2++) {
    127						int k1 = mp.pr[i1][i2/3][i2%3];
    128						if (k1 > 0) {
    129							for (int i3 = i2+1; i3 < 9; i3++) {
    130								int k2 = mp.pr[i1][i3/3][i3%3];
    131								if (k1 == k2) {
    132									sw = false;
    133									bd[i1].tx[i2/3][i2%3].setForeground(Color.red);
    134									bd[i1].tx[i3/3][i3%3].setForeground(Color.red);
    135								}
    136							}
    137						}
    138					}
    139				}
    140			}
    141						// 行内
    142			if (sw) {
    143				for (int i1 = 0; i1 < 9; i1++) {
    144					for (int i2 = 0; i2 < 8; i2++) {
    145						int k1 = mp.pr[i1/3*3+i2/3][i1%3][i2%3];
    146						if (k1 > 0) {
    147							for (int i3 = i2+1; i3 < 9; i3++) {
    148								int k2 = mp.pr[i1/3*3+i3/3][i1%3][i3%3];
    149								if (k1 == k2) {
    150									sw = false;
    151									bd[i1/3*3+i2/3].tx[i1%3][i2%3].setForeground(Color.red);
    152									bd[i1/3*3+i3/3].tx[i1%3][i3%3].setForeground(Color.red);
    153								}
    154							}
    155						}
    156					}
    157				}
    158			}
    159						// 列内
    160			if (sw) {
    161				for (int i1 = 0; i1 < 9; i1++) {
    162					for (int i2 = 0; i2 < 8; i2++) {
    163						int k1 = mp.pr[i1/3+i2/3*3][i2%3][i1%3];
    164						if (k1 > 0) {
    165							for (int i3 = i2+1; i3 < 9; i3++) {
    166								int k2 = mp.pr[i1/3+i3/3*3][i3%3][i1%3];
    167								if (k1 == k2) {
    168									sw = false;
    169									bd[i1/3+i2/3*3].tx[i2%3][i1%3].setForeground(Color.red);
    170									bd[i1/3+i3/3*3].tx[i3%3][i1%3].setForeground(Color.red);
    171								}
    172							}
    173						}
    174					}
    175				}
    176			}
    177	
    178			return sw;
    179		}
    180	}
    181	
    182	/******************/
    183	/* ゲームの遊び方 */
    184	/******************/
    185	class Method extends JDialog
    186	{
    187				// コンストラクタ
    188		Method()
    189		{
    190			setTitle("ゲームの遊び方");
    191					// ContetPain
    192			Container cp = getContentPane();
    193			cp.setLayout(new FlowLayout(FlowLayout.CENTER));
    194			cp.setBackground(new Color(220, 255, 220));   // 背景色
    195			Font f = new Font("MS 明朝", Font.PLAIN, 20);
    196			setSize(850, 370);
    197					// TextArea の追加
    198			JTextArea ta = new JTextArea(15, 80);
    199			ta.setFont(f);
    200			ta.setEditable(false);
    201			ta.setLineWrap(true);
    202			ta.setText("1.スタート画面\n");
    203			ta.append("\n");
    204			ta.append(" a.味方1: 配置したい位置をマウスでクリックした後,1 のキーを押せば( 3 のキーを押せばキャンセル)配置され,左方向に動き出す.敵と接触することによって敵を消滅させることができる.配置できる上限数のデフォルトは 10 である.\n");
    205			ta.append("\n");
    206			ta.append(" b.味方2: 配置したい位置をマウスでクリックした後,2 のキーを押せば( 3 のキーを押せばキャンセル)配置される.その位置に停止したままであるが,その近傍を通過した敵を消滅させることができる.配置できる上限数のデフォルトは 4 である.\n");
    207			ta.append("\n");
    208			ta.append("2.ゲーム画面\n");
    209			ta.append("\n");
    210			ta.append(" a.最初から決まっている数字に対しては,背景色が薄い緑になり,修正することができません.それ以外の空いている箇所に数字を入力し,「return」キーをクリックすることによって数字を設定していきます.( step1 )\n");
    211			ta.append("\n");
    212			ta.append(" b.数字を入力する際,ルール違反が起こっている場合は,数字色が赤になります.( step2 )\n");
    213			ta.append("\n");
    214			ta.append(" c.1 ~ 9 までの番号が付けられているボタン(「数字」ボタンと呼ぶ)の下にあるテキストフィールドには,各数字に対する出現回数が表示されます.この値が 9 になるとテキストフィールドの背景色が赤になります.上に示すゲーム版の数字色がすべて黒で,かつ,すべてのテキストフィールドの背景色が赤になった時点でゲーム終了となります.( step3 )\n");
    215			ta.append("\n");
    216			ta.append(" d.「数字」ボタンをクリックすると,ゲーム版の対応する数字を入力可能な場所の背景色がピンクになります.( step4 )\n");
    217			ta.append("\n");
    218			ta.append(" e.「記憶」ボタンをクリックするとことによって,現在の状態を記憶することが出来ます.また,「戻る」ボタンをクリックすることによって,最後に記憶した状態まで順番に戻すことが出来ます.なお,「記憶」ボタンは,「return」キーによって入力する数字を確定した後にクリックして下さい.( step5 )\n");
    219			JScrollPane scroll = new JScrollPane(ta);
    220			cp.add(scroll);
    221					// Window を閉じるため
    222			addWindowListener(new WinEnd());
    223		}
    224					// 終了処理
    225		class WinEnd extends WindowAdapter
    226		{
    227			public void windowClosing(WindowEvent e) {
    228				setVisible(false);
    229			}
    230		}
    231	}
    			
    096 行目~ 098 行目

      ステップ1で述べたように,「実行」ボタンがクリックされると,変数 pr に入力された数値が設定されます.その際,数字以外や 0 が入力されると,-1 が設定されます.096 行目では,ルール違反をチェックするメソッド check を呼び,ルール違反がなければ,098 行目においてゲーム画面に移動します.ルール違反があった場合は,対応する場所の文字が赤くなり,実行画面には移動しません.

    108 行目~ 179 行目

      ルール違反をチェックするためのメソッドです.112 行目~ 122 行目では,1 から 9 までの数字以外が入力されていないかをチェックしています.そのような文字や数字が存在した場合は,それらの文字や数字の色を赤くしています.125 行目~ 139 行目では同じブロック内,143 行目~ 157 行目では同じ行内,及び,161 行目~ 175 行目では同じ列内に同じ数字が現れていないかをチェックし,同様の処理を行っています.

      次は,GamePanel クラスの修正です.GamePanel においては,数字を入力し改行キーを押すと,ルール違反のチェックが行われます.

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import main.*;
    007	
    008	public class GamePanel extends JPanel implements ActionListener
    009	{
    010		MainPanel mp;
    011		Bord bd[] = new Bord [9];
    012		JButton next, end;
    013				//
    014				// コンストラクタ
    015				//
    016		public GamePanel(Dimension size, MainPanel mp1)
    017		{
    018			mp = mp1;
    019						// レイアウトマネージャの停止
    020			setLayout(null);
    021						// 背景色の設定
    022			setBackground(new Color(220, 255, 220));   // 背景色
    023						// パネルの配置
    024			JPanel jp = new JPanel();
    025			jp.setSize(size.width, size.width);
    026			jp.setLocation(0, 0);
    027			jp.setBackground(Color.green);
    028			add(jp);
    029								// グリッドレイアウト
    030			jp.setLayout(new GridLayout(3, 3, 5, 5));
    031								// ボードの配置
    032			for (int i1 = 0; i1 < 9; i1++) {
    033				bd[i1] = new Bord(i1, mp);
    034				for (int i2 = 0; i2 < 3; i2++) {
    035					for (int i3 = 0; i3 < 3; i3++)
    036						bd[i1].tx[i2][i3].addActionListener(this);
    037				}
    038				jp.add(bd[i1]);
    039			}
    040						// 「次の問題」ボタンの配置
    041			Font f = new Font("SansSerif", Font.BOLD, 20);
    042			next = new JButton("次の問題");
    043			next.setFont(f);
    044			next.setSize(120, 40);
    045			next.setLocation(140, 530);
    046			next.addActionListener(this);
    047			add(next);
    048						// 「終了」ボタンの配置
    049			end = new JButton("終了");
    050			end.setFont(f);
    051			end.setSize(80, 40);
    052			end.setLocation(280, 530);
    053			end.addActionListener(this);
    054			add(end);
    055		}
    056				//
    057				// ボタンがクリックされたときの処理
    058				//
    059		public void actionPerformed(ActionEvent e)
    060		{
    061						// 「終了」ボタン
    062			if (e.getSource() == end)
    063				mp.state = 2;
    064						// 「次の問題」ボタン
    065			else if (e.getSource() == next)
    066				mp.state = 0;
    067						// 数字が入力されたとき
    068			else {
    069				int k;
    070				for (int i1 = 0; i1 < 9; i1++) {
    071					for (int i2 = 0; i2 < 3; i2++) {
    072						for (int i3 = 0; i3 < 3; i3++) {
    073							String str = bd[i1].tx[i2][i3].getText();
    074							if (str.length() > 0) {
    075								try {
    076									k = Integer.parseInt(str);
    077									if (k == 0)
    078										k = -1;
    079								}
    080								catch (NumberFormatException ne)
    081								{
    082									k = -1;
    083								}
    084							}
    085							else
    086								k = 0;
    087							mp.pr[i1][i2][i3] = k;
    088						}
    089					}
    090				}
    091				boolean sw = check();
    092			}
    093		}
    094				//
    095				// データは適切か?
    096				//
    097		boolean check()
    098		{
    099			boolean sw = true;
    100						// 数字の適切性
    101			for (int i1 = 0; i1 < 9; i1++) {
    102				for (int i2 = 0; i2 < 3; i2++) {
    103					for (int i3 = 0; i3 < 3; i3++) {
    104						bd[i1].tx[i2][i3].setForeground(Color.black);
    105						if (mp.pr[i1][i2][i3] < 0 || mp.pr[i1][i2][i3] > 9) {
    106							sw = false;
    107							bd[i1].tx[i2][i3].setForeground(Color.red);
    108						}
    109					}
    110				}
    111			}
    112						// ブロック内
    113			if (sw) {
    114				for (int i1 = 0; i1 < 9; i1++) {
    115					for (int i2 = 0; i2 < 8; i2++) {
    116						int k1 = mp.pr[i1][i2/3][i2%3];
    117						if (k1 > 0) {
    118							for (int i3 = i2+1; i3 < 9; i3++) {
    119								int k2 = mp.pr[i1][i3/3][i3%3];
    120								if (k1 == k2) {
    121									sw = false;
    122									bd[i1].tx[i2/3][i2%3].setForeground(Color.red);
    123									bd[i1].tx[i3/3][i3%3].setForeground(Color.red);
    124								}
    125							}
    126						}
    127					}
    128				}
    129			}
    130						// 行内
    131			if (sw) {
    132				for (int i1 = 0; i1 < 9; i1++) {
    133					for (int i2 = 0; i2 < 8; i2++) {
    134						int k1 = mp.pr[i1/3*3+i2/3][i1%3][i2%3];
    135						if (k1 > 0) {
    136							for (int i3 = i2+1; i3 < 9; i3++) {
    137								int k2 = mp.pr[i1/3*3+i3/3][i1%3][i3%3];
    138								if (k1 == k2) {
    139									sw = false;
    140									bd[i1/3*3+i2/3].tx[i1%3][i2%3].setForeground(Color.red);
    141									bd[i1/3*3+i3/3].tx[i1%3][i3%3].setForeground(Color.red);
    142								}
    143							}
    144						}
    145					}
    146				}
    147			}
    148						// 列内
    149			if (sw) {
    150				for (int i1 = 0; i1 < 9; i1++) {
    151					for (int i2 = 0; i2 < 8; i2++) {
    152						int k1 = mp.pr[i1/3+i2/3*3][i2%3][i1%3];
    153						if (k1 > 0) {
    154							for (int i3 = i2+1; i3 < 9; i3++) {
    155								int k2 = mp.pr[i1/3+i3/3*3][i3%3][i1%3];
    156								if (k1 == k2) {
    157									sw = false;
    158									bd[i1/3+i2/3*3].tx[i2%3][i1%3].setForeground(Color.red);
    159									bd[i1/3+i3/3*3].tx[i3%3][i1%3].setForeground(Color.red);
    160								}
    161							}
    162						}
    163					}
    164				}
    165			}
    166	
    167			return sw;
    168		}
    169	}
    			
    034 行目~ 037 行目

      各ブロックのテキストフィールドに対してアクションリスナを付加しています.テキストフィールドに文字を入力し「 return 」キーを押すとアクションイベントが発生します.

    069 行目~ 091 行目

      各ブロックのテキストフィールドに文字を入力し「 return 」キーを押した時に対する処理であり,StartPanel クラスの 074 行目~ 096 行目と全く同じ処理を行っています.また,メソッド check における処理も StartPanel クラスにおけるメソッド check と同じです.

  3. ステップ3: 数字の出現回数の表示

      ここでは,1 から 9 までの数字に対して,各数字が盤面全体に現れる回数をカウントし,その値を表示する機能を付加しています.数字を入力して改行キーを押すと,数字が変化します.ゲーム終了時点では,すべての数字の出現回数が 9 になるはずです.修正するプログラムは,game パッケージ内の GamePanel クラスです( GamePanel.java ).

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import main.*;
    007	
    008	public class GamePanel extends JPanel implements ActionListener
    009	{
    010		MainPanel mp;
    011		Bord bd[] = new Bord [9];
    012		JTextField ct[] = new JTextField [9];
    013		JButton next, end, num_b[] = new JButton [9];
    014				//
    015				// コンストラクタ
    016				//
    017		public GamePanel(Dimension size, MainPanel mp1)
    018		{
    019			mp = mp1;
    020						// レイアウトマネージャの停止
    021			setLayout(null);
    022						// 背景色の設定
    023			setBackground(new Color(220, 255, 220));   // 背景色
    024						// パネルの配置
    025			JPanel jp = new JPanel();
    026			jp.setSize(size.width, size.width);
    027			jp.setLocation(0, 0);
    028			jp.setBackground(Color.green);
    029			add(jp);
    030								// グリッドレイアウト
    031			jp.setLayout(new GridLayout(3, 3, 5, 5));
    032								// ボードの配置
    033			for (int i1 = 0; i1 < 9; i1++) {
    034				bd[i1] = new Bord(i1, mp);
    035				for (int i2 = 0; i2 < 3; i2++) {
    036					for (int i3 = 0; i3 < 3; i3++)
    037						bd[i1].tx[i2][i3].addActionListener(this);
    038				}
    039				jp.add(bd[i1]);
    040			}
    041						// 数字ボタンの配置
    042			JPanel num = new JPanel();
    043			num.setBackground(new Color(220, 255, 220));
    044			num.setSize(size.width, 30);
    045			num.setLocation(0, 500);
    046			add(num);
    047			num.setLayout(new GridLayout(1, 9, 5, 5));
    048			for (int i1 = 0; i1 < 9; i1++) {
    049				num_b[i1] = new JButton(Integer.toString(i1+1));
    050				num_b[i1].setFont(new Font("SansSerif", Font.BOLD, 20));
    051				num.add(num_b[i1]);
    052			}
    053						// カウンタフィールドの配置
    054			JPanel count_f = new JPanel();
    055			count_f.setBackground(new Color(220, 255, 220));
    056			count_f.setSize(size.width, 30);
    057			count_f.setLocation(0, 530);
    058			add(count_f);
    059			count_f.setLayout(new GridLayout(1, 9, 5, 5));
    060			for (int i1 = 0; i1 < 9; i1++) {
    061				ct[i1] = new JTextField();
    062				ct[i1].setFont(new Font("SansSerif", Font.BOLD, 20));
    063				ct[i1].setBackground(Color.white);
    064				ct[i1].setHorizontalAlignment(JTextField.CENTER);
    065				ct[i1].setEditable(false);
    066				count_f.add(ct[i1]);
    067			}
    068						// 「次の問題」ボタンの配置
    069			Font f = new Font("SansSerif", Font.BOLD, 20);
    070			next = new JButton("次の問題");
    071			next.setFont(f);
    072			next.setSize(120, 40);
    073			next.setLocation(140, 560);
    074			next.addActionListener(this);
    075			add(next);
    076						// 「終了」ボタンの配置
    077			end = new JButton("終了");
    078			end.setFont(f);
    079			end.setSize(80, 40);
    080			end.setLocation(280, 560);
    081			end.addActionListener(this);
    082			add(end);
    083						// 数字の数を数える
    084			count();
    085		}
    086				//
    087				// ボタンがクリックされたときの処理
    088				//
    089		public void actionPerformed(ActionEvent e)
    090		{
    091						// 「終了」ボタン
    092			if (e.getSource() == end)
    093				mp.state = 2;
    094						// 「次の問題」ボタン
    095			else if (e.getSource() == next)
    096				mp.state = 0;
    097						// 数字が入力されたとき
    098			else {
    099				int k;
    100				for (int i1 = 0; i1 < 9; i1++) {
    101					for (int i2 = 0; i2 < 3; i2++) {
    102						for (int i3 = 0; i3 < 3; i3++) {
    103							String str = bd[i1].tx[i2][i3].getText();
    104							if (str.length() > 0) {
    105								try {
    106									k = Integer.parseInt(str);
    107									if (k == 0)
    108										k = -1;
    109								}
    110								catch (NumberFormatException ne)
    111								{
    112									k = -1;
    113								}
    114							}
    115							else
    116								k = 0;
    117							mp.pr[i1][i2][i3] = k;
    118						}
    119					}
    120				}
    121				boolean sw = check();
    122				if (sw)
    123					count();
    124			}
    125		}
    126				//
    127				// データは適切か?
    128				//
    129		boolean check()
    130		{
    131			boolean sw = true;
    132						// 数字の適切性
    133			for (int i1 = 0; i1 < 9; i1++) {
    134				for (int i2 = 0; i2 < 3; i2++) {
    135					for (int i3 = 0; i3 < 3; i3++) {
    136						bd[i1].tx[i2][i3].setForeground(Color.black);
    137						if (mp.pr[i1][i2][i3] < 0 || mp.pr[i1][i2][i3] > 9) {
    138							sw = false;
    139							bd[i1].tx[i2][i3].setForeground(Color.red);
    140						}
    141					}
    142				}
    143			}
    144						// ブロック内
    145			if (sw) {
    146				for (int i1 = 0; i1 < 9; i1++) {
    147					for (int i2 = 0; i2 < 8; i2++) {
    148						int k1 = mp.pr[i1][i2/3][i2%3];
    149						if (k1 > 0) {
    150							for (int i3 = i2+1; i3 < 9; i3++) {
    151								int k2 = mp.pr[i1][i3/3][i3%3];
    152								if (k1 == k2) {
    153									sw = false;
    154									bd[i1].tx[i2/3][i2%3].setForeground(Color.red);
    155									bd[i1].tx[i3/3][i3%3].setForeground(Color.red);
    156								}
    157							}
    158						}
    159					}
    160				}
    161			}
    162						// 行内
    163			if (sw) {
    164				for (int i1 = 0; i1 < 9; i1++) {
    165					for (int i2 = 0; i2 < 8; i2++) {
    166						int k1 = mp.pr[i1/3*3+i2/3][i1%3][i2%3];
    167						if (k1 > 0) {
    168							for (int i3 = i2+1; i3 < 9; i3++) {
    169								int k2 = mp.pr[i1/3*3+i3/3][i1%3][i3%3];
    170								if (k1 == k2) {
    171									sw = false;
    172									bd[i1/3*3+i2/3].tx[i1%3][i2%3].setForeground(Color.red);
    173									bd[i1/3*3+i3/3].tx[i1%3][i3%3].setForeground(Color.red);
    174								}
    175							}
    176						}
    177					}
    178				}
    179			}
    180						// 列内
    181			if (sw) {
    182				for (int i1 = 0; i1 < 9; i1++) {
    183					for (int i2 = 0; i2 < 8; i2++) {
    184						int k1 = mp.pr[i1/3+i2/3*3][i2%3][i1%3];
    185						if (k1 > 0) {
    186							for (int i3 = i2+1; i3 < 9; i3++) {
    187								int k2 = mp.pr[i1/3+i3/3*3][i3%3][i1%3];
    188								if (k1 == k2) {
    189									sw = false;
    190									bd[i1/3+i2/3*3].tx[i2%3][i1%3].setForeground(Color.red);
    191									bd[i1/3+i3/3*3].tx[i3%3][i1%3].setForeground(Color.red);
    192								}
    193							}
    194						}
    195					}
    196				}
    197			}
    198	
    199			return sw;
    200		}
    201				//
    202				// 数字の数を数える
    203				//
    204		void count()
    205		{
    206			for (int i1 = 0; i1 < 9; i1++) {
    207				int k1 = i1 + 1;
    208				int n = 0;
    209				for (int i2 = 0; i2 < 9; i2++) {
    210					for (int i3 = 0; i3 < 3; i3++) {
    211						for (int i4 = 0; i4 < 3; i4++) {
    212							if (mp.pr[i2][i3][i4] == k1)
    213								n++;
    214						}
    215					}
    216				}
    217				ct[i1].setText(Integer.toString(n));
    218				if (n == 9)
    219					ct[i1].setBackground(Color.red);
    220				else
    221					ct[i1].setBackground(Color.white);
    222			}
    223		}
    224	}
    			
    012 行目~ 013 行目,042 行目~ 067 行目

      1 から 9 までの数字を表示した「数字」ボタンと,各「数字」ボタンの下にはその数字が現れた回数を表示するためのテキストフィールドを貼り付けています.

    084 行目

      1 から 9 までの数字の出現回数をカウントするメソッド count を呼んでいます..

    122 行目~ 123 行目

      ルール違反が無かった場合,1 から 9 までの数字の出現回数をカウントするメソッド count を呼んでいます.

    204 行目~ 223 行目

      1 から 9 までの数字の出現回数をカウントし,その結果を「数字」ボタンの下のテキストフィールドに表示するためのメソッドです.出現回数が 9 になった場合は,そのテキストフィールドの背景色を赤に設定しています.

  4. ステップ4: 数字入力可能位置の表示

      「数字」ボタンをクリックすると,その数字を入力できる場所を表示する機能を追加します.修正するプログラムは,main パッケージ内の MainPanel クラス( MainPanel.java ),start パッケージ内の StartPanel クラス( StartPanel.java ),及び,game パッケージ内の GamePanel クラス( GamePanel.java )です.まず,MainPanel クラスの変更は簡単です.以下に示すように,後に述べるクラスで使用するため,問題の状態を表す配列 pr と同じ大きさの配列 wk を定義しておきます( 18 行目).

    01	package main;
    02	
    03	import java.awt.*;
    04	import java.awt.event.*;
    05	import javax.swing.*;
    06	import start.*;
    07	import game.*;
    08	
    09	public class MainPanel extends JPanel implements Runnable
    10	{
    11		Dimension size;   // パネルの大きさ
    12		boolean in_game = true;   // ゲーム実行中はtrue
    13		public int state = 0;   // ゲーム状態(0:表紙,1:ゲーム,2:終了)
    14		int old_state = 0;   // 直前のゲーム状態
    15		public int pr[][][] = new int [9][3][3];   // 問題
    16				// pr[i][j][k] : [i] : ブロック番号.左から右,上から下へ番号付け(i=0~9)
    17				//             : [j][k] : 各ブロックのj行k列(j,k=0~2)
    18		public int wk[][][] = new int [9][3][3];   // 作業域
    19		StartPanel sp;
    20		GamePanel gp;
    21		Thread td;
    22				//
    23				// コンストラクタ
    24				//
    25		public MainPanel(Dimension size1)
    26		{
    27			size = size1;
    28						// グリッドレイアウト
    29			setLayout(new GridLayout(1, 1, 0, 0));
    30						// ゲームパネルの生成
    31			sp = new StartPanel(size, this);   // スタート(タイトル)
    32			add(sp);
    33						// スレッドの生成
    34			td = new Thread(this);
    35			td.start();
    36		}
    37				//
    38				// ゲームの状態を変更
    39				//
    40		public void run()
    41		{
    42			while (in_game) {
    43				try {
    44					td.sleep(100);   // 100 ms 毎の実施
    45				}
    46				catch (InterruptedException e) {}
    47				if (state != old_state) {
    48								// 前のパネルの削除
    49					if (old_state == 0)
    50						remove(sp);
    51					else
    52						remove(gp);
    53								// 新しいパネルの追加
    54					if (state == 2)   // ゲーム終了
    55						in_game = false;
    56					else {
    57						if (state == 0) {   // StartPanel
    58							sp = new StartPanel(size, this);
    59							add(sp);
    60						}
    61						else {   // GamePanel
    62							gp = new GamePanel(size, this);
    63							add(gp);
    64						}
    65						validate();
    66						old_state = state;
    67					}
    68				}
    69			}
    70		}
    71	}
    			

      StartPanel クラスの修正も簡単です.090 行目,及び,094 行目に示すように,問題として数字が初期設定された場所に対応する配列 wk の値を 1 に,それ以外は 0 に設定しておきます.

    001	package start;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import main.*;
    007	
    008	public class StartPanel extends JPanel implements ActionListener
    009	{
    010		Bord bd[] = new Bord [9];
    011		Dimension size;   // パネルの大きさ
    012		MainPanel mp;
    013		JButton bt1, bt2, bt3;
    014				//
    015				// コンストラクタ
    016				//
    017		public StartPanel(Dimension size1, MainPanel mp1)
    018		{
    019			size = size1;
    020			mp   = mp1;
    021						// レイアウトマネージャの停止
    022			setLayout(null);
    023						// 背景色の設定
    024			setBackground(new Color(220, 255, 220));   // 背景色
    025						// パネルの配置
    026			JPanel jp = new JPanel();
    027			jp.setSize(size.width, size.width);
    028			jp.setLocation(0, 0);
    029			jp.setBackground(Color.green);
    030			add(jp);
    031								// グリッドレイアウト
    032			jp.setLayout(new GridLayout(3, 3, 5, 5));
    033								// ボードの配置
    034			for (int i1 = 0; i1 < 9; i1++) {
    035				bd[i1] = new Bord();
    036				jp.add(bd[i1]);
    037			}
    038						// ボタンの配置
    039			Font f = new Font("SansSerif", Font.BOLD, 20);
    040			bt1 = new JButton("遊び方");
    041			bt1.setFont(f);
    042			bt1.setSize(120, 40);
    043			bt1.setLocation(140, 530);
    044			bt1.addActionListener(this);
    045			add(bt1);
    046	
    047			bt2 = new JButton("実行");
    048			bt2.setFont(f);
    049			bt2.setSize(80, 40);
    050			bt2.setLocation(290, 530);
    051			bt2.addActionListener(this);
    052			add(bt2);
    053	
    054			bt3 = new JButton("問題");
    055			bt3.setFont(f);
    056			bt3.setSize(80, 40);
    057			bt3.setLocation(400, 530);
    058			bt3.addActionListener(this);
    059			add(bt3);
    060		}
    061				//
    062				// ボタンがクリックされたときの処理
    063				//
    064		public void actionPerformed(ActionEvent e)
    065		{
    066						// 遊び方
    067			if (e.getSource() == bt1) {
    068				Method db = new Method();
    069				db.setVisible(true);
    070				requestFocusInWindow();
    071			}
    072						// 実行
    073			else if (e.getSource() == bt2) {
    074				for (int i1 = 0; i1 < 9; i1++) {
    075					for (int i2 = 0; i2 < 3; i2++) {
    076						for (int i3 = 0; i3 < 3; i3++) {
    077							String str = bd[i1].tx[i2][i3].getText();
    078							if (str.length() > 0) {
    079								int k;
    080								try {
    081									k = Integer.parseInt(str);
    082									if (k == 0)
    083										k = -1;
    084								}
    085								catch (NumberFormatException ne)
    086								{
    087									k = -1;
    088								}
    089								mp.pr[i1][i2][i3] = k;
    090								mp.wk[i1][i2][i3] = 1;
    091							}
    092							else {
    093								mp.pr[i1][i2][i3] = 0;
    094								mp.wk[i1][i2][i3] = 0;
    095							}
    096						}
    097					}
    098				}
    099				boolean sw = check();
    100				if (sw)
    101					mp.state = 1;
    102			}
    103						// 問題
    104			else {
    105				Problem win = new Problem("問題作成", bd);
    106			}
    107		}
    108				//
    109				// データは適切か?
    110				//
    111		boolean check()
    112		{
    113			boolean sw = true;
    114						// 数字の適切性
    115			for (int i1 = 0; i1 < 9; i1++) {
    116				for (int i2 = 0; i2 < 3; i2++) {
    117					for (int i3 = 0; i3 < 3; i3++) {
    118						bd[i1].tx[i2][i3].setForeground(Color.black);
    119						if (mp.pr[i1][i2][i3] < 0 || mp.pr[i1][i2][i3] > 9) {
    120							sw = false;
    121							bd[i1].tx[i2][i3].setForeground(Color.red);
    122						}
    123					}
    124				}
    125			}
    126						// ブロック内
    127			if (sw) {
    128				for (int i1 = 0; i1 < 9; i1++) {
    129					for (int i2 = 0; i2 < 8; i2++) {
    130						int k1 = mp.pr[i1][i2/3][i2%3];
    131						if (k1 > 0) {
    132							for (int i3 = i2+1; i3 < 9; i3++) {
    133								int k2 = mp.pr[i1][i3/3][i3%3];
    134								if (k1 == k2) {
    135									sw = false;
    136									bd[i1].tx[i2/3][i2%3].setForeground(Color.red);
    137									bd[i1].tx[i3/3][i3%3].setForeground(Color.red);
    138								}
    139							}
    140						}
    141					}
    142				}
    143			}
    144						// 行内
    145			if (sw) {
    146				for (int i1 = 0; i1 < 9; i1++) {
    147					for (int i2 = 0; i2 < 8; i2++) {
    148						int k1 = mp.pr[i1/3*3+i2/3][i1%3][i2%3];
    149						if (k1 > 0) {
    150							for (int i3 = i2+1; i3 < 9; i3++) {
    151								int k2 = mp.pr[i1/3*3+i3/3][i1%3][i3%3];
    152								if (k1 == k2) {
    153									sw = false;
    154									bd[i1/3*3+i2/3].tx[i1%3][i2%3].setForeground(Color.red);
    155									bd[i1/3*3+i3/3].tx[i1%3][i3%3].setForeground(Color.red);
    156								}
    157							}
    158						}
    159					}
    160				}
    161			}
    162						// 列内
    163			if (sw) {
    164				for (int i1 = 0; i1 < 9; i1++) {
    165					for (int i2 = 0; i2 < 8; i2++) {
    166						int k1 = mp.pr[i1/3+i2/3*3][i2%3][i1%3];
    167						if (k1 > 0) {
    168							for (int i3 = i2+1; i3 < 9; i3++) {
    169								int k2 = mp.pr[i1/3+i3/3*3][i3%3][i1%3];
    170								if (k1 == k2) {
    171									sw = false;
    172									bd[i1/3+i2/3*3].tx[i2%3][i1%3].setForeground(Color.red);
    173									bd[i1/3+i3/3*3].tx[i3%3][i1%3].setForeground(Color.red);
    174								}
    175							}
    176						}
    177					}
    178				}
    179			}
    180	
    181			return sw;
    182		}
    183	}
    184	
    185	/******************/
    186	/* ゲームの遊び方 */
    187	/******************/
    188	class Method extends JDialog
    189	{
    190				// コンストラクタ
    191		Method()
    192		{
    193			setTitle("ゲームの遊び方");
    194					// ContetPain
    195			Container cp = getContentPane();
    196			cp.setLayout(new FlowLayout(FlowLayout.CENTER));
    197			cp.setBackground(new Color(220, 255, 220));   // 背景色
    198			Font f = new Font("MS 明朝", Font.PLAIN, 20);
    199			setSize(850, 370);
    200					// TextArea の追加
    201			JTextArea ta = new JTextArea(15, 80);
    202			ta.setFont(f);
    203			ta.setEditable(false);
    204			ta.setLineWrap(true);
    205			ta.setText("1.スタート画面\n");
    206			ta.append("\n");
    207			ta.append(" a.味方1: 配置したい位置をマウスでクリックした後,1 のキーを押せば( 3 のキーを押せばキャンセル)配置され,左方向に動き出す.敵と接触することによって敵を消滅させることができる.配置できる上限数のデフォルトは 10 である.\n");
    208			ta.append("\n");
    209			ta.append(" b.味方2: 配置したい位置をマウスでクリックした後,2 のキーを押せば( 3 のキーを押せばキャンセル)配置される.その位置に停止したままであるが,その近傍を通過した敵を消滅させることができる.配置できる上限数のデフォルトは 4 である.\n");
    210			ta.append("\n");
    211			ta.append("2.ゲーム画面\n");
    212			ta.append("\n");
    213			ta.append(" a.最初から決まっている数字に対しては,背景色が薄い緑になり,修正することができません.それ以外の空いている箇所に数字を入力し,「return」キーをクリックすることによって数字を設定していきます.( step1 )\n");
    214			ta.append("\n");
    215			ta.append(" b.数字を入力する際,ルール違反が起こっている場合は,数字色が赤になります.( step2 )\n");
    216			ta.append("\n");
    217			ta.append(" c.1 ~ 9 までの番号が付けられているボタン(「数字」ボタンと呼ぶ)の下にあるテキストフィールドには,各数字に対する出現回数が表示されます.この値が 9 になるとテキストフィールドの背景色が赤になります.上に示すゲーム版の数字色がすべて黒で,かつ,すべてのテキストフィールドの背景色が赤になった時点でゲーム終了となります.( step3 )\n");
    218			ta.append("\n");
    219			ta.append(" d.「数字」ボタンをクリックすると,ゲーム版の対応する数字を入力可能な場所の背景色がピンクになります.( step4 )\n");
    220			ta.append("\n");
    221			ta.append(" e.「記憶」ボタンをクリックするとことによって,現在の状態を記憶することが出来ます.また,「戻る」ボタンをクリックすることによって,最後に記憶した状態まで順番に戻すことが出来ます.なお,「記憶」ボタンは,「return」キーによって入力する数字を確定した後にクリックして下さい.( step5 )\n");
    222			JScrollPane scroll = new JScrollPane(ta);
    223			cp.add(scroll);
    224					// Window を閉じるため
    225			addWindowListener(new WinEnd());
    226		}
    227					// 終了処理
    228		class WinEnd extends WindowAdapter
    229		{
    230			public void windowClosing(WindowEvent e) {
    231				setVisible(false);
    232			}
    233		}
    234	}
    			

      最後に示すのは,GamePanel クラスの修正内容です.

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import main.*;
    007	
    008	public class GamePanel extends JPanel implements ActionListener
    009	{
    010		MainPanel mp;
    011		Bord bd[] = new Bord [9];
    012		JTextField ct[] = new JTextField [9];
    013		JButton next, end, num_b[] = new JButton [9];
    014				//
    015				// コンストラクタ
    016				//
    017		public GamePanel(Dimension size, MainPanel mp1)
    018		{
    019			mp = mp1;
    020						// レイアウトマネージャの停止
    021			setLayout(null);
    022						// 背景色の設定
    023			setBackground(new Color(220, 255, 220));   // 背景色
    024						// パネルの配置
    025			JPanel jp = new JPanel();
    026			jp.setSize(size.width, size.width);
    027			jp.setLocation(0, 0);
    028			jp.setBackground(Color.green);
    029			add(jp);
    030								// グリッドレイアウト
    031			jp.setLayout(new GridLayout(3, 3, 5, 5));
    032								// ボードの配置
    033			for (int i1 = 0; i1 < 9; i1++) {
    034				bd[i1] = new Bord(i1, mp);
    035				for (int i2 = 0; i2 < 3; i2++) {
    036					for (int i3 = 0; i3 < 3; i3++)
    037						bd[i1].tx[i2][i3].addActionListener(this);
    038				}
    039				jp.add(bd[i1]);
    040			}
    041						// 数字ボタンの配置
    042			JPanel num = new JPanel();
    043			num.setBackground(new Color(220, 255, 220));
    044			num.setSize(size.width, 30);
    045			num.setLocation(0, 500);
    046			add(num);
    047			num.setLayout(new GridLayout(1, 9, 5, 5));
    048			for (int i1 = 0; i1 < 9; i1++) {
    049				num_b[i1] = new JButton(Integer.toString(i1+1));
    050				num_b[i1].setFont(new Font("SansSerif", Font.BOLD, 20));
    051				num_b[i1].addActionListener(this);
    052				num.add(num_b[i1]);
    053			}
    054						// カウンタフィールドの配置
    055			JPanel count_f = new JPanel();
    056			count_f.setBackground(new Color(220, 255, 220));
    057			count_f.setSize(size.width, 30);
    058			count_f.setLocation(0, 530);
    059			add(count_f);
    060			count_f.setLayout(new GridLayout(1, 9, 5, 5));
    061			for (int i1 = 0; i1 < 9; i1++) {
    062				ct[i1] = new JTextField();
    063				ct[i1].setFont(new Font("SansSerif", Font.BOLD, 20));
    064				ct[i1].setBackground(Color.white);
    065				ct[i1].setHorizontalAlignment(JTextField.CENTER);
    066				ct[i1].setEditable(false);
    067				count_f.add(ct[i1]);
    068			}
    069						// 「次の問題」ボタンの配置
    070			Font f = new Font("SansSerif", Font.BOLD, 20);
    071			next = new JButton("次の問題");
    072			next.setFont(f);
    073			next.setSize(120, 40);
    074			next.setLocation(140, 560);
    075			next.addActionListener(this);
    076			add(next);
    077						// 「終了」ボタンの配置
    078			end = new JButton("終了");
    079			end.setFont(f);
    080			end.setSize(80, 40);
    081			end.setLocation(280, 560);
    082			end.addActionListener(this);
    083			add(end);
    084						// 数字の数を数える
    085			count();
    086		}
    087				//
    088				// ボタンがクリックされたときの処理
    089				//
    090		public void actionPerformed(ActionEvent e)
    091		{
    092						// 「終了」ボタン
    093			if (e.getSource() == end)
    094				mp.state = 2;
    095						// 「次の問題」ボタン
    096			else if (e.getSource() == next)
    097				mp.state = 0;
    098						// 数字ボタン
    099			else if (e.getSource() == num_b[0] || e.getSource() == num_b[1] || e.getSource() == num_b[2] || e.getSource() == num_b[3] || e.getSource() == num_b[4] || e.getSource() == num_b[5] || e.getSource() == num_b[6] || e.getSource() == num_b[7] || e.getSource() == num_b[8]) {
    100				for (int i1 = 0; i1 < 9; i1++)
    101					num_b[i1].setForeground(Color.black);
    102				int k;
    103				if (e.getSource() == num_b[0])
    104					k = 1;
    105				else if (e.getSource() == num_b[1])
    106					k = 2;
    107				else if (e.getSource() == num_b[2])
    108					k = 3;
    109				else if (e.getSource() == num_b[3])
    110					k = 4;
    111				else if (e.getSource() == num_b[4])
    112					k = 5;
    113				else if (e.getSource() == num_b[5])
    114					k = 6;
    115				else if (e.getSource() == num_b[6])
    116					k = 7;
    117				else if (e.getSource() == num_b[7])
    118					k = 8;
    119				else
    120					k = 9;
    121				num_b[k-1].setForeground(Color.green);
    122				safe(k);
    123			}
    124						// 数字が入力されたとき
    125			else {
    126				int k;
    127				for (int i1 = 0; i1 < 9; i1++) {
    128					for (int i2 = 0; i2 < 3; i2++) {
    129						for (int i3 = 0; i3 < 3; i3++) {
    130							String str = bd[i1].tx[i2][i3].getText();
    131							if (str.length() > 0) {
    132								try {
    133									k = Integer.parseInt(str);
    134									if (k == 0)
    135										k = -1;
    136								}
    137								catch (NumberFormatException ne)
    138								{
    139									k = -1;
    140								}
    141							}
    142							else
    143								k = 0;
    144							mp.pr[i1][i2][i3] = k;
    145						}
    146					}
    147				}
    148				boolean sw = check();
    149				if (sw)
    150					count();
    151			}
    152		}
    153				//
    154				// データは適切か?
    155				//
    156		boolean check()
    157		{
    158			boolean sw = true;
    159						// 背景色のクリア
    160			for (int i1 = 0; i1 < 9; i1++) {
    161				for (int i2 = 0; i2 < 3; i2++) {
    162					for (int i3 = 0; i3 < 3; i3++) {
    163						if (mp.wk[i1][i2][i3] == 0)
    164							bd[i1].tx[i2][i3].setBackground(Color.white);
    165					}
    166				}
    167			}
    168						// 数字の適切性
    169			for (int i1 = 0; i1 < 9; i1++) {
    170				for (int i2 = 0; i2 < 3; i2++) {
    171					for (int i3 = 0; i3 < 3; i3++) {
    172						bd[i1].tx[i2][i3].setForeground(Color.black);
    173						if (mp.pr[i1][i2][i3] < 0 || mp.pr[i1][i2][i3] > 9) {
    174							sw = false;
    175							bd[i1].tx[i2][i3].setForeground(Color.red);
    176						}
    177					}
    178				}
    179			}
    180						// ブロック内
    181			if (sw) {
    182				for (int i1 = 0; i1 < 9; i1++) {
    183					for (int i2 = 0; i2 < 8; i2++) {
    184						int k1 = mp.pr[i1][i2/3][i2%3];
    185						if (k1 > 0) {
    186							for (int i3 = i2+1; i3 < 9; i3++) {
    187								int k2 = mp.pr[i1][i3/3][i3%3];
    188								if (k1 == k2) {
    189									sw = false;
    190									bd[i1].tx[i2/3][i2%3].setForeground(Color.red);
    191									bd[i1].tx[i3/3][i3%3].setForeground(Color.red);
    192								}
    193							}
    194						}
    195					}
    196				}
    197			}
    198						// 行内
    199			if (sw) {
    200				for (int i1 = 0; i1 < 9; i1++) {
    201					for (int i2 = 0; i2 < 8; i2++) {
    202						int k1 = mp.pr[i1/3*3+i2/3][i1%3][i2%3];
    203						if (k1 > 0) {
    204							for (int i3 = i2+1; i3 < 9; i3++) {
    205								int k2 = mp.pr[i1/3*3+i3/3][i1%3][i3%3];
    206								if (k1 == k2) {
    207									sw = false;
    208									bd[i1/3*3+i2/3].tx[i1%3][i2%3].setForeground(Color.red);
    209									bd[i1/3*3+i3/3].tx[i1%3][i3%3].setForeground(Color.red);
    210								}
    211							}
    212						}
    213					}
    214				}
    215			}
    216						// 列内
    217			if (sw) {
    218				for (int i1 = 0; i1 < 9; i1++) {
    219					for (int i2 = 0; i2 < 8; i2++) {
    220						int k1 = mp.pr[i1/3+i2/3*3][i2%3][i1%3];
    221						if (k1 > 0) {
    222							for (int i3 = i2+1; i3 < 9; i3++) {
    223								int k2 = mp.pr[i1/3+i3/3*3][i3%3][i1%3];
    224								if (k1 == k2) {
    225									sw = false;
    226									bd[i1/3+i2/3*3].tx[i2%3][i1%3].setForeground(Color.red);
    227									bd[i1/3+i3/3*3].tx[i3%3][i1%3].setForeground(Color.red);
    228								}
    229							}
    230						}
    231					}
    232				}
    233			}
    234	
    235			return sw;
    236		}
    237				//
    238				// 数字の数を数える
    239				//
    240		void count()
    241		{
    242			for (int i1 = 0; i1 < 9; i1++) {
    243				int k1 = i1 + 1;
    244				int n = 0;
    245				for (int i2 = 0; i2 < 9; i2++) {
    246					for (int i3 = 0; i3 < 3; i3++) {
    247						for (int i4 = 0; i4 < 3; i4++) {
    248							if (mp.pr[i2][i3][i4] == k1)
    249								n++;
    250						}
    251					}
    252				}
    253				ct[i1].setText(Integer.toString(n));
    254				if (n == 9)
    255					ct[i1].setBackground(Color.red);
    256				else
    257					ct[i1].setBackground(Color.white);
    258			}
    259		}
    260				//
    261				// 数字がおける場所
    262				//
    263		void safe(int k)
    264		{
    265						// ブロック内
    266			for (int i1 = 0; i1 < 9; i1++) {
    267				boolean sw = false;
    268				for (int i2 = 0; i2 < 3; i2++) {
    269					for (int i3 = 0; i3 < 3; i3++) {
    270						if (mp.wk[i1][i2][i3] == 2)
    271							mp.wk[i1][i2][i3] = 0;
    272						else if (mp.wk[i1][i2][i3] == 0)
    273							bd[i1].tx[i2][i3].setBackground(Color.white);
    274						if (mp.pr[i1][i2][i3] == k)
    275							sw = true;
    276					}
    277				}
    278				for (int i2 = 0; i2 < 3; i2++) {
    279					for (int i3 = 0; i3 < 3; i3++) {
    280						if (mp.wk[i1][i2][i3] == 0 && (mp.pr[i1][i2][i3] > 0 || sw))
    281							mp.wk[i1][i2][i3] = 2;
    282					}
    283				}
    284			}
    285						// 行内
    286			for (int i1 = 0; i1 < 9; i1++) {
    287				for (int i2 = 0; i2 < 9; i2++) {
    288					int k1 = mp.pr[i1/3*3+i2/3][i1%3][i2%3];
    289					if (k1 == k) {
    290						for (int i3 = 0; i3 < 9; i3++) {
    291							if (mp.wk[i1/3*3+i3/3][i1%3][i3%3] == 0)
    292								mp.wk[i1/3*3+i3/3][i1%3][i3%3] = 2;
    293						}
    294						break;
    295					}
    296				}
    297			}
    298						// 列内
    299			for (int i1 = 0; i1 < 9; i1++) {
    300				for (int i2 = 0; i2 < 9; i2++) {
    301					int k1 = mp.pr[i1/3+i2/3*3][i2%3][i1%3];
    302					if (k1 == k) {
    303						for (int i3 = 0; i3 < 9; i3++) {
    304							if (mp.wk[i1/3+i3/3*3][i3%3][i1%3] == 0)
    305								mp.wk[i1/3+i3/3*3][i3%3][i1%3] = 2;
    306						}
    307						break;
    308					}
    309				}
    310			}
    311						// 背景色の変更
    312			for (int i1 = 0; i1 < 9; i1++) {
    313				for (int i2 = 0; i2 < 3; i2++) {
    314					for (int i3 = 0; i3 < 3; i3++) {
    315						if (mp.wk[i1][i2][i3] == 0)
    316							bd[i1].tx[i2][i3].setBackground(new Color(255, 215, 0));
    317					}
    318				}
    319			}
    320		}
    321	}
    			
    051 行目

      「数字」ボタンに対してアクションリスナを追加しています.

    099 行目~ 123 行目

      「数字」ボタンをクリックした時の処理です.クリックされたボタンを特定した後,そのボタンの文字色を緑にし( 121 行目),その数字を入力可能な場所を表示するためのメソッド safe を呼んでいます( 122 行目).

    160 行目~ 167 行目

      後に述べるように,「数字」ボタンがクリックされると,指定された数字を入力できる場所にあるテキストフィールドの背景色はピンクになりますが,ここでは,それを元の白に戻しています.ただし,配列 wk の値が 1 や 2 である場所は,既に数字が入力されている場所ですので,その場所の背景色はそのままにしておきます.

    263 行目~ 320 行目

      指定された数字( k )を入力できる場所を表示するためのメソッドです.基本的に,指定された数字を入力できない場所に対応する配列 wk の値を 2 に設定し,wk の値が 0 である場所の背景色をピンク( RGB : 0xFFD700 )に設定しています.

      まず,266 行目~ 284 行目では,各ブロック内を調べています.ブロック内に指定された数字がある場合,または,その場所に既に他の数字が入力されている場合は,ブロック内または数字が存在する場所に対応する配列 wk の値を 2 に設定しています.なお,270 行目~ 273 行目は,前回の探索結果をクリアするための処理です.

      286 行目~ 297 行目( 299 行目~ 310 行目)では,各行(各列)を調べています.行内(列内)に指定された数字がある場合,または,その場所に既に他の数字が入力されている場合は,行内(列内)または数字が存在する場所に対応する配列 wk の値を 2 に設定しています.

      312 行目~ 319 行目では,配列 wk の値が 0 である場所(テキストフィールド)の背景色をピンクに設定しています.

  5. ステップ5: 状態の記憶と復元

      ゲーム実行中,任意の状態を記憶し,再びその状態に戻ることが出来る機能を追加します.修正するプログラムは,game パッケージ内の GamePanel クラス( GamePanel.java )だけです.

    001	package game;
    002	
    003	import java.awt.*;
    004	import java.awt.event.*;
    005	import javax.swing.*;
    006	import java.util.*;
    007	import main.*;
    008	
    009	public class GamePanel extends JPanel implements ActionListener
    010	{
    011		MainPanel mp;
    012		Bord bd[] = new Bord [9];
    013		JTextField ct[] = new JTextField [9];
    014		JButton memory, back, next, end, num_b[] = new JButton [9];
    015		Stack <int[][][]> kp = new Stack <int[][][]> ();
    016				//
    017				// コンストラクタ
    018				//
    019		public GamePanel(Dimension size, MainPanel mp1)
    020		{
    021			mp = mp1;
    022						// レイアウトマネージャの停止
    023			setLayout(null);
    024						// 背景色の設定
    025			setBackground(new Color(220, 255, 220));   // 背景色
    026						// パネルの配置
    027			JPanel jp = new JPanel();
    028			jp.setSize(size.width, size.width);
    029			jp.setLocation(0, 0);
    030			jp.setBackground(Color.green);
    031			add(jp);
    032								// グリッドレイアウト
    033			jp.setLayout(new GridLayout(3, 3, 5, 5));
    034								// ボードの配置
    035			for (int i1 = 0; i1 < 9; i1++) {
    036				bd[i1] = new Bord(i1, mp);
    037				for (int i2 = 0; i2 < 3; i2++) {
    038					for (int i3 = 0; i3 < 3; i3++)
    039						bd[i1].tx[i2][i3].addActionListener(this);
    040				}
    041				jp.add(bd[i1]);
    042			}
    043						// 数字ボタンの配置
    044			JPanel num = new JPanel();
    045			num.setBackground(new Color(220, 255, 220));
    046			num.setSize(size.width, 30);
    047			num.setLocation(0, 500);
    048			add(num);
    049			num.setLayout(new GridLayout(1, 9, 5, 5));
    050			for (int i1 = 0; i1 < 9; i1++) {
    051				num_b[i1] = new JButton(Integer.toString(i1+1));
    052				num_b[i1].setFont(new Font("SansSerif", Font.BOLD, 20));
    053				num_b[i1].addActionListener(this);
    054				num.add(num_b[i1]);
    055			}
    056						// カウンタフィールドの配置
    057			JPanel count_f = new JPanel();
    058			count_f.setBackground(new Color(220, 255, 220));
    059			count_f.setSize(size.width, 30);
    060			count_f.setLocation(0, 530);
    061			add(count_f);
    062			count_f.setLayout(new GridLayout(1, 9, 5, 5));
    063			for (int i1 = 0; i1 < 9; i1++) {
    064				ct[i1] = new JTextField();
    065				ct[i1].setFont(new Font("SansSerif", Font.BOLD, 20));
    066				ct[i1].setBackground(Color.white);
    067				ct[i1].setHorizontalAlignment(JTextField.CENTER);
    068				ct[i1].setEditable(false);
    069				count_f.add(ct[i1]);
    070			}
    071						// 「記憶」ボタンの配置
    072			Font f = new Font("SansSerif", Font.BOLD, 20);
    073			memory = new JButton("記憶");
    074			memory.setFont(f);
    075			memory.setSize(80, 40);
    076			memory.setLocation(50, 560);
    077			memory.addActionListener(this);
    078			add(memory);
    079						// 「戻る」ボタンの配置
    080			back = new JButton("戻る");
    081			back.setFont(f);
    082			back.setSize(80, 40);
    083			back.setLocation(140, 560);
    084			back.addActionListener(this);
    085			back.setVisible(false);
    086			add(back);
    087						// 「次の問題」ボタンの配置
    088			next = new JButton("次の問題");
    089			next.setFont(f);
    090			next.setSize(120, 40);
    091			next.setLocation(230, 560);
    092			next.addActionListener(this);
    093			add(next);
    094						// 「終了」ボタンの配置
    095			end = new JButton("終了");
    096			end.setFont(f);
    097			end.setSize(80, 40);
    098			end.setLocation(360, 560);
    099			end.addActionListener(this);
    100			add(end);
    101						// 数字の数を数える
    102			count();
    103		}
    104				//
    105				// ボタンがクリックされたときの処理
    106				//
    107		public void actionPerformed(ActionEvent e)
    108		{
    109						// 「記憶」ボタン
    110			if (e.getSource() == memory) {
    111				back.setVisible(true);
    112				int state [][][] = new int [9][3][3];
    113				for (int i1 = 0; i1 < 9; i1++) {
    114					for (int i2 = 0; i2 < 3; i2++) {
    115						for (int i3 = 0; i3 < 3; i3++)
    116							state[i1][i2][i3] = mp.pr[i1][i2][i3];
    117					}
    118				}
    119				kp.push(state);
    120				Message msg = new Message(kp.size());
    121				msg.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
    122				msg.setLocation(0, 550);
    123				msg.setVisible(true);
    124			}
    125						// 「戻る」ボタン
    126			else if (e.getSource() == back) {
    127				mp.pr = kp.pop();
    128				if (kp.size() == 0)
    129					back.setVisible(false);
    130				for (int i1 = 0; i1 < 9; i1++) {
    131					for (int i2 = 0; i2 < 3; i2++) {
    132						for (int i3 = 0; i3 < 3; i3++) {
    133							if (mp.pr[i1][i2][i3] != 0)
    134								bd[i1].tx[i2][i3].setText(Integer.toString(mp.pr[i1][i2][i3]));
    135							else
    136								bd[i1].tx[i2][i3].setText("");
    137						}
    138					}
    139				}
    140				boolean sw = check();
    141				if (sw)
    142					count();
    143			}
    144						// 「終了」ボタン
    145			else if (e.getSource() == end)
    146				mp.state = 2;
    147						// 「次の問題」ボタン
    148			else if (e.getSource() == next)
    149				mp.state = 0;
    150						// 数字ボタン
    151			else if (e.getSource() == num_b[0] || e.getSource() == num_b[1] || e.getSource() == num_b[2] || e.getSource() == num_b[3] || e.getSource() == num_b[4] || e.getSource() == num_b[5] || e.getSource() == num_b[6] || e.getSource() == num_b[7] || e.getSource() == num_b[8]) {
    152				for (int i1 = 0; i1 < 9; i1++)
    153					num_b[i1].setForeground(Color.black);
    154				int k;
    155				if (e.getSource() == num_b[0])
    156					k = 1;
    157				else if (e.getSource() == num_b[1])
    158					k = 2;
    159				else if (e.getSource() == num_b[2])
    160					k = 3;
    161				else if (e.getSource() == num_b[3])
    162					k = 4;
    163				else if (e.getSource() == num_b[4])
    164					k = 5;
    165				else if (e.getSource() == num_b[5])
    166					k = 6;
    167				else if (e.getSource() == num_b[6])
    168					k = 7;
    169				else if (e.getSource() == num_b[7])
    170					k = 8;
    171				else
    172					k = 9;
    173				num_b[k-1].setForeground(Color.green);
    174				safe(k);
    175			}
    176						// 数字が入力されたとき
    177			else {
    178				int k;
    179				for (int i1 = 0; i1 < 9; i1++) {
    180					for (int i2 = 0; i2 < 3; i2++) {
    181						for (int i3 = 0; i3 < 3; i3++) {
    182							String str = bd[i1].tx[i2][i3].getText();
    183							if (str.length() > 0) {
    184								try {
    185									k = Integer.parseInt(str);
    186									if (k == 0)
    187										k = -1;
    188								}
    189								catch (NumberFormatException ne)
    190								{
    191									k = -1;
    192								}
    193							}
    194							else
    195								k = 0;
    196							mp.pr[i1][i2][i3] = k;
    197						}
    198					}
    199				}
    200				boolean sw = check();
    201				if (sw)
    202					count();
    203			}
    204		}
    205				//
    206				// データは適切か?
    207				//
    208		boolean check()
    209		{
    210			boolean sw = true;
    211						// 背景色のクリア
    212			for (int i1 = 0; i1 < 9; i1++) {
    213				for (int i2 = 0; i2 < 3; i2++) {
    214					for (int i3 = 0; i3 < 3; i3++) {
    215						if (mp.wk[i1][i2][i3] == 0)
    216							bd[i1].tx[i2][i3].setBackground(Color.white);
    217					}
    218				}
    219			}
    220						// 数字の適切性
    221			for (int i1 = 0; i1 < 9; i1++) {
    222				for (int i2 = 0; i2 < 3; i2++) {
    223					for (int i3 = 0; i3 < 3; i3++) {
    224						bd[i1].tx[i2][i3].setForeground(Color.black);
    225						if (mp.pr[i1][i2][i3] < 0 || mp.pr[i1][i2][i3] > 9) {
    226							sw = false;
    227							bd[i1].tx[i2][i3].setForeground(Color.red);
    228						}
    229					}
    230				}
    231			}
    232						// ブロック内
    233			if (sw) {
    234				for (int i1 = 0; i1 < 9; i1++) {
    235					for (int i2 = 0; i2 < 8; i2++) {
    236						int k1 = mp.pr[i1][i2/3][i2%3];
    237						if (k1 > 0) {
    238							for (int i3 = i2+1; i3 < 9; i3++) {
    239								int k2 = mp.pr[i1][i3/3][i3%3];
    240								if (k1 == k2) {
    241									sw = false;
    242									bd[i1].tx[i2/3][i2%3].setForeground(Color.red);
    243									bd[i1].tx[i3/3][i3%3].setForeground(Color.red);
    244								}
    245							}
    246						}
    247					}
    248				}
    249			}
    250						// 行内
    251			if (sw) {
    252				for (int i1 = 0; i1 < 9; i1++) {
    253					for (int i2 = 0; i2 < 8; i2++) {
    254						int k1 = mp.pr[i1/3*3+i2/3][i1%3][i2%3];
    255						if (k1 > 0) {
    256							for (int i3 = i2+1; i3 < 9; i3++) {
    257								int k2 = mp.pr[i1/3*3+i3/3][i1%3][i3%3];
    258								if (k1 == k2) {
    259									sw = false;
    260									bd[i1/3*3+i2/3].tx[i1%3][i2%3].setForeground(Color.red);
    261									bd[i1/3*3+i3/3].tx[i1%3][i3%3].setForeground(Color.red);
    262								}
    263							}
    264						}
    265					}
    266				}
    267			}
    268						// 列内
    269			if (sw) {
    270				for (int i1 = 0; i1 < 9; i1++) {
    271					for (int i2 = 0; i2 < 8; i2++) {
    272						int k1 = mp.pr[i1/3+i2/3*3][i2%3][i1%3];
    273						if (k1 > 0) {
    274							for (int i3 = i2+1; i3 < 9; i3++) {
    275								int k2 = mp.pr[i1/3+i3/3*3][i3%3][i1%3];
    276								if (k1 == k2) {
    277									sw = false;
    278									bd[i1/3+i2/3*3].tx[i2%3][i1%3].setForeground(Color.red);
    279									bd[i1/3+i3/3*3].tx[i3%3][i1%3].setForeground(Color.red);
    280								}
    281							}
    282						}
    283					}
    284				}
    285			}
    286	
    287			return sw;
    288		}
    289				//
    290				// 数字の数を数える
    291				//
    292		void count()
    293		{
    294			for (int i1 = 0; i1 < 9; i1++) {
    295				int k1 = i1 + 1;
    296				int n = 0;
    297				for (int i2 = 0; i2 < 9; i2++) {
    298					for (int i3 = 0; i3 < 3; i3++) {
    299						for (int i4 = 0; i4 < 3; i4++) {
    300							if (mp.pr[i2][i3][i4] == k1)
    301								n++;
    302						}
    303					}
    304				}
    305				ct[i1].setText(Integer.toString(n));
    306				if (n == 9)
    307					ct[i1].setBackground(Color.red);
    308				else
    309					ct[i1].setBackground(Color.white);
    310			}
    311		}
    312				//
    313				// 数字がおける場所
    314				//
    315		void safe(int k)
    316		{
    317						// ブロック内
    318			for (int i1 = 0; i1 < 9; i1++) {
    319				boolean sw = false;
    320				for (int i2 = 0; i2 < 3; i2++) {
    321					for (int i3 = 0; i3 < 3; i3++) {
    322						if (mp.wk[i1][i2][i3] == 2)
    323							mp.wk[i1][i2][i3] = 0;
    324						else if (mp.wk[i1][i2][i3] == 0)
    325							bd[i1].tx[i2][i3].setBackground(Color.white);
    326						if (mp.pr[i1][i2][i3] == k)
    327							sw = true;
    328					}
    329				}
    330				for (int i2 = 0; i2 < 3; i2++) {
    331					for (int i3 = 0; i3 < 3; i3++) {
    332						if (mp.wk[i1][i2][i3] == 0 && (mp.pr[i1][i2][i3] > 0 || sw))
    333							mp.wk[i1][i2][i3] = 2;
    334					}
    335				}
    336			}
    337						// 行内
    338			for (int i1 = 0; i1 < 9; i1++) {
    339				for (int i2 = 0; i2 < 9; i2++) {
    340					int k1 = mp.pr[i1/3*3+i2/3][i1%3][i2%3];
    341					if (k1 == k) {
    342						for (int i3 = 0; i3 < 9; i3++) {
    343							if (mp.wk[i1/3*3+i3/3][i1%3][i3%3] == 0)
    344								mp.wk[i1/3*3+i3/3][i1%3][i3%3] = 2;
    345						}
    346						break;
    347					}
    348				}
    349			}
    350						// 列内
    351			for (int i1 = 0; i1 < 9; i1++) {
    352				for (int i2 = 0; i2 < 9; i2++) {
    353					int k1 = mp.pr[i1/3+i2/3*3][i2%3][i1%3];
    354					if (k1 == k) {
    355						for (int i3 = 0; i3 < 9; i3++) {
    356							if (mp.wk[i1/3+i3/3*3][i3%3][i1%3] == 0)
    357								mp.wk[i1/3+i3/3*3][i3%3][i1%3] = 2;
    358						}
    359						break;
    360					}
    361				}
    362			}
    363						// 背景色の変更
    364			for (int i1 = 0; i1 < 9; i1++) {
    365				for (int i2 = 0; i2 < 3; i2++) {
    366					for (int i3 = 0; i3 < 3; i3++) {
    367						if (mp.wk[i1][i2][i3] == 0)
    368							bd[i1].tx[i2][i3].setBackground(new Color(255, 215, 0));
    369					}
    370				}
    371			}
    372		}
    373	}
    374	
    375	/******************/
    376	/* Message クラス */
    377	/******************/
    378	class Message extends JDialog {
    379						// コンストラクタ
    380		Message(int ct)
    381		{
    382			super();
    383	
    384			Container cp = getContentPane();
    385			cp.setLayout(new GridLayout(1, 1, 5, 5));
    386			setSize(200, 100);
    387	
    388			JTextArea ta = new JTextArea(2, 25);
    389			ta.setFont(new Font("SansSerif", Font.BOLD, 20));
    390			ta.setBackground(Color.white);
    391			ta.setEditable(false);
    392			ta.setText("状態を記憶しました\n 記憶状態数 = " + ct);
    393			cp.add(ta);
    394	
    395			addWindowListener(new WinEnd());
    396		}
    397						// 終了処理
    398		class WinEnd extends WindowAdapter
    399		{
    400			public void windowClosing(WindowEvent e) {
    401				setVisible(false);
    402			}
    403		}
    404	}
    			
    006 行目

      Stack クラスを利用するために必要です.

    014 行目

      「記憶」ボタン及び「戻る」ボタンに対する変数を追加しています.

    015 行目

      状態をスタックに記憶するため,Stack クラスのオブジェクトを生成しています.

    072 行目~ 086 行目,091 行目,098 行目

      「記憶」ボタンと「戻る」ボタンを追加すると共に,他のボタンの表示位置を変更しています.

    110 行目~ 124 行目

      「記憶」ボタンがクリックされたときの処理です.「戻る」ボタンを表示し( 111 行目).次に,現在の状態を表す配列 pr を配列 state にコピーして,その state をスタック kp に記憶( 112 行目~ 119 行目)した後,記憶したというメッセージダイアログとして出力しています( 120 行目~ 123 行目).119 行目の代わりに,kp.push(mp.pr) のように,スタックに配列 pr を直接保存する訳にはいかない点に注意してください.なぜなら,pr は配列を指すポインタ(配列のアドレス)ですので,同じものをスタックに積み上げることになってしまいます.

    126 行目~ 143 行目

      「戻る」ボタンをクリックした時の処理であり,最後に記憶された状態に戻します.現在の状態を表す配列 pr に最後に記憶された状態を設定し( 127 行目),スタックが空になった場合は,「戻る」ボタンを非表示にしています( 128 行目~ 129 行目).その後,pr の値に基づき,画面の表示を再設定しています( 130 行目~ 139 行目).その後,ルール違反のチェックと数字のカウントを行っています.

    378 行目~ 404 行目

      記憶したというメッセージを出力するためのダイアログを定義しているクラスです( JDialog クラスを継承).

情報学部 菅沼ホーム Java 目次 索引