JavaRush /Blog Java /Random-VI /Mục "Trò chơi" trên JavaRush: Lý thuyết hữu ích

Mục "Trò chơi" trên JavaRush: Lý thuyết hữu ích

Xuất bản trong nhóm
Trong phần “Trò chơi” của JavaRush, bạn sẽ tìm thấy các dự án thú vị để viết các trò chơi máy tính phổ biến. Bạn có muốn tạo phiên bản của riêng mình cho các trò chơi phổ biến “2048”, “Sapper”, “Snake” và các trò chơi khác không? Nó đơn giản. Chúng tôi đã biến việc viết trò chơi thành một quy trình từng bước. chươngĐể thử sức mình với tư cách là một nhà phát triển trò chơi, bạn không cần phải là một lập trình viên nâng cao nhưng vẫn cần có một số kiến ​​​​thức Java nhất định. Ở đây bạn sẽ tìm thấy thông tin hữu ích khi viết trò chơi .

1. Thừa kế

Làm việc với công cụ trò chơi JavaRush liên quan đến việc sử dụng tính kế thừa. Nhưng nếu bạn không biết nó là gì thì sao? Một mặt, bạn cần hiểu chủ đề này: nó được học ở cấp độ 11 . Mặt khác, động cơ được thiết kế có chủ đích rất đơn giản, vì vậy bạn có thể làm được điều đó nếu chỉ có kiến ​​thức sơ sài về kế thừa. Vậy thừa kế là gì? Nói một cách đơn giản, kế thừa là mối quan hệ giữa hai lớp. Một trong số họ trở thành cha mẹ và người thứ hai trở thành con (lớp kế thừa). Trong trường hợp này, lớp cha thậm chí có thể không biết rằng nó có các lớp con. Những thứ kia. nó không nhận được bất kỳ lợi ích cụ thể nào từ sự hiện diện của các lớp kế thừa. Nhưng sự kế thừa mang lại nhiều lợi ích cho lớp con cháu. Và cái chính là tất cả các biến và phương thức của lớp cha đều xuất hiện trong lớp con, như thể mã của lớp cha được sao chép vào lớp con. Điều này không hoàn toàn đúng, nhưng nó sẽ có tác dụng để hiểu đơn giản hơn về sự kế thừa. Dưới đây là một số ví dụ để hiểu rõ hơn về kế thừa. Ví dụ 1: sự kế thừa đơn giản nhất.
public class Родитель {

}
Lớp Con kế thừa từ lớp Cha bằng từ khóa mở rộng .
public class Потомок extends Родитель {

}
Ví dụ 2: Sử dụng biến lớp cha.
public class Родитель {

   public int age;
   public String name;
}
Lớp Child có thể sử dụng các biến tuổitên của lớp Parent như thể chúng đã được khai báo trong đó.
public class Потомок extends Родитель {

   public void printInfo() {

     System.out.println(name+" "+age);
   }
}
Ví dụ 3: Sử dụng các phương thức của lớp cha.
public class Родитель {

   public int age;
   public String name;

   public getName() {
      return name;
   }
}
Lớp Child có thể sử dụng các biến và phương thức của lớp Parent như thể chúng được khai báo trong đó. Trong ví dụ này chúng ta sử dụng phương thức getName ().
public class Потомок extends Родитель {

   public void printInfo() {

     System.out.println(getName()+" "+age);
   }
}
Lớp Descendant trông như thế này theo quan điểm của trình biên dịch:
public class Потомок extends Родитель {

   public int age; //  унаследованная переменная
   public String name; //  унаследованная переменная

   public getName() { //  унаследованный метод.
      return name;
  }
   public void printInfo() {

     System.out.println(getName()+" "+age);
   }
}

2. Ghi đè phương thức

Đôi khi có những tình huống chúng ta kế thừa lớp Descendant từ một lớp Parent rất hữu ích nào đó, cùng với tất cả các biến và phương thức, nhưng một số phương thức không hoạt động chính xác theo cách chúng ta muốn. Hoặc hoàn toàn không theo cách chúng ta không muốn. Phải làm gì trong tình huống này? Chúng ta có thể ghi đè một phương thức mà chúng ta không thích. Việc này được thực hiện rất đơn giản: trong lớp Descendant, chúng ta chỉ cần khai báo một phương thức có cùng chữ ký (tiêu đề) với phương thức của lớp Parent và viết mã của chúng ta vào đó. Ví dụ 1: Ghi đè phương thức.
public class Родитель {

   public String name;

   public void setName (String nameNew) {
       name = nameNew;
  }

   public getName() {
      return name;
  }
}
Phương thức printInfo() sẽ in ra cụm từ "Luke, No!!!"
public class Потомок extends Родитель {

