[Game Maker Adventure] Chương 014: Array – Mảng

Array là một công cụ quan trọng nhờ đặc tính quản lí được một dãy phần tử, các bài toán tối ưu, thuật toán sắp xếp...

Tóm tắt chương trước

Bạn cùng Kurobo khám phá sâu vào phế tích.

Trong phế tích nguy hiểm, Kurobo đã gợi ý bạn viết một chương trình điều khiển hành vi của Kurobo trong trận chiến.

Kiến thức chương này

  • Array là một tập hợp danh sách các biến có cùng kiểu.
  • Vị trí (hay phần tử) thứ n của một array sẽ tương ứng với index [n-1].
  • Để xem trong array có bao nhiêu phần tử ta cần sử dụng property Length.
  • Để sao chép Array, ta không thể dùng dấu gán.

Cốt truyện

“Cuối cùng cũng hoàn thành!!!”

“Chúc mừng Chủ nhân đã hoàn thành được 50% quãng đường!”

“…”

“?”

“50%?”

“Đúng vậy, chủ nhân quên mình phải thiết lập cho Kurobo sao?”

“À…”

Array (mảng) là gì?

Hãy giả sử bạn cần lưu một danh sách top những người chơi giỏi nhất trong game của mình.

Thông thường, mỗi vị trí sẽ cần một biến điểm và một biến tên.

int ptsTop1;
int ptsTop2;
int ptsTop3;
//...
int ptsTop100;

string nameTop1;
string nameTop2;
string nameTop3;
//...
string nameTop100;

Với một bảng xếp hạng (BXH) dài 100 trăm người, chẳng lẽ phải tạo ra 200 biến tương ứng?

Sẽ ra sao nếu ta muốn có BXH cho toàn server?

Thật may, Array là một trong những công cụ giúp ta xử lí vấn đề đó.

Array là một tập hợp danh sách các biến có cùng kiểu. Chẳng hạn, ta có thể viết gọn đoạn code trên lại như sau:

int[] ptsTop = new int[100];
string[] nameTop = new string[100];

Hàng trăm dòng giờ đã được rút gọn thành 2 dòng!

*Khai báo Array

Khai báo – tức declare – là quá trình thiết lập và đặt tên vùng nhớ

*Kurobo nhắc lại chương 3

Thông thường, array được khai báo theo công thức:

<<type>>[] + <<arrayName>> = new + <<type>>[<<số lượng phần tử>>];

Trong đó:

<<type>> là kiểu dữ liệu của các biến trong Array. Array chỉ có thể lưu một danh sách biến có cùng kiểu. Một Array kiểu int[] sẽ không thể lưu các biến khác int như string, float, bool…

<<arrayName>> là tên của Array, cách đặt tên giống cách đặt tên biến.

new là từ khoá bắt buộc khi khai báo các type phức tạp như array, list, struct, class… new có tác dụng tạo một vùng nhớ mới với độ lớn X phần tử.

<<số lượng phần tử>> là số lượng phần tử có trong Array. Chẳng hạn int[] scores = new int[10]; nghĩa là array scores chứa 10 phần tử kiểu int.

Tại thời điểm này, C# sẽ tạo trước 10 vị trí nhớ dành cho array scores. Các vị trí nhớ được tạo đó sẽ mang trong mình các giá trị mặc định. Về bản chất, ngay tại bước này array đã được khởi tạo.

Ngoài ra, khi một array đã được khai báo, ta không thể thay đổi số lượng phần tử chứa trong nó. Tuy nhiên, ta có thể xoá toàn bộ array cũ để tạo một array mới với từ khoá new.

Chẳng hạn:

//score 10 phan tu
int[] scores = new int[10];

//score 100 phan tu
scores = new int[100];

Lúc này, C# sẽ xoá toàn bộ 10 phần tử của array ban đầu để tạo ra 100 phần tử mới. Chúng ta sẽ nghiên cứu sâu hơn phần này khi vào các chương thuật toán.

*Khởi tạo Array

Khởi tạo – tức initializing – là hành động gán giá trị cho biến

*Kurobo nhắc lại chương 3

Để gán giá trị cho từng vị trí nhớ hay từng phần tử của Array, ta cần sử dụng indexer operator ([ ] – đóng mở ngoặc vuông). Sau đó thực hiện gán (=) giống như biến thông thường.

Thí dụ:

