オブジェクト指向でないコードで書いた15パズルの完成

2020年11月12日(木)
松本 吉生(まつもと よしお)
連載2回目となる今回は、オブジェクト指向を意識せずに書いた15パズルプログラムを完成させます。

ボタンを動かすコードを書く

前回は15パズルのデザインを作成しました。今回は実際にパズルを実行するプログラムを書きましょう。このパズルに必要な機能はいくつかありますが、まず「タイルを動かす」プログラムから作っていくことにします。

15パズルのデザインを表示している「デザイナ」で「12」のボタンをダブルクリックします。すると次のようなコードができます。

リスト1:ボタンをダブルクリックすると表示されるコード

1private void button12_Click(object sender, EventArgs e)
2{
3 
4}

ここにコードを書くと、「12」のボタンであるbutton12をクリックしたときに実行することができます。画面(その上にあるボタンを含みます)をクリックすることは「イベント」であり、その「クリックイベント」を捕捉するものが「イベントハンドラ」です。

初心者向けの書籍では、ここでできたコードを「イベントハンドラ」と呼ぶことが多いですが、厳密には「イベントハンドラによって実行されるメソッド」です。厳密な意味での「イベントハンドラ」のコード、クリックイベントを捕捉するコードはVisual Studioが自動的に作ってくれていて、表面上は見えなくなっています。

このようにVisual Studioにはプログラマーの操作により必要なコードを自動生成する機能があり、またそれをバックグラウンドに隠して見えなくし、肝心なコードだけを表示して開発しやすくする機能を持っているのです。

では、ボタンを動かすコードを書きましょう。この状態で「12」のボタンをクリックすると、空きスペースに移動することになりますが、実際はそこに見えない「16」のボタンがあるので、「16」のボタンと位置を交換することになります。ボタンの位置はプロパティのTopとLeftで決まります。一時的に値を保持する変数swapPosを作り、それぞれのTopとLeftを交換します。コードは次のようになります。

リスト2:ボタンを動かすためのコード

01private void button12_Click(object sender, EventArgs e)
02{
03    int swapPos;
04 
05    swapPos = button12.Top;
06    button12.Top = button16.Top;
07    button16.Top = swapPos;
08 
09    swapPos = button12.Left;
10    button12.Left = button16.Left;
11    button16.Left = swapPos;
12}

Visual Studioで緑の三角「開始」ボタンをクリックするか、「デバッグ」メニューの「デバッグの開始」でビルドしてプログラムを実行します。パズルが表示されたら「12」のタイルをクリックすると、タイルは空白の場所に移動します。

しかし、これだけでは困ります。タイルを動かすことができるのは、空白の場所である「16」のタイルと接している場合だけです。そこでタイルを動かす条件を決めます。タイルを動かせる条件を「横に接している場合」と「縦に接している場合」の2つの場合に分けて考えます。「横に接している場合」は、両方のタイルのTopの値が同じで、Leftの値の差が100です。「縦に接している場合」はタイルのLeftの値が同じで、Topの値の差が100です。それぞれ条件式にして、交換するコードを内側に記述します。コードは次のようになります。

リスト3:タイルを動かせることを確認してから動かす

01private void button12_Click(object sender, EventArgs e)
02{
03    int swapPos;
04 
05    if (button12.Left == button16.Left & (Math.Abs(button12.Top - button16.Top) == 100))
06    {
07        swapPos = button12.Top;
08        button12.Top = button16.Top;
09        button16.Top = swapPos;
10    }
11    if (button12.Top == button16.Top & (Math.Abs(button12.Left - button16.Left) == 100))
12    {
13        swapPos = button12.Left;
14        button12.Left = button16.Left;
15        button16.Left = swapPos;
16    }
17}

これでボタン「12」を正しい条件で動かすことができるようになりました。ほかのボタンも動かせるようにするには、同じコードを残りの14個のボタンに書かなければいけません。まずボタンをクリックしてイベントハンドラとイベントハンドラによって実行されるメソッドを自動的に作り、内側にコードを書きます。コードはコピーできますが、それぞれ対応するボタンの名前を変更しなければいけません。これは根気のいる繰り返し作業になります。またTopとLeftの値を入れ替えるときに使う一時的な変数swapPosの定義は、コードの先頭に移しておきましょう。次の位置です。