   public void setName (String nameNew) {
       name = nameNew + ",No!!!";
  }

   public void printInfo() {

      setName("Luke");
      System.out.println( getName());
   }
}
Lớp Descendant trông như thế này theo quan điểm của trình biên dịch:
public Потомок extends Родитель {

   public String name; //  унаследованная переменная

   public void setName (String nameNew) { //  Переопределенный метод взамен унаследованного

       name = nameNew + ", No!!!";
   }
   public getName() { //  унаследованный метод.

      return name;
   }
   public void printInfo() {

     setName("Luke");
     System.out.println(getName());
   }
}
Ví dụ 2: một chút phép thuật về kế thừa (và ghi đè phương thức).
public class Родитель {

   public getName() {
      return "Luke";
  }
   public void printInfo() {

     System.out.println(getName());
   }
}
public class Потомок extends Родитель {

   public getName() {
      return "I'm your father, Luke";
  }
}
Trong ví dụ này: nếu một phương thức printInfo(từ lớp Parent) không bị ghi đè trong lớp Descendant, khi phương thức này được gọi trên một đối tượng của lớp Descendant, thì phương thức của nó sẽ được gọi getName()chứ không phải getName()lớp Parent.
Родитель parent = new Родитель ();
parent.printnInfo();
Mã này hiển thị dòng chữ "Luke" trên màn hình .
Потомок child = new Потомок ();
child.printnInfo();
Mã này hiển thị dòng chữ "Tôi là cha của bạn, Luke;" .
Lớp Descendant trông như thế này theo quan điểm của trình biên dịch:
public class Потомок extends Родитель {

   public getName() {
      return "I'm your father, Luke";
   }
   public void printInfo() {

     System.out.println(getName());
   }
}

3. Danh sách

Nếu bạn chưa gặp Danh sách, đây là phần tóm tắt nhanh. Bạn có thể tìm thấy thông tin đầy đủ về cấp độ 6-7 của khóa học JavaRush . Danh sách có nhiều điểm chung với mảng:
  • có thể lưu trữ nhiều dữ liệu thuộc một loại nhất định;
  • cho phép bạn truy xuất các phần tử theo chỉ mục/số của chúng;
  • chỉ số phần tử bắt đầu từ 0.
Ưu điểm của danh sách: Không giống như mảng, danh sách có thể thay đổi kích thước một cách linh hoạt. Ngay sau khi tạo, danh sách có kích thước bằng 0. Khi bạn thêm các phần tử vào danh sách, kích thước của nó sẽ tăng lên. Ví dụ về tạo danh sách:
ArrayList<String> myList = new ArrayList<String>(); // создание нового списка типа ArrayList
Giá trị trong dấu ngoặc nhọn là loại dữ liệu mà danh sách có thể lưu trữ. Dưới đây là một số phương pháp để làm việc với danh sách:
Mã số Mô tả ngắn gọn về chức năng của mã
ArrayList<String> list = new ArrayList<String>(); Tạo danh sách chuỗi mới
list.add("name"); Thêm một phần tử vào cuối danh sách
list.add(0, "name"); Thêm một phần tử vào đầu danh sách
String name = list.get(5); Lấy một phần tử theo chỉ mục của nó
list.set(5, "new name"); Thay đổi phần tử theo chỉ mục của nó
int count = list.size(); Lấy số phần tử trong một danh sách
list.remove(4); Xóa một mục khỏi danh sách
Bạn có thể tìm hiểu thêm về danh sách từ các bài viết sau:
  1. Lớp danh sách mảng
  2. Làm việc với ArrayList trong ảnh
  3. Xóa một phần tử khỏi ArrayList

4. Mảng