int[] scores = new int[10];

scores[0] = 9928;
scores[1] = 9102;

Nằm giữa 2 dấu ngoặc vuông được gọi là index. Index là số chỉ ra vị trí vùng nhớ trong Array.

Vị trí đầu tiên của array được đánh index bằng 0.

Vị trí thứ 2 có index là 1.

Vị trí thứ 3 có index là 2.

Cứ như vậy, vị trí (hay phần tử) thứ n của một array sẽ tương ứng với index [n-1].

Ngoài ra, ta không thể truy cập vượt quá phạm vi của một array. Chẳng hạn, array với 10 phần tử thì không thể truy cập vào vị trí thứ 100, vị trí thứ 11 được.

Khi cố gắng truy cập vượt quá phạm vi, trình biên dịch ngay lập tức báo lỗi Index Out Of Range.

Các kiểu khai báo

Dưới đây là một số kiểu khai báo array thường được sử dụng.

Style 1

int[] playerIDs = new int[5];
string[] playerNames = new string[5];

//ID
playerIDs[0] = 102;
playerIDs[1] = 51;
playerIDs[2] = 126;
playerIDs[3] = 22;
playerIDs[4] = 1112;

//Name
playerNames[0] = "A";
playerNames[1] = "B";
playerNames[2] = "C";
playerNames[3] = "D";
playerNames[4] = "E";

Đây là kiểu thường được sử dụng, bởi tính đơn giản, rõ ràng.

Style 2

int[] playerIDs = new int[] { 102, 51, 126, 22, 1112 };
string[] playerNames = new string[] { "A", "B", "C", "D", "E" };

Không cần xác định số phần tử.

Style 3

int[] playerIDs = { 1, 2, 3, 4, 5, 6 };
string[] playerNames = { "A", "B", "C", "D", "E" };

Không cần từ khoá new, không cần xác định số phần tử.

Tuỳ từng trường hợp mà bạn có thể sử dụng bất kì kiểu nào, các kiểu gán lúc khởi tạo (style 2, style 3) thường sẽ sử dụng để load config khi mới vào game (vị trí các điểm spawn, danh sách quái, bản đồ…), style 1 thường dùng trong các bài toán có tính cập nhật (BXH, Slot đồ, Buff List…).

Length

Để xem trong array có bao nhiêu phần tử ta cần sử dụng property Length.

Chẳng hạn

int[] playerIDs = { 1, 2, 3, 4, 5, 6 };
Console.WriteLine(playerIDs.Length);

Màn hình sẽ cho ra kết quả 6 do playerIDs có 6 phần tử.

Length thường được sử dụng để làm điều kiện thoát vòng loop khi muốn xét duyệt tất cả các phần tử trong array.

Thí dụ

Trong game, khi mở một chiếc hòm, ta có thể in ra màn hình danh sách các món đồ trong nó như sau:

string[] items = new string[] 
{ 
    "Key", "Bottle", "A piece of paper", "5 Gold Coin", "A rat" 
};

for (int i = 0; i < items.Length; i++)
{
    Console.WriteLine(items[i]);
}

Kết quả:

items.Length trả về số lượng phần tử trong items – tức là 5.

Index của items bắt đầu từ 0 nên index chỉ có thể mang giá trị lớn nhất là 4 (tương ứng với phần tử thứ 5 của items).

Chú ý

Trường hợp dưới đây sẽ gây lỗi Index Out Of Range.

for (int i = 0; i <= items.Length; i++)
{
    Console.WriteLine(items[i]);
}

Sử dụng loop trong mảng để xét duyệt tất cả các phần tử có trong tủ đồ.

Sao chép Array

Để sao chép biến, bạn có thể dùng dấu gán (=)

int a = 5;
int b = 1;

a = b; //a = 1

Nhưng để sao chép Array, ta không thể dùng dấu gán như vậy

int[] array1 = new int[] { 30, 40, 59 };
int[] array2 = array1;

array1[0] = 9999;

Console.WriteLine(array2[0]);

Kết quả:

9999

Rõ ràng ta chỉ thay đổi dữ liệu ở array1 nhưng tại sao array2 cũng bị thay đổi theo?

Điều này là do Array chỉ ghi nhớ vị trí dữ liệu (hay còn gọi là Reference Value – sẽ bàn sâu hơn ở những chương cuối). Khi gán array2 = array1; tức là ta chỉ gán vị trí dữ liệu của chúng.