リスト4:コードの先頭でswapPos変数を定義

01namespace SampleProject
02{
03    public partial class Form1 : Form
04    {
05        public Form1()
06        {
07            InitializeComponent();
08        }
09        int swapPos;
10 
11        private void button1_Click(object sender, EventArgs e)
12        {
13(以下略)

完成を判定するコードを書く

次にパズルの完成を判定するコードを書きます。パズルの完成はどう判定すればよいでしょう。タイルの位置で判断すればよさそうです。ボタン「1」から「4」のTopの値は0、「5」から「8」のTopは100、のようにTopの値を判定し、ボタン「1」「5」「9」「13」はLeftの値が0、「2」「6」「10」「14」はLeftの値が100というように、すべてのボタンのTopとLeftの値を判定するメソッドを作ります。すべてのTopとLeftの値が判定できれば「Complete!」のメッセージボックスを表示することにしましょう。コードは次のようになります。

リスト5:パズルの完成を判定する

01private void complete()
02{
03    if(button1.Top == 0 & button2.Top == 0 & button3.Top == 0 & button4.Top == 0)
04    {
05        if (button5.Top == 100 & button6.Top == 100 & button7.Top == 100 & button8.Top == 100)
06        {
07            if (button9.Top == 200 & button10.Top == 200 & button11.Top == 200 & button12.Top == 200)
08            {
09                if (button13.Top == 300 & button14.Top == 300 & button15.Top == 300 & button16.Top == 300)
10                {
11                    if (button1.Left == 0 & button5.Left == 0 & button9.Left == 0 & button13.Left == 0)
12                    {
13                        if (button2.Left == 100 & button6.Left == 100 & button10.Left == 100 & button14.Left == 100)
14                        {
15                            if (button3.Left == 200 & button7.Left == 200 & button11.Left == 200 & button15.Left == 200)
16                            {
17                                if (button4.Left == 300 & button8.Left == 300 & button12.Left == 300 & button16.Top == 300)
18                                {
19                                    MessageBox.Show("Complete!");
20                                }
21                            }
22                        }
23                    }
24                }
25            }
26        }
27    }
28}

このメソッドは、タイルを動かすたびに判定したいので、すべてのボタンのクリックイベントで実行されるメソッドの最後でcomplete()メソッドを呼び出します。たとえばボタン「1」のクリックイベントで実行されるメソッドは次のようになります。

リスト6:完成の判定をするメソッドはここで呼び出す

01private void button1_Click(object sender, EventArgs e)
02{
03    if (button1.Left == button16.Left & (Math.Abs(button1.Top - button16.Top) == 100))
04    {
05        swapPos = button1.Top;
06        button1.Top = button16.Top;
07        button16.Top = swapPos;
08    }
09    if (button1.Top == button16.Top & (Math.Abs(button1.Left - button16.Left) == 100))
10    {
11        swapPos = button1.Left;
12        button1.Left = button16.Left;
13        button16.Left = swapPos;
14    }
15    complete();
16}

ここまでできたら、一度Visual Studioで緑の三角「開始」ボタンをクリックするか、「デバッグ」メニューの「デバッグの開始」でビルドしてプログラムを実行しましょう。パズルが表示されたら「12」か「15」のタイルをクリックして動かし、もう一度クリックして元の位置に戻します。完成の「Complete!」のメッセージボックスが表示されればプログラムは正しく動いています。

タイルをランダムに並べ替えてゲームを初期化するコードを書く

ゲームを開始する際にはタイルがランダムになっている必要があります。そこでプログラムの実行時に、まずタイルをランダムに交換するコードを実行することにしましょう。プログラムの実行時に実行したいコードは、フォームのLoadイベントで実行するようにすればいいでしょう。Loadイベントハンドラとイベントハンドラによって実行されるメソッドを作るには、デザイナでフォームの何もないところをダブルクリックします。このプログラムではフォームの内側はすでにボタンで敷き詰められているので、フォームの上部、タイトルバーをダブルクリックするとよいでしょう。次のようなコードが表示されます。

リスト7:ゲーム開始時に実行されるコードを書く場所

1private void Form1_Load(object sender, EventArgs e)
2{
3 
4}

ボタンをダブルクリックして生成するコードと同様に、ここで見えているのは「Loadイベントハンドラによって実行されるメソッド」です。「Loadイベントハンドラ」は別のところに生成していて隠されています。ここにコードを書くと、フォームがロードされたときに実行させることができます。

タイルをランダムに交換するには、Randomクラスを使って乱数を作り、その番号のボタンが「16」のボタンと位置を交換するようにします。対象のタイルは「1」から「15」の15枚なので、乱数は1から15を作り、case文で分岐するようにします。交換するコードは各ボタンのクリックイベントハンドラによって実行されるメソッドをコピーして使います。サンプルコードは次のようになります。

リスト8:ゲーム開始時に実行されるコード

01private void Form1_Load(object sender, EventArgs e)
02{
03    Random r = new System.Random();
04    for (int i=1 ; i<2000 ; i++)
05    {
06        int j = r.Next(1,15);
07        switch (j)
08        {
09            case 1:
10                if (button1.Left == button16.Left & (Math.Abs(button1.Top - button16.Top) == 100))
11                {
12                    swapPos = button1.Top;
13                    button1.Top = button16.Top;
14                    button16.Top = swapPos;
15                }
16                if (button1.Top == button16.Top & (Math.Abs(button1.Left - button16.Left) == 100))
17                {
18                    swapPos = button1.Left;
19                    button1.Left = button16.Left;
20                    button16.Left = swapPos;
21                }
22                break;
23            case 2:
24(以下、ボタン2以降の交換処理が続く)

これで15パズルが完成しました。コード全体は次のようになります。Visual Studioで緑の三角「開始」ボタンをクリックするか、「デバッグ」メニューの「デバッグの開始」でビルドしてプログラムを実行しましょう。デバッグのときはForm1_Loadメソッドにあるボタンの位置交換の繰り返し回数を減らしておくのがよいかもしれません。「for (int i=1 ; i<2000 ; i++)」のiの値を2000から20程度に変更すればよいでしょう。そうすればデバッグ時には少しのタイル移動でパズルが完成します。2000のままだとタイルが大きく動いてしまい、テストのたびにひたすら15パズルを解かされることになりますからね。

初期化の入れ替え回数を20回にした起動画面の例

初期化の入れ替え回数を20回にした起動画面の例

初期化の入れ替え回数を2000回にした起動画面の例

初期化の入れ替え回数を2000回にした起動画面の例

ここまでのサンプルコードは以下のようになります。

リスト9:15パズル(非オブジェクト指向)のコード(expand sourceをクリックで表示)

次回は、上記のコードの問題点を確認しながらオブジェクト指向によるプログラミングを始めてみましょう。

著者
松本 吉生(まつもと よしお)

京都に生まれ、神戸で幼少期を過ごす。

大学で応用化学を学んだのち、理科教諭として高等学校に勤務する。教育の情報化が進む中で校内ネットワークの構築運用に従事し、兵庫県立明石高等学校で文部科学省の「光ファイバー網による学校ネットワーク活用方法研究開発事業」に携わる。兵庫県立西宮香風高等学校では学籍管理データベースシステムをSQL Serverで構築・運用、兵庫県立神戸工業高等学校ではC#プログラミング、IoTなどのコンピュータ教育を行い、現在は兵庫県立神戸甲北高等学校に勤務する。

2004年からマイクロソフトMVP(Microsoft Most Valuable Professional)を受賞し、現在17回目の連続受賞。2020年1月14日に「令和元年度文部科学大臣優秀教職員表彰」を受賞。

連載バックナンバー

開発言語技術解説
第5回

オブジェクト指向で作った15パズルの完成

2020/12/24
連載5回目となる今回は、これまで作成してきた15パズルを完成させます。
開発言語技術解説
第4回

オブジェクト指向で作った15パズルの基本的な機能を完成させる

2020/12/10
連載4回目となる今回は、前回に続いてオブジェクト指向で作った15パズルにコードを追加し、基本的な機能を完成させます。
開発言語技術解説
第3回

オブジェクト指向プログラミングで15パズルを作ってみる

2020/11/26
連載3回目となる今回は、前回まで作成したオブジェクト指向でないプログラムの問題点を明らかにしつつ、オブジェクト指向を意識したプログラムを作成していきます。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています