Giới thiệu
Tetris là một trong những trò chơi máy tính phổ biến nhất từng được tạo ra. Trò chơi gốc đã được thiết kế và lập trình bởi một lập trình viên Nga Alexey Pajitnov năm 1985. Kể từ đó, Tetris có trên hầu hết mọi nền tảng máy tính trong rất nhiều biến thể. Ngay cả điện thoại di động của tôi có một phiên bản sửa đổi của trò chơi Tetris.
Tetris còn được gọi là gaem xếp gạch hay câu đố dạng xếp khối. Trong trò chơi này, chúng ta có bảy hình dạng khác nhau được gọi là tetrominoes: S, Z, T, L, I, L đối xứng ngược và hình vuông. Mỗi hình dạng được hình thành với bốn hình vuông. Các hình dạng đang rơi xuống theo thời gian. Các đối tượng của trò chơi Tetris có thể di chuyển và xoay hình dạng, để chúng kết hợp với nhau càng nhiều càng tốt. Nếu chúng ta xoay sở tạo thành được một hàng, hàng đó bị phá hủy và chúng ta ghi điểm. Tetris cứ được chơi mãi cho đến khi chúng ta thoát hoặc thua.
Chúng ta không dùng hình ảnh cho trò chơi Tetris, chúng ta sử dụng API trong Swing để vẽ các tetrominoes. Đằng sau mỗi trò chơi, hình thành mô hình toán học. Vì vậy, nó được gọi là Tetris.
Một số ý tưởng của trò chơi.
* Chúng ta sử dụng một lớp Timer để tạo ra chu kỳ trò chơi.
* Các tetrominoes được vẽ lên (không dùng Image để tránh làm tăng dung lượng game, thích hợp cho các hệ máy di động).
* Các hình di chuyển tmột hình vuông bằng khối vuông cơ sở(không dùng bằng pixel)
* Một cách toán học thì game khi đang chơi là 1 danh sách các con số (sẽ nói rõ hơn ở phần sau nhưng sẽ hay hơn nếu bạn tự tìm hiểu và tưởng tượng ra ^^).
Tôi đã đơn giản hóa trò chơi một chút, để nó dễ dàng hơn để hiểu. Trò chơi bắt đầu ngay lập tức, sau khi được chạy (lược bỏ màn hình mở đầu). Chúng ta có thể tạm dừng trò chơi bằng cách bấm phím p. Phím Space sẽ thả mảnh Tetris ngay lập tức xuống phía dưới (tăng tốc rơi cực đại). Phím d sẽ thả mảnh xuống một dòng (nó có thể được sử dụng để tăng tốc độ rơi xuống một chút) Các trò chơi. Tốc độ trò chơi là 1 hằng số không đổi, không thể thực hiện tăng tốc. Điểm số là số dòng mà chúng ta đã phá được.
Trò chơi có 3 lớp, được liệt kê ở cuối bài.
Hướng dẫn code lớp Tetris và lớp Shape
Trong lớp Tetris, chúng ta cài đặt các thành phần như vùng hiển thị và trạng thái của trò chơi.
Hàm start() bắt đầu game ngay lập tức!
Lớp Shape chứa thông tin về một mảnh Tetris được tạo ra bất kỳ.
Mã:
enum Tetrominoes (NoShape, ZShape, SShape, LineShape,
TShape, SquareShape, LShape, MirroredLShape);
Các liệt kê Tetrominoes giữ tất cả bảy dạng Tetris. Cộng với hình dạng đặc biệt gọi là NoShape.
Mã:
public Shape () (
coords = new int [4] [2];
setShape (Tetrominoes.NoShape);
)
Đây là phương thúc khởi tạo của lớp Shape. Các mảng coords giữ tọa độ thực tế của một mảnh Tetris.
Mã:
coordsTable = new int [][][] (
((0, 0), (0, 0), (0, 0), (0, 0)),
((0, -1), (0, 0), (-1, 0), (-1, 1)),
((0, -1), (0, 0), (1, 0), (1, 1)),
((0, -1), (0, 0), (0, 1), (0, 2)),
((-1, 0), (0, 0), (1, 0), (0, 1)),
((0, 0), (1, 0), (0, 1), (1, 1)),
((-1, -1), (0, -1), (0, 0), (0, 1)),
((1, -1), (0, -1), (0, 0), (0, 1))
);
Mảng coordsTable giữ tất cả giá trị có thể có để phối hợp hiển thị của miếng Tetris của chúng ta (cần phải suy nghĩ kỹ chỗ này nha, tương tự như bài toán con mã đi tuần). Đây là một mẫu mà từ đó tất cả các mảnh coordiate lấy giá trị của chúng.
Mã:
for (int i = 0; i <4; i + +) {
for (int j = 0; j <2; + + j) {
coords [i] [j] = coordsTable [shape.ordinal ()] [i] [j];
}
}
Ở đây chúng ta đặt một dãy các giá trị coordiate từ coordsTable đến một mảng coords của một mảnh Tetris. Lưu ý việc sử dụng hàm ordinal(). Trong C ++, kiểu liệt kê mặc định như là một số nguyên. Không giống như trong C++, kiểu liệt kê của Java là hỗ trợ đầy đủ các lớp. Và hàm ordinal() trả về vị trí hiện tại của biến enum trong đối tượng liệt kê.
Các hình ảnh sau đây sẽ giúp hiểu được sự phối hợp các giá trị hơn một chút. Các mảng coords lưu toạ độ của các mảnh Tetris. Ví dụ, các số (0, -1), (0, 0), (1, 0), (1, 1), đại diện cho các kiểu xoay của một hình chữ S. Sơ đồ dưới đây minh hoạ:
Mã:
public Shape rotateLeft()
{
if (pieceShape == Tetrominoes.SquareShape)
return this;
Shape result = new Shape();
result.pieceShape = pieceShape;
for (int i = 0; i < 4; ++i) {
result.setX(i, y(i));
result.setY(i, -x(i));
}
return result;
}
Đoạn mã này xoay các mảnh qua trái. Các hình vuông không phải xoay, đó là lý do tại sao chúng tôi chỉ đơn giản tra lại tham chiếu đến đối tượng hiện hành. Nhìn vào hình ảnh trước đó sẽ giúp hiểu được chúng xoay thế nào.
Hướng dẫn code cho lớp Board:
Mã:
...
isFallingFinished = false;
isStarted = false;
isPaused = false;
numLinesRemoved = 0;
curX = 0;
curY = 0;
...
Chúng ta khởi tạo một số biến quan trọng. Biến isFallingFinished xác định nếu hình dạng Tetris hoàn thành việc rơi xuống thì sau đó chúng ta cần phải tạo một hình mới. NumLinesRemoved đếm số dòng chúng ta đã loại bỏ (ăn điểm) cho đến nay. curX và curY xác định vị trí thực tế của hình Tetris đang rơi.
Chúng ta dứt khoát phải gọi phương thức setFocusable() để phần hiển thị của chúng ta có thể nhận được các phím nhấn.
Mã:
timer = new Timer (400, this);
timer.start ();
Đối tượng Timer thực hiện một hoặc nhiều sự kiện sau khi một thời gian trễ chỉ định (Interval). Trong trường hợp của chúng ta, là gọi phương thức actionPerformed() mỗi 400 ms.
Mã:
public void actionPerformed (ActionEvent e) {
if (isFallingFinished) {
isFallingFinished = false;
newPiece ();
} else {
oneLineDown ();
}
}
Hàm actionPerformed() kiểm tra nếu việc rơi xuống đã hoàn tất. Nếu đúng, một mảnh mới sẽ được tạo ra. Nếu không, những mảnh Tetris rơi xuống một dòng.
Bên trong hàm paint(), chúng ta vẽ ra tất cả các đối tượng ở trên bảng hiển thị (về sau sẽ gọi là board).
Mã:
for (int i = 0; i <boardheight; i="" +="" +)="" {<br=""> for (int j = 0; j <boardwidth; +="" j)="" {<br=""> Tetrominoes shape = shapeAt (j, BoardHeight - i - 1);
if (shape! = Tetrominoes.NoShape)
drawSquare (g, 0 + j * squareWidth (),
boardTop + i * squareHeight (), shape);
}
}
Trong bước đầu tiên chúng ta vẽ tất cả các hình dạng, hay những phần còn sót lại của 1 hình dạng đã rơi xuống tới dưới cùng. Tất cả các khối vuông đã được ghi nhớ trong mảng board. Chúng ta truy cập nó bằng cách sử dụng shapeAt().
Mã:
if (curPiece.getShape () = Tetrominoes.NoShape!) {
for (int i = 0; i <4; i + +) {
int x = curX + curPiece.x (i);
int y = curY - curPiece.y (i);
drawSquare (g, 0 + x * squareWidth (),
boardTop + (BoardHeight - y - 1) * squareHeight (),
curPiece.getShape ());
}
}
Trong bước thứ hai, chúng ta vẽ mảnh thực sự đang rơi.
Mã:
private void dropDown()
{
int newY = curY;
while (newY > 0) {
if (!tryMove(curPiece, curX, newY - 1))
break;
--newY;
}
pieceDropped();
}
Nếu chúng ta bấm phím Space, mảnh rơi ngay xuống dưới. Chúng ta chỉ đơn giản là cố gắng thả 1 mảnh xuống từng dòng cho đến khi nó đạt đến đáy hoặc trên một mảnh Tetris trước đó.
Mã:
private void clearBoard ()
{
for (int i = 0; i board [i] = Tetrominoes.NoShape;
}
Hàm clearBoard() lấp đầy board bằng những hình NoShapes. Việc này sau đó được sử dụng để phát hiện va chạm.
Mã:
private void pieceDropped ()
{
for (int i = 0; i <4; i + +) {
int x = curX + curPiece.x (i);
int y = curY - curPiece.y (i);
board [(y * BoardWidth) + x] = curPiece.getShape ();
}
removeFullLines ();
if (! isFallingFinished)
newPiece ();
}
Hàm pieceDropped() đặt các mảnh rơi vào mảng board. Một lần nữa, board lưu giữ tất cả các khối vuông của mảnh và những phần còn sót lại của những mảnh đã rơi xuống trước đó. Khi đã hoàn tất rơi xuống, đó là thời gian để kiểm tra, nếu chúng ta có thể loại bỏ một số đường thẳng trên board. Đây là công việc của hàm removeFullLines (). Sau đó, chúng ta tạo ra một mảnh mới. Chính xác hơn, chúng ta cố gắng tạo ra một mảnh mới.
Mã:
private void newPiece ()
{
curPiece.setRandomShape ();
curX = BoardWidth / 2 + 1;
curY = BoardHeight - 1 + curPiece.minY ();
if (tryMove (curPiece, curX, curY!)) {
curPiece.setShape (Tetrominoes.NoShape);
timer.stop ();
isStarted = false;
statusbar.setText ( "Game over");
}
}
Hàm newPiece() tạo ra một mảnh Tetris mới. Các mảnh được tạo hình ngẫu nhiên dựa trên những hình cơ bản. Sau đó chúng ta tính toán curX ban đầu và curY. Nếu chúng ta không thể di chuyển đến vị trí ban đầu, trò chơi kết thúc. Giờ được ngừng lại. Chúng ta đặt lại chuỗi trên thanh statusbar.
Mã:
private boolean tryMove (Shape newPiece, int newX, int newY)
{
for (int i = 0; i <4; i + +) {
int x = newX + newPiece.x (i);
int y = newY - newPiece.y (i);
if (x <0 | | x> = BoardWidth | | y <0 | | y> = BoardHeight)
return false;
if (shapeAt (x, y) = Tetrominoes.NoShape!)
return false;
}
curPiece = newPiece;
curX = newX;
curY = newY;
repaint ();
return true;
}
Hàm tryMove () cố gắng di chuyển các mảnh Tetris. Trả về false, nếu nó đụng biên của board hay những mảnh đã rơi xuống trước đó.
Mã:
for (int i = BoardHeight - 1; i> = 0; - i) {
boolean lineIsFull = true;
for (int j = 0; j <boardwidth; +="" j)="" {<br=""> if (shapeAt (j, i) == Tetrominoes.NoShape) (
lineIsFull = false;
break;
}
}
if (lineIsFull) {
++numFullLines;
for (int k = i; k < BoardHeight - 1; ++k) {
for (int j = 0; j < BoardWidth; ++j)
board [(k * BoardWidth) + j] = shapeAt (j, k + 1);
}
}
}
Bên trong hàm removeFullLines (), chúng ta kiểm tra nếu có bất kỳ hàng nào đủ điều kiện để ghi điểm trong số tất cả các hàng của board. Nếu có ít nhất một dòng đầy đủ, nó được loại bỏ. Sau khi tìm thấy một dòng đầy đủ, chúng ta tăng biến đếm. Chúng tôi di chuyển tất cả các dòng ở trên dòng đó xuóng một dòng. Bằng cách này chúng ta tiêu diệt 1 dòng đầy đủ.
Mỗi mảnh Tetris có bốn hình vuông. Mỗi ô vuông được vẽ bằng hàm drawSquare (). Mỗi mảnh Tetris có màu sắc khác nhau.
Mã:
g.setColor (color.brighter ());
g.drawLine (x, y + squareHeight () - 1, x, y);
g.drawLine (x, y, x + squareWidth () - 1, y);
Bên trái và trên của một hình vuông được vẽ ra với một màu sáng hơn. Tương tự, phía dưới và bên phải được vẽ ra với các màu sắc tối hơn. Điều này mô phỏng một cạnh 3D.
Chúng ta điều khiển game với bàn phím. Các cơ chế kiểm soát được thực hiện với một KeyAdapter. Đây là một lớp bên trong đó sẽ nạp chồng hàm keyPressed().
Mã:
case KeyEvent.VK_RIGHT:
tryMove (curPiece, curX + 1, curY);
break;
Nếu chúng ta nhấn phím mũi tên trái, chúng ta sẽ cố gắng để di chuyển các mảnh rơi xuống một khối vuông bên trái.
=================================================
Như vậy, còn những phần như combo, hiển thị trước mảnh tiếp theo, ghi điểm hay thêm các mảnh mới là do bạn quyết định! Hãy thử đi!
Do lỗi chuyển dữ liệu nên diễn đàn đã mất source bản gốc, hiện tại chỉ có bản đã upgrade của mình, nếu bạn nào quan tâm thì down về dùng thử (có thể có lỗi ngớ ngẩn nào đó ^^)
Dịch: Shin