Do đó, nếu ta thay đổi phần tử của array1 thì phần tử trong array2 cũng thay đổi theo.

Giống như việc cả hai người dùng chung một cuốn sách vậy. Nếu cuốn sách bị viết bậy thì cả hai đều ảnh hưởng.

Ta có thể sao chép array bằng cách kết hợp từ khoá new, for loop và Length:

int[] array1 = new int[] { 30, 40, 59 };
int[] array2 = new int[array1.Length];

for (int i = 0; i < array1.Length; i++)
{
    array2[i] = array1[i];
}

array1[0] = 9999;

Console.WriteLine(array2[0]);

Về cơ bản, ta tạo một array2 mới với số phần tử bằng array1. Sau đó gán tất cả các phần tử tương ứng của array1 sang array2.

Kết quả:

30

Quest: Kurobo Upgrade!

Bạn và Kurobo tìm thấy một khu vực trống trải.

Bạn nhìn chằm chằm vào những hình nhân quái vật kì dị.

Chúng không có phản ứng gì.

Dường như, đây là một bãi tập luyện. Một công đôi việc, vừa hay, bạn cũng muốn nâng cấp Kurobo để tăng sức chiến đấu cho mình.

Quest: Lại vào thế giới cổ thư! ✦✦✧✧✧

Đi được một đoạn, cuốn cổ thư đột nhiên bay từ trong túi bạn.

Bạn chưa kịp định thần thì đã bị kéo vào không gian quen thuộc ấy.

/3
You must log in to pass this quiz.

Quest: Kurobo Upgrade! ✦✦✦✦✧

“Người trở về rồi hả?”

“… so với lần trước thì Kurobo có vẻ bình tĩnh nhỉ?”

“Thì tại, Kurobo có hốt hoảng cũng không giải quyết được gì.”

“Mà, dù sao thì chủ nhân mau vào khu tập luyện thôi!”

***

Để khiến Kurobo có thể chiến đấu, ngoài thiết lập các chỉ số cơ bản, phần khó khăn nhất là khiến Kurobo tấn công kẻ địch có lượng máu thấp nhất.

Hãy viết tiếp chương trình dựa trên các gợi ý ban đầu, thoả mãn:

  1. Sử dụng chương trình gốc ban đầu để khai triển (xem bên dưới).
  2. Phe địch gồm 3 hình nhân quái vật. Phe ta gồm bạn và Kurobo.
  3. Chỉ số cơ bản của các nhân vật gồm: Máu (HP), Tốc độ (SPD) và Công (ATK). Thông số chi tiết giống hình.
  4. Trận chiến diễn ra theo lượt. Ai có chỉ số SPD cao sẽ được ưu tiên đánh trước.
  5. Khi thay thế chỉ số Tốc độ (SPD) khởi điểm, thứ tự turn order cũng phải thay đổi động theo config ban đầu.
  6. Ngay khi bắt đầu lượt, Kurobo được ưu tiên tấn công trước, không cần biết SPD của nó là bao nhiêu.
  7. Trong lượt của Kurobo, nó tự động tấn công kẻ địch ít máu (HP) nhất.
  8. Trong lượt của bạn, bạn được lựa chọn tấn công kẻ địch nào.
  9. Kẻ địch chỉ tấn công bạn (Kurobo không thể bị tấn công).
  10. Trận chiến kết thúc khi 1 trong 2 phe hoàn toàn bị hạ gục.

Chương trình gốc cần được khôi phục:

string[] enemiesName = new string[] { "Dummy A", "Dummy B", "Dummy C" };

int[] enemiesHP = new int[] { 400, 200, 500 };
int[] enemiesATK = new int[] { 40, 90, 60 };
int[] enemiesSPD = new int[] { 100, 50, 88 };

int yourHP = 600;
int yourATK = 140;
int yourSPD = 77;

int kuroboATK = 60;

bool isEnd = false;
int currentTurn = 1;

Gợi ý thành phẩm:

Xem đáp án

Premium Content

Kể từ phần này, bạn cần đăng nhập và một lượng Point để tiếp tục xem 🙁

Chương Trước | Tổng quan | Chương Kế

Kurrwo

Công nhân👷gõ 💬 văn bản ⌨️ trò chơi 🎮 điện tử 📱 👏👏👏

Post navigation

Subscribe
Notify of
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments