Để tổng kết cho khóa học lập trình C mang tên “Học C Bá Đạo” của Lập trình không khó, hôm nay mình sẽ cùng các bạn thực hành một bài tập tổng hợp cũng là bài tập lớn kết thúc khóa học này. Mình sẽ cùng các bạn làm game rắn săn mồi trên màn hình console sử dụng ngôn ngữ lập trình C. Kết quả của sản phẩm hoàn thiện trông như thế này đây
Lưu ý:Video demo của game rắn săn mồi này chưa có tính năng dùng chuột điều khiển, lỗi xuất hiện trong video đã được xử lý triệt để.
Đây là source code bài tập lớn môn Đồ họa máy tính thời sinh viên của mình. Do là thời sinh viên nên code chưa được tối ưu nhưng đó không phải vấn đề lớn. Mục tiêu của bài này là mình giúp được các bạn nắm được ý tưởng, các thức để xây dựng và vận hành một trò chơi đơn giản. Chúng ta cùng vào bài nhé.
Các kiến thức yêu cầu của bài học
Để hoàn thành trò chơi rắn săn mồi này, chúng ta cần rất nhiều các kiến thức. Tuy nhiên bạn không cần bắt buộc phải nắm được các kiến thức dưới đây mà chỉ cần hoàn thành khóa học lập trình C của mình là có thể lao vào chiến đấu được rồi. Khi gặp kiến thức mới các bạn cần tự tìm hiểu bổ sung nhé. Dưới đây là các kiến thức cần có mình liệt kê ra để các bạn chuẩn bị trước, hoặc khi dùng tới nó thì tìm hiểu:
- Kiến thức lập trình C & từ duy lập trình cơ bản – và khóa học C của mình đã cho bạn điều đó.
- Kiến thức lập trình đồ họa trong C, thư viện graphics.h/ winbgim.h (xem Chương 9 tài liệu lập Kỹ thuật lập trình C thầy Ất, mình để ở tài liệu tham khảo ở bài 1)
- Kỹ thuật đọc ghi file
- Thao tác với bàn phím và chuột sử dụng ngôn ngữ C
Giới thiệu về game rắn săn mồi
Trò chơi rắn săn mồi quá quen thuộc với các bạn rồi, mình sẽ không trình bày đó là trò chơi gì. Nhưng mình cần trình bày lại trò chơi này cho các bạn ở khía cạnh kỹ thuật, để từ đó ta tìm ra giải pháp xây dựng trò chơi:
Từng bước mô hình hóa game rắn săn mồi
Con rắn thực tế và rắn trong game
Con rắn săn mồi của chúng ta sẽ là một chuỗi các hình tròn nhỏ (các đốt của con rắn) nối lại với nhau (số hình tròn nhỏ chính là độ dài của con rắn). Khởi tạo trò chơi, chúng ta có thể đặt độ dài ban đầu của nó là 3 chẳng hạn. Trong quá trình trò chơi diễn ra, ta phải lưu vết được tọa độ của từng hình tròn đó.
Tại mỗi bước dịch chuyển của rắn, mỗi đốt thân của rắn sẽ dịch chuyển đi 1 đơn vị độ dài bằng nhau. Trong đó, đốt thân đầu tiên (đầu của rắn) sẽ tiến lên theo hướng dịch chuyển, các đốt thân phía sau di chuyển đến vị trí cũ của đốt thân phía trước nó. Ví dụ:
Giả sử con rắn có 3 đốt và tọa độ của nó hiện tại là: x1(3,0) – đầu, x2(2,0) và x3(1,0) và đang đi theo hướng trục Ox. Bây giờ rắn đổi hướng di chuyển sang bên trái. Khi đó tọa độ mới của từng đốt là: x1(3,-1), x2 sẽ là tọa độ của x1 cũ (3, 0) và x3 chuyển sang vị trí của x2 cũ là (2, 0).
Đưa các đối tượng của game lên ngôn ngữ lập trình
Như vậy, ta sẽ xây dựng một đối tượng Điểm. Đối tượng này giúp ta lưu được tọa độ của một điểm trên trục tọa độ 2 chiều Oxy. Ta có cấu trúc Điểm như sau:
struct Point { int x, y; int x0, y0; };
Khi đó:
- Một con rắn sẽ là 1 mảng các đối tượng
Point
tương ứng với các đốt của rắn. Tại mỗi đốt, cặp tọa độ (x,y) sẽ lưu vị trí đốt hiện tại và cặp tọa độ (x0,y0) sẽ lưu ví trí trước đó của đốt hiện tại để các đốt sau đó của con rắn có thể sử dụng. - Mỗi đối tượng thức ăn sẽ là 1 đối tượng
Point
. Ta chỉ cần sử dụng cặp biến (x, y) để lưu 1 đối tượng thức ăn. Tại một thời điểm trên màn hình chỉ có 1 thức ăn, và thức ăn đó xuất hiện ở 1 vị trí ngẫu nhiên bất kỳ. - Để rắn di chuyển được trên màn hình thì ta cần thêm một biến lưu hướng đi của nó. Ta sẽ tận dụng luôn đối tượng Point để xác định hướng theo tọa độ (x,y). Ví dụ nếu rắn đang di chuyển theo hướng trái sang phải, như vậy đối tượng direction của ta sẽ là (10, 0). Tức là ở mỗi bước đi, tọa độ x sẽ tăng thêm 10 đơn vị và tọa độ y không đổi. Khi ta thay đổi hướng đi, ta chỉ cần thay đổi giá trị (x, y) của đối tượng
direction
như hình dưới đây:
Mô hình hóa cách di chuyển của rắn
Giả sử rắn của chúng ta có chiều dài tối đa là 100 (chơi lên 100 chắc khủng lắm :v), ta sẽ khai báo mảng Point
:
Point snake[100]; // con rắn của chúng ta Point direction; // hướng đi hiện tại của rắn. Point food; // đối tượng thức ăn const int DIRECTION = 10; // khoảng cách di chuyển
Vậy để di chuyển con rắn, ta cần di chuyển đầu của con rắn trước tiên:
snake[0].x += direction.x; snake[0].y += direction.y;
Nhưng trước đó ta phải lưu lại tọa độ cũ của nó để đốt kế sau nó lần theo:
snake[0].x0 = snake[0].x; snake[0].y0 = snake[0].y;
Vậy tổng quát, để di chuyển toàn bộ phần thân còn lại của rắn ta sẽ chạy một vòng lặp hết phần thân rắn và thực hiện:
for (int i = 0;i < snakeLength;i++){ # Nếu là đầu rắn if (i == 0){ snake[0].x0 = snake[0].x;snake[0].y0 = snake[0].y; snake[0].x += direction.x; snake[0].y += direction.y; } else { snake[i].x0 = snake[i].x;snake[i].y0 = snake[i].y; snake[i].x = snake[i-1].x0;snake[i].y = snake[i-1].y0; } }
Rắn sẽ ăn được thức ăn khi tọa độ đầu snake[0]
trùng với tọa độ thức ăn food
. Khi đó ta cần :
- Tăng chiều dài của rắn
- Khởi tạo ngẫu nhiên tọa độ thức ăn mới – Lưu ý không khởi tạo trùng vào các đốt thân của rắn.
- Tăng điểm số (game phải có điểm chứ)
Nếu đầu Rắn chạm tường hoặc chạm thân thì game kết thúc ( Chế độ chơi hiện đại) và chỉ chết khi chạm vào thân (chế độ chơi cổ điển)
Tính năng lưu điểm cao trong game
Để tăng tính hấp dẫn cho game rắn săn mồi, ta tạo thêm một đối tượng HighScore lưu thông tin Tên người chơi và Số điểm đạt được nếu người chơi đó đạt điểm cao.
struct HighScore { int score; char name[30]; };
Thông tin điểm cao sẽ được ghi ra một file txt và lưu lại để so sánh với các người chơi sau đó. Nếu người chơi mới đạt điểm cao hơn trong danh sách đã lưu thì sẽ thay thế thành tích của người chơi cũ tương ứng bởi thành tích của người chơi mới.
QC: Nếu bạn có nhu cầu thuê thiết kế web, ứng dụng hay crawler tool thì hãy tham khảo Dịch vụ code thuê úy tín của Lập trình Không khó nhé.
Các hàm trong game rắn săn mồi
Mình sẽ không đi sâu vào giải thích từng dòng code cho các bạn được. Thay vào đó, mình chỉ cho các bạn các đối tượng của game rắn săn mồi này, các chức năng cần xây dựng của game và cách thức vận hành game. Thông qua đó bạn đọc cố gắng tìm hiểu thêm để hoàn thiện game dựa trên ý tưởng của mình. Các bạn cũng có thể tham khảo source code của mình (hiểu được thì càng tốt :v).
Các biến toàn cục trong trò chơi
int level; # level của game , đi nhanh hay đi chậm đó bool endGame; # Lưu trạng thái của game: kết thúc hay chưa int snakeLength; # Độ dài hiện tại của rắn Point snake[100]; # Con rắn Point direction; # Hướng đi Point food; # Đồ ăn const int DIRECTION = 10; # Khoảng cách của 1 lần dịch chuyển HighScore highscore[5]; # Lưu 5 người chơi có điểm cao nhất
Danh sách các hàm mình xây dựng cho trò chơi rắn săn mồi này:
void initGame (); bool checkPoint (); void drawPoint (int x,int y,int r); void moveSnake (); void drawSnake (); void drawFood (); void drawGame (); void classic(); void modern(); void mainLoop (void (*gloop)()); void run (); void changeDirecton (int x); void showHighScore(); void getHighScore(); void checkHighScore(int score); void initScore(); bool isEmpty(); void showText(int x,int y,char *str); void showTextBackground(int x,int y,char *str,int color);
Các hàm và vai trò của nó
void initGame ();
- Vẽ khung của trò chơi
- Khởi tạo tọa độ ban đầu cho Snake (Gồm 3 Point) và Food
- Khởi tạo hướng đi ban đầu
- Vẽ màu backgound và các đối tượng hiển thị không thay đổi vị trí.
bool checkPoint ();
Hàm này có chức năng kiểm tra trùng lặp khi tạo mới đối tượng Food. Kiểm tra chắc chắn rằng tọa độ thức ăn không trùng với thân Rắn
void drawPoint (int x,int y,int r);
Hàm này có chức năng vẽ một đối tượng Point (hình tròn) lên màn hình đồ họa có tọa độ tâm (x,y) và có bán kính r
void drawSnake ();
Hàm này gọi làm hàm drawPoint () có các chức năng :
- Vẽ đối tượng Snake lên màn hình đồ họa.
- Xóa bỏ đốt cuối cùng của thân rắn mỗi khi rắn di chuyển được thêm một bước.
void drawFood ();
Hàm này gọi lại hàm drawPoint () và hàm checkPoint (); để thực hiện nhiệm vụ hiển thị thức ăn lên màn hình đồ họa.
void drawGame ();
Hàm này gọi lại hai hàm drawSnake() và hàm drawFood() và thực hiện thêm công việc hiển thị điểm của người chơi lên màn hình.
void classic();
Hàm này thực hiện chức năng chơi cổ điển – Người chơi có thể đi xuyên tường.
Chức năng của hàm:
- Di chuyển Rắn theo điều khiển hướng đi
- Nếu Rắn đi ra ngoài mép tường – cho rắn xuất hiện lại ở phía bên kia
- Kiểm tra rắn ăn mồi : tăng chiều dài, điểm số và random lại tọa độ thức ăn
- Kiểm tra game kết thúc
void modern();
Hàm này tương tự hàm classic() nhưng thay vào đó rắn không thể đi xuyên tường và đó là một trong những điều kiện bổ sung vào kiểm tra game kết thúc của hàm này.
void changeDirecton (int x);
x là biến kiểu nguyên lưu giữ giá trị ASCII tương ứng của của phím ở kiểu int. Đây là hàm thực thi việc nhận lệnh điều khiển hướng đi của rắn từ bàn phím, cụ thể là các phím mũi tên điều hướng và phím ESC. Hàm sẽ nhận vào mã phím nhập từ bàn phím và thực hiện thay đổi hướng đi theo mã phím tương ứng. Nếu người dùng nhấn phím ESC – Game sẽ kết thúc.
void mainLoop (void (*gloop)());
Hàm này chính là hàm lặp cho phép game chạy và thực thi toàn bộ các hành động trong khi game chạy
Tại đây, mình sử dụng đệ quy để hàm gọi lại chính nó. Tham số truyền vào là một trong hai hàm classic() hoặc hàm modern() tùy theo lựa chọn của người chơi. Hàm này còn có chức năng Pause/Resum game khi đang chơi nếu người dùng ấn phím SPACE.
bool isEmpty();
Hàm này kiểm tra file lưu điểm cao. Nếu file là rỗng thì trả về giá trị true.
void initScore();
Hàm này kiểm tra file lưu điểm cao có rỗng hay không bằng cách gọi hàm isEmpty(). Nếu file rỗng thì hàm sẽ khởi tạo 5 giá trị mặc định có tên PLAYER và điểm cao là 0. Nếu hàm có chứa thông tin hàm sẽ đọc thông tin điểm cao lưu vào mảng kiểu HighScore.
void showHighScore();
Hàm này có chức năng đọc thông tin của file điểm cao và xuất ra thông tin lên màn hình đồ họa khi được gọi.
void getHighScore ();
Hàm này có chức năng ghi lại thông tin điểm cao của người chơi + lưu vào file.
Hàm checkHighScore (int _score)
Hàm này thực hiện nhiệm vụ nhận điểm cao của người chơi, lấy thông tin tên người chơi nếu người chơi đó đạt thành tích cao. Hàm sẽ lưu giá trị vào mảng kiểu HighScore và gọi lại hàm getHighScore() để lưu lại thông tin.
Ngoài ta chương trình còn có một số hàm nhỏ thực hiện một số chức năng nhất định, bao gồm:
- Thêm tính năng click chuột thay cho nhấn bàn phím.
- Thêm nhạc cho game trở nên sống động
Cài đặt mã nguồn của mình như nào?
Phần này không hướng dẫn gì cả, chỉ hướng dẫn bạn nào thích chạy thử code của mình thôi.
Game rắn của mình có tính năng gì?
Play/Pause trong khi chơi game
Lưu điểm cao của người chơi ra tệp
Điều khiển bằng chuột & bàn phím
Thêm file setup trong Release
Âm thanh sống động
Cách cài đặt
- Nơi tải mã nguồn: https://github.com/nguyenvanhieuvn/Snake
- IDE phát triển: Dev-C++ (Chưa kiểm tra với IDE khác nhé, nhưng miễn dùng được thư viện winbgim.h là chạy được à)
- OS: Windows x64, chắc đa số dùng cái này rồi
- Bản cài đặt, chạy luôn bạn có thể vào link mã nguồn trên và tải từ tab Release của dự án. Tải về cài như cài đặt các phần mềm thông thường. Mình tạo file cài bằng Visual Studio Deploy gì đó (lâu quá quên rồi).
Đầu tiên, hãy chắc rằng bạn đã thêm thư viện winbgim.h cho IDE của mình. Hướng dẫn thêm thư viện đồ họa cho Dev-C++, nếu bạn chưa cài thì xem ở đây. Còn với CodeBlocks thì xem ở đây.
Với Dev-C++:
- Mở project bằng cách vào: File -> Open -> Chọn file .dev trong thư mục source code của mình, sau đó mở nó lên.
- Đi tới Menu project > project options > parameters and type “-lwinmm” in the LINKER section. Cái này để Dev-C++ có thể play audio của game (Chỉ chơi được file .wav thôi nha). Cái này ở IDE khác bạn tìm giúp mình nhé, nó chỉ là một tham số compiler thôi nên chắc IDE nào cũng có thể thêm.
- Chạy thôi.
Tóm lại, đây là một bài hướng dẫn game rắn săn mồi cơ bản giúp các bạn nắm bắt ý tưởng, tự xây dựng được các trò chơi cơ bản khác (caro, đua xe, bắn bóng, crazy math, …). Mình rất hi vọng các bạn có thể làm ra các sản phẩm xịn xò, cố gắng làm ra những siêu phẩm hơn mình. Nếu như vậy thì mình rất vui khi các bài chia sẻ của mình thực sự góp ích cho cồng động!
Lời cuối, xin trân thành và cảm ơn các bạn đã ủng hộ khóa học và ủng hộ Lập trình không khó!
Để lại một bình luận