- vừa được xem lúc

Cùng thử viết một game xếp hình (Tetris) hoàn chỉnh từ con số 0 (Phần 3: Ăn điểm và Game Over)

0 0 58

Người đăng: Trần Xuân Thắng

Theo Viblo Asia

Chào các bạn, sau khi đã hiatus cái series về Tetris này cả năm trời thì hôm nay mình mới nổi hứng tái khởi động cái series viết game Tetris này ?

Những vấn đề khó nhất trong quá trình làm game ở bài này (xây dựng phần giao diện, logic phát hiện va chạm,...) đã được giải quyết ở 2 phần trước đó. Tuy nhiên, sản phẩm của chúng ta vẫn còn rất xa ở mức hoàn thành, cũng như vẫn còn rất nhiều bài toán thú vị khác đang chờ chúng ta khám phá.

Trong phần này, mình xin được viết một bài ngắn về phát triển thêm tính năng ăn điểmgame over cho trò chơi.

Clear hàng và ăn điểm

Phát hiện hàng có thể clear

Bài toán này quá đơn giản. Nhiệm vụ của bạn lại là sử dụng vòng lặp để kiểm tra xem hàng nào trong ma trận landedBoard của bạn có tất cả các ô đều có giá trị là được.

findClearableRows() { const clearableIndexes = [] this.landedBoard.forEach((row, index) => { if (row.every(cell => cell > 0)) { clearableIndexes.push(index) } }) return clearableIndexes
}

Xóa đi hàng có thể clear

Bước này có phần "tricky" hơn: bạn cần implement một method sao cho nó có đủ khả năng:

  • Có thể "xóa" đi một hàng
  • Dồn các phần tử phía trên nó xuống phía dưới
  • Vẫn đảm bảo số hàng như ban đầu

Vì "lười nhác" nên mình sử dụng một cách có hoạt động tốt, nhưng có phần kém hiệu năng một tẹo: mình sử dụng hàm splice để xóa đi các hàng và sau đó thêm lại hàng trống mới cho đủ số lượng bằng method unshift().

clearRows(rowIndexes) { for (let i = this.landedBoard.length - 1; i>=0; i--) { for (let j = 0; j < rowIndexes.length; j++) { if (rowIndexes[j] === i) { this.landedBoard.splice(rowIndexes[j], 1) } } }
}

Một điều cần lưu ý ở phương pháp này: do sau khi sử dụng splice() số index của mảng ban đầu sẽ bị thay đổi. Đó là lý do mình phải duyệt vòng lặp for theo chiều ngược lại.

Ăn điểm

Việc ăn điểm này hoàn toàn phụ thuộc vào sở thích của mỗi người, nhưng thường là phải làm sao để nếu người chơi càng mạo hiểm "ăn dày", tức clear "combo" được càng nhiều hàng trong một nước đi, thì càng phải được cộng nhiều điểm.

Ở bài này, mình sẽ làm công thức tính điểm dựa trên dãy số tam giác (Triangular Number Sequence):

  • Nếu chỉ ăn được 1 hàng: bạn nhận được 1 điểm
  • Nếu ăn được 2 hàng: bạn nhận được 3 điểm (tức +1 điểm so với bình thường)
  • Nếu ăn được 3 hàng: bạn nhận được 6 điểm (tức +3 điểm so với bình thường)
  • Nếu ăn được 4 hàng: bạn nhận được 10 điểm (tức +6 điểm so với bình thường) (tối đa)

Công thức tính cho dãy số này đơn giản như sau: xn=n(n+1)2x_n = \frac{n(n+1)}{2}

Viết thành method nào:

calculateScore(rowsCount) { return (rowsCount * (rowsCount + 1)) / 2
}

Thêm những hàm đã implement vào progress

Bạn cần bổ sung chạy những method clear hàng + ăn điểm vừa viết vào lúc ngay sau khi khối Tetromino bị va chạm (với đáy hoặc các phần tử đã hạ) và merge vào mảng chính:

progress() {
 let nextTetromino = new this.currentTetromino.constructor(this.currentTetromino.row + 1, this.currentTetromino.col, this.currentTetromino.angle) if (!this.bottomOverlapped(nextTetromino) && !this.landedOverlapped(nextTetromino)) { this.currentTetromino.fall() } else { this.mergeCurrentTetromino()
+
+ const clearableRowIndexes = this.findClearableRows()
+ this.clearRows(clearableRowIndexes)
+ this.score += this.calculateScore(clearableRowIndexes.length)
+
 this.currentTetromino = this.randomTetromino() } }

