Chào mừng đến với Diễn đàn lập trình - Cộng đồng lập trình.
Trang 1 của 3 123 CuốiCuối
Kết quả 1 đến 10 của 24
  1. #1
    Ngày tham gia
    Sep 2015
    Bài viết
    0

  2. #2
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    khi ta đánh dấu đúng vào bom thì clink 2 chuột vào 1 ô đã mở ở bên ngoài miễn là phải đánh dấu đủ bom thì nó sẽ mở hết những ô sát đó ra cho (cái này rất tiện). Nếu đánh dấu sai mà clink 2 chuột là die luôn.

  3. #3
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Cảm ơn các bạn đã giúp đỡ, mình vừa cập nhật chức năng click 2 nút như bạn Magiczvn và iloveit1208 mô tả.
    Đã update link down lên bài #1

  4. #4
    Ngày tham gia
    Sep 2015
    Bài viết
    0

    Hướng dẫn viết game Dò mìn (Minesweeper) bằng C# + sourcecode

    Minesweeper là một game mini được tích hợp sẵn trong Windows nên có lẽ ai cũng từng chơi qua vài lần. Và nếu bạn đang có hứng thú muốn tạo ra một game nhỏ để thử sức thì đây là một trong những lựa chọn thích hợp. Minesweeper có giao diện và luật chơi đơn giản, thuật toán cài đặt cũng dễ dàng và không tốn nhiều tài nguyên máy.
    Trong bài này tôi sử dụng C# để viết, bạn có thể sử dụng bất kì ngôn ngữ nào dựa theo cách thức tương tự mà tôi trình bày. Vì phần thuật toán khá đơn giản nên tôi sẽ tập trung vào phần giao diện coi như hướng dẫn cách thiết kế giao diện cơ bản.

    Luật chơi

    Minisweeper (của Windows) có phần giao diện chính là một bảng các ô vuông xếp liền nhau tạo thành một hình chữ nhật có chiều rộng và dài tối thiểu là 9 ô (đơn vị là ô vuông) và số mìn tối thiểu là 10.
    Trong bảng này sẽ có các ô được đặt mìn ngẫu nhiên và nhiệm vụ của người chơi là mở tất cả các ô không có mìn bằng cách click chuột trái vào các ô đó, khi chỉ còn các ô có mìn còn lại thì kết thúc màn chơi.
    Các trạng thái của một ô
    Tùy theo quá trình khởi tạo và thao tác của người dùng mà các ô trong bảng có thể có một hoặc vài trạng thái trong các trạng thái sau:
    - Có mìn: được đặt ngẫu nhiên lúc khởi tạo
    - Đã mở: Khi người dùng nhấn chuột trái vào ô
    - Được cắm cờ: Khi người dùng nhấn phải vào ô
    - Được đánh dấu: Khi nhấn phải vào ô đã được “cắm cờ”
    - Bình thường: không có tất cả các trạng thái trên

    Các trường hợp khi mở một ô

    Khi mở một ô X nào đó, có 3 trường hợp có thể xảy ra:
    X có mìn: hiện tất cả mìn trong bảng ra và ‘game over’.
    X không có mìn nhưng 8 ô xung quanh có mìn: hiện số mìn xung quanh vào X.
    X không có mìn và xung quanh cũng không có mìn: mở lần lượt các ô xung quanh X cho đến khi gặp các trường hợp 1 và 2.

    Cắm cờ và đánh dấu

    Minisweeper cho phép bạn đánh dấu các ô nghi ngờ có mìn bằng cách “cắm cờ” và “đánh dấu”. Khi bạn “cắm cờ”, tức là bạn xác định rằng ô đó có mìn và ô đó được hiển thị là một lá cờ. Bạn không thể mở ô đó bằng chuột trái được. Bạn chỉ có thể hủy bỏ trạng thái “cắm cờ” bằng cách click chuột phải, tùy theo thiết lập mà ô đó sẽ chuyển sang trại thái “đánh dấu” hoặc “bình thường”.
    Khi bạn “đánh dấu”, tức là bạn đoán rằng ô đó có thể có mìn nhưng không chắc chắn.
    Xây dựng chương trình

    Như các project trước tôi đã làm, ta sẽ tách các phần logic và UI và ra để dễ nâng cấp khi cần thiết.
    Phần Bussineses

    Phần này tôi thiết kế một lớp Cell và lớp MinesBoard đại diện cho ô và bảng mìn trong trò chơi.
    Lớp Cell dựa trên các trạng thái của một ô, bạn có thể sử dụng enum thay cho class, trong bài này tôi sử dụng class để người đọc dễ hiểu. Lớp này chỉ bao gồm các field (bạn có thể dùng Property cho “máy móc”) sau:


    Mã:
    public class Cell{    public bool IsMine = false;    public bool IsOpened = false;    public bool IsFlag = false;    public bool IsMarked = false;    public int  MinesAround = 0;}
    Lớp MinesBoard sẽ chứa một mảng hai chiều các đối tượng kiểu Cell, ngoài ra còn chứa dữ liệu cần thiết khác như số dòng, số cột, số mìn, số lá cờ được cắm, số ô đã được mở và hai trạng thái thắng, thua.
    Note: Trong bài này, tôi dùng Rows và Cols thay cho Height và Width để tránh nhầm lẫn khi làm việc với mảng hai chiều. Bạn có thể hiểu Rows là Height và Cols là Width.
    Việc khởi tạo MinesBoard bạn sẽ lặp vào random đánh dấu trạng thái IsMine của các Cell là true:


    Mã:
    private void InitBoard(){    // […]     while (count < _MinesCount)    {        int index = rnd.Next(_CellsCount);        int r = index / _Cols;        int c = index % _Cols;         if (!_cells[r, c].IsMine)        {            _cells[r, c].IsMine = true;            count++;        }    }}
    Tiếp đến ta cần viết một phương thức để “mở” một ô trong bảng, như đã giới thiệu trong phần luật chơi, bạn phải kiểm tra các trạng thái của ô đó và quyết định sẽ làm gì. Trong trường hợp thứ 3 bạn phải dùng đệ quy để duyệt và mở các ô xung quanh. Nhưng trước khi viết phương thức này, ta hãy xem một chút về phương pháp duyệt các ô để đếm số mìn quanh một ô.


    Mã:
    private int CountAroundMines(int row, int col){    int count = 0;    int r1 = row == 0 ? 0 : -1;    int c1 = col == 0 ? 0 : -1;    int r2 = row == _Rows-1 ? 1 : 2;    int c2 = col == _Cols-1 ? 1 : 2;     for (; r1 < r2; r1++)        for (int j=c1; j < c2; j++)        {            if (_cells[row + r1, col + j].IsMine)                count++;        }    return count;}
    Như bạn thấy trong đoạn mã trên thì r1 và c1 tương ứng cho giá trị của dòng và cột đầu tiên, r2 và c2 cho dòng và cột cuối cùng ta sẽ lặp qua. Đây chỉ là vị trí tương đối và nếu ô đang xét không nằm ở biên của bảng thì r1 và c1 sẽ có giá trị là -1, r2 và c2 có giá trị là 2. Tức là bạn có thể lặp tối đa từ -1 đến 1, cộng các giá trị này với vị trí row và col để duyệt tất cả 9 ô xung quanh ô hiện tại.
    (Ở đây bạn có thể thêm phần kiểm tra nếu r1=0, j=0 (trong đoạn mã trên) tức là ô duyệt đến là ô hiện tại, bạn có thể bỏ qua không xét ô này)
    Bạn đã biết cách cách lặp qua các ô xung quanh một ô, bây giờ là phần viết lệnh để mở một ô. Hãy xem hình minh họa sau để hiểu cách thức mà mã lệnh của chúng ta sẽ thực hiện:


    Mã:
    /// <summary>/// /// </summary>/// <param name="row"></param>/// <param name="col"></param>/// <returns>true nếu trúng mìn</returns>public bool OpenCell(int row, int col){    if (_cells[row, col].IsOpened || _cells[row, col].IsFlag)        return false;    _cells[row, col].IsOpened = true;     if (_cells[row, col].IsMine)    {        return true;    }    _OpenedCellsCount++;     // Đếm số mìn xung quanh và kiểm tra các trường hợp    int count = CountAroundMines(row, col);     if (count > 0)    {        _cells[row, col].MinesAround = count;    }    else    {        int r1 = row == 0 ? 0 : -1;        int c1 = col == 0 ? 0 : -1;        int r2 = row == _Rows - 1 ? 1 : 2;        int c2 = col == _Cols - 1 ? 1 : 2;         for (; r1 < r2; r1++)            for (int j = c1; j < c2; j++)            {                OpenCell(row + r1, col + j);            }    }    return false;}
    Đó là những vấn đề chính của phần bussiness này, bạn có áp dụng dùng nó để tạo nên phần “nhân” cho một WindowsForms App hoặc Console App bất kì của trò chơi này.

    Phần Presentation

    Thông thường tôi sẽ tạo một UserControl để “tạo hình” cho lớp MinesBoard trên. Bạn có thể hiểu lớp MinesBoard trên là “hồn” và cần gắn vào một cái “xác” để nó có thể hoạt động được. Ở đây tôi đặt tên lớp này là MinesBoardUI. Tôi đặt tên giống nhau như vậy để bạn hiểu rằng không nên thiết kế và gắn thêm quá nhiều thứ vào lớp này ngoài những chức năng mà MinesBoard đã có sẵn.
    Vậy ta chỉ cần đơn giản là tạo lớp này để nó vẽ ra một cái bảng đồng thời cập nhật những trạng thái của các ô thành hình ảnh để người dùng có thể thấy và thao tác được. Nếu bạn gắn thêm những thứ khác như button, label để thực thi lệnh gì đó hay để hiển thị điểm thì bạn phải thiết kế lại UserControl này mỗi lần muốn nâng cấp chương trình. Giả sử nó là một DLL ngoài để cho người khác dùng thì sẽ rất bất tiện.

    Vẽ giao diện

    Khi thiết kế lớp này, bạn chỉ cần tập trung vào hai phần chính: Hiển thị và xử lý thao tác của người dùng. Hai sự kiện tương ứng mà tôi chọn là Paint và MouseDown. Hãy override các sự phương thức tương ứng của hai sự kiện trên. Các đoạn code sau sẽ thay phần giải thích của tôi, bạn có thể thấy hơi dài và rối, tuy nhiên nó không phức tạp mà chỉ đơn giản là kiểm tra từng trạng thái của các ô nên rất dễ hiểu.


    Mã:
    protected override void OnPaint(PaintEventArgs e){    e.Graphics.FillRectangle(Brushes.LightGray, 0, 0, this.Width, this.Height);     for (int i = 0; i < _board._Rows; i++)    {        int y = CELL_SIZE * i;         for (int j = 0; j < _board._Cols; j++)        {            int x = CELL_SIZE * j;             if (_board[i, j].IsOpened)            {                if (_board[i, j].IsMine)                {                    e.Graphics.FillRectangle(Brushes.Red, x, y, CELL_SIZE, CELL_SIZE);                    e.Graphics.DrawImage(_imgBomb, x, y);                }                else if (_board[i, j].MinesAround > 0)                {                    string s = _board[i, j].MinesAround.ToString();                    SizeF size = e.Graphics.MeasureString(s, this.Font);                     e.Graphics.DrawString(s,                        this.Font, new SolidBrush(_foreColors[_board[i, j].MinesAround - 1]),                            x + (CELL_SIZE - size.Width) / 2, y + (CELL_SIZE - size.Height) / 2);                }            }            else                e.Graphics.DrawImage(_imgCell, x, y);             if (_board._IsLost)            {                if (_board[i, j].IsMine)                    e.Graphics.DrawImage(_imgBomb, x, y);            }             if (_board[i, j].IsFlag)            {                e.Graphics.DrawImage(_imgFlag, x, y);            }            else if (_board[i, j].IsMarked)            {                string s = "?";                SizeF size = e.Graphics.MeasureString(s, this.Font);                 e.Graphics.DrawString(s,                    this.Font, Brushes.Black,                        x + (CELL_SIZE - size.Width) / 2, y + (CELL_SIZE - size.Height) / 2);             }            // vertical            if(i==0)            e.Graphics.DrawLine(Pens.Gray, x, 0, x, this.Height);            }                // hoz        e.Graphics.DrawLine(Pens.Gray, 0, y, this.Width, y);    }     base.OnPaint(e);}
    Có một phương thức của lớp Graphisc có thể bạn thắc mắc là MeasureString(). Đây là phương thức để lấy về kích thước của một chuỗi dựa trên Font dùng để viết chuỗi đó. Tôi dùng phương thức này để tính toán và vẽ để chuỗi hiển thị chính giữa ô.

    Bạn có thể thấy là trong Minesweeper, mỗi một con số có giá trị khác nhau hiển thị trong ô vuông sẽ có màu sắc khác nhau. Ta làm việc này đơn giản bằng cách tạo một mảng các đối tượng Color, và truy xuất dựa theo giá trị của số:


    Mã:
    Color[] _foreColors = {    Color.Blue,Color.Green,Color.Red,Color.Purple,Color.Peru,    Color.PaleGreen,Color.Orchid,Color.Olive};
    Để tạo một Brush để vẽ dựa theo màu, bạn dùng đối tượng SolidBrush. Như trong đoạn mã trên:


    Mã:
    e.Graphics.DrawString(s,    this.Font, new SolidBrush(_foreColors[ số mìn của ô đang xét - 1]),        x + (CELL_SIZE - size.Width) / 2, y + (CELL_SIZE - size.Height) / 2);
    Xử lý thao tác người dùng


    Mã:
    protected override void OnMouseDown(MouseEventArgs e){    if (!_board._IsLost && !_board._IsFinish)    {        int c = e.X / CELL_SIZE;        int r = e.Y / CELL_SIZE;         if (_board[r, c].IsOpened)            return;                              if (!_board[r, c].IsFlag && e.Button == MouseButtons.Left)        {            _board[r, c].IsMarked = false;            if (_board.OpenCell(r, c))            {                _board._IsLost = true;                 pictureBox1.Left = e.X - pictureBox1.Width / 2;                pictureBox1.Top = e.Y - pictureBox1.Height / 2;                pictureBox1.Visible = true;                timer1.Enabled = true;                // Dùng cho event                OnMinesExplode();             }            else            {                // Win                if (RemainCellsCount == MinesCount)                {                    _board._IsFinish = true;                     // Cắm cờ tất cả ô còn lại                    for (int i = 0; i < Rows; i++)                    {                        for (int j = 0; j < Cols; j++)                        {                            if (!_board[i, j].IsOpened)                                _board[i, j].IsFlag = true;                        }                    }                }            }             Invalidate();        }        else if (e.Button == MouseButtons.Right)        {            if (_board[r, c].IsMarked)            {                _board[r, c].IsMarked = false;            }            else            {                _board[r, c].IsFlag = !_board[r, c].IsFlag;                _board[r, c].IsMarked = !_board[r, c].IsFlag;                 if (_board[r, c].IsFlag)                    _board._FlagsCount++;                else                    _board._FlagsCount--;            }            Invalidate();         }        // Dùng cho event        OnCellClick();    }    base.OnMouseDown(e);}
    Thêm các Event

    Để hoàn tất UserControl này, bạn cần thêm các event để từ Form ta có thể xử lý trong những trường hợp như đạp trúng mìn, mở một ô,... Đây là đoạn mã lệnh tương ứng để tạo các event cho lớp này. Bạn có thể thấy chúng được kích hoạt trong đoạn mã OnMouseDown trên:


    Mã:
    public event EventHandler CellClick;public event EventHandler MinesExplode;   #region CustomEvent private void OnCellClick(){    if (CellClick != null)        CellClick(this, null);}protected void OnMinesExplode(){    if (MinesExplode != null)        MinesExplode(this, null);} #endregion
    <font size="3"><font color="DarkOrange">Bài tập cho người đọc


    Với những gì tôi trình bày ở trên, bạn có thể theo ý tưởng đó và tạo ra một game Dò mìn mà không cần source code đầy đủ của project, chỉ mất khoảng 3,4 tiếng để bạn có thể hoàn thành phần cơ bản.
    Project tôi cung cấp ở đầu bài chưa hoàn chỉnh và còn thiếu chức năng đếm giờ, lưu điểm người chơi. Ngoài hai chức năng trên bạn có thể làm thêm một số yêu cầu như bài tập để tăng cường kĩ năng lập trình của mình.
    - Thêm chức năng tính giờ cho chương trình bằng cách dùng Timer.
    - Thêm chức năng lưu điểm người chơi bằng file text hoặc file nhị phân. Hiển thị những người chơi có số điểm cao nhất (cài đặt bằng một loại collection).
    - Lưu trạng thái của trò chơi để có thể chơi lại lần sau (tương tự cách trên)/
    - Cho phép người dùng thay đổi hình nền của bảng dò mìn, sử dụng thuộc tính BackgroundImage hoặc dùng Graphisc để vẽ trực tiếp.
    - Thêm dòng chữ “Congratulation” và “Game Over” khi người chơi thắng hoặc thua lên màn hình (sử dụng picturebox chứa ảnh gif).
    - Sử dụng hàm ShellAbout của Windows API để hiển thị hôp thoại About như hình dưới. Khai báo hàm này như sau:


    Mã:
    [DllImport("shell32.dll")]        static extern int ShellAbout(IntPtr hWnd, string szApp, string szOtherStuff, IntPtr hIcon);
    Bạn có thể xem thêm bài này để tìm hiểu về attribute DllImport này.</font></font>

  5. #5
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Hay hay! Mình cũng muốn làm 1 game nho nhỏ nhưng mà ý tưởng không có, trình độ thì kém. Qua bài viết này cũng biết được chút ít về cách lập trình game, nhưng chắc xem ra phải vài tháng nữa mới làm được. [IMG]images/smilies/biggrin.png[/IMG].
    Cho mình hỏi, cái giao diện bạn vẽ bằng code C# đúng không? Thế làm cách nào để vẽ nó từ 1 file ảnh, sau đó cắt file ảnh đó thành các hàng, cột rồi đưa vào form giống như game xếp hình.

  6. #6
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    good job !
    i like it [IMG]images/smilies/wink.png[/IMG]

    ps: hi vọng sắp tới có phần mềm dò "hot girl" [IMG]images/smilies/biggrin.png[/IMG]

  7. #7
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    Bài viết rất hay!
    Thank you very much!

  8. #8
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    mình rất thích chơi trò này, thiếu chức năng clink 2 chuột

  9. #9
    Trích dẫn Gửi bởi snake_programmer
    Hay hay! Mình cũng muốn làm 1 game nho nhỏ nhưng mà ý tưởng không có, trình độ thì kém. Qua bài viết này cũng biết được chút ít về cách lập trình game, nhưng chắc xem ra phải vài tháng nữa mới làm được. [IMG]images/smilies/biggrin.png[/IMG].
    Cho mình hỏi, cái giao diện bạn vẽ bằng code C# đúng không? Thế làm cách nào để vẽ nó từ 1 file ảnh, sau đó cắt file ảnh đó thành các hàng, cột rồi đưa vào form giống như game xếp hình.
    phương thức DrawImage() có tham số là 1 hcn xác định vị trí với kích thước đó, bạn cứ tăng dần theo vòng lặp là được

    @iloveit: Ko bít chức năng click 2 chuột sử dụng như nào [IMG]images/smilies/17.gif[/IMG]

  10. #10
    Ngày tham gia
    Sep 2015
    Bài viết
    0
    em đã down load code về rồi. Nhưng mà không biết mở thế nào. Em mới học c# nên chưa biết làm thế nào để mở nó bây giờ. em dùng vst2005

 

 
Trang 1 của 3 123 CuốiCuối

Quyền viết bài

  • Bạn Không thể gửi Chủ đề mới
  • Bạn Không thể Gửi trả lời
  • Bạn Không thể Gửi file đính kèm
  • Bạn Không thể Sửa bài viết của mình
  •