Swift_Tetris game - part 3
Bài đăng này đã không được cập nhật trong 10 năm
Bài trước, ta đã khởi tạo các biến và những thuộc tính cần thiết cho màu sắc trong game, bài này ta sẽ tiếp tục xử lý để màu sắc xuất hiện trong game theo ý muốn. Ta sẽ xử lý trong Block.swift. Hãy xem đoạn code dưới đây

- đầu tiên ta khai báo class
Blockimplement 2 protocolHashablevàPrintable. Trong đóBlockđược lưu trongArray2Dthông quaHashable - Đoạn
#2, ta định nghĩa thuộc tínhcolor. - Đoạn
#3, ta định nghĩa các biếncolumnvàrow(hàng và cột). Những thuộc tính này để xác định vị trí của Block trên màn hình.SKSpriteNodesẽ mô tả các yêu tố liên quan đến hình ảnh của mỗi Block và sẽ được dùng thông quaGameScenemỗi khi có bất cứ chuyển động nào của Block. - Đoạn
#4ta khai báo 1 shorstcut name của sprite. Từ giờ ta có thể gọiblock.spriteNamegắn gọn hơnblock.color.spriteName. - Đoạn
#5ta khai báo 1 implementhasValueđể thực hiện tính toán các thuộc tính, giá trị trả về là kết quả của phép XOR giữarowvàcolumn, ta nhận đc 1 giá trị integer duy nhất cho mỗi Block - Đoạn 6, ta khai báo implement
description.Mỗi đối tượng củaPrintablecó thể nằm trong string và bao quanh bởi\(và). Ví dụ 1 block màu xanh ở vị trí hàng số 3, cột số 8 thìblue:[8, 3]sẽ biểu diễn block đó. - Cuối cùng, đoạn số
#7ta dùng toán tử==để so sánh các block với nhau. Giá trị trả về làtruenếu và chỉ nếu 2 block cùng vị trí và màu sắc.
Tạo hình dạng cho Block

- phần trước ta đã xây dựng và thiết lập các thuộc tính liên quan đến màu sắc cho Block, tiếp theo ta sẽ tạo hình thù cho chúng. Ta sẽ tạo những Block với dạng Tetromino Tetromino trong class
Shape. Tạo file Sharp.swift ( giống như đã tạo Block.swift).Mở fileShape.swift, trước tiên, xoá hết những đoạn code mà xcode tự generate ra. Như với các file khác, ta cần import thư việnSpriteKit
import SpriteKit
- tiếp theo ta khai báo 1 bộ dữ liệu liệt kê enum để định nghĩa các hướng xoay của khối hình. Mỗi khối hình có thể xoay 4 hướng để rơi xuống, ta sẽ quy định là 4 góc
0,90,180, và270.Ta có thể tưởng tượng như 4 góc của 1 chiếc đồng hồ như hình dưới đây

