Pawn (ngôn ngữ lập trình)

**PAWN** (tiền nhiệm là Small-C) là một ngôn ngữ lập trình *[[mã nguồn mở]]* được sử dụng trên nhiều nền tảng.

Cú pháp của ngôn ngữ gần giống với C nhưng khác kiểu. Không có con trỏ, toàn bộ các chương trình được quản lý bên trong một abstract machine (hay máy ảo) của PAWN để đảm bảo kiểm tra các chỉ số của mảng cũng như bảo vệ chương trình khỏi bị tràn bộ nhớ. Trình biên dịch Pawn tạo ra mã P (hoặc "bytecode").

## Công dụng
Pawn là ngôn ngữ được điều chỉnh phù hợp với các máy có tài nguyên hạn chế, nó đã được dùng trong một số kỹ thuật nhúng như của vệ tinh ESTCube-1 hoặc phổ biến hơn là trong các máy chủ trò chơi SA-MP (*San Andreas - Multiplayer*).

## Phần tử trong PAWN
Giống như bất kỳ ngôn ngữ lập trình khác, PAWN cũng có những đặc tính riêng.

```pawn
// main() là hàm chính của mọi chương trình, nó sẽ được thực thi đầu tiên
main()
{
  // In ra màn hình dòng chữ "Hello world !"
  print("Hello world !");
}
```


### Biến
Kiểu của các biến trong PAWN hạn chế hơn so với C và cách khai báo của chúng cũng khác.

Để khai báo một biến, chúng ta sử dụng từ khóa "new" theo sau là tên của biến.

Nếu không có xác định kiểu, mặc định biến sẽ được xem như kiểu số nguyên.

Nên khởi tạo biến mà chúng ta khai báo để chúng không chứa giá trị lỗi ngay cả khi mặc định đã gán giá trị cho chúng.


```pawn
main()
{
   new Float:pi = 3.14;
   printf("Gia tri gan nhat cua pi la %f", pi);
}
```


### Cấu trúc điều kiện
Chúng ta sẽ phân biệt hai cấu trúc điều khiển chính: "if" cũng như "switch".

Dạng đầu tiên của cấu trúc này được định nghĩa trong một so sánh điều kiện đơn giản dưới đây:

```pawn
hand()
{
  new a = -4;
  if(a < 0)
  {
    printf("So am! (%d)", a);
  }
}
```


Tuy nhiên, chúng ta cũng có thể xử lý phủ định của biểu thức trước đó bằng từ khóa "else".

```pawn
hand()
{
  new a = -4;
  if(a < 0)
  {
    printf("So am! (%d)", a);
  }
  else
  {
    printf("So duong! (%d)", a);
  }
}
```


Từ khóa cuối cùng cho phép kiểm tra một biểu thức nếu biểu thức trước đó trả về giá trị "false": "else if".

```pawn
hand()
{
  new a = -4;
  if(a < 0)
  {
    printf("So am! (%d)", a);
  }
  else if(a == 0)
  {
    printf("So 0!");
  }
  else
  {
    printf("So duong! (%d)", a);
  }
}
```

Lưu ý: bằng cách sử dụng "else" ở cuối, tôi kiểm tra xem một trong các điều kiện trước nó đã được thỏa mãn hay không.

Cấu trúc điều kiện luôn bắt đầu bằng "if" trong mọi trường hợp.

#### Cấu trúc "switch"
Cấu trúc điều kiện "switch" được sử dụng trong trường hợp bạn phải kiểm tra lần lượt các giá trị của một biến. Bạn có thể sử dụng cấu trúc "if" và "else if" để có công dụng tương tự.

Tuy nhiên, cấu trúc "switch" sẽ phù hợp hơn trong trường hợp này. Thay vì sử dụng nhiều lệnh "else if", chúng ta sẽ sử dụng từ khóa "case" để kiểm tra giá trị của biến.

```pawn
hand()
{
  new a = 3;
  switch(a)
  {
    case 1:
    {
      printf("Bien 'a' chua gia tri 1");
    }
    case 0, -1:
    {
      printf("Bien 'a' chua gia tri 0 hoac -1");
    }
    case 2..5:
    {
      printf("Bien 'a' chua gia tri tu 2 den 5");
    }
    default:
    {
      printf("Bien 'a' chua gia tri khac voi cac gia tri da kiem tra truoc do");
    }
  }
}
```


### Cấu trúc lặp
Cấu trúc lặp (hoặc vòng lặp) lặp lại một hoặc nhiều lệnh một số lần nhất định.

Chúng ta cần phân biệt hai cấu trúc lặp chính, mỗi cấu trúc này được đặc trưng bởi số lần lặp xác định.

#### Vòng lặp "for"
Vòng lặp "for" sẽ thực thi các lệnh lồng nhau một số lần nhất định.

Trong một vòng lặp như vậy, chúng ta thường sử dụng một *bước nhảy*. Nó đặc biệt hữu ích khi một lệnh phải được lặp lại nhiều lần và ta cần một cách để kiểm soát số lần lặp.


```pawn
hand()
{
  new sum = 0;
  for(new i = 0; i < 10; i++)
  {
    sum += i; // tương đương với sum = sum + i;
  }
  printf("Tong cua 10 so nguyen dau tien la %d.", sum);
  /*
    Ở đây chúng ta tính tổng của 10 số nguyên đầu tiên.
  */
}
```


#### Vòng lặp "while"
Vòng lặp "while" là một vòng lặp không xác định.