Ma trận là gì? Ma trận không gì khác hơn là một bảng hình chữ nhật có thể chứa đầy dữ liệu. Nói cách khác, nó là một mảng hai chiều. Như bạn có thể đã biết, mảng trong Java là đối tượng. Kiểu mảng một chiều tiêu chuẩn inttrông như thế này:
int [] array = {12, 32, 43, 54, 15, 36, 67, 28};
Hãy tưởng tượng điều này một cách trực quan:
0 1 2 3 4 5 6 7
12 32 43 54 15 36 67 28
Dòng trên cùng cho biết địa chỉ ô. Tức là để lấy số 67, bạn cần truy cập vào phần tử mảng có chỉ số 6:
int number = array[6];
Mọi thứ ở đây rất đơn giản. Mảng hai chiều là mảng của các mảng một chiều. Nếu đây là lần đầu tiên bạn nghe về điều này, hãy dừng lại và hình dung nó trong đầu. Mảng hai chiều trông giống như thế này:
0 Mảng một chiều Mảng một chiều
1 Mảng một chiều
2 Mảng một chiều
3 Mảng một chiều
4 Mảng một chiều
5 Mảng một chiều
6 Mảng một chiều
7 Mảng một chiều
Trong mã:
int [][] matrix = {
{65, 99, 87, 90, 156, 75, 98, 78}, {76, 15, 76, 91, 66, 90, 15, 77}, {65, 96, 17, 25, 36, 75, 54, 78}, {59, 45, 68, 14, 57, 1, 9, 63}, {81, 74, 47, 52, 42, 785, 56, 96}, {66, 74, 58, 16, 98, 140, 55, 77}, {120, 99, 13, 90, 78, 98, 14, 78}, {20, 18, 74, 91, 96, 104, 105, 77} }
0 0 1 2 3 4 5 6 7
65 99 87 90 156 75 98 78
1 0 1 2 3 4 5 6 7
76 15 76 91 66 90 15 77
2 0 1 2 3 4 5 6 7
65 96 17 25 36 75 54 78
3 0 1 2 3 4 5 6 7
59 45 68 14 57 1 9 63
4 0 1 2 3 4 5 6 7
81 74 47 52 42 785 56 96
5 0 1 2 3 4 5 6 7
66 74 58 16 98 140 55 77
6 0 1 2 3 4 5 6 7
120 99 13 90 78 98 14 78
7 0 1 2 3 4 5 6 7
20 18 74 91 96 104 105 77
Để lấy giá trị 47, bạn cần truy cập phần tử ma trận tại [4] [2].
int number = matrix[4][2];
Nếu bạn để ý thì tọa độ ma trận khác với hệ tọa độ hình chữ nhật cổ điển (hệ tọa độ Descartes). Khi truy cập vào một ma trận, bạn chỉ định y trước rồi đến x , trong khi trong toán học, người ta thường chỉ định x(x, y) trước. Bạn có thể tự hỏi: “Tại sao không đảo ngược ma trận trong trí tưởng tượng của bạn và truy cập các phần tử theo cách thông thường thông qua (x, y)? Điều này sẽ không thay đổi nội dung của ma trận.” Vâng, sẽ không có gì thay đổi. Nhưng trong thế giới lập trình, người ta thường gọi ma trận ở dạng “đầu tiên là y, sau đó là x”. Điều này phải được coi là đương nhiên. Bây giờ hãy nói về việc chiếu ma trận lên công cụ của chúng ta (class Game). Như bạn đã biết, động cơ có nhiều phương pháp thay đổi các ô của sân chơi tại tọa độ nhất định. Ví dụ setCellValue(int x, int y, String value): . Nó đặt một ô nhất định có tọa độ (x, y) thành giá trị value. Như bạn đã nhận thấy, phương pháp này trước tiên lấy chính xác x, như trong hệ tọa độ cổ điển. Các phương pháp động cơ còn lại hoạt động theo cách tương tự. Khi phát triển trò chơi, thường sẽ cần phải tái tạo trạng thái của ma trận trên màn hình. làm như thế nào? Đầu tiên, trong một vòng lặp, bạn cần lặp qua tất cả các phần tử của ma trận. Thứ hai, đối với mỗi phương thức, hãy gọi một phương thức để hiển thị với tọa độ ĐẢO NGƯỢC. Ví dụ:
private void drawScene() {
    for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length; j++) {
            setCellValue(j, i, String.valueOf(matrix[i][j]));
        }
    }
}
Đương nhiên, sự đảo ngược hoạt động theo hai hướng. setCellValueBạn có thể chuyển (i, j) cho phương thức , nhưng đồng thời lấy phần tử [j][i] từ ma trận. Việc đảo ngược có vẻ hơi khó khăn nhưng đó là điều cần lưu ý. Và luôn luôn, nếu có bất kỳ vấn đề nào phát sinh, bạn nên lấy một tờ giấy bằng bút, vẽ ma trận và mô phỏng lại những quá trình đang xảy ra với nó.

5. Số ngẫu nhiên

