検索
連載

第2回 DataGridViewコントロールでマインスイーパ連載:.NETグリッド・コントロール大研究(2/4 ページ)

新しいグリッド・コントロールはデータ連結しなくても使えるため、さまざまに活用できる。その応用例としてゲームに挑戦。

PC用表示 関連情報
Share
Tweet
LINE
Hatena

■フォームの設定

 まずはVisual Studio 2005を起動し、「Windowsアプリケーション」の新規プロジェクトを作成する。プロジェクト名は「GridSweeperVB」(C#版は「GridSweeperCS」)とする。

 次にDGVコントロールをフォームに配置する。このとき表示される「DataGridViewタスク」のメニューから[親コンテナにドッキング]を選択して、フォーム全面に配置しておく(DGVコントロールの右上にある小さな三角マークからも可能)。

 またプロパティ・ウィンドウでは、名前「(Name)」を「dgv」に変更し、さらにDefaultCellStyleプロパティを選択すると現れる[...]ボタンをクリックし、セルで使用されるフォントについて、以下の2つの設定を行う。

  • 配置の[Alignment]を「MiddleCenter」に指定
  • 表示用フォントを[Font]で指定(本稿では「Arial Black」の9ptを選択)

 このDefaultCellStyleプロパティで設定されたセル・スタイルは、DGVコントロールに追加される行で使用される既定のセル・スタイルとなる。これらの設定はコードからも行えるが、IDEで設定した方が楽だ。もちろんフォントの種類は自由に選択していただいてよい。

 C#の場合には、さらにプロパティ・ウィンドウから次の4つのイベントについてイベント・ハンドラをフォームおよびDGVコントロールに追加しておく。

  • フォームのLoadイベント
  • DGVコントロールのCellClickイベント
  • DGVコントロールのKeyDownイベント
  • DGVコントロールのSelectionChangedイベント

■全ソース・コード

 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();
    }
  }
}

Gridスイーパのソース・コード(上:VB、下:C#)

 以下では、プログラムの実行順に従って解説していく。

Copyright© Digital Advantage Corp. All Rights Reserved.

ページトップに戻る