Code game xếp hình bằng javascript
Hẳn rằng ai trong chúng ta cũng đều biết và đã từng chơi thử trò chơi xếp hình (tetris) rồi đúng không, nhưng không phải trò "xếp hình" như bạn đang nghĩ đâu nhé. Tetris là một game hết sức đơn giản được làm bởi những người bạn Liên Xô của chúng ta từ những năm 80, đi cùng không thể thiếu là bản nhạc của Hồng Quân huyền thoại Korobeiniki. Show Tuy đơn giản và lâu đời như vậy, Tetris có sức hút và tính gây nghiện rất lớn (tác giả gốc của tựa game còn nói rằng khi đang phát triển game, anh mải chơi game này đến mức quên cả việc hoàn thành nốt các đoạn code!). Hội chứng Tetris Effect nổi tiếng cũng dùng để chỉ trạng thái người chơi một tựa game (như Tetris) quá nhiều, đến mức nhìn đâu cũng thấy mấy hình khối rơi xuống, kể cả trong giấc mơ. Hôm nay, mình sẽ cùng các bạn làm một tựa game Tetris từ A-Z hoàn chỉnh, không bug, đủ chức năng nhất. Trong bài này, mình xin lựa chọn ngôn ngữ Javascript để tiện lợi cho việc demo ngay trên trình duyệt. Tuy nhiên, sau khi đọc xong bài này, bạn hoàn toàn có thể đủ khả năng để làm nó với bất cứ ngôn ngữ hướng đối tượng nào khác. Sơ lược về TetrisCó lẽ bạn cũng đã biết hay cũng chơi game Tetris rất nhiều lần rồi. Nhưng để cho chắc ăn, mình xin phép được nhắc lại chút ít về nguyên tắc tựa game này: Game BoardCòn hay được gọi là "playfield", hay "matrix",... Đại loại đây chính là phần bố cục ô lưới, và là nơi chính để chơi game của bạn. Ở màn hình khi chơi game, bạn sẽ thấy game board rộng cỡ 20 hàng x 10 cột. Tuy nhiên, bạn có biết thực tế ở những game Tetris, board game có chiều cao thật từ 22 lên đến 40 ô (tức 40 hàng x 10 cột), và các ô trên cao từ thứ 20 từ dưới lên trở đi thực chất bị ẩn khỏi màn hình? Trong bài này, mình sẽ sử dụng board game rộng 23 x 10, với 3 hàng trên cùng bị ẩn khỏi giao diện game. Các bạn hãy thử đoán xem tại sao mình lại để dư 3 hàng trên cùng đó nhé. TetrominoLà những khối hình thù quái dị từ trên trời rơi xuống. Có 7 loại tất cả: khối chữ L, J, O, T, S, Z, và I. Mỗi loại khối có màu sắc tương ứng khác nhau. Các khối này đều có thể bị xoay (theo chiều kim đồng hồ) cũng như di chuyển (sang trái hoặc phải). Tuy nhiên khối sẽ không thể xoay hay di chuyển được nếu gặp va chạm (với phần cạnh hay với các khối đã hạ cánh). Game tickCứ sau cùng một khoảng thời gian ngắn (thường được đặt từ 0,5 đến 1 giây), khối Tetromino hiện tại sẽ rơi xuống thêm một ô. Sau khi rơi xuống tận cùng (chạm mặt đất hay chạm vào các khối đã hạ cánh khác), khối hiện tại sẽ bị gắn lại, và một khối mới khác sẽ rơi xuống. Ăn điểmKhi một hàng được "hoàn thành", hay được lấp đầy bởi các khối, bạn sẽ được ăn đ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. Game overTrò 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. Sơ qua như phía trên cũng là kha khá chi tiết rồi, giờ bắt tay vào làm thôi. Tiến hànhKhởi tạo dự ánTạo các thư mục và file như thế này:
Khởi tạo file index.html:
Từ giờ các mã javascript sẽ được viết chủ yếu ở main.js. Bước 1: Xây dựng giao diện cơ bản cho gameVới tựa game đơn giản như Tetris, ta có thể lựa chọn dựng giao diện chơi bằng HTML và CSS (tức dựa vào DOM), hoặc dùng HTML5 Canvas. Trong bài này, mình sẽ thử xây dựng game qua HTML5 Canvas. Mục tiêu của chúng ta sẽ là xây dựng một giao diện trông như thế này: Khóa họcVẽ hình vuông/hình chữ nhật lên canvas rất đơn giản. Vẽ một hình vuông nhỏ lên canvas như sau:
Bạn sẽ được hình vuông đã tô màu đen, nằm ở tọa độ x = 10, y = 20 và cạnh dài 50px. Một ví dụ khác, ta thử vẽ một hình chữ nhật có viền màu đỏ:
Bạn sẽ có ngay hình chữ nhật không tô màu nhưng có viền đỏ, đặt ở tọa độ x = 70, y = 20, rộng 50px và cao 100px. Viết chữ lên canvas cũng rất đơn giản. Tương tự 2 ví dụ trên, chữ này được viết với màu đen, kích thước 14px, đặt ở tọa độ x = 10, y = 140:
Demo như dưới đây, nhấn vào tab Result nhé. Áp dụng vào bàiTrước hết, để game hoạt động được, ta cần có một vài biến để lưu lại trạng thái của game theo thời gian. Ví dụ như những cái sau:
Chúng ta sẽ đi sâu nhiều hơn về các thuộc tính này ở phần sau của bài. Mình sẽ dùng class syntax của ES6 để định nghĩa một class Game:
Ở phần
Lấy context của phần tử
Thêm phương thức
Hỏi: Tại sao giá trị lặp i lại bắt đầu từ 3? Đáp: Vì chúng ta cần bỏ lại 3 hàng đầu của board, không hiển thị ra giao diện game. Hỏi: Cái đống cộng trừ nhân chia loằng ngoằng kia là gì @@! Đáp: Đó là phép tính để tính toán vị trí của các ô nhỏ (block), sao cho chúng cách đều nhau và giữa chúng lại có khoảng cách (padding) hợp lý nhất. Bạn yên tâm vì giờ đọc lại đoạn vừa rồi mình cũng không hiểu gì đâu Hỏi: Đáp: Nó có tác dụng xóa trắng canvas mỗi khi chạy mới phương thức Xong bước này, ta đã có thể khởi tạo đối tượng mới từ object Game và thực thi hàm
Xong rồi, bạn đã (tạm) hoàn thành tạo canvas cho giao diện game. Kết quả của bước 1 này như ở JSFiddle dưới đây: Bước 2: Định nghĩa class cho các hình khối (Tetromino)Ý tưởng để biểu diễn dữ liệuNhư ở phần trước, chúng ta đã sử dụng thuộc tính
Để diễn tả hình dạng các khối Tetromino, mình cũng sẽ sử dụng các mảng 2 chiều, ví dụ với khối chữ L nằm dọc:
Ấy nhưng trong Tetris, người chơi có thể xoay Tetromino đang rơi xuống theo nhiều góc khác nhau (theo chiều kim đồng hồ): 0, 90, 180 và 270 độ. Nếu muốn, bạn có thể làm một phương thức để "xoay" mảng trên và trả về mảng đã xoay bằng chút thuật toán. Tuy nhiên, để cho đơn giản, ở đây chúng ta sẽ định nghĩa mọi chiều xoay của các Tetromino luôn. Ví dụ với Tetromino chữ L như sau:
Tạo class cho các TetrominoGiờ chúng ta sẽ bắt tay vào viết code nào. Mình sẽ tạo một abstract class tên
Tóm lại, ta có các class định nghĩa các Tetromino như sau:
Đây là JSFiddle sau khi hoàn thành xong bước này. Nên nhớ về chức năng vẫn chưa có gì mới so với bước 1 đâu nhé. Bước 3: Làm khối Tetromino rơi xuống!Lấy ngẫu nhiên khối TetrominoTrước hết, mình sẽ tạo một phương thức để lấy ngẫu nhiên 1 trong 7 loại khối Tetromino. Khi khởi tạo
Thế nhưng, đôi khi RNG cũng hơi bất công, nếu bạn nhân phẩm kém thì có thể sẽ random ra toàn khối chữ S hay Z liên tiếp nhau, trong khi khối chữ I mãi không thấy đâu, khá là ức chế. Về sau, chúng ta sẽ tìm cách cải thiện cơ chế random của game để chơi sướng nhất (gợi ý: ta sẽ tạo cơ chế random 7-bag). Game LoopChắc hẳn bạn đã quen thuộc với cửa sổ dòng lệnh (console hay terminal) trên các hệ điều hành rồi đúng không? Khi đang chờ người dùng nhập câu lệnh, cửa sổ dòng lệnh sẽ hiện ra dấu nháy chờ input của người
dùng. Nếu bạn gõ Thế nhưng video game thì lại khác. Nếu game đang chạy, thì dù bạn không điều khiển gì, game vẫn sẽ chạy như thường. Gió vẫn sẽ thổi, mây vẫn bay, chim vẫn hót, ngày dần dần vẫn chuyển thành đêm, NPC vẫn đi dạo trên phố và kẻ thù vẫn lao vào đánh bạn,... Cụ thể hơn, ở trong game Tetris, cho dù nếu bạn không nhập input gì, khối Tetromino trên màn hình vẫn sẽ dần hạ xuống, buộc bạn phải sớm tìm vị trí thích hợp để đặt khối Tetromino này. Đây chính là cách hiểu cơ bản về game loop. Ở trong bài này, mình sẽ tạm đặt thời gian mỗi khi khối Tetromino rơi xuống một ô là 0,8 giây. Để làm được điều này, trong bài
này mình sẽ đơn giản sử dụng Cứ với mỗi 0,8 giây, chúng ta cần làm 3 việc:
Kết quả là bạn được một khối Tetromino đang chuyển động dần từ trên xuống: Tạm kếtTuy ta đã có thể thấy khối Tetromino đã từ từ rơi xuống, nhưng hiện tại ta vẫn còn thiếu rất nhiều chức năng, trước mắt chính là:
Để hoàn thành được 2 chức năng trên, chúng ta phải giải quyết một thử thách mới: phát hiện va chạm. Mình xin được đi tiếp cùng các bạn ở Phần 2 của series, mong được các bạn đón đọc và ủng hộ! |