Tích chất của game: Tính logic: Trò chơi dựa trên cơ sở tính toán sự phân phối trong không gian các quả mìn dựa trên dữ kiện là các con số hiện ra chỉ số lượng quả mìn kề với một ô..
Trang 1TRƯỜNG ĐẠI HỌC GIAO THÔNG VẬN TẢI TP HỒ CHÍ MINH
KHOA CÔNG NGHỆ THÔNG TIN
Báo cáo dự án: Bài 6.
Viết chương trình trò chơi dò mìn
(Mines) bằng C++
Project:
Minesw
eeper with C++
Trang 2Chương 1: Xử lí và mô tả đề bài
Phần I: Giới thiệu về game Mines
Giới thiệu cơ bản: Minesweeper là một thể loại trò chơi điện tử giải đố
logic cổ điển, phát hành với phiên bản đầu tiên “Microsoft Minesweeper” của những năm 1990 của thế kỉ trước, khi nền công nghiệp game vừa mới chớm nở Game nổi tiếng và quen thuộc với thế hệ 8x, 9x với cái tên là Dò mìn và thường được chơi những con điện thoại hay máy tính cá nhân đời cũ
Dò mìn (minesweeper) được phát triển ban đầu như 1 trò chơi giáo dục với mục đích giúp người chơi rèn luyện khả năng tư duy logic và đã nhanh chóng trở thành hiện tượng vào thời điểm đó bởi nguyên tắc đơn giản dễ tiếp cận, mang tính thách thức cao và phù hợp với nhiều độ tuổi
Tích chất của game:
Tính logic: Trò chơi dựa trên cơ sở tính toán sự phân phối (trong không gian) các quả mìn dựa trên dữ kiện là các con số hiện ra (chỉ số lượng quả mìn kề với một ô)
Tính ngẫu nhiên: Trong một số trường hợp, không thể xác định chính xác
vị trí của mìn chỉ dựa vào những con số gợi ý; do đó trò chơi mang tính may rủi
Biến thể của game: với sự phát triển của ngành game, hiện nay đã có nhiều
biến thể của Mines, ví dụ như:
3D Minesweeper với bãi mìn 3 chiều
HexMines, dò mìn với ô hình lục giác và Triangular Minesweeper với ô hình tam giác
Dò mìn với bảng chơi không phải bảng vuông hay chữ nhật mà là các hình dạng khác như hình tròn, tứ giác…
Ngoài ra còn có nhiều biến thể với giao diện và cách chơi hấp dẫn
Phần II: Mô tả chi tiết đề tài:
1 Luật chơi:
o Trong Minesweeper, giao diện game sẽ mở ra 1 bảng hình vuông (hình chữ nhật) chia thành các ô vuông nhỏ gọi là bãi mìn, cơ bản gồm các loại:
Các ô chưa mở (che bảng khi bắt đầu trò chơi, cũng có thể được tạo
bằng cách xóa cờ)
Trang 3 Các ô được đánh số (có thể hiển thị 1-8)
Ô trống (không có mỏ nào nằm trên đường ngang, chéo/liền kề với ô)
Ô được gắn cờ (xuất hiện sau khi nhấp chuột phải vào ô chưa mở)
o Người chơi chọn một ô để mở nó Nếu người chơi mở ô chứa mìn, trò chơi
sẽ kết thúc với thông báo thất bại Nếu không, ô đã mở sẽ hiển thị một số, cho biết số lượng mìn hoặc một ô trống (đôi khi được hiển thị là 0) và tất cả các ô trống (0) liền kề sẽ tự động được mở.S ố trên một ô là lượng mìn trong
8 ô nằm quanh với ô đó
o Nếu chắc chắn một ô có mìn, người chơi có thể đánh dấu vào ô đó với hình
lá cờ bằng cách click chuột phải Các ô được gắn cờ vẫn được coi là chưa
mở và có thể hủy gắn cờ nếu nhận thấy đây không phải là ô có mìn
o Nếu những ô lân cận của một ô đã có đủ số mìn mà vẫn còn các ô trống khác thì những ô đó không có mìn
o Trò chơi kết thúc với phần thắng dành cho người chơi nếu mở được tất cả các ô không có mìn
2 Mục tiêu và chiến lược:
Trò chơi Dò mìn bắt đầu khi người chơi lần đầu tiên chọn một ô trên
bảng Trong một số biến thể, lần nhấp đầu tiên được đảm bảo an toàn và một số biến thể còn đảm bảo rằng tất cả các ô lân cận cũng an toàn Trong trò chơi, người chơi sử dụng thông tin được cung cấp từ các ô đã mở để suy ra các ô tiếp theo an toàn để mở, lặp đi lặp lại để thu thập thêm thông tin để giải bảng Người chơi cũng được cung cấp số lượng mìn còn lại trong bảng, được gọi là số lượng mìn , được tính bằng tổng số lượng mìn trừ đi số lượng ô được gắn cờ (do đó số lượng mìn có thể âm nếu đặt quá nhiều cờ)
Để thắng trò chơi Minesweeper, tất cả các ô không phải mìn phải được mở Game không có điểm số, tuy nhiên có một bộ đếm thời gian ghi lại thời gian hoàn thành trò chơi Độ khó có thể tăng lên bằng cách thêm mìn hoặc bắt đầu với bảng lớn hơn Hầu hết các biến thể của Minesweeper không được chơi trên một bảng cố định mà được đều cung cấp ba mức độ bảng, thường được gọi là Dễ , Trung bình
và Khó, theo thứ tự độ khó tăng dần Người mới bắt đầu thường chơi trên bảng 8x8 hoặc 9x9 chứa 10 mìn, Trung bình thường chơi trên bảng 16x16 với 40 mìn và Khó thường chơi trên bảng 30x16 với 99 mìn, tuy nhiên thường có tùy chọn để tùy chỉnh kích thước bảng và số lượng mỏ
3 Đầu vào và đầu ra:
Trang 4o Input:
Người chơi có thể nhập kích thước của bảng trò chơi (NxN) và số lượng mìn cần đặt trên bảng hoặc lựa chọn mức độ khó phù hợp
Thao tác chọn ô để mở/ đặt cờ/ hủy cờ
Tiếp tục chơi hoặc thoát sau khi kết thúc màn chơi
o Output:
Hiển thị giao diện đồ họa với bảng trò chơi và các ô ẩn và sau khi tương tác thì các ô hiện số, hiện cờ và số cờ còn lại hoặc hiện mìn
Thông báo về kết quả khi trò chơi kết thúc: thắng hoặc thua
Hiển thị thời gian người chơi đã mất để hoàn thành trò chơi (nếu cần) hoặc điểm người chơi đạt được với số ô đã mở
4 Khai báo dữ liệu:
o Hằng số: kích cỡ tối đa của bảng, số mìn tối đa, các hằng số chỉ cấp độ khó,
số ô có thể di chuyển mà không chứa mìn
o Biến toàn cục : số mìn (int), thông số kích thước (int)
o Một số biến cục bộ quan trọng: hàng, cột (int); bảng chơi (char kiểu mảng 2 chiều); bảng vị trí mìn (int kiểu mảng 2 chiều); số lượng ô có thể di chuyển còn lại (int)
5 Thuật toán: gồm 1 số thuật toán cơ bảng sau:
o Thuật toán loang (Recursive Backtracking) một trong những thuật toán quan trọng được áp dụng để tạo bảng chơi; mở các ô trống (không chứa mìn) và hiển thị số lượng mìn xung quanh các ô; quản lí và kiểm tra trạng thái trò chơi …Gồm các bước:
Chọn 1 lựa chọn: Bắt đầu với một lựa chọn.
Thử nghiệm lựa chọn: Kiểm tra xem lựa chọn có hợp lệ hay không.
Đệ quy: Nếu lựa chọn hợp lệ, thực hiện đệ quy để thử nghiệm các lựa chọn tiếp theo.
Quay lại: Nếu tất cả các lựa chọn đã được thử nghiệm mà không tìm ra giải pháp, quay lại và thử nghiệm lựa chọn khác.
o Thuật toán đặt mìn ngẫu nhiên: Trước khi người chơi thực hiện bất kỳ động tác nào, trò chơi thường sử dụng thuật toán random để đặt mìn ngẫu nhiên trên bảng Điều này đảm bảo tính ngẫu nhiên và tính công bằng của trò chơi
Trang 5o Thuật toán đếm mìn xung quanh: Khi người chơi mở một ô trống, cần kiểm tra và hiển thị số lượng mìn xung quanh ô đó Thuật toán này sử dụng vòng lặp để kiểm tra 8 ô xung quanh mỗi ô
6 Lên kế hoạch kĩ thuật thực hiện_các chức năng cần có:
o Tạo bảng chơi: vì giao diện game sẽ mở ra 1 bảng hình vuông (hình chữ nhật) chia thành các ô vuông nhỏ nên trước hết ta cấu trúc mảng 2 chiều (ma trận ) để tạo bảng chơi
o Đặt mìn ngẫu nhiên: mỗi bảng chơi phải sử dụng 1 bản đồ mìn riêng để tăng tính thú vị và tái sử dụng nên ta cần hàm ‘rand()’ của thư viện <cstdlib>
o Đếm số mìn xung quanh một ô: sử dụng vào lặp và duyệt mảng lần lượt các
ô xung quanh theo đường dọc, ngang và chéo, sau đó đánh số các ô
o Ẩn tất cả các ô: sao chép dữ liệu bảng đã dặt mìn và đánh số vào 1 bảng mới, trong bảng mới các ô được đặt là ‘-’ để ẩn, khi mở thì thao chiếu vào
dữ liệu bảng ban đầu để xác định
o Giao diện tương tác với người chơi: dung cout, cin và hàm điều kiện để xác định yêu cầu người chơi ví dụ: mở ô, đặt cờ hoặc hủy cờ với cấu trúc if hoặc switch; kiểm tra kết thúc trò chơi; hiển thị kết quả
o Các hàm hỗ trợ, các hằng số và 1 số chức năng bổ trợ: ví dụ như: Chọn độ khó; Đặt cờ đánh dấu, Hiện thị số cờ (số mìn) còn lại và Hàm chơi lại hay thoát…
Chương 2: Cấu trúc dự án (Giải thích về Source Code)
I Thực hiện_Giải thích chức năng chương trình
1 Các thư viện đã sử dụng:
<iostream> : thư viện chuẩn trong C++, sử dụng để đọc và ghi dữ liệu, như cin (đầu vào) và cout (đầu ra)
<cstdlid>: được sử dụng để dùng hàm rand() để sinh số ngẫu nhiên
Trang 6 <ctime>: sử dụng để thiết lập seed cho hàm rand() dựa trên thời gian hiện tại, đảm bảo mỗi lần chạy chương trình là mỗi bản đồ mìn ngẫu nhiên
<cstring>: dùng để đặt một giá trị cụ thể vào một số lượng bytes trong vùng nhớ được chỉ định hay lưu bản đồ mìn và số để tham chiếu (dùng trong hàm placeMines )
<iomanip>: cung cấp các định dạng đặc biệt cho đầu ra (output), cụ thể bằng cách sử dụng hàm setw (dùng trong hàm printBoard )
2 Các hằng số và biến toàn cục
-Hằng
‘BEGINNER’, ‘INTERMEDIA’ , ‘ADVANCED’: các hằng số biểu diễn cấp độ trò chơi tương ứng dễ, trung bình, khó với số mìn và số ô tăng tương ứng
‘MAXSIZE’ : kích thước tối đa mỗi chiều của bảng chơi dò mìn
‘MAXMINES’: số mìn tối đa của trò chơi
‘MOVESIZE’: đại diện cho kích thước của mảng lưu trữ các bước di chuyển trong trò chơi Trong trường hợp này, ‘MOVESIZE’ được tính bằng cách trừ
đi số lượng mìn từ tổng số ô trên bảng
-Biến toàn cục
‘SIDE’: sử dụng để lưu trữ kích thước của mỗi chiều của bảng trò chơi Minesweeper
‘MINES’: sử dụng để lưu trữ số lượng mìn trong trò chơi
3 Các hàm đã sử dụng:
o ‘isValid (int row, int col)’
- Code:
bool isValid(int row, int col)
{
return (row >= 0) && (row < SIDE) && (col >= 0) && (col < SIDE);
}
Trang 7- Chức năng: Hàm này kiểm tra tính hợp lệ một ô với hàng “row ”và cột “col
”có nằm trong giới hạn của bảng trò chơi hay không Trả về “true”nếu hợp lệ
và “false” nếu không
-Vị trí: Sử dụng trong hàm ‘countAdjacentMines’
o ‘isMine (int row, int col, char board[][MAXSIDE])’
- Code
bool isMine(int row, int col, char board[][MAXSIDE])
{
if (board[row][col] == '*')
return (true);
else
return (false);
}
- Chức năng: Kiểm tra xem ô có chứa mìn hay không thông qua cấu trúc điều kiện để kiểm tra xem ô có kí tự ‘*’ không Trả về “true”nếu có và
“false” nếu không
- Vị trí: trong hàm countAdjacetMines
o ‘makeMove (int &x, int &y, char myBoard[][MAXSIDE], int
&FLAGS)’
- Code:
void makeMove(int &x, int &y, char myBoard[][MAXSIDE], int &FLAGS)
{
cout << "Nhap vi tri o muon mo hoac dat co (dong, cot) -> ";
cin >> x >> y;
cout << "Chon thao tac (0: mo o, 1: dat co, 2: huy co) -> ";
int action;
cin >> action;
if (action == 1 && myBoard[x][y] == '-' && FLAGS > 0)
{
Trang 8myBoard[x][y] = 'F'; // Dat co
FLAGS ;
}
else if (action == 2 && myBoard[x][y] == 'F')
{
myBoard[x][y] = '-'; // Huy co
FLAGS++;
}
else if (action == 0)
{
// Thuc hien mo o
}
else
{
cout << "Khong the thuc hien thao tac tai vi tri da chon.\n";
makeMove(x, y, myBoard, FLAGS); // Yeu cau nhap lai
}
}
- Chức năng: cho phép người chơi chọn một ô để mở hoặc đặt/hủy cờ thông qua cấu trúc điều kiện:
action==1 và myBoard[x][y]==’-’ và FLAGS>0 thì đặt cờ vào ô và giảm số lượng cờ
action==2 và myBoard[x][y]==’F’ thì hủy cờ, mở ô và tăng số lượng cờ
action==0 thực hiện các bước mở ô
Nếu thao tác không hợp lệ hoặc không thể thực hiện tại vị trí đã chọn, yêu cầu người chơi nhập lại
-Các đối số:
x: Tham chiếu đến biến lưu hàng của ô người chơi chọn
y: Tham chiếu đến biến lưu cột của ô người chơi chọn
‘myBoard’: Mảng 2D biểu diễn bảng trò chơi hiện tại của người chơi
‘FLAGS’: Tham chiếu đến biến lưu số lượng cờ còn lại
-Vị trí: trong hàm ‘playMinesweeperUtil’
o ‘printBoard (char myBoard[][MAXSIDE])’
- Code:
void printBoard(char myBoard[][MAXSIDE])
{
int i, j;
Trang 9cout << " ";
for (i = 0; i < SIDE; i++)
cout << setw(2) << i << " "; //su dung setw de dat do rong co dinh moi cot cout << endl;
for (i = 0; i < SIDE; i++)
{
cout << " +";
for (j = 0; j < SIDE; j++)
cout << " -+";
cout << endl;
cout << setw(2) << i << " |";
for (j = 0; j < SIDE; j++)
cout << " " << myBoard[i][j] << " |";
cout << endl;
}
cout << " +";
for (i = 0; i < SIDE; i++)
cout << " -+";
cout << endl;
}
- Chức năng: Hàm này in ra bảng hiện tại của người chơi ra màn hình console Nó hiển thị trạng thái của từng ô trên bảng, bao gồm cả ô đã mở
và ô còn đóng Có sử dụng hàm setw() để đặt độ rộng cố định của cột
In ra hàng số của mỗi cột để dễ theo dõi, dùng vòng lặp for
Duyệt qua từng ô trên bảng và in ra trạng thái của ô đó, dùng vòng lặp for
Sử dụng các dấu cách và ký tự đặc biệt để tạo ra bảng có định dạng
- Vị trí: trong hàm ‘playMinesweeper’ và ‘playMinesweeperUtil’
o ‘chooseDifficultyLevel()’
- Code:
void chooseDifficultyLevel()
{
int level;
cout << "Nhap cap do kho\n";
cout << "Chon 0 cho BEGINNER_De (9 * 9 o va 10 min)\n";
Trang 10cout << "Chon 1 cho INTERMEDIATE_TrungBinh (16 * 16 o va 40 min)\n"; cout << "Chon 2 cho ADVANCED_Kho (24 * 24 o va 99 min)\n";
cin >> level;
if (level == BEGINNER)
{
SIDE = 9;
MINES = 10;
}
else if (level == INTERMEDIATE)
{
SIDE = 16;
MINES = 40;
}
else if (level == ADVANCED)
{
SIDE = 24;
MINES = 99;
}
else
{
cout << "Cap do khong hop le Tu dong chon BEGINNER.\n";
SIDE = 9;
MINES = 10;
}
}
- Chức năng: tạo giao diện lựa chọn độ khó với người chơi, chọn 1 trong 3 mỗi lựa chọn có số ô và số mìn trong bảng tăng dần
- Vị trí: hàm main()
o ’ placeMines(int mines[][2], char realBoard[][MAXSIDE])’
- Code:
void placeMines(int mines[][2], char realBoard[][MAXSIDE])
{
bool mark[MAXSIDE * MAXSIDE];
memset(mark, false, sizeof(mark));
for (int i = 0; i < MINES;)
{
int random = rand() % (SIDE * SIDE);
int x = random / SIDE;
int y = random % SIDE;
Trang 11if (!mark[random])
{
mines[i][0] = x;
mines[i][1] = y;
realBoard[mines[i][0]][mines[i][1]] = '*';
mark[random] = true;
i++;
}
}
}
- Chức năng: được sử dụng để đặt mìn ngẫu nhiên trên bảng và lưu lại vị trí của chúng Gồm các thao tác:
Tạo 1 mảng boolean để đánh dấu các ô trên bảng, sau đó khởi tạo mảng ‘mark’ với giá trị false => ngăn chặn việc đặt mìn 1 ô nhiều lần
Sử dụng hàm ‘rand()’để tạo số ngẫu nhiên từ 0 đến SIDE*SIDE
Chuyển đổi số ngẫu nhiên thành vị trí trên bảng (hàng và cột), chia lấy nguyên cho SIDE để lấy hang x, và chia lấy dư cho SIDE để lấy cột y
Kiểm tra xem vị trí đã được chọn chưa bằng cách kiểm tra giá trị của mảng ‘mark’ tại vị trí đó, nếu false ( tức chưa chọn )thì chuyển qua bước tiếp
Đặt mìn và lưu lại vào mảng ‘mines’ và đặt kí tự ‘*’ vào bảng realBoard, đánh dấu vị trí mark là ‘true’
Lặp lại 2 bước trên tới khi đủ số mìn với vòng lặp for
- Vị trí: hàm ‘initialise’
o initialise(char realBoard[][MAXSIDE], char myBoard[][MAXSIDE], int mines[][2])
- Code:
void initialise(char realBoard[][MAXSIDE], char myBoard[][MAXSIDE], int mines[][2])
{
srand(time(NULL));
Trang 12for (int i = 0; i < SIDE; i++)
{
for (int j = 0; j < SIDE; j++)
{
myBoard[i][j] = realBoard[i][j] = '-';
}
}
placeMines(mines, realBoard);
}
- Chức năng: thực hiện các bước khởi tạo cần thiết cho Minesweeper, bao gồm việc đặt mìn, khởi tạo bảng trạng thái thực và bảng hiển thị của người chơi Gồm các thao tác:
Sử dụng ‘srand(time(NULL))’ để khởi tạo seed cho hàm ‘rand()’ Seed này thường được sử dụng để tạo ra các số ngẫu nhiên mỗi lần chương trình chạy, giúp tránh trường hợp các số ngẫu nhiên giống nhau
Thiết lập trạng thái ban đầu cho cả bảng thực và bảng hiển thị của người chơi bằng cách đặt ký tự ‘ - ’ tại mọi vị trí
- Vị trí: hàm playMinesweeper
o countAdjacentMines(int row, int col, int mines[][2], char realBoard[] [MAXSIDE])
- Code:
int countAdjacentMines(int row, int col, int mines[][2], char realBoard[]
[MAXSIDE])
{
int i;
int count = 0;
int directions[][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {-1, -1}, {-1, 1}, {1, -1}, {1, 1}};
for (i = 0; i < 8; ++i)
{
int newRow = row + directions[i][0];
int newCol = col + directions[i][1];
if (isValid(newRow, newCol) && isMine(newRow, newCol, realBoard))