■フォームの設定
まずはVisual Studio 2005を起動し、「Windowsアプリケーション」の新規プロジェクトを作成する。プロジェクト名は「GridSweeperVB」(C#版は「GridSweeperCS」)とする。
次にDGVコントロールをフォームに配置する。このとき表示される「DataGridViewタスク」のメニューから[親コンテナにドッキング]を選択して、フォーム全面に配置しておく(DGVコントロールの右上にある小さな三角マークからも可能)。
またプロパティ・ウィンドウでは、名前「(Name)」を「dgv」に変更し、さらにDefaultCellStyleプロパティを選択すると現れる[...]ボタンをクリックし、セルで使用されるフォントについて、以下の2つの設定を行う。
このDefaultCellStyleプロパティで設定されたセル・スタイルは、DGVコントロールに追加される行で使用される既定のセル・スタイルとなる。これらの設定はコードからも行えるが、IDEで設定した方が楽だ。もちろんフォントの種類は自由に選択していただいてよい。
C#の場合には、さらにプロパティ・ウィンドウから次の4つのイベントについてイベント・ハンドラをフォームおよびDGVコントロールに追加しておく。
■全ソース・コード
Gridスイーパのソース・コード(Form1.vbあるいはForm1.csに記述するコード)は200行程度なので、以下にそのすべてを示しておく。上記の設定を行い、このコードをForm1.vb(C#の場合はForm1.cs)にコピー&ペーストすれば、プログラムを実行できるはずだ。
次のページからは、この中のポイントとなる部分を解説していく。
Public Class Form1
Dim cellSize As Integer = 20 ' セルのサイズ
Dim sx As Integer = 16 ' フィールドの幅
Dim sy As Integer = 16 ' フィールドの高さ
Dim numMine As Integer = 40 ' 爆弾の数
Const UNOPEN As Integer = -2
Const MINE As Integer = -1
Dim numCellOpened As Integer ' 開いたセルの数
Dim gameStarted As Boolean ' ゲームを開始しているか
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
' 以下はプロパティ・ウィンドウでも設定可能
dgv.ReadOnly = True
dgv.ColumnHeadersVisible = False
dgv.RowHeadersVisible = False
dgv.AllowUserToResizeColumns = False
dgv.AllowUserToResizeRows = False
dgv.AllowUserToAddRows = False
dgv.ShowCellToolTips = False ' 答えが見えないように
' フィールドの作成
dgv.RowTemplate.Height = cellSize ' 追加される行の高さ
dgv.ColumnCount = sx
dgv.RowCount = sy
For Each col As DataGridViewColumn In dgv.Columns
col.Width = cellSize
Next
' フォームのサイズをDGVに合わせる
Dim cell As DataGridViewCell = dgv(0, 0)
Me.ClientSize = New Size( _
cell.Size.Width * sx + 3, _
cell.Size.Height * sy + 3)
initGame()
End Sub
' ゲームの初期化
Sub initGame()
numCellOpened = 0
gameStarted = True
' すべてのセルの初期化
For x As Integer = 0 To sx - 1
For y As Integer = 0 To sy - 1
dgv(x, y).Value = UNOPEN
dgv(x, y).Style.ForeColor = Color.WhiteSmoke
dgv(x, y).Style.BackColor = Color.WhiteSmoke
Next
Next
' 爆弾の配置
Dim rnd As New Random
Dim doneNum As Integer = 0
While doneNum < numMine
Dim x As Integer = rnd.Next(sx)
Dim y As Integer = rnd.Next(sy)
If CInt(dgv(x, y).Value) <> MINE Then
dgv(x, y).Value = MINE
doneNum += 1
End If
End While
showRemain()
End Sub
' 残りセル(開いていないセル)の表示
Sub showRemain()
Me.Text = "爆弾:" & numMine _
& " 残りのセル:" & (sx * sy - numCellOpened)
End Sub
' (x, y)がグリッド内に含まれるか
Function isInField(ByVal x As Integer, ByVal y As Integer)
If x < 0 Or y < 0 Or x >= sx Or y >= sy Then
Return False
End If
Return True
End Function
' cellに隣接するセルを配列で返す
Function getNeighbors(ByVal cell As DataGridViewCell)
Dim x As Integer = cell.ColumnIndex
Dim y As Integer = cell.RowIndex
Dim cc As New List(Of DataGridViewCell)
If isInField(x - 1, y - 1) Then cc.Add(dgv(x - 1, y - 1))
If isInField(x - 1, y + 1) Then cc.Add(dgv(x - 1, y + 1))
If isInField(x + 1, y - 1) Then cc.Add(dgv(x + 1, y - 1))
If isInField(x + 1, y + 1) Then cc.Add(dgv(x + 1, y + 1))
If isInField(x - 1, y) Then cc.Add(dgv(x - 1, y))
If isInField(x + 1, y) Then cc.Add(dgv(x + 1, y))
If isInField(x, y - 1) Then cc.Add(dgv(x, y - 1))
If isInField(x, y + 1) Then cc.Add(dgv(x, y + 1))
Return cc.ToArray()
End Function
' 数字セルの表示
Sub drawNumberCell(ByVal cell As DataGridViewCell)
cell.Style.BackColor = Color.LightGray
If CInt(cell.Value) = 0 Then
' 0は表示しない(背景色で描画)
cell.Style.ForeColor = Color.LightGray
Else
' 1〜8の数字
cell.Style.ForeColor = Color.Blue
End If
End Sub
' セルを開こうとする
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
' セルを開く
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
Sub gameOver(ByVal isSuccess As Boolean)
gameStarted = False
Me.Text = "F2キーでリトライ"
' 爆弾位置の表示
For x As Integer = 0 To sx - 1
For y As Integer = 0 To sy - 1
If CInt(dgv(x, y).Value) = MINE Then
dgv(x, y).Value = ""
dgv(x, y).Style.BackColor = IIf(isSuccess, Color.Green, Color.Red)
End If
Next
Next
End Sub
' DGVのKeyDownイベント・ハンドラ
Private Sub dgv_KeyDown(ByVal sender As Object, ByVal e As KeyEventArgs) Handles dgv.KeyDown
If e.KeyCode = Keys.F2 Then
initGame()
End If
If e.KeyCode = Keys.Space Then
tryCell(dgv.CurrentCell)
End If
End Sub
' DGVのCellClickイベント・ハンドラ
Private Sub dgv_CellClick(ByVal sender As Object, ByVal e As DataGridViewCellEventArgs) Handles dgv.CellClick
tryCell(dgv(e.ColumnIndex, e.RowIndex))
End Sub
' DGVのSelectionChangedイベント・ハンドラ
Private Sub dgv_SelectionChanged(ByVal sender As Object, ByVal e As EventArgs) Handles dgv.SelectionChanged
' セルを選択状態にさせない
dgv.ClearSelection()
End Sub
End Class
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace GridSweeperCS {
public partial class Form1 : Form {
int cellSize = 20; // セルのサイズ
int sx = 16; // フィールドの幅
int sy = 16; // フィールドの高さ
int numMine = 40; // 爆弾の数
const int UNOPEN = -2;
const int MINE = -1;
int numCellOpened; // 開いたセルの数
bool gameStarted; // ゲームを開始しているか
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// 以下はプロパティ・ウィンドウでも設定可能
dgv.ReadOnly = true;
dgv.ColumnHeadersVisible = false;
dgv.RowHeadersVisible = false;
dgv.AllowUserToResizeColumns = false;
dgv.AllowUserToResizeRows = false;
dgv.AllowUserToAddRows = false;
dgv.ShowCellToolTips = false; // 答えが見えないように
// フィールドの作成
dgv.RowTemplate.Height = cellSize; // 追加される行の高さ
dgv.ColumnCount = sx;
dgv.RowCount = sy;
foreach (DataGridViewColumn col in dgv.Columns) {
col.Width = cellSize;
}
// フォームのサイズをDGVに合わせる
DataGridViewCell cell = dgv[0, 0];
this.ClientSize = new Size(
cell.Size.Width * sx + 3,
cell.Size.Height * sy + 3);
initGame();
}
// ゲームの初期化
void initGame() {
numCellOpened = 0;
gameStarted = true;
// すべてのセルの初期化
for (int x = 0; x < sx; x++) {
for (int y = 0; y < sy; y++) {
dgv[x, y].Value = UNOPEN;
dgv[x, y].Style.ForeColor = Color.WhiteSmoke;
dgv[x, y].Style.BackColor = Color.WhiteSmoke;
}
}
// 爆弾の配置
Random rnd = new Random();
for (int doneNum = 0; doneNum < numMine; ) {
int x = rnd.Next(sx);
int y = rnd.Next(sy);
if ((int)dgv[x, y].Value != MINE) {
dgv[x, y].Value = MINE;
doneNum++;
}
}
showRemain();
}
// 残りセル(開いていないセル)の表示
void showRemain() {
this.Text = "爆弾:" + numMine
+ " 残りのセル:" + (sx * sy - numCellOpened);
}
// (x, y)がグリッド内に含まれるか
bool isInField(int x, int y) {
if (x < 0 || y < 0 || x >= sx || y >= sy)
return false;
return true;
}
// cellに隣接するセルを配列で返す
DataGridViewCell[] getNeighbors(DataGridViewCell cell) {
int x = cell.ColumnIndex;
int y = cell.RowIndex;
List<DataGridViewCell> cc = new List<DataGridViewCell>();
if (isInField(x - 1, y - 1)) cc.Add(dgv[x - 1, y - 1]);
if (isInField(x - 1, y + 1)) cc.Add(dgv[x - 1, y + 1]);
if (isInField(x + 1, y - 1)) cc.Add(dgv[x + 1, y - 1]);
if (isInField(x + 1, y + 1)) cc.Add(dgv[x + 1, y + 1]);
if (isInField(x - 1, y)) cc.Add(dgv[x - 1, y]);
if (isInField(x + 1, y)) cc.Add(dgv[x + 1, y]);
if (isInField(x, y - 1)) cc.Add(dgv[x, y - 1]);
if (isInField(x, y + 1)) cc.Add(dgv[x, y + 1]);
return cc.ToArray();
}
// 数字セルの表示
void drawNumberCell(DataGridViewCell cell) {
cell.Style.BackColor = Color.LightGray;
if ((int)cell.Value == 0) {
// 0は表示しない(背景色で描画)
cell.Style.ForeColor = Color.LightGray;
} else {
// 1〜8の数字
cell.Style.ForeColor = Color.Blue;
}
}
// セルを開こうとする
void tryCell(DataGridViewCell cell) {
if (gameStarted == false) {
return;
}
if ((int)cell.Value == MINE) {
gameOver(false);
} else if ((int)cell.Value == UNOPEN) {
openCell(cell);
showRemain();
if (numCellOpened == sx * sy - numMine) {
gameOver(true);
}
}
}
// セルを開く
void openCell(DataGridViewCell cell) {
numCellOpened++;
int count = 0;
// 周りの爆弾の数を数える
foreach (DataGridViewCell c in getNeighbors(cell)) {
if ((int)c.Value == MINE) {
count++;
}
}
// 周りの爆弾の数を表示
cell.Value = count;
drawNumberCell(cell);
// 周りのセルに爆弾がない場合、周りのセルも開く
if (count == 0) {
foreach (DataGridViewCell c in getNeighbors(cell)) {
if ((int)c.Value == UNOPEN) {
openCell(c); // 再帰呼び出し
}
}
}
}
void gameOver(bool isSuccess) {
gameStarted = false;
this.Text = "F2キーでリトライ";
// 爆弾位置の表示
for (int x = 0; x < sx; x++) {
for (int y = 0; y < sy; y++) {
if ((int)dgv[x, y].Value == MINE) {
dgv[x, y].Value = "";
dgv[x, y].Style.BackColor
= isSuccess ? Color.Green : Color.Red;
}
}
}
}
// DGVのKeyDownイベント・ハンドラ
private void dgv_KeyDown(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.F2) {
initGame();
}
if (e.KeyCode == Keys.Space) {
tryCell(dgv.CurrentCell);
}
}
// DGVのCellClickイベント・ハンドラ
private void dgv_CellClick(object sender, DataGridViewCellEventArgs e) {
tryCell(dgv[e.ColumnIndex, e.RowIndex]);
}
// DGVのSelectionChangedイベント・ハンドラ
private void dgv_SelectionChanged(object sender, EventArgs e) {
// セルを選択状態にさせない
dgv.ClearSelection();
}
}
}
以下では、プログラムの実行順に従って解説していく。
Copyright© Digital Advantage Corp. All Rights Reserved.