Làm thế nào để làm việc với một trình tạo số ngẫu nhiên? Lớp Gameđịnh nghĩa một phương thức getRandomNumber(int). Bên trong, nó sử dụng một lớp Randomtừ gói java.util, nhưng điều này không thay đổi nguyên tắc làm việc với trình tạo số ngẫu nhiên. getRandomNumber(int)Lấy một số nguyên làm đối số . Con số này sẽ là giới hạn trên mà trình tạo có thể trả về. Giới hạn dưới là 0. Quan trọng! Trình tạo sẽ KHÔNG BAO GIỜ trả về số giới hạn trên. Ví dụ: nếu được gọi getRandomNumber(3)ngẫu nhiên, nó có thể trả về 0, 1, 2. Như bạn thấy, nó không thể trả về 3. Việc sử dụng máy phát điện này khá đơn giản nhưng lại rất hiệu quả trong nhiều trường hợp. Bạn cần lấy một số ngẫu nhiên trong một số giới hạn: Hãy tưởng tượng rằng bạn cần một số có ba chữ số (100..999). Như bạn đã biết, số tối thiểu được trả về là 0. Vì vậy, bạn sẽ cần thêm 100. Nhưng trong trường hợp này, bạn cần cẩn thận để không vượt quá giới hạn trên. Để lấy 999 làm giá trị ngẫu nhiên tối đa, bạn nên gọi phương thức getRandomNumber(int)với đối số 1000. Nhưng chúng ta nhớ về phép cộng 100 tiếp theo: điều này có nghĩa là giới hạn trên phải được hạ xuống 100. Nghĩa là, mã để lấy số có ba chữ số ngẫu nhiên sẽ trông như thế này:
int number = 100 + getRandomNumber(900);
Nhưng để đơn giản hóa quy trình như vậy, công cụ cung cấp một phương thức getRandomNumber(int, int)lấy số tối thiểu để trả về làm đối số đầu tiên. Sử dụng phương pháp này, ví dụ trước có thể được viết lại:
int number = getRandomNumber(100, 1000);
Các số ngẫu nhiên có thể được sử dụng để thu được một phần tử mảng ngẫu nhiên:
String [] names = {"Andrey", "Валентин", "Сергей"};
String randomName = names[getRandomNumber(names.length)]
Kích hoạt các sự kiện nhất định với một xác suất nhất định. Buổi sáng của một người bắt đầu theo những tình huống có thể xảy ra: Ngủ quên – 50%; Thức dậy đúng giờ – 40%; Dậy sớm hơn một giờ so với dự kiến ​​– 10%. Hãy tưởng tượng rằng bạn đang viết một chương trình mô phỏng buổi sáng của con người. Bạn cần kích hoạt các sự kiện với một xác suất nhất định. Để làm điều này, một lần nữa, bạn cần sử dụng trình tạo số ngẫu nhiên. Việc triển khai có thể khác nhau, nhưng cách đơn giản nhất nên tuân theo thuật toán sau:
  1. chúng tôi đặt ra giới hạn trong đó số lượng cần được tạo;
  2. tạo ra một số ngẫu nhiên;
  3. Chúng tôi xử lý số kết quả.
Vì vậy, trong trường hợp này, giới hạn sẽ là 10. Hãy gọi phương thức getRandomNumber(10)và phân tích những gì nó có thể trả về cho chúng ta. Nó có thể trả về 10 chữ số (từ 0 đến 9) và mỗi chữ số có cùng xác suất - 10%. Bây giờ chúng ta cần kết hợp tất cả các kết quả có thể xảy ra và kết hợp chúng với các sự kiện có thể xảy ra. Có thể có rất nhiều sự kết hợp, tùy theo trí tưởng tượng của bạn, nhưng âm thanh rõ ràng nhất là: “Nếu một số ngẫu nhiên nằm trong [0..4] - hãy gọi sự kiện là “Ngủ quên”, nếu số đó nằm trong [5..8] ] - “Dậy sớm” đúng giờ,” và chỉ khi con số là 9 thì “Tôi dậy sớm hơn dự kiến ​​một tiếng”. Mọi thứ rất đơn giản: trong [0..4] có 5 số, mỗi số có thể trả về với xác suất 10%, tổng cộng sẽ là 50%; trong [5..8] có 4 số và 9 là số duy nhất xuất hiện với xác suất 10%. Trong mã, toàn bộ thiết kế thông minh này trông thậm chí còn đơn giản hơn:
int randomNumber = getRandomNumber(10);
if (randomNumber < 5) {
    System.out.println("Проспал ");
} else if (randomNumber < 9) {
    System.out.println("Встал вовремя ");
} else {
    System.out.println("Встал на час раньше положенного ");
}
Nói chung, có thể có rất nhiều lựa chọn khi sử dụng số ngẫu nhiên. Tất cả chỉ phụ thuộc vào trí tưởng tượng của bạn. Nhưng chúng được sử dụng hiệu quả nhất nếu bạn cần đạt được kết quả nào đó nhiều lần. Khi đó kết quả này sẽ khác với kết quả trước đó. Tất nhiên là với một số xác suất. Đó là tất cả! Nếu bạn muốn tìm hiểu thêm về phần Trò chơi, đây là một số tài liệu hữu ích có thể trợ giúp:
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION