Tetris game là một tựa game chạy trên AK Embedded Base Kit.
Hình 1: AK Embedded Base Kit - STM32L151
AK Embedded Base Kit là một evaluation kit dành cho các bạn học phần mềm nhúng nâng cao.
KIT tích hợp LCD OLED 1.3", 3 nút nhấn, và 1 loa Buzzer phát nhạc, với các trang bị này thì đã đủ để học hệ thống event-driven thông qua thực hành thiết kế máy chơi game.
KIT cũng tích hợp RS485, NRF24L01+, và Flash lên đến 32MB, thích hợp cho prototype các ứng dụng thực tế trong hệ thống nhúng hay sử dụng như: truyền thông có dây, không dây wireless, các ứng dụng lưu trữ data logger,...
Phần mô tả sau đây về “Tetris game” , giải thích cách chơi và cơ chế xử lý của trò chơi. Tài liệu này dùng để tham khảo thiết kế và phát triển trò chơi về sau.
Hình 2: Menu game
Trò chơi bắt đầu bằng màn hình Menu game với các lựa chọn sau:
- Play: Chọn vào để bắt đầu chơi game.
- Setting: Chọn vào để cài đặt các thông số của game.
- Charts: Chọn vào để xem top 3 điểm cao nhất đạt được.
- Exit: Thoát menu vào màn hình chờ.
| Đối tượng | Tên đối tượng | Ý nghĩa |
|---|---|---|
| Tetromino | Block | Gồm 7 khối và di chuyển từ trên xuống |
| Board | Board | Lưới chơi |
| Game State | Trạng thái game | Hoạt động dựa trên các tín hiệu |
| Tetris Game Manager | Lớp quản lý | Lớp trung tâm được các task sử dụng trong quá trình xử lý dữ liệu |
-
Trong trò chơi này bạn sẽ điều khiển Block, di chuyển trái/phải bằng hai nút [Down]/[Up].
-
Khi nhấn nút [Mode] Block sẽ xoay một góc 90 độ.
-
Mục tiêu trò chơi là kiếm được càng nhiều điểm càng tốt, trò chơi sẽ kết thúc khi có chạm vào Border.
-
Cứ sau mỗi khoảng thời gian ngắn, khối Tetromino sẽ rơi xuống thêm một ô. Sau khi rơi xuống chạm đáy hoặc chạm vào các khối đã rơi xuống trước, khối hiện tại sẽ bị gắn lại và một khối mới sẽ rơi xuống.
-
Khi một hàng được "hoàn thành", hay được lấp đầy bởi các khối, bạn sẽ được cộng điểm. Với càng nhiều hàng được hoàn thành một lúc, bạn càng được nhiều điểm, tối đa là 4 hàng với khối chữ I.
-
Các hàng đã được hoàn thành sau đó sẽ được xóa khỏi bảng, cùng với khiến các khối ô ở phía trên nó thấp xuống dưới.
-
Trò chơi kết thúc khi lượng khối đã rơi xuống chồng chất lên đến mức chạm vào cạnh trên cùng của board, và khối mới không thể rơi xuống được nữa.
-
Kết thúc trò chơi: Kết thúc trò chơi: Khi Block chạm vào Board, trò chơi sẽ kết thúc. Các đối tượng sẽ được reset và số điểm sẽ được lưu. Bạn sẽ vào màn hình “Game Over” với 3 lựa chọn là:
- Restart: chơi lại.
- Charts: vào xem bảng xếp hạng.
- Home: về lại menu game.
Sơ đồ trình tự được sử dụng để mô tả trình tự của các Message và luồng tương tác giữa các đối tượng trong một hệ thống.
Hình 3: The sequence diagram
SCREEN_ENTRY: Cài đặt các đối tượng trong game.
- TT_GAME_BOARD_SETUP: Thiết lập thông số ban đầu cho Board.
- TT_GAME_BLOCK_SETUP: Thiết lập thông số ban đầu cho đối tượng Block.
- TT_GAME_STATE_SETUP: Thiết lập thông số ban đầu cho Game State.
- Setup timer - Time tick: Khởi tạo Timer - Time tick cho game.
- STATE (GAME_ON): Cập nhật trạng thái game -> GAME_ON
- Level setup: Thiết lập thông số cấp độ game.
GAME PLAY - NORMAL: GAME hoạt động ở trạng thái bình thường
- TT_GAME_BLOCK_MOVE_DOWN: Cập nhật di chuyển cho Block
- TT_GAME_CHECK_GAME_OVER: Cập nhật kiểm tra Game Over.
- TT_GAME_BLOCK_LOCK: Khóa block khi đã rơi xuống đáy.
- TT_GAME_BOARD_CHECK_LINES: Board kiểm tra hàng.
- TT_GAME_STATE_CHECK_GAME_OVER: Game State kiểm tra game over mỗi khi có khối rơi.
GAME PLAY - ACTION: Game hoạt động ở trạng thái có nút nhấn.
- TT_GAME_BLOCK_MOVE_RIGHT: Player nhấn nút Up điều khiển block di chuyển sang phải
- TT_GAME_BLOCK__MOVE_LEFT: Player nhấn nút Down điều khiển block di chuyển sang trái
- TT_GAME_BLOCK_ROTATE: Player nhấn nút Mode điều khiển block xoay.
RESET GAME: Đặt lại các thông số trước khi thoát game
-
STATE(GAME_OVER): Cập nhật trạng thái game -> GAME_OVER
-
TT_GAME_STATE_CHECK_GAME_OVER: kiểm tra game over.
-
TT_GAME_RESET: Signal cài đặt lại game do Border gửi đến.
-
TT_GAME_BOARD_RESET: cài đặt lại thông số cho Board.
-
TT_GAME_STATE_RESET: cài đặt lại các thông số cho State.
-
TT_GAME_BLOCK_RESET: cài đặt lại các thông số cho Block.
-
Save and reset Score: Lưu số điểm hiện tại và cài đặt lại.
-
Timer remove - Timer exit: xóa timer - time tick
-
Setup timer - Timer exit: Tạo 1 timer one shot để thoát game. Nhằm tạo ra một khoảng delay cho người chơi có thể nhận thức được là mình đã game over trước khi chuyển sang màn hình thông báo game over.
EXIT: Thoát khỏi game và chuyển sang màn hình Game Over.
- TT_GAME_EXIT: Signal do Timer exit gửi đến.
- STATE (GAME_OFF): Cập nhật trạng thái game -> GAME_OFF
- Change the screen - SCREEN_TRAN(scr_game_over_handle, &scr_game_over): Chuyển màn hình sang màn hình Game Over..
Sau khi xác định được các đối tượng trong game mà chúng ta cần, tiếp theo chúng ta phải liệt kê ra các thuộc tính, các task, các signal và bitmap mà trong game sẽ sử dụng tới. Việc liệt kê càng chi tiết thì việc làm game diễn ra càng nhanh và tạo tình rõ ràng minh bạch cho phần tài nguyên giúp phần code game diễn ra suông sẻ hơn.
Việc liệt kê các thuộc tính của đối tượng trong game có các tác dụng quan trọng sau:
- Giúp xác định rõ thông tin về đối tượng trong game.
- Giúp xác định cấu trúc dữ liệu phù hợp để lưu trữ thông tin của đối tượng.
- Khi bạn xác định trước các thuộc tính cần thiết, bạn giảm thiểu khả năng bỏ sót hoặc nhầm lẫn trong việc xử lý và sử dụng các thuộc tính.
Các biến quan trọng:
-
tt_game_score: Điểm của trò chơi.
-
tt_game_status: Trạng thái quả trò chơi.
- GAME_OFF: Tắt .
- GAME_ON: Bật.
- GAME_OVER: Đã thua.
-
tt_game_setting_t settingsetup : Cấu hình cấp độ của trò chơi.
- settingsetup.silent : Bật/tắt chế độ im lặng.
- settingsetup.block_speed : Cấu hình tốc độ các khối tetromino
Sequence diagram:
Hình 4: Block sequence
Tóm tắt nguyên lý: Tetris sẽ nhận Signal thông được gửi từ 2 nguồn là Screen và Button. Quá trình xử lý của đối tượng phần làm 3 giai đoạn:
- Giai đoạn 1: Bắt đầu game, cài đặt các thông số của Tetris như vị trí và hình ảnh.
- Giai đoạn 2: Chơi game, trong giai đoạn này chia làm 2 hoạt động là:
- Cập nhật: Screen gửi Signal cập nhật cho Block mỗi 500ms để cập nhật trạng thái hiện tại của Block.
- Hành động: Button gửi Signal di chuyển trái/phải cho Block mỗi khi nhấn nút.
- Giai đoạn 3: Kết thúc game, thực hiện cài đặt lại trạng thái của Block trước khi thoát game.
Code:
Sequence diagram:
Hình 5: Board sequence
Tóm tắt nguyên lý: Board sẽ nhận Signal thông được gửi từ 2 nguồn là Screen và Block. Quá trình xử lý của đối tượng phần làm 3 giai đoạn:
-
Giai đoạn 1: Bắt đầu game, cài đặt các thông số của Board.
-
Giai đoạn 2: Chơi game, trong giai đoạn này:
- Cập nhật: Mỗi khi một Block chạm đáy thì board sẽ khóa khối và gọi hàm kiểm tra hàng đầy.
Sequence diagram:
Hình 6:State sequence
Tóm tắt nguyên lý: GameState sẽ nhận Signal từ Screen.
-
Giai đoạn 1: Bắt đầu game, cài đặt các thông số score, level, nextBlock lên màn hình.
-
Giai đoạn 2: Chơi game, khi một Block được tạo ra sẽ gọi hàm handleCheckGameOver để xem khối có va chạm vào phần trên cùng của màn hình không.
-
Giai đoạn 3: Kết thúc game, thực hiện cài đặt lại các thông số trước khi thoát game.
Tóm tắt nguyên lý: Border là 1 lớp đối tượng quản lý và không nhận cũng như xử lý tín hiệu.
Trong trò chơi, màn hình hiện thị là 1 màn hình LCD OLed 1.3" có kích thước là 128px*64px. Nên các đối tượng được hiển thị trong game phải có kích thước hiển thị phù hợp với màn hình nên cần được thiết kế riêng.
Đồ họa được thiết kế từng phần theo từng đối tượng bằng hàng, nhạc game.
Code:
// Âm thanh Bắt đầu game
BUZZER_PlayTones(tones_SMB);
// Âm thanh Vụ nổ
BUZZER_PlayTones(tones_BUM);
// Âm thanh nút nhấn
BUZZER_PlayTones(tones_cc);
// Âm thanh cảnh báo
BUZZER_PlayTones(tones_3beep);
// Merry Christmas
BUZZER_PlayTones(tones_merryChristmas);
/*________________BUZZER______________*/
void BUZZER_Sleep(bool sleep);
/* sleep = 0 : bật âm thanh
sleep = 1 : tắt âm thanh */
static const Tone_TypeDef tones_BUM[] = {
{3000,3},
{4500,6},
{ 0,0}
};
static const Tone_TypeDef tones_cc[] = {
{2000,2},
{ 0,0},
};
static const Tone_TypeDef tones_startup[] = {
{2000,3},
{ 0,3},
{3000,3},
{ 0,3},
{4000,3},
{ 0,3},
{1200,4},
{ 0,6},
{4500,6},
{ 0,0} // <-- tones end
};
static const Tone_TypeDef tones_3beep[] = {
{4000, 3},
{ 0,10},
{1000, 6},
{ 0,10},
{4000, 3},
{ 0, 0}
};
// "Super Mario bros." =
static const Tone_TypeDef tones_SMB[] = {
{2637,18}, // E7 x2
{ 0, 9}, // x3
{2637, 9}, // E7
{ 0, 9}, // x3
{2093, 9}, // C7
{2637, 9}, // E7
{ 0, 9}, // x3
{3136, 9}, // G7
{ 0,27}, // x3
{1586, 9}, // G6
{ 0,27}, // x3
{2093, 9}, // C7
{ 0,18}, // x2
{1586, 9}, // G6
{ 0,18}, // x2
{1319, 9}, // E6
{ 0,18}, // x2
{1760, 9}, // A6
{ 0, 9}, // x1
{1976, 9}, // B6
{ 0, 9}, // x1
{1865, 9}, // AS6
{1760, 9}, // A6
{ 0, 9}, // x1
{1586,12}, // G6
{2637,12}, // E7
{3136,12}, // G7
{3520, 9}, // A7
{ 0, 9}, // x1
{2794, 9}, // F7
{3136, 9}, // G7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2093, 9}, // C7
{2349, 9}, // D7
{1976, 9}, // B6
{ 0,18}, // x2
{2093, 9}, // C7
{ 0,18}, // x2
{1586, 9}, // G6
{ 0,18}, // x2
{1319, 9}, // E6
{ 0,18}, // x2
{1760, 9}, // A6
{ 0, 9}, // x1
{1976, 9}, // B6
{ 0, 9}, // x1
{1865, 9}, // AS6
{1760, 9}, // A6
{ 0, 9}, // x1
{1586,12}, // G6
{2637,12}, // E7
{3136,12}, // G7
{3520, 9}, // A7
{ 0, 9}, // x1
{2794, 9}, // F7
{3136, 9}, // G7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2093, 9}, // C7
{2349, 9}, // D7
{1976, 9}, // B6
{ 0, 0}
};
// Merry Christmas
static const Tone_TypeDef tones_merryChristmas[] = {
{2637, 9}, // E7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0,18}, // x2
{2637, 9}, // E7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0,18}, // x2
{2637, 9}, // E7
{ 0, 9}, // x1
{3136, 9}, // G7
{ 0, 9}, // x1
{2093, 9}, // C7
{ 0, 9}, // x1
{2349, 9}, // D7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0,24}, // x2
{2794, 9}, // F7
{ 0, 9}, // x1
{2794, 9}, // F7
{ 0, 9}, // x1
{2794, 9}, // F7
{ 0, 9}, // x1
{2794, 9}, // F7
{ 0, 9}, // x1
{2794, 9}, // F7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2349, 9}, // D7
{ 0, 9}, // x1
{2349, 9}, // D7
{ 0, 9}, // x1
{2637, 9}, // E7
{ 0, 9}, // x1
{2349, 9}, // D7
{ 0, 9}, // x1
{3136, 9}, // G7
{ 0, 0} // <-- tones end
};