Vòng lặp này thường được đặt với một điều kiện không để kiểm tra số lần lặp.

Vòng lặp này không nhất thiết phải có một *bước nhảy* như vòng lặp "for".

```pawn
hand()
{
  new nb = 17;
  while(nb > 0)
  {
    nb /= 2; // tương đương với nb = nb / 2;
  }
  /*
    Chia số nb cho 2 liên tục cho đến khi nó bằng 0 thì thoát khỏi vòng lặp.
  */
}
```


#### Vòng lặp "do..while"
Vòng lặp "do..while" cũng không xác định.

Khác với vòng lặp "while", vòng lặp "do..while" sẽ thực thi lệnh trước khi kiểm tra điều kiện lần đầu tiên.

Điều này đảm bảo sẽ luôn có ít nhất một lần lặp. Nó đặc biệt được sử dụng trong kiểm tra đầu vào người dùng.

```pawn
/* Chương trình tính tổng N số nguyên dương đầu tiên (N được nhập bởi người dùng) */
hand()
{
  new n_integers = 0, sum = 0;
  /* Kiểm tra đầu vào người dùng */
  do
  {
    printf("Nhap so nguyen cuoi cung (so > 1): ");
    n_integers = getvalue(); // hàm getvalue() trả về giá trị số nguyên được nhập bởi người dùng trong console
  }
  while(n_integers <= 1);

  /* Tính tổng các số nguyên liên tiếp đến n_integers */
  for(new i = 0; i <= n_integers; i++) sum += i;

  /* Hiển thị kết quả */
  printf("Tong cac so nguyen tu 1 den %d la %d", n_integers, sum);
}
```


### Hàm / thủ tục
Trong thuật toán, chúng ta thường phân biệt hàm của thủ tục bằng sự tồn tại hay không của giá trị trả về. Trong C, chúng ta phân biệt bằng từ khóa đặt trước định nghĩa hàm (void hoặc một kiểu dữ liệu).
Trong PAWN, hai khái niệm này thường bị nhầm lẫn vì nó không phân biệt rõ ràng.

#### Khai báo một hàm
Khai báo một hàm trong PAWN được thực hiện như sau:

```pawn
hand()
{
  /* Nhập giá trị từ người dùng */
  new a = getvalue();
  new b = getvalue();
  /* Hiển thị kết quả */
  printf("Nhan a voi b se tra ve: %d", multiply(a, b)); // gọi hàm đã tạo
}
multiply(a, b) // nhân số nguyên a với b
{
  return a * b; // ở đây chúng ta gọi từ khóa return để gán giá trị trả về cho hàm.
}
```


Tương tự, nếu bạn muốn trả về dữ liệu của một kiểu khác ngoài số nguyên, bạn chỉ cần xác định kiểu trước khi khai báo:

```pawn
bool:AreEquals(a, b) // hàm này trả về true hoặc false
{
  return a == b; // trả về giá trị của phép so sánh bằng
}
```


Tuy nhiên, sẽ cần phải xác định kiểu dữ liệu mà hàm trả về nếu nó khác với kiểu mặc định (số nguyên). Do đó, bạn sẽ cần tạo ra một tiêu đề (header) của hàm trước khi gọi nó. Chúng ta sẽ có:

```pawn
forward bool:AreEquals(a, b); // tạo một tiêu đề bằng cách gọi từ khóa forward

hand()
{
  new a = 5, b = 3;
  printf("A va B co bang nhau? %b\n", AreEquals(a, b));
}
bool:AreEquals(a, b)
{
  return a == b;
}
```


#### Truyền tham số
Một hàm có thể (hoặc không) truyền đối số trong khai báo hàm của nó.
Ngôn ngữ PAWN cung cấp hai kiểu truyền tham số cho hàm.

Kiểu đầu tiên là truyền tham số theo giá trị, nó sẽ tạo ra một bản sao của biến được truyền làm tham số khi gọi hàm.
Giá trị của biến bản sao không thể ảnh hưởng đến giá trị của biến ban đầu.

```pawn
hand()
{
  new a = 2;
  square(a);
  printf("a hien tai la: %d", a); // a sẽ vẫn có giá trị 2 với kiểu truyền tham số này
}
square(value)
{
  value *= value; // không trả về kết quả ở đây, chúng ta cố gắng thay đổi giá trị của biến gốc.
}
```

Kiểu truyền tham số này được ưu tiên cho việc xử lý giá trị của một biến hoặc chỉ đọc giá trị của nó.

Tuy nhiên, nếu muốn thay đổi giá trị của biến được truyền làm tham số, chúng ta sẽ phải sử dụng kiểu truyền tham số thứ hai: theo tham chiếu.
Kiểu truyền tham số này là thay vì truyền giá trị của biến để tạo ra một biến khác, thì sẽ truyền địa chỉ bộ nhớ của nó, từ đó cho phép thay đổi trực tiếp biến trong bộ nhớ.

```pawn
hand()
{
  new a = 2;
  square(a);
  printf("a hien tai la: %d", a); // a sẽ có giá trị 4 sau khi thay đổi kiểu truyền tham số
}
square(&value) // dùng dấu & để truyền tham số theo tham chiếu
{
  value *= value;
}
```

Kiểu truyền tham số này được ưu tiên khi chúng ta cần phải trả về nhiều giá trị từ một hàm (ví dụ: chuyển đổi giây thành định dạng hh-mm-ss).

## Tài liệu tham khảo