■セルのクリック処理
DGVコントロールではセルがクリックされると、そのセルが選択されフォーカスが設定されるが、Gridスイーパではセルがクリックされたら、そのセルを開く(実際には開いたように見せるために色を変更し、Valueプロパティを書き換える)処理が必要だ。
そこで、セルがクリックされたときに発生するCellClickイベントをハンドリングする。CellClickイベント・ハンドラでは、メソッドのパラメータとしてDataGridViewCellEventArgsオブジェクトが渡されるので、そのColumnIndexプロパティとRowIndexプロパティにより、どの位置のセルがクリックされたかを知ることができる。つまり、クリックされたセル・オブジェクトは、
dgv(e.ColumnIndex, e.RowIndex)
により得ることができる。
CellClickイベント・ハンドラは次のようになる。ここではクリックされたセルをtryCellメソッドに渡す。
Private Sub dgv_CellClick(ByVal sender As Object, ByVal e As DataGridViewCellEventArgs) Handles dgv.CellClick
' クリックされたセルをtryCellメソッドに渡す
tryCell(dgv(e.ColumnIndex, e.RowIndex))
End Sub
■キー入力の処理
DGVコントロールではあらかじめ多くのキーをハンドリングしており*、例えばフォーカスのあるセルを移動するにはカーソル・キーやタブ・キーが使える。
Gridスイーパでは、[F2]キーでゲームの再スタートを行い、[スペース]キーで現在フォーカスのあるセルを開くようにして、キーボードだけでもゲームができるようにしてみた。
キーの入力に関しては、KeyDownイベントに対応すればよい。このイベント・ハンドラは次のようになる。e.KeyCodeがDGVコントロールに対して入力されたキーのコードとなる。
Private Sub dgv_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs) Handles dgv.KeyDown
' [F2]キーで再スタート
If e.KeyCode = Keys.F2 Then
initGame()
End If
' [スペース]キーで現在のセルを開く
If e.KeyCode = Keys.Space Then
tryCell(dgv.CurrentCell)
End If
End Sub
現在フォーカスのあるセルは、DGVコントロールのCurrentCellプロパティによって得られるので、[スペース]キーが入力された場合には、セルのクリック時と同様に、これをtryCellメソッドに渡す。
■セルのオープン
tryCellメソッドでは、開こうとするセルの値が爆弾(MINE)であれば即ゲームオーバーだが、まだ開かれていないセル(UNOPEN)であれば、openCellメソッドを呼び出してそれを開く。
これらの処理が終わった後、もしすでに開かれているセルの数(numCellOpened)が爆弾のないセルの数(sx * sy - numMine)と等しければ、爆弾撤去が完了だ。
Sub tryCell(ByVal cell As DataGridViewCell)
' ゲームが開始されていなければ何もしない
If gameStarted = False Then
Return
End If
If CInt(cell.Value) = MINE Then
gameOver(False) ' 爆弾だったらゲームオーバー
ElseIf CInt(cell.Value) = UNOPEN Then
openCell(cell) ' 実際にセルを開く
showRemain()
' 開いたセルと爆弾のないセルが同じ数なら撤去完了
If numCellOpened = sx * sy - numMine Then
gameOver(True)
End If
End If
End Sub
■セルの実際のオープン
クリックされたセルを実際に開いて数字(あるいは空文字)に書き換える処理はopenCellメソッドで行っている。マインスイーパでは単にクリックされたセルを開くだけでなく、セルに爆弾がなかったら連鎖的に周りのセルも開かなければならない場合があるので、このメソッドは少しだけ複雑だ。
openCellメソッドではまず、メソッドのパラメータで指定されたセルに隣接するセル(最高8つ)をgetNeighborsメソッドにより取得する。そして爆弾のあるセルの数を数え、それをセルの値とし、drawNumberCellメソッドにより表示する。
Sub openCell(ByVal cell As DataGridViewCell)
numCellOpened += 1
Dim count As Integer = 0
' 周りの爆弾の数を数える
For Each c As DataGridViewCell In getNeighbors(cell)
If CInt(c.Value) = MINE Then
count += 1
End If
Next
' 周りの爆弾の数を表示
cell.Value = count
drawNumberCell(cell)
' 周りのセルに爆弾がない場合、周りのセルも開く
If count = 0 Then
For Each c As DataGridViewCell In getNeighbors(cell)
If CInt(c.Value) = UNOPEN Then
openCell(c) ' 再帰呼び出し
End If
Next
End If
End Sub
隣接するセルに爆弾があればセルに数字を書いて処理は終わりだが、もし隣接するセルのいずれにも爆弾がなかったら、それらのセルを開き、さらにそのセルの周りにも爆弾がなかったらそれらを開き……という処理をそういうセルがなくなるまで繰り返さなくてはならない。
処理は難しいがご覧のようにメソッドの再帰呼び出しを使えばコードは非常に単純に記述できる。隣接するまだ開いていない各セルに対して、さらにopenCellメソッドを呼び出すだけである。
以上、今回はゲームを作りながらDGVコントロールの非連結モードについて解説してみた。DGVコントロールはデータ連結しなくても手軽に使えるようになっているので、これまで以上にさまざまな用途で活用できるのではないかと思う。
Copyright© Digital Advantage Corp. All Rights Reserved.