Game Over

Nhắc lại một chút, ở phần 1, mình có nói rằng mặc dù bạn nhìn thấy board của Tetris có 20 hàng x 10 cột, thực chất đằng sau logic của trò chơi là có đến 23 hàng x 10 cột, với 3 hàng trên cùng không thể nhìn thấy được!

Board game của tetris

Vậy để kiểm tra xem game over hay chưa, bạn chỉ cần check hàng thứ 3 xem có bất kỳ khối nào không là xong!

isGameOver() { for (let i = 0; i < this.boardWidth; i++) { if (this.landedBoard[2][i] > 0) { return true } } return false
}

Bonus: Màu sắc cho từng khối Tetromino

Bạn cần bổ sung thêm method getColor như sau:

getColor(cellNumber) { switch (cellNumber) { case 1: return LShape.color case 2: return JShape.color case 3: return OShape.color case 4: return TShape.color case 5: return SShape.color case 6: return ZShape.color case 7: return IShape.color }
}

Sau đó, ở method draw(), hãy thay màu đen rgb(0, 0, 0) bằng màu sắc được trả về từ method getColor() đã định nghĩa:

 for (let i = 3; i < this.boardHeight; i++) { for (let j = 0; j < this.boardWidth; j++) { if (this.currentBoard[i][j] > 0) {
- this.ctx.fillStyle = 'rgb(0, 0, 0)'
+ this.ctx.fillStyle = this.getColor(this.currentBoard[i][j])
 } else { this.ctx.fillStyle = 'rgb(248, 248, 248)' }

Thành quả của ngày hôm nay ^^

Nhấn vào CodePen để xem đầy đủ code nhé.

Còn gì nữa không?

Rất nhiều là đằng khác:

  • Hiện ra khối tetromino kế tiếp ở cạnh bên, để giúp người chơi vạch ra chiến thuật và có thể chuẩn bị tốt hơn
  • Thuật toán random 7-bag, giúp trải nghiệm chơi dễ chịu hơn, tránh trường hợp mãi không ra "khối dọc" mà bạn đang cần
  • Cải thiện giao diện cho thật hoàn chỉnh
  • Thử refactor toàn bộ code về TypeScript, cho vui là chính
  • Thêm animation mượt mà khi khối tetromino di chuyển, rơi, hay khi ăn điểm??
  • ...

Hy vọng rằng mình có thể viết về nó trong các bài viết tiếp theo!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Giới thiệu Typescript - Sự khác nhau giữa Typescript và Javascript

Typescript là gì. TypeScript là một ngôn ngữ giúp cung cấp quy mô lớn hơn so với JavaScript.

0 0 499

- vừa được xem lúc

Bạn đã biết các tips này khi làm việc với chuỗi trong JavaScript chưa ?

Hi xin chào các bạn, tiếp tục chuỗi chủ đề về cái thằng JavaScript này, hôm nay mình sẽ giới thiệu cho các bạn một số thủ thuật hay ho khi làm việc với chuỗi trong JavaScript có thể bạn đã hoặc chưa từng dùng. Cụ thể như nào thì hãy cùng mình tìm hiểu trong bài viết này nhé (go).

0 0 414

- vừa được xem lúc

Một số phương thức với object trong Javascript

Trong Javascript có hỗ trợ các loại dữ liệu cơ bản là giống với hầu hết những ngôn ngữ lập trình khác. Bài viết này mình sẽ giới thiệu về Object và một số phương thức thường dùng với nó.

0 0 136

- vừa được xem lúc

Tìm hiểu về thư viện axios

Giới thiệu. Axios là gì? Axios là một thư viện HTTP Client dựa trên Promise.

0 0 117

- vừa được xem lúc

Imports và Exports trong JavaScript ES6

. Giới thiệu. ES6 cung cấp cho chúng ta import (nhập), export (xuất) các functions, biến từ module này sang module khác và sử dụng nó trong các file khác.

0 0 93

- vừa được xem lúc

Bài toán đọc số thành chữ (phần 2) - Hoàn chỉnh chương trình dưới 100 dòng code

Tiếp tục bài viết còn dang dở ở phần trước Phân tích bài toán đọc số thành chữ (phần 1) - Phân tích đề và những mảnh ghép đầu tiên. Bạn nào chưa đọc thì có thể xem ở link trên trước nhé.

0 0 229