enum Orientation: Int, Printable {
case Zero = 0, Ninety, OneEighty, TwoSeventy
var description: String {
switch self {
case .Zero:
return "0"
case .Ninety:
return "90"
case .OneEighty:
return "180"
case .TwoSeventy:
return "270"
}
}
}
- Tiếp theo ta khai báo 1 function để tính toán và trả ra gián trị góc của khối hình khi ta điều khiển thay đổi góc của nó.
static func rotate(orientation:Orientation, clockwise: Bool) -> Orientation {
var rotated = orientation.rawValue + (clockwise ? 1 : -1)
if rotated > Orientation.TwoSeventy.rawValue {
rotated = Orientation.Zero.rawValue
} else if rotated < 0 {
rotated = Orientation.TwoSeventy.rawValue
}
return Orientation(rawValue:rotated)!
}
- tiếp theo ta sẽ khai báo 1 số constant.
// The number of total shape varieties
let NumShapeTypes: UInt32 = 7
// Shape indexes
let FirstBlockIdx: Int = 0
let SecondBlockIdx: Int = 1
let ThirdBlockIdx: Int = 2
let FourthBlockIdx: Int = 3
- định nghĩa class
Shapecùng các thuộc tính như màu sắc, vị trí(hàng cột), orientation và mảng những block
class Shape: Hashable, Printable {
// The color of the shape
let color:BlockColor
// The blocks comprising the shape
var blocks = Array<Block>()
// The current orientation of the shape
var orientation: Orientation
// The column and row representing the shape's anchor point
var column, row:Int
}
- Bước kế tiếp, ta định nghĩa 2 thuộc tính tính toán và chúng trả ra kết quả rỗng, 2 thuộc tính này đc override bởi các class hình dạng của block sẽ đc thêm vào dưới đây.
// Subclasses must override this property
var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
return [:]
}
// Subclasses must override this property
var bottomBlocksForOrientations: [Orientation: Array<Block>] {
return [:]
}
blockRowColumnPositionsđịnh nghĩa 1Dictionary.dictionarytrong swift được khai báo bằng ngoặc vuông với các bộ key:value, mỗi bộ được phân tách nhau bằng dấu hai chấm. Việc truy xuất phần tử trong Dictionary cũng giống như truy xuất mảng và chúng ta sẽ dùngkeyđể truy xuất. Trong trường hợp này, key củablockRowColumnPositionssẽ là những đối tượngOrientationvà value của nó là 1 mảngArray<(columnDiff: Int, rowDiff: Int)>.- Trong swift thì mảng là 1
tuple(1 bộ dữ liệu). Thông thường thì ta hay sử dụng 1tupleđể truyền hoặc trả về nhiều giá trị 1 lúc.Mỗi tuple thì chúng ta có thể truyền ko giới hạn phần tử. Trường hợp của chúng ta đã khai báo thì tuple có 2 phần tửcolumnDiffvàrowDiffđều có kiểuInt. Dưới đây là ví dụ việc truy xuất phần tử của 1 dictionary
let arrayOfDiffs = blockRowColumnPositions[Orientation.0]!
let columnDifference = arrayOfDiffs[0].columnDiff
- Vì các phần tử trong dictionary có giá trị mặc định là optional do đó ta phải thêm
!vào cuối câu lệnh truy xuất để có thể access được dictionary. Để truy xuất giá trịcolumnDiffcủa phần tử đầu tiên , ta đánh index phần tử đầu tiên của mảng từ 0 và dùng dấu chấm.để lấy được giá trị mong muốn. - Tiếp theo ta khai báo 1 thuộc tính tính toán để trả ra vị trí đáy của block với hướng hiện tại của nó.
var bottomBlocks:Array<Block> {
if let bottomBlocks = bottomBlocksForOrientations[orientation]
{
return bottomBlocks
}
return []
}
- tiếp dến ta sử dụng 1 dạng đặc biệt của initializer đó là
convenienceinitializer. Mục đích của nó là làm rút gọn việc khởi tạouserscho classShape. Nó gán các giá trị hàng và cột trong khi lấy ngẫu nhiên các giá trị màu sắc và hướng.
convenience init(column:Int, row:Int) {
self.init(column:column, row:row, color:BlockColor.random(), orientation:Orientation.random())
}
- Ta cần khai báo thêm 1
finalfunction và các class con không thể override phương thức này. Trong function ta thực hiện kiểm tra điều kiện, ta gánblockRowColumnTranslationsvào mảng từ kết quả trả ra của bộ thuộc tính tính toán (dictionary property). NếublockRowColumnTranslationsnot found thì các câu lệnh bên trongifsẽ không được thực thi.
final func initializeBlocks() {
if let blockRowColumnTranslations = blockRowColumnPositions[orientation] {
for i in 0..<blockRowColumnTranslations.count {
let blockRow = row + blockRowColumnTranslations[i].rowDiff
let blockColumn = column + blockRowColumnTranslations[i].columnDiff
let newBlock = Block(column: blockColumn, row: blockRow, color: color)
blocks.append(newBlock)
}
}
}
Cách viết tường minh hơn:
let blockRowColumnTranslations = blockRowColumnPositions[orientation]
if blockRowColumnTranslations != nil {
// Code…
}
Các class con
Trên đây ta đã tạo 1 base class, có thể coi nó như 1 công cụ để gọi và xử lý các khôi hình, và với mỗi dạng khối hình ta cần khai báo 1 class cho nó. Có tổng cộng 7 class con như vậy và ta phải tạo 7 file.
- Đầu tiên là khối hình vuông, ta tạo file
SquareShape.swiftvà khai báo classSquareShape. Bên trong class, ta khai báo khoảng cách giữa các cạnh ( thông qua 2 chỉ số row và column) và hướng quay của khối hình (orientation). Có thể thấy, hình vuông là đơn giản nhất, ta coi như giá trị orientation không đổi khi nó quay theo mọi hướng. Ta quy định hình dạng của khôi hình vuông ứng với vị trí như sau
| 0•| 1 |
| 2 | 3 |
do đó, đáy của hình vuống luôn là vị trí thứ 3 và 4 trong block.

- ta thực hiện override bộ thuộc tính tính toán
blockRowColumnPositionsđể đưa ra bộ dữ liệu hoàn chỉnh cho hình dạng khối hình vuông. Mỗi chỉ số của mảng tương ứng với góc của khối. ví dụ, góc trên bên trái ứng với giá trị 0 trong block ta quy định bên trên, bộ giá trị của nó sẽ là (0,0) ứng với (column, row). Tương tự, góc trên bên phải sẽ là (1, 0). - cuối cùng, ta cũng thực hiện override tương tự để đưa ra bộ dữ liệu cho đáy hình vuông, cũng là để xác định hướng xoay của khối hình. Và với hình vuông thì vị trí đáy luôn cố định là block số 3 và 4 trong khối hình quy dịnh bên trên.
class SquareShape:Shape {
override var blockRowColumnPositions: [Orientation: Array<(columnDiff: Int, rowDiff: Int)>] {
return [
Orientation.Zero: [(0, 0), (1, 0), (0, 1), (1, 1)],
Orientation.OneEighty: [(0, 0), (1, 0), (0, 1), (1, 1)],
Orientation.Ninety: [(0, 0), (1, 0), (0, 1), (1, 1)],
Orientation.TwoSeventy: [(0, 0), (1, 0), (0, 1), (1, 1)]
]
}
override var bottomBlocksForOrientations: [Orientation: Array<Block>] {
return [
Orientation.Zero: [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
Orientation.OneEighty: [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
Orientation.Ninety: [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]],
Orientation.TwoSeventy: [blocks[ThirdBlockIdx], blocks[FourthBlockIdx]]
]
}
}
Như vậy ta đã thực hiện xong việcn khai báo class cho khôi hình vuông. Từ đó có thể tương tự khai báo class cho 6 hình còn lại.
souce code: https://github.com/ngocthang/swift_tetris
All rights reserved