SQパズル 第3回(最終回) ~ SQパズルのプログラムの中身 ~

マイクロソフトの田中達彦です。
新たなゲームのテンプレートとして、SQパズルというものを作成しました。
今回は、そのプログラムの中身を説明します。

[プログラムの構成]
SQパズルは、Visual Studioのテンプレートの「新しいアプリケーション」を使用して作成しています。
この「新しいアプリケーション」はいちばんシンプルなアプリの形態で、ページ遷移のないゲームなどを作るときに使われます。
ソースコードとして手を入れているのは、UI部分を構成しているMainPage.xamlとC#で書かれたコードのMainPage.xaml.csの2つのファイルだけです。

[UI部分]
MainPage.xamlの中は、このように構成されています。

ページそのものであるpage1の中に、アプリバーであるappBar1とゲーム画面であるviewBox1が含まれています。
viewBox1はViewBoxというコントロールで、その中に含まれるものを自動的に拡大/縮小する機能を持っています。
今回のSQパズルを縦長の画面でプレイすると、ゲーム画面が横幅に合わせて小さくなり、上下に黒い部分が表示されます。
ViewBoxは、このように画面サイズに合わせて中のものの大きさを自動的に変更します。

viewBox1の中には、Gridというコントロール(上の図で青くなっている部分)があります。
そのGridの中に、grid1というGridコントロール、button1、button2、button3というButtonコントロール、textBlock2というTextBlockコントロール、progressRing1というProgressRingコントロールがあります。
grid1には、パズルのもととなる画像を表示させています。
button1、button2、button3は、それぞれ「初級」、「中級」、「上級」と表示させているボタンです。button2は「再開」ボタンとしても流用しています。
textBlock1は、文字を表示させるためのコントロールで、経過時間を表示させています。
progressRing1は、時間のかかる処理を行うときに、中央でくるくる回るリングです。

grid1の中にはimage1というパズルの画像を表示させるコントロールがあり、その中にはClip、さらにはRectangleGeometryというものがあり、rg1という名前がついています。
ClipとRectangleGeometryは、パズルの画像を分割するために使用しています。

[ピースへの分割]
初級、中級または上級のボタンを押すと画像が一旦消え、ピースごとの画像が左上から右下に向かって順番に表示されます。
これは元の画像をピースの大きさでクリップして、1つずつのピースの画像を取り出している処理をしているところです。
Windows 8.1にはRenderTargetBitmapと呼ばれる機能があり、表示されている画像のデータを取得することができます。
クリップすれば、その画像の一部分を抜き出すことができるのです。
この処理は、StartGameメソッド内で行っています。

なおクリップしているところで、grid1.Background = nullというように、背景を一時消しています。
この部分は背景を消さなくても、

rect.Width = PieceWidth;
rect.Height = PieceHeight;

というようにクリップする範囲の幅と高さを指定すれば、目的の場所をクリップできます。

[ピースのシャッフル]
各ピースの画像データを作成した後は、それぞれのピースが適当な場所に移動させています。
これはDispatcherTimerを使用して実現しています。

DispatcherTimerを使用すると、一定時間ごとにイベントを発生することができます。
ゲーム開始直後は以下のように20ミリ秒ごとにイベントが発生するように設定してあります。

Timer.Interval = new TimeSpan(0, 0, 0, 0, 20);

イベントが発生すると、Timer_Tickが呼ばれます。

ピースの画像を作ったときに、ピースの移動先をランダムに決めています。
その移動先に進めるために、イベント発生ごとにX方向Y方向それぞれどのくらい動かせばよいかという数値をあらかじめ計算しています。
Timer_Tickというイベントハンドラーの中では、それぞれのピースの位置を計算した数値分だけ動かしています。

[ピースの移動]
ゲーム開始後は、ピースをクリックまたはタップすると、そのピースを動かせます。
ピースがクリックされたら、そのピースをいちばん手前に表示させています。
この処理は、Page_PointerPressedの中で以下の部分で行っています。

// 触っているピースを一旦削除し、グリッドの一番手前に追加する
grid1.Children.Remove(PieceImage[MovingPieceX, MovingPieceY]);
grid1.Children.Add(PieceImage[MovingPieceX, MovingPieceY]);

grid1には、それぞれのピースと、ピースを置く場所である四角がStartGameメソッドで追加されています。
Gridコントロールに何かを追加するときには、ChildrenのAddメソッドを使います。
複数のものを追加したときは、最後に追加されたものがいちばん手前に表示されます。
もしクリックしたピースがいちばん手前に表示されているとは限らないので、クリックしたピースを一旦grid1から削除したうえで、もう一度grid1に追加することにより、そのピースがいちばん最後にgrid1に追加されたことになり、手前に表示されるようになります。

ピースをクリックしたときはPage_PointerPressedが呼ばれ(正確にはピース以外のところをクリックしても呼ばれる)、ドラッグしているときはPage_PointerMovedが呼ばれます。
これらのPointer何とかというイベントハンドラーは、マウスによるクリックでも指によるタッチでも呼ばれます。
ドラッグしているピースを離すと、Page_PointerReleasedというイベントハンドラーが呼ばれます。
もしピースを離した位置がピースを置くべき四角に近い位置であれば、自動的にその四角にピースがはまるように処理しています。

[ゲームの中断と再開]
ゲームのプレイ中であれば、ピースを動かして離したとき、すなわちPage_PointerReleasedイベントハンドラーの中で、ピースを動かした後の位置を以下のように記録しています。

// ピースの位置を記録する
AppData.LocalSettings.Values["PieceImage" + MovingPieceX.ToString("00#") + MovingPieceY.ToString("00#") + "X"]
    = PieceImage[MovingPieceX, MovingPieceY].Margin.Left;
AppData.LocalSettings.Values["PieceImage" + MovingPieceX.ToString("00#") + MovingPieceY.ToString("00#") + "Y"]
    = PieceImage[MovingPieceX, MovingPieceY].Margin.Top;

Windowsストアアプリには、アプリごとに情報を記録するエリアがあり、そこにピースの位置を書き込んでいます。
ゲームを再開したときには、その記録エリアから情報を引き出し、その位置にピースを配置しています。

[ゲームの終了判定]
ピースが四角にはまるごとに、パズルが完成しているかどうかをCheckCompleteメソッドで判断しています。
CheckCompleteメソッドでは、それぞれのピースが正しい場所にあるかどうかを判断し、もし違った場所にあるときには、ピースのTagプロパティに1という適当な数を入れています。
Tagプロパティというプロパティは、特に用途が決まっていないため、自由なデータを入れることができるのです。
すべてのピースがどこかの四角にはまっているときでパズルが完成していないときは、アプリバーのヒントボタンが有効になります。
ここでヒントボタンを押すと、各ピースのTagプロパティが1であるものを少し目立たせるような処理をしています。

[すべての記事]
第1回 SQパズルのテンプレート
第2回 SQパズルのカスタマイズ
第3回(最終回) SQパズルのプログラムの中身 (今回)

日本マイクロソフト
田中達彦